|
|
1.1 root 1: //
2: // nono
3: // Copyright (C) 2024 nono project
4: // Licensed under nono-license.txt
5: //
6:
7: //
8: // AVX2
9: //
10:
11: #include "accel_avx2.h"
12: #include "planevram.h"
13: #include "renderer.h"
14: #include "videoctlr.h"
15: #include <x86intrin.h>
16:
17: // ホスト CPU、OS が AVX2 をサポートしていれば true を返す。
18: bool
19: DetectAVX2()
20: {
21: uint32 ebx;
22: uint32 ecx;
23: uint32 edx;
24:
25: // CPUID(eax=0x07, ecx=0) で ebx の bit5 が立っていれば、
26: // CPU は AVX2 をサポートしている。
27: __asm__ __volatile__(
28: "cpuid"
29: : // output
30: "=b" (ebx),
31: "=c" (ecx),
32: "=d" (edx)
33: : // input
34: "a" (0x7),
35: "c" (0x0)
36: );
37: bool has_avx2 = ebx & (1U << 5);
38:
39: // CPUID(eax=0x01) で ecx の bit27 (OSXSAVE) が立っていれば、
40: // OS が XSAVE を有効にしている。
41: __asm__ __volatile__(
42: "cpuid"
43: : // output
44: "=b" (ebx),
45: "=c" (ecx),
46: "=d" (edx)
47: : // input
48: "a" (0x1)
49: );
50: bool has_osxsave = ecx & (1U << 27);
51:
52: if (has_avx2 && has_osxsave) {
53: // XSAVE で保存するレジスタセットは XGETBV(0) で読み出せる
54: // XCR0 レジスタに入っていて、
55: // これの bit1 が SSE(XMM?)、bit2 が AVX(YMM?) のようだ。
56: uint32 xcr0;
57: __asm__ __volatile__(
58: "xgetbv"
59: : // output
60: "=a" (xcr0)
61: : // input
62: "c" (0)
63: : // used
64: "edx"
65: );
66: if ((xcr0 & 6) == 6) {
67: return true;
68: }
69: }
70:
71: return false;
72: }
73:
74: // I8 から RGBX への変換。
75: void
76: PlaneVRAMDevice::RenderI8toRGBX_avx2(BitmapRGBX& dst, const BitmapI8& view,
77: const std::vector<uint8>& view_mod)
78: {
79: for (int y = 0; y < view_mod.size(); y++) {
80: if (view_mod[y] == 0) {
81: continue;
82: }
83:
84: const uint8 *s8 = view.GetRowPtr(y);
85: uint32 *d = (uint32 *)dst.GetRowPtr(y);
86:
87: const uint32 *s = (const uint32 *)s8;
88: const uint32 *send = (const uint32 *)(s8 + view.GetWidth());
89: for (; s < send;) {
90: // 8ピクセルずつ処理する
91:
92: // s は BitmapI8 なのでビッグエンディアン並びに相当。
93: // idx0 = $00000000'00000000'I7I6I5I4'I3I2I1I0
94: __m128i idx0 = _mm_loadl_epi64((const __m128i *)s);
95: s += 2;
96:
97: // idx = $000000I7'000000I6'000000I5'000000I4'
98: // 000000I3'000000I2'000000I1'000000I0
99: __m256i idx = _mm256_cvtepu8_epi32(idx0);
100:
101: // 8個の 32bit インデックスから XBGR を取得
102: // c = $00B7G7R7'00B6G6R6'00B5G5R5'00B4G4R4'
103: // 00B3G3R3'00B2G2R2'00B1G1R1'00B0G0R0
104: __m256i c = _mm256_i32gather_epi32(
105: (const int *)palette, idx, 4);
106:
107: _mm256_storeu_si256((__m256i *)d, c);
108: d += 8;
109: }
110: }
111: }
112:
113: // RGBX から RGB への変換。
114: /*static*/ void
115: Renderer::RGBXtoRGB_avx2(BitmapRGB& dst, const BitmapRGBX& src)
116: {
117: for (int y = 0, yend = src.GetHeight(); y < yend; y++) {
118: const uint32 *s = (const uint32 *)src.GetRowPtr(y);
119: uint32 *d = (uint32 *)dst.GetRowPtr(y);
120:
121: const uint32 *send = s + src.GetWidth();
122: for (; s < send; ) {
123: // 8ピクセルずつ処理する
124:
125: // c = $x7B7G7R7'x6B6G6R6'x5B5G5R5'x4B4G4R4'
126: // x3B3G3R3'x2B2G2R2'x1B1G1R1'x0B0G0R0
127: __m256i c = _mm256_loadu_si256((const __m256i *)s);
128: s += 8;
129:
130: // 256bit リニアに使えれば下詰めで済んだんだが、
131: // 上位 128bit と下位 128bit をまたいでシャッフルは
132: // 出来なくて、かつこの後のストアは連続領域への
133: // 書き込みになるので、中央でつなげておく。うーん。
134: // c = $00000000'B7G7R7B6'G6R6B5G5'R5B4G4R4'
135: // B3G3R3B2'G2R2B1G1'R1B0G0R0'00000000
136: __m256i shuff = _mm256_set_epi8(
137: -1,-1,-1,-1, 14,13,12,10, 9, 8, 6, 5, 4, 2, 1, 0,
138: 14,13,12,10, 9, 8, 6, 5, 4, 2, 1, 0, -1,-1,-1,-1
139: );
140: __m256i res = _mm256_shuffle_epi8(c, shuff);
141:
142: // 先頭(下位)と末尾(上位)の 32bit ずつを除いたところを
143: // 書き出す。
144: __m256i mask = _mm256_set_epi32(
145: 0, -1, -1, -1, -1, -1, -1, 0
146: );
147: _mm256_maskstore_epi32((int *)(d - 1), mask, res);
148: d += 6;
149: }
150: }
151: }
152:
153: // コントラスト (0-254) を適用。
154: /*static*/ void
155: VideoCtlrDevice::RenderContrast_avx2(BitmapRGBX& dst, const BitmapRGBX& src,
156: uint32 contrast)
157: {
158: #define USE_NEW
159:
160: #if defined(USE_NEW)
161: // 8ビットのまま、筆算の掛け算と固定小数点の要領で、
162: // ビットが立っている桁だけ右シフトしたものを足していく。
163: // 例えば $b7(%1011'0111) を %1001'0000/256 (= 0.562) 倍する場合、
164: // 除数の
165: // b7=%1 なので $b7 >> 1 = %0101'1011
166: // b4=%1 なので $b7 >> 4 = %0000'1011
167: // : +
168: // ----------
169: // 結果は %0110'0110 (= $66)
170: //
171: // 立っているビット数が増えると演算回数で不利になるので、5 ビット
172: // 以上立っている場合は被除数を反転して計算して元の値から引く。
173: int n = contrast == 0 ? 0 : __builtin_popcount(contrast);
174: bool use_sub;
175: if (n <= 4) {
176: use_sub = false;
177: } else {
178: contrast = (uint32)-contrast;
179: use_sub = true;
180: }
181: std::array<int, 4> shift_count;
182: __m256i shift_mask[4];
183: n = 0;
184: for (int i = 0; i < 8; i++) {
185: if ((contrast & (0x80 >> i)) != 0) {
186: shift_count[n] = i + 1;
187: shift_mask[n] = _mm256_set1_epi8(0xffU >> shift_count[n]);
188: n++;
189: }
190: }
191: #else
192: const __m256i vcontrast = _mm256_set1_epi16(contrast);
193: const __m256i idxh = _mm256_set_epi32(-1, -1, -1, -1, 7, 6, 3, 2);
194: const __m256i idxl = _mm256_set_epi32(-1, -1, -1, -1, 5, 4, 1, 0);
195: #endif
196:
197: for (int y = 0, yend = src.GetHeight(); y < yend; y++) {
198: const uint32 *s = (const uint32 *)src.GetRowPtr(y);
199: uint32 *d = (uint32 *)dst.GetRowPtr(y);
200:
201: const uint32 *send = s + src.GetWidth();
202: for (; s < send; ) {
203: // 8ピクセルずつ処理する
204:
205: __m256i a = _mm256_loadu_si256((const __m256i *)s);
206: s += 8;
207: #if defined(USE_NEW)
208: // 144 usec
209:
210: __m256i r;
211: if (__predict_false(n == 0)) {
212: r = _mm256_setzero_si256();
213: } else {
214: // AVX には 8 ビットのシフト演算がないので、16 ビット
215: // 単位で右シフトして、右にはみ出た分をマスクする…。
216: auto b0 = _mm256_srli_epi16(a, shift_count[0]);
217: r = _mm256_and_si256(b0, shift_mask[0]);
218:
219: if (n >= 2) {
220: auto b1 = _mm256_srli_epi16(a, shift_count[1]);
221: auto c1 = _mm256_and_si256(b1, shift_mask[1]);
222: r = _mm256_add_epi8(r, c1);
223:
224: if (n >= 3) {
225: auto b2 = _mm256_srli_epi16(a, shift_count[2]);
226: auto c2 = _mm256_and_si256(b2, shift_mask[2]);
227: r = _mm256_add_epi8(r, c2);
228:
229: if (n >= 4) {
230: auto b3 = _mm256_srli_epi16(a, shift_count[3]);
231: auto c3 = _mm256_and_si256(b3, shift_mask[3]);
232: r = _mm256_add_epi8(r, c3);
233: }
234: }
235: }
236: }
237: if (use_sub) {
238: r = _mm256_sub_epi8(a, r);
239: }
240: #else
241: // 146 usec
242:
243: // a = $x7B7G7R7'x6B6G6R6'x5B5G5R5'x4B4G4R4'
244: // x3B3G3R3'x2B2G2R2'x1B1G1R1'x0B0G0R0
245:
246: // 上位 128bit を下位 128bit に持っていく必要がどうせあるし、
247: // 最後に packus 一発で元に戻せる位置に permute する。
248: // ah = $...'x7B7G7R7'x6B6G6R6'x3B3G3R3'x2B2G2R2
249: // al = $...'x5B5G5R5'x4B4G4R4'x1B1G1R1'x0B0G0R0
250: __m256i ah = _mm256_permutevar8x32_epi32(a, idxh);
251: __m256i al = _mm256_permutevar8x32_epi32(a, idxl);
252:
253: // bh = $00x700B7'00G700R7'00x600B6'00G600R6'
254: // 00x300B3'00G300R3'00x200B2'00G200R2
255: // bl = $00x500B5'00G500R5'00x400B4'00G400R4'
256: // 00x100B1'00G100R1'00x000B0'00G000R0
257: __m256i bh = _mm256_cvtepu8_epi16(_mm256_castsi256_si128(ah));
258: __m256i bl = _mm256_cvtepu8_epi16(_mm256_castsi256_si128(al));
259:
260: __m256i ch = _mm256_mullo_epi16(bh, vcontrast);
261: __m256i cl = _mm256_mullo_epi16(bl, vcontrast);
262:
263: __m256i sh = _mm256_srli_epi16(ch, 8);
264: __m256i sl = _mm256_srli_epi16(cl, 8);
265:
266: // r = $x7B7G7R7'x6B6G6R6'x5B5G5R5'x4B4G4R4'
267: // x3B3G3R3'x2B2G2R2'x1B1G1R1'x0B0G0R0
268: __m256i r = _mm256_packus_epi16(sl, sh);
269: #endif
270: _mm256_storeu_si256((__m256i *)d, r);
271: d += 8;
272: }
273: }
274: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.