text: #include "FBPostProcessor.hh"
  #include "RawFrame.hh"
  #include "StretchScalerOutput.hh"
  #include "ScalerOutput.hh"
  #include "RenderSettings.hh"
  #include "Scaler.hh"
  #include "ScalerFactory.hh"
  #include "SDLOutputSurface.hh"
  #include "aligned.hh"
  #include "checked_cast.hh"
  #include "random.hh"
  #include "xrange.hh"
  #include <algorithm>
  #include <cassert>
  #include <cmath>
  #include <cstdint>
  #include <cstddef>
  #include <numeric>
  #ifdef __SSE2__
  #include <emmintrin.h>
  #endif
  
  namespace openmsx {
  
  constexpr unsigned NOISE_SHIFT = 8192;
  constexpr unsigned NOISE_BUF_SIZE = 2 * NOISE_SHIFT;
  ALIGNAS_SSE static signed char noiseBuf[NOISE_BUF_SIZE];
  
  template<typename Pixel>
  void FBPostProcessor<Pixel>::preCalcNoise(float factor)
  {
  	// We skip noise drawing if the factor is 0, so there is no point in
  	// initializing the random data in that case.
  	if (factor == 0.0f) return;
  
  	// for 32bpp groups of 4 consecutive noiseBuf elements (starting at
  	// 4 element boundaries) must have the same value. Later optimizations
  	// depend on it.
  
  	float scale[4];
  	if (sizeof(Pixel) == 4) {
  		// 32bpp
  		// TODO ATM we compensate for big endian here. A better
  		// alternative is to turn noiseBuf into an array of ints (it's
  		// now bytes) and in the 16bpp code extract R,G,B components
  		// from those ints
  		const auto p = Pixel(OPENMSX_BIGENDIAN ? 0x00010203
  		                                       : 0x03020100);
  		// TODO we can also fill the array with 'factor' and only set
  		// 'alpha' to 0.0. But PixelOperations doesn't offer a simple
  		// way to get the position of the alpha byte (yet).
  		scale[0] = scale[1] = scale[2] = scale[3] = 0.0f;
  		scale[pixelOps.red  (p)] = factor;
  		scale[pixelOps.green(p)] = factor;
  		scale[pixelOps.blue (p)] = factor;
  	} else {
  		// 16bpp
  		scale[0] = (pixelOps.getMaxRed()   / 255.0f) * factor;
  		scale[1] = (pixelOps.getMaxGreen() / 255.0f) * factor;
  		scale[2] = (pixelOps.getMaxBlue()  / 255.0f) * factor;
  		scale[3] = 0.0f;
  	}
  
  	auto& generator = global_urng(); // fast (non-cryptographic) random numbers
  	std::normal_distribution<float> distribution(0.0f, 1.0f);
  	for (unsigned i = 0; i < NOISE_BUF_SIZE; i += 4) {
  		float r = distribution(generator);
  		noiseBuf[i + 0] = std::clamp(int(roundf(r * scale[0])), -128, 127);
  		noiseBuf[i + 1] = std::clamp(int(roundf(r * scale[1])), -128, 127);
  		noiseBuf[i + 2] = std::clamp(int(roundf(r * scale[2])), -128, 127);
  		noiseBuf[i + 3] = std::clamp(int(roundf(r * scale[3])), -128, 127);
  	}
  }
  
  #ifdef __SSE2__
  static inline void drawNoiseLineSse2(uint32_t* buf_, signed char* noise, size_t width)
  {
  	// To each of the RGBA color components (a value in range [0..255]) we
  	// want to add a signed noise value (in range [-128..127]) and also clip
  	// the result to the range [0..255]. There is no SSE instruction that
  	// directly performs this operation. But we can:
  	//   - subtract 128 from the RGBA component to get a signed byte
  	//   - perform the addition with signed saturation
  	//   - add 128 to the result to get back to the unsigned byte range
  	// For 8-bit values the following 3 expressions are equivalent:
  	//   x + 128 == x - 128 == x ^ 128
  	// So the expression becomes:
  	//   signed_add_sat(value ^ 128, noise) ^ 128
  	// The following loop does just that, though it processes 64 bytes per
  	// iteration.
  	ptrdiff_t x = width * sizeof(uint32_t);
  	assert((x & 63) == 0);
  	assert((uintptr_t(buf_) & 15) == 0);
  
  	char* buf = reinterpret_cast<char*>(buf_)  + x;
  	char* nse = reinterpret_cast<char*>(noise) + x;
  	x = -x;
  
  	__m128i b7 = _mm_set1_epi8(-128); // 0x80
  	do {
  		__m128i i0 = _mm_load_si128(reinterpret_cast<__m128i*>(buf + x +  0));
  		__m128i i1 = _mm_load_si128(reinterpret_cast<__m128i*>(buf + x + 16));
  		__m128i i2 = _mm_load_si128(reinterpret_cast<__m128i*>(buf + x + 32));
  		__m128i i3 = _mm_load_si128(reinterpret_cast<__m128i*>(buf + x + 48));
  		__m128i n0 = _mm_load_si128(reinterpret_cast<__m128i*>(nse + x +  0));
  		__m128i n1 = _mm_load_si128(reinterpret_cast<__m128i*>(nse + x + 16));
  		__m128i n2 = _mm_load_si128(reinterpret_cast<__m128i*>(nse + x + 32));
  		__m128i n3 = _mm_load_si128(reinterpret_cast<__m128i*>(nse + x + 48));
  		__m128i o0 = _mm_xor_si128(_mm_adds_epi8(_mm_xor_si128(i0, b7), n0), b7);
  		__m128i o1 = _mm_xor_si128(_mm_adds_epi8(_mm_xor_si128(i1, b7), n1), b7);
  		__m128i o2 = _mm_xor_si128(_mm_adds_epi8(_mm_xor_si128(i2, b7), n2), b7);
  		__m128i o3 = _mm_xor_si128(_mm_adds_epi8(_mm_xor_si128(i3, b7), n3), b7);
  		_mm_store_si128(reinterpret_cast<__m128i*>(buf + x +  0), o0);
  		_mm_store_si128(reinterpret_cast<__m128i*>(buf + x + 16), o1);
  		_mm_store_si128(reinterpret_cast<__m128i*>(buf + x + 32), o2);
  		_mm_store_si128(reinterpret_cast<__m128i*>(buf + x + 48), o3);
  		x += 4 * sizeof(__m128i);
  	} while (x < 0);
  }
  #endif
  
  /** Add noise to the given pixel.
   * @param p contains 4 8-bit unsigned components, so components have range [0, 255]
   * @param n contains 4 8-bit   signed components, so components have range [-128, 127]
   * @result per component result of clip<0, 255>(p + n)
   */
  static constexpr uint32_t addNoise4(uint32_t p, uint32_t n)
  {
  	// unclipped result (lower 8 bits of each component)
  	// alternative:
  	//   uint32_t s20 = ((p & 0x00FF00FF) + (n & 0x00FF00FF)) & 0x00FF00FF;
  	//   uint32_t s31 = ((p & 0xFF00FF00) + (n & 0xFF00FF00)) & 0xFF00FF00;
  	//   uint32_t s = s20 | s31;
  	uint32_t s0 = p + n;                     // carry spills to neighbors
  	uint32_t ci = (p ^ n ^ s0) & 0x01010100; // carry-in bits of prev sum
  	uint32_t s  = s0 - ci;                   // subtract carry bits again
  
  	// Underflow of a component happens ONLY
  	//   WHEN input  component is in range [0, 127]
  	//   AND  noise  component is negative
  	//   AND  result component is in range [128, 255]
  	// Overflow of a component happens ONLY
  	//   WHEN input  component in in range [128, 255]
  	//   AND  noise  component is positive
  	//   AND  result component is in range [0, 127]
  	// Create a mask per component containing 00 for no under/overflow,
  	//                                        FF for    under/overflow
  	// ((~p & n & s) | (p & ~n & ~s)) == ((p ^ n) & (p ^ s))
  	uint32_t t = (p ^ n) & (p ^ s) & 0x80808080;
  	uint32_t u1 = t & s; // underflow   (alternative: u1 = t & n)
  	// alternative1: uint32_t u2 = u1 | (u1 >> 1);
  	//               uint32_t u4 = u2 | (u2 >> 2);
  	//               uint32_t u8 = u4 | (u4 >> 4);
  	// alternative2: uint32_t u8 = (u1 >> 7) * 0xFF;
  	uint32_t u8 = (u1 << 1) - (u1 >> 7);
  
  	uint32_t o1 = t & p; // overflow
  	uint32_t o8 = (o1 << 1) - (o1 >> 7);
  
  	// clip result
  	return (s & (~u8)) | o8;
  }
  
  template<typename Pixel>
  void FBPostProcessor<Pixel>::drawNoiseLine(
  		Pixel* buf, signed char* noise, size_t width)
  {
  #ifdef __SSE2__
  	if (sizeof(Pixel) == 4) {
  		// cast to avoid compilation error in case of 16bpp (even
  		// though this code is dead in that case).
  		auto* buf32 = reinterpret_cast<uint32_t*>(buf);
  		drawNoiseLineSse2(buf32, noise, width);
  		return;
  	}
  #endif
  	// c++ version
  	if (sizeof(Pixel) == 4) {
  		// optimized version for 32bpp
  		auto* noise4 = reinterpret_cast<uint32_t*>(noise);
  		for (auto i : xrange(width)) {
  			buf[i] = addNoise4(buf[i], noise4[i]);
  		}
  	} else {
  		int mr = pixelOps.getMaxRed();
  		int mg = pixelOps.getMaxGreen();
  		int mb = pixelOps.getMaxBlue();
  		for (auto i : xrange(width)) {
  			Pixel p = buf[i];
  			int r = pixelOps.red(p);
  			int g = pixelOps.green(p);
  			int b = pixelOps.blue(p);
  
  			r += noise[4 * i + 0];
  			g += noise[4 * i + 1];
  			b += noise[4 * i + 2];
  
  			r = std::min(std::max(r, 0), mr);
  			g = std::min(std::max(g, 0), mg);
  			b = std::min(std::max(b, 0), mb);
  
  			buf[i] = pixelOps.combine(r, g, b);
  		}
  	}
  }
  
  template<typename Pixel>
  void FBPostProcessor<Pixel>::drawNoise(OutputSurface& output_)
  {
  	if (renderSettings.getNoise() == 0.0f) return;
  
  	auto& output = checked_cast<SDLOutputSurface&>(output_);
  	auto [w, h] = output.getLogicalSize();
  	auto pixelAccess = output.getDirectPixelAccess();
  	for (auto y : xrange(h)) {
  		auto* buf = pixelAccess.getLinePtr<Pixel>(y);
  		drawNoiseLine(buf, &noiseBuf[noiseShift[y]], w);
  	}
  }
  
  template<typename Pixel>
  void FBPostProcessor<Pixel>::update(const Setting& setting)
  {
  	VideoLayer::update(setting);
  	auto& noiseSetting = renderSettings.getNoiseSetting();
  	if (&setting == &noiseSetting) {
  		preCalcNoise(noiseSetting.getDouble());
  	}
  }
  
  
  template<typename Pixel>
  FBPostProcessor<Pixel>::FBPostProcessor(MSXMotherBoard& motherBoard_,
  	Display& display_, OutputSurface& screen_, const std::string& videoSource,
  	unsigned maxWidth_, unsigned height_, bool canDoInterlace_)
  	: PostProcessor(
  		motherBoard_, display_, screen_, videoSource, maxWidth_, height_,
  		canDoInterlace_)
  	, noiseShift(screen.getLogicalHeight())
  	, pixelOps(screen.getPixelFormat())
  {
  	scaleAlgorithm = RenderSettings::NO_SCALER;
  	scaleFactor = unsigned(-1);
  	stretchWidth = unsigned(-1);
  
  	auto& noiseSetting = renderSettings.getNoiseSetting();
  	noiseSetting.attach(*this);
  	preCalcNoise(noiseSetting.getDouble());
  	assert((screen.getLogicalWidth() * sizeof(Pixel)) < NOISE_SHIFT);
  }
  
  template<typename Pixel>
  FBPostProcessor<Pixel>::~FBPostProcessor()
  {
  	renderSettings.getNoiseSetting().detach(*this);
  }
  
  template<typename Pixel>
  void FBPostProcessor<Pixel>::paint(OutputSurface& output_)
  {
  	auto& output = checked_cast<SDLOutputSurface&>(output_);
  	if (renderSettings.getInterleaveBlackFrame()) {
  		interleaveCount ^= 1;
  		if (interleaveCount) {
  			output.clearScreen();
  			return;
  		}
  	}
  
  	if (!paintFrame) return;
  
  	// New scaler algorithm selected? Or different horizontal stretch?
  	auto algo = renderSettings.getScaleAlgorithm();
  	unsigned factor = renderSettings.getScaleFactor();
  	unsigned inWidth = lrintf(renderSettings.getHorizontalStretch());
  	if ((scaleAlgorithm != algo) || (scaleFactor != factor) ||
  	    (inWidth != stretchWidth) || (lastOutput != &output)) {
  		scaleAlgorithm = algo;
  		scaleFactor = factor;
  		stretchWidth = inWidth;
  		lastOutput = &output;
  		currScaler = ScalerFactory<Pixel>::createScaler(
  			PixelOperations<Pixel>(output.getPixelFormat()),
  			renderSettings);
  		stretchScaler = StretchScalerOutputFactory<Pixel>::create(
  			output, pixelOps, inWidth);
  	}
  
  	// Scale image.
  	const unsigned srcHeight = paintFrame->getHeight();
  	const unsigned dstHeight = output.getLogicalHeight();
  
  	unsigned g = std::gcd(srcHeight, dstHeight);
  	unsigned srcStep = srcHeight / g;
  	unsigned dstStep = dstHeight / g;
  
  	// TODO: Store all MSX lines in RawFrame and only scale the ones that fit
  	//       on the PC screen, as a preparation for resizable output window.
  	unsigned srcStartY = 0;
  	unsigned dstStartY = 0;
  	while (dstStartY < dstHeight) {
  		// Currently this is true because the source frame height
  		// is always >= dstHeight/(dstStep/srcStep).
  		assert(srcStartY < srcHeight);
  
  		// get region with equal lineWidth
  		unsigned lineWidth = getLineWidth(paintFrame, srcStartY, srcStep);
  		unsigned srcEndY = srcStartY + srcStep;
  		unsigned dstEndY = dstStartY + dstStep;
  		while ((srcEndY < srcHeight) && (dstEndY < dstHeight) &&
  		       (getLineWidth(paintFrame, srcEndY, srcStep) == lineWidth)) {
  			srcEndY += srcStep;
  			dstEndY += dstStep;
  		}
  
  		// fill region
  		//fprintf(stderr, "post processing lines %d-%d: %d\n",
  		//        srcStartY, srcEndY, lineWidth);
  		currScaler->scaleImage(
  			*paintFrame, superImposeVideoFrame,
  			srcStartY, srcEndY, lineWidth, // source
  			*stretchScaler, dstStartY, dstEndY); // dest
  
  		// next region
  		srcStartY = srcEndY;
  		dstStartY = dstEndY;
  	}
  
  	drawNoise(output);
  
  	output.flushFrameBuffer();
  }
  
  template<typename Pixel>
  std::unique_ptr<RawFrame> FBPostProcessor<Pixel>::rotateFrames(
  	std::unique_ptr<RawFrame> finishedFrame, EmuTime::param time)
  {
  	auto& generator = global_urng(); // fast (non-cryptographic) random numbers
  	std::uniform_int_distribution<int> distribution(0, NOISE_SHIFT / 16 - 1);
  	for (auto y : xrange(screen.getLogicalHeight())) {
  		noiseShift[y] = distribution(generator) * 16;
  	}
  
  	return PostProcessor::rotateFrames(std::move(finishedFrame), time);
  }
  
  
  // Force template instantiation.
  #if HAVE_16BPP
  template class FBPostProcessor<uint16_t>;
  #endif
  #if HAVE_32BPP
  template class FBPostProcessor<uint32_t>;
  #endif
  
  } // namespace openmsx
  