OXYGENHUB
# 8e8d9cad1884 x16-emulator / src / video.c
71.0 KiB · C · last changed main: while the CPU waits in WAI, step the machine to its next event at once 15 days ago source blame history raw
03a3459e Andy McCall 1
// Commander X16 Emulator
2
// Copyright (c) 2019 Michael Steil
3
// Copyright (c) 2020 Frank van den Hoef
4
// All rights reserved. License: 2-clause BSD
5
6
#include "video.h"
7
#include "memory.h"
8
#include "glue.h"
9
#include "debugger.h"
10
#include "keyboard.h"
11
#include "gif.h"
12
#include "joystick.h"
13
#include "vera_spi.h"
14
#include "vera_psg.h"
15
#include "vera_pcm.h"
16
#include "icon.h"
17
#include "sdcard.h"
18
#include "i2c.h"
19
#include "audio.h"
20
8e8d9cad Andy McCall ↶ 21
#include <math.h>
03a3459e Andy McCall 22
#include <stdbool.h>
23
#include <limits.h>
24
#include <stdint.h>
25
#include <time.h>
26
27
#ifdef __EMSCRIPTEN__
28
#include "emscripten.h"
29
#endif
30
31
#ifndef __EMSCRIPTEN__
32
#define STB_IMAGE_WRITE_IMPLEMENTATION
33
#include "stb_image_write.h"
34
#endif
35
36
#define APPROX_TITLEBAR_HEIGHT 30
37
38
#define VERA_VERSION_MAJOR  47
39
#define VERA_VERSION_MINOR  0
40
#define VERA_VERSION_PATCH  2
41
42
#define ADDR_VRAM_START     0x00000
43
#define ADDR_VRAM_END       0x20000
44
#define ADDR_PSG_START      0x1F9C0
45
#define ADDR_PSG_END        0x1FA00
46
#define ADDR_PALETTE_START  0x1FA00
47
#define ADDR_PALETTE_END    0x1FC00
48
#define ADDR_SPRDATA_START  0x1FC00
49
#define ADDR_SPRDATA_END    0x20000
50
51
#define NUM_SPRITES 128
52
53
// both VGA and NTSC
54
#define SCAN_HEIGHT 525
55
#define PIXEL_FREQ 25.0
56
57
// VGA
58
#define VGA_SCAN_WIDTH 800
59
#define VGA_Y_OFFSET 0
60
61
// NTSC: 262.5 lines per frame, lower field first
62
#define NTSC_HALF_SCAN_WIDTH 794
63
#define NTSC_X_OFFSET 270
64
#define NTSC_Y_OFFSET_LOW 42
65
#define NTSC_Y_OFFSET_HIGH 568
66
#define TITLE_SAFE_X 0.067
67
#define TITLE_SAFE_Y 0.05
68
69
// visible area we're drawing
70
#define SCREEN_WIDTH 640
71
#define SCREEN_HEIGHT 480
72
73
#define SCREEN_RAM_OFFSET 0x00000
74
75
#ifdef __APPLE__
76
#define LSHORTCUT_KEY SDL_SCANCODE_LGUI
77
#define RSHORTCUT_KEY SDL_SCANCODE_RGUI
78
#else
79
#define LSHORTCUT_KEY SDL_SCANCODE_LCTRL
80
#define RSHORTCUT_KEY SDL_SCANCODE_RCTRL
81
#endif
82
83
// When rendering a layer line, we can amortize some of the cost by calculating multiple pixels at a time.
84
#define LAYER_PIXELS_PER_ITERATION 8
85
86
#define MAX(a,b) ((a) > (b) ? a : b)
87
88
static SDL_Window *window;
89
static SDL_Renderer *renderer;
90
static SDL_Texture *sdlTexture;
91
static bool is_fullscreen = false;
92
bool mouse_grabbed = false;
93
bool no_keyboard_capture = false;
94
bool kernal_mouse_enabled = false;
95
96
static uint8_t video_ram[0x20000];
97
static uint8_t palette[256 * 2];
98
static uint8_t sprite_data[128][8];
99
100
// I/O registers
101
static uint32_t io_addr[2];
102
static uint8_t io_rddata[2];
103
static uint8_t io_inc[2];
104
static uint8_t io_addrsel;
105
static uint8_t io_dcsel;
106
107
static uint8_t ien;
108
static uint8_t isr;
109
110
static uint16_t irq_line;
111
112
static uint8_t reg_layer[2][7];
113
114
#define COMPOSER_SLOTS 4*64
115
static uint8_t reg_composer[COMPOSER_SLOTS];
116
static uint8_t prev_reg_composer[2][COMPOSER_SLOTS];
117
118
static uint8_t layer_line[2][SCREEN_WIDTH];
119
static uint8_t sprite_line_col[SCREEN_WIDTH];
120
static uint8_t sprite_line_z[SCREEN_WIDTH];
121
static uint8_t sprite_line_mask[SCREEN_WIDTH];
122
static uint8_t sprite_line_collisions;
123
static bool layer_line_enable[2];
124
static bool old_layer_line_enable[2];
125
static bool old_sprite_line_enable;
126
static bool sprite_line_enable;
127
128
////////////////////////////////////////////////////////////
129
// FX registers
130
////////////////////////////////////////////////////////////
131
static uint8_t fx_addr1_mode;
132
133
// These are all 16.16 fixed point in the emulator
134
// even though the VERA uses smaller bit widths
135
// for the whole and fractional parts.
136
//
137
// Sign extension is done manually when assigning negative numbers
138
//
139
// Native VERA bit widths are shown below.
140
static uint32_t fx_x_pixel_increment;  // 11.9 fixed point (6.9 without 32x multiplier, 11.4 with 32x multiplier on)
141
static uint32_t fx_y_pixel_increment;  // 11.9 fixed point (6.9 without 32x multiplier, 11.4 with 32x multiplier on)
142
static uint32_t fx_x_pixel_position;   // 11.9 fixed point
143
static uint32_t fx_y_pixel_position;   // 11.9 fixed point
144
145
static uint16_t fx_poly_fill_length;      // 10 bits
146
147
static uint32_t fx_affine_tile_base;
148
static uint32_t fx_affine_map_base;
149
150
static uint8_t fx_affine_map_size;
151
152
static bool fx_4bit_mode;
153
static bool fx_16bit_hop;
154
static bool fx_cache_byte_cycling;
155
static bool fx_cache_fill;
156
static bool fx_cache_write;
157
static bool fx_trans_writes;
158
159
static bool fx_2bit_poly;
160
static bool fx_2bit_poking;
161
162
static bool fx_cache_increment_mode;
163
static bool fx_cache_nibble_index;
164
static uint8_t fx_cache_byte_index;
165
static bool fx_multiplier;
166
static bool fx_subtract;
167
168
static bool fx_affine_clip;
169
170
static uint8_t fx_16bit_hop_align;
171
172
static bool fx_nibble_bit[2];
173
static bool fx_nibble_incr[2];
174
175
static uint8_t fx_cache[4];
176
177
static int32_t fx_mult_accumulator;
178
179
static const uint8_t vera_version_string[] = {'V',
180
	VERA_VERSION_MAJOR,
181
	VERA_VERSION_MINOR,
182
	VERA_VERSION_PATCH
183
};
184
185
float vga_scan_pos_x;
186
uint16_t vga_scan_pos_y;
187
float ntsc_half_cnt;
188
uint16_t ntsc_scan_pos_y;
189
int frame_count = 0;
190
191
static uint8_t framebuffer[SCREEN_WIDTH * SCREEN_HEIGHT * 4];
192
#ifndef __EMSCRIPTEN__
193
static uint8_t png_buffer[SCREEN_WIDTH * SCREEN_HEIGHT * 3];
194
#endif
195
196
static GifWriter gif_writer;
197
198
static const uint16_t default_palette[] = {
199
0x000,0xfff,0x800,0xafe,0xc4c,0x0c5,0x00a,0xee7,0xd85,0x640,0xf77,0x333,0x777,0xaf6,0x08f,0xbbb,0x000,0x111,0x222,0x333,0x444,0x555,0x666,0x777,0x888,0x999,0xaaa,0xbbb,0xccc,0xddd,0xeee,0xfff,0x211,0x433,0x644,0x866,0xa88,0xc99,0xfbb,0x211,0x422,0x633,0x844,0xa55,0xc66,0xf77,0x200,0x411,0x611,0x822,0xa22,0xc33,0xf33,0x200,0x400,0x600,0x800,0xa00,0xc00,0xf00,0x221,0x443,0x664,0x886,0xaa8,0xcc9,0xfeb,0x211,0x432,0x653,0x874,0xa95,0xcb6,0xfd7,0x210,0x431,0x651,0x862,0xa82,0xca3,0xfc3,0x210,0x430,0x640,0x860,0xa80,0xc90,0xfb0,0x121,0x343,0x564,0x786,0x9a8,0xbc9,0xdfb,0x121,0x342,0x463,0x684,0x8a5,0x9c6,0xbf7,0x120,0x241,0x461,0x582,0x6a2,0x8c3,0x9f3,0x120,0x240,0x360,0x480,0x5a0,0x6c0,0x7f0,0x121,0x343,0x465,0x686,0x8a8,0x9ca,0xbfc,0x121,0x242,0x364,0x485,0x5a6,0x6c8,0x7f9,0x020,0x141,0x162,0x283,0x2a4,0x3c5,0x3f6,0x020,0x041,0x061,0x082,0x0a2,0x0c3,0x0f3,0x122,0x344,0x466,0x688,0x8aa,0x9cc,0xbff,0x122,0x244,0x366,0x488,0x5aa,0x6cc,0x7ff,0x022,0x144,0x166,0x288,0x2aa,0x3cc,0x3ff,0x022,0x044,0x066,0x088,0x0aa,0x0cc,0x0ff,0x112,0x334,0x456,0x668,0x88a,0x9ac,0xbcf,0x112,0x224,0x346,0x458,0x56a,0x68c,0x79f,0x002,0x114,0x126,0x238,0x24a,0x35c,0x36f,0x002,0x014,0x016,0x028,0x02a,0x03c,0x03f,0x112,0x334,0x546,0x768,0x98a,0xb9c,0xdbf,0x112,0x324,0x436,0x648,0x85a,0x96c,0xb7f,0x102,0x214,0x416,0x528,0x62a,0x83c,0x93f,0x102,0x204,0x306,0x408,0x50a,0x60c,0x70f,0x212,0x434,0x646,0x868,0xa8a,0xc9c,0xfbe,0x211,0x423,0x635,0x847,0xa59,0xc6b,0xf7d,0x201,0x413,0x615,0x826,0xa28,0xc3a,0xf3c,0x201,0x403,0x604,0x806,0xa08,0xc09,0xf0b
200
};
201
202
uint8_t video_space_read(uint32_t address);
203
static void video_space_read_range(uint8_t* dest, uint32_t address, uint32_t size);
204
205
static void refresh_palette();
206
207
void
208
mousegrab_toggle() {
209
	mouse_grabbed = !mouse_grabbed;
210
	SDL_SetWindowGrab(window, mouse_grabbed && !no_keyboard_capture);
211
	SDL_SetRelativeMouseMode(mouse_grabbed);
212
	SDL_ShowCursor((mouse_grabbed || kernal_mouse_enabled) ? SDL_DISABLE : SDL_ENABLE);
213
	sprintf(window_title, WINDOW_TITLE "%s", mouse_grabbed ? MOUSE_GRAB_MSG : "");
214
	video_update_title(window_title);
215
}
216
217
void
218
video_reset()
219
{
220
	// init I/O registers
221
	memset(io_addr, 0, sizeof(io_addr));
222
	memset(io_inc, 0, sizeof(io_inc));
223
	io_addrsel = 0;
224
	io_dcsel = 0;
225
	io_rddata[0] = 0;
226
	io_rddata[1] = 0;
227
228
	ien = 0;
229
	isr = 0;
230
	irq_line = 0;
231
232
	// init Layer registers
233
	memset(reg_layer, 0, sizeof(reg_layer));
234
235
	// init composer registers
236
	memset(reg_composer, 0, sizeof(reg_composer));
237
	reg_composer[1] = 128; // hscale = 1.0
238
	reg_composer[2] = 128; // vscale = 1.0
239
	reg_composer[5] = 640 >> 2;
240
	reg_composer[7] = 480 >> 1;
241
242
	// Initialize FX registers
243
	fx_addr1_mode = 0;
244
	fx_x_pixel_position = 0x8000;
245
	fx_y_pixel_position = 0x8000;
246
	fx_x_pixel_increment = 0;
247
	fx_y_pixel_increment = 0;
248
249
	fx_cache_write = false;
250
	fx_cache_fill = false;
251
	fx_4bit_mode = false;
252
	fx_16bit_hop = false;
253
	fx_subtract = false;
254
	fx_cache_byte_cycling = false;
255
	fx_trans_writes = false;
256
	fx_multiplier = false;
257
258
	fx_mult_accumulator = 0;
259
260
	fx_2bit_poly = false;
261
	fx_2bit_poking = false;
262
263
	fx_cache_nibble_index = 0;
264
	fx_cache_byte_index = 0;
265
	fx_cache_increment_mode = 0;
266
267
	fx_cache[0] = 0;
268
	fx_cache[1] = 0;
269
	fx_cache[2] = 0;
270
	fx_cache[3] = 0;
271
272
	fx_16bit_hop_align = 0;
273
274
	fx_nibble_bit[0] = false;
275
	fx_nibble_bit[1] = false;
276
	fx_nibble_incr[0] = false;
277
	fx_nibble_incr[1] = false;
278
279
	fx_poly_fill_length = 0;
280
	fx_affine_tile_base = 0;
281
	fx_affine_map_base = 0;
282
	fx_affine_map_size = 2;
283
	fx_affine_clip = false;
284
285
	// init sprite data
286
	memset(sprite_data, 0, sizeof(sprite_data));
287
288
	// copy palette
289
	memcpy(palette, default_palette, sizeof(palette));
290
	for (int i = 0; i < 256; i++) {
291
		palette[i * 2 + 0] = default_palette[i] & 0xff;
292
		palette[i * 2 + 1] = default_palette[i] >> 8;
293
	}
294
295
	refresh_palette();
296
297
	// fill video RAM with random data
298
	for (int i = 0; i < 128 * 1024; i++) {
299
		video_ram[i] = rand();
300
	}
301
302
	sprite_line_collisions = 0;
303
304
	vga_scan_pos_x = 0;
305
	vga_scan_pos_y = 0;
306
	ntsc_half_cnt = 0;
307
	ntsc_scan_pos_y = 0;
308
309
	psg_reset();
310
	pcm_reset();
311
}
312
313
bool
314
video_init(int window_scale, float screen_x_scale, char *quality, bool fullscreen, float opacity)
315
{
316
	uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI;
317
318
#ifdef __EMSCRIPTEN__
319
	// Setting this flag would render the web canvas outside of its bounds on high dpi screens
320
	window_flags &= ~SDL_WINDOW_ALLOW_HIGHDPI;
321
#endif
322
323
	video_reset();
324
325
	SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, quality);
326
	SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1"); // Grabs keyboard shortcuts from the system during window grab
327
	SDL_CreateWindowAndRenderer(SCREEN_WIDTH * window_scale * screen_x_scale, SCREEN_HEIGHT * window_scale, window_flags, &window, &renderer);
328
#ifndef __MORPHOS__
329
	SDL_SetWindowResizable(window, true);
330
#endif
331
	SDL_RenderSetLogicalSize(renderer, SCREEN_WIDTH * screen_x_scale, SCREEN_HEIGHT);
332
333
	sdlTexture = SDL_CreateTexture(renderer,
334
									SDL_PIXELFORMAT_RGB888,
335
									SDL_TEXTUREACCESS_STREAMING,
336
									SCREEN_WIDTH, SCREEN_HEIGHT);
337
338
	SDL_SetWindowTitle(window, WINDOW_TITLE);
339
	SDL_SetWindowIcon(window, CommanderX16Icon());
340
	if (fullscreen) {
341
		is_fullscreen = true;
342
		SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
343
	} else {
344
		int winX, winY;
345
		SDL_GetWindowPosition(window, &winX, &winY);
346
		if (winX < 0 || winY < APPROX_TITLEBAR_HEIGHT) {
347
			winX = winX < 0 ? 0 : winX;
348
			winY = winY < APPROX_TITLEBAR_HEIGHT ? APPROX_TITLEBAR_HEIGHT : winY;
349
			SDL_SetWindowPosition(window, winX, winY);
350
		}
351
	}
352
353
	SDL_SetWindowOpacity(window, opacity);
354
355
#ifdef _WIN32
356
	extern void video_win32_set_rounded_corners(SDL_Window *window);
357
	video_win32_set_rounded_corners(window);
358
#endif
359
360
	if (record_gif != RECORD_GIF_DISABLED) {
361
		if (!strcmp(gif_path+strlen(gif_path)-5, ",wait")) {
362
			// wait for POKE
363
			record_gif = RECORD_GIF_PAUSED;
364
			// move the string terminator to remove the ",wait"
365
			gif_path[strlen(gif_path)-5] = 0;
366
		} else {
367
			// start now
368
			record_gif = RECORD_GIF_ACTIVE;
369
		}
370
		if (!GifBegin(&gif_writer, gif_path, SCREEN_WIDTH, SCREEN_HEIGHT, 1, 8, false)) {
371
			record_gif = RECORD_GIF_DISABLED;
372
		}
373
	}
374
375
	if (debugger_enabled) {
376
		DEBUGInitUI(renderer);
377
	}
378
379
	if (grab_mouse && !mouse_grabbed)
380
		mousegrab_toggle();
381
382
	return true;
383
}
384
385
struct video_layer_properties
386
{
387
	uint8_t color_depth;
388
	uint32_t map_base;
389
	uint32_t tile_base;
390
391
	bool text_mode;
392
	bool text_mode_256c;
393
	bool tile_mode;
394
	bool bitmap_mode;
395
396
	uint16_t hscroll;
397
	uint16_t vscroll;
398
399
	uint8_t  mapw_log2;
400
	uint8_t  maph_log2;
401
	uint16_t tilew;
402
	uint16_t tileh;
403
	uint8_t  tilew_log2;
404
	uint8_t  tileh_log2;
405
406
	uint16_t mapw_max;
407
	uint16_t maph_max;
408
	uint16_t tilew_max;
409
	uint16_t tileh_max;
410
	uint16_t layerw_max;
411
	uint16_t layerh_max;
412
413
	uint8_t tile_size_log2;
414
415
	int min_eff_x;
416
	int max_eff_x;
417
418
	uint8_t bits_per_pixel;
419
	uint8_t first_color_pos;
420
	uint8_t color_mask;
421
	uint8_t color_fields_max;
422
};
423
424
#define NUM_LAYERS 2
425
struct video_layer_properties layer_properties[NUM_LAYERS];
426
struct video_layer_properties prev_layer_properties[2][NUM_LAYERS];
427
428
static int
429
calc_layer_eff_x(const struct video_layer_properties *props, const int x)
430
{
431
	return (x + props->hscroll) & (props->layerw_max);
432
}
433
434
static int
435
calc_layer_eff_y(const struct video_layer_properties *props, const int y)
436
{
437
	return (y + props->vscroll) & (props->layerh_max);
438
}
439
440
static uint32_t
441
calc_layer_map_addr_base2(const struct video_layer_properties *props, const int eff_x, const int eff_y)
442
{
443
	// Slightly faster on some platforms because we know that tilew and tileh are powers of 2.
444
	return props->map_base + ((((eff_y >> props->tileh_log2) << props->mapw_log2) + (eff_x >> props->tilew_log2)) << 1);
445
}
446
447
//TODO: Unused in all current cases. Delete? Or leave commented as a reminder?
448
//static uint32_t
449
//calc_layer_map_addr(struct video_layer_properties *props, int eff_x, int eff_y)
450
//{
451
//	return props->map_base + ((eff_y / props->tileh) * props->mapw + (eff_x / props->tilew)) * 2;
452
//}
453
static void
454
refresh_layer_properties(const uint8_t layer)
455
{
456
	struct video_layer_properties* props = &layer_properties[layer];
457
458
	uint16_t prev_layerw_max = props->layerw_max;
459
	uint16_t prev_hscroll = props->hscroll;
460
461
	props->color_depth    = reg_layer[layer][0] & 0x3;
462
	props->map_base       = reg_layer[layer][1] << 9;
463
	props->tile_base      = (reg_layer[layer][2] & 0xFC) << 9;
464
	props->bitmap_mode    = (reg_layer[layer][0] & 0x4) != 0;
465
	props->text_mode      = (props->color_depth == 0) && !props->bitmap_mode;
466
	props->text_mode_256c = (reg_layer[layer][0] & 8) != 0;
467
	props->tile_mode      = !props->bitmap_mode && !props->text_mode;
468
469
	if (!props->bitmap_mode) {
470
		props->hscroll = reg_layer[layer][3] | (reg_layer[layer][4] & 0xf) << 8;
471
		props->vscroll = reg_layer[layer][5] | (reg_layer[layer][6] & 0xf) << 8;
472
	} else {
473
		props->hscroll = 0;
474
		props->vscroll = 0;
475
	}
476
477
	uint16_t mapw = 0;
478
	uint16_t maph = 0;
479
	props->tilew = 0;
480
	props->tileh = 0;
481
482
	if (props->tile_mode || props->text_mode) {
483
		props->mapw_log2 = 5 + ((reg_layer[layer][0] >> 4) & 3);
484
		props->maph_log2 = 5 + ((reg_layer[layer][0] >> 6) & 3);
485
		mapw      = 1 << props->mapw_log2;
486
		maph      = 1 << props->maph_log2;
487
488
		// Scale the tiles or text characters according to TILEW and TILEH.
489
		props->tilew_log2 = 3 + (reg_layer[layer][2] & 1);
490
		props->tileh_log2 = 3 + ((reg_layer[layer][2] >> 1) & 1);
491
		props->tilew      = 1 << props->tilew_log2;
492
		props->tileh      = 1 << props->tileh_log2;
493
	} else if (props->bitmap_mode) {
494
		// bitmap mode is basically tiled mode with a single huge tile
495
		props->tilew = (reg_layer[layer][2] & 1) ? 640 : 320;
496
		props->tileh = SCREEN_HEIGHT;
497
	}
498
499
	// We know mapw, maph, tilew, and tileh are powers of two in all cases except bitmap modes, and any products of that set will be powers of two,
500
	// so there's no need to modulo against them if we have bitmasks we can bitwise-and against.
501
502
	props->mapw_max = mapw - 1;
503
	props->maph_max = maph - 1;
504
	props->tilew_max = props->tilew - 1;
505
	props->tileh_max = props->tileh - 1;
506
	props->layerw_max = (mapw * props->tilew) - 1;
507
	props->layerh_max = (maph * props->tileh) - 1;
508
509
	// Find min/max eff_x for bulk reading in tile data during draw.
510
	if (prev_layerw_max != props->layerw_max || prev_hscroll != props->hscroll) {
511
		int min_eff_x = INT_MAX;
512
		int max_eff_x = INT_MIN;
513
		for (int x = 0; x < SCREEN_WIDTH; ++x) {
514
			int eff_x = calc_layer_eff_x(props, x);
515
			if (eff_x < min_eff_x) {
516
				min_eff_x = eff_x;
517
			}
518
			if (eff_x > max_eff_x) {
519
				max_eff_x = eff_x;
520
			}
521
		}
522
		props->min_eff_x = min_eff_x;
523
		props->max_eff_x = max_eff_x;
524
	}
525
526
	props->bits_per_pixel = 1 << props->color_depth;
527
	props->tile_size_log2 = props->tilew_log2 + props->tileh_log2 + props->color_depth - 3;
528
529
	props->first_color_pos  = 8 - props->bits_per_pixel;
530
	props->color_mask       = (1 << props->bits_per_pixel) - 1;
531
	props->color_fields_max = (8 >> props->color_depth) - 1;
532
}
533
534
struct video_sprite_properties
535
{
536
	int8_t sprite_zdepth;
537
	uint8_t sprite_collision_mask;
538
539
	int16_t sprite_x;
540
	int16_t sprite_y;
541
	uint8_t sprite_width_log2;
542
	uint8_t sprite_height_log2;
543
	uint8_t sprite_width;
544
	uint8_t sprite_height;
545
546
	bool hflip;
547
	bool vflip;
548
549
	uint8_t color_mode;
550
	uint32_t sprite_address;
551
552
	uint16_t palette_offset;
553
};
554
555
#ifndef __EMSCRIPTEN__
556
static void
557
screenshot(void)
558
{
559
	char path[PATH_MAX];
560
	const time_t now = time(NULL);
561
	strftime(path, PATH_MAX, "x16emu-%Y-%m-%d-%H-%M-%S.png", localtime(&now));
562
563
	memset(png_buffer, 0, SCREEN_WIDTH * SCREEN_HEIGHT * 3);
564
565
	// The framebuffer stores pixels in BRGA but we want RGB:
566
	for (int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) {
567
		png_buffer[(i*3)+0] = framebuffer[(i*4)+2];
568
		png_buffer[(i*3)+1] = framebuffer[(i*4)+1];
569
		png_buffer[(i*3)+2] = framebuffer[(i*4)+0];
570
	}
571
572
	if (stbi_write_png(path, SCREEN_WIDTH, SCREEN_HEIGHT, 3, png_buffer, SCREEN_WIDTH*3)) {
573
		printf("Wrote screenshot to %s\n", path);
574
	} else {
575
		printf("WARNING: Couldn't write screenshot to %s\n", path);
576
	}
577
}
578
#endif
579
580
/* Control API accessors (retro-remote-debug-controller). */
581
const uint8_t *
582
video_get_framebuffer(int *width, int *height)
583
{
584
	if (width) *width = SCREEN_WIDTH;
585
	if (height) *height = SCREEN_HEIGHT;
586
	return framebuffer;
587
}
588
589
uint32_t
590
video_get_frame_count(void)
591
{
592
	return (uint32_t)frame_count;
593
}
594
595
struct video_sprite_properties sprite_properties[128];
596
597
static void
598
refresh_sprite_properties(const uint16_t sprite)
599
{
600
	struct video_sprite_properties* props = &sprite_properties[sprite];
601
602
	props->sprite_zdepth = (sprite_data[sprite][6] >> 2) & 3;
603
	props->sprite_collision_mask = sprite_data[sprite][6] & 0xf0;
604
605
	props->sprite_x = sprite_data[sprite][2] | (sprite_data[sprite][3] & 3) << 8;
606
	props->sprite_y = sprite_data[sprite][4] | (sprite_data[sprite][5] & 3) << 8;
607
	props->sprite_width_log2  = (((sprite_data[sprite][7] >> 4) & 3) + 3);
608
	props->sprite_height_log2 = ((sprite_data[sprite][7] >> 6) + 3);
609
	props->sprite_width       = 1 << props->sprite_width_log2;
610
	props->sprite_height      = 1 << props->sprite_height_log2;
611
612
	// fix up negative coordinates
613
	if (props->sprite_x >= 0x400 - props->sprite_width) {
614
		props->sprite_x -= 0x400;
615
	}
616
	if (props->sprite_y >= 0x400 - props->sprite_height) {
617
		props->sprite_y -= 0x400;
618
	}
619
620
	props->hflip = sprite_data[sprite][6] & 1;
621
	props->vflip = (sprite_data[sprite][6] >> 1) & 1;
622
623
	props->color_mode     = (sprite_data[sprite][1] >> 7) & 1;
624
	props->sprite_address = sprite_data[sprite][0] << 5 | (sprite_data[sprite][1] & 0xf) << 13;
625
626
	props->palette_offset = (sprite_data[sprite][7] & 0x0f) << 4;
627
}
628
629
struct video_palette
630
{
631
	uint32_t entries[256];
632
	bool dirty;
633
};
634
635
struct video_palette video_palette;
636
637
static void
638
refresh_palette() {
639
	const uint8_t out_mode = reg_composer[0] & 3;
640
	const bool chroma_disable = ((reg_composer[0] & 0x07) == 6);
641
	for (int i = 0; i < 256; ++i) {
642
		uint8_t r;
643
		uint8_t g;
644
		uint8_t b;
645
		if (out_mode == 0) {
646
			// video generation off
647
			// -> show blue screen
648
			r = 0;
649
			g = 0;
650
			b = 255;
651
		} else {
652
			uint16_t entry = palette[i * 2] | palette[i * 2 + 1] << 8;
653
			r = ((entry >> 8) & 0xf) << 4 | ((entry >> 8) & 0xf);
654
			g = ((entry >> 4) & 0xf) << 4 | ((entry >> 4) & 0xf);
655
			b = (entry & 0xf) << 4 | (entry & 0xf);
656
			if (chroma_disable) {
657
				r = g = b = (r + b + g) / 3;
658
			}
659
		}
660
661
		video_palette.entries[i] = (uint32_t)(r << 16) | ((uint32_t)g << 8) | ((uint32_t)b);
662
	}
663
	video_palette.dirty = false;
664
}
665
666
static void
667
expand_4bpp_data(uint8_t *dst, const uint8_t *src, int dst_size)
668
{
669
	while (dst_size >= 2) {
670
		*dst = (*src) >> 4;
671
		++dst;
672
		*dst = (*src) & 0xf;
673
		++dst;
674
675
		++src;
676
		dst_size -= 2;
677
	}
678
}
679
680
static void
681
render_sprite_line(const uint16_t y)
682
{
683
	memset(sprite_line_col, 0, SCREEN_WIDTH);
684
	memset(sprite_line_z, 0, SCREEN_WIDTH);
685
	memset(sprite_line_mask, 0, SCREEN_WIDTH);
686
687
	uint16_t sprite_budget = 800 + 1;
688
	for (int i = 0; i < NUM_SPRITES; i++) {
689
		// one clock per lookup
690
		sprite_budget--; if (sprite_budget == 0) break;
691
		const struct video_sprite_properties *props = &sprite_properties[i];
692
693
		if (props->sprite_zdepth == 0) {
694
			continue;
695
		}
696
697
		// check whether this line falls within the sprite
698
		if (y < props->sprite_y || y >= props->sprite_y + props->sprite_height) {
699
			continue;
700
		}
701
702
		const uint16_t eff_sy = props->vflip ? ((props->sprite_height - 1) - (y - props->sprite_y)) : (y - props->sprite_y);
703
704
		int16_t       eff_sx      = (props->hflip ? (props->sprite_width - 1) : 0);
705
		const int16_t eff_sx_incr = props->hflip ? -1 : 1;
706
707
		const uint8_t *bitmap_data = video_ram + props->sprite_address + (eff_sy << (props->sprite_width_log2 - (1 - props->color_mode)));
708
709
		uint8_t unpacked_sprite_line[64];
710
		const uint16_t width = (props->sprite_width<64? props->sprite_width : 64);
711
		const uint8_t vram_fetch_mask = ((2 - props->color_mode) << 2) - 1;
712
		if (props->color_mode == 0) {
713
			// 4bpp
714
			expand_4bpp_data(unpacked_sprite_line, bitmap_data, width);
715
		} else {
716
			// 8bpp
717
			memcpy(unpacked_sprite_line, bitmap_data, width);
718
		}
719
720
		for (uint16_t sx = 0; sx < props->sprite_width; ++sx) {
721
			const uint16_t line_x = props->sprite_x + sx;
722
			if (line_x >= SCREEN_WIDTH) {
723
				eff_sx += eff_sx_incr;
724
				continue;
725
			}
726
727
			// one clock per fetched 32 bits
728
			if (!(sx & vram_fetch_mask)) {
729
				sprite_budget--; if (sprite_budget == 0) break;
730
			}
731
732
			// one clock per rendered pixel
733
			sprite_budget--; if (sprite_budget == 0) break;
734
735
			uint8_t col_index = unpacked_sprite_line[eff_sx];
736
			eff_sx += eff_sx_incr;
737
738
			// palette offset
739
			if (col_index > 0) {
740
				sprite_line_collisions |= sprite_line_mask[line_x] & props->sprite_collision_mask;
741
				sprite_line_mask[line_x] |= props->sprite_collision_mask;
742
743
				if (props->sprite_zdepth > sprite_line_z[line_x]) {
744
					if (col_index < 16) {
745
						col_index += props->palette_offset;
746
					}
747
					sprite_line_col[line_x] = col_index;
748
					sprite_line_z[line_x] = props->sprite_zdepth;
749
				}
750
			}
751
		}
752
	}
753
}
754
755
static void
756
render_layer_line_text(uint8_t layer, uint16_t y)
757
{
758
	const struct video_layer_properties *props = &prev_layer_properties[1][layer];
759
	const struct video_layer_properties *props0 = &prev_layer_properties[0][layer];
760
761
	const uint8_t max_pixels_per_byte = (8 >> props->color_depth) - 1;
762
	const int     eff_y               = calc_layer_eff_y(props0, y);
763
	const int     yy                  = eff_y & props->tileh_max;
764
765
	// additional bytes to reach the correct line of the tile
766
	const uint32_t y_add = (yy << props->tilew_log2) >> 3;
767
768
	const uint32_t map_addr_begin = calc_layer_map_addr_base2(props, props->min_eff_x, eff_y);
769
	const uint32_t map_addr_end   = calc_layer_map_addr_base2(props, props->max_eff_x, eff_y);
770
	const int      size           = (map_addr_end - map_addr_begin) + 2;
771
772
	uint8_t tile_bytes[512]; // max 256 tiles, 2 bytes each.
773
	video_space_read_range(tile_bytes, map_addr_begin, size);
774
775
	uint32_t tile_start;
776
777
	uint8_t  fg_color;
778
	uint8_t  bg_color;
779
	uint8_t  s;
780
	uint8_t  color_shift;
781
782
	{
783
		const int eff_x = calc_layer_eff_x(props, 0);
784
		const int xx    = eff_x & props->tilew_max;
785
786
		// extract all information from the map
787
		const uint32_t map_addr = calc_layer_map_addr_base2(props, eff_x, eff_y) - map_addr_begin;
788
789
		const uint8_t tile_index = tile_bytes[map_addr];
790
		const uint8_t byte1      = tile_bytes[map_addr + 1];
791
792
		if (!props->text_mode_256c) {
793
			fg_color = byte1 & 15;
794
			bg_color = byte1 >> 4;
795
		} else {
796
			fg_color = byte1;
797
			bg_color = 0;
798
		}
799
800
		// offset within tilemap of the current tile
801
		tile_start = tile_index << props->tile_size_log2;
802
803
		// additional bytes to reach the correct column of the tile
804
		const uint16_t x_add       = xx >> 3;
805
		const uint32_t tile_offset = tile_start + y_add + x_add;
806
807
		s           = video_space_read(props->tile_base + tile_offset);
808
		color_shift = max_pixels_per_byte - (xx & 0x7);
809
	}
810
811
	// Render tile line.
812
	for (int x = 0; x < SCREEN_WIDTH; x++) {
813
		// Scrolling
814
		const int eff_x = calc_layer_eff_x(props, x);
815
		const int xx = eff_x & props->tilew_max;
816
817
		if ((eff_x & 0x7) == 0) {
818
			if ((eff_x & props->tilew_max) == 0) {
819
				// extract all information from the map
820
				const uint32_t map_addr = calc_layer_map_addr_base2(props, eff_x, eff_y) - map_addr_begin;
821
822
				const uint8_t tile_index = tile_bytes[map_addr];
823
				const uint8_t byte1      = tile_bytes[map_addr + 1];
824
825
				if (!props->text_mode_256c) {
826
					fg_color = byte1 & 15;
827
					bg_color = byte1 >> 4;
828
				} else {
829
					fg_color = byte1;
830
					bg_color = 0;
831
				}
832
833
				// offset within tilemap of the current tile
834
				tile_start = tile_index << props->tile_size_log2;
835
			}
836
837
			// additional bytes to reach the correct column of the tile
838
			const uint16_t x_add       = xx >> 3;
839
			const uint32_t tile_offset = tile_start + y_add + x_add;
840
841
			s           = video_space_read(props->tile_base + tile_offset);
842
			color_shift = max_pixels_per_byte;
843
		}
844
845
		// convert tile byte to indexed color
846
		const uint8_t col_index = (s >> color_shift) & 1;
847
		--color_shift;
848
		layer_line[layer][x] = col_index ? fg_color : bg_color;
849
	}
850
}
851
852
static void
853
render_layer_line_tile(uint8_t layer, uint16_t y)
854
{
855
	const struct video_layer_properties *props = &prev_layer_properties[1][layer];
856
	const struct video_layer_properties *props0 = &prev_layer_properties[0][layer];
857
858
	const uint8_t max_pixels_per_byte = (8 >> props->color_depth) - 1;
859
	const int     eff_y               = calc_layer_eff_y(props0, y);
860
	const uint8_t yy                  = eff_y & props->tileh_max;
861
	const uint8_t yy_flip             = yy ^ props->tileh_max;
862
	const uint32_t y_add              = (yy << ((props->tilew_log2 + props->color_depth - 3) & 31));
863
	const uint32_t y_add_flip         = (yy_flip << ((props->tilew_log2 + props->color_depth - 3) & 31));
864
865
	const uint32_t map_addr_begin = calc_layer_map_addr_base2(props, props->min_eff_x, eff_y);
866
	const uint32_t map_addr_end   = calc_layer_map_addr_base2(props, props->max_eff_x, eff_y);
867
	const int      size           = (map_addr_end - map_addr_begin) + 2;
868
869
	uint8_t tile_bytes[512]; // max 256 tiles, 2 bytes each.
870
	video_space_read_range(tile_bytes, map_addr_begin, size);
871
872
	uint8_t  palette_offset;
873
	bool     vflip;
874
	bool     hflip;
875
	uint32_t tile_start;
876
	uint8_t  s;
877
	uint8_t  color_shift;
878
	int8_t   color_shift_incr;
879
880
	{
881
		const int eff_x = calc_layer_eff_x(props, 0);
882
883
		// extract all information from the map
884
		const uint32_t map_addr = calc_layer_map_addr_base2(props, eff_x, eff_y) - map_addr_begin;
885
886
		const uint8_t byte0 = tile_bytes[map_addr];
887
		const uint8_t byte1 = tile_bytes[map_addr + 1];
888
889
		// Tile Flipping
890
		vflip = (byte1 >> 3) & 1;
891
		hflip = (byte1 >> 2) & 1;
892
893
		palette_offset = byte1 & 0xf0;
894
895
		// offset within tilemap of the current tile
896
		const uint16_t tile_index = byte0 | ((byte1 & 3) << 8);
897
		tile_start                = tile_index << props->tile_size_log2;
898
899
		color_shift_incr = hflip ? props->bits_per_pixel : -props->bits_per_pixel;
900
901
		int xx = eff_x & props->tilew_max;
902
		if (hflip) {
903
			xx          = xx ^ (props->tilew_max);
904
			color_shift = 0;
905
		} else {
906
			color_shift = props->first_color_pos;
907
		}
908
909
		// additional bytes to reach the correct column of the tile
910
		uint16_t x_add       = (xx << props->color_depth) >> 3;
911
		uint32_t tile_offset = tile_start + (vflip ? y_add_flip : y_add) + x_add;
912
913
		s = video_space_read(props->tile_base + tile_offset);
914
	}
915
916
917
	// Render tile line.
918
	for (int x = 0; x < SCREEN_WIDTH; x++) {
919
		const int eff_x = calc_layer_eff_x(props, x);
920
921
		if ((eff_x & max_pixels_per_byte) == 0) {
922
			if ((eff_x & props->tilew_max) == 0) {
923
				// extract all information from the map
924
				const uint32_t map_addr = calc_layer_map_addr_base2(props, eff_x, eff_y) - map_addr_begin;
925
926
				const uint8_t byte0 = tile_bytes[map_addr];
927
				const uint8_t byte1 = tile_bytes[map_addr + 1];
928
929
				// Tile Flipping
930
				vflip = (byte1 >> 3) & 1;
931
				hflip = (byte1 >> 2) & 1;
932
933
				palette_offset = byte1 & 0xf0;
934
935
				// offset within tilemap of the current tile
936
				const uint16_t tile_index = byte0 | ((byte1 & 3) << 8);
937
				tile_start                = tile_index << props->tile_size_log2;
938
939
				color_shift_incr = hflip ? props->bits_per_pixel : -props->bits_per_pixel;
940
			}
941
942
			int xx = eff_x & props->tilew_max;
943
			if (hflip) {
944
				xx = xx ^ (props->tilew_max);
945
				color_shift = 0;
946
			} else {
947
				color_shift = props->first_color_pos;
948
			}
949
950
			// additional bytes to reach the correct column of the tile
951
			const uint16_t x_add       = (xx << props->color_depth) >> 3;
952
			const uint32_t tile_offset = tile_start + (vflip ? y_add_flip : y_add) + x_add;
953
954
			s = video_space_read(props->tile_base + tile_offset);
955
		}
956
957
		// convert tile byte to indexed color
958
		uint8_t col_index = (s >> color_shift) & props->color_mask;
959
		color_shift += color_shift_incr;
960
961
		// Apply Palette Offset
962
		if (col_index > 0 && col_index < 16) {
963
			col_index += palette_offset;
964
			if (props->text_mode_256c) {
965
				col_index |= 0x80;
966
			}
967
		}
968
		layer_line[layer][x] = col_index;
969
	}
970
}
971
972
973
static void
974
render_layer_line_bitmap(uint8_t layer, uint16_t y)
975
{
976
	const struct video_layer_properties *props = &prev_layer_properties[1][layer];
977
//	const struct video_layer_properties *props0 = &prev_layer_properties[0][layer];
978
979
	int yy = y % props->tileh;
980
	// additional bytes to reach the correct line of the tile
981
	uint32_t y_add = (yy * props->tilew * props->bits_per_pixel) >> 3;
982
983
	// Render tile line.
984
	for (int x = 0; x < SCREEN_WIDTH; x++) {
985
		int xx = x % props->tilew;
986
987
		// extract all information from the map
988
		uint8_t palette_offset = reg_layer[layer][4] & 0xf;
989
990
		// additional bytes to reach the correct column of the tile
991
		uint16_t x_add = (xx * props->bits_per_pixel) >> 3;
992
		uint32_t tile_offset = y_add + x_add;
993
		uint8_t s = video_space_read(props->tile_base + tile_offset);
994
995
		// convert tile byte to indexed color
996
		uint8_t col_index = (s >> (props->first_color_pos - ((xx & props->color_fields_max) << props->color_depth))) & props->color_mask;
997
998
		// Apply Palette Offset
999
		if (col_index > 0 && col_index < 16) {
1000
			col_index += palette_offset << 4;
1001
			if (props->text_mode_256c) {
1002
				col_index |= 0x80;
1003
			}
1004
		}
1005
		layer_line[layer][x] = col_index;
1006
	}
1007
}
1008
1009
static uint8_t calculate_line_col_index(uint8_t spr_zindex, uint8_t spr_col_index, uint8_t l1_col_index, uint8_t l2_col_index)
1010
{
1011
	uint8_t col_index = 0;
1012
	switch (spr_zindex) {
1013
		case 3:
1014
			col_index = spr_col_index ? spr_col_index : (l2_col_index ? l2_col_index : l1_col_index);
1015
			break;
1016
		case 2:
1017
			col_index = l2_col_index ? l2_col_index : (spr_col_index ? spr_col_index : l1_col_index);
1018
			break;
1019
		case 1:
1020
			col_index = l2_col_index ? l2_col_index : (l1_col_index ? l1_col_index : spr_col_index);
1021
			break;
1022
		case 0:
1023
			col_index = l2_col_index ? l2_col_index : l1_col_index;
1024
			break;
1025
	}
1026
	return col_index;
1027
}
1028
1029
static void
1030
render_line(uint16_t y, float scan_pos_x)
1031
{
1032
	static uint16_t y_prev;
1033
	static uint16_t s_pos_x_p;
1034
	static uint32_t eff_y_fp; // 16.16 fixed point
1035
	static uint32_t eff_x_fp; // 16.16 fixed point
1036
1037
	static uint8_t col_line[SCREEN_WIDTH];
1038
1039
	uint8_t dc_video = reg_composer[0];
1040
	uint16_t vstart = reg_composer[6] << 1;
1041
1042
	if (y != y_prev) {
1043
		y_prev = y;
1044
		s_pos_x_p = 0;
1045
1046
		// Copy the composer array to 2-line history buffer
1047
		// so that the raster effects that happen on a delay take effect
1048
		// at exactly the right time
1049
1050
		// This simulates different effects happening at render,
1051
		// render but delayed until the next line, or applied mid-line
1052
		// at scan-out
1053
1054
		memcpy(prev_reg_composer[1], prev_reg_composer[0], sizeof(*reg_composer) * COMPOSER_SLOTS);
1055
		memcpy(prev_reg_composer[0], reg_composer, sizeof(*reg_composer) * COMPOSER_SLOTS);
1056
1057
		// Same with the layer properties
1058
1059
		memcpy(prev_layer_properties[1], prev_layer_properties[0], sizeof(*layer_properties) * NUM_LAYERS);
1060
		memcpy(prev_layer_properties[0], layer_properties, sizeof(*layer_properties) * NUM_LAYERS);
1061
1062
		if ((dc_video & 3) > 1) { // 480i or 240p
1063
			if ((y >> 1) == 0) {
1064
				eff_y_fp = y*(prev_reg_composer[1][2] << 9);
1065
			} else if ((y & 0xfffe) > vstart) {
1066
				eff_y_fp += (prev_reg_composer[1][2] << 10);
1067
			}
1068
		} else {
1069
			if (y == 0) {
1070
				eff_y_fp = 0;
1071
			} else if (y > vstart) {
1072
				eff_y_fp += (prev_reg_composer[1][2] << 9);
1073
			}
1074
		}
1075
	}
1076
1077
	if ((dc_video & 8) && (dc_video & 3) > 1) { // progressive NTSC/RGB mode
1078
		y &= 0xfffe;
1079
	}
1080
1081
	// refresh palette for next entry
1082
	if (video_palette.dirty) {
1083
		refresh_palette();
1084
	}
1085
1086
	if (y >= SCREEN_HEIGHT) {
1087
		return;
1088
	}
1089
1090
	uint16_t s_pos_x = round(scan_pos_x);
1091
	if (s_pos_x > SCREEN_WIDTH) {
1092
		s_pos_x = SCREEN_WIDTH;
1093
	}
1094
1095
	if (s_pos_x_p == 0) {
1096
		eff_x_fp = 0;
1097
	}
1098
1099
	uint8_t out_mode = reg_composer[0] & 3;
1100
1101
	uint8_t border_color = reg_composer[3];
1102
	uint16_t hstart = reg_composer[4] << 2;
1103
	uint16_t hstop = reg_composer[5] << 2;
1104
	uint16_t vstop = reg_composer[7] << 1;
1105
1106
	uint16_t eff_y = (eff_y_fp >> 16);
1107
	if (eff_y >= 480) eff_y = 480 - (y & 1);
1108
1109
	layer_line_enable[0] = dc_video & 0x10;
1110
	layer_line_enable[1] = dc_video & 0x20;
1111
	sprite_line_enable   = dc_video & 0x40;
1112
1113
	// clear layer_line if layer gets disabled
1114
	for (uint8_t layer = 0; layer < 2; layer++) {
1115
		if (!layer_line_enable[layer] && old_layer_line_enable[layer]) {
1116
			for (uint16_t i = s_pos_x_p; i < SCREEN_WIDTH; i++) {
1117
				layer_line[layer][i] = 0;
1118
			}
1119
		}
1120
		if (s_pos_x_p == 0)
1121
			old_layer_line_enable[layer] = layer_line_enable[layer];
1122
	}
1123
1124
	// clear sprite_line if sprites get disabled
1125
	if (!sprite_line_enable && old_sprite_line_enable) {
1126
		for (uint16_t i = s_pos_x_p; i < SCREEN_WIDTH; i++) {
1127
			sprite_line_col[i] = 0;
1128
			sprite_line_z[i] = 0;
1129
			sprite_line_mask[i] = 0;
1130
		}
1131
	}
1132
1133
	if (s_pos_x_p == 0)
1134
		old_sprite_line_enable = sprite_line_enable;
1135
1136
1137
1138
	if (sprite_line_enable) {
1139
		render_sprite_line(eff_y);
1140
	}
1141
1142
	if (warp_mode && (frame_count & 63)) {
1143
		// sprites were needed for the collision IRQ, but we can skip
1144
		// everything else if we're in warp mode, most of the time
1145
		return;
1146
	}
1147
1148
	if (layer_line_enable[0]) {
1149
		if (prev_layer_properties[1][0].text_mode) {
1150
			render_layer_line_text(0, eff_y);
1151
		} else if (prev_layer_properties[1][0].bitmap_mode) {
1152
			render_layer_line_bitmap(0, eff_y);
1153
		} else {
1154
			render_layer_line_tile(0, eff_y);
1155
		}
1156
	}
1157
	if (layer_line_enable[1]) {
1158
		if (prev_layer_properties[1][1].text_mode) {
1159
			render_layer_line_text(1, eff_y);
1160
		} else if (prev_layer_properties[1][1].bitmap_mode) {
1161
			render_layer_line_bitmap(1, eff_y);
1162
		} else {
1163
			render_layer_line_tile(1, eff_y);
1164
		}
1165
	}
1166
1167
	// If video output is enabled, calculate color indices for line.
1168
	if (out_mode != 0) {
1169
		// Add border after if required.
1170
		if (y < vstart || y >= vstop) {
1171
			uint32_t border_fill = border_color;
1172
			border_fill = border_fill | (border_fill << 8);
1173
			border_fill = border_fill | (border_fill << 16);
1174
			memset(col_line, border_fill, SCREEN_WIDTH);
1175
		} else {
1176
			hstart = hstart < 640 ? hstart : 640;
1177
			hstop = hstop < 640 ? hstop : 640;
1178
1179
			for (uint16_t x = s_pos_x_p; x < hstart && x < s_pos_x; ++x) {
1180
				col_line[x] = border_color;
1181
			}
1182
1183
			const uint32_t scale = reg_composer[1];
1184
			for (uint16_t x = MAX(hstart, s_pos_x_p); x < hstop && x < s_pos_x; ++x) {
1185
				uint16_t eff_x = eff_x_fp >> 16;
1186
				col_line[x] = (eff_x < SCREEN_WIDTH) ? calculate_line_col_index(sprite_line_z[eff_x], sprite_line_col[eff_x], layer_line[0][eff_x], layer_line[1][eff_x]) : 0;
1187
				eff_x_fp += (scale << 9);
1188
			}
1189
			for (uint16_t x = hstop; x < s_pos_x; ++x) {
1190
				col_line[x] = border_color;
1191
			}
1192
		}
1193
	}
1194
1195
	// Look up all color indices.
1196
	uint32_t* framebuffer4_begin = ((uint32_t*)framebuffer) + (y * SCREEN_WIDTH) + s_pos_x_p;
1197
	{
1198
		uint32_t* framebuffer4 = framebuffer4_begin;
1199
		for (uint16_t x = s_pos_x_p; x < s_pos_x; x++) {
1200
			*framebuffer4++ = video_palette.entries[col_line[x]];
1201
		}
1202
	}
1203
1204
	// NTSC overscan
1205
	if (out_mode == 2) {
1206
		uint32_t* framebuffer4 = framebuffer4_begin;
1207
		for (uint16_t x = s_pos_x_p; x < s_pos_x; x++)
1208
		{
1209
			if (x < SCREEN_WIDTH * TITLE_SAFE_X ||
1210
				x > SCREEN_WIDTH * (1 - TITLE_SAFE_X) ||
1211
				y < SCREEN_HEIGHT * TITLE_SAFE_Y ||
1212
				y > SCREEN_HEIGHT * (1 - TITLE_SAFE_Y)) {
1213
1214
				// Divide RGB elements by 4.
1215
				*framebuffer4 &= 0x00fcfcfc;
1216
				*framebuffer4 >>= 2;
1217
			}
1218
			framebuffer4++;
1219
		}
1220
	}
1221
1222
	s_pos_x_p = s_pos_x;
1223
}
1224
1225
static void
1226
update_isr_and_coll(uint16_t y, uint16_t compare)
1227
{
1228
	if (y == SCREEN_HEIGHT) {
1229
		if (sprite_line_collisions != 0) {
1230
			isr |= 4;
1231
		}
1232
		isr = (isr & 0xf) | sprite_line_collisions;
1233
		sprite_line_collisions = 0;
1234
		isr |= 1; // VSYNC IRQ
1235
	}
1236
	if (y == compare) { // LINE IRQ
1237
		isr |= 2;
1238
	}
1239
}
1240
12673a41 Andy McCall ↶ 1241
// How far the beam moves in `steps` CPU clocks: PIXEL_FREQ * steps / mhz.
1242
// video_step() runs after every instruction, almost always for a handful
1243
// of clocks, so the quotient for each small count is kept rather than
1244
// divided out again; it is the same expression, so the same value.
1245
static double
1246
scan_pixels(float mhz, float steps)
1247
{
1248
	static float table_mhz;
1249
	static double table[32];
1250
	if (mhz != table_mhz) {
1251
		for (int i = 0; i < 32; i++) {
1252
			table[i] = PIXEL_FREQ * (float)i / mhz;
1253
		}
1254
		table_mhz = mhz;
1255
	}
1256
	if (steps >= 0 && steps < 32) {
1257
		uint32_t s = (uint32_t)steps;
1258
		if (s == steps) {
1259
			return table[s];
1260
		}
1261
	}
1262
	return PIXEL_FREQ * steps / mhz;
1263
}
1264
8e8d9cad Andy McCall ↶ 1265
// The least n for which pos + n * step passes end, for a pos on the grid
1266
// of eighths; 1 for one off it.
1267
static uint32_t
1268
clocks_to_pass(float pos, float end, double step)
1269
{
1270
	if (pos > end || pos * 8 != floorf(pos * 8)) {
1271
		return 1;
1272
	}
1273
	return (uint32_t)((end - pos) / step) + 1;
1274
}
1275
1276
// How many CPU clocks video_step() can take in one call and still do
1277
// exactly what that many one-clock calls would: up to and including the
1278
// clock on which the beam passes the end of a VGA or NTSC line, the first
1279
// time it can raise an interrupt or end a frame. That holds only while the
1280
// beam moves by a whole number of eighths a clock (25 MHz / mhz, so mhz
1281
// divides 200, as the X16's 8 does): then every position is exact in a
1282
// float, and one big step adds up to the same as many small ones.
1283
// Otherwise the answer is 1.
1284
uint32_t
1285
video_clocks_to_line_end(uint8_t mhz)
1286
{
1287
	if (mhz == 0 || 200 % mhz) {
1288
		return 1;
1289
	}
1290
	const double step = scan_pixels(mhz, 1);
1291
	const uint32_t vga = clocks_to_pass(vga_scan_pos_x, VGA_SCAN_WIDTH, step);
1292
	const uint32_t ntsc = clocks_to_pass(ntsc_half_cnt, NTSC_HALF_SCAN_WIDTH, step);
1293
	return vga < ntsc ? vga : ntsc;
1294
}
1295
03a3459e Andy McCall 1296
bool
1297
video_step(float mhz, float steps, bool midline)
1298
{
1299
	uint16_t y = 0;
1300
	bool ntsc_mode = reg_composer[0] & 2;
1301
	bool new_frame = false;
12673a41 Andy McCall ↶ 1302
	const double pixels = scan_pixels(mhz, steps);
1303
	vga_scan_pos_x += pixels;
03a3459e Andy McCall 1304
	if (vga_scan_pos_x > VGA_SCAN_WIDTH) {
1305
		vga_scan_pos_x -= VGA_SCAN_WIDTH;
1306
		if (!ntsc_mode) {
1307
			render_line(vga_scan_pos_y - VGA_Y_OFFSET, VGA_SCAN_WIDTH);
1308
		}
1309
		vga_scan_pos_y++;
1310
		if (vga_scan_pos_y == SCAN_HEIGHT) {
1311
			vga_scan_pos_y = 0;
1312
			if (!ntsc_mode) {
1313
				new_frame = true;
1314
				frame_count++;
1315
			}
1316
		}
1317
		if (!ntsc_mode) {
1318
			update_isr_and_coll(vga_scan_pos_y - VGA_Y_OFFSET, irq_line);
1319
		}
1320
	} else if (midline) {
1321
		if (!ntsc_mode) {
1322
			render_line(vga_scan_pos_y - VGA_Y_OFFSET, vga_scan_pos_x);
1323
		}
1324
	}
12673a41 Andy McCall ↶ 1325
	ntsc_half_cnt += pixels;
03a3459e Andy McCall 1326
	if (ntsc_half_cnt > NTSC_HALF_SCAN_WIDTH) {
1327
		ntsc_half_cnt -= NTSC_HALF_SCAN_WIDTH;
1328
		if (ntsc_mode) {
1329
			if (ntsc_scan_pos_y < SCAN_HEIGHT) {
1330
				y = ntsc_scan_pos_y - NTSC_Y_OFFSET_LOW;
1331
				if ((y & 1) == 0) {
1332
					render_line(y, NTSC_HALF_SCAN_WIDTH);
1333
				}
1334
			} else {
1335
				y = ntsc_scan_pos_y - NTSC_Y_OFFSET_HIGH;
1336
				if ((y & 1) == 0) {
1337
					render_line(y | 1, NTSC_HALF_SCAN_WIDTH);
1338
				}
1339
			}
1340
		}
1341
		ntsc_scan_pos_y++;
1342
		if (ntsc_scan_pos_y == SCAN_HEIGHT) {
1343
			reg_composer[0] |= 0x80;
1344
			if (ntsc_mode) {
1345
				new_frame = true;
1346
				frame_count++;
1347
			}
1348
		}
1349
		if (ntsc_scan_pos_y == SCAN_HEIGHT*2) {
1350
			reg_composer[0] &= ~0x80;
1351
			ntsc_scan_pos_y = 0;
1352
			if (ntsc_mode) {
1353
				new_frame = true;
1354
				frame_count++;
1355
			}
1356
		}
1357
		if (ntsc_mode) {
1358
			// this is correct enough for even screen heights
1359
			if (ntsc_scan_pos_y < SCAN_HEIGHT) {
1360
				update_isr_and_coll(ntsc_scan_pos_y - NTSC_Y_OFFSET_LOW, irq_line & ~1);
1361
			} else {
1362
				update_isr_and_coll(ntsc_scan_pos_y - NTSC_Y_OFFSET_HIGH, irq_line & ~1);
1363
			}
1364
		}
1365
	} else if (midline) {
1366
		if (ntsc_mode) {
1367
			if (ntsc_scan_pos_y < SCAN_HEIGHT) {
1368
				y = ntsc_scan_pos_y - NTSC_Y_OFFSET_LOW;
1369
				if ((y & 1) == 0) {
1370
					render_line(y, ntsc_half_cnt);
1371
				}
1372
			} else {
1373
				y = ntsc_scan_pos_y - NTSC_Y_OFFSET_HIGH;
1374
				if ((y & 1) == 0) {
1375
					render_line(y | 1, ntsc_half_cnt);
1376
				}
1377
			}
1378
		}
1379
	}
1380
1381
	return new_frame;
1382
}
1383
1384
bool
1385
video_get_irq_out()
1386
{
1387
	uint8_t tmp_isr = isr | (pcm_is_fifo_almost_empty() ? 8 : 0);
1388
	return (tmp_isr & ien) != 0;
1389
}
1390
1391
//
1392
// saves the video memory and register content into a file
1393
//
1394
1395
void
1396
video_save(SDL_RWops *f)
1397
{
1398
	SDL_RWwrite(f, &video_ram[0], sizeof(uint8_t), sizeof(video_ram));
1399
	SDL_RWwrite(f, &reg_composer[0], sizeof(uint8_t), sizeof(reg_composer));
1400
	SDL_RWwrite(f, &palette[0], sizeof(uint8_t), sizeof(palette));
1401
	SDL_RWwrite(f, &reg_layer[0][0], sizeof(uint8_t), sizeof(reg_layer));
1402
	SDL_RWwrite(f, &sprite_data[0], sizeof(uint8_t), sizeof(sprite_data));
1403
}
1404
1405
bool
1406
video_update()
1407
{
1408
	static bool cmd_down = false;
1409
	static bool alt_down = false;
1410
	bool mouse_changed = false;
1411
1412
	// for activity LED, overlay red 8x4 square into top right of framebuffer
1413
	// for progressive modes, draw LED only on even scanlines
1414
	for (int y = 0; y < 4; y+=1+!!((reg_composer[0] & 0x0b) > 0x09)) {
1415
		for (int x = SCREEN_WIDTH - 8; x < SCREEN_WIDTH; x++) {
1416
			uint8_t b = framebuffer[(y * SCREEN_WIDTH + x) * 4 + 0];
1417
			uint8_t g = framebuffer[(y * SCREEN_WIDTH + x) * 4 + 1];
1418
			uint8_t r = framebuffer[(y * SCREEN_WIDTH + x) * 4 + 2];
1419
			r = (uint32_t)r * (255 - activity_led) / 255 + activity_led;
1420
			g = (uint32_t)g * (255 - activity_led) / 255;
1421
			b = (uint32_t)b * (255 - activity_led) / 255;
1422
			framebuffer[(y * SCREEN_WIDTH + x) * 4 + 0] = b;
1423
			framebuffer[(y * SCREEN_WIDTH + x) * 4 + 1] = g;
1424
			framebuffer[(y * SCREEN_WIDTH + x) * 4 + 2] = r;
1425
			framebuffer[(y * SCREEN_WIDTH + x) * 4 + 3] = 0x00;
1426
		}
1427
	}
1428
1429
	SDL_UpdateTexture(sdlTexture, NULL, framebuffer, SCREEN_WIDTH * 4);
1430
1431
	if (record_gif > RECORD_GIF_PAUSED) {
1432
		if(!GifWriteFrame(&gif_writer, framebuffer, SCREEN_WIDTH, SCREEN_HEIGHT, 2, 8, false)) {
1433
			// if that failed, stop recording
1434
			GifEnd(&gif_writer);
1435
			record_gif = RECORD_GIF_DISABLED;
1436
			printf("Unexpected end of recording.\n");
1437
		}
1438
		if (record_gif == RECORD_GIF_SINGLE) { // if single-shot stop recording
1439
			record_gif = RECORD_GIF_PAUSED;  // need to close in video_end()
1440
		}
1441
	}
1442
1443
	SDL_RenderClear(renderer);
1444
	SDL_RenderCopy(renderer, sdlTexture, NULL, NULL);
1445
1446
	if (debugger_enabled && showDebugOnRender != 0) {
1447
		DEBUGRenderDisplay(SCREEN_WIDTH, SCREEN_HEIGHT);
1448
		SDL_RenderPresent(renderer);
1449
		return true;
1450
	}
1451
1452
	SDL_RenderPresent(renderer);
1453
1454
	SDL_Event event;
1455
	while (SDL_PollEvent(&event)) {
1456
		if (event.type == SDL_QUIT) {
1457
			return false;
1458
		}
1459
		if (event.type == SDL_KEYDOWN) {
1460
			bool consumed = false;
1461
			if (cmd_down && !(disable_emu_cmd_keys || mouse_grabbed)) {
1462
				if (event.key.keysym.sym == SDLK_s) {
1463
					machine_dump("user keyboard request");
1464
					consumed = true;
1465
				} else if (event.key.keysym.sym == SDLK_r) {
1466
					machine_reset();
1467
					consumed = true;
1468
				} else if (event.key.keysym.sym == SDLK_BACKSPACE) {
1469
					machine_nmi();
1470
					consumed = true;
1471
				} else if (event.key.keysym.sym == SDLK_v) {
1472
					machine_paste(SDL_GetClipboardText(), true);
1473
					consumed = true;
1474
				} else if (event.key.keysym.sym == SDLK_f || event.key.keysym.sym == SDLK_RETURN) {
1475
					is_fullscreen = !is_fullscreen;
1476
					SDL_SetWindowFullscreen(window, is_fullscreen ? SDL_WINDOW_FULLSCREEN : 0);
1477
					consumed = true;
1478
				} else if (event.key.keysym.sym == SDLK_PLUS || event.key.keysym.sym == SDLK_EQUALS) {
1479
					machine_toggle_warp();
1480
					consumed = true;
1481
				} else if (event.key.keysym.sym == SDLK_a) {
1482
					sdcard_attach();
1483
					consumed = true;
1484
				} else if (event.key.keysym.sym == SDLK_d) {
1485
					sdcard_detach();
1486
					consumed = true;
1487
#ifndef __EMSCRIPTEN__
1488
				} else if (event.key.keysym.sym == SDLK_p) {
1489
					screenshot();
1490
					consumed = true;
1491
#endif
1492
				}
1493
			}
1494
			if (cmd_down) {
1495
				if (event.key.keysym.sym == SDLK_m) {
1496
					mousegrab_toggle();
1497
					consumed = true;
1498
				}
1499
			}
1500
			if (alt_down && !disable_emu_cmd_keys) {
1501
				// Capture ALT+F4 key sequence to exit emulator if it is not handled by SDL
1502
				if (event.key.keysym.sym == SDLK_F4) {
1503
					printf("Shutdown requested by ALT+F4\n");
1504
					main_shutdown();
1505
#ifdef __EMSCRIPTEN__
1506
					emscripten_force_exit(0);
1507
#endif
1508
					exit(0);
1509
				}
1510
			}
1511
			if (!consumed) {
1512
				if (event.key.keysym.scancode == LSHORTCUT_KEY || event.key.keysym.scancode == RSHORTCUT_KEY) {
1513
					cmd_down = true;
1514
				}
1515
				if (event.key.keysym.scancode == SDL_SCANCODE_LALT || event.key.keysym.scancode == SDL_SCANCODE_RALT) {
1516
					alt_down = true;
1517
				}
1518
				handle_keyboard(true, event.key.keysym.sym, event.key.keysym.scancode);
1519
			}
1520
			continue;
1521
		}
1522
		if (event.type == SDL_KEYUP) {
1523
			if (event.key.keysym.scancode == LSHORTCUT_KEY || event.key.keysym.scancode == RSHORTCUT_KEY) {
1524
				cmd_down = false;
1525
			}
1526
			if (event.key.keysym.scancode == SDL_SCANCODE_LALT || event.key.keysym.scancode == SDL_SCANCODE_RALT) {
1527
				alt_down = false;
1528
			}
1529
			handle_keyboard(false, event.key.keysym.sym, event.key.keysym.scancode);
1530
			continue;
1531
		}
1532
		if (event.type == SDL_MOUSEBUTTONDOWN) {
1533
			switch (event.button.button) {
1534
				case SDL_BUTTON_LEFT:
1535
					mouse_button_down(0);
1536
					mouse_changed = true;
1537
					break;
1538
				case SDL_BUTTON_RIGHT:
1539
					mouse_button_down(1);
1540
					mouse_changed = true;
1541
					break;
1542
				case SDL_BUTTON_MIDDLE:
1543
					mouse_button_down(2);
1544
					mouse_changed = true;
1545
					break;
1546
			}
1547
		}
1548
		if (event.type == SDL_MOUSEBUTTONUP) {
1549
			switch (event.button.button) {
1550
				case SDL_BUTTON_LEFT:
1551
					mouse_button_up(0);
1552
					mouse_changed = true;
1553
					break;
1554
				case SDL_BUTTON_RIGHT:
1555
					mouse_button_up(1);
1556
					mouse_changed = true;
1557
					break;
1558
				case SDL_BUTTON_MIDDLE:
1559
					mouse_button_up(2);
1560
					mouse_changed = true;
1561
					break;
1562
			}
1563
		}
1564
		if (event.type == SDL_MOUSEMOTION) {
1565
			static int mouse_x;
1566
			static int mouse_y;
1567
			if (mouse_grabbed) {
1568
				mouse_move(event.motion.xrel, event.motion.yrel);
1569
			} else {
1570
				mouse_move(event.motion.x - mouse_x, event.motion.y - mouse_y);
1571
			}
1572
			mouse_x = event.motion.x;
1573
			mouse_y = event.motion.y;
1574
			mouse_changed = true;
1575
		}
1576
		if (event.type == SDL_MOUSEWHEEL) {
1577
			mouse_set_wheel(event.wheel.y);
1578
			mouse_changed = true;
1579
		}
1580
1581
		if (event.type == SDL_JOYDEVICEADDED) {
1582
			joystick_add(event.jdevice.which);
1583
		}
1584
	    if (event.type == SDL_JOYDEVICEREMOVED) {
1585
		    joystick_remove(event.jdevice.which);
1586
	    }
1587
	    if (event.type == SDL_CONTROLLERBUTTONDOWN) {
1588
		    joystick_button_down(event.cbutton.which, event.cbutton.button);
1589
	    }
1590
		if (event.type == SDL_CONTROLLERBUTTONUP) {
1591
		    joystick_button_up(event.cbutton.which, event.cbutton.button);
1592
	    }
1593
1594
	}
1595
	if (mouse_changed) {
1596
		mouse_send_state();
1597
	}
1598
	return true;
1599
}
1600
1601
void
1602
video_end()
1603
{
1604
	if (debugger_enabled) {
1605
		DEBUGFreeUI();
1606
	}
1607
1608
	if (record_gif != RECORD_GIF_DISABLED) {
1609
		GifEnd(&gif_writer);
1610
		record_gif = RECORD_GIF_DISABLED;
1611
	}
1612
1613
	is_fullscreen = false;
1614
	SDL_SetWindowFullscreen(window, 0);
1615
	SDL_DestroyRenderer(renderer);
1616
	SDL_DestroyWindow(window);
1617
}
1618
1619
1620
static const int increments[32] = {
1621
	0,   0,
1622
	1,   -1,
1623
	2,   -2,
1624
	4,   -4,
1625
	8,   -8,
1626
	16,  -16,
1627
	32,  -32,
1628
	64,  -64,
1629
	128, -128,
1630
	256, -256,
1631
	512, -512,
1632
	40,  -40,
1633
	80,  -80,
1634
	160, -160,
1635
	320, -320,
1636
	640, -640,
1637
};
1638
1639
uint32_t
1640
video_get_address(uint8_t sel)
1641
{
1642
	uint32_t address = io_addr[sel];
1643
	return address & 0x1ffff;
1644
}
1645
1646
uint32_t
1647
get_and_inc_address(uint8_t sel, bool write)
1648
{
1649
	uint32_t address = io_addr[sel];
1650
	int16_t incr = increments[io_inc[sel]];
1651
1652
	if (fx_4bit_mode && fx_nibble_incr[sel] && !incr) {
1653
		if (fx_nibble_bit[sel]) {
1654
			if ((io_inc[sel] & 1) == 0) io_addr[sel] += 1;
1655
			fx_nibble_bit[sel] = 0;
1656
		} else {
1657
			if (io_inc[sel] & 1) io_addr[sel] -= 1;
1658
			fx_nibble_bit[sel] = 1;
1659
		}
1660
	}
1661
1662
	if (sel == 1 && fx_16bit_hop) {
1663
		if (incr == 4) {
1664
			if (fx_16bit_hop_align == (address & 0x3))
1665
				incr = 1;
1666
			else
1667
				incr = 3;
1668
		} else if (incr == 320) {
1669
			if (fx_16bit_hop_align == (address & 0x3))
1670
				incr = 1;
1671
			else
1672
				incr = 319;
1673
		}
1674
	}
1675
1676
	io_addr[sel] += incr;
1677
1678
	if (sel == 1 && fx_addr1_mode == 1) { // FX line draw mode
1679
		fx_x_pixel_position += fx_x_pixel_increment;
1680
		if (fx_x_pixel_position & 0x10000) {
1681
			fx_x_pixel_position &= ~0x10000;
1682
			if (fx_4bit_mode && fx_nibble_incr[0]) {
1683
				if (fx_nibble_bit[1]) {
1684
					if ((io_inc[0] & 1) == 0) io_addr[1] += 1;
1685
					fx_nibble_bit[1] = 0;
1686
				} else {
1687
					if (io_inc[0] & 1) io_addr[1] -= 1;
1688
					fx_nibble_bit[1] = 1;
1689
				}
1690
			}
1691
			io_addr[1] += increments[io_inc[0]];
1692
		}
1693
	} else if (fx_addr1_mode == 2 && write == false) { // FX polygon fill mode
1694
		fx_x_pixel_position += fx_x_pixel_increment;
1695
		fx_y_pixel_position += fx_y_pixel_increment;
1696
		fx_poly_fill_length = ((int32_t) fx_y_pixel_position >> 16) - ((int32_t) fx_x_pixel_position >> 16);
1697
		if (sel == 0 && fx_cache_byte_cycling && !fx_cache_fill) {
1698
			fx_cache_byte_index = (fx_cache_byte_index + 1) & 3;
1699
		}
1700
		if (sel == 1) {
1701
			if (fx_4bit_mode) {
1702
				io_addr[1] = io_addr[0] + (fx_x_pixel_position >> 17);
1703
				fx_nibble_bit[1] = (fx_x_pixel_position >> 16) & 1;
1704
			} else {
1705
				io_addr[1] = io_addr[0] + (fx_x_pixel_position >> 16);
1706
			}
1707
		}
1708
	} else if (sel == 1 && fx_addr1_mode == 3 && write == false) { // FX affine mode
1709
		fx_x_pixel_position += fx_x_pixel_increment;
1710
		fx_y_pixel_position += fx_y_pixel_increment;
1711
	}
1712
	return address;
1713
}
1714
1715
void
1716
fx_affine_prefetch(void)
1717
{
1718
	if (fx_addr1_mode != 3) return; // only if affine mode is selected
1719
1720
	uint32_t address;
1721
	uint8_t affine_x_tile = (fx_x_pixel_position >> 19) & 0xff;
1722
	uint8_t affine_y_tile = (fx_y_pixel_position >> 19) & 0xff;
1723
	uint8_t affine_x_sub_tile = (fx_x_pixel_position >> 16) & 0x07;
1724
	uint8_t affine_y_sub_tile = (fx_y_pixel_position >> 16) & 0x07;
1725
1726
	if (!fx_affine_clip) { // wrap
1727
		affine_x_tile &= fx_affine_map_size - 1;
1728
		affine_y_tile &= fx_affine_map_size - 1;
1729
	}
1730
1731
	if (affine_x_tile >= fx_affine_map_size || affine_y_tile >= fx_affine_map_size) {
1732
		// We clipped, return value for tile 0
1733
		address = fx_affine_tile_base + (affine_y_sub_tile << (3 - fx_4bit_mode)) + (affine_x_sub_tile >> (uint8_t)fx_4bit_mode);
1734
		fx_nibble_bit[1] = (affine_x_sub_tile & 1) >> (1 - fx_4bit_mode);
1735
	} else {
1736
		// Get the address within the tile map
1737
		address = fx_affine_map_base + (affine_y_tile * fx_affine_map_size) + affine_x_tile;
1738
		// Now translate that to the tile base address
1739
		uint8_t affine_tile_idx = video_space_read(address);
1740
		address = fx_affine_tile_base + (affine_tile_idx << (6 - fx_4bit_mode));
1741
		// Now add the sub-tile address
1742
		address += (affine_y_sub_tile << (3 - fx_4bit_mode)) + (affine_x_sub_tile >> (uint8_t)fx_4bit_mode);
1743
		fx_nibble_bit[1] = (affine_x_sub_tile & 1) >> (1 - fx_4bit_mode);
1744
	}
1745
	io_addr[1] = address;
1746
	io_rddata[1] = video_space_read(address);
1747
}
1748
1749
//
1750
// Vera: Internal Video Address Space
1751
//
1752
1753
uint8_t
1754
video_space_read(uint32_t address)
1755
{
1756
	return video_ram[address & 0x1FFFF];
1757
}
1758
1759
static void
1760
video_space_read_range(uint8_t* dest, uint32_t address, uint32_t size)
1761
{
1762
	if (address >= ADDR_VRAM_START && (address+size) <= ADDR_VRAM_END) {
1763
		memcpy(dest, &video_ram[address], size);
1764
	} else {
1765
		for(int i = 0; i < size; ++i) {
1766
			*dest++ = video_space_read(address + i);
1767
		}
1768
	}
1769
}
1770
1771
void
1772
video_space_write(uint32_t address, uint8_t value)
1773
{
1774
	video_ram[address & 0x1FFFF] = value;
1775
1776
	if (address >= ADDR_PSG_START && address < ADDR_PSG_END) {
1777
		audio_render();
1778
		psg_writereg(address & 0x3f, value);
1779
	} else if (address >= ADDR_PALETTE_START && address < ADDR_PALETTE_END) {
1780
		palette[address & 0x1ff] = value;
1781
		video_palette.dirty = true;
1782
	} else if (address >= ADDR_SPRDATA_START && address < ADDR_SPRDATA_END) {
1783
		sprite_data[(address >> 3) & 0x7f][address & 0x7] = value;
1784
		refresh_sprite_properties((address >> 3) & 0x7f);
1785
	}
1786
}
1787
1788
1789
void
1790
fx_video_space_write(uint32_t address, bool nibble, uint8_t value)
1791
{
1792
	if (fx_4bit_mode) {
1793
		if (nibble) {
1794
			if (!fx_trans_writes || (value & 0x0f) > 0) {
1795
				video_ram[address & 0x1FFFF] = (video_ram[address & 0x1FFFF] & 0xf0) | (value & 0x0f);
1796
			}
1797
		} else {
1798
			if (!fx_trans_writes || (value & 0xf0) > 0) {
1799
				video_ram[address & 0x1FFFF] = (video_ram[address & 0x1FFFF] & 0x0f) | (value & 0xf0);
1800
			}
1801
		}
1802
	} else {
1803
		if (!fx_trans_writes || value > 0) video_ram[address & 0x1FFFF] = value;
1804
	}
1805
	if (address >= ADDR_PSG_START && address < ADDR_PSG_END) {
1806
		audio_render();
1807
		psg_writereg(address & 0x3f, value);
1808
	} else if (address >= ADDR_PALETTE_START && address < ADDR_PALETTE_END) {
1809
		palette[address & 0x1ff] = value;
1810
		video_palette.dirty = true;
1811
	} else if (address >= ADDR_SPRDATA_START && address < ADDR_SPRDATA_END) {
1812
		sprite_data[(address >> 3) & 0x7f][address & 0x7] = value;
1813
		refresh_sprite_properties((address >> 3) & 0x7f);
1814
	}
1815
}
1816
1817
void
1818
fx_vram_cache_write(uint32_t address, uint8_t value, uint8_t mask)
1819
{
1820
	if (!fx_trans_writes || value > 0) {
1821
		switch (mask) {
1822
			case 0:
1823
				video_ram[address & 0x1FFFF] = value;
1824
				break;
1825
			case 1:
1826
				video_ram[address & 0x1FFFF] = (video_ram[address & 0x1FFFF] & 0x0f) | (value & 0xf0);
1827
				break;
1828
			case 2:
1829
				video_ram[address & 0x1FFFF] = (video_ram[address & 0x1FFFF] & 0xf0) | (value & 0x0f);
1830
				break;
1831
			case 3:
1832
				// Do nothing
1833
				break;
1834
		}
1835
	}
1836
}
1837
1838
uint32_t video_get_fx_accum() {
1839
	return fx_mult_accumulator;
1840
}
1841
uint8_t video_get_dc_value(uint8_t reg) {
1842
	switch (reg & 0x1F) {
1843
		case 0x00:
1844
		case 0x01:
1845
		case 0x02:
1846
		case 0x03:
1847
		case 0x04:
1848
		case 0x05:
1849
		case 0x06:
1850
		case 0x07:
1851
		case 0x08:
1852
		case 0x09:
1853
		case 0x0a:
1854
		case 0x0c:
1855
		case 0x0d:
1856
		case 0x0e:
1857
		case 0x0f:
1858
			return reg_composer[reg];
1859
			break;
1860
		case 0x0b:
1861
			return reg_composer[reg] & 0x3f;
1862
			break;
1863
		case 0x10: // DCSEL=4, $9F29
1864
			return (fx_x_pixel_position >> 16) & 0xff;
1865
			break;
1866
		case 0x11: // DCSEL=4, $9F2A
1867
			return ((fx_x_pixel_position >> 24) & 0x07) | (fx_x_pixel_position & 0x80);
1868
			break;
1869
		case 0x12: // DCSEL=4, $9F2B
1870
			return (fx_y_pixel_position >> 16) & 0xff;
1871
			break;
1872
		case 0x13: // DCSEL=4, $9F2C
1873
			return ((fx_y_pixel_position >> 24) & 0x07) | (fx_y_pixel_position & 0x80);
1874
			break;
1875
		case 0x14: // DCSEL=5, $9F29
1876
			return (fx_x_pixel_position >> 8) & 0xff;
1877
			break;
1878
		case 0x15: // DCSEL=4, $9F2A
1879
			return (fx_y_pixel_position >> 8) & 0xff;
1880
			break;
1881
		case 0x16: // DCSEL=5, 0x9F2B
1882
			if (fx_poly_fill_length >= 768) {
1883
				return ((fx_2bit_poly && fx_addr1_mode == 2) ? 0x00 : 0x80);
1884
			}
1885
			if (fx_4bit_mode) {
1886
				if (fx_2bit_poly && fx_addr1_mode == 2) {
1887
					return ((fx_y_pixel_position & 0x00008000) >> 8) |
1888
						((fx_x_pixel_position >> 11) & 0x60) |
1889
						((fx_x_pixel_position >> 14) & 0x10) |
1890
						((fx_poly_fill_length & 0x0007) << 1) |
1891
						((fx_x_pixel_position & 0x00008000) >> 15);
1892
				} else {
1893
					return ((!!(fx_poly_fill_length & 0xfff8)) << 7) |
1894
						((fx_x_pixel_position >> 11) & 0x60) |
1895
						((fx_x_pixel_position >> 14) & 0x10) |
1896
						((fx_poly_fill_length & 0x0007) << 1);
1897
				}
1898
			} else {
1899
				return ((!!(fx_poly_fill_length & 0xfff0)) << 7) |
1900
					((fx_x_pixel_position >> 11) & 0x60) |
1901
					((fx_poly_fill_length & 0x000f) << 1);
1902
			}
1903
			break;
1904
		case 0x17: // DCSEL=5, 0x9F2C
1905
			return ((fx_poly_fill_length & 0x03f8) >> 2);
1906
			break;
1907
		case 0x18: // DCSEL=6, 0x9F29
1908
			return fx_cache[0];
1909
			break;
1910
		case 0x19: // DCSEL=6, 0x9F2A
1911
			return fx_cache[1];
1912
			break;
1913
		case 0x1a: // DCSEL=6, 0x9F2B
1914
			return fx_cache[2];
1915
			break;
1916
		case 0x1b: // DCSEL=6, 0x9F2C
1917
			return fx_cache[3];
1918
			break;
1919
		default:
1920
			break;
1921
	}
1922
1923
	return vera_version_string[reg % 4];
1924
}
1925
1926
//
1927
// Vera: 6502 I/O Interface
1928
//
1929
// if debugOn, read without any side effects (registers & memory unchanged)
1930
1931
static void check_not_readonly(uint8_t reg) {
1932
	bool wrong = false;
1933
	switch(io_dcsel) {
1934
		case 5: {
1935
			switch(reg) {
1936
				case 0x0b:		// DCSEL=5 FX_POLY_FILL_L
1937
				case 0x0c:		// DCSEL=5 FX_POLY_FILL_H
1938
					wrong=true;
1939
					break;
1940
			}
1941
			break;
1942
		}
1943
		case 63: {
1944
			switch(reg) {
1945
				case 0x09:		// DCSEL=63 DC_VER0
1946
				case 0x0a:		// DCSEL=63 DC_VER1
1947
				case 0x0b:		// DCSEL=63 DC_VER2
1948
				case 0x0c:		// DCSEL=63 DC_VER3
1949
					wrong=true;
1950
					break;
1951
			}
1952
			break;
1953
		}
1954
	}
1955
1956
	if(wrong)
1957
		printf("Warning: %04X wrote to read-only VERA register at 9F%02X (DCSEL=%d)\n", opcode_addr, reg+0x20, io_dcsel);
1958
}
1959
1960
static void check_not_writeonly(uint8_t reg) {
1961
	bool wrong = false;
1962
	switch(io_dcsel) {
1963
		case 2: {
1964
			switch(reg) {
1965
				case 0x0a:		// DCSEL=2 FX_TILEBASE
1966
				case 0x0b:		// DCSEL=2 FX_MAPBASE
1967
				case 0x0c:		// DCSEL=2 FX_MULT
1968
					wrong=true;
1969
					break;
1970
			}
1971
			break;
1972
		}
1973
		case 3:
1974
		case 4: {
1975
			switch(reg) {
1976
				case 0x09:		// DCSEL=3/4 FX_X_INCR_L/FX_X_POS_L
1977
				case 0x0a:		// DCSEL=3/4 FX_X_INCR_H/FX_X_POS_H
1978
				case 0x0b:		// DCSEL=3/4 FX_Y_INCR_L/FX_Y_POS_L
1979
				case 0x0c:		// DCSEL=3/4 FX_Y_INCR_H/FX_Y_POS_H
1980
					wrong=true;
1981
					break;
1982
			}
1983
			break;
1984
		}
1985
		case 5: {
1986
			switch(reg) {
1987
				case 0x09:		// DCSEL=5 FX_X_POS_S
1988
				case 0x0a:		// DCSEL=5 FX_Y_POS_S
1989
					wrong=true;
1990
					break;
1991
			}
1992
			break;
1993
		}
1994
		case 6: {
1995
			switch(reg) {
1996
				case 0x0b:		// DCSEL=6 FX_CACHE_H
1997
				case 0x0c:		// DCSEL=6 FX_CACHE_U
1998
					wrong=true;
1999
					break;
2000
			}
2001
			break;
2002
		}
2003
	}
2004
2005
	if(wrong)
2006
		printf("Warning: %04X read from write-only VERA register at 9F%02X (DCSEL=%d)\n", opcode_addr, reg+0x20, io_dcsel);
2007
}
2008
2009
uint8_t video_read(uint8_t reg, bool debugOn) {
2010
	bool ntsc_mode = reg_composer[0] & 2;
2011
	uint16_t scanline = ntsc_mode ? ntsc_scan_pos_y % SCAN_HEIGHT : vga_scan_pos_y;
2012
	if (scanline >= 512) scanline=511;
2013
2014
	check_not_writeonly(reg);
2015
2016
	switch (reg & 0x1F) {
2017
		case 0x00: return io_addr[io_addrsel] & 0xff;
2018
		case 0x01: return (io_addr[io_addrsel] >> 8) & 0xff;
2019
		case 0x02: return (io_addr[io_addrsel] >> 16) | (fx_nibble_bit[io_addrsel] << 1) | (fx_nibble_incr[io_addrsel] << 2) | (io_inc[io_addrsel] << 3);
2020
		case 0x03:
2021
		case 0x04: {
2022
			if (debugOn) {
2023
				return io_rddata[reg - 3];
2024
			}
2025
2026
			bool addr_nibble = fx_nibble_bit[reg - 3];
2027
			uint32_t address = get_and_inc_address(reg - 3, false);
2028
2029
			uint8_t value = io_rddata[reg - 3];
2030
2031
			if (reg == 4 && fx_addr1_mode == 3)
2032
				fx_affine_prefetch();
2033
			else
2034
				io_rddata[reg - 3] = video_space_read(io_addr[reg - 3]);
2035
2036
			if (fx_cache_fill) {
2037
				if (fx_4bit_mode) {
2038
					uint8_t nibble_read = (addr_nibble ? ((value & 0x0f) << 4) : (value & 0xf0));
2039
2040
					if (fx_cache_nibble_index) {
2041
						fx_cache[fx_cache_byte_index] = (fx_cache[fx_cache_byte_index] & 0xf0) | (nibble_read >> 4);
2042
						fx_cache_nibble_index = 0;
2043
						fx_cache_byte_index = ((fx_cache_byte_index + 1) & 0x3);
2044
					} else {
2045
						fx_cache[fx_cache_byte_index] = (fx_cache[fx_cache_byte_index] & 0x0f) | (nibble_read);
2046
						fx_cache_nibble_index = 1;
2047
					}
2048
				} else {
2049
					fx_cache[fx_cache_byte_index] = value;
2050
					if (fx_cache_increment_mode)
2051
						fx_cache_byte_index = (fx_cache_byte_index & 0x2) | ((fx_cache_byte_index + 1) & 0x1);
2052
					else
2053
						fx_cache_byte_index = ((fx_cache_byte_index + 1) & 0x3);
2054
				}
2055
			}
2056
2057
			if (log_video) {
2058
				printf("READ  video_space[$%X] = $%02X\n", address, value);
2059
			}
2060
			return value;
2061
		}
2062
		case 0x05: return (io_dcsel << 1) | io_addrsel;
2063
		case 0x06: return ((irq_line & 0x100) >> 1) | ((scanline & 0x100) >> 2) | (ien & 0xF);
2064
		case 0x07: return isr | (pcm_is_fifo_almost_empty() ? 8 : 0);
2065
		case 0x08: return scanline & 0xFF;
2066
2067
		case 0x09:
2068
		case 0x0A:
2069
		case 0x0B:
2070
		case 0x0C: {
2071
			int i = reg - 0x09 + (io_dcsel << 2);
2072
			if (debugOn) return video_get_dc_value(i);
2073
			switch (i) {
2074
				case 0x00:
2075
				case 0x01:
2076
				case 0x02:
2077
				case 0x03:
2078
				case 0x04:
2079
				case 0x05:
2080
				case 0x06:
2081
				case 0x07:
2082
				case 0x08:
2083
				case 0x16: // DCSEL=5, 0x9F2B
2084
				case 0x17: // DCSEL=5, 0x9F2C
2085
					return video_get_dc_value(i);
2086
					break;
2087
				case 0x18: // DCSEL=6, 0x9F29
2088
					fx_mult_accumulator = 0;
2089
					// fall out of the switch
2090
					break;
2091
				case 0x19: // DCSEL=6, 0x9F2A
2092
					; // <- avoids the error in some compilers about a declaration after a label
2093
					int32_t m_result = (int16_t)((fx_cache[1] << 8) | fx_cache[0]) * (int16_t)((fx_cache[3] << 8) | fx_cache[2]);
2094
					if (fx_subtract)
2095
						fx_mult_accumulator -= m_result;
2096
					else
2097
						fx_mult_accumulator += m_result;
2098
					// fall out of the switch
2099
					break;
2100
				default:
2101
					// The rest of the space is write-only,
2102
					// so reading the values out instead returns the version string.
2103
					// fall out of the switch
2104
					break;
2105
			}
2106
			return vera_version_string[i % 4];
2107
			break;
2108
		}
2109
		case 0x0D:
2110
		case 0x0E:
2111
		case 0x0F:
2112
		case 0x10:
2113
		case 0x11:
2114
		case 0x12:
2115
		case 0x13: return reg_layer[0][reg - 0x0D];
2116
2117
		case 0x14:
2118
		case 0x15:
2119
		case 0x16:
2120
		case 0x17:
2121
		case 0x18:
2122
		case 0x19:
2123
		case 0x1A: return reg_layer[1][reg - 0x14];
2124
2125
		case 0x1B: audio_render(); return pcm_read_ctrl();
2126
		case 0x1C: return pcm_read_rate();
2127
		case 0x1D: return 0;
2128
2129
		case 0x1E:
2130
		case 0x1F: return vera_spi_read(reg & 1);
2131
	}
2132
	return 0;
2133
}
2134
2135
void video_write(uint8_t reg, uint8_t value) {
2136
	// if (reg > 4) {
2137
	// 	printf("ioregisters[0x%02X] = 0x%02X\n", reg, value);
2138
	// }
2139
	//	printf("ioregisters[%d] = $%02X\n", reg, value);
2140
2141
	check_not_readonly(reg);
2142
2143
	switch (reg & 0x1F) {
2144
		case 0x00:
2145
			if (fx_2bit_poly && fx_4bit_mode && fx_addr1_mode == 2 && io_addrsel == 1) {
2146
				fx_2bit_poking = true;
2147
				io_addr[1] = (io_addr[1] & 0x1fffc) | (value & 0x3);
2148
			} else {
2149
				io_addr[io_addrsel] = (io_addr[io_addrsel] & 0x1ff00) | value;
2150
				if (fx_16bit_hop && io_addrsel == 1)
2151
					fx_16bit_hop_align = value & 3;
2152
			}
2153
			io_rddata[io_addrsel] = video_space_read(io_addr[io_addrsel]);
2154
			break;
2155
		case 0x01:
2156
			io_addr[io_addrsel] = (io_addr[io_addrsel] & 0x100ff) | (value << 8);
2157
			io_rddata[io_addrsel] = video_space_read(io_addr[io_addrsel]);
2158
			break;
2159
		case 0x02:
2160
			io_addr[io_addrsel] = (io_addr[io_addrsel] & 0x0ffff) | ((value & 0x1) << 16);
2161
			fx_nibble_bit[io_addrsel] = (value >> 1) & 0x1;
2162
			fx_nibble_incr[io_addrsel] = (value >> 2) & 0x1;
2163
			io_inc[io_addrsel]  = value >> 3;
2164
			io_rddata[io_addrsel] = video_space_read(io_addr[io_addrsel]);
2165
			break;
2166
		case 0x03:
2167
		case 0x04: {
2168
			if (fx_2bit_poking && fx_addr1_mode) {
2169
				fx_2bit_poking = false;
2170
				uint8_t mask = value >> 6;
2171
				switch (mask) {
2172
					case 0x00:
2173
						video_ram[io_addr[1] & 0x1FFFF] = (fx_cache[fx_cache_byte_index] & 0xc0) | (io_rddata[1] & 0x3f);
2174
						break;
2175
					case 0x01:
2176
						video_ram[io_addr[1] & 0x1FFFF] = (fx_cache[fx_cache_byte_index] & 0x30) | (io_rddata[1] & 0xcf);
2177
						break;
2178
					case 0x02:
2179
						video_ram[io_addr[1] & 0x1FFFF] = (fx_cache[fx_cache_byte_index] & 0x0c) | (io_rddata[1] & 0xf3);
2180
						break;
2181
					case 0x03:
2182
						video_ram[io_addr[1] & 0x1FFFF] = (fx_cache[fx_cache_byte_index] & 0x03) | (io_rddata[1] & 0xfc);
2183
						break;
2184
				}
2185
				break; // break out of the enclosing switch statement early, too
2186
			}
2187
2188
			if (enable_midline)
2189
				video_step(MHZ, 0, true); // potential midline raster effect
2190
			bool nibble = fx_nibble_bit[reg - 3];
2191
			uint32_t address = get_and_inc_address(reg - 3, true);
2192
			if (log_video) {
2193
				printf("WRITE video_space[$%X] = $%02X\n", address, value);
2194
			}
2195
2196
			uint8_t wrdata_to_use;
2197
			uint8_t ram_wrdata[4];
2198
			uint8_t nibble_mask[4];
2199
			uint8_t cache_to_use[4];
2200
2201
			if (fx_multiplier) {
2202
				int32_t m_result = (int16_t)((fx_cache[1] << 8) | fx_cache[0]) * (int16_t)((fx_cache[3] << 8) | fx_cache[2]);
2203
				if (fx_subtract)
2204
					m_result = fx_mult_accumulator - m_result;
2205
				else
2206
					m_result = fx_mult_accumulator + m_result;
2207
				cache_to_use[0] = (m_result) & 0xff;
2208
				cache_to_use[1] = (m_result >> 8) & 0xff;
2209
				cache_to_use[2] = (m_result >> 16) & 0xff;
2210
				cache_to_use[3] = (m_result >> 24) & 0xff;
2211
			} else {
2212
				memcpy(cache_to_use, fx_cache, sizeof(fx_cache));
2213
			}
2214
2215
			if (fx_cache_byte_cycling) {
2216
				wrdata_to_use = fx_cache[fx_cache_byte_index];
2217
			} else {
2218
				wrdata_to_use = value;
2219
			}
2220
2221
			if (fx_cache_write && !fx_cache_byte_cycling) {
2222
				ram_wrdata[0] = cache_to_use[0];
2223
				ram_wrdata[1] = cache_to_use[1];
2224
				ram_wrdata[2] = cache_to_use[2];
2225
				ram_wrdata[3] = cache_to_use[3];
2226
			} else {
2227
				ram_wrdata[0] = wrdata_to_use;
2228
				ram_wrdata[1] = wrdata_to_use;
2229
				ram_wrdata[2] = wrdata_to_use;
2230
				ram_wrdata[3] = wrdata_to_use;
2231
			}
2232
2233
			if (fx_cache_write) {
2234
				address &= 0x1fffc;
2235
				if (fx_trans_writes) {
2236
					if (fx_4bit_mode) {
2237
						nibble_mask[0] = (((ram_wrdata[0] & 0xf0) == 0) << 1) | ((ram_wrdata[0] & 0x0f) == 0);
2238
						nibble_mask[1] = (((ram_wrdata[1] & 0xf0) == 0) << 1) | ((ram_wrdata[1] & 0x0f) == 0);
2239
						nibble_mask[2] = (((ram_wrdata[2] & 0xf0) == 0) << 1) | ((ram_wrdata[2] & 0x0f) == 0);
2240
						nibble_mask[3] = (((ram_wrdata[3] & 0xf0) == 0) << 1) | ((ram_wrdata[3] & 0x0f) == 0);
2241
					} else {
2242
						nibble_mask[0] = (ram_wrdata[0] != 0) ? 0 : 3;
2243
						nibble_mask[1] = (ram_wrdata[1] != 0) ? 0 : 3;
2244
						nibble_mask[2] = (ram_wrdata[2] != 0) ? 0 : 3;
2245
						nibble_mask[3] = (ram_wrdata[3] != 0) ? 0 : 3;
2246
					}
2247
				} else {
2248
					nibble_mask[0] = value & 0x3;
2249
					nibble_mask[1] = (value >> 2) & 0x3;
2250
					nibble_mask[2] = (value >> 4) & 0x3;
2251
					nibble_mask[3] = (value >> 6) & 0x3;
2252
				}
2253
2254
				fx_vram_cache_write(address+0, ram_wrdata[0], nibble_mask[0]);
2255
				fx_vram_cache_write(address+1, ram_wrdata[1], nibble_mask[1]);
2256
				fx_vram_cache_write(address+2, ram_wrdata[2], nibble_mask[2]);
2257
				fx_vram_cache_write(address+3, ram_wrdata[3], nibble_mask[3]);
2258
			} else {
2259
				fx_video_space_write(address, nibble, wrdata_to_use); // Normal write
2260
			}
2261
2262
			io_rddata[reg - 3] = video_space_read(io_addr[reg - 3]);
2263
			break;
2264
		}
2265
		case 0x05:
2266
			if (value & 0x80) {
2267
				video_reset();
2268
			}
2269
			io_dcsel = (value >> 1) & 0x3f;
2270
			io_addrsel = value & 1;
2271
			break;
2272
		case 0x06:
2273
			irq_line = (irq_line & 0xFF) | ((value >> 7) << 8);
2274
			ien = value & 0xF;
2275
			break;
2276
		case 0x07:
2277
			isr &= value ^ 0xff;
2278
			break;
2279
		case 0x08:
2280
			irq_line = (irq_line & 0x100) | value;
2281
			break;
2282
2283
		case 0x09:
2284
		case 0x0A:
2285
		case 0x0B:
2286
		case 0x0C: {
2287
			video_step(MHZ, 0, true); // potential midline raster effect
2288
			int i = reg - 0x09 + (io_dcsel << 2);
2289
			if (i == 0) {
2290
				// if progressive mode field goes from 0 to 1
2291
				// or if mode goes from vga to something else with
2292
				// progressive mode on, clear the framebuffer
2293
				if (((reg_composer[0] & 0x8) == 0 && (value & 0x8)) ||
2294
					((reg_composer[0] & 0x3) == 1 && (value & 0x3) > 1 && (value & 0x8))) {
2295
					memset(framebuffer, 0x00, SCREEN_WIDTH * SCREEN_HEIGHT * 4);
2296
				}
2297
2298
				// interlace field bit is read-only
2299
				reg_composer[0] = (reg_composer[0] & ~0x7f) | (value & 0x7f);
2300
				video_palette.dirty = true;
2301
			} else {
2302
				reg_composer[i] = value;
2303
			}
2304
2305
			switch (i) {
2306
				case 0x08: // DCSEL=2, $9F29
2307
					fx_addr1_mode = value & 0x03;
2308
					fx_4bit_mode = (value & 0x04) >> 2;
2309
					fx_16bit_hop = (value & 0x08) >> 3;
2310
					fx_cache_byte_cycling = (value & 0x10) >> 4;
2311
					fx_cache_fill = (value & 0x20) >> 5;
2312
					fx_cache_write = (value & 0x40) >> 6;
2313
					fx_trans_writes = (value & 0x80) >> 7;
2314
					break;
2315
				case 0x09: // DCSEL=2, $9F2A
2316
					fx_affine_tile_base = (value & 0xfc) << 9;
2317
					fx_affine_clip = (value & 0x02) >> 1;
2318
					fx_2bit_poly = (value & 0x01);
2319
					break;
2320
				case 0x0a: // DCSEL=2, $9F2B
2321
					fx_affine_map_base = (value & 0xfc) << 9;
2322
					fx_affine_map_size = 2 << ((value & 0x03) << 1);
2323
					break;
2324
				case 0x0b: // DCSEL=2, $9F2C
2325
					fx_cache_increment_mode = value & 0x01;
2326
					fx_cache_nibble_index = (value & 0x02) >> 1;
2327
					fx_cache_byte_index = (value & 0x0c) >> 2;
2328
					fx_multiplier = (value & 0x10) >> 4;
2329
					fx_subtract = (value & 0x20) >> 5;
2330
					if (value & 0x40) { // accumulate
2331
						int32_t m_result = (int16_t)((fx_cache[1] << 8) | fx_cache[0]) * (int16_t)((fx_cache[3] << 8) | fx_cache[2]);
2332
						if (fx_subtract)
2333
							fx_mult_accumulator -= m_result;
2334
						else
2335
							fx_mult_accumulator += m_result;
2336
					}
2337
					if (value & 0x80) { // reset accumulator
2338
						fx_mult_accumulator = 0;
2339
					}
2340
					break;
2341
				case 0x0c: // DCSEL=3, $9F29
2342
					fx_x_pixel_increment = ((((reg_composer[0x0d] & 0x7f) << 15) + (reg_composer[0x0c] << 7)) // base value
2343
						| ((reg_composer[0x0d] & 0x40) ? 0xffc00000 : 0)) // sign extend if negative
2344
						<< 5*(!!(reg_composer[0x0d] & 0x80)); // multiply by 32 if flag set
2345
					break;
2346
				case 0x0d: // DCSEL=3, $9F2A
2347
					fx_x_pixel_increment = ((((reg_composer[0x0d] & 0x7f) << 15) + (reg_composer[0x0c] << 7)) // base value
2348
						| ((reg_composer[0x0d] & 0x40) ? 0xffc00000 : 0)) // sign extend if negative
2349
						<< 5*(!!(reg_composer[0x0d] & 0x80)); // multiply by 32 if flag set
2350
					if (fx_addr1_mode == 1 || fx_addr1_mode == 2) {
2351
						// Reset subpixel to 0.5
2352
						fx_x_pixel_position = (fx_x_pixel_position & 0x07ff0000) | 0x00008000;
2353
					}
2354
					break;
2355
				case 0x0e: // DCSEL=3, $9F2B
2356
					fx_y_pixel_increment = ((((reg_composer[0x0f] & 0x7f) << 15) + (reg_composer[0x0e] << 7)) // base value
2357
						| ((reg_composer[0x0f] & 0x40) ? 0xffc00000 : 0)) // sign extend if negative
2358
						<< 5*(!!(reg_composer[0x0f] & 0x80)); // multiply by 32 if flag set
2359
					break;
2360
				case 0x0f: // DCSEL=3, $9F2C
2361
					fx_y_pixel_increment = ((((reg_composer[0x0f] & 0x7f) << 15) + (reg_composer[0x0e] << 7)) // base value
2362
						| ((reg_composer[0x0f] & 0x40) ? 0xffc00000 : 0)) // sign extend if negative
2363
						<< 5*(!!(reg_composer[0x0f] & 0x80)); // multiply by 32 if flag set
2364
					if (fx_addr1_mode == 1 || fx_addr1_mode == 2) {
2365
						// Reset subpixel to 0.5
2366
						fx_y_pixel_position = (fx_y_pixel_position & 0x07ff0000) | 0x00008000;
2367
					}
2368
					break;
2369
				case 0x10: // DCSEL=4, $9F29
2370
					fx_x_pixel_position = (fx_x_pixel_position & 0x0700ff80) | (value << 16);
2371
					fx_affine_prefetch();
2372
					break;
2373
				case 0x11: // DCSEL=4, $9F2A
2374
					fx_x_pixel_position = (fx_x_pixel_position & 0x00ffff00) | ((value & 0x7) << 24) | (value & 0x80);
2375
					fx_affine_prefetch();
2376
					break;
2377
				case 0x12: // DCSEL=4, $9F2B
2378
					fx_y_pixel_position = (fx_y_pixel_position & 0x0700ff80) | (value << 16);
2379
					fx_affine_prefetch();
2380
					break;
2381
				case 0x13: // DCSEL=4, $9F2C
2382
					fx_y_pixel_position = (fx_y_pixel_position & 0x00ffff00) | ((value & 0x7) << 24) | (value & 0x80);
2383
					fx_affine_prefetch();
2384
					break;
2385
				case 0x14: // DCSEL=5, $9F29
2386
					fx_x_pixel_position = (fx_x_pixel_position & 0x07ff0080) | (value << 8);
2387
					break;
2388
				case 0x15: // DCSEL=5, $9F2A
2389
					fx_y_pixel_position = (fx_y_pixel_position & 0x07ff0080) | (value << 8);
2390
					break;
2391
				case 0x18: // DCSEL=6, $9F29
2392
					fx_cache[0] = value;
2393
					break;
2394
				case 0x19: // DCSEL=6, $9F2A
2395
					fx_cache[1] = value;
2396
					break;
2397
				case 0x1a: // DCSEL=6, $9F2B
2398
					fx_cache[2] = value;
2399
					break;
2400
				case 0x1b: // DCSEL=6, $9F2C
2401
					fx_cache[3] = value;
2402
					break;
2403
			}
2404
			break;
2405
		}
2406
2407
		case 0x0D:
2408
		case 0x0E:
2409
		case 0x0F:
2410
		case 0x10:
2411
		case 0x11:
2412
		case 0x12:
2413
		case 0x13:
2414
			video_step(MHZ, 0, true); // potential midline raster effect
2415
			reg_layer[0][reg - 0x0D] = value;
2416
			refresh_layer_properties(0);
2417
			break;
2418
2419
		case 0x14:
2420
		case 0x15:
2421
		case 0x16:
2422
		case 0x17:
2423
		case 0x18:
2424
		case 0x19:
2425
		case 0x1A:
2426
			video_step(MHZ, 0, true); // potential midline raster effect
2427
			reg_layer[1][reg - 0x14] = value;
2428
			refresh_layer_properties(1);
2429
			break;
2430
2431
		case 0x1B: audio_render(); pcm_write_ctrl(value); break;
2432
		case 0x1C: audio_render(); pcm_write_rate(value); break;
2433
		case 0x1D: audio_render(); pcm_write_fifo(value); break;
2434
2435
		case 0x1E:
2436
		case 0x1F:
2437
			vera_spi_write(reg & 1, value);
2438
			break;
2439
	}
2440
}
2441
2442
void
2443
video_update_title(const char* window_title)
2444
{
2445
	SDL_SetWindowTitle(window, window_title);
2446
}
2447
2448
bool video_is_tilemap_address(int addr)
2449
{
2450
	for (int l = 0; l < 2; ++l) {
2451
		struct video_layer_properties *props = &layer_properties[l];
2452
		if (addr < props->map_base) {
2453
			continue;
2454
		}
2455
		if (addr >= props->map_base + (2 << (props->mapw_log2 + props->maph_log2))) {
2456
			continue;
2457
		}
2458
2459
		return true;
2460
	}
2461
	return false;
2462
}
2463
2464
bool video_is_tiledata_address(int addr)
2465
{
2466
	for (int l = 0; l < 2; ++l) {
2467
		struct video_layer_properties *props = &layer_properties[l];
2468
		if (addr < props->tile_base) {
2469
			continue;
2470
		}
2471
		int tile_size = props->tilew * props->tileh * props->bits_per_pixel / 8;
2472
		if (addr >= props->tile_base + tile_size * (props->bits_per_pixel == 1 ? 256 : 1024)) {
2473
			continue;
2474
		}
2475
2476
		return true;
2477
	}
2478
	return false;
2479
}
2480
2481
bool video_is_special_address(int addr)
2482
{
2483
	return addr >= 0x1F9C0;
2484
}
2485
2486
void
2487
stop6502(uint16_t address, uint8_t bank) {
2488
	if (debugger_enabled) {
2489
		DEBUGBreakToDebugger();
2490
	} else if (testbench) {
2491
		printf("STP\n");
2492
        fflush(stdout);
2493
	} else {
2494
		int return_btn;
2495
		char error_message[80];
2496
		const SDL_MessageBoxButtonData btns[2] = {
2497
			{SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Reset Machine"},
2498
			{SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, -1, "Ignore"}
2499
		};
2500
		const SDL_MessageBoxData msg_box = {
2501
			SDL_MESSAGEBOX_ERROR, window, "Error", error_message,
2502
			2, btns, NULL
2503
		};
2504
2505
		if (is_gen2) {
2506
			sprintf(error_message, "Encountered stop instruction at address $%06X. CPU cannot continue.", (regs.k << 16) | address);
2507
		} else {
2508
			sprintf(error_message, "Encountered stop instruction at address $%04X. CPU cannot continue.", address);
2509
		}
2510
		if (SDL_ShowMessageBox(&msg_box, &return_btn) == 0 && return_btn == 0) {
2511
			machine_reset();
2512
		};
2513
	}
2514
}