aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/opts/SkBlitRow_opts_SSE4.cpp
blob: f4273d27b4b0bfaf81d1df0c5d86f8f25f5a8164 (plain)
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
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#include "SkBlitRow_opts_SSE4.h"

// Some compilers can't compile SSSE3 or SSE4 intrinsics.  We give them stub methods.
// The stubs should never be called, so we make them crash just to confirm that.
#if SK_CPU_SSE_LEVEL < SK_CPU_SSE_LEVEL_SSE41
void S32A_Opaque_BlitRow32_SSE4(SkPMColor* SK_RESTRICT, const SkPMColor* SK_RESTRICT, int, U8CPU) {
    sk_throw();
}

void Color32A_D565_SSE4(uint16_t dst[], SkPMColor src, int count, int x, int y) {
    sk_throw();
}

#else

#include <smmintrin.h>  // SSE4.1 intrinsics

#include "SkColorPriv.h"
#include "SkColor_opts_SSE2.h"

void S32A_Opaque_BlitRow32_SSE4(SkPMColor* SK_RESTRICT dst,
                                const SkPMColor* SK_RESTRICT src,
                                int count,
                                U8CPU alpha) {
    SkASSERT(alpha == 255);
    // As long as we can, we'll work on 16 pixel pairs at once.
    int count16 = count / 16;
    __m128i* dst4 = (__m128i*)dst;
    const __m128i* src4 = (const __m128i*)src;

    for (int i = 0; i < count16 * 4; i += 4) {
        // Load 16 source pixels.
        __m128i s0 = _mm_loadu_si128(src4+i+0),
                s1 = _mm_loadu_si128(src4+i+1),
                s2 = _mm_loadu_si128(src4+i+2),
                s3 = _mm_loadu_si128(src4+i+3);

        const __m128i alphaMask = _mm_set1_epi32(0xFF << SK_A32_SHIFT);
        const __m128i ORed = _mm_or_si128(s3, _mm_or_si128(s2, _mm_or_si128(s1, s0)));
        if (_mm_testz_si128(ORed, alphaMask)) {
            // All 16 source pixels are fully transparent.  There's nothing to do!
            continue;
        }
        const __m128i ANDed = _mm_and_si128(s3, _mm_and_si128(s2, _mm_and_si128(s1, s0)));
        if (_mm_testc_si128(ANDed, alphaMask)) {
            // All 16 source pixels are fully opaque.  There's no need to read dst or blend it.
            _mm_storeu_si128(dst4+i+0, s0);
            _mm_storeu_si128(dst4+i+1, s1);
            _mm_storeu_si128(dst4+i+2, s2);
            _mm_storeu_si128(dst4+i+3, s3);
            continue;
        }
        // The general slow case: do the blend for all 16 pixels.
        _mm_storeu_si128(dst4+i+0, SkPMSrcOver_SSE2(s0, _mm_loadu_si128(dst4+i+0)));
        _mm_storeu_si128(dst4+i+1, SkPMSrcOver_SSE2(s1, _mm_loadu_si128(dst4+i+1)));
        _mm_storeu_si128(dst4+i+2, SkPMSrcOver_SSE2(s2, _mm_loadu_si128(dst4+i+2)));
        _mm_storeu_si128(dst4+i+3, SkPMSrcOver_SSE2(s3, _mm_loadu_si128(dst4+i+3)));
    }

    // Wrap up the last <= 15 pixels.
    for (int i = count16*16; i < count; i++) {
        // This check is not really necessarily, but it prevents pointless autovectorization.
        if (src[i] & 0xFF000000) {
            dst[i] = SkPMSrcOver(src[i], dst[i]);
        }
    }
}

static inline uint16_t Color32A_D565_1x(uint16_t dst, unsigned scale, uint32_t src_expand) {
    uint32_t dst_expand = SkExpand_rgb_16(dst) * scale;
    return SkCompact_rgb_16((src_expand + dst_expand) >> 5);
}

void Color32A_D565_SSE4(uint16_t dst[], SkPMColor src, int count, int x, int y) {
    SkASSERT(count > 0);

    uint32_t src_expand = (SkGetPackedG32(src) << 24) |
                          (SkGetPackedR32(src) << 13) |
                          (SkGetPackedB32(src) << 2);
    unsigned scale = SkAlpha255To256(0xFF - SkGetPackedA32(src)) >> 3;

    // Check if we have enough pixels to run SIMD
    if (count >= (int)(8 + (((16 - (size_t)dst) & 0x0F) >> 1))) {
        __m128i* dst_wide;
        const __m128i src_expand_wide = _mm_set1_epi32(src_expand);
        const __m128i scale_wide = _mm_set1_epi32(scale);
        const __m128i mask_green = _mm_set1_epi32(SK_R16_MASK_IN_PLACE |
                                                  SK_B16_MASK_IN_PLACE |
                                                 (SK_G16_MASK_IN_PLACE << 16));

        // Align dst to an even 16 byte address (0-7 pixels)
        while (((((size_t)dst) & 0x0F) != 0) && (count > 0)) {
            *dst = Color32A_D565_1x(*dst, scale, src_expand);
            dst += 1;
            count--;
        }

        dst_wide = reinterpret_cast<__m128i*>(dst);
        do {
            // Load 8 RGB565 pixels
            __m128i pixels = _mm_load_si128(dst_wide);

            // Duplicate and mask
            __m128i pixels_high = _mm_unpackhi_epi16(pixels, pixels);
            pixels_high = _mm_and_si128(mask_green, pixels_high);
            pixels = _mm_unpacklo_epi16(pixels, pixels);
            pixels = _mm_and_si128(mask_green, pixels);

            // Scale with alpha
            pixels_high = _mm_mullo_epi32(pixels_high, scale_wide);
            pixels = _mm_mullo_epi32(pixels, scale_wide);

            // Add src_expand_wide and shift down again
            pixels_high = _mm_add_epi32(pixels_high, src_expand_wide);
            pixels_high = _mm_srli_epi32(pixels_high, 5);
            pixels = _mm_add_epi32(pixels, src_expand_wide);
            pixels = _mm_srli_epi32(pixels, 5);

            // Mask
            pixels_high = _mm_and_si128(mask_green, pixels_high);
            pixels = _mm_and_si128(mask_green, pixels);

            // Combine into RGB565 and store
            pixels = _mm_hadd_epi16(pixels, pixels_high);
            _mm_store_si128(dst_wide, pixels);
            count -= 8;
            dst_wide++;
        } while (count >= 8);

        dst = reinterpret_cast<uint16_t*>(dst_wide);
    }

    // Small loop to handle remaining pixels.
    while (count > 0) {
        *dst = Color32A_D565_1x(*dst, scale, src_expand);
        dst += 1;
        count--;
    }
}

#endif