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