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