1 ////////////////////////////////////////////////////////////////////////////////////////
2 //
3 // Nestopia - NES/Famicom emulator written in C++
4 //
5 // Copyright (C) 2003-2008 Martin Freij
6 //
7 // This file is part of Nestopia.
8 //
9 // Nestopia is free software; you can redistribute it and/or modify
10 // it under the terms of the GNU General Public License as published by
11 // the Free Software Foundation; either version 2 of the License, or
12 // (at your option) any later version.
13 //
14 // Nestopia is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with Nestopia; if not, write to the Free Software
21 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22 //
23 ////////////////////////////////////////////////////////////////////////////////////////
24 
25 #ifdef __LIBRETRO__
26 #define NST_NO_SCALEX 1
27 #define NST_NO_HQ2X 1
28 #define NST_NO_2XSAI 1
29 #define NST_NO_XBR 1
30 #endif
31 
32 #include <cstring>
33 #include <cmath>
34 #include <new>
35 #include "NstCore.hpp"
36 #include "NstAssert.hpp"
37 #include "NstFpuPrecision.hpp"
38 #include "api/NstApiVideo.hpp"
39 #include "NstVideoRenderer.hpp"
40 #include "NstVideoFilterNone.hpp"
41 
42 #ifndef NO_NTSC
43 #include "NstVideoFilterNtsc.hpp"
44 #endif
45 #ifndef NST_NO_SCALEX
46 #include "NstVideoFilterScaleX.hpp"
47 #endif
48 #ifndef NST_NO_HQ2X
49 #include "NstVideoFilterHqX.hpp"
50 #endif
51 #ifndef NST_NO_2XSAI
52 #include "NstVideoFilter2xSaI.hpp"
53 #endif
54 #ifndef NST_NO_XBR
55 #include "NstVideoFilterxBR.hpp"
56 #endif
57 
58 namespace Nes
59 {
60 	namespace Core
61 	{
62 		namespace Video
63 		{
64 			const byte Renderer::Palette::pc10Palette[64][3] =
65 			{
66 				{0x6D,0x6D,0x6D}, {0x00,0x24,0x92}, {0x00,0x00,0xDB}, {0x6D,0x49,0xDB},
67 				{0x92,0x00,0x6D}, {0xB6,0x00,0x6D}, {0xB6,0x24,0x00}, {0x92,0x49,0x00},
68 				{0x6D,0x49,0x00}, {0x24,0x49,0x00}, {0x00,0x6D,0x24}, {0x00,0x92,0x00},
69 				{0x00,0x49,0x49}, {0x00,0x00,0x00}, {0x00,0x00,0x00}, {0x00,0x00,0x00},
70 				{0xB6,0xB6,0xB6}, {0x00,0x6D,0xDB}, {0x00,0x49,0xFF}, {0x92,0x00,0xFF},
71 				{0xB6,0x00,0xFF}, {0xFF,0x00,0x92}, {0xFF,0x00,0x00}, {0xDB,0x6D,0x00},
72 				{0x92,0x6D,0x00}, {0x24,0x92,0x00}, {0x00,0x92,0x00}, {0x00,0xB6,0x6D},
73 				{0x00,0x92,0x92}, {0x24,0x24,0x24}, {0x00,0x00,0x00}, {0x00,0x00,0x00},
74 				{0xFF,0xFF,0xFF}, {0x6D,0xB6,0xFF}, {0x92,0x92,0xFF}, {0xDB,0x6D,0xFF},
75 				{0xFF,0x00,0xFF}, {0xFF,0x6D,0xFF}, {0xFF,0x92,0x00}, {0xFF,0xB6,0x00},
76 				{0xDB,0xDB,0x00}, {0x6D,0xDB,0x00}, {0x00,0xFF,0x00}, {0x49,0xFF,0xDB},
77 				{0x00,0xFF,0xFF}, {0x49,0x49,0x49}, {0x00,0x00,0x00}, {0x00,0x00,0x00},
78 				{0xFF,0xFF,0xFF}, {0xB6,0xDB,0xFF}, {0xDB,0xB6,0xFF}, {0xFF,0xB6,0xFF},
79 				{0xFF,0x92,0xFF}, {0xFF,0xB6,0xB6}, {0xFF,0xDB,0x92}, {0xFF,0xFF,0x49},
80 				{0xFF,0xFF,0x6D}, {0xB6,0xFF,0x49}, {0x92,0xFF,0x6D}, {0x49,0xFF,0xDB},
81 				{0x92,0xDB,0xFF}, {0x92,0x92,0x92}, {0x00,0x00,0x00}, {0x00,0x00,0x00}
82 			};
83 
84 			const byte Renderer::Palette::vsPalette[4][64][3] =
85 			{
86 				{
87 					{0xFF,0xB6,0xB6}, {0xDB,0x6D,0xFF}, {0xFF,0x00,0x00}, {0x92,0x92,0xFF},
88 					{0x00,0x92,0x92}, {0x24,0x49,0x00}, {0x49,0x49,0x49}, {0xFF,0x00,0x92},
89 					{0xFF,0xFF,0xFF}, {0x6D,0x6D,0x6D}, {0xFF,0xB6,0x00}, {0xB6,0x00,0x6D},
90 					{0x92,0x00,0x6D}, {0xDB,0xDB,0x00}, {0x6D,0x49,0x00}, {0xFF,0xFF,0xFF},
91 					{0x6D,0xB6,0xFF}, {0xDB,0xB6,0x6D}, {0x6D,0x24,0x00}, {0x6D,0xDB,0x00},
92 					{0x92,0xDB,0xFF}, {0xDB,0xB6,0xFF}, {0xFF,0xDB,0x92}, {0x00,0x49,0xFF},
93 					{0xFF,0xDB,0x00}, {0x49,0xFF,0xDB}, {0x00,0x00,0x00}, {0x49,0x00,0x00},
94 					{0xDB,0xDB,0xDB}, {0x92,0x92,0x92}, {0xFF,0x00,0xFF}, {0x00,0x24,0x92},
95 					{0x00,0x00,0x6D}, {0xB6,0xDB,0xFF}, {0xFF,0xB6,0xFF}, {0x00,0xFF,0x00},
96 					{0x00,0xFF,0xFF}, {0x00,0x49,0x49}, {0x00,0xB6,0x6D}, {0xB6,0x00,0xFF},
97 					{0x00,0x00,0x00}, {0x92,0x49,0x00}, {0xFF,0x92,0xFF}, {0xB6,0x24,0x00},
98 					{0x92,0x00,0xFF}, {0x00,0x00,0xDB}, {0xFF,0x92,0x00}, {0x00,0x00,0x00},
99 					{0x00,0x00,0x00}, {0x24,0x92,0x00}, {0xB6,0xB6,0xB6}, {0x00,0x6D,0x24},
100 					{0xB6,0xFF,0x49}, {0x6D,0x49,0xDB}, {0xFF,0xFF,0x00}, {0xDB,0x6D,0x00},
101 					{0x00,0x49,0x00}, {0x00,0x6D,0xDB}, {0x00,0x92,0x00}, {0x24,0x24,0x24},
102 					{0xFF,0xFF,0x6D}, {0xFF,0x6D,0xFF}, {0x92,0x6D,0x00}, {0x92,0xFF,0x6D}
103 				},
104 				{
105 					{0x00,0x00,0x00}, {0xFF,0xB6,0x00}, {0x92,0x6D,0x00}, {0xB6,0xFF,0x49},
106 					{0x92,0xFF,0x6D}, {0xFF,0x6D,0xFF}, {0x00,0x92,0x92}, {0xB6,0xDB,0xFF},
107 					{0xFF,0x00,0x00}, {0x92,0x00,0xFF}, {0xFF,0xFF,0x6D}, {0xFF,0x92,0xFF},
108 					{0xFF,0xFF,0xFF}, {0xDB,0x6D,0xFF}, {0x92,0xDB,0xFF}, {0x00,0x92,0x00},
109 					{0x00,0x49,0x00}, {0x6D,0xB6,0xFF}, {0xB6,0x24,0x00}, {0xDB,0xDB,0xDB},
110 					{0x00,0xB6,0x6D}, {0x6D,0xDB,0x00}, {0x49,0x00,0x00}, {0x92,0x92,0xFF},
111 					{0x49,0x49,0x49}, {0xFF,0x00,0xFF}, {0x00,0x00,0x6D}, {0x49,0xFF,0xDB},
112 					{0xDB,0xB6,0xFF}, {0x6D,0x49,0x00}, {0x00,0x00,0x00}, {0x6D,0x49,0xDB},
113 					{0x92,0x00,0x6D}, {0xFF,0xDB,0x92}, {0xFF,0x92,0x00}, {0xFF,0xB6,0xFF},
114 					{0x00,0x6D,0xDB}, {0x6D,0x24,0x00}, {0xB6,0xB6,0xB6}, {0x00,0x00,0xDB},
115 					{0xB6,0x00,0xFF}, {0xFF,0xDB,0x00}, {0x6D,0x6D,0x6D}, {0x24,0x49,0x00},
116 					{0x00,0x49,0xFF}, {0x00,0x00,0x00}, {0xDB,0xDB,0x00}, {0xFF,0xFF,0xFF},
117 					{0xDB,0xB6,0x6D}, {0x24,0x24,0x24}, {0x00,0xFF,0x00}, {0xDB,0x6D,0x00},
118 					{0x00,0x49,0x49}, {0x00,0x24,0x92}, {0xFF,0x00,0x92}, {0x24,0x92,0x00},
119 					{0x00,0x00,0x00}, {0x00,0xFF,0xFF}, {0x92,0x49,0x00}, {0xFF,0xFF,0x00},
120 					{0xFF,0xB6,0xB6}, {0xB6,0x00,0x6D}, {0x00,0x6D,0x24}, {0x92,0x92,0x92}
121 				},
122 				{
123 					{0xB6,0x00,0xFF}, {0xFF,0x6D,0xFF}, {0x92,0xFF,0x6D}, {0xB6,0xB6,0xB6},
124 					{0x00,0x92,0x00}, {0xFF,0xFF,0xFF}, {0xB6,0xDB,0xFF}, {0x24,0x49,0x00},
125 					{0x00,0x24,0x92}, {0x00,0x00,0x00}, {0xFF,0xDB,0x92}, {0x6D,0x49,0x00},
126 					{0xFF,0x00,0x92}, {0xDB,0xDB,0xDB}, {0xDB,0xB6,0x6D}, {0x92,0xDB,0xFF},
127 					{0x92,0x92,0xFF}, {0x00,0x92,0x92}, {0xB6,0x00,0x6D}, {0x00,0x49,0xFF},
128 					{0x24,0x92,0x00}, {0x92,0x6D,0x00}, {0xDB,0x6D,0x00}, {0x00,0xB6,0x6D},
129 					{0x6D,0x6D,0x6D}, {0x6D,0x49,0xDB}, {0x00,0x00,0x00}, {0x00,0x00,0xDB},
130 					{0xFF,0x00,0x00}, {0xB6,0x24,0x00}, {0xFF,0x92,0xFF}, {0xFF,0xB6,0xB6},
131 					{0xDB,0x6D,0xFF}, {0x00,0x49,0x00}, {0x00,0x00,0x6D}, {0xFF,0xFF,0x00},
132 					{0x24,0x24,0x24}, {0xFF,0xB6,0x00}, {0xFF,0x92,0x00}, {0xFF,0xFF,0xFF},
133 					{0x6D,0xDB,0x00}, {0x92,0x00,0x6D}, {0x6D,0xB6,0xFF}, {0xFF,0x00,0xFF},
134 					{0x00,0x6D,0xDB}, {0x92,0x92,0x92}, {0x00,0x00,0x00}, {0x6D,0x24,0x00},
135 					{0x00,0xFF,0xFF}, {0x49,0x00,0x00}, {0xB6,0xFF,0x49}, {0xFF,0xB6,0xFF},
136 					{0x92,0x49,0x00}, {0x00,0xFF,0x00}, {0xDB,0xDB,0x00}, {0x49,0x49,0x49},
137 					{0x00,0x6D,0x24}, {0x00,0x00,0x00}, {0xDB,0xB6,0xFF}, {0xFF,0xFF,0x6D},
138 					{0x92,0x00,0xFF}, {0x49,0xFF,0xDB}, {0xFF,0xDB,0x00}, {0x00,0x49,0x49}
139 				},
140 				{
141 					{0x92,0x6D,0x00}, {0x6D,0x49,0xDB}, {0x00,0x92,0x92}, {0xDB,0xDB,0x00},
142 					{0x00,0x00,0x00}, {0xFF,0xB6,0xB6}, {0x00,0x24,0x92}, {0xDB,0x6D,0x00},
143 					{0xB6,0xB6,0xB6}, {0x6D,0x24,0x00}, {0x00,0xFF,0x00}, {0x00,0x00,0x6D},
144 					{0xFF,0xDB,0x92}, {0xFF,0xFF,0x00}, {0x00,0x92,0x00}, {0xB6,0xFF,0x49},
145 					{0xFF,0x6D,0xFF}, {0x49,0x00,0x00}, {0x00,0x49,0xFF}, {0xFF,0x92,0xFF},
146 					{0x00,0x00,0x00}, {0x49,0x49,0x49}, {0xB6,0x24,0x00}, {0xFF,0x92,0x00},
147 					{0xDB,0xB6,0x6D}, {0x00,0xB6,0x6D}, {0x92,0x92,0xFF}, {0x24,0x92,0x00},
148 					{0x92,0x00,0x6D}, {0x00,0x00,0x00}, {0x92,0xFF,0x6D}, {0x6D,0xB6,0xFF},
149 					{0xB6,0x00,0x6D}, {0x00,0x6D,0x24}, {0x92,0x49,0x00}, {0x00,0x00,0xDB},
150 					{0x92,0x00,0xFF}, {0xB6,0x00,0xFF}, {0x6D,0x6D,0x6D}, {0xFF,0x00,0x92},
151 					{0x00,0x49,0x49}, {0xDB,0xDB,0xDB}, {0x00,0x6D,0xDB}, {0x00,0x49,0x00},
152 					{0x24,0x24,0x24}, {0xFF,0xFF,0x6D}, {0x92,0x92,0x92}, {0xFF,0x00,0xFF},
153 					{0xFF,0xB6,0xFF}, {0xFF,0xFF,0xFF}, {0x6D,0x49,0x00}, {0xFF,0x00,0x00},
154 					{0xFF,0xDB,0x00}, {0x49,0xFF,0xDB}, {0xFF,0xFF,0xFF}, {0x92,0xDB,0xFF},
155 					{0x00,0x00,0x00}, {0xFF,0xB6,0x00}, {0xDB,0x6D,0xFF}, {0xB6,0xDB,0xFF},
156 					{0x6D,0xDB,0x00}, {0xDB,0xB6,0xFF}, {0x00,0xFF,0xFF}, {0x24,0x49,0x00}
157 				}
158 			};
159 
160 			const double Renderer::Palette::Constants::pi = 3.141592653589793;
161 			const double Renderer::Palette::Constants::deg = 0.017453292519943296;
162 
163 			const double Renderer::Palette::Constants::levels[2][4] =
164 			{
165 				{-0.12, 0.00, 0.31, 0.72 },
166 				{ 0.40, 0.68, 1.00, 1.00 }
167 			};
168 
169 			#ifdef NST_MSVC_OPTIMIZE
170 			#pragma optimize("s", on)
171 			#endif
172 
Custom()173 			inline Renderer::Palette::Custom::Custom()
174 			: emphasis(NULL) {}
175 
~Custom()176 			inline Renderer::Palette::Custom::~Custom()
177 			{
178 				delete [] emphasis;
179 			}
180 
EnableEmphasis(bool enable)181 			bool Renderer::Palette::Custom::EnableEmphasis(bool enable)
182 			{
183 				if (!enable)
184 				{
185 					delete [] emphasis;
186 					emphasis = NULL;
187 				}
188 				else if (!emphasis)
189 				{
190 					emphasis = new (std::nothrow) byte [7][64][3];
191 				}
192 
193 				return bool(emphasis) == enable;
194 			}
195 
Palette()196 			Renderer::Palette::Palette()
197 			: type(PALETTE_YUV), custom(NULL)
198 			{
199 			}
200 
~Palette()201 			Renderer::Palette::~Palette()
202 			{
203 				delete custom;
204 			}
205 
SetDecoder(const Decoder & d)206 			Result Renderer::Palette::SetDecoder(const Decoder& d)
207 			{
208 				if (decoder == d)
209 					return RESULT_NOP;
210 
211 				for (uint i=0; i < 3; ++i)
212 				{
213 					if (d.axes[i].angle >= 360 || d.axes[i].gain > 2.0)
214 						return RESULT_ERR_INVALID_PARAM;
215 				}
216 
217 				decoder = d;
218 				return RESULT_OK;
219 			}
220 
Store(const double (& src)[3],byte (& dst)[3])221 			void Renderer::Palette::Store(const double (&src)[3],byte (&dst)[3])
222 			{
223 				for (uint i=0; i < 3; ++i)
224 					dst[i] = Clamp<0,255>( src[i] * 255 + 0.5 );
225 			}
226 
LoadCustom(const byte (* colors)[3],const bool emphasis)227 			Result Renderer::Palette::LoadCustom(const byte (*colors)[3],const bool emphasis)
228 			{
229 				if (!colors)
230 					return RESULT_ERR_INVALID_PARAM;
231 
232 				if ((custom == NULL && NULL == (custom = new (std::nothrow) Custom)) || !custom->EnableEmphasis( emphasis ))
233 					return RESULT_ERR_OUT_OF_MEMORY;
234 
235 				std::memcpy( custom->palette, colors, 64*3 );
236 
237 				if (emphasis)
238 					std::memcpy( custom->emphasis, colors + 64, 7*64*3 );
239 
240 				return RESULT_OK;
241 			}
242 
SaveCustom(byte (* colors)[3],const bool emphasis) const243 			uint Renderer::Palette::SaveCustom(byte (*colors)[3],const bool emphasis) const
244 			{
245 				if (!colors)
246 					return 0;
247 
248 				std::memcpy( colors, custom ? custom->palette : pc10Palette, 64*3 );
249 
250 				if (!emphasis || !custom || !custom->emphasis)
251 					return 64;
252 
253 				std::memcpy( colors + 64, custom->emphasis, 7*64*3 );
254 
255 				return 7*64;
256 			}
257 
ResetCustom()258 			bool Renderer::Palette::ResetCustom()
259 			{
260 				if (custom)
261 				{
262 					custom->EnableEmphasis( false );
263 					std::memcpy( custom->palette, pc10Palette, 64*3 );
264 					return true;
265 				}
266 
267 				return false;
268 			}
269 
SetType(PaletteType t)270 			Result Renderer::Palette::SetType(PaletteType t)
271 			{
272 				if (t == type)
273 					return RESULT_NOP;
274 
275 				if (t == PALETTE_CUSTOM && !custom)
276 				{
277 					if (NULL == (custom = new (std::nothrow) Custom))
278 						return RESULT_ERR_OUT_OF_MEMORY;
279 
280 					ResetCustom();
281 				}
282 
283 				type = t;
284 
285 				return RESULT_OK;
286 			}
287 
GenerateEmphasis(uint tint,double s,double & y,double & i,double & q)288 			void Renderer::Palette::GenerateEmphasis(uint tint,double s,double& y,double& i,double& q)
289 			{
290 				if (tint == 7)
291 				{
292 					y = y * (0.79399 * 1.13) - (0.0782838 * 1.13);
293 				}
294 				else
295 				{
296 					s = s * (0.5 - 0.79399 * 0.5) + 0.0782838 * 0.5;
297 					y -= s * 0.5;
298 
299 					if (tint >= 3 && tint != 4)
300 					{
301 						s *= 0.6;
302 						y -= s;
303 					}
304 
305 					static const byte tints[8] =
306 					{
307 						0, 6, 10, 8, 2, 4, 0, 0
308 					};
309 
310 					const double a = Constants::pi / 12 * (tints[tint] * 2 - 7);
311 
312 					i += std::sin( a ) * s;
313 					q += std::cos( a ) * s;
314 				}
315 			}
316 
Build(const int bi,const int si,const int ci,const int hue)317 			void Renderer::Palette::Build(const int bi,const int si,const int ci,const int hue)
318 			{
319 				NST_ASSERT( type != PALETTE_YUV );
320 
321 				const double brightness = bi / 200.0;
322 				const double saturation = (si + 100) / 100.0;
323 				const double contrast = (ci + 100) / 100.0;
324 
325 				const double matrix[6] =
326 				{
327 					std::sin( (90  - 33 - hue) * Constants::deg ) * (0.570 * 2),
328 					std::cos( (90  - 33 - hue) * Constants::deg ) * (0.570 * 2),
329 					std::sin( (236 - 33 - hue) * Constants::deg ) * (0.351 * 2),
330 					std::cos( (236 - 33 - hue) * Constants::deg ) * (0.351 * 2),
331 					std::sin( (0   - 33 - hue) * Constants::deg ) * (1.015 * 2),
332 					std::cos( (0   - 33 - hue) * Constants::deg ) * (1.015 * 2)
333 				};
334 
335 				const byte (*from)[3] =
336 				(
337 					type == PALETTE_CUSTOM ? custom->palette :
338 					type == PALETTE_VS1    ? vsPalette[0] :
339 					type == PALETTE_VS2    ? vsPalette[1] :
340 					type == PALETTE_VS3    ? vsPalette[2] :
341 					type == PALETTE_VS4    ? vsPalette[3] :
342                                              pc10Palette
343 				);
344 
345 				NST_ASSERT( from );
346 
347 				for (uint i=0; i < 8; ++i)
348 				{
349 					if (i && type == PALETTE_CUSTOM && custom->emphasis)
350 						from = custom->emphasis[i-1];
351 
352 					for (uint j=0; j < 64; ++j)
353 					{
354 						double rgb[3] =
355 						{
356 							from[j][0] / 255.0,
357 							from[j][1] / 255.0,
358 							from[j][2] / 255.0
359 						};
360 
361 						if (i && type != PALETTE_CUSTOM)
362 						{
363 							switch (i)
364 							{
365 								case 1: rgb[0] = 1;                         break;
366 								case 2: rgb[1] = 1;                         break;
367 								case 3: rgb[0] = 1; rgb[1] = 1;             break;
368 								case 4: rgb[2] = 1;                         break;
369 								case 5: rgb[0] = 1; rgb[2] = 1;             break;
370 								case 6: rgb[1] = 1; rgb[2] = 1;             break;
371 								case 7: rgb[0] = 1; rgb[1] = 1; rgb[2] = 1; break;
372 							}
373 						}
374 
375 						double yiq[3] =
376 						{
377 							0.299 * rgb[0] + 0.587 * rgb[1] + 0.114 * rgb[2],
378 							0.596 * rgb[0] - 0.275 * rgb[1] - 0.321 * rgb[2],
379 							0.212 * rgb[0] - 0.523 * rgb[1] + 0.311 * rgb[2]
380 						};
381 
382 						if (i && type == PALETTE_CUSTOM && !custom->emphasis && (j & 0xF) <= 0xD)
383 							GenerateEmphasis( i, Constants::levels[(j & 0xF) != 0xD][j >> 4 & 0x3], yiq[0], yiq[1], yiq[2] );
384 
385 						yiq[0] = yiq[0] * contrast + brightness;
386 						yiq[1] *= saturation;
387 						yiq[2] *= saturation;
388 
389 						for (uint k=0; k < 3; ++k)
390 							rgb[k] = yiq[0] + matrix[k*2+0] * yiq[1] + matrix[k*2+1] * yiq[2];
391 
392 						Store( rgb, palette[(i * 64) + j] );
393 					}
394 				}
395 			}
396 
Generate(const int b,const int s,const int c,int hue)397 			void Renderer::Palette::Generate(const int b,const int s,const int c,int hue)
398 			{
399 				NST_ASSERT( type == PALETTE_YUV );
400 
401 				const double brightness = b / 200.0;
402 				const double saturation = (s + 100) / 100.0;
403 				const double contrast = (c + 100) / 100.0;
404 				hue += 33;
405 
406 				const double matrix[6] =
407 				{
408 					std::sin( (int(decoder.axes[0].angle) - hue) * Constants::deg ) * decoder.axes[0].gain * 2,
409 					std::cos( (int(decoder.axes[0].angle) - hue) * Constants::deg ) * decoder.axes[0].gain * 2,
410 					std::sin( (int(decoder.axes[1].angle) - hue) * Constants::deg ) * decoder.axes[1].gain * 2,
411 					std::cos( (int(decoder.axes[1].angle) - hue) * Constants::deg ) * decoder.axes[1].gain * 2,
412 					std::sin( (int(decoder.axes[2].angle) - hue) * Constants::deg ) * decoder.axes[2].gain * 2,
413 					std::cos( (int(decoder.axes[2].angle) - hue) * Constants::deg ) * decoder.axes[2].gain * 2
414 				};
415 
416 				for (uint n=0; n < PALETTE; ++n)
417 				{
418 					double level[2] =
419 					{
420 						Constants::levels[0][n >> 4 & 3],
421 						Constants::levels[1][n >> 4 & 3]
422 					};
423 
424 					const int color = n & 0x0F;
425 
426 					if (color == 0x00)
427 					{
428 						level[0] = level[1];
429 					}
430 					else if (color == 0x0D)
431 					{
432 						level[1] = level[0];
433 					}
434 					else if (color > 0x0D)
435 					{
436 						level[0] = 0;
437 						level[1] = 0;
438 					}
439 
440 					double y = (level[1] + level[0]) * 0.5;
441 					double s = (level[1] - level[0]) * 0.5;
442 					double h = Constants::pi / 6 * (color - 3);
443 
444 					double i = std::sin( h ) * s;
445 					double q = std::cos( h ) * s;
446 
447 					const uint tint = n >> 6 & 7;
448 
449 					if (tint && color <= 0x0D)
450 						GenerateEmphasis( tint, level[1], y, i, q );
451 
452 					if (decoder.boostYellow)
453 					{
454 						const double yellowness = i - q;
455 
456 						if (yellowness > DBL_EPSILON)
457 						{
458 							i = i + yellowness * ((n >> 4 & 3) / 4.0);
459 							q = q - yellowness * ((n >> 4 & 3) / 4.0);
460 						}
461 					}
462 
463 					i *= saturation;
464 					q *= saturation;
465 					y = y * contrast + brightness;
466 
467 					const double rgb[3] =
468 					{
469 						y + matrix[0] * i + matrix[1] * q,
470 						y + matrix[2] * i + matrix[3] * q,
471 						y + matrix[4] * i + matrix[5] * q
472 					};
473 
474 					Store( rgb, palette[n] );
475 				}
476 			}
477 
Update(int brightness,int saturation,int contrast,int hue)478 			void Renderer::Palette::Update(int brightness,int saturation,int contrast,int hue)
479 			{
480 				FpuPrecision precision;
481 				(*this.*(type == PALETTE_YUV ? &Palette::Generate : &Palette::Build))( brightness, saturation, contrast, hue );
482 			}
483 
Get() const484 			inline const Renderer::PaletteEntries& Renderer::Palette::Get() const
485 			{
486 				return palette;
487 			}
488 
Format(const RenderState & state)489 			Renderer::Filter::Format::Format(const RenderState& state)
490 			: bpp(state.bits.count)
491 			{
492 				for (uint i=0; i < 3; ++i)
493 				{
494 					ulong mask = (i == 0 ? state.bits.mask.r : i == 1 ? state.bits.mask.g : state.bits.mask.b);
495 
496 					shifts[i] = 0;
497 
498 					if (mask)
499 					{
500 						while (!(mask & 0x1))
501 						{
502 							mask >>= 1;
503 							shifts[i]++;
504 						}
505 					}
506 
507 					masks[i] = mask;
508 				}
509 			}
510 
Filter(const RenderState & state)511 			Renderer::Filter::Filter(const RenderState& state)
512 			: format(state) {}
513 
Transform(const byte (& src)[PALETTE][3],Input::Palette & dst) const514 			void Renderer::Filter::Transform(const byte (&src)[PALETTE][3],Input::Palette& dst) const
515 			{
516 				for (uint i=0; i < PALETTE; ++i)
517 				{
518 					dst[i] =
519 					(
520 						((src[i][0] * format.masks[0] + 0x7F) / 0xFF) << format.shifts[0] |
521 						((src[i][1] * format.masks[1] + 0x7F) / 0xFF) << format.shifts[1] |
522 						((src[i][2] * format.masks[2] + 0x7F) / 0xFF) << format.shifts[2]
523 					);
524 				}
525 			}
526 
State()527 			Renderer::State::State()
528 			:
529 			width        (0),
530 			height       (0),
531 			filter       (RenderState::FILTER_NONE),
532 			update       (UPDATE_PALETTE),
533 			fieldMerging (0),
534 			brightness   (0),
535 			saturation   (0),
536 			hue          (0),
537 			contrast     (0),
538 			sharpness    (0),
539 			resolution   (0),
540 			bleed        (0),
541 			artifacts    (0),
542 			fringing     (0),
543 			blendPixels	 (1),
544 			xbr_corner_rounding(0)
545 			{
546 				mask.r = 0;
547 				mask.g = 0;
548 				mask.b = 0;
549 			}
550 
Renderer()551 			Renderer::Renderer()
552 			: filter(NULL) {}
553 
~Renderer()554 			Renderer::~Renderer()
555 			{
556 				delete filter;
557 			}
558 
SetState(const RenderState & renderState)559 			Result Renderer::SetState(const RenderState& renderState)
560 			{
561 				if (filter)
562 				{
563 					if
564 					(
565 						state.filter == renderState.filter &&
566 						state.width == renderState.width &&
567 						state.height == renderState.height &&
568 						filter->format.bpp == renderState.bits.count &&
569 						state.mask.r == renderState.bits.mask.r &&
570 						state.mask.g == renderState.bits.mask.g &&
571 						state.mask.b == renderState.bits.mask.b
572 					)
573 						return RESULT_NOP;
574 
575 					delete filter;
576 					filter = NULL;
577 				}
578 
579 				try
580 				{
581 					switch (renderState.filter)
582 					{
583 						case RenderState::FILTER_NONE:
584 
585 							if (FilterNone::Check( renderState ))
586 								filter = new FilterNone( renderState );
587 
588 							break;
589 
590 					#ifndef NST_NO_SCALEX
591 
592 						case RenderState::FILTER_SCALE2X:
593 						case RenderState::FILTER_SCALE3X:
594 
595 							if (FilterScaleX::Check( renderState ))
596 								filter = new FilterScaleX( renderState );
597 
598 							break;
599 					#endif
600 					#ifndef NST_NO_HQ2X
601 
602 						case RenderState::FILTER_HQ2X:
603 						case RenderState::FILTER_HQ3X:
604 						case RenderState::FILTER_HQ4X:
605 
606 							if (FilterHqX::Check( renderState ))
607 								filter = new FilterHqX( renderState );
608 
609 							break;
610 
611 					#endif
612 					#ifndef NST_NO_2XSAI
613 
614 						case RenderState::FILTER_2XSAI:
615 
616 							if (Filter2xSaI::Check( renderState ))
617 								filter = new Filter2xSaI( renderState );
618 
619 							break;
620 
621 					#endif
622 
623 					#ifndef NO_NTSC
624 						case RenderState::FILTER_NTSC:
625 
626 							if (FilterNtsc::Check( renderState ))
627 							{
628 								filter = new FilterNtsc
629 								(
630 									renderState,
631 									GetPalette(),
632 									state.sharpness,
633 									state.resolution,
634 									state.bleed,
635 									state.artifacts,
636 									state.fringing,
637 									state.fieldMerging
638 								);
639 							}
640 							break;
641 					#endif
642 
643 					#ifndef NST_NO_XBR
644 						case RenderState::FILTER_2XBR:
645 						case RenderState::FILTER_3XBR:
646 						case RenderState::FILTER_4XBR:
647 
648 							if (FilterxBR::Check( renderState ))
649 								filter = new FilterxBR( renderState, state.blendPixels, state.xbr_corner_rounding );
650 							break;
651 					#endif
652 					}
653 				}
654 				catch (const std::bad_alloc&)
655 				{
656 					delete filter;
657 					filter = NULL;
658 
659 					return RESULT_ERR_OUT_OF_MEMORY;
660 				}
661 
662 				if (filter)
663 				{
664 					state.filter = renderState.filter;
665 					state.width = renderState.width;
666 					state.height = renderState.height;
667 					state.mask = renderState.bits.mask;
668 
669 					if (state.filter == RenderState::FILTER_NTSC)
670 						state.update = 0;
671 					else
672 						state.update |= uint(State::UPDATE_FILTER);
673 
674 					return RESULT_OK;
675 				}
676 				else
677 				{
678 					return RESULT_ERR_UNSUPPORTED;
679 				}
680 			}
681 
GetState(RenderState & output) const682 			Result Renderer::GetState(RenderState& output) const
683 			{
684 				if (filter)
685 				{
686 					output.filter = static_cast<RenderState::Filter>(state.filter);
687 					output.width = state.width;
688 					output.height = state.height;
689 					output.bits.count = filter->format.bpp;
690 					output.bits.mask = state.mask;
691 
692 					return RESULT_OK;
693 				}
694 
695 				return RESULT_ERR_NOT_READY;
696 			}
697 
EnableFieldMerging(bool fieldMerging)698 			void Renderer::EnableFieldMerging(bool fieldMerging)
699 			{
700 				const bool old = state.fieldMerging;
701 				state.fieldMerging &= uint(State::FIELD_MERGING_FORCED);
702 
703 				if (fieldMerging)
704 					state.fieldMerging |= uint(State::FIELD_MERGING_USER);
705 
706 				if (bool(state.fieldMerging) != old)
707 					state.update |= uint(State::UPDATE_NTSC);
708 			}
709 
EnableForcedFieldMerging(bool fieldMerging)710 			void Renderer::EnableForcedFieldMerging(bool fieldMerging)
711 			{
712 				const bool old = state.fieldMerging;
713 				state.fieldMerging &= uint(State::FIELD_MERGING_USER);
714 
715 				if (fieldMerging)
716 					state.fieldMerging |= uint(State::FIELD_MERGING_FORCED);
717 
718 				if (bool(state.fieldMerging) != old)
719 					state.update |= uint(State::UPDATE_NTSC);
720 			}
721 
SetHue(int hue)722 			Result Renderer::SetHue(int hue)
723 			{
724 				if (hue < -45 || hue > 45)
725 					return RESULT_ERR_INVALID_PARAM;
726 
727 				if (state.hue == hue)
728 					return RESULT_NOP;
729 
730 				state.hue = hue;
731 				state.update |= uint(State::UPDATE_PALETTE|State::UPDATE_FILTER);
732 
733 				return RESULT_OK;
734 			}
735 
SetLevel(schar & type,int value,uint update)736 			Result Renderer::SetLevel(schar& type,int value,uint update)
737 			{
738 				if (value < -100 || value > 100)
739 					return RESULT_ERR_INVALID_PARAM;
740 
741 				if (type == value)
742 					return RESULT_NOP;
743 
744 				type = value;
745 				state.update |= update;
746 
747 				return RESULT_OK;
748 			}
749 
SetDecoder(const Decoder & decoder)750 			Result Renderer::SetDecoder(const Decoder& decoder)
751 			{
752 				const Result result = palette.SetDecoder( decoder );
753 
754 				if (NES_SUCCEEDED(result) && result != RESULT_NOP && palette.GetType() == PALETTE_YUV)
755 					state.update |= uint(State::UPDATE_PALETTE|State::UPDATE_FILTER);
756 
757 				return result;
758 			}
759 
SetPaletteType(PaletteType type)760 			Result Renderer::SetPaletteType(PaletteType type)
761 			{
762 				const Result result = palette.SetType( type );
763 
764 				if (NES_SUCCEEDED(result) && result != RESULT_NOP)
765 					state.update |= uint(State::UPDATE_PALETTE|State::UPDATE_FILTER);
766 
767 				return result;
768 			}
769 
LoadCustomPalette(const byte (* colors)[3],const bool emphasis)770 			Result Renderer::LoadCustomPalette(const byte (*colors)[3],const bool emphasis)
771 			{
772 				const Result result = palette.LoadCustom( colors, emphasis );
773 
774 				if (NES_SUCCEEDED(result) && result != RESULT_NOP && palette.GetType() == PALETTE_CUSTOM)
775 					state.update |= uint(State::UPDATE_PALETTE|State::UPDATE_FILTER);
776 
777 				return result;
778 			}
779 
ResetCustomPalette()780 			void Renderer::ResetCustomPalette()
781 			{
782 				if (palette.ResetCustom() && palette.GetType() == PALETTE_CUSTOM)
783 					state.update |= uint(State::UPDATE_PALETTE|State::UPDATE_FILTER);
784 			}
785 
GetPalette()786 			const Renderer::PaletteEntries& Renderer::GetPalette()
787 			{
788 				if (state.update & uint(State::UPDATE_PALETTE))
789 				{
790 					state.update &= ~uint(State::UPDATE_PALETTE);
791 					palette.Update( state.brightness, state.saturation, state.contrast, state.hue );
792 				}
793 
794 				return palette.Get();
795 			}
796 
UpdateFilter(Input & input)797 			void Renderer::UpdateFilter(Input& input)
798 			{
799 				NST_VERIFY( state.update );
800 
801 				if (state.filter == RenderState::FILTER_NTSC || state.update == 1)
802 				{
803 					RenderState renderState;
804 					GetState( renderState );
805 
806 					delete filter;
807 					filter = NULL;
808 
809 					SetState( renderState );
810 				}
811 				else if (state.update & uint(State::UPDATE_FILTER))
812 				{
813 					filter->Transform( GetPalette(), input.palette );
814 				}
815 
816 				state.update = 0;
817 			}
818 
819 			#ifdef NST_MSVC_OPTIMIZE
820 			#pragma optimize("", on)
821 			#endif
822 
Blit(Output & output,Input & input,uint burstPhase)823 			void Renderer::Blit(Output& output,Input& input,uint burstPhase)
824 			{
825 				if (filter)
826 				{
827 					if (state.update)
828 						UpdateFilter( input );
829 
830 					if (Output::lockCallback( output ))
831 					{
832 						NST_VERIFY( std::labs(output.pitch) >= dword(state.width) << (filter->format.bpp / 16) );
833 
834 						filter->bgColor = bgColor;
835 
836 						if (std::labs(output.pitch) >= dword(state.width) << (filter->format.bpp / 16))
837 							filter->Blit( input, output, burstPhase );
838 
839 						Output::unlockCallback( output );
840 					}
841 				}
842 			}
843 		}
844 	}
845 }
846