openMSX
Deflicker.cc
Go to the documentation of this file.
1#include "Deflicker.hh"
2
3#include "PixelOperations.hh"
4#include "RawFrame.hh"
5
6#include "vla.hh"
7#include "xrange.hh"
8
9#include <bit>
10#ifdef __SSE2__
11#include <emmintrin.h>
12#endif
13
14namespace openmsx {
15
16using Pixel = uint32_t;
17
18Deflicker::Deflicker(std::span<std::unique_ptr<RawFrame>, 4> lastFrames_)
19 : lastFrames(lastFrames_)
20{
21}
22
23Deflicker::~Deflicker() = default;
24
30
31unsigned Deflicker::getLineWidth(unsigned line) const
32{
33 return lastFrames[0]->getLineWidthDirect(line);
34}
35
36#ifdef __SSE2__
37static __m128i blend(__m128i x, __m128i y)
38{
39 // 32bpp
40 return _mm_avg_epu8(x, y);
41}
42
43static __m128i uload(const Pixel* ptr, ptrdiff_t byteOffst)
44{
45 const auto* p8 = std::bit_cast<const char *>(ptr);
46 const auto* p128 = std::bit_cast<const __m128i*>(p8 + byteOffst);
47 return _mm_loadu_si128(p128);
48}
49
50static void ustore(Pixel* ptr, ptrdiff_t byteOffst, __m128i val)
51{
52 auto* p8 = std::bit_cast< char *>(ptr);
53 auto* p128 = std::bit_cast<__m128i*>(p8 + byteOffst);
54 return _mm_storeu_si128(p128, val);
55}
56
57static __m128i compare(__m128i x, __m128i y)
58{
59 // 32bpp
60 return _mm_cmpeq_epi32(x, y);
61}
62#endif
63
64std::span<const Pixel> Deflicker::getUnscaledLine(
65 unsigned line, std::span<Pixel> helpBuf) const
66{
67 unsigned width0 = lastFrames[0]->getLineWidthDirect(line);
68 unsigned width1 = lastFrames[1]->getLineWidthDirect(line);
69 unsigned width2 = lastFrames[2]->getLineWidthDirect(line);
70 unsigned width3 = lastFrames[3]->getLineWidthDirect(line);
71 const Pixel* line0 = lastFrames[0]->getLineDirect(line).data();
72 const Pixel* line1 = lastFrames[1]->getLineDirect(line).data();
73 const Pixel* line2 = lastFrames[2]->getLineDirect(line).data();
74 const Pixel* line3 = lastFrames[3]->getLineDirect(line).data();
75 if ((width0 != width3) || (width0 != width2) || (width0 != width1)) {
76 // Not all the same width.
77 return std::span{line0, width0};
78 }
79
80 // Prefer to write directly to the output buffer, if that's not
81 // possible store the intermediate result in a temp buffer.
82 VLA_SSE_ALIGNED(Pixel, buf2, width0);
83 auto* buf = helpBuf.data();
84 Pixel* out = (width0 <= helpBuf.size()) ? buf : buf2.data();
85
86 // Detect pixels that alternate between two different color values and
87 // replace those with the average color. We search for an alternating
88 // sequence with length (at least) 4. Or IOW we look for "A B A B".
89 // The implementation below also detects a constant pixel value
90 // "A A A A" as alternating between "A" and "A", but that's fine.
91 Pixel* dst = out;
92 size_t remaining = width0;
93#ifdef __SSE2__
94 size_t pixelsPerSSE = sizeof(__m128i) / sizeof(Pixel);
95 size_t widthSSE = remaining & ~(pixelsPerSSE - 1); // rounded down to a multiple of pixels in a SSE register
96 line0 += widthSSE;
97 line1 += widthSSE;
98 line2 += widthSSE;
99 line3 += widthSSE;
100 dst += widthSSE;
101 auto byteOffst = -ptrdiff_t(widthSSE * sizeof(Pixel));
102
103 while (byteOffst < 0) {
104 __m128i a0 = uload(line0, byteOffst);
105 __m128i a1 = uload(line1, byteOffst);
106 __m128i a2 = uload(line2, byteOffst);
107 __m128i a3 = uload(line3, byteOffst);
108
109 __m128i e02 = compare(a0, a2); // a0 == a2
110 __m128i e13 = compare(a1, a3); // a1 == a3
111 __m128i cnd = _mm_and_si128(e02, e13); // (a0==a2) && (a1==a3)
112
113 __m128i a01 = blend(a0, a1);
114 __m128i p = _mm_xor_si128(a0, a01);
115 __m128i q = _mm_and_si128(p, cnd);
116 __m128i r = _mm_xor_si128(q, a0); // select(a0, a01, cnd)
117
118 ustore(dst, byteOffst, r);
119 byteOffst += sizeof(__m128i);
120 }
121 remaining &= pixelsPerSSE - 1;
122#endif
123 PixelOperations pixelOps;
124 for (auto x : xrange(remaining)) {
125 dst[x] = ((line0[x] == line2[x]) && (line1[x] == line3[x]))
126 ? pixelOps.template blend<1, 1>(line0[x], line1[x])
127 : line0[x];
128 }
129
130 if (width0 <= helpBuf.size()) {
131 // If it already fits, we're done
132 return std::span{buf, width0};
133 } else {
134 // Otherwise scale so that it does fit.
135 scaleLine(std::span{out, width0}, helpBuf);
136 return helpBuf;
137 }
138}
139
140} // namespace openmsx
std::span< const Pixel > getUnscaledLine(unsigned line, std::span< Pixel > helpBuf) const override
Get a specific line, with the 'native' line-width.
Definition Deflicker.cc:64
Deflicker(std::span< std::unique_ptr< RawFrame >, 4 > lastFrames)
Definition Deflicker.cc:18
unsigned getLineWidth(unsigned line) const override
Gets the number of display pixels on the given line.
Definition Deflicker.cc:31
void scaleLine(std::span< const Pixel > in, std::span< Pixel > out) const
void setHeight(unsigned height_)
void init(FieldType fieldType_)
(Re)initialize an existing FrameSource.
unsigned getHeight() const
Gets the number of lines in this frame.
@ NONINTERLACED
Interlacing is off for this frame.
This file implemented 3 utility functions:
Definition Autofire.cc:11
CharacterConverter::Pixel Pixel
#define VLA_SSE_ALIGNED(TYPE, NAME, LENGTH)
Definition vla.hh:50
constexpr auto xrange(T e)
Definition xrange.hh:132