openMSX
Deflicker.cc
Go to the documentation of this file.
1#include "Deflicker.hh"
2#include "RawFrame.hh"
3#include "PixelOperations.hh"
4#include "one_of.hh"
5#include "unreachable.hh"
6#include "vla.hh"
7#include "xrange.hh"
8#include "build-info.hh"
9#include <concepts>
10#include <memory>
11#ifdef __SSE2__
12#include <emmintrin.h>
13#endif
14
15namespace openmsx {
16
17template<std::unsigned_integral Pixel> class DeflickerImpl final : public Deflicker
18{
19public:
21 std::span<std::unique_ptr<RawFrame>, 4> lastFrames);
22
23private:
24 [[nodiscard]] const void* getLineInfo(
25 unsigned line, unsigned& width,
26 void* buf, unsigned bufWidth) const override;
27
28private:
30};
31
32
33std::unique_ptr<Deflicker> Deflicker::create(
34 const PixelFormat& format,
35 std::span<std::unique_ptr<RawFrame>, 4> lastFrames)
36{
37#if HAVE_16BPP
38 if (format.getBytesPerPixel() == 2) {
39 return std::make_unique<DeflickerImpl<uint16_t>>(format, lastFrames);
40 }
41#endif
42#if HAVE_32BPP
43 if (format.getBytesPerPixel() == 4) {
44 return std::make_unique<DeflickerImpl<uint32_t>>(format, lastFrames);
45 }
46#endif
47 UNREACHABLE; return nullptr; // avoid warning
48}
49
50
52 std::span<std::unique_ptr<RawFrame>, 4> lastFrames_)
54 , lastFrames(lastFrames_)
55{
56}
57
59{
62}
63
64unsigned Deflicker::getLineWidth(unsigned line) const
65{
66 return lastFrames[0]->getLineWidthDirect(line);
67}
68
69
70template<std::unsigned_integral Pixel>
72 std::span<std::unique_ptr<RawFrame>, 4> lastFrames_)
73 : Deflicker(format, lastFrames_)
74 , pixelOps(format)
75{
76}
77
78#ifdef __SSE2__
79template<std::unsigned_integral Pixel>
80static __m128i blend(__m128i x, __m128i y, Pixel blendMask)
81{
82 if constexpr (sizeof(Pixel) == 4) {
83 // 32bpp
84 return _mm_avg_epu8(x, y);
85 } else {
86 // 16bpp, (x & y) + (((x ^ y) & blendMask) >> 1)
87 __m128i m = _mm_set1_epi16(blendMask);
88 __m128i a = _mm_and_si128(x, y);
89 __m128i b = _mm_xor_si128(x, y);
90 __m128i c = _mm_and_si128(b, m);
91 __m128i d = _mm_srli_epi16(c, 1);
92 return _mm_add_epi16(a, d);
93 }
94}
95
96template<std::unsigned_integral Pixel>
97static __m128i uload(const Pixel* ptr, ptrdiff_t byteOffst)
98{
99 const auto* p8 = reinterpret_cast<const char *>(ptr);
100 const auto* p128 = reinterpret_cast<const __m128i*>(p8 + byteOffst);
101 return _mm_loadu_si128(p128);
102}
103
104template<std::unsigned_integral Pixel>
105static void ustore(Pixel* ptr, ptrdiff_t byteOffst, __m128i val)
106{
107 auto* p8 = reinterpret_cast< char *>(ptr);
108 auto* p128 = reinterpret_cast<__m128i*>(p8 + byteOffst);
109 return _mm_storeu_si128(p128, val);
110}
111
112template<std::unsigned_integral Pixel>
113static __m128i compare(__m128i x, __m128i y)
114{
115 static_assert(sizeof(Pixel) == one_of(2u, 4u));
116 if constexpr (sizeof(Pixel) == 4) {
117 return _mm_cmpeq_epi32(x, y);
118 } else {
119 return _mm_cmpeq_epi16(x, y);
120 }
121}
122#endif
123
124template<std::unsigned_integral Pixel>
125const void* DeflickerImpl<Pixel>::getLineInfo(
126 unsigned line, unsigned& width, void* buf_, unsigned bufWidth) const
127{
128 unsigned width0 = lastFrames[0]->getLineWidthDirect(line);
129 unsigned width1 = lastFrames[1]->getLineWidthDirect(line);
130 unsigned width2 = lastFrames[2]->getLineWidthDirect(line);
131 unsigned width3 = lastFrames[3]->getLineWidthDirect(line);
132 const Pixel* line0 = lastFrames[0]->template getLineDirect<Pixel>(line).data();
133 const Pixel* line1 = lastFrames[1]->template getLineDirect<Pixel>(line).data();
134 const Pixel* line2 = lastFrames[2]->template getLineDirect<Pixel>(line).data();
135 const Pixel* line3 = lastFrames[3]->template getLineDirect<Pixel>(line).data();
136 if ((width0 != width3) || (width0 != width2) || (width0 != width1)) {
137 // Not all the same width.
138 width = width0;
139 return line0;
140 }
141
142 // Prefer to write directly to the output buffer, if that's not
143 // possible store the intermediate result in a temp buffer.
144 VLA_SSE_ALIGNED(Pixel, buf2, width0);
145 auto* buf = static_cast<Pixel*>(buf_);
146 Pixel* out = (width0 <= bufWidth) ? buf : buf2.data();
147
148 // Detect pixels that alternate between two different color values and
149 // replace those with the average color. We search for an alternating
150 // sequence with length (at least) 4. Or IOW we look for "A B A B".
151 // The implementation below also detects a constant pixel value
152 // "A A A A" as alternating between "A" and "A", but that's fine.
153 Pixel* dst = out;
154 size_t remaining = width0;
155#ifdef __SSE2__
156 size_t pixelsPerSSE = sizeof(__m128i) / sizeof(Pixel);
157 size_t widthSSE = remaining & ~(pixelsPerSSE - 1); // rounded down to a multiple of pixels in a SSE register
158 line0 += widthSSE;
159 line1 += widthSSE;
160 line2 += widthSSE;
161 line3 += widthSSE;
162 dst += widthSSE;
163 auto byteOffst = -ptrdiff_t(widthSSE * sizeof(Pixel));
164
165 Pixel blendMask = pixelOps.getBlendMask();
166 while (byteOffst < 0) {
167 __m128i a0 = uload(line0, byteOffst);
168 __m128i a1 = uload(line1, byteOffst);
169 __m128i a2 = uload(line2, byteOffst);
170 __m128i a3 = uload(line3, byteOffst);
171
172 __m128i e02 = compare<Pixel>(a0, a2); // a0 == a2
173 __m128i e13 = compare<Pixel>(a1, a3); // a1 == a3
174 __m128i cnd = _mm_and_si128(e02, e13); // (a0==a2) && (a1==a3)
175
176 __m128i a01 = blend(a0, a1, blendMask);
177 __m128i p = _mm_xor_si128(a0, a01);
178 __m128i q = _mm_and_si128(p, cnd);
179 __m128i r = _mm_xor_si128(q, a0); // select(a0, a01, cnd)
180
181 ustore(dst, byteOffst, r);
182 byteOffst += sizeof(__m128i);
183 }
184 remaining &= pixelsPerSSE - 1;
185#endif
186 for (auto x : xrange(remaining)) {
187 dst[x] = ((line0[x] == line2[x]) && (line1[x] == line3[x]))
188 ? pixelOps.template blend<1, 1>(line0[x], line1[x])
189 : line0[x];
190 }
191
192 if (width0 <= bufWidth) {
193 // It it already fits, we're done
194 width = width0;
195 } else {
196 // Otherwise scale so that it does fit.
197 width = bufWidth;
198 scaleLine(std::span<const Pixel>{out, width0}, std::span{buf, bufWidth});
199 }
200 return buf;
201}
202
203} // namespace openmsx
Definition: one_of.hh:7
DeflickerImpl(const PixelFormat &format, std::span< std::unique_ptr< RawFrame >, 4 > lastFrames)
Definition: Deflicker.cc:71
std::span< std::unique_ptr< RawFrame >, 4 > lastFrames
Definition: Deflicker.hh:29
static std::unique_ptr< Deflicker > create(const PixelFormat &format, std::span< std::unique_ptr< RawFrame >, 4 > lastFrames)
Definition: Deflicker.cc:33
Deflicker(const PixelFormat &format, std::span< std::unique_ptr< RawFrame >, 4 > lastFrames)
Definition: Deflicker.cc:51
unsigned getLineWidth(unsigned line) const override
Gets the number of display pixels on the given line.
Definition: Deflicker.cc:64
Interface for getting lines from a video frame.
Definition: FrameSource.hh:20
void setHeight(unsigned height_)
Definition: FrameSource.hh:159
void init(FieldType fieldType_)
(Re)initialize an existing FrameSource.
Definition: FrameSource.hh:39
unsigned getHeight() const
Gets the number of lines in this frame.
Definition: FrameSource.hh:49
@ FIELD_NONINTERLACED
Interlacing is off for this frame.
Definition: FrameSource.hh:27
void format(SectorAccessibleDisk &disk, MSXBootSectorType bootType)
Format the given disk (= a single partition).
This file implemented 3 utility functions:
Definition: Autofire.cc:9
uint32_t Pixel
#define UNREACHABLE
Definition: unreachable.hh:38
#define VLA_SSE_ALIGNED(TYPE, NAME, LENGTH)
Definition: vla.hh:50
constexpr auto xrange(T e)
Definition: xrange.hh:132