File:  [Isaki's NoNo m68k/m88k emulator] / nono / vm / accel_avx2.cpp
Revision 1.1.1.3 (vendor branch): download - view: text, annotated - select for diffs
Wed Apr 29 17:05:50 2026 UTC (2 months, 3 weeks ago) by root
Branches: MAIN, Isaki
CVS tags: v027, v026, v025, HEAD
nono 1.5.0

//
// nono
// Copyright (C) 2024 nono project
// Licensed under nono-license.txt
//

//
// AVX2
//

#include "accel_avx2.h"
#include "videoctlr.h"
#include <x86intrin.h>

// ホスト CPU、OS が AVX2 をサポートしていれば true を返す。
bool
DetectAVX2()
{
	uint32 ebx;
	uint32 ecx;
	uint32 edx;

	// CPUID(eax=0x07, ecx=0) で ebx の bit5 が立っていれば、
	// CPU は AVX2 をサポートしている。
	__asm__ __volatile__(
		"cpuid"
		: // output
			"=b" (ebx),
			"=c" (ecx),
			"=d" (edx)
		: // input
			"a" (0x7),
			"c" (0x0)
	);
	bool has_avx2 = ebx & (1U << 5);

	// CPUID(eax=0x01) で ecx の bit27 (OSXSAVE) が立っていれば、
	// OS が XSAVE を有効にしている。
	__asm__ __volatile__(
		"cpuid"
		: // output
			"=b" (ebx),
			"=c" (ecx),
			"=d" (edx)
		: // input
			"a" (0x1)
	);
	bool has_osxsave = ecx & (1U << 27);

	if (has_avx2 && has_osxsave) {
		// XSAVE で保存するレジスタセットは XGETBV(0) で読み出せる
		// XCR0 レジスタに入っていて、
		// これの bit1 が SSE(XMM?)、bit2 が AVX(YMM?) のようだ。
		uint32 xcr0;
		__asm__ __volatile__(
			"xgetbv"
			: // output
				"=a" (xcr0)
			: // input
				"c" (0)
			: // used
				"edx"
		);
		if ((xcr0 & 6) == 6) {
			return true;
		}
	}

	return false;
}

// コントラスト (1-254) を適用。dst サイズでクリップする。
// (ちなみに _gen は 409 usec)
/*static*/ void
VideoCtlrDevice::RenderContrast_avx2(BitmapRGBX& dst, const BitmapRGBX& src,
	uint32 contrast)
{
	// 8ビットのまま、筆算の掛け算と固定小数点の要領で、
	// ビットが立っている桁だけ右シフトしたものを足していく。
	// 例えば $b7(%1011'0111) を %1001'0000/256 (= 0.562) 倍する場合、
	// 除数の
	// bit7=%1 なので $b7 >> 1 = %0101'1011
	// bit4=%1 なので $b7 >> 4 = %0000'1011
	//                          +
	//                           ----------
	// 結果は                    %0110'0110 (= $66)
	//
	// 立っているビット数が増えると演算回数で不利になるので、5 ビット
	// 以上立っている場合は被除数を反転して計算して元の値から引く。
	bool use_sub;
	uint n = __builtin_popcount(contrast);
	if (n <= 4) {
		use_sub = false;
	} else {
		contrast = (uint32)-contrast;
		use_sub = true;
	}
	std::array<int, 4> shift_count;
	__m256i shift_mask[4];
	n = 0;
	for (int i = 0; i < 8; i++) {
		if ((contrast & (0x80 >> i)) != 0) {
			shift_count[n] = i + 1;
			shift_mask[n] = _mm256_set1_epi8(0xffU >> shift_count[n]);
			n++;
		}
	}

	// 今のところ幅は 8ピクセルで割り切れる。
	uint width = dst.GetWidth();
	for (uint y = 0, yend = dst.GetHeight(); y < yend; y++) {
		const uint32 *s32 = (const uint32 *)src.GetRowPtr(y);
		uint32 *d32 = (uint32 *)dst.GetRowPtr(y);
		uint32 *d32end = d32 + width;
		for (; d32 < d32end; ) {
			// 8ピクセルずつ処理する。
			// 101 usec

			__m256i a = _mm256_load_si256((const __m256i *)s32);
			s32 += 8;

			__m256i r;
			// AVX には 8 ビットのシフト演算がないので、16 ビット
			// 単位で右シフトして、右にはみ出た分をマスクする…。
			auto b0 = _mm256_srli_epi16(a, shift_count[0]);
			r = _mm256_and_si256(b0, shift_mask[0]);

			if (n >= 2) {
				auto b1 = _mm256_srli_epi16(a, shift_count[1]);
				auto c1 = _mm256_and_si256(b1, shift_mask[1]);
				r = _mm256_add_epi8(r, c1);

				if (n >= 3) {
					auto b2 = _mm256_srli_epi16(a, shift_count[2]);
					auto c2 = _mm256_and_si256(b2, shift_mask[2]);
					r = _mm256_add_epi8(r, c2);

					if (n >= 4) {
						auto b3 = _mm256_srli_epi16(a, shift_count[3]);
						auto c3 = _mm256_and_si256(b3, shift_mask[3]);
						r = _mm256_add_epi8(r, c3);
					}
				}
			}
			if (use_sub) {
				r = _mm256_sub_epi8(a, r);
			}

			_mm256_store_si256((__m256i *)d32, r);
			d32 += 8;
		}
	}
}

unix.superglobalmegacorp.com

This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.