openMSX
HQCommon.hh
Go to the documentation of this file.
1#ifndef HQCOMMON_HH
2#define HQCOMMON_HH
3
4#include "FrameSource.hh"
5#include "ScalerOutput.hh"
6#include "LineScalers.hh"
7#include "PixelOperations.hh"
8#include "endian.hh"
9#include "narrow.hh"
10#include "vla.hh"
11#include "xrange.hh"
12#include <algorithm>
13#include <cassert>
14#include <cstdint>
15
16namespace openmsx {
17
18template<std::unsigned_integral Pixel>
19[[nodiscard]] inline uint32_t readPixel(Pixel p)
20{
21 // TODO: Use surface info instead.
22 if constexpr (sizeof(Pixel) == 2) {
23 return ((p & 0xF800) << 8) |
24 ((p & 0x07C0) << 5) | // drop lowest green bit
25 ((p & 0x001F) << 3);
26 } else {
27 return p & 0xF8F8F8F8;
28 }
29}
30
31template<std::unsigned_integral Pixel>
32inline Pixel writePixel(uint32_t p)
33{
34 // TODO: Use surface info instead.
35 if constexpr (sizeof(Pixel) == 2) {
36 return ((p & 0xF80000) >> 8) |
37 ((p & 0x00FC00) >> 5) |
38 ((p & 0x0000F8) >> 3);
39 } else {
40 return (p & 0xF8F8F8F8) | ((p & 0xE0E0E0E0) >> 5);
41 }
42}
43
44class EdgeHQ
45{
46public:
47 EdgeHQ(unsigned shiftR_, unsigned shiftG_, unsigned shiftB_)
48 : shiftR(shiftR_), shiftG(shiftG_), shiftB(shiftB_)
49 {
50 }
51
52 [[nodiscard]] inline bool operator()(uint32_t c1, uint32_t c2) const
53 {
54 if (c1 == c2) return false;
55
56 unsigned r1 = (c1 >> shiftR) & 0xFF;
57 unsigned g1 = (c1 >> shiftG) & 0xFF;
58 unsigned b1 = (c1 >> shiftB) & 0xFF;
59
60 unsigned r2 = (c2 >> shiftR) & 0xFF;
61 unsigned g2 = (c2 >> shiftG) & 0xFF;
62 unsigned b2 = (c2 >> shiftB) & 0xFF;
63
64 auto dr = narrow_cast<int>(r1 - r2);
65 auto dg = narrow_cast<int>(g1 - g2);
66 auto db = narrow_cast<int>(b1 - b2);
67
68 int dy = dr + dg + db;
69 if (dy < -0xC0 || dy > 0xC0) return true;
70
71 int du = dr - db;
72 if (du < -0x1C || du > 0x1C) return true;
73
74 int dv = 3 * dg - dy;
75 if (dv < -0x30 || dv > 0x30) return true;
76
77 return false;
78 }
79private:
80 const unsigned shiftR;
81 const unsigned shiftG;
82 const unsigned shiftB;
83};
84
85template<std::unsigned_integral Pixel>
87{
88 if constexpr (sizeof(Pixel) == 2) {
89 return {0, 8, 16};
90 } else {
91 return {pixelOps.getRshift(),
92 pixelOps.getGshift(),
93 pixelOps.getBshift()};
94 }
95}
96
98{
99 [[nodiscard]] inline bool operator()(uint32_t c1, uint32_t c2) const
100 {
101 return c1 != c2;
102 }
103};
104
105template<typename EdgeOp>
106void calcEdgesGL(std::span<const uint32_t> curr, std::span<const uint32_t> next,
107 std::span<Endian::L32> edges2, EdgeOp edgeOp)
108{
109 assert(curr.size() == 320);
110 assert(next.size() == 320);
111 assert(edges2.size() == 320 / 2);
112 // Consider a grid of 3x3 pixels, numbered like this:
113 // 1 | 2 | 3
114 // ---A---B---
115 // 4 | 5 | 6
116 // ---C---D---
117 // 7 | 8 | 9
118 // Then we calculate 12 'edges':
119 // * 8 star-edges, from the central pixel '5' to the 8 neighboring pixels.
120 // Let's call these edges 1, 2, 3, 4, 6, 7, 8, 9 (note: 5 is skipped).
121 // * 4 cross-edges, between pixels (2,4), (2,6), (4,8), (6,8).
122 // Let's call these respectively A, B, C, D.
123 // An edge between two pixels means the color of the two pixels is sufficiently distant.
124 // * For the HQ scaler see 'EdgeHQ' for the definition of this distance function.
125 // * The HQlite scaler uses a much simpler distance function.
126 //
127 // We store these 12 edges in a 16-bit value and order them like this:
128 // (MSB (bit 15) on the left, LSB (bit 0) on the left, 'x' means bit is not used)
129 // || B 3 6 9 | D 2 x x || 8 1 A 4 | C 7 x x ||
130 // This order has two important properties:
131 // * The 12 bits are split in 2 groups of 6 bits and each group is MSB
132 // aligned within a byte. This allows to upload this data as a
133 // openGL texture and interpret each texel as a vec2 which
134 // represents a texture coordinate in another 64x64 texture.
135 // * This order allows to calculate the edges incrementally:
136 // Suppose we already calculated the edges for the pixel immediate
137 // above and immediately to the left of the current pixel. Then the edges
138 // (1, 2, 3, A, B) can be calculated as: (upper << 3) & 0xc460
139 // and (4, 7, C) can be calculated as: (left >> 9) & 0x001C
140 // And only edges (6, 8, 9, D) must be newly calculated for this pixel.
141 // So only 4 new edges per pixel instead of all 12.
142 //
143 // This function takes as input:
144 // * an in/out-array 'edges2':
145 // This contains the edge information for the upper row of pixels.
146 // And it gets update in-place to the edge information of the current
147 // row of pixels.
148 // * 2 rows of input pixels: the middle and the lower pixel rows.
149 // * An edge-function (to distinguish 'hq' from 'hqlite').
150
151 using Pixel = uint32_t;
152
153 uint32_t pattern = 0;
154 Pixel c5 = curr[0];
155 Pixel c8 = next[0];
156 if (edgeOp(c5, c8)) pattern |= 0x1800'0000; // edges: 9,D (right pixel)
157
158 for (auto xx : xrange((320 - 2) / 2)) {
159 pattern = (pattern >> (16 + 9)) & 0x001C; // edges: 6,D,9 -> 4,7,C (left pixel)
160 pattern |= (edges2[xx] << 3) & 0xC460'C460; // edges C,8,D,7,9 -> 1,2,3,A,B (left and right)
161
162 if (edgeOp(c5, c8)) pattern |= 0x0000'0080; // edge: 8 (left)
163 Pixel c6 = curr[2 * xx + 1];
164 if (edgeOp(c6, c8)) pattern |= 0x0004'0800; // edge: D (left), 7 (right)
165 if (edgeOp(c5, c6)) pattern |= 0x0010'2000; // edge: 6 (left), 4 (right)
166 Pixel c9 = next[2 * xx + 1];
167 if (edgeOp(c5, c9)) pattern |= 0x0008'1000; // edge: 9 (left), C (right)
168
169 if (edgeOp(c6, c9)) pattern |= 0x0080'0000; // edge: 8 (right)
170 c5 = curr[2 * xx + 2];
171 if (edgeOp(c5, c9)) pattern |= 0x0800'0000; // edge: D (right)
172 if (edgeOp(c6, c5)) pattern |= 0x2000'0000; // edge: 6 (right)
173 c8 = next[2 * xx + 2];
174 if (edgeOp(c6, c8)) pattern |= 0x1000'0000; // edge: 9 (right)
175
176 edges2[xx] = pattern;
177 }
178
179 pattern = (pattern >> (16 + 9)) & 0x001C; // edges: 6,D,9 -> 4,7,C (left pixel)
180 pattern |= (edges2[159] << 3) & 0xC460'C460; // edges: C,8,D,7,9 -> 1,2,3,A,B (left and right)
181
182 if (edgeOp(c5, c8)) pattern |= 0x0000'0080; // edge: 8 (left)
183 Pixel c6 = curr[319];
184 if (edgeOp(c6, c8)) pattern |= 0x0004'0800; // edge: D (left), 7 (right)
185 if (edgeOp(c5, c6)) pattern |= 0x0010'2000; // edge: 6 (left), 4 (right)
186 Pixel c9 = next[319];
187 if (edgeOp(c5, c9)) pattern |= 0x0008'1000; // edge: 9 (left), C (right)
188
189 if (edgeOp(c6, c9)) pattern |= 0x1880'0000; // edges: 8,9,D (right)
190
191 edges2[159] = pattern;
192}
193
194template<std::unsigned_integral Pixel, typename EdgeOp>
196 std::span<const Pixel> srcPrev, std::span<const Pixel> srcCurr,
197 std::span<uint16_t> edgeBuf, EdgeOp edgeOp)
198{
199 auto srcWidth = edgeBuf.size();
200 assert(srcPrev.size() == srcWidth);
201 assert(srcCurr.size() == srcWidth);
202
203 size_t x = 0;
204 uint32_t c1 = readPixel(srcPrev[x]);
205 uint32_t c2 = readPixel(srcCurr[x]);
206 uint16_t pattern = edgeOp(c1, c2) ? ((1 << 6) | (1 << 7)) : 0;
207 for (/* */; x < (srcWidth - 1); ++x) {
208 pattern >>= 6;
209 uint32_t n1 = readPixel(srcPrev[x + 1]);
210 uint32_t n2 = readPixel(srcCurr[x + 1]);
211 if (edgeOp(c1, c2)) pattern |= (1 << 5);
212 if (edgeOp(c1, n2)) pattern |= (1 << 6);
213 if (edgeOp(c2, n1)) pattern |= (1 << 7);
214 edgeBuf[x] = pattern;
215 c1 = n1; c2 = n2;
216 }
217 pattern >>= 6;
218 if (edgeOp(c1, c2)) pattern |= (1 << 5) | (1 << 6) | (1 << 7);
219 edgeBuf[x] = pattern;
220}
221
222template<std::unsigned_integral Pixel, typename HQScale, typename EdgeOp>
223void doHQScale2(HQScale hqScale, EdgeOp edgeOp, PolyLineScaler<Pixel>& postScale,
224 FrameSource& src, unsigned srcStartY, unsigned /*srcEndY*/, unsigned srcWidth,
225 ScalerOutput<Pixel>& dst, unsigned dstStartY, unsigned dstEndY)
226{
227 VLA(uint16_t, edgeBuf, srcWidth);
228 VLA_SSE_ALIGNED(Pixel, buf1, srcWidth);
229 VLA_SSE_ALIGNED(Pixel, buf2, srcWidth);
230 VLA_SSE_ALIGNED(Pixel, buf3, srcWidth);
231 VLA_SSE_ALIGNED(Pixel, bufA, 2 * srcWidth);
232 VLA_SSE_ALIGNED(Pixel, bufB, 2 * srcWidth);
233
234 int srcY = narrow<int>(srcStartY);
235 auto srcPrev = src.getLine(srcY - 1, buf1);
236 auto srcCurr = src.getLine(srcY + 0, buf2);
237
238 calcInitialEdges(srcPrev, srcCurr, edgeBuf, edgeOp);
239
240 bool isCopy = postScale.isCopy();
241 for (unsigned dstY = dstStartY; dstY < dstEndY; srcY += 1, dstY += 2) {
242 auto srcNext = src.getLine(srcY + 1, buf3);
243 auto dst0 = dst.acquireLine(dstY + 0);
244 auto dst1 = dst.acquireLine(dstY + 1);
245 if (isCopy) {
246 hqScale(srcPrev, srcCurr, srcNext, dst0, dst1,
247 edgeBuf, edgeOp);
248 } else {
249 hqScale(srcPrev, srcCurr, srcNext, bufA, bufB,
250 edgeBuf, edgeOp);
251 postScale(bufA, dst0);
252 postScale(bufB, dst1);
253 }
254 dst.releaseLine(dstY + 0, dst0);
255 dst.releaseLine(dstY + 1, dst1);
256 srcPrev = srcCurr;
257 srcCurr = srcNext;
258 std::swap(buf1, buf2);
259 std::swap(buf2, buf3);
260 }
261}
262
263template<std::unsigned_integral Pixel, typename HQScale, typename EdgeOp>
264void doHQScale3(HQScale hqScale, EdgeOp edgeOp, PolyLineScaler<Pixel>& postScale,
265 FrameSource& src, unsigned srcStartY, unsigned /*srcEndY*/, unsigned srcWidth,
266 ScalerOutput<Pixel>& dst, unsigned dstStartY, unsigned dstEndY)
267{
268 VLA(uint16_t, edgeBuf, srcWidth);
269 VLA_SSE_ALIGNED(Pixel, buf1, srcWidth);
270 VLA_SSE_ALIGNED(Pixel, buf2, srcWidth);
271 VLA_SSE_ALIGNED(Pixel, buf3, srcWidth);
272 VLA_SSE_ALIGNED(Pixel, bufA, 3 * srcWidth);
273 VLA_SSE_ALIGNED(Pixel, bufB, 3 * srcWidth);
274 VLA_SSE_ALIGNED(Pixel, bufC, 3 * srcWidth);
275
276 int srcY = narrow<int>(srcStartY);
277 auto srcPrev = src.getLine(srcY - 1, buf1);
278 auto srcCurr = src.getLine(srcY + 0, buf2);
279
280 calcInitialEdges(srcPrev, srcCurr, edgeBuf, edgeOp);
281
282 bool isCopy = postScale.isCopy();
283 for (unsigned dstY = dstStartY; dstY < dstEndY; srcY += 1, dstY += 3) {
284 auto srcNext = src.getLine(srcY + 1, buf3);
285 auto dst0 = dst.acquireLine(dstY + 0);
286 auto dst1 = dst.acquireLine(dstY + 1);
287 auto dst2 = dst.acquireLine(dstY + 2);
288 if (isCopy) {
289 hqScale(srcPrev, srcCurr, srcNext, dst0, dst1, dst2,
290 edgeBuf, edgeOp);
291 } else {
292 hqScale(srcPrev, srcCurr, srcNext, bufA, bufB, bufC,
293 edgeBuf, edgeOp);
294 postScale(bufA, dst0);
295 postScale(bufB, dst1);
296 postScale(bufC, dst2);
297 }
298 dst.releaseLine(dstY + 0, dst0);
299 dst.releaseLine(dstY + 1, dst1);
300 dst.releaseLine(dstY + 2, dst2);
301 srcPrev = srcCurr;
302 srcCurr = srcNext;
303 std::swap(buf1, buf2);
304 std::swap(buf2, buf3);
305 }
306}
307
308} // namespace openmsx
309
310#endif
bool operator()(uint32_t c1, uint32_t c2) const
Definition: HQCommon.hh:52
EdgeHQ(unsigned shiftR_, unsigned shiftG_, unsigned shiftB_)
Definition: HQCommon.hh:47
Interface for getting lines from a video frame.
Definition: FrameSource.hh:20
std::span< const Pixel > getLine(int line, std::span< Pixel > buf) const
Gets a pointer to the pixels of the given line number.
Definition: FrameSource.hh:96
unsigned getGshift() const
unsigned getBshift() const
unsigned getRshift() const
Polymorphic line scaler.
Definition: LineScalers.hh:286
virtual bool isCopy() const =0
Is this scale operation actually a copy? This info can be used to (in a multi-step scale operation) i...
virtual void releaseLine(unsigned y, std::span< Pixel > buf)=0
virtual std::span< Pixel > acquireLine(unsigned y)=0
This file implemented 3 utility functions:
Definition: Autofire.cc:9
void calcInitialEdges(std::span< const Pixel > srcPrev, std::span< const Pixel > srcCurr, std::span< uint16_t > edgeBuf, EdgeOp edgeOp)
Definition: HQCommon.hh:195
uint32_t Pixel
EdgeHQ createEdgeHQ(const PixelOperations< Pixel > &pixelOps)
Definition: HQCommon.hh:86
Pixel writePixel(uint32_t p)
Definition: HQCommon.hh:32
void doHQScale3(HQScale hqScale, EdgeOp edgeOp, PolyLineScaler< Pixel > &postScale, FrameSource &src, unsigned srcStartY, unsigned, unsigned srcWidth, ScalerOutput< Pixel > &dst, unsigned dstStartY, unsigned dstEndY)
Definition: HQCommon.hh:264
uint32_t readPixel(Pixel p)
Definition: HQCommon.hh:19
void doHQScale2(HQScale hqScale, EdgeOp edgeOp, PolyLineScaler< Pixel > &postScale, FrameSource &src, unsigned srcStartY, unsigned, unsigned srcWidth, ScalerOutput< Pixel > &dst, unsigned dstStartY, unsigned dstEndY)
Definition: HQCommon.hh:223
void calcEdgesGL(std::span< const uint32_t > curr, std::span< const uint32_t > next, std::span< Endian::L32 > edges2, EdgeOp edgeOp)
Definition: HQCommon.hh:106
void swap(openmsx::MemBuffer< T > &l, openmsx::MemBuffer< T > &r) noexcept
Definition: MemBuffer.hh:202
bool operator()(uint32_t c1, uint32_t c2) const
Definition: HQCommon.hh:99
#define VLA(TYPE, NAME, LENGTH)
Definition: vla.hh:12
#define VLA_SSE_ALIGNED(TYPE, NAME, LENGTH)
Definition: vla.hh:50
constexpr auto xrange(T e)
Definition: xrange.hh:132