Skip to content

Commit f6ac74f

Browse files
committed
Supporting SIMD blending for ARM architecture
- LLM-generated NEON blender is now used for ARM targets.
1 parent 8b95726 commit f6ac74f

10 files changed

Lines changed: 131 additions & 42 deletions

File tree

.github/workflows/cmake.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@ name: Build / Test
22

33
on:
44
push:
5-
branches: [ master ]
6-
pull_request:
7-
branches: [ master ]
5+
branches: '*'
86

97
env:
108
BUILD_TYPE: Release
@@ -17,7 +15,9 @@ jobs:
1715
config:
1816
- os: ubuntu-latest
1917
- os: macos-latest
18+
- os: macos-15-intel
2019
- os: windows-latest
20+
- os: windows-11-arm
2121

2222
steps:
2323
- uses: actions/checkout@v2

agge/blenders_simd.h

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,24 @@
33
#include "config.h"
44
#include "pixel.h"
55

6-
#include <emmintrin.h>
6+
#if defined(AGGE_ARCH_INTEL)
7+
#include <emmintrin.h>
8+
#elif defined(AGGE_ARCH_ARM)
9+
#include <arm_neon.h>
10+
#else
11+
#error "Unsupported architecture"
12+
#endif
713

814
namespace agge
915
{
1016
namespace simd
1117
{
18+
#if defined(AGGE_ARCH_INTEL)
19+
typedef __m128i uint16x8_t;
20+
#elif defined(AGGE_ARCH_ARM)
21+
typedef ::uint16x8_t uint16x8_t;
22+
#endif
23+
1224
// This blender requires and assumes the following:
1325
// 1. Result of blending of empty covers vector (n == 0) is undefined;
1426
// 2. Covers vector must be accessible beyond the length (n) up to the nearest multiple of 4;
@@ -26,10 +38,10 @@ namespace agge
2638
void operator ()(pixel *pixels, int x, int y, count_t n, const cover_type *covers) const;
2739

2840
private:
29-
static void blend4(pixel *pixels, __m128i color_u16, __m128i alpha_u16, unsigned int covers_packed);
41+
static void blend4(pixel *pixels, uint16x8_t color_u16, uint16x8_t alpha_u16, unsigned int covers_packed);
3042

3143
private:
32-
__m128i _color_u16, _alpha_u16;
44+
uint16x8_t _color_u16, _alpha_u16;
3345
pixel _components;
3446
static unsigned int _tail_mask[5];
3547
};
@@ -39,8 +51,8 @@ namespace agge
3951
AGGE_INLINE void blender_solid_color::operator ()(pixel *pixels, int /*x*/, int /*y*/, count_t n,
4052
const cover_type *covers) const
4153
{
42-
const __m128i alpha_u16 = _mm_load_si128(&_alpha_u16);
43-
const __m128i color_u16 = _mm_load_si128(&_color_u16);
54+
const uint16x8_t alpha_u16 = _alpha_u16;
55+
const uint16x8_t color_u16 = _color_u16;
4456

4557
for (; n > 4; pixels += 4, covers += 4, n -= 4)
4658
blend4(pixels, color_u16, alpha_u16, *reinterpret_cast<const unsigned int *>(covers));

agge/config.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
#elif defined(__ARM_ARCH_5__) || defined(__ARM_ARCH_5T__)
2424
#define AGGE_ARCH_ARM 5
2525
#else
26-
#define AGGE_ARCH_ARM 1
26+
#define AGGE_ARCH_ARM
2727
#endif
28+
#elif defined(__aarch64__)
29+
#define AGGE_ARCH_ARM 8
2830
#else
2931
#define AGGE_ARCH_GENERIC
3032
#endif

samples/balls-async/balls-async.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ namespace
4040
}
4141

4242
virtual void schedule(function<void()> &&task, mt::milliseconds defer_by) override
43-
{ _underlying.schedule(move(task), defer_by); }
43+
{ _underlying.schedule(std::move(task), defer_by); }
4444

4545
private:
4646
void run()
@@ -86,11 +86,11 @@ namespace
8686
if (_ready.empty())
8787
_pending.push_back(completion);
8888
else
89-
item = move(_ready.back()), _ready.pop_back();
89+
item = std::move(_ready.back()), _ready.pop_back();
9090
}
9191
if (item)
92-
completion->set(move(item));
93-
return task< shared_ptr<T> >(move(completion));
92+
completion->set(std::move(item));
93+
return task< shared_ptr<T> >(std::move(completion));
9494
}
9595

9696
void put_back(const shared_ptr<T> &item)
@@ -104,7 +104,7 @@ namespace
104104
if (_pending.empty())
105105
_ready.push_back(item);
106106
else
107-
completion = move(_pending.back()), _pending.pop_back();
107+
completion = std::move(_pending.back()), _pending.pop_back();
108108
}
109109

110110
if (completion)

samples/common/shell.h

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
#include <agge/bitmap.h>
44
#include <agge/blenders.h>
5-
#include <agge/blenders_generic.h>
5+
#include <agge/blenders_simd.h>
66
#include <agge/config.h>
77

88
#if defined(AGGE_PLATFORM_ANDROID)
@@ -25,14 +25,7 @@
2525

2626
#endif
2727

28-
#if defined(AGGE_ARCH_INTEL)
29-
#include <agge/blenders_simd.h>
30-
31-
typedef agge::blender_solid_color<agge::simd::blender_solid_color, platform_pixel_order> platform_blender_solid_color;
32-
33-
#else
34-
typedef agge::blender_solid_color_rgb<agge::pixel32, platform_pixel_order> platform_blender_solid_color;
35-
#endif
28+
typedef agge::blender_solid_color<agge::simd::blender_solid_color, platform_pixel_order> platform_blender_solid_color;
3629

3730
struct services;
3831

src/agge/CMakeLists.txt

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ add_compile_options($<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-std=c++03>)
55
set(AGGE_SOURCES
66
color.cpp
77
curves.cpp
8+
blenders_simd.cpp
89
dash.cpp
910
figures.cpp
1011
hybrid_event.cpp
@@ -15,12 +16,6 @@ set(AGGE_SOURCES
1516
vector_rasterizer.cpp
1617
)
1718

18-
if (AGGE_ARCHITECTURE STREQUAL "intel")
19-
set(AGGE_SOURCES ${AGGE_SOURCES}
20-
blenders_intel.cpp
21-
)
22-
endif()
23-
2419
if (WIN32)
2520
set(AGGE_SOURCES ${AGGE_SOURCES}
2621
platform/win32/bitmap.cpp

src/agge/blenders_arm.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#include <agge/blenders_simd.h>
2+
3+
namespace agge
4+
{
5+
namespace simd
6+
{
7+
unsigned int blender_solid_color::_tail_mask[5] = {
8+
0x00000000, 0x000000FF, 0x0000FFFF, 0x00FFFFFF, 0xFFFFFFFF,
9+
};
10+
11+
namespace
12+
{
13+
uint16x8_t make_color_u16(pixel32 components)
14+
{
15+
const uint8x8_t c = vcreate_u8(reinterpret_cast<const unsigned int &>(components));
16+
const uint16x8_t c_u16 = vmovl_u8(c);
17+
18+
return vcombine_u16(vget_low_u16(c_u16), vget_low_u16(c_u16));
19+
}
20+
21+
uint16x8_t make_alpha_u16(unsigned int alpha)
22+
{
23+
alpha = (alpha << 6) + 505 * alpha / 1000;
24+
return vdupq_n_u16(static_cast<uint16_t>(alpha));
25+
}
26+
}
27+
28+
29+
blender_solid_color::blender_solid_color(pixel components, uint8_t alpha)
30+
: _color_u16(make_color_u16(components)), _alpha_u16(make_alpha_u16(alpha)), _components(components)
31+
{ }
32+
33+
void blender_solid_color::operator ()(pixel *pixels, int /*x*/, int /*y*/, count_t n) const
34+
{
35+
for (; n; --n, ++pixels)
36+
*pixels = _components;
37+
}
38+
39+
void blender_solid_color::blend4(pixel *pixels, uint16x8_t color_u16, uint16x8_t alpha_u16, unsigned int covers_packed)
40+
{
41+
uint8x8_t covers = vdup_n_u8(0);
42+
covers = vset_lane_u8(static_cast<uint8_t>(covers_packed >> 0), covers, 0);
43+
covers = vset_lane_u8(static_cast<uint8_t>(covers_packed >> 8), covers, 1);
44+
covers = vset_lane_u8(static_cast<uint8_t>(covers_packed >> 16), covers, 2);
45+
covers = vset_lane_u8(static_cast<uint8_t>(covers_packed >> 24), covers, 3);
46+
47+
const uint16x4_t covers_u16 = vget_low_u16(vshlq_n_u16(vmovl_u8(covers), 8));
48+
const uint16x4_t alpha = vshrn_n_u32(vmull_u16(covers_u16, vget_low_u16(alpha_u16)), 16);
49+
50+
const uint16_t a0 = vget_lane_u16(alpha, 0);
51+
const uint16_t a1 = vget_lane_u16(alpha, 1);
52+
const uint16_t a2 = vget_lane_u16(alpha, 2);
53+
const uint16_t a3 = vget_lane_u16(alpha, 3);
54+
55+
const int16x8_t alpha10 = vreinterpretq_s16_u16(vcombine_u16(vdup_n_u16(a0), vdup_n_u16(a1)));
56+
const int16x8_t alpha32 = vreinterpretq_s16_u16(vcombine_u16(vdup_n_u16(a2), vdup_n_u16(a3)));
57+
58+
const uint8x16_t source_u8 = vld1q_u8(reinterpret_cast<uint8_t *>(pixels));
59+
const int16x8_t source10 = vreinterpretq_s16_u16(vmovl_u8(vget_low_u8(source_u8)));
60+
const int16x8_t source32 = vreinterpretq_s16_u16(vmovl_u8(vget_high_u8(source_u8)));
61+
62+
const int16x8_t d10 = vshlq_n_s16(vsubq_s16(source10, vreinterpretq_s16_u16(color_u16)), 2);
63+
const int16x8_t d32 = vshlq_n_s16(vsubq_s16(source32, vreinterpretq_s16_u16(color_u16)), 2);
64+
65+
const int32x4_t m10l = vmull_s16(vget_low_s16(d10), vget_low_s16(alpha10));
66+
const int32x4_t m10h = vmull_s16(vget_high_s16(d10), vget_high_s16(alpha10));
67+
const int32x4_t m32l = vmull_s16(vget_low_s16(d32), vget_low_s16(alpha32));
68+
const int32x4_t m32h = vmull_s16(vget_high_s16(d32), vget_high_s16(alpha32));
69+
70+
const int16x8_t c10 = vcombine_s16(vshrn_n_s32(m10l, 16), vshrn_n_s32(m10h, 16));
71+
const int16x8_t c32 = vcombine_s16(vshrn_n_s32(m32l, 16), vshrn_n_s32(m32h, 16));
72+
73+
const int16x8_t r10 = vsubq_s16(source10, c10);
74+
const int16x8_t r32 = vsubq_s16(source32, c32);
75+
76+
const uint8x8_t result10 = vqmovun_s16(r10);
77+
const uint8x8_t result32 = vqmovun_s16(r32);
78+
79+
vst1q_u8(reinterpret_cast<uint8_t *>(pixels), vcombine_u8(result10, result32));
80+
}
81+
}
82+
}

src/agge/blenders_intel.cpp

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,18 @@ namespace agge
1010

1111
namespace
1212
{
13-
__m128i make_color_u16(pixel32 components)
13+
uint16x8_t make_color_u16(pixel32 components)
1414
{
15-
__m128i t = _mm_cvtsi32_si128(reinterpret_cast<const unsigned int &>(components));
15+
uint16x8_t t = _mm_cvtsi32_si128(reinterpret_cast<const unsigned int &>(components));
1616

1717
t = _mm_unpacklo_epi8(t, _mm_setzero_si128());
1818
return _mm_unpacklo_epi64(t, t);
1919
}
2020

21-
__m128i make_alpha_u16(unsigned int alpha)
21+
uint16x8_t make_alpha_u16(unsigned int alpha)
2222
{
2323
alpha = (alpha << 6) + 505 * alpha / 1000;
24-
__m128i t = _mm_shufflelo_epi16(_mm_cvtsi32_si128(alpha), 0);
24+
uint16x8_t t = _mm_shufflelo_epi16(_mm_cvtsi32_si128(alpha), 0);
2525
return _mm_unpacklo_epi64(t, t);
2626
}
2727
}
@@ -37,15 +37,15 @@ namespace agge
3737
*pixels = _components;
3838
}
3939

40-
void blender_solid_color::blend4(pixel *pixels, __m128i color_u16, __m128i alpha_u16, unsigned int covers_packed)
40+
void blender_solid_color::blend4(pixel *pixels, uint16x8_t color_u16, uint16x8_t alpha_u16, unsigned int covers_packed)
4141
{
42-
__m128i alpha = _mm_mulhi_epu16(_mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(covers_packed)),
42+
uint16x8_t alpha = _mm_mulhi_epu16(_mm_unpacklo_epi8(_mm_setzero_si128(), _mm_cvtsi32_si128(covers_packed)),
4343
alpha_u16);
4444

4545
alpha = _mm_unpacklo_epi16(alpha, alpha);
4646

47-
__m128i source10 = _mm_loadu_si128(reinterpret_cast<__m128i *>(pixels));
48-
__m128i source32 = _mm_unpackhi_epi8(source10, _mm_setzero_si128());
47+
uint16x8_t source10 = _mm_loadu_si128(reinterpret_cast<uint16x8_t *>(pixels));
48+
uint16x8_t source32 = _mm_unpackhi_epi8(source10, _mm_setzero_si128());
4949
source10 = _mm_unpacklo_epi8(source10, _mm_setzero_si128());
5050

5151
// source -= ((source - color) << 2) * alpha >> 16;
@@ -54,7 +54,7 @@ namespace agge
5454
source10 = _mm_sub_epi16(source10, _mm_mulhi_epi16(_mm_slli_epi16(_mm_sub_epi16(source10, color_u16), 2),
5555
_mm_unpacklo_epi32(alpha, alpha)));
5656

57-
_mm_storeu_si128(reinterpret_cast<__m128i *>(pixels), _mm_packus_epi16(source10, source32));
57+
_mm_storeu_si128(reinterpret_cast<uint16x8_t *>(pixels), _mm_packus_epi16(source10, source32));
5858
}
5959
}
6060
}

src/agge/blenders_simd.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#include <agge/config.h>
2+
3+
#if defined(AGGE_ARCH_INTEL)
4+
#include "blenders_intel.cpp"
5+
#elif defined(AGGE_ARCH_ARM)
6+
#include "blenders_arm.cpp"
7+
#else
8+
#endif

tests/agge/CMakeLists.txt

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ set(AGGE_TEST_SOURCES
2121
RendererTests.cpp
2222
RendererParallelTests.cpp
2323
ScanlineAdapterTests.cpp
24+
SIMDBlendersTests.cpp
2425
StrokeFeaturesTests.cpp
2526
StrokeTests.cpp
2627
VectorRasterizerTests.cpp
@@ -31,9 +32,5 @@ if (WIN32)
3132
else()
3233
endif()
3334

34-
if (AGGE_ARCHITECTURE STREQUAL "intel")
35-
set(AGGE_TEST_SOURCES ${AGGE_TEST_SOURCES} SIMDBlendersTests.cpp)
36-
endif()
37-
3835
add_library(agge.tests SHARED ${AGGE_TEST_SOURCES})
3936
target_link_libraries(agge.tests agge tests-common utfia)

0 commit comments

Comments
 (0)