| /* | |
| * Copyright 2026 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/graphite/TextureFormatXferFn.h" | |
| #include "include/core/SkColorType.h" | |
| #include "include/private/SkAttributes.h" | |
| #include "include/private/SkLog.h" | |
| #include "src/core/SkAutoMalloc.h" | |
| #include "src/core/SkColorSpaceXformSteps.h" | |
| #include "src/core/SkFloatBits.h" | |
| #include "src/core/SkHalf.h" | |
| #include "src/core/SkImageInfoPriv.h" | |
| #include "src/core/SkMathPriv.h" | |
| #include "src/core/SkRasterPipeline.h" | |
| #include "src/core/SkRasterPipelineOpContexts.h" | |
| #include "src/core/SkRasterPipelineOpList.h" | |
| #include "src/core/SkSwizzlePriv.h" | |
| #include "src/core/SkVx.h" | |
| #include <functional> | |
| namespace skgpu::graphite { | |
| namespace { | |
| using TF = TextureFormat; | |
| // This is intentionally not a class enum and is distinct from FormatXferOps for two reasons: | |
| // 1. We can't use SkEnumBitmask inside template parameters for the row bit manipulation functions. | |
| // 2. It's convenient to split the FormatXferOps into specific ops based on their conversion | |
| // direction that doesn't need to be exposed in the public API. | |
| enum ExtendedFormatXferOp : uint8_t { | |
| kDropAlpha = 0x1, // FormatXferOp::kDropAlpha for CPU->GPU conversion | |
| kPadAlpha = 0x2, // FormatXferOp::kDropAlpha for GPU->CPU conversion | |
| kSwapRB = 0x4, // FormatXferOp::kSwapRB behaves the same for either conversion direction | |
| kForceOpaque = 0x8, // RGBx handling to avoid raster pipeline is the same in both directions | |
| kIgnoreSrc = 0x10, // The output value does not depend on the src, will be either 0 or 1. | |
| }; | |
| using XferRowFn = std::function<void(const char* src, char* dst, int width)>; | |
| template <typename Px, int N, typename PixelFn /* [](Vec<N,Px>, Px opaqueAlpha) -> Vec<N,Px> */> | |
| XferRowFn create_xfer_row_fn(int n, int srcBpp, int dstBpp, | |
| Px opaqueAlpha, Px zeroValue, PixelFn applyPixel) { | |
| using PxVec = skvx::Vec<N, Px>; | |
| if (srcBpp != 0) { | |
| // PxVec should be sufficient to hold N src and dst pixels, and should match at least one | |
| SkASSERT(sizeof(PxVec) >= (size_t)srcBpp*n && sizeof(PxVec) >= (size_t)dstBpp*n); | |
| SkASSERT(sizeof(PxVec) == (size_t)srcBpp*n || sizeof(PxVec) == (size_t)dstBpp*n); | |
| // NOTE: These must all be named explicitly, since using the & to auto-capture does not copy | |
| // them, so then their references become corrupted when executing the lambda later. | |
| return [n, srcBpp, dstBpp, opaqueAlpha, applyPixel](const char* src, char* dst, int width) { | |
| const int srcBppN = n * srcBpp; | |
| const int dstBppN = n * dstBpp; | |
| PxVec pixel{}; | |
| while (width >= n) { | |
| SK_UNSAFE_TODO(memcpy(&pixel, src, srcBppN)); | |
| pixel = applyPixel(pixel, opaqueAlpha); | |
| SK_UNSAFE_TODO(memcpy(dst, &pixel, dstBppN)); | |
| width -= n; | |
| SK_UNSAFE_TODO(src += srcBppN); | |
| SK_UNSAFE_TODO(dst += dstBppN); | |
| } | |
| if (width > 0) { | |
| // Process tail that is less than a full vector | |
| SkASSERT(width < n); | |
| SK_UNSAFE_TODO(memcpy(&pixel, src, width * srcBpp)); | |
| pixel = applyPixel(pixel, opaqueAlpha); | |
| SK_UNSAFE_TODO(memcpy(dst, &pixel, width * dstBpp)); | |
| } | |
| }; | |
| } else { | |
| // Ignore the source in favor of loading zeroValue into pixel each time | |
| SkASSERT(sizeof(PxVec) >= (size_t)dstBpp*n); | |
| return [n, opaqueAlpha, zeroValue, dstBpp, applyPixel](const char*, char* dst, int width) { | |
| const int dstBppN = n * dstBpp; | |
| PxVec pixel{}; | |
| while (width >= n) { | |
| pixel = zeroValue; | |
| pixel = applyPixel(pixel, opaqueAlpha); | |
| SK_UNSAFE_TODO(memcpy(dst, &pixel, dstBppN)); | |
| width -= n; | |
| SK_UNSAFE_TODO(dst += dstBppN); | |
| } | |
| if (width > 0) { | |
| // Process tail that is less than a full vector | |
| SkASSERT(width < n); | |
| pixel = zeroValue; | |
| pixel = applyPixel(pixel, opaqueAlpha); | |
| SK_UNSAFE_TODO(memcpy(dst, &pixel, width * dstBpp)); | |
| } | |
| }; | |
| } | |
| } | |
| // N represents the number of pixels being processed; each pixel is packed into a single element of | |
| // the Vec. To implement kSwapRB, this requires that the R and B channels have the same number of | |
| // bits. It arbitrarily requires that R is originally in the lower-significance bits relative to B. | |
| template <typename Px, int N, uint8_t Ops, Px RShift, Px BShift, Px RBBits> | |
| skvx::Vec<N, Px> apply_ops_packed(skvx::Vec<N, Px> pixel, Px opaqueAlpha) { | |
| static_assert(!(Ops & (kPadAlpha | kDropAlpha)), "Packed formats do not drop/pad alpha"); | |
| static_assert(RShift < BShift, "Shifts are not in the correct significance order"); | |
| static_assert(RShift + RBBits <= BShift, "Low swap channel overflows"); | |
| static_assert(BShift + RBBits <= sizeof(Px)*8, "Hi swap channel overflows"); | |
| if constexpr (Ops & kSwapRB) { | |
| static constexpr Px kChannelMask = (1 << RBBits) - 1; | |
| static constexpr Px kRMask = kChannelMask << RShift; | |
| static constexpr Px kBMask = kChannelMask << BShift; | |
| pixel = (pixel & ~(kRMask | kBMask)) | // Preserve non-RB bits | |
| ((pixel & kRMask) << (BShift - RShift)) | // Move red (lo) to blue (hi) position | |
| ((pixel & kBMask) >> (BShift - RShift)); // Move blue (hi) to red (lo) position | |
| } | |
| if constexpr (Ops & kForceOpaque) { | |
| // This assumes the opaqueAlpha value will have 0 bits set in the R,G,B channels. | |
| pixel = pixel | opaqueAlpha; | |
| } | |
| return pixel; | |
| } | |
| // NOTE: This takes no parameters for alpha because none of the formats that use this support | |
| // kDrop/PadAlpha. Since the channels are packed into Px, both source and dst must have the same bpp | |
| // `opaqueAlpha` specifies the bit pattern for the entire pixel representing {0, 0, 0, 1.0}. | |
| // `zeroValue` specifies the bit pattern for the entire pixel representing {0, 0, 0, 0}. | |
| template <typename Px, Px RShift, Px BShift, Px RBBits> | |
| XferRowFn xfer_rows_packed(uint8_t ops, Px opaqueAlpha, Px zeroValue = 0) { | |
| static constexpr int kBpp = sizeof(Px); | |
| static constexpr int N = 16 / sizeof(Px); // Fit to 128-bit/16-byte SIMD | |
| static_assert(N == 1 || N == 2 || N == 4 || N == 8); | |
| #define RETURN_XFER_ROW_FN(ops) \ | |
| case ops: \ | |
| return create_xfer_row_fn<Px, N>( \ | |
| N, (ops) & kIgnoreSrc ? 0 : kBpp, kBpp, opaqueAlpha, zeroValue, \ | |
| apply_ops_packed<Px, N, (ops) & ~kIgnoreSrc, RShift, BShift, RBBits>); \ | |
| switch (ops) { | |
| // The expected combination of ExtendedFormatXferOps | |
| RETURN_XFER_ROW_FN(kSwapRB) | |
| RETURN_XFER_ROW_FN(kIgnoreSrc) | |
| RETURN_XFER_ROW_FN(kForceOpaque) | |
| RETURN_XFER_ROW_FN(kForceOpaque | kSwapRB) | |
| RETURN_XFER_ROW_FN(kForceOpaque | kIgnoreSrc) | |
| default: | |
| SK_ABORT("Unsupported ExtendedFormatXferOps combination: %u", ops); | |
| } | |
| #undef RETURN_XFER_ROW_FN | |
| } | |
| // N represents the number of pixels being processed; some Ops are only supported for C == 1 or | |
| // C == 4. For other channel counts, those Ops are ignored at compile time (to make the ops switch | |
| // tables easier to write). Hopefully the linker will see that they are equivalent to the template | |
| // w/o the incompatible Ops. | |
| template <typename Cx, int N, int CPow2, uint8_t Ops> | |
| skvx::Vec<CPow2*N, Cx> apply_ops_by_channel(skvx::Vec<CPow2*N, Cx> pixel, Cx opaqueAlpha) { | |
| static_assert(CPow2 == 1 || CPow2 == 2 || CPow2 == 4); | |
| if constexpr (Ops & kPadAlpha && CPow2 == 4) { | |
| // If we are padding alpha, we are moving from a 3-channel source data (loaded in the first | |
| // N*3 values of pixel) to a 4-channel value. 3*N+1 holds undefined data after loading the | |
| // pixel, which we can set to the opaque alpha value. Then we shuffle the slots to spread | |
| // out each pixel's R,G, and B values and insert copies of the opaque alpha value. | |
| static constexpr int kA = 3*N; | |
| pixel[kA] = opaqueAlpha; | |
| if constexpr (N == 4) { | |
| pixel = skvx::shuffle<0,1,2,kA, 3,4,5,kA, 6,7,8,kA, 9,10,11,kA>(pixel); | |
| } else if constexpr (N == 2) { | |
| pixel = skvx::shuffle<0,1,2,kA, 3,4,5,kA>(pixel); | |
| } else { | |
| // No shuffling needed for N=1, since pixel == shuffle<0,1,2,A>(pixel) | |
| static_assert(N == 1); | |
| } | |
| } | |
| // Ops that assume 4 channels can be applied between kPadAlpha and kDropAlpha w/o worrying about | |
| // how to handle the 3-channel formats. | |
| if constexpr (Ops & kSwapRB && CPow2 == 4) { | |
| // For all 3 and 4 channel formats, it is assumed that R and B are in channels 0 and 2. | |
| if constexpr (N == 4) { | |
| pixel = skvx::shuffle<2,1,0,3, 6,5,4,7, 10,9,8,11, 14,13,12,15>(pixel); | |
| } else if constexpr (N == 2) { | |
| pixel = skvx::shuffle<2,1,0,3, 6,5,4,7>(pixel); | |
| } else { | |
| static_assert(N == 1); | |
| pixel = skvx::shuffle<2,1,0,3>(pixel); | |
| } | |
| } | |
| // 2-channel formats are incompatible with forcing opaque since there's never an alpha channel | |
| if constexpr (Ops & kForceOpaque && (CPow2 != 2)) { | |
| if constexpr (CPow2 == 1) { | |
| // For single-channel formats, the opaque alpha gets splatted over the whole vector | |
| pixel = opaqueAlpha; | |
| } else { | |
| // Between kPadAlpha and kDropAlpha, it is assumed that alpha is always channel 3 | |
| if constexpr (N == 4) { | |
| pixel[3] = pixel[7] = pixel[11] = pixel[15] = opaqueAlpha; | |
| } else if constexpr (N == 2) { | |
| pixel[3] = pixel[7] = opaqueAlpha; | |
| } else { | |
| static_assert(N == 1); | |
| pixel[3] = opaqueAlpha; | |
| } | |
| } | |
| } | |
| if constexpr (Ops & kDropAlpha && CPow2 == 4) { | |
| // If we are dropping alpha, we need to shuffle the R,G, and B values of the 4-channel | |
| // source data into the first N*3 slots of the returned pixel. The remaining N slots will | |
| // be ignored by the final memcpy. | |
| if constexpr (N == 4) { | |
| pixel = skvx::shuffle<0,1,2, 4,5,6, 8,9,10, 12,13,14, 14,14,14,14>(pixel); | |
| } else if constexpr (N == 2) { | |
| pixel = skvx::shuffle<0,1,2, 4,5,6, 6,6>(pixel); | |
| } else { | |
| // No shuffling needed for N=1, since pixel == shuffle<0,1,2,_>(pixel) | |
| static_assert(N == 1); | |
| } | |
| } | |
| return pixel; | |
| } | |
| // NOTE: This takes no parameters for SwapRB or DropAlpha because for all formats that use this, | |
| // SwapRB involves swapping channel 0 and channel 2, and dropping alpha removes channel 3. This | |
| // can be parameterized via template parameters to be able to push into the skvx::shuffle calls | |
| // if needed in the future. | |
| // `opaqueAlpha` specifies the bit pattern for a channel representing 1.0. | |
| // `zeroValue` specifies the bit pattern for a channel representing 0.0. | |
| template <typename Cx, int C> | |
| XferRowFn xfer_rows_by_channel(uint8_t ops, Cx opaqueAlpha, Cx zeroValue=0) { | |
| static_assert(C == 1 || C == 2 || C == 3 || C == 4); | |
| static constexpr int CPow2 = SkNextPow2(C); | |
| static constexpr int N = 16 / (CPow2 * sizeof(Cx)); // Fit to 128-bit/16-byte SIMD | |
| static_assert(N == 1 || N == 2 || N == 4 || N == 8 || N == 16); | |
| int srcBpp = C * sizeof(Cx); | |
| int dstBpp = C * sizeof(Cx); | |
| if (ops & kDropAlpha) { | |
| // Going from 4-channel src to the 3-channel format | |
| SkASSERT(C == 3); | |
| srcBpp = CPow2 * sizeof(Cx); | |
| } else if (ops & kPadAlpha) { | |
| // Going from the 3-channel format to 4-channel dst | |
| SkASSERT(C == 3); | |
| dstBpp = CPow2 * sizeof(Cx); | |
| } | |
| #define RETURN_XFER_ROW_FN(ops) \ | |
| case ops: \ | |
| return create_xfer_row_fn<Cx, N*CPow2>( \ | |
| N, (ops) & kIgnoreSrc ? 0 : srcBpp, dstBpp, opaqueAlpha, zeroValue, \ | |
| apply_ops_by_channel<Cx, N, CPow2, (ops) & ~kIgnoreSrc>); | |
| switch (ops) { | |
| // The expected combination of ExtendedFormatXferOps | |
| RETURN_XFER_ROW_FN(kDropAlpha) | |
| RETURN_XFER_ROW_FN(kDropAlpha | kSwapRB) | |
| RETURN_XFER_ROW_FN(kDropAlpha | kIgnoreSrc) | |
| RETURN_XFER_ROW_FN(kPadAlpha) | |
| RETURN_XFER_ROW_FN(kPadAlpha | kSwapRB) | |
| RETURN_XFER_ROW_FN(kPadAlpha | kIgnoreSrc) | |
| RETURN_XFER_ROW_FN(kSwapRB) | |
| RETURN_XFER_ROW_FN(kIgnoreSrc) | |
| RETURN_XFER_ROW_FN(kForceOpaque) | |
| RETURN_XFER_ROW_FN(kForceOpaque | kSwapRB) | |
| RETURN_XFER_ROW_FN(kForceOpaque | kIgnoreSrc) | |
| default: | |
| SK_ABORT("Unsupported ExtendedFormatXferOps combination: %u", ops); | |
| } | |
| #undef RETURN_XFERFN_CASE | |
| } | |
| XferRowFn get_xfer_row_fn(TextureFormat format, uint8_t ops) { | |
| static constexpr uint32_t kFloatBits1 = 0x3f800000; // SkFloat2Bits isn't constexpr | |
| SkASSERT(kFloatBits1 == SkFloat2Bits(1.f)); | |
| // 10 bits for _XR formats that encodes 1.0 and 0.0 given their extended normalization range. | |
| static constexpr uint32_t kXROne = 0x37e; | |
| static constexpr uint32_t kXRZero = 0x180; | |
| SkASSERT(ops); // For now, assume we only call into this if we have work to do. | |
| switch (format) { | |
| // Packed formats operate on a primitive that holds the entire pixel value | |
| case TF::kB5_G6_R5: | |
| case TF::kR5_G6_B5: | |
| return xfer_rows_packed<uint16_t, /*RShift=*/0, /*BShift=*/11, /*RBBits=*/5>( | |
| ops, /*opaqueAlpha=*/0); | |
| case TF::kABGR4: | |
| case TF::kARGB4: | |
| return xfer_rows_packed<uint16_t, /*RShift=*/4, /*BShift=*/12, /*RBBits=*/4>( | |
| ops, /*opaqueAlpha=*/0xF); | |
| case TF::kRGB10_A2: | |
| case TF::kBGR10_A2: | |
| return xfer_rows_packed<uint32_t, /*RShift=*/0, /*BShift=*/20, /*RBBits=*/10>( | |
| ops, /*opaqueAlpha=*/0b11 << 30); | |
| case TF::kBGR10_XR: | |
| // NOTE: opaqueAlpha doesn't matter here since it's just BGRx data, but to match | |
| // SkRP's handling of BGR10A2_XR, treat the last 2 bits as regular unorm. | |
| return xfer_rows_packed<uint32_t, /*RShift=*/0, /*BShift=*/20, /*RBBits=*/10>( | |
| ops, /*opaqueAlpha=*/0b11 << 30, | |
| /*zeroValue=*/(kXRZero << 20) | (kXRZero << 10) | kXRZero); | |
| // The remaining formats can be operated on with each channel as a primitive | |
| case TF::kR8: | |
| case TF::kA8: | |
| return xfer_rows_by_channel<uint8_t, /*C=*/1>(ops, /*opaqueAlpha=*/0xFF); | |
| case TF::kR16: | |
| return xfer_rows_by_channel<uint16_t, /*C=*/1>(ops, /*opaqueAlpha=*/0xFFFF); | |
| case TF::kR16F: | |
| return xfer_rows_by_channel<uint16_t, /*C=*/1>(ops, /*opaqueAlpha=*/SK_Half1); | |
| case TF::kRG8: | |
| return xfer_rows_by_channel<uint8_t, /*C=*/2>(ops, /*opaqueAlpha=*/0xFF); | |
| case TF::kRG16: | |
| return xfer_rows_by_channel<uint16_t, /*C=*/2>(ops, /*opaqueAlpha=*/0xFF); | |
| case TF::kRG16F: | |
| return xfer_rows_by_channel<uint16_t, /*C=*/2>(ops, /*opaqueAlpha=*/SK_Half1); | |
| case TF::kRG32F: | |
| return xfer_rows_by_channel<uint32_t, /*C=*/2>(ops, /*opaqueAlpha=*/kFloatBits1); | |
| case TF::kRGB8_sRGB: | |
| case TF::kRGB8: | |
| case TF::kBGR8: | |
| return xfer_rows_by_channel<uint8_t, /*C=*/3>(ops, /*opaqueAlpha=*/0xFF); | |
| case TF::kRGB16: | |
| return xfer_rows_by_channel<uint16_t, /*C=*/3>(ops, /*opaqueAlpha=*/0xFFFF); | |
| case TF::kRGB16F: | |
| return xfer_rows_by_channel<uint16_t, /*C=*/3>(ops, /*opaqueAlpha=*/SK_Half1); | |
| case TF::kRGB32F: | |
| return xfer_rows_by_channel<uint32_t, /*C=*/3>(ops, /*opaqueAlpha=*/kFloatBits1); | |
| case TF::kRGBA8: | |
| case TF::kRGBA8_sRGB: | |
| case TF::kBGRA8: | |
| case TF::kBGRA8_sRGB: | |
| return xfer_rows_by_channel<uint8_t, /*C=*/4>(ops, /*opaqueAlpha=*/0xFF); | |
| case TF::kRGBA10x6: | |
| // Each channel is the 10 real bits, with the least significant 6 padding bits. | |
| return xfer_rows_by_channel<uint16_t, /*C=*/4>(ops, /*opaqueAlpha=*/0xFFC0); | |
| case TF::kBGRA10x6_XR: | |
| // Like RGBA10x6 except the constants have to fit the extended range. | |
| return xfer_rows_by_channel<uint16_t, /*C=*/4>( | |
| ops, /*opaqueAlpha=*/kXROne << 6, /*zeroValue=*/kXRZero << 6); | |
| case TF::kRGBA16: | |
| return xfer_rows_by_channel<uint16_t, /*C=*/4>(ops, /*opaqueAlpha=*/0xFFFF); | |
| case TF::kRGBA16F: | |
| return xfer_rows_by_channel<uint16_t, /*C=*/4>(ops, /*opaqueAlpha=*/SK_Half1); | |
| case TF::kRGBA32F: | |
| return xfer_rows_by_channel<uint32_t, /*C=*/4>(ops, /*opaqueAlpha=*/kFloatBits1); | |
| default: | |
| // Remaining cases are compressed, multiplanar, or non-color so shouldn't be reached. | |
| // If the first assert trips, we missed a valid transfer format in the cases above. | |
| // If we hit the unreachable, we missed rejecting the transfer sooner. | |
| SkASSERT(TextureFormatColorTypeInfo(format).second & FormatXferOp::kDisabled); | |
| SkUNREACHABLE; | |
| } | |
| } | |
| // --- Functions for collapsing equivalent colortypes and formats into fewer xfer ops | |
| // An RPModifier for RPOps::Make() that handles calculating bt709 luminance. The API is that it | |
| // must have an explicit bool operator, and an apply(SkRasterPipeline*) function. | |
| struct BT709Luminance { | |
| bool fEnabled; | |
| explicit operator bool() const { return fEnabled; } | |
| void apply(SkRasterPipeline* rp) const { | |
| if (fEnabled) { | |
| rp->append(SkRasterPipelineOp::bt709_luminance_or_luma_to_alpha); | |
| } // else no op needed | |
| } | |
| }; | |
| bool account_for_luminance(SkColorType srcCT, | |
| bool hasColorSpaceTransform, | |
| SkColorType* dstCT) { | |
| if (*dstCT == kGray_8_SkColorType) { | |
| if (srcCT != kGray_8_SkColorType || hasColorSpaceTransform) { | |
| // Luminance must be calculated by raster pipeline, but we'll have to apply it before | |
| // any srcToDst swizzle, so pull it out to its own RPModifier. The op stores the value | |
| // in the alpha channel, so that needs to be dstCT that SkRasterPipeline sees. | |
| *dstCT = kAlpha_8_SkColorType; | |
| return true; | |
| } // else leave it as gray->gray | |
| } else if (srcCT == kGray_8_SkColorType && | |
| *dstCT == kR8_unorm_SkColorType && | |
| !hasColorSpaceTransform) { | |
| // The source gray value can just be copied to the red channel, so adjust this to be | |
| // gray->"gray" for a no-op copy | |
| *dstCT = kGray_8_SkColorType; | |
| return false; | |
| } // else trivially no extra luminance needs to be calculated, leave dstCT as-is | |
| return false; | |
| } | |
| bool rgbx_to_rgba(SkColorType* ct) { | |
| switch (*ct) { | |
| case kRGB_888x_SkColorType: *ct = kRGBA_8888_SkColorType; return true; | |
| case kRGB_101010x_SkColorType: *ct = kRGBA_1010102_SkColorType; return true; | |
| case kRGB_F16F16F16x_SkColorType: *ct = kRGBA_F16_SkColorType; return true; | |
| case kBGR_101010x_SkColorType: *ct = kBGRA_1010102_SkColorType; return true; | |
| default: | |
| // No colortype consolidation possible by switching to a kForceOpaque op instead. | |
| return false; | |
| } | |
| } | |
| bool bgra_to_rgba(SkColorType* ct) { | |
| switch (*ct) { | |
| case kBGRA_8888_SkColorType: *ct = kRGBA_8888_SkColorType; return true; | |
| case kBGRA_1010102_SkColorType: *ct = kRGBA_1010102_SkColorType; return true; | |
| case kBGR_101010x_SkColorType: *ct = kRGB_101010x_SkColorType; return true; | |
| // NOTE: For now there's no RGBA version of the _XR color types, so they can't be swapped | |
| case kBGRA_10101010_XR_SkColorType: | |
| case kBGR_101010x_XR_SkColorType: [[fallthrough]]; | |
| default: | |
| // No colortype consolidation possible by switching to a kSwapRB op instead. | |
| return false; | |
| } | |
| } | |
| uint32_t gray_adjusted_channels(SkColorType ct) { | |
| return ct == kGray_8_SkColorType ? kRGB_SkColorChannelFlags : SkColorTypeChannelFlags(ct); | |
| } | |
| uint32_t swizzle_adjusted_channels(uint32_t channels, Swizzle swizzle) { | |
| auto removeChannelIfConstant = [&](SkColorChannelFlag flag, int index) { | |
| if (swizzle[index] == '0' || swizzle[index] == '1') { | |
| channels &= ~flag; | |
| } | |
| }; | |
| removeChannelIfConstant(kRed_SkColorChannelFlag, 0); | |
| removeChannelIfConstant(kGreen_SkColorChannelFlag, 1); | |
| removeChannelIfConstant(kBlue_SkColorChannelFlag, 2); | |
| removeChannelIfConstant(kAlpha_SkColorChannelFlag, 3); | |
| return channels; | |
| } | |
| template <bool TextureIsDst> | |
| std::pair</*ops=*/uint8_t, /*computeLuminance=*/bool> optimize_transfer( | |
| SkColorType* cpuCT, | |
| SkColorType* texBaseCT, | |
| Swizzle* texReadSwizzle, | |
| SkColorSpaceXformSteps* csSteps, | |
| SkEnumBitMask<FormatXferOp> xferOps) { | |
| // Aliases for color types that are based on the transfer direction | |
| SkColorType* srcCT = TextureIsDst ? cpuCT : texBaseCT; | |
| SkColorType* dstCT = TextureIsDst ? texBaseCT : cpuCT; | |
| uint8_t finalOps = 0; | |
| // Some combinations of swizzle and load/store ops in raster pipeline are redundant so try to | |
| // make adjustments to reduce or eliminate the use of raster pipeline entirely. | |
| // First, adjust texBaseCT to match colortype semantics that can be inferred from swizzle for | |
| // alpha-only and red-only color types (hopefully creating a no-op transfer). These also test | |
| // the forced-opaque swizzle equivalents. | |
| { | |
| SkColorType adjustedBase = *texBaseCT; | |
| if (*texReadSwizzle == Swizzle("000r") || *texReadSwizzle == Swizzle("0001")) { | |
| // Red -> Alpha so shift the texture's "base" colortype to be the its alpha type | |
| switch(adjustedBase) { | |
| case kR8_unorm_SkColorType: adjustedBase = kAlpha_8_SkColorType; break; | |
| case kR16_unorm_SkColorType: adjustedBase = kA16_unorm_SkColorType; break; | |
| case kR16_float_SkColorType: adjustedBase = kA16_float_SkColorType; break; | |
| default: break; // Go through regular RP + swizzle flow | |
| } | |
| } else if ((*texReadSwizzle == Swizzle("rrra") || *texReadSwizzle == Swizzle("rrr1")) && | |
| adjustedBase == kR8_unorm_SkColorType) { | |
| // Red -> Gray so shift to kGray, which either ensures RP will generate the gray values | |
| // from a non-gray input, or will be detected as a no-op when transferring to/from | |
| // existing gray. | |
| adjustedBase = kGray_8_SkColorType; | |
| } | |
| if (adjustedBase != *texBaseCT) { | |
| *texBaseCT = adjustedBase; | |
| // Must preserve any forced opacity in the swizzle | |
| if ((SkColorTypeChannelFlags(adjustedBase) & kAlpha_SkColorChannelFlag) && | |
| (*texReadSwizzle)[3] == '1') { | |
| *texReadSwizzle = Swizzle::RGB1(); | |
| } else { | |
| *texReadSwizzle = Swizzle::RGBA(); | |
| } | |
| } | |
| } | |
| // Second, remove normalized floating point semantics, since historically we don't enforce them | |
| // during transfers (for better or worse). | |
| { | |
| if (*srcCT == kRGBA_F16Norm_SkColorType) { | |
| *srcCT = kRGBA_F16_SkColorType; | |
| } | |
| if (*dstCT == kRGBA_F16Norm_SkColorType) { | |
| *dstCT = kRGBA_F16_SkColorType; | |
| } | |
| } | |
| // Third, discard colorspace conversions if there's no RGB data that would be adjusted | |
| { | |
| if (SkToBool(*csSteps) && | |
| (SkColorTypeIsAlphaOnly(*srcCT) || SkColorTypeIsAlphaOnly(*dstCT))) { | |
| *csSteps = {}; | |
| } | |
| } | |
| // Fourth, switch swizzle components back to their default if the format doesn't have them | |
| { | |
| uint32_t texChannels = SkColorTypeChannelFlags(*texBaseCT); | |
| const char c[4] = {(*texReadSwizzle)[0], (*texReadSwizzle)[1], | |
| (*texReadSwizzle)[2], (*texReadSwizzle)[3]}; | |
| *texReadSwizzle = Swizzle((texChannels & kRed_SkColorChannelFlag) ? c[0] : 'r', | |
| (texChannels & kGreen_SkColorChannelFlag) ? c[1] : 'g', | |
| (texChannels & kBlue_SkColorChannelFlag) ? c[2] : 'b', | |
| (texChannels & kAlpha_SkColorChannelFlag) ? c[3] : 'a'); | |
| } | |
| // Fifth, if the channels are disjoint between the src and the dst, then the dst values can | |
| // ignore the source, at which point we reset everything else to the identity and flag what | |
| // value is required by the dst (1 if the dst is alpha, 0 if it's RGB/gray). | |
| { | |
| uint32_t cpuChannels = gray_adjusted_channels(*cpuCT); | |
| uint32_t gpuChannels = swizzle_adjusted_channels(gray_adjusted_channels(*texBaseCT), | |
| *texReadSwizzle); | |
| if (!(gpuChannels & cpuChannels)) { | |
| finalOps |= kIgnoreSrc; | |
| if (SkToBool(xferOps & FormatXferOp::kDropAlpha)) { | |
| // We're skipping the final xfer op handling, but we still have to make sure to drop | |
| // the alpha channel for 3-channel formats | |
| if constexpr (TextureIsDst) { | |
| finalOps |= kDropAlpha; | |
| } else { | |
| // Normally this would be kPadAlpha, but we're ignoring the src texture data, | |
| // so instead it can just use kForceOpaque | |
| finalOps |= kForceOpaque; | |
| } | |
| } else if (!SkColorTypeIsAlwaysOpaque(*dstCT)) { | |
| finalOps |= kForceOpaque; | |
| } | |
| // Disable everything else for the transfer | |
| *texReadSwizzle = Swizzle::RGBA(); | |
| *srcCT = *dstCT; | |
| *csSteps = SkColorSpaceXformSteps{}; | |
| return {finalOps, /*computeLuminance=*/false}; | |
| } | |
| } | |
| // Sixth, handle masking any unknown alpha bits if the dst doesn't already mask them. This must | |
| // happen *after* checking for channel overlap to optimize to the kIgnoreSrc case. If not, the | |
| // RGBx color types getting lifted to RGBA adds a channel overlap when there wasn't one. | |
| { | |
| bool cpuMasksAlpha = rgbx_to_rgba(cpuCT); | |
| bool gpuMasksAlpha = rgbx_to_rgba(texBaseCT) || (*texReadSwizzle)[3] == '1'; | |
| bool srcHasJunkAlpha = TextureIsDst ? cpuMasksAlpha : gpuMasksAlpha; | |
| bool dstStoresAlpha = | |
| (SkColorTypeChannelFlags(*dstCT) & kAlpha_SkColorChannelFlag) && | |
| !(TextureIsDst ? gpuMasksAlpha : cpuMasksAlpha); | |
| if (srcHasJunkAlpha) { | |
| csSteps->fFlags.premul = false; | |
| csSteps->fFlags.unpremul = false; | |
| } | |
| if (dstStoresAlpha && srcHasJunkAlpha) { | |
| finalOps |= kForceOpaque; | |
| } | |
| // Reset any opacity forcing in the swizzle since it's handled by kForceOpaque. | |
| if ((*texReadSwizzle)[3] == '1') { | |
| *texReadSwizzle = Swizzle((*texReadSwizzle)[0], | |
| (*texReadSwizzle)[1], | |
| (*texReadSwizzle)[2], | |
| 'a'); | |
| } | |
| } | |
| // Seventh, lift gray/luminance calculation out of colortype so that its placement in the | |
| // raster pipeline ops list can be controlled (vs. attached to a store). If luminance has to be | |
| // calculated, some additional optimizations may not be possible. | |
| const bool computeLuminance = account_for_luminance(*srcCT, SkToBool(*csSteps), dstCT); | |
| // Eighth, consolidate red/blue swaps present in colortype, swizzle, and xferOps into just ops | |
| { | |
| // Any swaps from the texture's base color type, swizzle, and xfer ops can always be | |
| // combined since those operations are grouped together, regardless of `TextureIsDst`. | |
| int numRBSwaps = 0; | |
| if ((*texReadSwizzle)[0] == 'b' && (*texReadSwizzle)[2] == 'r') { | |
| // Remove the swap in the swizzle (moving towards a no-op swizzle). | |
| *texReadSwizzle = Swizzle::Concat(*texReadSwizzle, Swizzle::BGRA()); | |
| numRBSwaps++; | |
| } | |
| if (bgra_to_rgba(texBaseCT)) { | |
| numRBSwaps++; | |
| } | |
| if (xferOps & FormatXferOp::kSwapRB) { | |
| numRBSwaps++; | |
| } | |
| // If there is not any RGB-dependent calculation between CPU and GPU data, we can also | |
| // consolidate the swap from cpu color type (CS transfer functions and unpremul/premul | |
| // steps are per-channel so swapping is still okay). | |
| if (!computeLuminance && !(csSteps->fFlags.gamut_transform || | |
| csSteps->fFlags.src_ootf || | |
| csSteps->fFlags.dst_ootf)) { | |
| // TODO(michaelludwig): Once we push finalOps back into raster pipeline if we need SkRP, | |
| // we can always apply this to cpuCT to remove an implicit swap_rb op. | |
| SkColorType swappedCT = *cpuCT; | |
| if (bgra_to_rgba(&swappedCT) && swappedCT == *texBaseCT) { | |
| numRBSwaps++; | |
| *cpuCT = swappedCT; | |
| } | |
| } | |
| // An even number of swaps is a no-op; an odd number of swaps is the same as one swap. The | |
| // swap can be skipped if the source data is known to have the same values in R and B, or | |
| // if R and B will be discarded. | |
| bool srcHasRBData = *srcCT != kGray_8_SkColorType && !SkColorTypeIsAlphaOnly(*srcCT); | |
| bool dstKeepsRBData = !SkColorTypeIsAlphaOnly(*dstCT) || computeLuminance; | |
| if ((numRBSwaps & 1) && srcHasRBData && dstKeepsRBData) { | |
| finalOps |= kSwapRB; | |
| } | |
| } | |
| if (xferOps & FormatXferOp::kDropAlpha) { | |
| if constexpr (TextureIsDst) { | |
| // On CPU->GPU conversion, FormatXferOp::kDropAlpha actually drops the alpha bits | |
| finalOps |= kDropAlpha; | |
| } else { | |
| finalOps |= kPadAlpha; | |
| } | |
| // Remove kForceOpaque as it's redundant with kDropAlpha/kPadAlpha | |
| finalOps &= ~kForceOpaque; | |
| } | |
| return {finalOps, computeLuminance}; | |
| } | |
| } // anonymous namespace | |
| std::optional<TextureFormatXferFn> TextureFormatXferFn::MakeCpuToGpu( | |
| SkColorType srcCT, | |
| const SkColorSpaceXformSteps& csSteps, | |
| TextureFormat dstFormat, | |
| Swizzle dstReadSwizzle) { | |
| auto [baseCT, xferOps] = TextureFormatColorTypeInfo(dstFormat); | |
| if (xferOps & FormatXferOp::kDisabled) { | |
| return std::nullopt; | |
| } | |
| SkColorSpaceXformSteps csStepsOptimized = csSteps; | |
| auto [postOps, luminance] = optimize_transfer</*TextureIsDst=*/true>( | |
| &srcCT, &baseCT, &dstReadSwizzle, &csStepsOptimized, xferOps); | |
| // The CPU -> GPU transform is: | |
| // SkRP{load(srcCT) -> | |
| // csSteps? -> | |
| // luminance? -> NOTE: luminance must be computed *before* the texture's swizzle | |
| // srcToDst(dstReadSwizzle^-1)? -> | |
| // store(baseCT)}? -> | |
| // postOps(baseCT->TF)? | |
| auto rp = RPOps::Make(srcCT, baseCT, &postOps, // ==> rpModifiers | |
| csStepsOptimized, BT709Luminance{luminance}, dstReadSwizzle.invert()); | |
| return TextureFormatXferFn(dstFormat, /*preOps=*/0, std::move(rp), postOps); | |
| } | |
| std::optional<TextureFormatXferFn> TextureFormatXferFn::MakeGpuToCpu( | |
| TextureFormat srcFormat, | |
| Swizzle srcReadSwizzle, | |
| const SkColorSpaceXformSteps& csSteps, | |
| SkColorType dstCT) { | |
| auto [baseCT, xferOps] = TextureFormatColorTypeInfo(srcFormat); | |
| if (xferOps & FormatXferOp::kDisabled) { | |
| return std::nullopt; | |
| } | |
| SkColorSpaceXformSteps csStepsOptimized = csSteps; | |
| auto [preOps, luminance] = optimize_transfer</*TextureIsDst=*/false>( | |
| &dstCT, &baseCT, &srcReadSwizzle, &csStepsOptimized, xferOps); | |
| if (preOps & kIgnoreSrc) { | |
| // The source is the texture format, which is what defines how ops are implemented, is | |
| // currently set to be ignored, so pick a new format that matches the `dstCT`. Actual GPU | |
| // support doesn't matter, this will just ensure the CPU data is initialized correctly. | |
| srcFormat = PreferredTextureFormats(dstCT)[0]; | |
| } | |
| // The GPU -> CPU transform is: | |
| // preOps(TF->baseCT)? -> | |
| // SkRP{load(baseCT) -> | |
| // srcToDst(srcReadSwizzle)? -> | |
| // csSteps? -> | |
| // luminance? -> | |
| // store(dstCT)}? | |
| auto rp = RPOps::Make(baseCT, dstCT, &preOps, // ==> rpModifiers | |
| srcReadSwizzle, csStepsOptimized, BT709Luminance{luminance}); | |
| return TextureFormatXferFn(srcFormat, preOps, std::move(rp), /*postOps=*/0); | |
| } | |
| std::optional<TextureFormatXferFn> TextureFormatXferFn::MakeIdentity(TextureFormat format) { | |
| auto [baseCT, xferOps] = TextureFormatColorTypeInfo(format); | |
| if (xferOps & FormatXferOp::kDisabled) { | |
| if (TextureFormatCompressionType(format) == SkTextureCompressionType::kNone) { | |
| return std::nullopt; | |
| } // else allow compressed formats through for identity conversion uploads | |
| } | |
| return TextureFormatXferFn(format, /*preOps=*/0, /*rp=*/nullptr, /*postOps=*/0); | |
| } | |
| template <typename... RPModifiers> | |
| sk_sp<TextureFormatXferFn::RPOps> TextureFormatXferFn::RPOps::Make( | |
| SkColorType srcColorType, | |
| SkColorType dstColorType, | |
| uint8_t* xferOps, | |
| RPModifiers... rpModifiers) { | |
| if (srcColorType == dstColorType && | |
| (!SkToBool(rpModifiers) && ...)) { | |
| return nullptr; // Identity conversion | |
| } | |
| // Luminance has to be calculated before the texture's swizzle so it's pulled out manually | |
| // and put in the right place in `rpModifiers`, so we don't want to encounter them where | |
| // the appendStore() also computes luminance. | |
| SkASSERT(dstColorType != kGray_8_SkColorType); | |
| sk_sp<RPOps> ops{new RPOps(/*srcBpp=*/SkColorTypeBytesPerPixel(srcColorType), | |
| /*dstBpp=*/SkColorTypeBytesPerPixel(dstColorType))}; | |
| // Match SkConvertPixels swizzle_or_premul and delegate to SkOpts for certain combinations. Not | |
| // all of the Swizzler functions are relevant because the same cases are already covered by sole | |
| // use of ExtendedXferOps (e.g. RGBA_to_BGRA is just kSwapRB) or never occur in format transfers | |
| // (e.g. the CMYK conversions). | |
| auto isOnlyPremul = [] <typename RPModifier> (RPModifier modifier) { | |
| if constexpr (std::is_same_v<RPModifier, SkColorSpaceXformSteps>) { | |
| auto flags = modifier.fFlags; | |
| return flags.premul && | |
| !(flags.unpremul || flags.linearize || flags.gamut_transform || flags.encode); | |
| } else { | |
| return !SkToBool(modifier); | |
| } | |
| }; | |
| if ((srcColorType == kRGBA_8888_SkColorType || srcColorType == kBGRA_8888_SkColorType) && | |
| (dstColorType == kRGBA_8888_SkColorType || dstColorType == kBGRA_8888_SkColorType)) { | |
| // 32-bit unorm8 colors that can be swizzled with a premul go to SkOpts. | |
| if ((isOnlyPremul(rpModifiers) && ...)) { | |
| SkASSERT(srcColorType == dstColorType && !(*xferOps & kForceOpaque)); | |
| if (*xferOps & kSwapRB) { | |
| ops->fSwizzler = SkOpts::RGBA_to_bgrA; // swap RB and premultiply | |
| *xferOps &= ~kSwapRB; | |
| } else { | |
| ops->fSwizzler = SkOpts::RGBA_to_rgbA; // just premultiply | |
| } | |
| return ops; | |
| } // else fall through to use raster pipeline | |
| } | |
| // NOTE: The src and dst memory contexts are not modified here, they just provide stable | |
| // pointers for the appended ops to reference, and will be patched during run(). | |
| ops->fRP.appendLoad(srcColorType, &ops->fSrcCtx); | |
| auto apply = [target = ops.get()] <typename RPModifier> (RPModifier modifier, | |
| uint8_t* xferOps) { | |
| const RPModifier* stableModifier = &modifier; | |
| if constexpr (std::is_same_v<RPModifier, Swizzle>) { | |
| // Swizzle does not require any persisted context, but we can also fold kSwapRB into | |
| // the swizzle. This is done here so that the kSwapRB op is grouped with the swizzle | |
| // modifier (given the order of modifiers passed into Make). | |
| if (*xferOps & kSwapRB) { | |
| modifier = Swizzle::Concat(modifier, Swizzle::BGRA()); | |
| *xferOps &= ~kSwapRB; | |
| } | |
| if (*xferOps & kForceOpaque) { | |
| modifier = Swizzle::Concat(modifier, Swizzle::RGB1()); | |
| *xferOps &= ~kForceOpaque; | |
| } | |
| } else if constexpr (!std::is_same_v<RPModifier, BT709Luminance>) { | |
| // We must create a copy of rpModifiers[i] in the arena, because its apply() function | |
| // may reference parts of itself as the context's passed to the appended ops. | |
| stableModifier = target->fArena.make<RPModifier>(modifier); | |
| } // else BT709Luminance does not require any persisted context | |
| stableModifier->apply(&target->fRP); | |
| }; | |
| (apply(rpModifiers, xferOps), ...); | |
| ops->fRP.appendStore(dstColorType, &ops->fDstCtx); | |
| return ops; | |
| } | |
| int TextureFormatXferFn::getRowInvokeCount(int* width, | |
| int* height, | |
| size_t srcRowBytes, | |
| size_t dstRowBytes) const { | |
| bool denseTransfer; | |
| const size_t widthSz = (size_t) (*width); | |
| if (fRP) { | |
| // Mixed transfer methods are always row-by-row to minimize intermediate storage | |
| if (((fPreOps | fPostOps) != 0) || | |
| // Swizzler has to go row by row if the src and dst strides aren't dense | |
| (fRP->fSwizzler && (widthSz * fRP->fSrcBpp != srcRowBytes || | |
| widthSz * fRP->fDstBpp != dstRowBytes)) || | |
| // RasterPipeline operates in pixel units so its built-in row handling can be used if | |
| // the src and dst strides are multiples of the pixel sizes | |
| (!fRP->fSwizzler && srcRowBytes % fRP->fSrcBpp == 0 && | |
| dstRowBytes % fRP->fDstBpp == 0)) { | |
| // Set the strides to 0 when the control loop will be handling the src/dst pointers | |
| fRP->fSrcCtx.stride = 0; | |
| fRP->fDstCtx.stride = 0; | |
| denseTransfer = false; | |
| } else { | |
| // RasterPipeline or the Swizzler function can be invoked just once | |
| fRP->fSrcCtx.stride = SkTo<int>(srcRowBytes / fRP->fSrcBpp); | |
| fRP->fDstCtx.stride = SkTo<int>(dstRowBytes / fRP->fDstBpp); | |
| denseTransfer = true; | |
| } | |
| } else { | |
| // There's only extended xfer ops, so the src and dst bpp's can be derived from the format. | |
| const int bpp = TextureFormatBytesPerBlock(fFormat); | |
| if (fPreOps & kPadAlpha) { | |
| const size_t srcStride = widthSz * bpp; | |
| const size_t dstStride = widthSz * (bpp + bpp/3); | |
| denseTransfer = srcStride == srcRowBytes && dstStride == dstRowBytes; | |
| } else if (fPostOps & kDropAlpha) { | |
| const size_t srcStride = widthSz * (bpp + bpp/3); | |
| const size_t dstStride = widthSz * bpp; | |
| denseTransfer = srcStride == srcRowBytes && dstStride == dstRowBytes; | |
| } else { | |
| const size_t stride = widthSz * bpp; | |
| denseTransfer = stride == srcRowBytes && stride == dstRowBytes; | |
| } | |
| } | |
| if (denseTransfer) { | |
| if (fRP && !fRP->fRP.empty()) { | |
| // RasterPipeline manages its own dense transfer and needs to see the original width and | |
| // height values, since it will use the set strides on the MemoryCtxs to adjust pointers | |
| return 1; // one RP invocation over width x height pixels | |
| } else { | |
| // When not using the raster pipeline, the dense transfer happens by treating the data | |
| // as a single row that is width*height long. This can only be done if that still fits | |
| // into an int. | |
| SkASSERT(!fRP || fRP->fSwizzler); | |
| if (std::numeric_limits<int>::max() / *height > *width) { | |
| *width *= *height; | |
| *height = 1; | |
| return 1; // one "width*height" invocation of swizzler or xfer_row_fn | |
| } // otherwise fall through to do a row-by-row transfer | |
| } | |
| } | |
| // If we're here, it can't be dense, so the transfer will be row-by-row | |
| const int rowInvokeCount = *height; | |
| *height = 1; | |
| return rowInvokeCount; | |
| } | |
| void TextureFormatXferFn::run(int width, int height, | |
| const void* src, size_t srcRowBytes, | |
| void* dst, size_t dstRowBytes) const { | |
| SkASSERT(width >= 1 && height >= 1); | |
| const int rowInvokeCount = this->getRowInvokeCount(&width, &height, srcRowBytes, dstRowBytes); | |
| SkAutoMalloc tempRowStorage; // empty if not mixing transfer methods | |
| skia_private::STArray<2, XferRowFn> rowFns; // At most 2 actions per row | |
| if (fPreOps) { | |
| // `src` is definitively the texture | |
| if (fRP) { | |
| // We need a temporary buffer equal to srcBpp*width to hold the output of the preOps | |
| // that is used as the source of data for SkRasterPipeline (executed per row). | |
| tempRowStorage.reset(fRP->fSrcBpp * width); | |
| } | |
| rowFns.push_back(get_xfer_row_fn(fFormat, fPreOps)); | |
| } | |
| if (fRP) { | |
| if (fRP->fSwizzler) { | |
| SkASSERT(fRP->fRP.empty()); | |
| rowFns.push_back([&](const char* src, char* dst, int width) { | |
| const uint32_t* srcU32 = reinterpret_cast<const uint32_t*>(src); | |
| uint32_t* dstU32 = reinterpret_cast<uint32_t*>(dst); | |
| fRP->fSwizzler(dstU32, srcU32, width); | |
| }); | |
| } else { | |
| SkASSERT(!fRP->fRP.empty()); | |
| rowFns.push_back([&](const char* src, char* dst, int width) { | |
| // NOTE: When height != 1, this invocation actually processes the entire image. | |
| // Otherwise we assume src and dst have been offset by y so we update the | |
| // MemoryCtx's pixel addresses. | |
| fRP->fSrcCtx.pixels = const_cast<char*>(src); // This won't be written to | |
| fRP->fDstCtx.pixels = dst; | |
| fRP->fRP.run(0, 0, width, height); | |
| }); | |
| } | |
| } | |
| if (fPostOps) { | |
| // `dst` is definitively the texture | |
| if (fRP) { | |
| // We need a temporary buffer equal to dstBpp*width to hold the output of the | |
| // SkRasterPipeline conversion that is used as the input to postOps (executed per row). | |
| tempRowStorage.reset(fRP->fDstBpp * width); | |
| } | |
| rowFns.push_back(get_xfer_row_fn(fFormat, fPostOps)); | |
| } | |
| if (rowFns.empty()) { | |
| // Identity conversion function still needs to move the data | |
| const int bpp = TextureFormatBytesPerBlock(fFormat); | |
| rowFns.push_back([bpp](const char* src, char* dst, int width) { | |
| SK_UNSAFE_TODO(memcpy(dst, src, bpp * width)); | |
| }); | |
| } | |
| for (int y = 0; y < rowInvokeCount; ++y) { | |
| // Always start by processing `src` | |
| const char* input = SK_UNSAFE_TODO(static_cast<const char*>(src) + y * srcRowBytes); | |
| for (int i = 0; i < rowFns.size(); ++i) { | |
| // And either output to the temporary row or the final `dst` | |
| char* target = i == rowFns.size() - 1 | |
| ? (SK_UNSAFE_TODO(static_cast<char*>(dst) + y * dstRowBytes)) | |
| : static_cast<char*>(tempRowStorage.get()); | |
| rowFns[i](input, target, width); | |
| // If there's more than one rowFn, switch to using the temporary row as input | |
| input = target; | |
| } | |
| } | |
| } | |
| #if defined(GPU_TEST_UTILS) | |
| bool TextureFormatXferFn::isIgnoreSrcForceOpaque() const { | |
| uint8_t ops = fPreOps | fPostOps; | |
| return ops == (kIgnoreSrc | kForceOpaque); | |
| } | |
| bool TextureFormatXferFn::isDropOrPadAlpha() const { | |
| uint8_t ops = fPreOps | fPostOps; | |
| return ops == kPadAlpha || ops == kDropAlpha; | |
| } | |
| #endif // defined(GPU_TEST_UTILS) | |
| } // namespace skgpu::graphite | |
| // TODO(b/390473370): TextureUploadWriter isn't very useful with TextureFormatXferFn's | |
| // capabilities and the writer model doesn't work well with the more explicit placement of dst | |
| // data that has to happen. UploadBufferManager should just provide a span and then we can call | |
| // run() directly and delete TextureUploadWriter entirely. | |
| #include "src/gpu/BufferWriter.h" | |
| namespace skgpu { | |
| void TextureUploadWriter::convert(size_t offset, int width, int height, | |
| const void* src, size_t srcRowBytes, | |
| const graphite::TextureFormatXferFn& dst, size_t dstRowBytes) { | |
| this->validate(offset + dstRowBytes * height); | |
| void* dstPtr = SkTAddOffset<void>(fPtr, offset); | |
| dst.run(width, height, src, srcRowBytes, dstPtr, dstRowBytes); | |
| } | |
| } // namespace skgpu |