1 /*
2  *  Copyright (C) 2002-2021  The DOSBox Team
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License along
15  *  with this program; if not, write to the Free Software Foundation, Inc.,
16  *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "dosbox.h"
20 
21 #include <array>
22 #include <cmath>
23 #include <cstdint>
24 #include <cstring>
25 
26 #include "control.h"
27 #include "inout.h"
28 #include "mapper.h"
29 #include "mem.h"
30 #include "pic.h"
31 #include "render.h"
32 #include "support.h"
33 #include "vga.h"
34 
write_crtc_index_other(io_port_t,io_val_t value,io_width_t)35 static void write_crtc_index_other(io_port_t, io_val_t value, io_width_t)
36 {
37 	const auto val = check_cast<uint8_t>(value);
38 	// only receives 8-bit data per its IO port registration
39 	vga.other.index = val;
40 }
41 
read_crtc_index_other(io_port_t,io_width_t)42 static uint8_t read_crtc_index_other(io_port_t, io_width_t)
43 {
44 	// only returns 8-bit data per its IO port registration
45 	return vga.other.index;
46 }
47 
write_crtc_data_other(io_port_t,io_val_t value,io_width_t)48 static void write_crtc_data_other(io_port_t, io_val_t value, io_width_t)
49 {
50 	// only receives 8-bit data per its IO port registration
51 	auto val = check_cast<uint8_t>(value);
52 
53 	switch (vga.other.index) {
54 	case 0x00:		//Horizontal total
55 		if (vga.other.htotal ^ val) VGA_StartResize();
56 		vga.other.htotal = val;
57 		break;
58 	case 0x01:		//Horizontal displayed chars
59 		if (vga.other.hdend ^ val) VGA_StartResize();
60 		vga.other.hdend = val;
61 		break;
62 	case 0x02:		//Horizontal sync position
63 		vga.other.hsyncp = val;
64 		break;
65 	case 0x03:		//Horizontal sync width
66 		if (machine == MCH_TANDY)
67 			vga.other.vsyncw = val >> 4;
68 		else
69 			// The MC6845 has a fixed v-sync width of 16 lines
70 			vga.other.vsyncw = 16;
71 		vga.other.hsyncw = val & 0xf;
72 		break;
73 	case 0x04:		//Vertical total
74 		if (vga.other.vtotal ^ val) VGA_StartResize();
75 		vga.other.vtotal = val;
76 		break;
77 	case 0x05:		//Vertical display adjust
78 		if (vga.other.vadjust ^ val) VGA_StartResize();
79 		vga.other.vadjust = val;
80 		break;
81 	case 0x06:		//Vertical rows
82 		if (vga.other.vdend ^ val) VGA_StartResize();
83 		vga.other.vdend = val;
84 		break;
85 	case 0x07:		//Vertical sync position
86 		vga.other.vsyncp = val;
87 		break;
88 	case 0x09:		//Max scanline
89 		val &= 0x1f; // VGADOC says bit 0-3 but the MC6845 datasheet says bit 0-4
90  		if (vga.other.max_scanline ^ val) VGA_StartResize();
91 		vga.other.max_scanline = val;
92 		break;
93 	case 0x0A:	/* Cursor Start Register */
94 		vga.other.cursor_start = val & 0x3f;
95 		vga.draw.cursor.sline = val & 0x1f;
96 		vga.draw.cursor.enabled = ((val & 0x60) != 0x20);
97 		break;
98 	case 0x0B:	/* Cursor End Register */
99 		vga.other.cursor_end = val & 0x1f;
100 		vga.draw.cursor.eline = val & 0x1f;
101 		break;
102 	case 0x0C:	/* Start Address High Register */
103 		// Bit 12 (depending on video mode) and 13 are actually masked too,
104 		// but so far no need to implement it.
105 		vga.config.display_start = (vga.config.display_start & 0x00FF) |
106 		                           static_cast<uint16_t>((val & 0x3F) << 8);
107 		break;
108 	case 0x0D:	/* Start Address Low Register */
109 		vga.config.display_start=(vga.config.display_start & 0xFF00) | val;
110 		break;
111 	case 0x0E:	/*Cursor Location High Register */
112 		vga.config.cursor_start &= 0x00ff;
113 		vga.config.cursor_start |= static_cast<uint16_t>(val << 8);
114 		break;
115 	case 0x0F: /* Cursor Location Low Register */
116 		vga.config.cursor_start &= 0xff00;
117 		vga.config.cursor_start |= val;
118 		break;
119 	case 0x10:	/* Light Pen High */
120 		vga.other.lightpen &= 0xff;
121 		vga.other.lightpen |= (val & 0x3f)<<8;		// only 6 bits
122 		break;
123 	case 0x11:	/* Light Pen Low */
124 		vga.other.lightpen &= 0xff00;
125 		vga.other.lightpen |= val;
126 		break;
127 	default:
128 		LOG(LOG_VGAMISC, LOG_NORMAL)("MC6845:Write %u to illegal index %x", val, vga.other.index);
129 	}
130 }
read_crtc_data_other(io_port_t,io_width_t)131 static uint8_t read_crtc_data_other(io_port_t, io_width_t)
132 {
133 	// only returns 8-bit data per its IO port registration
134 	switch (vga.other.index) {
135 	case 0x00:		//Horizontal total
136 		return vga.other.htotal;
137 	case 0x01:		//Horizontal displayed chars
138 		return vga.other.hdend;
139 	case 0x02:		//Horizontal sync position
140 		return vga.other.hsyncp;
141 	case 0x03:		//Horizontal and vertical sync width
142 		// hsyncw and vsyncw should only be populated with their lower 4-bits
143 		assert(vga.other.hsyncw >> 4 == 0);
144 		assert(vga.other.vsyncw >> 4 == 0);
145 		if (machine == MCH_TANDY)
146 			return static_cast<uint8_t>(vga.other.hsyncw | (vga.other.vsyncw << 4));
147 		else
148 			return vga.other.hsyncw;
149 	case 0x04:		//Vertical total
150 		return vga.other.vtotal;
151 	case 0x05:		//Vertical display adjust
152 		return vga.other.vadjust;
153 	case 0x06:		//Vertical rows
154 		return vga.other.vdend;
155 	case 0x07:		//Vertical sync position
156 		return vga.other.vsyncp;
157 	case 0x09:		//Max scanline
158 		return vga.other.max_scanline;
159 	case 0x0A:	/* Cursor Start Register */
160 		return vga.other.cursor_start;
161 	case 0x0B:	/* Cursor End Register */
162 		return vga.other.cursor_end;
163 	case 0x0C:	/* Start Address High Register */
164 		return static_cast<uint8_t>(vga.config.display_start >> 8);
165 	case 0x0D:	/* Start Address Low Register */
166 		return static_cast<uint8_t>(vga.config.display_start & 0xff);
167 	case 0x0E:	/*Cursor Location High Register */
168 		return static_cast<uint8_t>(vga.config.cursor_start >> 8);
169 	case 0x0F:	/* Cursor Location Low Register */
170 		return static_cast<uint8_t>(vga.config.cursor_start & 0xff);
171 	case 0x10:	/* Light Pen High */
172 		return static_cast<uint8_t>(vga.other.lightpen >> 8);
173 	case 0x11:	/* Light Pen Low */
174 		return static_cast<uint8_t>(vga.other.lightpen & 0xff);
175 	default:
176 		LOG(LOG_VGAMISC,LOG_NORMAL)("MC6845:Read from illegal index %x",vga.other.index);
177 	}
178 	return static_cast<uint8_t>(~0);
179 }
180 
write_lightpen(io_port_t port,io_val_t,io_width_t)181 static void write_lightpen(io_port_t port, io_val_t, io_width_t)
182 {
183 	// only receives 8-bit data per its IO port registration
184 	switch (port) {
185 	case 0x3db:	// Clear lightpen latch
186 		vga.other.lightpen_triggered = false;
187 		break;
188 	case 0x3dc:	// Preset lightpen latch
189 		if (!vga.other.lightpen_triggered) {
190 			vga.other.lightpen_triggered = true; // TODO: this shows at port 3ba/3da bit 1
191 
192 			const auto timeInFrame = PIC_FullIndex() - vga.draw.delay.framestart;
193 			const auto timeInLine = fmod(timeInFrame, vga.draw.delay.htotal);
194 			Bitu current_scanline = (Bitu)(timeInFrame / vga.draw.delay.htotal);
195 
196 			vga.other.lightpen = (Bit16u)((vga.draw.address_add/2) * (current_scanline/2));
197 			vga.other.lightpen += static_cast<uint16_t>(
198 			        (timeInLine / vga.draw.delay.hdend) *
199 			        (static_cast<double>(vga.draw.address_add / 2)));
200 		}
201 		break;
202 	}
203 }
204 
205 class knob_t {
206 public:
207 	knob_t() = delete;
knob_t(const int default_value,const int min_value,const int max_value)208 	knob_t(const int default_value, const int min_value, const int max_value)
209 	        : def(default_value),
210 	          min(min_value),
211 	          max(max_value),
212 	          val(default_value)
213 	{
214 		assert(def == val);
215 		assert(min <= val && val <= max);
216 		assert(min <= max);
217 	}
218 	// modify the value
get() const219 	int get() const { return val; }
set(int new_val)220 	void set(int new_val) { val = wrap(new_val, min, max); }
turn(int amount)221 	void turn(int amount) { set(val + amount); }
reset()222 	void reset() { set(def); }
223 
224 	// read-only functions to get constraints
as_float() const225 	float as_float() const { return static_cast<float>(val); }
get_default() const226 	int get_default() const { return def; }
get_min() const227 	int get_min() const { return min; }
get_max() const228 	int get_max() const { return max; }
229 
230 private:
231 	const int def; // "default" is a C++ keyword (don't use it)
232 	const int min;
233 	const int max;
234 	int val;
235 };
236 
237 static knob_t hue(0, -360, 360);
238 static knob_t saturation(100, 0, 360);
239 static knob_t contrast(100, 0, 360);
240 static knob_t brightness(0, -100, 100);
241 static knob_t convergence(0, -50, 50);
242 
243 enum class COMPOSITE_STATE : uint8_t {
244 	AUTO = 0,
245 	ON,
246 	OFF,
247 };
248 static COMPOSITE_STATE cga_comp = COMPOSITE_STATE::AUTO;
249 static bool is_composite_new_era = false;
250 
251 static uint8_t herc_pal = 0;
252 static uint8_t mono_cga_pal = 0;
253 static uint8_t mono_cga_bright = 0;
254 
255 constexpr uint8_t mono_cga_palettes[8][16][3] = {
256         {
257                 // 0 - green, 4-color-optimized contrast
258                 {0x00, 0x00, 0x00},
259                 {0x00, 0x0d, 0x03},
260                 {0x01, 0x17, 0x05},
261                 {0x01, 0x1a, 0x06},
262                 {0x02, 0x28, 0x09},
263                 {0x02, 0x2c, 0x0a},
264                 {0x03, 0x39, 0x0d},
265                 {0x03, 0x3c, 0x0e},
266                 {0x00, 0x07, 0x01},
267                 {0x01, 0x13, 0x04},
268                 {0x01, 0x1f, 0x07},
269                 {0x01, 0x23, 0x08},
270                 {0x02, 0x31, 0x0b},
271                 {0x02, 0x35, 0x0c},
272                 {0x05, 0x3f, 0x11},
273                 {0x0d, 0x3f, 0x17},
274         },
275         {
276                 // 1 - green, 16-color-optimized contrast
277                 {0x00, 0x00, 0x00},
278                 {0x00, 0x0d, 0x03},
279                 {0x01, 0x15, 0x05},
280                 {0x01, 0x17, 0x05},
281                 {0x01, 0x21, 0x08},
282                 {0x01, 0x24, 0x08},
283                 {0x02, 0x2e, 0x0b},
284                 {0x02, 0x31, 0x0b},
285                 {0x01, 0x22, 0x08},
286                 {0x02, 0x28, 0x09},
287                 {0x02, 0x30, 0x0b},
288                 {0x02, 0x32, 0x0c},
289                 {0x03, 0x39, 0x0d},
290                 {0x03, 0x3b, 0x0e},
291                 {0x09, 0x3f, 0x14},
292                 {0x0d, 0x3f, 0x17},
293         },
294         {
295                 // 2 - amber, 4-color-optimized contrast
296                 {0x00, 0x00, 0x00},
297                 {0x15, 0x05, 0x00},
298                 {0x20, 0x0b, 0x00},
299                 {0x24, 0x0d, 0x00},
300                 {0x33, 0x18, 0x00},
301                 {0x37, 0x1b, 0x00},
302                 {0x3f, 0x26, 0x01},
303                 {0x3f, 0x2b, 0x06},
304                 {0x0b, 0x02, 0x00},
305                 {0x1b, 0x08, 0x00},
306                 {0x29, 0x11, 0x00},
307                 {0x2e, 0x14, 0x00},
308                 {0x3b, 0x1e, 0x00},
309                 {0x3e, 0x21, 0x00},
310                 {0x3f, 0x32, 0x0a},
311                 {0x3f, 0x38, 0x0d},
312         },
313         {
314                 // 3 - amber, 16-color-optimized contrast
315                 {0x00, 0x00, 0x00},
316                 {0x15, 0x05, 0x00},
317                 {0x1e, 0x09, 0x00},
318                 {0x21, 0x0b, 0x00},
319                 {0x2b, 0x12, 0x00},
320                 {0x2f, 0x15, 0x00},
321                 {0x38, 0x1c, 0x00},
322                 {0x3b, 0x1e, 0x00},
323                 {0x2c, 0x13, 0x00},
324                 {0x32, 0x17, 0x00},
325                 {0x3a, 0x1e, 0x00},
326                 {0x3c, 0x1f, 0x00},
327                 {0x3f, 0x27, 0x01},
328                 {0x3f, 0x2a, 0x04},
329                 {0x3f, 0x36, 0x0c},
330                 {0x3f, 0x38, 0x0d},
331         },
332         {
333                 // 4 - grey, 4-color-optimized contrast
334                 {0x00, 0x00, 0x00},
335                 {0x0d, 0x0d, 0x0d},
336                 {0x15, 0x15, 0x15},
337                 {0x18, 0x18, 0x18},
338                 {0x24, 0x24, 0x24},
339                 {0x27, 0x27, 0x27},
340                 {0x33, 0x33, 0x33},
341                 {0x37, 0x37, 0x37},
342                 {0x08, 0x08, 0x08},
343                 {0x10, 0x10, 0x10},
344                 {0x1c, 0x1c, 0x1c},
345                 {0x20, 0x20, 0x20},
346                 {0x2c, 0x2c, 0x2c},
347                 {0x2f, 0x2f, 0x2f},
348                 {0x3b, 0x3b, 0x3b},
349                 {0x3f, 0x3f, 0x3f},
350         },
351         {
352                 // 5 - grey, 16-color-optimized contrast
353                 {0x00, 0x00, 0x00},
354                 {0x0d, 0x0d, 0x0d},
355                 {0x12, 0x12, 0x12},
356                 {0x15, 0x15, 0x15},
357                 {0x1e, 0x1e, 0x1e},
358                 {0x20, 0x20, 0x20},
359                 {0x29, 0x29, 0x29},
360                 {0x2c, 0x2c, 0x2c},
361                 {0x1f, 0x1f, 0x1f},
362                 {0x23, 0x23, 0x23},
363                 {0x2b, 0x2b, 0x2b},
364                 {0x2d, 0x2d, 0x2d},
365                 {0x34, 0x34, 0x34},
366                 {0x36, 0x36, 0x36},
367                 {0x3d, 0x3d, 0x3d},
368                 {0x3f, 0x3f, 0x3f},
369         },
370         {
371                 // 6 - paper-white, 4-color-optimized contrast
372                 {0x00, 0x00, 0x00},
373                 {0x0e, 0x0f, 0x10},
374                 {0x15, 0x17, 0x18},
375                 {0x18, 0x1a, 0x1b},
376                 {0x24, 0x25, 0x25},
377                 {0x27, 0x28, 0x28},
378                 {0x33, 0x34, 0x32},
379                 {0x37, 0x38, 0x35},
380                 {0x09, 0x0a, 0x0b},
381                 {0x11, 0x12, 0x13},
382                 {0x1c, 0x1e, 0x1e},
383                 {0x20, 0x22, 0x22},
384                 {0x2c, 0x2d, 0x2c},
385                 {0x2f, 0x30, 0x2f},
386                 {0x3c, 0x3c, 0x38},
387                 {0x3f, 0x3f, 0x3b},
388         },
389         {
390                 // 7 - paper-white, 16-color-optimized contrast
391                 {0x00, 0x00, 0x00},
392                 {0x0e, 0x0f, 0x10},
393                 {0x13, 0x14, 0x15},
394                 {0x15, 0x17, 0x18},
395                 {0x1e, 0x20, 0x20},
396                 {0x20, 0x22, 0x22},
397                 {0x29, 0x2a, 0x2a},
398                 {0x2c, 0x2d, 0x2c},
399                 {0x1f, 0x21, 0x21},
400                 {0x23, 0x25, 0x25},
401                 {0x2b, 0x2c, 0x2b},
402                 {0x2d, 0x2e, 0x2d},
403                 {0x34, 0x35, 0x33},
404                 {0x37, 0x37, 0x34},
405                 {0x3e, 0x3e, 0x3a},
406                 {0x3f, 0x3f, 0x3b},
407         }};
408 
get_rgbi_coefficient(const bool is_new_cga,const uint8_t ovescan)409 constexpr float get_rgbi_coefficient(const bool is_new_cga, const uint8_t ovescan)
410 {
411 	const auto r_coefficient = is_new_cga ? 0.10f : 0.0f;
412 	const auto g_coefficient = is_new_cga ? 0.22f : 0.0f;
413 	const auto b_coefficient = is_new_cga ? 0.07f : 0.0f;
414 	const auto i_coefficient = is_new_cga ? 0.32f : 0.28f;
415 
416 	const auto r = (ovescan & 4) ? r_coefficient : 0.0f;
417 	const auto g = (ovescan & 2) ? g_coefficient : 0.0f;
418 	const auto b = (ovescan & 1) ? b_coefficient : 0.0f;
419 	const auto i = (ovescan & 8) ? i_coefficient : 0.0f;
420 	return r + g + b + i;
421 }
422 
update_cga16_color_pcjr()423 static void update_cga16_color_pcjr()
424 {
425 	assert(machine == MCH_PCJR);
426 
427 	// First composite algorithm based on code by reenigne updated by NewRisingSun and tailored for PCjr-only
428 	// composite modes (for DOSBox Staging).
429 	constexpr auto tau = 6.28318531f;    // == 2*pi
430 	constexpr auto ns = 567.0f / 440.0f; // degrees of hue shift per nanosecond
431 
432 	const auto tv_brightness = brightness.as_float() / 100.0f;
433 	const auto tv_saturation = saturation.as_float() / 100.0f;
434 	const auto tv_contrast = (1 - tv_brightness) * contrast.as_float() / 100.0f;
435 
436 	const bool bw = vga.tandy.mode_control & 4;
437 	const bool bpp1 = vga.tandy.gfx_control & 0x08;
438 
439 	std::array<float, 16> rgbi_coefficients = {};
440 	for (uint8_t c = 0; c < rgbi_coefficients.size(); c++)
441 		rgbi_coefficients[c] = get_rgbi_coefficient(is_composite_new_era, c);
442 
443 	// The pixel clock delay calculation is not accurate for 2bpp, but the difference is small and a more accurate
444 	// calculation would be too slow.
445 	constexpr auto rgbi_pixel_delay = 15.5f * ns;
446 	constexpr float chroma_pixel_delays[8] = {0.0f,        // Black:   no chroma
447 	                                          35.0f * ns,  // Blue:    no XORs
448 	                                          44.5f * ns,  // Green:   XOR on rising and falling edges
449 	                                          39.5f * ns,  // Cyan:    XOR on falling but not rising edge
450 	                                          44.5f * ns,  // Red:     XOR on rising and falling edges
451 	                                          39.5f * ns,  // Magenta: XOR on falling but not/ rising edge
452 	                                          44.5f * ns,  // Yellow:  XOR on rising and falling edges
453 	                                          39.5f * ns}; // White:   XOR on falling but not rising edge
454 
455 	constexpr uint8_t overscan = 15;
456 	constexpr auto cp_d = chroma_pixel_delays[overscan & 7];
457 	const auto rgbi_d = rgbi_coefficients[overscan];
458 	const auto chroma_coefficient = is_composite_new_era ? 0.29f : 0.72f;
459 
460 	constexpr auto burst_delay = 60.0f * ns;
461 	const auto color_delay = bpp1 ? 0.0f : 25.0f * ns;
462 
463 	const float pixel_clock_delay = (cp_d * chroma_coefficient + rgbi_pixel_delay * rgbi_d) /
464 	                                        (chroma_coefficient + rgbi_d) +
465 	                                burst_delay + color_delay;
466 
467 	const float hue_adjust = (-(90.0f - 33.0f) - hue.as_float() + pixel_clock_delay) * tau / 360.0f;
468 	float chroma_signals[8][4];
469 	for (uint8_t i = 0; i < 4; i++) {
470 		chroma_signals[0][i] = 0;
471 		chroma_signals[7][i] = 1;
472 		for (uint8_t j = 0; j < 6; j++) {
473 			constexpr float phases[6] = {270.0f - 21.5f * ns,  // blue
474 			                             135.0f - 29.5f * ns,  // green
475 			                             180.0f - 21.5f * ns,  // cyan
476 			                             000.0f - 21.5f * ns,  // red
477 			                             315.0f - 29.5f * ns,  // magenta
478 			                             090.0f - 21.5f * ns}; // yellow/burst
479 
480 			// All the duty cycle fractions are the same, just under 0.5 as the rising edge is delayed 2ns
481 			// more than the falling edge.
482 			constexpr float duty = 0.5f - 2 * ns / 360.0f;
483 
484 			// We have a rectangle wave with period 1 (in units of the reciprocal of the color burst
485 			// frequency) and duty cycle fraction "duty" and phase "phase". We band-limit this wave to
486 			// frequency 2 and sample it at intervals of 1/4. We model our band-limited wave with
487 
488 			// 4 frequency components:
489 			//   f(x) = a + b*sin(x*tau) + c*cos(x*tau) +
490 			//   d*sin(x*2*tau)
491 			// Then:
492 			//   a =   integral(0, 1, f(x)*dx) = duty
493 			//   b = 2*integral(0, 1, f(x)*sin(x*tau)*dx) =
494 			//   2*integral(0, duty, sin(x*tau)*dx) =
495 			//   2*(1-cos(x*tau))/tau c = 2*integral(0, 1,
496 			//   f(x)*cos(x*tau)*dx) = 2*integral(0, duty,
497 			//   cos(x*tau)*dx) = 2*sin(duty*tau)/tau d =
498 			//   2*integral(0, 1, f(x)*sin(x*2*tau)*dx) =
499 			//   2*integral(0, duty, sin(x*4*pi)*dx) =
500 			//   2*(1-cos(2*tau*duty))/(2*tau)
501 
502 			constexpr auto a = duty;
503 			const auto b = 2.0f * (1.0f - cosf(duty * tau)) / tau;
504 			const auto c = 2.0f * sinf(duty * tau) / tau;
505 			const auto d = 2.0f * (1.0f - cosf(duty * 2 * tau)) / (2 * tau);
506 			const auto x = (phases[j] + 21.5f * ns + pixel_clock_delay) / 360.0f + i / 4.0f;
507 
508 			chroma_signals[j + 1][i] = a + b * sinf(x * tau) + c * cosf(x * tau) + d * sinf(x * 2 * tau);
509 		}
510 	}
511 
512 	// PCjr CGA palette table
513 	const std::array<uint8_t, 4> pcjr_palette = {
514 	        vga.attr.palette[0],
515 	        vga.attr.palette[1],
516 	        vga.attr.palette[2],
517 	        vga.attr.palette[3],
518 	};
519 
520 	for (uint8_t x = 0; x < 4; ++x) { // Position of pixel in question
521 		const bool even = !(x & 1);
522 		for (uint8_t bits = 0; bits < (even ? 0x10 : 0x40); ++bits) {
523 			float Y = 0.0f, I = 0.0f, Q = 0.0f;
524 			for (uint8_t p = 0; p < 4; ++p) { // Position within color carrier cycle
525 				// generate pixel pattern.
526 				uint8_t rgbi = 0;
527 				if (bpp1) {
528 					rgbi = ((bits >> (3 - p)) & (even ? 1 : 2)) != 0 ? overscan : 0;
529 				} else {
530 					const size_t i = even ? (bits >> (2 - (p & 2))) & 3
531 					                      : (bits >> (4 - ((p + 1) & 6))) & 3;
532 					assert(i < pcjr_palette.size());
533 					rgbi = pcjr_palette[i];
534 				}
535 				const uint8_t c = (bw && rgbi & 7) ? 7 : rgbi & 7;
536 
537 				// calculate composite output
538 				const auto chroma = chroma_signals[c][(p + x) & 3] * chroma_coefficient;
539 				const auto composite = chroma + rgbi_coefficients[rgbi];
540 
541 				Y += composite;
542 				if (!bw) { // burst on
543 					I += composite * 2 * cosf(hue_adjust + (p + x) * tau / 4.0f);
544 					Q += composite * 2 * sinf(hue_adjust + (p + x) * tau / 4.0f);
545 				}
546 			}
547 
548 			Y = clamp(tv_brightness + tv_contrast * Y / 4.0f, 0.0f, 1.0f);
549 			I = clamp(tv_saturation * tv_contrast * I / 4.0f, -0.5957f, 0.5957f);
550 			Q = clamp(tv_saturation * tv_contrast * Q / 4.0f, -0.5226f, 0.5226f);
551 
552 			constexpr auto gamma = 2.2f;
553 
554 			auto normalize_and_apply_gamma = [=](float v) -> float {
555 				const auto normalized = clamp((v - 0.075f) / (1 - 0.075f), 0.0f, 1.0f);
556 				return powf(normalized, gamma);
557 			};
558 			const auto R = normalize_and_apply_gamma(Y + 0.9563f * I + 0.6210f * Q);
559 			const auto G = normalize_and_apply_gamma(Y - 0.2721f * I - 0.6474f * Q);
560 			const auto B = normalize_and_apply_gamma(Y - 1.1069f * I + 1.7046f * Q);
561 
562 			auto to_8bit_without_gamma = [=](float v) -> uint8_t {
563 				const auto without_gamma = clamp(powf(v, 1 / gamma), 0.0f, 1.0f);
564 				return static_cast<uint8_t>(255 * without_gamma);
565 			};
566 			const auto r = to_8bit_without_gamma(1.5073f * R - 0.3725f * G - 0.0832f * B);
567 			const auto g = to_8bit_without_gamma(-0.0275f * R + 0.9350f * G + 0.0670f * B);
568 			const auto b = to_8bit_without_gamma(-0.0272f * R - 0.0401f * G + 1.1677f * B);
569 
570 			const uint8_t index = bits | ((x & 1) == 0 ? 0x30 : 0x80) | ((x & 2) == 0 ? 0x40 : 0);
571 			RENDER_SetPal(index, r, g, b);
572 		}
573 	}
574 }
575 
576 template <typename chroma_t>
new_cga_v(const chroma_t c,const float i,const float r,const float g,const float b)577 constexpr float new_cga_v(const chroma_t c,
578                           const float i,
579                           const float r,
580                           const float g,
581                           const float b)
582 {
583 	const auto c_weighted = 0.29f * c / 0.72f;
584 	const auto i_weighted = 0.32f * i / 0.28f;
585 	const auto r_weighted = 0.10f * r / 0.28f;
586 	const auto g_weighted = 0.22f * g / 0.28f;
587 	const auto b_weighted = 0.07f * b / 0.28f;
588 	return c_weighted + i_weighted + r_weighted + g_weighted + b_weighted;
589 }
590 
update_cga16_color()591 static void update_cga16_color()
592 {
593 	// New algorithm by reenigne
594 	// Works in all CGA modes/color settings and can simulate older and
595 	// newer CGA revisions
596 	constexpr auto tau = static_cast<float>(2 * M_PI);
597 
598 	constexpr uint8_t chroma_multiplexer[256] = {
599 	        2,   2,   2,   2,   114, 174, 4,   3,   2,   1,   133, 135, 2,
600 	        113, 150, 4,   133, 2,   1,   99,  151, 152, 2,   1,   3,   2,
601 	        96,  136, 151, 152, 151, 152, 2,   56,  62,  4,   111, 250, 118,
602 	        4,   0,   51,  207, 137, 1,   171, 209, 5,   140, 50,  54,  100,
603 	        133, 202, 57,  4,   2,   50,  153, 149, 128, 198, 198, 135, 32,
604 	        1,   36,  81,  147, 158, 1,   42,  33,  1,   210, 254, 34,  109,
605 	        169, 77,  177, 2,   0,   165, 189, 154, 3,   44,  33,  0,   91,
606 	        197, 178, 142, 144, 192, 4,   2,   61,  67,  117, 151, 112, 83,
607 	        4,   0,   249, 255, 3,   107, 249, 117, 147, 1,   50,  162, 143,
608 	        141, 52,  54,  3,   0,   145, 206, 124, 123, 192, 193, 72,  78,
609 	        2,   0,   159, 208, 4,   0,   53,  58,  164, 159, 37,  159, 171,
610 	        1,   248, 117, 4,   98,  212, 218, 5,   2,   54,  59,  93,  121,
611 	        176, 181, 134, 130, 1,   61,  31,  0,   160, 255, 34,  1,   1,
612 	        58,  197, 166, 0,   177, 194, 2,   162, 111, 34,  96,  205, 253,
613 	        32,  1,   1,   57,  123, 125, 119, 188, 150, 112, 78,  4,   0,
614 	        75,  166, 180, 20,  38,  78,  1,   143, 246, 42,  113, 156, 37,
615 	        252, 4,   1,   188, 175, 129, 1,   37,  118, 4,   88,  249, 202,
616 	        150, 145, 200, 61,  59,  60,  60,  228, 252, 117, 77,  60,  58,
617 	        248, 251, 81,  212, 254, 107, 198, 59,  58,  169, 250, 251, 81,
618 	        80,  100, 58,  154, 250, 251, 252, 252, 252,
619 	};
620 
621 	constexpr float intensity[4] = {
622 	        77.175381f,
623 	        88.654656f,
624 	        166.564623f,
625 	        174.228438f,
626 	};
627 
628 	constexpr auto i0 = intensity[0];
629 	constexpr auto i3 = intensity[3];
630 
631 	const auto min_v = is_composite_new_era
632 	                           ? new_cga_v(chroma_multiplexer[0], i0, i0, i0, i0)
633 	                           : chroma_multiplexer[0] + i0;
634 
635 	const auto max_v = is_composite_new_era
636 	                           ? new_cga_v(chroma_multiplexer[255], i3, i3, i3, i3)
637 	                           : chroma_multiplexer[255] + i3;
638 
639 	const auto mode_contrast = 2.56f * contrast.as_float() / (max_v - min_v);
640 
641 	const auto mode_brightness = brightness.as_float() * 5 - 256 * min_v / (max_v - min_v);
642 
643 	const bool in_tandy_text_mode = (vga.mode == M_CGA_TEXT_COMPOSITE) &&
644 	                                (vga.tandy.mode_control & 1);
645 	const auto mode_hue = in_tandy_text_mode ? 14.0f : 4.0f;
646 
647 	const auto mode_saturation = saturation.as_float() * (is_composite_new_era ? 5.8f : 2.9f) / 100;
648 
649 	// Update the Composite CGA palette
650 	const bool in_tandy_mode_4 = vga.tandy.mode_control & 4;
651 	for (uint16_t x = 0; x < 1024; ++x) {
652 		const uint16_t right = (x >> 2) & 15;
653 		const uint16_t rc = in_tandy_mode_4
654 		                            ? (right & 8) |
655 		                                      ((right & 7) != 0 ? 7 : 0)
656 		                            : right;
657 
658 		const uint16_t left = (x >> 6) & 15;
659 		const uint16_t lc = in_tandy_mode_4
660 		                            ? (left & 8) | ((left & 7) != 0 ? 7 : 0)
661 		                            : left;
662 
663 		const uint16_t phase = x & 3;
664 		const float c = chroma_multiplexer[((lc & 7) << 5) | ((rc & 7) << 2) | phase];
665 
666 		const float i = intensity[(left >> 3) | ((right >> 2) & 2)];
667 
668 		if (is_composite_new_era) {
669 			const float r = intensity[((left >> 2) & 1) | ((right >> 1) & 2)];
670 			const float g = intensity[((left >> 1) & 1) | (right & 2)];
671 			const float b = intensity[(left & 1) | ((right << 1) & 2)];
672 			const auto v = new_cga_v(c, i, r, g, b);
673 			CGA_Composite_Table[x] = static_cast<int>(
674 			        v * mode_contrast + mode_brightness);
675 		} else {
676 			const auto v = c + i;
677 			CGA_Composite_Table[x] = static_cast<int>(
678 			        v * mode_contrast + mode_brightness);
679 		}
680 	}
681 
682 	const auto i = static_cast<float>(CGA_Composite_Table[6 * 68] -
683 	                                  CGA_Composite_Table[6 * 68 + 2]);
684 	const auto q = static_cast<float>(CGA_Composite_Table[6 * 68 + 1] -
685 	                                  CGA_Composite_Table[6 * 68 + 3]);
686 
687 	const auto a = tau * (33 + 90 + hue.as_float() + mode_hue) / 360.0f;
688 	const auto c = cosf(a);
689 	const auto s = sinf(a);
690 
691 	const auto r = in_tandy_mode_4
692 	                       ? 0.0f
693 	                       : 256 * mode_saturation / sqrt(i * i + q * q);
694 
695 	const auto iq_adjust_i = -(i * c + q * s) * r;
696 	const auto iq_adjust_q = (q * c - i * s) * r;
697 
698 	constexpr auto ri = 0.9563f;
699 	constexpr auto rq = 0.6210f;
700 	constexpr auto gi = -0.2721f;
701 	constexpr auto gq = -0.6474f;
702 	constexpr auto bi = -1.1069f;
703 	constexpr auto bq = 1.7046f;
704 
705 	vga.ri = static_cast<int>(ri * iq_adjust_i + rq * iq_adjust_q);
706 	vga.rq = static_cast<int>(-ri * iq_adjust_q + rq * iq_adjust_i);
707 	vga.gi = static_cast<int>(gi * iq_adjust_i + gq * iq_adjust_q);
708 	vga.gq = static_cast<int>(-gi * iq_adjust_q + gq * iq_adjust_i);
709 	vga.bi = static_cast<int>(bi * iq_adjust_i + bq * iq_adjust_q);
710 	vga.bq = static_cast<int>(-bi * iq_adjust_q + bq * iq_adjust_i);
711 	vga.sharpness = convergence.get() * 256 / 100;
712 }
713 
714 enum CRT_KNOB : uint8_t {
715 	ERA = 0,
716 	HUE,
717 	SATURATION,
718 	CONTRAST,
719 	BRIGHTNESS,
720 	CONVERGENCE,
721 	ENUM_END
722 };
723 static auto crt_knob = CRT_KNOB::ERA;
724 
log_crt_knob_value()725 static void log_crt_knob_value()
726 {
727 	switch (crt_knob) {
728 	case CRT_KNOB::ERA:
729 		LOG_MSG("COMPOSITE: %s-era CGA selected",
730 		        is_composite_new_era ? "New" : "Old");
731 		break;
732 	case CRT_KNOB::HUE: LOG_MSG("COMPOSITE: Hue is %d", hue.get()); break;
733 	case CRT_KNOB::SATURATION: LOG_MSG("COMPOSITE: Saturation is %d", saturation.get()); break;
734 	case CRT_KNOB::CONTRAST: LOG_MSG("COMPOSITE: Contrast is %d", contrast.get()); break;
735 	case CRT_KNOB::BRIGHTNESS: LOG_MSG("COMPOSITE: Brightness is %d", brightness.get()); break;
736 	case CRT_KNOB::CONVERGENCE: LOG_MSG("COMPOSITE: Convergence is %d", convergence.get()); break;
737 	case CRT_KNOB::ENUM_END: assertm(false, "Should not reach CRT knob end marker"); break;
738 	}
739 }
740 static void turn_crt_knob(bool pressed, const int amount);
741 
turn_crt_knob_positive(bool pressed)742 static void turn_crt_knob_positive(bool pressed)
743 {
744 	turn_crt_knob(pressed, 5);
745 }
746 
turn_crt_knob_negative(bool pressed)747 static void turn_crt_knob_negative(bool pressed)
748 {
749 	turn_crt_knob(pressed, -5);
750 }
751 
select_next_crt_knob(bool pressed)752 static void select_next_crt_knob(bool pressed)
753 {
754 	if (!pressed)
755 		return;
756 
757 	auto next_knob = static_cast<uint8_t>(crt_knob) + 1;
758 
759 	// PCjr doesn't have a convergence knob
760 	if (machine == MCH_PCJR && next_knob >= CRT_KNOB::CONVERGENCE)
761 		next_knob++;
762 
763 	crt_knob = static_cast<CRT_KNOB>(next_knob % CRT_KNOB::ENUM_END);
764 
765 	log_crt_knob_value();
766 }
767 
write_cga_color_select(uint8_t val)768 static void write_cga_color_select(uint8_t val)
769 {
770 	// only receives 8-bit data per its IO port registration
771 	vga.tandy.color_select=val;
772 	switch(vga.mode) {
773 	case M_TANDY4:
774 	case M_CGA4_COMPOSITE: {
775 		uint8_t base = (val & 0x10) ? 0x08 : 0;
776 		uint8_t bg = val & 0xf;
777 		if (vga.tandy.mode_control & 0x4) // cyan red white
778 			VGA_SetCGA4Table(bg, 3 + base, 4 + base, 7 + base);
779 		else if (val & 0x20) // cyan magenta white
780 			VGA_SetCGA4Table(bg, 3 + base, 5 + base, 7 + base);
781 		else // green red brown
782 			VGA_SetCGA4Table(bg, 2 + base, 4 + base, 6 + base);
783 		vga.tandy.border_color = bg;
784 		vga.attr.overscan_color = bg;
785 		break;
786 	}
787 	case M_TANDY2:
788 	case M_CGA2_COMPOSITE:
789 		VGA_SetCGA2Table(0,val & 0xf);
790 		vga.attr.overscan_color = 0;
791 		break;
792 	case M_CGA16: update_cga16_color_pcjr(); break;
793 	case M_TEXT:
794 		vga.tandy.border_color = val & 0xf;
795 		vga.attr.overscan_color = 0;
796 		break;
797 	default: //Else unhandled values warning
798 		break;
799 	}
800 }
801 
write_cga(io_port_t port,io_val_t value,io_width_t)802 static void write_cga(io_port_t port, io_val_t value, io_width_t)
803 {
804 	const auto val = check_cast<uint8_t>(value);
805 	// only receives 8-bit data per its IO port registration
806 	switch (port) {
807 	case 0x3d8:
808 		vga.tandy.mode_control = val;
809 		vga.attr.disabled = (val&0x8)? 0: 1;
810 		if (vga.tandy.mode_control & 0x2) {		// graphics mode
811 			if (vga.tandy.mode_control & 0x10) {// highres mode
812 				if (cga_comp == COMPOSITE_STATE::ON ||
813 				    ((cga_comp == COMPOSITE_STATE::AUTO &&
814 				      !(val & 0x4)) &&
815 				     !mono_cga)) {
816 
817 					// composite ntsc 640x200 16 color mode
818 					if (machine == MCH_PCJR) {
819 						VGA_SetMode(M_CGA16);
820 					} else {
821 						VGA_SetMode(M_CGA2_COMPOSITE);
822 						update_cga16_color();
823 					}
824 				} else {
825 					VGA_SetMode(M_TANDY2);
826 				}
827 			} else {							// lowres mode
828 				if (cga_comp == COMPOSITE_STATE::ON) {
829 					// composite ntsc 640x200 16 color mode
830 					if (machine == MCH_PCJR) {
831 						VGA_SetMode(M_CGA16);
832 					} else {
833 						VGA_SetMode(M_CGA4_COMPOSITE);
834 						update_cga16_color();
835 					}
836 				} else if (machine != MCH_PCJR) {
837 					VGA_SetMode(M_TANDY4);
838 				}
839 			}
840 
841 			write_cga_color_select(vga.tandy.color_select);
842 		} else {
843 			if (cga_comp == COMPOSITE_STATE::ON) { // composite display
844 				VGA_SetMode(M_CGA_TEXT_COMPOSITE);
845 				update_cga16_color();
846 			} else {
847 				VGA_SetMode(M_TANDY_TEXT);
848 			}
849 		}
850 		VGA_SetBlinking(val & 0x20);
851 		break;
852 	case 0x3d9: // color select
853 		write_cga_color_select(val);
854 		break;
855 	}
856 }
857 
858 static void PCJr_FindMode();
859 
apply_composite_state()860 static void apply_composite_state()
861 {
862 	// switch RGB and Composite if in graphics mode
863 	if (vga.tandy.mode_control & 0x2 && machine == MCH_PCJR)
864 		PCJr_FindMode();
865 	else
866 		write_cga(0x3d8, vga.tandy.mode_control, io_width_t::byte);
867 
868 /* 	if (vga.tandy.mode_control & 0x2) {
869 		if (machine == MCH_PCJR) {
870 			PCJr_FindMode();
871 		} else {
872 			write_cga(0x3d8, vga.tandy.mode_control, io_width_t::byte);
873 		}
874 	}
875  */
876 
877 }
878 
Composite(bool pressed)879 static void Composite(bool pressed)
880 {
881 	if (!pressed)
882 		return;
883 
884 	// Step through the composite modes
885 	if (cga_comp == COMPOSITE_STATE::AUTO)
886 		cga_comp = COMPOSITE_STATE::ON;
887 	else if (cga_comp == COMPOSITE_STATE::ON)
888 		cga_comp = COMPOSITE_STATE::OFF;
889 	else
890 		cga_comp = COMPOSITE_STATE::AUTO;
891 
892 	LOG_MSG("COMPOSITE: State is %s", cga_comp == COMPOSITE_STATE::AUTO ? "auto"
893 	                                  : cga_comp == COMPOSITE_STATE::ON ? "on"
894 	                                                                    : "off");
895 	apply_composite_state();
896 }
897 
tandy_update_palette()898 static void tandy_update_palette() {
899 	// TODO mask off bits if needed
900 	if (machine == MCH_TANDY) {
901 		switch (vga.mode) {
902 		case M_TANDY2:
903 			VGA_SetCGA2Table(vga.attr.palette[0],
904 				vga.attr.palette[vga.tandy.color_select&0xf]);
905 			break;
906 		case M_TANDY4:
907 			if (vga.tandy.gfx_control & 0x8) {
908 				// 4-color high resolution - might be an idea to introduce M_TANDY4H
909 				VGA_SetCGA4Table( // function sets both medium and highres 4color tables
910 					vga.attr.palette[0], vga.attr.palette[1],
911 					vga.attr.palette[2], vga.attr.palette[3]);
912 			} else {
913 				uint8_t color_set = 0;
914 				uint8_t r_mask = 0xf;
915 				if (vga.tandy.color_select & 0x10)
916 					color_set |= 8; // intensity
917 				if (vga.tandy.color_select & 0x20)
918 					color_set |= 1; // Cyan Mag. White
919 				if (vga.tandy.mode_control & 0x04) { // Cyan Red White
920 					color_set |= 1;
921 					r_mask &= ~1;
922 				}
923 				VGA_SetCGA4Table(
924 					vga.attr.palette[vga.tandy.color_select&0xf],
925 					vga.attr.palette[(2|color_set)& vga.tandy.palette_mask],
926 					vga.attr.palette[(4|(color_set& r_mask))& vga.tandy.palette_mask],
927 					vga.attr.palette[(6|color_set)& vga.tandy.palette_mask]);
928 			}
929 			break;
930 		default:
931 			break;
932 		}
933 	} else {
934 		// PCJr
935 		switch (vga.mode) {
936 		case M_TANDY2:
937 			VGA_SetCGA2Table(vga.attr.palette[0],vga.attr.palette[1]);
938 			break;
939 		case M_TANDY4:
940 			VGA_SetCGA4Table(
941 				vga.attr.palette[0], vga.attr.palette[1],
942 				vga.attr.palette[2], vga.attr.palette[3]);
943 			break;
944 		default:
945 			break;
946 		}
947 		if (machine == MCH_PCJR)
948 			update_cga16_color_pcjr();
949 		else
950 			update_cga16_color();
951 	}
952 }
953 
954 void VGA_SetModeNow(VGAModes mode);
955 
TANDY_FindMode()956 static void TANDY_FindMode()
957 {
958 	if (vga.tandy.mode_control & 0x2) {
959 		if (vga.tandy.gfx_control & 0x10) {
960 			if (vga.mode==M_TANDY4) {
961 				VGA_SetModeNow(M_TANDY16);
962 			} else VGA_SetMode(M_TANDY16);
963 		}
964 		else if (vga.tandy.gfx_control & 0x08) {
965 			if (cga_comp == COMPOSITE_STATE::ON) {
966 				// composite ntsc 640x200 16 color mode
967 				VGA_SetMode(M_CGA4_COMPOSITE);
968 				update_cga16_color();
969 			} else {
970 				VGA_SetMode(M_TANDY4);
971 			}
972 		} else if (vga.tandy.mode_control & 0x10) {
973 			if (cga_comp == COMPOSITE_STATE::ON) {
974 				// composite ntsc 640x200 16 color mode
975 				VGA_SetMode(M_CGA2_COMPOSITE);
976 				update_cga16_color();
977 			} else {
978 				VGA_SetMode(M_TANDY2);
979 			}
980 		} else {
981 			// otherwise some 4-colour graphics mode
982 			const auto new_mode = (cga_comp == COMPOSITE_STATE::ON)
983 			                              ? M_CGA4_COMPOSITE
984 			                              : M_TANDY4;
985 			if (vga.mode == M_TANDY16) {
986 				VGA_SetModeNow(new_mode);
987 			} else {
988 				VGA_SetMode(new_mode);
989 			}
990 		}
991 		tandy_update_palette();
992 	} else {
993 		VGA_SetMode(M_TANDY_TEXT);
994 	}
995 }
996 
PCJr_FindMode()997 static void PCJr_FindMode()
998 {
999 	assert(machine == MCH_PCJR);
1000 	if (vga.tandy.mode_control & 0x2) {
1001 		if (vga.tandy.mode_control & 0x10) {
1002 			// bit4 of mode control 1 signals 16 colour graphics mode
1003 			if (vga.mode == M_TANDY4)
1004 				VGA_SetModeNow(M_TANDY16); // TODO lowres mode only
1005 			else
1006 				VGA_SetMode(M_TANDY16);
1007 		} else if (vga.tandy.gfx_control & 0x08) {
1008 			// bit3 of mode control 2 signals 2 colour graphics mode
1009 			if (cga_comp == COMPOSITE_STATE::ON ||
1010 			    (cga_comp == COMPOSITE_STATE::AUTO &&
1011 			     !(vga.tandy.mode_control & 0x4))) {
1012 				VGA_SetMode(M_CGA16);
1013 			} else {
1014 				VGA_SetMode(M_TANDY2);
1015 			}
1016 		} else {
1017 			// otherwise some 4-colour graphics mode
1018 			const auto new_mode = (cga_comp == COMPOSITE_STATE::ON) ? M_CGA16 : M_TANDY4;
1019 			if (vga.mode == M_TANDY16) {
1020 				VGA_SetModeNow(new_mode);
1021 			} else {
1022 				VGA_SetMode(new_mode);
1023 			}
1024 		}
1025 		if (vga.mode == M_CGA16) {
1026 			update_cga16_color_pcjr();
1027 		}
1028 		tandy_update_palette();
1029 	} else {
1030 		VGA_SetMode(M_TANDY_TEXT);
1031 	}
1032 }
1033 
TandyCheckLineMask(void)1034 static void TandyCheckLineMask(void ) {
1035 	if ( vga.tandy.extended_ram & 1 ) {
1036 		vga.tandy.line_mask = 0;
1037 	} else if ( vga.tandy.mode_control & 0x2) {
1038 		vga.tandy.line_mask |= 1;
1039 	}
1040 	if ( vga.tandy.line_mask ) {
1041 		vga.tandy.line_shift = 13;
1042 		vga.tandy.addr_mask = (1 << 13) - 1;
1043 	} else {
1044 		vga.tandy.addr_mask = (Bitu)(~0);
1045 		vga.tandy.line_shift = 0;
1046 	}
1047 }
1048 
write_tandy_reg(uint8_t val)1049 static void write_tandy_reg(uint8_t val)
1050 {
1051 	// only receives 8-bit data per its IO port registration
1052 	switch (vga.tandy.reg_index) {
1053 	case 0x0:
1054 		if (machine==MCH_PCJR) {
1055 			vga.tandy.mode_control=val;
1056 			VGA_SetBlinking(val & 0x20);
1057 			PCJr_FindMode();
1058 			if (val&0x8) vga.attr.disabled &= ~1;
1059 			else vga.attr.disabled |= 1;
1060 		} else {
1061 			LOG(LOG_VGAMISC,LOG_NORMAL)("Unhandled Write %2X to tandy reg %X",val,vga.tandy.reg_index);
1062 		}
1063 		break;
1064 	case 0x1:	/* Palette mask */
1065 		vga.tandy.palette_mask = val;
1066 		tandy_update_palette();
1067 		break;
1068 	case 0x2:	/* Border color */
1069 		vga.tandy.border_color=val;
1070 		break;
1071 	case 0x3:	/* More control */
1072 		vga.tandy.gfx_control=val;
1073 		if (machine==MCH_TANDY) TANDY_FindMode();
1074 		else PCJr_FindMode();
1075 		break;
1076 	case 0x5:	/* Extended ram page register */
1077 		// Bit 0 enables extended ram
1078 		// Bit 7 Switches clock, 0 -> cga 28.6 , 1 -> mono 32.5
1079 		vga.tandy.extended_ram = val;
1080 		//This is a bit of a hack to enable mapping video memory differently for highres mode
1081 		TandyCheckLineMask();
1082 		VGA_SetupHandlers();
1083 		break;
1084 	default:
1085 		if ((vga.tandy.reg_index & 0xf0) == 0x10) { // color palette
1086 			vga.attr.palette[vga.tandy.reg_index-0x10] = val&0xf;
1087 			tandy_update_palette();
1088 		} else
1089 			LOG(LOG_VGAMISC,LOG_NORMAL)("Unhandled Write %2X to tandy reg %X",val,vga.tandy.reg_index);
1090 	}
1091 }
1092 
write_tandy(io_port_t port,io_val_t value,io_width_t)1093 static void write_tandy(io_port_t port, io_val_t value, io_width_t)
1094 {
1095 	auto val = check_cast<uint8_t>(value);
1096 	// only receives 8-bit data per its IO port registration
1097 	switch (port) {
1098 	case 0x3d8:
1099 		val &= 0x3f; // only bits 0-6 are used
1100 		if (vga.tandy.mode_control ^ val) {
1101 			vga.tandy.mode_control = val;
1102 			if (val&0x8) vga.attr.disabled &= ~1;
1103 			else vga.attr.disabled |= 1;
1104 			TandyCheckLineMask();
1105 			VGA_SetBlinking(val & 0x20);
1106 			TANDY_FindMode();
1107 			VGA_StartResize();
1108 		}
1109 		break;
1110 	case 0x3d9:
1111 		vga.tandy.color_select=val;
1112 		tandy_update_palette();
1113 		// Re-apply the composite mode after updating the palette
1114 		if (cga_comp == COMPOSITE_STATE::ON)
1115 			apply_composite_state();
1116 		break;
1117 	case 0x3da:
1118 		vga.tandy.reg_index = val;
1119 		//if (val&0x10) vga.attr.disabled |= 2;
1120 		//else vga.attr.disabled &= ~2;
1121 		break;
1122 //	case 0x3dd:	//Extended ram page address register:
1123 //		break;
1124 	case 0x3de: write_tandy_reg(val); break;
1125 	case 0x3df:
1126 		// CRT/processor page register
1127 		// See the comments on the PCJr version of this register.
1128 		// A difference to it is:
1129 		// Bit 3-5: Processor page CPU_PG
1130 		// The remapped range is 32kB instead of 16. Therefore CPU_PG bit 0
1131 		// appears to be ORed with CPU A14 (to preserve some sort of
1132 		// backwards compatibility?), resulting in odd pages being mapped
1133 		// as 2x16kB. Implemeted in vga_memory.cpp Tandy handler.
1134 
1135 		vga.tandy.line_mask = val >> 6;
1136 		vga.tandy.draw_bank = val & ((vga.tandy.line_mask&2) ? 0x6 : 0x7);
1137 		vga.tandy.mem_bank = (val >> 3) & 7;
1138 		TandyCheckLineMask();
1139 		VGA_SetupHandlers();
1140 		break;
1141 	}
1142 }
1143 
write_pcjr(io_port_t port,io_val_t value,io_width_t)1144 static void write_pcjr(io_port_t port, io_val_t value, io_width_t)
1145 {
1146 	// only receives 8-bit data per its IO port registration
1147 	const auto val = check_cast<uint8_t>(value);
1148 	switch (port) {
1149 	case 0x3da:
1150 		if (vga.tandy.pcjr_flipflop)
1151 			write_tandy_reg(val);
1152 		else {
1153 			vga.tandy.reg_index = val;
1154 			if (vga.tandy.reg_index & 0x10)
1155 				vga.attr.disabled |= 2;
1156 			else vga.attr.disabled &= ~2;
1157 		}
1158 		vga.tandy.pcjr_flipflop=!vga.tandy.pcjr_flipflop;
1159 		break;
1160 	case 0x3df:
1161 		// CRT/processor page register
1162 
1163 		// Bit 0-2: CRT page PG0-2
1164 		// In one- and two bank modes, bit 0-2 select the 16kB memory
1165 		// area of system RAM that is displayed on the screen.
1166 		// In 4-banked modes, bit 1-2 select the 32kB memory area.
1167 		// Bit 2 only has effect when the PCJR upgrade to 128k is installed.
1168 
1169 		// Bit 3-5: Processor page CPU_PG
1170 		// Selects the 16kB area of system RAM that is mapped to
1171 		// the B8000h IBM PC video memory window. Since A14-A16 of the
1172 		// processor are unconditionally replaced with these bits when
1173 		// B8000h is accessed, the 16kB area is mapped to the 32kB
1174 		// range twice in a row. (Scuba Venture writes across the boundary)
1175 
1176 		// Bit 6-7: Video Address mode
1177 		// 0: CRTC addresses A0-12 directly, accessing 8k characters
1178 		//    (+8k attributes). Used in text modes (one bank).
1179 		//    PG0-2 in effect. 16k range.
1180 		// 1: CRTC A12 is replaced with CRTC RA0 (see max_scanline).
1181 		//    This results in the even/odd scanline two bank system.
1182 		//    PG0-2 in effect. 16k range.
1183 		// 2: Documented as unused. CRTC addresses A0-12, PG0 is replaced
1184 		//    with RA1. Looks like nonsense.
1185 		//    PG1-2 in effect. 32k range which cannot be used completely.
1186 		// 3: CRTC A12 is replaced with CRTC RA0, PG0 is replaced with
1187 		//    CRTC RA1. This results in the 4-bank mode.
1188 		//    PG1-2 in effect. 32k range.
1189 
1190 		vga.tandy.line_mask = val >> 6;
1191 		vga.tandy.draw_bank = val & ((vga.tandy.line_mask&2) ? 0x6 : 0x7);
1192 		vga.tandy.mem_bank = (val >> 3) & 7;
1193 		vga.tandy.draw_base = &MemBase[vga.tandy.draw_bank * 16 * 1024];
1194 		vga.tandy.mem_base = &MemBase[vga.tandy.mem_bank * 16 * 1024];
1195 		TandyCheckLineMask();
1196 		VGA_SetupHandlers();
1197 		break;
1198 	}
1199 }
1200 
palette_num(const char * colour)1201 static uint8_t palette_num(const char *colour)
1202 {
1203 	if (strcasecmp(colour, "green") == 0)
1204 		return 0;
1205 	if (strcasecmp(colour, "amber") == 0)
1206 		return 1;
1207 	if (strcasecmp(colour, "white") == 0)
1208 		return 2;
1209 	if (strcasecmp(colour, "paperwhite") == 0)
1210 		return 3;
1211 	return 2;
1212 }
1213 
VGA_SetMonoPalette(const char * colour)1214 void VGA_SetMonoPalette(const char *colour)
1215 {
1216 	if (machine == MCH_HERC) {
1217 		herc_pal = palette_num(colour);
1218 		Herc_Palette();
1219 		return;
1220 	}
1221 	if (machine == MCH_CGA && mono_cga) {
1222 		mono_cga_pal = palette_num(colour);
1223 		Mono_CGA_Palette();
1224 		return;
1225 	}
1226 }
1227 
CycleMonoCGAPal(bool pressed)1228 static void CycleMonoCGAPal(bool pressed) {
1229 	if (!pressed) return;
1230 	if (++mono_cga_pal>3) mono_cga_pal=0;
1231 	Mono_CGA_Palette();
1232 }
1233 
CycleMonoCGABright(bool pressed)1234 static void CycleMonoCGABright(bool pressed) {
1235 	if (!pressed) return;
1236 	if (++mono_cga_bright>1) mono_cga_bright=0;
1237 	Mono_CGA_Palette();
1238 }
1239 
Mono_CGA_Palette()1240 void Mono_CGA_Palette()
1241 {
1242 	for (uint8_t ct = 0; ct < 16; ++ct) {
1243 		VGA_DAC_SetEntry(
1244 		        ct,
1245 		        mono_cga_palettes[2 * mono_cga_pal + mono_cga_bright][ct][0],
1246 		        mono_cga_palettes[2 * mono_cga_pal + mono_cga_bright][ct][1],
1247 		        mono_cga_palettes[2 * mono_cga_pal + mono_cga_bright][ct][2]);
1248 		VGA_DAC_CombineColor(ct, ct);
1249 	}
1250 }
1251 
CycleHercPal(bool pressed)1252 static void CycleHercPal(bool pressed) {
1253 	if (!pressed) return;
1254 	if (++herc_pal>3) herc_pal=0;
1255 	Herc_Palette();
1256 	VGA_DAC_CombineColor(1,7);
1257 }
1258 
Herc_Palette()1259 void Herc_Palette()
1260 {
1261 	switch (herc_pal) {
1262 	case 0: // Green
1263 		VGA_DAC_SetEntry(0x7, 0x00, 0x26, 0x00);
1264 		VGA_DAC_SetEntry(0xf, 0x00, 0x3f, 0x00);
1265 		break;
1266 	case 1: // Amber
1267 		VGA_DAC_SetEntry(0x7, 0x34, 0x20, 0x00);
1268 		VGA_DAC_SetEntry(0xf, 0x3f, 0x34, 0x00);
1269 		break;
1270 	case 2: // White
1271 		VGA_DAC_SetEntry(0x7, 0x2a, 0x2a, 0x2a);
1272 		VGA_DAC_SetEntry(0xf, 0x3f, 0x3f, 0x3f);
1273 		break;
1274 	case 3: // Paper-white
1275 		VGA_DAC_SetEntry(0x7, 0x2d, 0x2e, 0x2d);
1276 		VGA_DAC_SetEntry(0xf, 0x3f, 0x3f, 0x3b);
1277 		break;
1278 	}
1279 }
1280 
write_hercules(io_port_t port,io_val_t value,io_width_t)1281 static void write_hercules(io_port_t port, io_val_t value, io_width_t)
1282 {
1283 	const auto val = check_cast<uint8_t>(value);
1284 	switch (port) {
1285 	case 0x3b8: {
1286 		// the protected bits can always be cleared but only be set if the
1287 		// protection bits are set
1288 		if (vga.herc.mode_control&0x2) {
1289 			// already set
1290 			if (!(val&0x2)) {
1291 				vga.herc.mode_control &= ~0x2;
1292 				VGA_SetMode(M_HERC_TEXT);
1293 			}
1294 		} else {
1295 			// not set, can only set if protection bit is set
1296 			if ((val & 0x2) && (vga.herc.enable_bits & 0x1)) {
1297 				vga.herc.mode_control |= 0x2;
1298 				VGA_SetMode(M_HERC_GFX);
1299 			}
1300 		}
1301 		if (vga.herc.mode_control&0x80) {
1302 			if (!(val&0x80)) {
1303 				vga.herc.mode_control &= ~0x80;
1304 				vga.tandy.draw_base = &vga.mem.linear[0];
1305 			}
1306 		} else {
1307 			if ((val & 0x80) && (vga.herc.enable_bits & 0x2)) {
1308 				vga.herc.mode_control |= 0x80;
1309 				vga.tandy.draw_base = &vga.mem.linear[32*1024];
1310 			}
1311 		}
1312 		vga.draw.blinking = (val&0x20)!=0;
1313 		vga.herc.mode_control &= 0x82;
1314 		vga.herc.mode_control |= val & ~0x82;
1315 		break;
1316 		}
1317 	case 0x3bf:
1318 		if ( vga.herc.enable_bits ^ val) {
1319 			vga.herc.enable_bits=val;
1320 			// Bit 1 enables the upper 32k of video memory,
1321 			// so update the handlers
1322 			VGA_SetupHandlers();
1323 		}
1324 		break;
1325 	}
1326 }
1327 
read_herc_status(io_port_t,io_width_t)1328 uint8_t read_herc_status(io_port_t, io_width_t)
1329 {
1330 	// only returns 8-bit data per its IO port registration
1331 
1332 	// 3BAh (R):  Status Register
1333 	// bit   0  Horizontal sync
1334 	//       1  Light pen status (only some cards)
1335 	//       3  Video signal
1336 	//     4-6	000: Hercules
1337 	//			001: Hercules Plus
1338 	//			101: Hercules InColor
1339 	//			111: Unknown clone
1340 	//       7  Vertical sync inverted
1341 
1342 	const auto timeInFrame = PIC_FullIndex() - vga.draw.delay.framestart;
1343 	uint8_t retval = 0x72; // Hercules ident; from a working card (Winbond
1344 	                       // W86855AF) Another known working card has 0x76
1345 	                       // ("KeysoGood", full-length)
1346 	if (timeInFrame < vga.draw.delay.vrstart || timeInFrame > vga.draw.delay.vrend)
1347 		retval |= 0x80;
1348 
1349 	const auto timeInLine = fmod(timeInFrame, vga.draw.delay.htotal);
1350 	if (timeInLine >= vga.draw.delay.hrstart &&
1351 		timeInLine <= vga.draw.delay.hrend) retval |= 0x1;
1352 
1353 	// 688 Attack sub checks bit 3 - as a workaround have the bit enabled
1354 	// if no sync active (corresponds to a completely white screen)
1355 	if ((retval&0x81)==0x80) retval |= 0x8;
1356 	return retval;
1357 }
1358 
VGA_SetupOther()1359 void VGA_SetupOther()
1360 {
1361 	vga.tandy = VGA_TANDY(); // reset our Tandy struct
1362 	vga.attr.disabled = 0;
1363 	vga.config.bytes_skip=0;
1364 
1365 	//Initialize values common for most machines, can be overwritten
1366 	vga.tandy.draw_base = vga.mem.linear;
1367 	vga.tandy.mem_base = vga.mem.linear;
1368 	vga.tandy.addr_mask = 8*1024 - 1;
1369 	vga.tandy.line_mask = 3;
1370 	vga.tandy.line_shift = 13;
1371 
1372 	if (machine==MCH_CGA || IS_TANDY_ARCH) {
1373 		extern uint8_t int10_font_08[256 * 8];
1374 		for (int i = 0; i < 256; ++i) {
1375 			memcpy(&vga.draw.font[i * 32], &int10_font_08[i * 8], 8);
1376 		}
1377 		vga.draw.font_tables[0] = vga.draw.font_tables[1] = vga.draw.font;
1378 	}
1379 	if (machine==MCH_CGA || IS_TANDY_ARCH || machine==MCH_HERC) {
1380 		IO_RegisterWriteHandler(0x3db, write_lightpen, io_width_t::byte);
1381 		IO_RegisterWriteHandler(0x3dc, write_lightpen, io_width_t::byte);
1382 	}
1383 	if (machine==MCH_HERC) {
1384 		extern uint8_t int10_font_14[256 * 14];
1385 		for (int i = 0; i < 256; ++i) {
1386 			memcpy(&vga.draw.font[i * 32], &int10_font_14[i * 14], 14);
1387 		}
1388 		vga.draw.font_tables[0] = vga.draw.font_tables[1] = vga.draw.font;
1389 		MAPPER_AddHandler(CycleHercPal, SDL_SCANCODE_F11, 0,
1390 		                  "hercpal", "Herc Pal");
1391 	}
1392 	if (machine==MCH_CGA) {
1393 		IO_RegisterWriteHandler(0x3d8, write_cga, io_width_t::byte);
1394 		IO_RegisterWriteHandler(0x3d9, write_cga, io_width_t::byte);
1395 		if (mono_cga) {
1396 			MAPPER_AddHandler(CycleMonoCGAPal, SDL_SCANCODE_F11, 0,
1397 			                  "monocgapal", "Mono CGA Pal");
1398 			MAPPER_AddHandler(CycleMonoCGABright, SDL_SCANCODE_F11, MMOD2,
1399 			                  "monocgabright", "Mono CGA Bright");
1400 		}
1401 	}
1402 	if (machine==MCH_TANDY) {
1403 		write_tandy(0x3df, 0x0, io_width_t::byte);
1404 		IO_RegisterWriteHandler(0x3d8, write_tandy, io_width_t::byte);
1405 		IO_RegisterWriteHandler(0x3d9, write_tandy, io_width_t::byte);
1406 		IO_RegisterWriteHandler(0x3da, write_tandy, io_width_t::byte);
1407 		IO_RegisterWriteHandler(0x3de, write_tandy, io_width_t::byte);
1408 		IO_RegisterWriteHandler(0x3df, write_tandy, io_width_t::byte);
1409 	}
1410 	if (machine == MCH_PCJR) {
1411 		//write_pcjr will setup base address
1412 		write_pcjr(0x3df, 0x7 | (0x7 << 3), io_width_t::byte);
1413 		IO_RegisterWriteHandler(0x3da, write_pcjr, io_width_t::byte);
1414 		IO_RegisterWriteHandler(0x3df, write_pcjr, io_width_t::byte);
1415 	}
1416 	// Add composite hotkeys for CGA, Tandy, and PCjr
1417 	if ((machine == MCH_CGA && !mono_cga) || machine == MCH_TANDY ||
1418 	    machine == MCH_PCJR) {
1419 		MAPPER_AddHandler(select_next_crt_knob, SDL_SCANCODE_F10, 0,
1420 		                  "select", "Sel Knob");
1421 		MAPPER_AddHandler(turn_crt_knob_positive, SDL_SCANCODE_F11, 0,
1422 		                  "incval", "Inc Knob");
1423 		MAPPER_AddHandler(turn_crt_knob_negative, SDL_SCANCODE_F11,
1424 		                  MMOD2, "decval", "Dec Knob");
1425 		MAPPER_AddHandler(Composite, SDL_SCANCODE_F12, 0, "cgacomp",
1426 		                  "CGA Comp");
1427 	}
1428 	if (machine == MCH_HERC) {
1429 		constexpr uint16_t base = 0x3b0;
1430 		for (uint8_t i = 0; i < 4; ++i) {
1431 			// The registers are repeated as the address is not decoded properly;
1432 			// The official ports are 3b4, 3b5
1433 			const uint16_t index_port = base + i * 2u;
1434 			IO_RegisterWriteHandler(index_port, write_crtc_index_other, io_width_t::byte);
1435 			IO_RegisterReadHandler(index_port, read_crtc_index_other, io_width_t::byte);
1436 
1437 			const uint16_t data_port = index_port + 1u;
1438 			IO_RegisterWriteHandler(data_port, write_crtc_data_other, io_width_t::byte);
1439 			IO_RegisterReadHandler(data_port, read_crtc_data_other, io_width_t::byte);
1440 		}
1441 		vga.herc.enable_bits=0;
1442 		vga.herc.mode_control=0xa; // first mode written will be text mode
1443 		vga.crtc.underline_location = 13;
1444 		IO_RegisterWriteHandler(0x3b8, write_hercules, io_width_t::byte);
1445 		IO_RegisterWriteHandler(0x3bf, write_hercules, io_width_t::byte);
1446 		IO_RegisterReadHandler(0x3ba, read_herc_status, io_width_t::byte);
1447 	} else if (!IS_EGAVGA_ARCH) {
1448 		constexpr uint16_t base = 0x3d0;
1449 		for (uint8_t port_ct = 0; port_ct < 4; ++port_ct) {
1450 			IO_RegisterWriteHandler(base + port_ct * 2, write_crtc_index_other, io_width_t::byte);
1451 			IO_RegisterWriteHandler(base + port_ct * 2 + 1, write_crtc_data_other, io_width_t::byte);
1452 			IO_RegisterReadHandler(base + port_ct * 2, read_crtc_index_other, io_width_t::byte);
1453 			IO_RegisterReadHandler(base + port_ct * 2 + 1, read_crtc_data_other, io_width_t::byte);
1454 		}
1455 	}
1456 }
composite_init(Section * sec)1457 static void composite_init(Section *sec)
1458 {
1459 	assert(sec);
1460 	const auto conf = static_cast<Section_prop *>(sec);
1461 	assert(conf);
1462 	const std::string state = conf->Get_string("composite");
1463 	cga_comp = (state == "auto" ? COMPOSITE_STATE::AUTO
1464 	            : state == "on" ? COMPOSITE_STATE::ON
1465 	                            : COMPOSITE_STATE::OFF);
1466 
1467 	const auto era_choice = std::string(conf->Get_string("era"));
1468 	is_composite_new_era = era_choice == "new" ||
1469 	                       (machine == MCH_PCJR && era_choice == "auto");
1470 
1471 	hue.set(conf->Get_int("hue"));
1472 	saturation.set(conf->Get_int("saturation"));
1473 	contrast.set(conf->Get_int("contrast"));
1474 	brightness.set(conf->Get_int("brightness"));
1475 	convergence.set(conf->Get_int("convergence"));
1476 
1477 	if (cga_comp == COMPOSITE_STATE::ON) {
1478 		LOG_MSG("COMPOSITE: %s-era enabled with settings: hue %d, saturation %d,"
1479 		        " contrast %d, brightness %d, and convergence %d",
1480 		        is_composite_new_era ? "New" : "Old", hue.get(), saturation.get(), contrast.get(),
1481 		        brightness.get(), convergence.get());
1482 	}
1483 }
1484 
composite_settings(Section_prop & secprop)1485 static void composite_settings(Section_prop &secprop)
1486 {
1487 	constexpr auto when_idle = Property::Changeable::WhenIdle;
1488 
1489 	const char *states[] = {"auto", "on", "off", 0};
1490 	auto str_prop = secprop.Add_string("composite", when_idle, "auto");
1491 	str_prop->Set_values(states);
1492 	str_prop->Set_help("Enable composite mode on start. 'auto' lets the program decide.\n"
1493 	                   "Note: Fine-tune the settings below (ie: hue) using the composite hotkeys.\n"
1494 	                   "      Then read the new settings from your console and enter them here.");
1495 
1496 	const char *eras[] = {"auto", "old", "new", 0};
1497 	str_prop = secprop.Add_string("era", when_idle, "auto");
1498 	str_prop->Set_values(eras);
1499 	str_prop->Set_help(
1500 	        "Era of composite technology. When 'auto', PCjr uses new and CGA/Tandy use old.");
1501 
1502 	auto int_prop = secprop.Add_int("hue", when_idle, hue.get_default());
1503 	int_prop->SetMinMax(hue.get_min(), hue.get_max());
1504 	int_prop->Set_help("Appearance of RGB palette. For example, adjust until sky is blue.");
1505 
1506 	int_prop = secprop.Add_int("saturation", when_idle, saturation.get_default());
1507 	int_prop->SetMinMax(saturation.get_min(), saturation.get_max());
1508 	int_prop->Set_help("Intensity of colors, from washed out to vivid.");
1509 
1510 	int_prop = secprop.Add_int("contrast", when_idle, contrast.get_default());
1511 	int_prop->SetMinMax(contrast.get_min(), contrast.get_max());
1512 	int_prop->Set_help("Ratio between the dark and light area.");
1513 
1514 	int_prop = secprop.Add_int("brightness", when_idle, brightness.get_default());
1515 	int_prop->SetMinMax(brightness.get_min(), brightness.get_max());
1516 	int_prop->Set_help("Luminosity of the image, from dark to light.");
1517 
1518 	int_prop = secprop.Add_int("convergence", when_idle, convergence.get_default());
1519 	int_prop->SetMinMax(convergence.get_min(), convergence.get_max());
1520 	int_prop->Set_help("Convergence of subpixel elements, from blurry to sharp (CGA and Tandy-only).");
1521 }
1522 
turn_crt_knob(bool pressed,const int amount)1523 static void turn_crt_knob(bool pressed, const int amount)
1524 {
1525 	if (!pressed)
1526 		return;
1527 	switch (crt_knob) {
1528 	case CRT_KNOB::ERA: is_composite_new_era = !is_composite_new_era; break;
1529 	case CRT_KNOB::HUE: hue.turn(amount); break;
1530 	case CRT_KNOB::SATURATION: saturation.turn(amount); break;
1531 	case CRT_KNOB::CONTRAST: contrast.turn(amount); break;
1532 	case CRT_KNOB::BRIGHTNESS: brightness.turn(amount); break;
1533 	case CRT_KNOB::CONVERGENCE: convergence.turn(amount); break;
1534 	case CRT_KNOB::ENUM_END: assertm(false, "Should not reach CRT knob end marker"); break;
1535 	}
1536 	if (machine == MCH_PCJR)
1537 		update_cga16_color_pcjr();
1538 	else
1539 		update_cga16_color();
1540 
1541 	log_crt_knob_value();
1542 }
1543 
VGA_AddCompositeSettings(Config & conf)1544 void VGA_AddCompositeSettings(Config &conf)
1545 {
1546 	auto sec = conf.AddSection_prop("composite", &composite_init, true);
1547 	assert(sec);
1548 	composite_settings(*sec);
1549 }
1550