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