OXYGENHUB
# 0adc2e74f5cf x16-emulator / src / video.c
69.2 KiB · C · last changed chore(): initial commit 15 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
	const struct video_layer_properties *props = &prev_layer_properties[1][layer];
976
//	const struct video_layer_properties *props0 = &prev_layer_properties[0][layer];
977
978
	int yy = y % props->tileh;
979
	// additional bytes to reach the correct line of the tile
980
	uint32_t y_add = (yy * props->tilew * props->bits_per_pixel) >> 3;
981
982
	// Render tile line.
983
	for (int x = 0; x < SCREEN_WIDTH; x++) {
984
		int xx = x % props->tilew;
985
986
		// extract all information from the map
987
		uint8_t palette_offset = reg_layer[layer][4] & 0xf;
988
989
		// additional bytes to reach the correct column of the tile
990
		uint16_t x_add = (xx * props->bits_per_pixel) >> 3;
991
		uint32_t tile_offset = y_add + x_add;
992
		uint8_t s = video_space_read(props->tile_base + tile_offset);
993
994
		// convert tile byte to indexed color
995
		uint8_t col_index = (s >> (props->first_color_pos - ((xx & props->color_fields_max) << props->color_depth))) & props->color_mask;
996
997
		// Apply Palette Offset
998
		if (col_index > 0 && col_index < 16) {
999
			col_index += palette_offset << 4;
1000
			if (props->text_mode_256c) {
1001
				col_index |= 0x80;
1002
			}
1003
		}
1004
		layer_line[layer][x] = col_index;
1005
	}
1006
}
1007
1008
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)
1009
{
1010
	uint8_t col_index = 0;
1011
	switch (spr_zindex) {
1012
		case 3:
1013
			col_index = spr_col_index ? spr_col_index : (l2_col_index ? l2_col_index : l1_col_index);
1014
			break;
1015
		case 2:
1016
			col_index = l2_col_index ? l2_col_index : (spr_col_index ? spr_col_index : l1_col_index);
1017
			break;
1018
		case 1:
1019
			col_index = l2_col_index ? l2_col_index : (l1_col_index ? l1_col_index : spr_col_index);
1020
			break;
1021
		case 0:
1022
			col_index = l2_col_index ? l2_col_index : l1_col_index;
1023
			break;
1024
	}
1025
	return col_index;
1026
}
1027
1028
static void
1029
render_line(uint16_t y, float scan_pos_x)
1030
{
1031
	static uint16_t y_prev;
1032
	static uint16_t s_pos_x_p;
1033
	static uint32_t eff_y_fp; // 16.16 fixed point
1034
	static uint32_t eff_x_fp; // 16.16 fixed point
1035
1036
	static uint8_t col_line[SCREEN_WIDTH];
1037
1038
	uint8_t dc_video = reg_composer[0];
1039
	uint16_t vstart = reg_composer[6] << 1;
1040
1041
	if (y != y_prev) {
1042
		y_prev = y;
1043
		s_pos_x_p = 0;
1044
1045
		// Copy the composer array to 2-line history buffer
1046
		// so that the raster effects that happen on a delay take effect
1047
		// at exactly the right time
1048
1049
		// This simulates different effects happening at render,
1050
		// render but delayed until the next line, or applied mid-line
1051
		// at scan-out
1052
1053
		memcpy(prev_reg_composer[1], prev_reg_composer[0], sizeof(*reg_composer) * COMPOSER_SLOTS);
1054
		memcpy(prev_reg_composer[0], reg_composer, sizeof(*reg_composer) * COMPOSER_SLOTS);
1055
1056
		// Same with the layer properties
1057
1058
		memcpy(prev_layer_properties[1], prev_layer_properties[0], sizeof(*layer_properties) * NUM_LAYERS);
1059
		memcpy(prev_layer_properties[0], layer_properties, sizeof(*layer_properties) * NUM_LAYERS);
1060
1061
		if ((dc_video & 3) > 1) { // 480i or 240p
1062
			if ((y >> 1) == 0) {
1063
				eff_y_fp = y*(prev_reg_composer[1][2] << 9);
1064
			} else if ((y & 0xfffe) > vstart) {
1065
				eff_y_fp += (prev_reg_composer[1][2] << 10);
1066
			}
1067
		} else {
1068
			if (y == 0) {
1069
				eff_y_fp = 0;
1070
			} else if (y > vstart) {
1071
				eff_y_fp += (prev_reg_composer[1][2] << 9);
1072
			}
1073
		}
1074
	}
1075
1076
	if ((dc_video & 8) && (dc_video & 3) > 1) { // progressive NTSC/RGB mode
1077
		y &= 0xfffe;
1078
	}
1079
1080
	// refresh palette for next entry
1081
	if (video_palette.dirty) {
1082
		refresh_palette();
1083
	}
1084
1085
	if (y >= SCREEN_HEIGHT) {
1086
		return;
1087
	}
1088
1089
	uint16_t s_pos_x = round(scan_pos_x);
1090
	if (s_pos_x > SCREEN_WIDTH) {
1091
		s_pos_x = SCREEN_WIDTH;
1092
	}
1093
1094
	if (s_pos_x_p == 0) {
1095
		eff_x_fp = 0;
1096
	}
1097
1098
	uint8_t out_mode = reg_composer[0] & 3;
1099
1100
	uint8_t border_color = reg_composer[3];
1101
	uint16_t hstart = reg_composer[4] << 2;
1102
	uint16_t hstop = reg_composer[5] << 2;
1103
	uint16_t vstop = reg_composer[7] << 1;
1104
1105
	uint16_t eff_y = (eff_y_fp >> 16);
1106
	if (eff_y >= 480) eff_y = 480 - (y & 1);
1107
1108
	layer_line_enable[0] = dc_video & 0x10;
1109
	layer_line_enable[1] = dc_video & 0x20;
1110
	sprite_line_enable   = dc_video & 0x40;
1111
1112
	// clear layer_line if layer gets disabled
1113
	for (uint8_t layer = 0; layer < 2; layer++) {
1114
		if (!layer_line_enable[layer] && old_layer_line_enable[layer]) {
1115
			for (uint16_t i = s_pos_x_p; i < SCREEN_WIDTH; i++) {
1116
				layer_line[layer][i] = 0;
1117
			}
1118
		}
1119
		if (s_pos_x_p == 0)
1120
			old_layer_line_enable[layer] = layer_line_enable[layer];
1121
	}
1122
1123
	// clear sprite_line if sprites get disabled
1124
	if (!sprite_line_enable && old_sprite_line_enable) {
1125
		for (uint16_t i = s_pos_x_p; i < SCREEN_WIDTH; i++) {
1126
			sprite_line_col[i] = 0;
1127
			sprite_line_z[i] = 0;
1128
			sprite_line_mask[i] = 0;
1129
		}
1130
	}
1131
1132
	if (s_pos_x_p == 0)
1133
		old_sprite_line_enable = sprite_line_enable;
1134
1135
1136
1137
	if (sprite_line_enable) {
1138
		render_sprite_line(eff_y);
1139
	}
1140
1141
	if (warp_mode && (frame_count & 63)) {
1142
		// sprites were needed for the collision IRQ, but we can skip
1143
		// everything else if we're in warp mode, most of the time
1144
		return;
1145
	}
1146
1147
	if (layer_line_enable[0]) {
1148
		if (prev_layer_properties[1][0].text_mode) {
1149
			render_layer_line_text(0, eff_y);
1150
		} else if (prev_layer_properties[1][0].bitmap_mode) {
1151
			render_layer_line_bitmap(0, eff_y);
1152
		} else {
1153
			render_layer_line_tile(0, eff_y);
1154
		}
1155
	}
1156
	if (layer_line_enable[1]) {
1157
		if (prev_layer_properties[1][1].text_mode) {
1158
			render_layer_line_text(1, eff_y);
1159
		} else if (prev_layer_properties[1][1].bitmap_mode) {
1160
			render_layer_line_bitmap(1, eff_y);
1161
		} else {
1162
			render_layer_line_tile(1, eff_y);
1163
		}
1164
	}
1165
1166
	// If video output is enabled, calculate color indices for line.
1167
	if (out_mode != 0) {
1168
		// Add border after if required.
1169
		if (y < vstart || y >= vstop) {
1170
			uint32_t border_fill = border_color;
1171
			border_fill = border_fill | (border_fill << 8);
1172
			border_fill = border_fill | (border_fill << 16);
1173
			memset(col_line, border_fill, SCREEN_WIDTH);
1174
		} else {
1175
			hstart = hstart < 640 ? hstart : 640;
1176
			hstop = hstop < 640 ? hstop : 640;
1177
1178
			for (uint16_t x = s_pos_x_p; x < hstart && x < s_pos_x; ++x) {
1179
				col_line[x] = border_color;
1180
			}
1181
1182
			const uint32_t scale = reg_composer[1];
1183
			for (uint16_t x = MAX(hstart, s_pos_x_p); x < hstop && x < s_pos_x; ++x) {
1184
				uint16_t eff_x = eff_x_fp >> 16;
1185
				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;
1186
				eff_x_fp += (scale << 9);
1187
			}
1188
			for (uint16_t x = hstop; x < s_pos_x; ++x) {
1189
				col_line[x] = border_color;
1190
			}
1191
		}
1192
	}
1193
1194
	// Look up all color indices.
1195
	uint32_t* framebuffer4_begin = ((uint32_t*)framebuffer) + (y * SCREEN_WIDTH) + s_pos_x_p;
1196
	{
1197
		uint32_t* framebuffer4 = framebuffer4_begin;
1198
		for (uint16_t x = s_pos_x_p; x < s_pos_x; x++) {
1199
			*framebuffer4++ = video_palette.entries[col_line[x]];
1200
		}
1201
	}
1202
1203
	// NTSC overscan
1204
	if (out_mode == 2) {
1205
		uint32_t* framebuffer4 = framebuffer4_begin;
1206
		for (uint16_t x = s_pos_x_p; x < s_pos_x; x++)
1207
		{
1208
			if (x < SCREEN_WIDTH * TITLE_SAFE_X ||
1209
				x > SCREEN_WIDTH * (1 - TITLE_SAFE_X) ||
1210
				y < SCREEN_HEIGHT * TITLE_SAFE_Y ||
1211
				y > SCREEN_HEIGHT * (1 - TITLE_SAFE_Y)) {
1212
1213
				// Divide RGB elements by 4.
1214
				*framebuffer4 &= 0x00fcfcfc;
1215
				*framebuffer4 >>= 2;
1216
			}
1217
			framebuffer4++;
1218
		}
1219
	}
1220
1221
	s_pos_x_p = s_pos_x;
1222
}
1223
1224
static void
1225
update_isr_and_coll(uint16_t y, uint16_t compare)
1226
{
1227
	if (y == SCREEN_HEIGHT) {
1228
		if (sprite_line_collisions != 0) {
1229
			isr |= 4;
1230
		}
1231
		isr = (isr & 0xf) | sprite_line_collisions;
1232
		sprite_line_collisions = 0;
1233
		isr |= 1; // VSYNC IRQ
1234
	}
1235
	if (y == compare) { // LINE IRQ
1236
		isr |= 2;
1237
	}
1238
}
1239
1240
bool
1241
video_step(float mhz, float steps, bool midline)
1242
{
1243
	uint16_t y = 0;
1244
	bool ntsc_mode = reg_composer[0] & 2;
1245
	bool new_frame = false;
1246
	vga_scan_pos_x += PIXEL_FREQ * steps / mhz;
1247
	if (vga_scan_pos_x > VGA_SCAN_WIDTH) {
1248
		vga_scan_pos_x -= VGA_SCAN_WIDTH;
1249
		if (!ntsc_mode) {
1250
			render_line(vga_scan_pos_y - VGA_Y_OFFSET, VGA_SCAN_WIDTH);
1251
		}
1252
		vga_scan_pos_y++;
1253
		if (vga_scan_pos_y == SCAN_HEIGHT) {
1254
			vga_scan_pos_y = 0;
1255
			if (!ntsc_mode) {
1256
				new_frame = true;
1257
				frame_count++;
1258
			}
1259
		}
1260
		if (!ntsc_mode) {
1261
			update_isr_and_coll(vga_scan_pos_y - VGA_Y_OFFSET, irq_line);
1262
		}
1263
	} else if (midline) {
1264
		if (!ntsc_mode) {
1265
			render_line(vga_scan_pos_y - VGA_Y_OFFSET, vga_scan_pos_x);
1266
		}
1267
	}
1268
	ntsc_half_cnt += PIXEL_FREQ * steps / mhz;
1269
	if (ntsc_half_cnt > NTSC_HALF_SCAN_WIDTH) {
1270
		ntsc_half_cnt -= NTSC_HALF_SCAN_WIDTH;
1271
		if (ntsc_mode) {
1272
			if (ntsc_scan_pos_y < SCAN_HEIGHT) {
1273
				y = ntsc_scan_pos_y - NTSC_Y_OFFSET_LOW;
1274
				if ((y & 1) == 0) {
1275
					render_line(y, NTSC_HALF_SCAN_WIDTH);
1276
				}
1277
			} else {
1278
				y = ntsc_scan_pos_y - NTSC_Y_OFFSET_HIGH;
1279
				if ((y & 1) == 0) {
1280
					render_line(y | 1, NTSC_HALF_SCAN_WIDTH);
1281
				}
1282
			}
1283
		}
1284
		ntsc_scan_pos_y++;
1285
		if (ntsc_scan_pos_y == SCAN_HEIGHT) {
1286
			reg_composer[0] |= 0x80;
1287
			if (ntsc_mode) {
1288
				new_frame = true;
1289
				frame_count++;
1290
			}
1291
		}
1292
		if (ntsc_scan_pos_y == SCAN_HEIGHT*2) {
1293
			reg_composer[0] &= ~0x80;
1294
			ntsc_scan_pos_y = 0;
1295
			if (ntsc_mode) {
1296
				new_frame = true;
1297
				frame_count++;
1298
			}
1299
		}
1300
		if (ntsc_mode) {
1301
			// this is correct enough for even screen heights
1302
			if (ntsc_scan_pos_y < SCAN_HEIGHT) {
1303
				update_isr_and_coll(ntsc_scan_pos_y - NTSC_Y_OFFSET_LOW, irq_line & ~1);
1304
			} else {
1305
				update_isr_and_coll(ntsc_scan_pos_y - NTSC_Y_OFFSET_HIGH, irq_line & ~1);
1306
			}
1307
		}
1308
	} else if (midline) {
1309
		if (ntsc_mode) {
1310
			if (ntsc_scan_pos_y < SCAN_HEIGHT) {
1311
				y = ntsc_scan_pos_y - NTSC_Y_OFFSET_LOW;
1312
				if ((y & 1) == 0) {
1313
					render_line(y, ntsc_half_cnt);
1314
				}
1315
			} else {
1316
				y = ntsc_scan_pos_y - NTSC_Y_OFFSET_HIGH;
1317
				if ((y & 1) == 0) {
1318
					render_line(y | 1, ntsc_half_cnt);
1319
				}
1320
			}
1321
		}
1322
	}
1323
1324
	return new_frame;
1325
}
1326
1327
bool
1328
video_get_irq_out()
1329
{
1330
	uint8_t tmp_isr = isr | (pcm_is_fifo_almost_empty() ? 8 : 0);
1331
	return (tmp_isr & ien) != 0;
1332
}
1333
1334
//
1335
// saves the video memory and register content into a file
1336
//
1337
1338
void
1339
video_save(SDL_RWops *f)
1340
{
1341
	SDL_RWwrite(f, &video_ram[0], sizeof(uint8_t), sizeof(video_ram));
1342
	SDL_RWwrite(f, &reg_composer[0], sizeof(uint8_t), sizeof(reg_composer));
1343
	SDL_RWwrite(f, &palette[0], sizeof(uint8_t), sizeof(palette));
1344
	SDL_RWwrite(f, &reg_layer[0][0], sizeof(uint8_t), sizeof(reg_layer));
1345
	SDL_RWwrite(f, &sprite_data[0], sizeof(uint8_t), sizeof(sprite_data));
1346
}
1347
1348
bool
1349
video_update()
1350
{
1351
	static bool cmd_down = false;
1352
	static bool alt_down = false;
1353
	bool mouse_changed = false;
1354
1355
	// for activity LED, overlay red 8x4 square into top right of framebuffer
1356
	// for progressive modes, draw LED only on even scanlines
1357
	for (int y = 0; y < 4; y+=1+!!((reg_composer[0] & 0x0b) > 0x09)) {
1358
		for (int x = SCREEN_WIDTH - 8; x < SCREEN_WIDTH; x++) {
1359
			uint8_t b = framebuffer[(y * SCREEN_WIDTH + x) * 4 + 0];
1360
			uint8_t g = framebuffer[(y * SCREEN_WIDTH + x) * 4 + 1];
1361
			uint8_t r = framebuffer[(y * SCREEN_WIDTH + x) * 4 + 2];
1362
			r = (uint32_t)r * (255 - activity_led) / 255 + activity_led;
1363
			g = (uint32_t)g * (255 - activity_led) / 255;
1364
			b = (uint32_t)b * (255 - activity_led) / 255;
1365
			framebuffer[(y * SCREEN_WIDTH + x) * 4 + 0] = b;
1366
			framebuffer[(y * SCREEN_WIDTH + x) * 4 + 1] = g;
1367
			framebuffer[(y * SCREEN_WIDTH + x) * 4 + 2] = r;
1368
			framebuffer[(y * SCREEN_WIDTH + x) * 4 + 3] = 0x00;
1369
		}
1370
	}
1371
1372
	SDL_UpdateTexture(sdlTexture, NULL, framebuffer, SCREEN_WIDTH * 4);
1373
1374
	if (record_gif > RECORD_GIF_PAUSED) {
1375
		if(!GifWriteFrame(&gif_writer, framebuffer, SCREEN_WIDTH, SCREEN_HEIGHT, 2, 8, false)) {
1376
			// if that failed, stop recording
1377
			GifEnd(&gif_writer);
1378
			record_gif = RECORD_GIF_DISABLED;
1379
			printf("Unexpected end of recording.\n");
1380
		}
1381
		if (record_gif == RECORD_GIF_SINGLE) { // if single-shot stop recording
1382
			record_gif = RECORD_GIF_PAUSED;  // need to close in video_end()
1383
		}
1384
	}
1385
1386
	SDL_RenderClear(renderer);
1387
	SDL_RenderCopy(renderer, sdlTexture, NULL, NULL);
1388
1389
	if (debugger_enabled && showDebugOnRender != 0) {
1390
		DEBUGRenderDisplay(SCREEN_WIDTH, SCREEN_HEIGHT);
1391
		SDL_RenderPresent(renderer);
1392
		return true;
1393
	}
1394
1395
	SDL_RenderPresent(renderer);
1396
1397
	SDL_Event event;
1398
	while (SDL_PollEvent(&event)) {
1399
		if (event.type == SDL_QUIT) {
1400
			return false;
1401
		}
1402
		if (event.type == SDL_KEYDOWN) {
1403
			bool consumed = false;
1404
			if (cmd_down && !(disable_emu_cmd_keys || mouse_grabbed)) {
1405
				if (event.key.keysym.sym == SDLK_s) {
1406
					machine_dump("user keyboard request");
1407
					consumed = true;
1408
				} else if (event.key.keysym.sym == SDLK_r) {
1409
					machine_reset();
1410
					consumed = true;
1411
				} else if (event.key.keysym.sym == SDLK_BACKSPACE) {
1412
					machine_nmi();
1413
					consumed = true;
1414
				} else if (event.key.keysym.sym == SDLK_v) {
1415
					machine_paste(SDL_GetClipboardText(), true);
1416
					consumed = true;
1417
				} else if (event.key.keysym.sym == SDLK_f || event.key.keysym.sym == SDLK_RETURN) {
1418
					is_fullscreen = !is_fullscreen;
1419
					SDL_SetWindowFullscreen(window, is_fullscreen ? SDL_WINDOW_FULLSCREEN : 0);
1420
					consumed = true;
1421
				} else if (event.key.keysym.sym == SDLK_PLUS || event.key.keysym.sym == SDLK_EQUALS) {
1422
					machine_toggle_warp();
1423
					consumed = true;
1424
				} else if (event.key.keysym.sym == SDLK_a) {
1425
					sdcard_attach();
1426
					consumed = true;
1427
				} else if (event.key.keysym.sym == SDLK_d) {
1428
					sdcard_detach();
1429
					consumed = true;
1430
#ifndef __EMSCRIPTEN__
1431
				} else if (event.key.keysym.sym == SDLK_p) {
1432
					screenshot();
1433
					consumed = true;
1434
#endif
1435
				}
1436
			}
1437
			if (cmd_down) {
1438
				if (event.key.keysym.sym == SDLK_m) {
1439
					mousegrab_toggle();
1440
					consumed = true;
1441
				}
1442
			}
1443
			if (alt_down && !disable_emu_cmd_keys) {
1444
				// Capture ALT+F4 key sequence to exit emulator if it is not handled by SDL
1445
				if (event.key.keysym.sym == SDLK_F4) {
1446
					printf("Shutdown requested by ALT+F4\n");
1447
					main_shutdown();
1448
#ifdef __EMSCRIPTEN__
1449
					emscripten_force_exit(0);
1450
#endif
1451
					exit(0);
1452
				}
1453
			}
1454
			if (!consumed) {
1455
				if (event.key.keysym.scancode == LSHORTCUT_KEY || event.key.keysym.scancode == RSHORTCUT_KEY) {
1456
					cmd_down = true;
1457
				}
1458
				if (event.key.keysym.scancode == SDL_SCANCODE_LALT || event.key.keysym.scancode == SDL_SCANCODE_RALT) {
1459
					alt_down = true;
1460
				}
1461
				handle_keyboard(true, event.key.keysym.sym, event.key.keysym.scancode);
1462
			}
1463
			continue;
1464
		}
1465
		if (event.type == SDL_KEYUP) {
1466
			if (event.key.keysym.scancode == LSHORTCUT_KEY || event.key.keysym.scancode == RSHORTCUT_KEY) {
1467
				cmd_down = false;
1468
			}
1469
			if (event.key.keysym.scancode == SDL_SCANCODE_LALT || event.key.keysym.scancode == SDL_SCANCODE_RALT) {
1470
				alt_down = false;
1471
			}
1472
			handle_keyboard(false, event.key.keysym.sym, event.key.keysym.scancode);
1473
			continue;
1474
		}
1475
		if (event.type == SDL_MOUSEBUTTONDOWN) {
1476
			switch (event.button.button) {
1477
				case SDL_BUTTON_LEFT:
1478
					mouse_button_down(0);
1479
					mouse_changed = true;
1480
					break;
1481
				case SDL_BUTTON_RIGHT:
1482
					mouse_button_down(1);
1483
					mouse_changed = true;
1484
					break;
1485
				case SDL_BUTTON_MIDDLE:
1486
					mouse_button_down(2);
1487
					mouse_changed = true;
1488
					break;
1489
			}
1490
		}
1491
		if (event.type == SDL_MOUSEBUTTONUP) {
1492
			switch (event.button.button) {
1493
				case SDL_BUTTON_LEFT:
1494
					mouse_button_up(0);
1495
					mouse_changed = true;
1496
					break;
1497
				case SDL_BUTTON_RIGHT:
1498
					mouse_button_up(1);
1499
					mouse_changed = true;
1500
					break;
1501
				case SDL_BUTTON_MIDDLE:
1502
					mouse_button_up(2);
1503
					mouse_changed = true;
1504
					break;
1505
			}
1506
		}
1507
		if (event.type == SDL_MOUSEMOTION) {
1508
			static int mouse_x;
1509
			static int mouse_y;
1510
			if (mouse_grabbed) {
1511
				mouse_move(event.motion.xrel, event.motion.yrel);
1512
			} else {
1513
				mouse_move(event.motion.x - mouse_x, event.motion.y - mouse_y);
1514
			}
1515
			mouse_x = event.motion.x;
1516
			mouse_y = event.motion.y;
1517
			mouse_changed = true;
1518
		}
1519
		if (event.type == SDL_MOUSEWHEEL) {
1520
			mouse_set_wheel(event.wheel.y);
1521
			mouse_changed = true;
1522
		}
1523
1524
		if (event.type == SDL_JOYDEVICEADDED) {
1525
			joystick_add(event.jdevice.which);
1526
		}
1527
	    if (event.type == SDL_JOYDEVICEREMOVED) {
1528
		    joystick_remove(event.jdevice.which);
1529
	    }
1530
	    if (event.type == SDL_CONTROLLERBUTTONDOWN) {
1531
		    joystick_button_down(event.cbutton.which, event.cbutton.button);
1532
	    }
1533
		if (event.type == SDL_CONTROLLERBUTTONUP) {
1534
		    joystick_button_up(event.cbutton.which, event.cbutton.button);
1535
	    }
1536
1537
	}
1538
	if (mouse_changed) {
1539
		mouse_send_state();
1540
	}
1541
	return true;
1542
}
1543
1544
void
1545
video_end()
1546
{
1547
	if (debugger_enabled) {
1548
		DEBUGFreeUI();
1549
	}
1550
1551
	if (record_gif != RECORD_GIF_DISABLED) {
1552
		GifEnd(&gif_writer);
1553
		record_gif = RECORD_GIF_DISABLED;
1554
	}
1555
1556
	is_fullscreen = false;
1557
	SDL_SetWindowFullscreen(window, 0);
1558
	SDL_DestroyRenderer(renderer);
1559
	SDL_DestroyWindow(window);
1560
}
1561
1562
1563
static const int increments[32] = {
1564
	0,   0,
1565
	1,   -1,
1566
	2,   -2,
1567
	4,   -4,
1568
	8,   -8,
1569
	16,  -16,
1570
	32,  -32,
1571
	64,  -64,
1572
	128, -128,
1573
	256, -256,
1574
	512, -512,
1575
	40,  -40,
1576
	80,  -80,
1577
	160, -160,
1578
	320, -320,
1579
	640, -640,
1580
};
1581
1582
uint32_t
1583
video_get_address(uint8_t sel)
1584
{
1585
	uint32_t address = io_addr[sel];
1586
	return address & 0x1ffff;
1587
}
1588
1589
uint32_t
1590
get_and_inc_address(uint8_t sel, bool write)
1591
{
1592
	uint32_t address = io_addr[sel];
1593
	int16_t incr = increments[io_inc[sel]];
1594
1595
	if (fx_4bit_mode && fx_nibble_incr[sel] && !incr) {
1596
		if (fx_nibble_bit[sel]) {
1597
			if ((io_inc[sel] & 1) == 0) io_addr[sel] += 1;
1598
			fx_nibble_bit[sel] = 0;
1599
		} else {
1600
			if (io_inc[sel] & 1) io_addr[sel] -= 1;
1601
			fx_nibble_bit[sel] = 1;
1602
		}
1603
	}
1604
1605
	if (sel == 1 && fx_16bit_hop) {
1606
		if (incr == 4) {
1607
			if (fx_16bit_hop_align == (address & 0x3))
1608
				incr = 1;
1609
			else
1610
				incr = 3;
1611
		} else if (incr == 320) {
1612
			if (fx_16bit_hop_align == (address & 0x3))
1613
				incr = 1;
1614
			else
1615
				incr = 319;
1616
		}
1617
	}
1618
1619
	io_addr[sel] += incr;
1620
1621
	if (sel == 1 && fx_addr1_mode == 1) { // FX line draw mode
1622
		fx_x_pixel_position += fx_x_pixel_increment;
1623
		if (fx_x_pixel_position & 0x10000) {
1624
			fx_x_pixel_position &= ~0x10000;
1625
			if (fx_4bit_mode && fx_nibble_incr[0]) {
1626
				if (fx_nibble_bit[1]) {
1627
					if ((io_inc[0] & 1) == 0) io_addr[1] += 1;
1628
					fx_nibble_bit[1] = 0;
1629
				} else {
1630
					if (io_inc[0] & 1) io_addr[1] -= 1;
1631
					fx_nibble_bit[1] = 1;
1632
				}
1633
			}
1634
			io_addr[1] += increments[io_inc[0]];
1635
		}
1636
	} else if (fx_addr1_mode == 2 && write == false) { // FX polygon fill mode
1637
		fx_x_pixel_position += fx_x_pixel_increment;
1638
		fx_y_pixel_position += fx_y_pixel_increment;
1639
		fx_poly_fill_length = ((int32_t) fx_y_pixel_position >> 16) - ((int32_t) fx_x_pixel_position >> 16);
1640
		if (sel == 0 && fx_cache_byte_cycling && !fx_cache_fill) {
1641
			fx_cache_byte_index = (fx_cache_byte_index + 1) & 3;
1642
		}
1643
		if (sel == 1) {
1644
			if (fx_4bit_mode) {
1645
				io_addr[1] = io_addr[0] + (fx_x_pixel_position >> 17);
1646
				fx_nibble_bit[1] = (fx_x_pixel_position >> 16) & 1;
1647
			} else {
1648
				io_addr[1] = io_addr[0] + (fx_x_pixel_position >> 16);
1649
			}
1650
		}
1651
	} else if (sel == 1 && fx_addr1_mode == 3 && write == false) { // FX affine mode
1652
		fx_x_pixel_position += fx_x_pixel_increment;
1653
		fx_y_pixel_position += fx_y_pixel_increment;
1654
	}
1655
	return address;
1656
}
1657
1658
void
1659
fx_affine_prefetch(void)
1660
{
1661
	if (fx_addr1_mode != 3) return; // only if affine mode is selected
1662
1663
	uint32_t address;
1664
	uint8_t affine_x_tile = (fx_x_pixel_position >> 19) & 0xff;
1665
	uint8_t affine_y_tile = (fx_y_pixel_position >> 19) & 0xff;
1666
	uint8_t affine_x_sub_tile = (fx_x_pixel_position >> 16) & 0x07;
1667
	uint8_t affine_y_sub_tile = (fx_y_pixel_position >> 16) & 0x07;
1668
1669
	if (!fx_affine_clip) { // wrap
1670
		affine_x_tile &= fx_affine_map_size - 1;
1671
		affine_y_tile &= fx_affine_map_size - 1;
1672
	}
1673
1674
	if (affine_x_tile >= fx_affine_map_size || affine_y_tile >= fx_affine_map_size) {
1675
		// We clipped, return value for tile 0
1676
		address = fx_affine_tile_base + (affine_y_sub_tile << (3 - fx_4bit_mode)) + (affine_x_sub_tile >> (uint8_t)fx_4bit_mode);
1677
		fx_nibble_bit[1] = (affine_x_sub_tile & 1) >> (1 - fx_4bit_mode);
1678
	} else {
1679
		// Get the address within the tile map
1680
		address = fx_affine_map_base + (affine_y_tile * fx_affine_map_size) + affine_x_tile;
1681
		// Now translate that to the tile base address
1682
		uint8_t affine_tile_idx = video_space_read(address);
1683
		address = fx_affine_tile_base + (affine_tile_idx << (6 - fx_4bit_mode));
1684
		// Now add the sub-tile address
1685
		address += (affine_y_sub_tile << (3 - fx_4bit_mode)) + (affine_x_sub_tile >> (uint8_t)fx_4bit_mode);
1686
		fx_nibble_bit[1] = (affine_x_sub_tile & 1) >> (1 - fx_4bit_mode);
1687
	}
1688
	io_addr[1] = address;
1689
	io_rddata[1] = video_space_read(address);
1690
}
1691
1692
//
1693
// Vera: Internal Video Address Space
1694
//
1695
1696
uint8_t
1697
video_space_read(uint32_t address)
1698
{
1699
	return video_ram[address & 0x1FFFF];
1700
}
1701
1702
static void
1703
video_space_read_range(uint8_t* dest, uint32_t address, uint32_t size)
1704
{
1705
	if (address >= ADDR_VRAM_START && (address+size) <= ADDR_VRAM_END) {
1706
		memcpy(dest, &video_ram[address], size);
1707
	} else {
1708
		for(int i = 0; i < size; ++i) {
1709
			*dest++ = video_space_read(address + i);
1710
		}
1711
	}
1712
}
1713
1714
void
1715
video_space_write(uint32_t address, uint8_t value)
1716
{
1717
	video_ram[address & 0x1FFFF] = value;
1718
1719
	if (address >= ADDR_PSG_START && address < ADDR_PSG_END) {
1720
		audio_render();
1721
		psg_writereg(address & 0x3f, value);
1722
	} else if (address >= ADDR_PALETTE_START && address < ADDR_PALETTE_END) {
1723
		palette[address & 0x1ff] = value;
1724
		video_palette.dirty = true;
1725
	} else if (address >= ADDR_SPRDATA_START && address < ADDR_SPRDATA_END) {
1726
		sprite_data[(address >> 3) & 0x7f][address & 0x7] = value;
1727
		refresh_sprite_properties((address >> 3) & 0x7f);
1728
	}
1729
}
1730
1731
1732
void
1733
fx_video_space_write(uint32_t address, bool nibble, uint8_t value)
1734
{
1735
	if (fx_4bit_mode) {
1736
		if (nibble) {
1737
			if (!fx_trans_writes || (value & 0x0f) > 0) {
1738
				video_ram[address & 0x1FFFF] = (video_ram[address & 0x1FFFF] & 0xf0) | (value & 0x0f);
1739
			}
1740
		} else {
1741
			if (!fx_trans_writes || (value & 0xf0) > 0) {
1742
				video_ram[address & 0x1FFFF] = (video_ram[address & 0x1FFFF] & 0x0f) | (value & 0xf0);
1743
			}
1744
		}
1745
	} else {
1746
		if (!fx_trans_writes || value > 0) video_ram[address & 0x1FFFF] = value;
1747
	}
1748
	if (address >= ADDR_PSG_START && address < ADDR_PSG_END) {
1749
		audio_render();
1750
		psg_writereg(address & 0x3f, value);
1751
	} else if (address >= ADDR_PALETTE_START && address < ADDR_PALETTE_END) {
1752
		palette[address & 0x1ff] = value;
1753
		video_palette.dirty = true;
1754
	} else if (address >= ADDR_SPRDATA_START && address < ADDR_SPRDATA_END) {
1755
		sprite_data[(address >> 3) & 0x7f][address & 0x7] = value;
1756
		refresh_sprite_properties((address >> 3) & 0x7f);
1757
	}
1758
}
1759
1760
void
1761
fx_vram_cache_write(uint32_t address, uint8_t value, uint8_t mask)
1762
{
1763
	if (!fx_trans_writes || value > 0) {
1764
		switch (mask) {
1765
			case 0:
1766
				video_ram[address & 0x1FFFF] = value;
1767
				break;
1768
			case 1:
1769
				video_ram[address & 0x1FFFF] = (video_ram[address & 0x1FFFF] & 0x0f) | (value & 0xf0);
1770
				break;
1771
			case 2:
1772
				video_ram[address & 0x1FFFF] = (video_ram[address & 0x1FFFF] & 0xf0) | (value & 0x0f);
1773
				break;
1774
			case 3:
1775
				// Do nothing
1776
				break;
1777
		}
1778
	}
1779
}
1780
1781
uint32_t video_get_fx_accum() {
1782
	return fx_mult_accumulator;
1783
}
1784
uint8_t video_get_dc_value(uint8_t reg) {
1785
	switch (reg & 0x1F) {
1786
		case 0x00:
1787
		case 0x01:
1788
		case 0x02:
1789
		case 0x03:
1790
		case 0x04:
1791
		case 0x05:
1792
		case 0x06:
1793
		case 0x07:
1794
		case 0x08:
1795
		case 0x09:
1796
		case 0x0a:
1797
		case 0x0c:
1798
		case 0x0d:
1799
		case 0x0e:
1800
		case 0x0f:
1801
			return reg_composer[reg];
1802
			break;
1803
		case 0x0b:
1804
			return reg_composer[reg] & 0x3f;
1805
			break;
1806
		case 0x10: // DCSEL=4, $9F29
1807
			return (fx_x_pixel_position >> 16) & 0xff;
1808
			break;
1809
		case 0x11: // DCSEL=4, $9F2A
1810
			return ((fx_x_pixel_position >> 24) & 0x07) | (fx_x_pixel_position & 0x80);
1811
			break;
1812
		case 0x12: // DCSEL=4, $9F2B
1813
			return (fx_y_pixel_position >> 16) & 0xff;
1814
			break;
1815
		case 0x13: // DCSEL=4, $9F2C
1816
			return ((fx_y_pixel_position >> 24) & 0x07) | (fx_y_pixel_position & 0x80);
1817
			break;
1818
		case 0x14: // DCSEL=5, $9F29
1819
			return (fx_x_pixel_position >> 8) & 0xff;
1820
			break;
1821
		case 0x15: // DCSEL=4, $9F2A
1822
			return (fx_y_pixel_position >> 8) & 0xff;
1823
			break;
1824
		case 0x16: // DCSEL=5, 0x9F2B
1825
			if (fx_poly_fill_length >= 768) {
1826
				return ((fx_2bit_poly && fx_addr1_mode == 2) ? 0x00 : 0x80);
1827
			}
1828
			if (fx_4bit_mode) {
1829
				if (fx_2bit_poly && fx_addr1_mode == 2) {
1830
					return ((fx_y_pixel_position & 0x00008000) >> 8) |
1831
						((fx_x_pixel_position >> 11) & 0x60) |
1832
						((fx_x_pixel_position >> 14) & 0x10) |
1833
						((fx_poly_fill_length & 0x0007) << 1) |
1834
						((fx_x_pixel_position & 0x00008000) >> 15);
1835
				} else {
1836
					return ((!!(fx_poly_fill_length & 0xfff8)) << 7) |
1837
						((fx_x_pixel_position >> 11) & 0x60) |
1838
						((fx_x_pixel_position >> 14) & 0x10) |
1839
						((fx_poly_fill_length & 0x0007) << 1);
1840
				}
1841
			} else {
1842
				return ((!!(fx_poly_fill_length & 0xfff0)) << 7) |
1843
					((fx_x_pixel_position >> 11) & 0x60) |
1844
					((fx_poly_fill_length & 0x000f) << 1);
1845
			}
1846
			break;
1847
		case 0x17: // DCSEL=5, 0x9F2C
1848
			return ((fx_poly_fill_length & 0x03f8) >> 2);
1849
			break;
1850
		case 0x18: // DCSEL=6, 0x9F29
1851
			return fx_cache[0];
1852
			break;
1853
		case 0x19: // DCSEL=6, 0x9F2A
1854
			return fx_cache[1];
1855
			break;
1856
		case 0x1a: // DCSEL=6, 0x9F2B
1857
			return fx_cache[2];
1858
			break;
1859
		case 0x1b: // DCSEL=6, 0x9F2C
1860
			return fx_cache[3];
1861
			break;
1862
		default:
1863
			break;
1864
	}
1865
1866
	return vera_version_string[reg % 4];
1867
}
1868
1869
//
1870
// Vera: 6502 I/O Interface
1871
//
1872
// if debugOn, read without any side effects (registers & memory unchanged)
1873
1874
static void check_not_readonly(uint8_t reg) {
1875
	bool wrong = false;
1876
	switch(io_dcsel) {
1877
		case 5: {
1878
			switch(reg) {
1879
				case 0x0b:		// DCSEL=5 FX_POLY_FILL_L
1880
				case 0x0c:		// DCSEL=5 FX_POLY_FILL_H
1881
					wrong=true;
1882
					break;
1883
			}
1884
			break;
1885
		}
1886
		case 63: {
1887
			switch(reg) {
1888
				case 0x09:		// DCSEL=63 DC_VER0
1889
				case 0x0a:		// DCSEL=63 DC_VER1
1890
				case 0x0b:		// DCSEL=63 DC_VER2
1891
				case 0x0c:		// DCSEL=63 DC_VER3
1892
					wrong=true;
1893
					break;
1894
			}
1895
			break;
1896
		}
1897
	}
1898
1899
	if(wrong)
1900
		printf("Warning: %04X wrote to read-only VERA register at 9F%02X (DCSEL=%d)\n", opcode_addr, reg+0x20, io_dcsel);
1901
}
1902
1903
static void check_not_writeonly(uint8_t reg) {
1904
	bool wrong = false;
1905
	switch(io_dcsel) {
1906
		case 2: {
1907
			switch(reg) {
1908
				case 0x0a:		// DCSEL=2 FX_TILEBASE
1909
				case 0x0b:		// DCSEL=2 FX_MAPBASE
1910
				case 0x0c:		// DCSEL=2 FX_MULT
1911
					wrong=true;
1912
					break;
1913
			}
1914
			break;
1915
		}
1916
		case 3:
1917
		case 4: {
1918
			switch(reg) {
1919
				case 0x09:		// DCSEL=3/4 FX_X_INCR_L/FX_X_POS_L
1920
				case 0x0a:		// DCSEL=3/4 FX_X_INCR_H/FX_X_POS_H
1921
				case 0x0b:		// DCSEL=3/4 FX_Y_INCR_L/FX_Y_POS_L
1922
				case 0x0c:		// DCSEL=3/4 FX_Y_INCR_H/FX_Y_POS_H
1923
					wrong=true;
1924
					break;
1925
			}
1926
			break;
1927
		}
1928
		case 5: {
1929
			switch(reg) {
1930
				case 0x09:		// DCSEL=5 FX_X_POS_S
1931
				case 0x0a:		// DCSEL=5 FX_Y_POS_S
1932
					wrong=true;
1933
					break;
1934
			}
1935
			break;
1936
		}
1937
		case 6: {
1938
			switch(reg) {
1939
				case 0x0b:		// DCSEL=6 FX_CACHE_H
1940
				case 0x0c:		// DCSEL=6 FX_CACHE_U
1941
					wrong=true;
1942
					break;
1943
			}
1944
			break;
1945
		}
1946
	}
1947
1948
	if(wrong)
1949
		printf("Warning: %04X read from write-only VERA register at 9F%02X (DCSEL=%d)\n", opcode_addr, reg+0x20, io_dcsel);
1950
}
1951
1952
uint8_t video_read(uint8_t reg, bool debugOn) {
1953
	bool ntsc_mode = reg_composer[0] & 2;
1954
	uint16_t scanline = ntsc_mode ? ntsc_scan_pos_y % SCAN_HEIGHT : vga_scan_pos_y;
1955
	if (scanline >= 512) scanline=511;
1956
1957
	check_not_writeonly(reg);
1958
1959
	switch (reg & 0x1F) {
1960
		case 0x00: return io_addr[io_addrsel] & 0xff;
1961
		case 0x01: return (io_addr[io_addrsel] >> 8) & 0xff;
1962
		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);
1963
		case 0x03:
1964
		case 0x04: {
1965
			if (debugOn) {
1966
				return io_rddata[reg - 3];
1967
			}
1968
1969
			bool addr_nibble = fx_nibble_bit[reg - 3];
1970
			uint32_t address = get_and_inc_address(reg - 3, false);
1971
1972
			uint8_t value = io_rddata[reg - 3];
1973
1974
			if (reg == 4 && fx_addr1_mode == 3)
1975
				fx_affine_prefetch();
1976
			else
1977
				io_rddata[reg - 3] = video_space_read(io_addr[reg - 3]);
1978
1979
			if (fx_cache_fill) {
1980
				if (fx_4bit_mode) {
1981
					uint8_t nibble_read = (addr_nibble ? ((value & 0x0f) << 4) : (value & 0xf0));
1982
1983
					if (fx_cache_nibble_index) {
1984
						fx_cache[fx_cache_byte_index] = (fx_cache[fx_cache_byte_index] & 0xf0) | (nibble_read >> 4);
1985
						fx_cache_nibble_index = 0;
1986
						fx_cache_byte_index = ((fx_cache_byte_index + 1) & 0x3);
1987
					} else {
1988
						fx_cache[fx_cache_byte_index] = (fx_cache[fx_cache_byte_index] & 0x0f) | (nibble_read);
1989
						fx_cache_nibble_index = 1;
1990
					}
1991
				} else {
1992
					fx_cache[fx_cache_byte_index] = value;
1993
					if (fx_cache_increment_mode)
1994
						fx_cache_byte_index = (fx_cache_byte_index & 0x2) | ((fx_cache_byte_index + 1) & 0x1);
1995
					else
1996
						fx_cache_byte_index = ((fx_cache_byte_index + 1) & 0x3);
1997
				}
1998
			}
1999
2000
			if (log_video) {
2001
				printf("READ  video_space[$%X] = $%02X\n", address, value);
2002
			}
2003
			return value;
2004
		}
2005
		case 0x05: return (io_dcsel << 1) | io_addrsel;
2006
		case 0x06: return ((irq_line & 0x100) >> 1) | ((scanline & 0x100) >> 2) | (ien & 0xF);
2007
		case 0x07: return isr | (pcm_is_fifo_almost_empty() ? 8 : 0);
2008
		case 0x08: return scanline & 0xFF;
2009
2010
		case 0x09:
2011
		case 0x0A:
2012
		case 0x0B:
2013
		case 0x0C: {
2014
			int i = reg - 0x09 + (io_dcsel << 2);
2015
			if (debugOn) return video_get_dc_value(i);
2016
			switch (i) {
2017
				case 0x00:
2018
				case 0x01:
2019
				case 0x02:
2020
				case 0x03:
2021
				case 0x04:
2022
				case 0x05:
2023
				case 0x06:
2024
				case 0x07:
2025
				case 0x08:
2026
				case 0x16: // DCSEL=5, 0x9F2B
2027
				case 0x17: // DCSEL=5, 0x9F2C
2028
					return video_get_dc_value(i);
2029
					break;
2030
				case 0x18: // DCSEL=6, 0x9F29
2031
					fx_mult_accumulator = 0;
2032
					// fall out of the switch
2033
					break;
2034
				case 0x19: // DCSEL=6, 0x9F2A
2035
					; // <- avoids the error in some compilers about a declaration after a label
2036
					int32_t m_result = (int16_t)((fx_cache[1] << 8) | fx_cache[0]) * (int16_t)((fx_cache[3] << 8) | fx_cache[2]);
2037
					if (fx_subtract)
2038
						fx_mult_accumulator -= m_result;
2039
					else
2040
						fx_mult_accumulator += m_result;
2041
					// fall out of the switch
2042
					break;
2043
				default:
2044
					// The rest of the space is write-only,
2045
					// so reading the values out instead returns the version string.
2046
					// fall out of the switch
2047
					break;
2048
			}
2049
			return vera_version_string[i % 4];
2050
			break;
2051
		}
2052
		case 0x0D:
2053
		case 0x0E:
2054
		case 0x0F:
2055
		case 0x10:
2056
		case 0x11:
2057
		case 0x12:
2058
		case 0x13: return reg_layer[0][reg - 0x0D];
2059
2060
		case 0x14:
2061
		case 0x15:
2062
		case 0x16:
2063
		case 0x17:
2064
		case 0x18:
2065
		case 0x19:
2066
		case 0x1A: return reg_layer[1][reg - 0x14];
2067
2068
		case 0x1B: audio_render(); return pcm_read_ctrl();
2069
		case 0x1C: return pcm_read_rate();
2070
		case 0x1D: return 0;
2071
2072
		case 0x1E:
2073
		case 0x1F: return vera_spi_read(reg & 1);
2074
	}
2075
	return 0;
2076
}
2077
2078
void video_write(uint8_t reg, uint8_t value) {
2079
	// if (reg > 4) {
2080
	// 	printf("ioregisters[0x%02X] = 0x%02X\n", reg, value);
2081
	// }
2082
	//	printf("ioregisters[%d] = $%02X\n", reg, value);
2083
2084
	check_not_readonly(reg);
2085
2086
	switch (reg & 0x1F) {
2087
		case 0x00:
2088
			if (fx_2bit_poly && fx_4bit_mode && fx_addr1_mode == 2 && io_addrsel == 1) {
2089
				fx_2bit_poking = true;
2090
				io_addr[1] = (io_addr[1] & 0x1fffc) | (value & 0x3);
2091
			} else {
2092
				io_addr[io_addrsel] = (io_addr[io_addrsel] & 0x1ff00) | value;
2093
				if (fx_16bit_hop && io_addrsel == 1)
2094
					fx_16bit_hop_align = value & 3;
2095
			}
2096
			io_rddata[io_addrsel] = video_space_read(io_addr[io_addrsel]);
2097
			break;
2098
		case 0x01:
2099
			io_addr[io_addrsel] = (io_addr[io_addrsel] & 0x100ff) | (value << 8);
2100
			io_rddata[io_addrsel] = video_space_read(io_addr[io_addrsel]);
2101
			break;
2102
		case 0x02:
2103
			io_addr[io_addrsel] = (io_addr[io_addrsel] & 0x0ffff) | ((value & 0x1) << 16);
2104
			fx_nibble_bit[io_addrsel] = (value >> 1) & 0x1;
2105
			fx_nibble_incr[io_addrsel] = (value >> 2) & 0x1;
2106
			io_inc[io_addrsel]  = value >> 3;
2107
			io_rddata[io_addrsel] = video_space_read(io_addr[io_addrsel]);
2108
			break;
2109
		case 0x03:
2110
		case 0x04: {
2111
			if (fx_2bit_poking && fx_addr1_mode) {
2112
				fx_2bit_poking = false;
2113
				uint8_t mask = value >> 6;
2114
				switch (mask) {
2115
					case 0x00:
2116
						video_ram[io_addr[1] & 0x1FFFF] = (fx_cache[fx_cache_byte_index] & 0xc0) | (io_rddata[1] & 0x3f);
2117
						break;
2118
					case 0x01:
2119
						video_ram[io_addr[1] & 0x1FFFF] = (fx_cache[fx_cache_byte_index] & 0x30) | (io_rddata[1] & 0xcf);
2120
						break;
2121
					case 0x02:
2122
						video_ram[io_addr[1] & 0x1FFFF] = (fx_cache[fx_cache_byte_index] & 0x0c) | (io_rddata[1] & 0xf3);
2123
						break;
2124
					case 0x03:
2125
						video_ram[io_addr[1] & 0x1FFFF] = (fx_cache[fx_cache_byte_index] & 0x03) | (io_rddata[1] & 0xfc);
2126
						break;
2127
				}
2128
				break; // break out of the enclosing switch statement early, too
2129
			}
2130
2131
			if (enable_midline)
2132
				video_step(MHZ, 0, true); // potential midline raster effect
2133
			bool nibble = fx_nibble_bit[reg - 3];
2134
			uint32_t address = get_and_inc_address(reg - 3, true);
2135
			if (log_video) {
2136
				printf("WRITE video_space[$%X] = $%02X\n", address, value);
2137
			}
2138
2139
			uint8_t wrdata_to_use;
2140
			uint8_t ram_wrdata[4];
2141
			uint8_t nibble_mask[4];
2142
			uint8_t cache_to_use[4];
2143
2144
			if (fx_multiplier) {
2145
				int32_t m_result = (int16_t)((fx_cache[1] << 8) | fx_cache[0]) * (int16_t)((fx_cache[3] << 8) | fx_cache[2]);
2146
				if (fx_subtract)
2147
					m_result = fx_mult_accumulator - m_result;
2148
				else
2149
					m_result = fx_mult_accumulator + m_result;
2150
				cache_to_use[0] = (m_result) & 0xff;
2151
				cache_to_use[1] = (m_result >> 8) & 0xff;
2152
				cache_to_use[2] = (m_result >> 16) & 0xff;
2153
				cache_to_use[3] = (m_result >> 24) & 0xff;
2154
			} else {
2155
				memcpy(cache_to_use, fx_cache, sizeof(fx_cache));
2156
			}
2157
2158
			if (fx_cache_byte_cycling) {
2159
				wrdata_to_use = fx_cache[fx_cache_byte_index];
2160
			} else {
2161
				wrdata_to_use = value;
2162
			}
2163
2164
			if (fx_cache_write && !fx_cache_byte_cycling) {
2165
				ram_wrdata[0] = cache_to_use[0];
2166
				ram_wrdata[1] = cache_to_use[1];
2167
				ram_wrdata[2] = cache_to_use[2];
2168
				ram_wrdata[3] = cache_to_use[3];
2169
			} else {
2170
				ram_wrdata[0] = wrdata_to_use;
2171
				ram_wrdata[1] = wrdata_to_use;
2172
				ram_wrdata[2] = wrdata_to_use;
2173
				ram_wrdata[3] = wrdata_to_use;
2174
			}
2175
2176
			if (fx_cache_write) {
2177
				address &= 0x1fffc;
2178
				if (fx_trans_writes) {
2179
					if (fx_4bit_mode) {
2180
						nibble_mask[0] = (((ram_wrdata[0] & 0xf0) == 0) << 1) | ((ram_wrdata[0] & 0x0f) == 0);
2181
						nibble_mask[1] = (((ram_wrdata[1] & 0xf0) == 0) << 1) | ((ram_wrdata[1] & 0x0f) == 0);
2182
						nibble_mask[2] = (((ram_wrdata[2] & 0xf0) == 0) << 1) | ((ram_wrdata[2] & 0x0f) == 0);
2183
						nibble_mask[3] = (((ram_wrdata[3] & 0xf0) == 0) << 1) | ((ram_wrdata[3] & 0x0f) == 0);
2184
					} else {
2185
						nibble_mask[0] = (ram_wrdata[0] != 0) ? 0 : 3;
2186
						nibble_mask[1] = (ram_wrdata[1] != 0) ? 0 : 3;
2187
						nibble_mask[2] = (ram_wrdata[2] != 0) ? 0 : 3;
2188
						nibble_mask[3] = (ram_wrdata[3] != 0) ? 0 : 3;
2189
					}
2190
				} else {
2191
					nibble_mask[0] = value & 0x3;
2192
					nibble_mask[1] = (value >> 2) & 0x3;
2193
					nibble_mask[2] = (value >> 4) & 0x3;
2194
					nibble_mask[3] = (value >> 6) & 0x3;
2195
				}
2196
2197
				fx_vram_cache_write(address+0, ram_wrdata[0], nibble_mask[0]);
2198
				fx_vram_cache_write(address+1, ram_wrdata[1], nibble_mask[1]);
2199
				fx_vram_cache_write(address+2, ram_wrdata[2], nibble_mask[2]);
2200
				fx_vram_cache_write(address+3, ram_wrdata[3], nibble_mask[3]);
2201
			} else {
2202
				fx_video_space_write(address, nibble, wrdata_to_use); // Normal write
2203
			}
2204
2205
			io_rddata[reg - 3] = video_space_read(io_addr[reg - 3]);
2206
			break;
2207
		}
2208
		case 0x05:
2209
			if (value & 0x80) {
2210
				video_reset();
2211
			}
2212
			io_dcsel = (value >> 1) & 0x3f;
2213
			io_addrsel = value & 1;
2214
			break;
2215
		case 0x06:
2216
			irq_line = (irq_line & 0xFF) | ((value >> 7) << 8);
2217
			ien = value & 0xF;
2218
			break;
2219
		case 0x07:
2220
			isr &= value ^ 0xff;
2221
			break;
2222
		case 0x08:
2223
			irq_line = (irq_line & 0x100) | value;
2224
			break;
2225
2226
		case 0x09:
2227
		case 0x0A:
2228
		case 0x0B:
2229
		case 0x0C: {
2230
			video_step(MHZ, 0, true); // potential midline raster effect
2231
			int i = reg - 0x09 + (io_dcsel << 2);
2232
			if (i == 0) {
2233
				// if progressive mode field goes from 0 to 1
2234
				// or if mode goes from vga to something else with
2235
				// progressive mode on, clear the framebuffer
2236
				if (((reg_composer[0] & 0x8) == 0 && (value & 0x8)) ||
2237
					((reg_composer[0] & 0x3) == 1 && (value & 0x3) > 1 && (value & 0x8))) {
2238
					memset(framebuffer, 0x00, SCREEN_WIDTH * SCREEN_HEIGHT * 4);
2239
				}
2240
2241
				// interlace field bit is read-only
2242
				reg_composer[0] = (reg_composer[0] & ~0x7f) | (value & 0x7f);
2243
				video_palette.dirty = true;
2244
			} else {
2245
				reg_composer[i] = value;
2246
			}
2247
2248
			switch (i) {
2249
				case 0x08: // DCSEL=2, $9F29
2250
					fx_addr1_mode = value & 0x03;
2251
					fx_4bit_mode = (value & 0x04) >> 2;
2252
					fx_16bit_hop = (value & 0x08) >> 3;
2253
					fx_cache_byte_cycling = (value & 0x10) >> 4;
2254
					fx_cache_fill = (value & 0x20) >> 5;
2255
					fx_cache_write = (value & 0x40) >> 6;
2256
					fx_trans_writes = (value & 0x80) >> 7;
2257
					break;
2258
				case 0x09: // DCSEL=2, $9F2A
2259
					fx_affine_tile_base = (value & 0xfc) << 9;
2260
					fx_affine_clip = (value & 0x02) >> 1;
2261
					fx_2bit_poly = (value & 0x01);
2262
					break;
2263
				case 0x0a: // DCSEL=2, $9F2B
2264
					fx_affine_map_base = (value & 0xfc) << 9;
2265
					fx_affine_map_size = 2 << ((value & 0x03) << 1);
2266
					break;
2267
				case 0x0b: // DCSEL=2, $9F2C
2268
					fx_cache_increment_mode = value & 0x01;
2269
					fx_cache_nibble_index = (value & 0x02) >> 1;
2270
					fx_cache_byte_index = (value & 0x0c) >> 2;
2271
					fx_multiplier = (value & 0x10) >> 4;
2272
					fx_subtract = (value & 0x20) >> 5;
2273
					if (value & 0x40) { // accumulate
2274
						int32_t m_result = (int16_t)((fx_cache[1] << 8) | fx_cache[0]) * (int16_t)((fx_cache[3] << 8) | fx_cache[2]);
2275
						if (fx_subtract)
2276
							fx_mult_accumulator -= m_result;
2277
						else
2278
							fx_mult_accumulator += m_result;
2279
					}
2280
					if (value & 0x80) { // reset accumulator
2281
						fx_mult_accumulator = 0;
2282
					}
2283
					break;
2284
				case 0x0c: // DCSEL=3, $9F29
2285
					fx_x_pixel_increment = ((((reg_composer[0x0d] & 0x7f) << 15) + (reg_composer[0x0c] << 7)) // base value
2286
						| ((reg_composer[0x0d] & 0x40) ? 0xffc00000 : 0)) // sign extend if negative
2287
						<< 5*(!!(reg_composer[0x0d] & 0x80)); // multiply by 32 if flag set
2288
					break;
2289
				case 0x0d: // DCSEL=3, $9F2A
2290
					fx_x_pixel_increment = ((((reg_composer[0x0d] & 0x7f) << 15) + (reg_composer[0x0c] << 7)) // base value
2291
						| ((reg_composer[0x0d] & 0x40) ? 0xffc00000 : 0)) // sign extend if negative
2292
						<< 5*(!!(reg_composer[0x0d] & 0x80)); // multiply by 32 if flag set
2293
					if (fx_addr1_mode == 1 || fx_addr1_mode == 2) {
2294
						// Reset subpixel to 0.5
2295
						fx_x_pixel_position = (fx_x_pixel_position & 0x07ff0000) | 0x00008000;
2296
					}
2297
					break;
2298
				case 0x0e: // DCSEL=3, $9F2B
2299
					fx_y_pixel_increment = ((((reg_composer[0x0f] & 0x7f) << 15) + (reg_composer[0x0e] << 7)) // base value
2300
						| ((reg_composer[0x0f] & 0x40) ? 0xffc00000 : 0)) // sign extend if negative
2301
						<< 5*(!!(reg_composer[0x0f] & 0x80)); // multiply by 32 if flag set
2302
					break;
2303
				case 0x0f: // DCSEL=3, $9F2C
2304
					fx_y_pixel_increment = ((((reg_composer[0x0f] & 0x7f) << 15) + (reg_composer[0x0e] << 7)) // base value
2305
						| ((reg_composer[0x0f] & 0x40) ? 0xffc00000 : 0)) // sign extend if negative
2306
						<< 5*(!!(reg_composer[0x0f] & 0x80)); // multiply by 32 if flag set
2307
					if (fx_addr1_mode == 1 || fx_addr1_mode == 2) {
2308
						// Reset subpixel to 0.5
2309
						fx_y_pixel_position = (fx_y_pixel_position & 0x07ff0000) | 0x00008000;
2310
					}
2311
					break;
2312
				case 0x10: // DCSEL=4, $9F29
2313
					fx_x_pixel_position = (fx_x_pixel_position & 0x0700ff80) | (value << 16);
2314
					fx_affine_prefetch();
2315
					break;
2316
				case 0x11: // DCSEL=4, $9F2A
2317
					fx_x_pixel_position = (fx_x_pixel_position & 0x00ffff00) | ((value & 0x7) << 24) | (value & 0x80);
2318
					fx_affine_prefetch();
2319
					break;
2320
				case 0x12: // DCSEL=4, $9F2B
2321
					fx_y_pixel_position = (fx_y_pixel_position & 0x0700ff80) | (value << 16);
2322
					fx_affine_prefetch();
2323
					break;
2324
				case 0x13: // DCSEL=4, $9F2C
2325
					fx_y_pixel_position = (fx_y_pixel_position & 0x00ffff00) | ((value & 0x7) << 24) | (value & 0x80);
2326
					fx_affine_prefetch();
2327
					break;
2328
				case 0x14: // DCSEL=5, $9F29
2329
					fx_x_pixel_position = (fx_x_pixel_position & 0x07ff0080) | (value << 8);
2330
					break;
2331
				case 0x15: // DCSEL=5, $9F2A
2332
					fx_y_pixel_position = (fx_y_pixel_position & 0x07ff0080) | (value << 8);
2333
					break;
2334
				case 0x18: // DCSEL=6, $9F29
2335
					fx_cache[0] = value;
2336
					break;
2337
				case 0x19: // DCSEL=6, $9F2A
2338
					fx_cache[1] = value;
2339
					break;
2340
				case 0x1a: // DCSEL=6, $9F2B
2341
					fx_cache[2] = value;
2342
					break;
2343
				case 0x1b: // DCSEL=6, $9F2C
2344
					fx_cache[3] = value;
2345
					break;
2346
			}
2347
			break;
2348
		}
2349
2350
		case 0x0D:
2351
		case 0x0E:
2352
		case 0x0F:
2353
		case 0x10:
2354
		case 0x11:
2355
		case 0x12:
2356
		case 0x13:
2357
			video_step(MHZ, 0, true); // potential midline raster effect
2358
			reg_layer[0][reg - 0x0D] = value;
2359
			refresh_layer_properties(0);
2360
			break;
2361
2362
		case 0x14:
2363
		case 0x15:
2364
		case 0x16:
2365
		case 0x17:
2366
		case 0x18:
2367
		case 0x19:
2368
		case 0x1A:
2369
			video_step(MHZ, 0, true); // potential midline raster effect
2370
			reg_layer[1][reg - 0x14] = value;
2371
			refresh_layer_properties(1);
2372
			break;
2373
2374
		case 0x1B: audio_render(); pcm_write_ctrl(value); break;
2375
		case 0x1C: audio_render(); pcm_write_rate(value); break;
2376
		case 0x1D: audio_render(); pcm_write_fifo(value); break;
2377
2378
		case 0x1E:
2379
		case 0x1F:
2380
			vera_spi_write(reg & 1, value);
2381
			break;
2382
	}
2383
}
2384
2385
void
2386
video_update_title(const char* window_title)
2387
{
2388
	SDL_SetWindowTitle(window, window_title);
2389
}
2390
2391
bool video_is_tilemap_address(int addr)
2392
{
2393
	for (int l = 0; l < 2; ++l) {
2394
		struct video_layer_properties *props = &layer_properties[l];
2395
		if (addr < props->map_base) {
2396
			continue;
2397
		}
2398
		if (addr >= props->map_base + (2 << (props->mapw_log2 + props->maph_log2))) {
2399
			continue;
2400
		}
2401
2402
		return true;
2403
	}
2404
	return false;
2405
}
2406
2407
bool video_is_tiledata_address(int addr)
2408
{
2409
	for (int l = 0; l < 2; ++l) {
2410
		struct video_layer_properties *props = &layer_properties[l];
2411
		if (addr < props->tile_base) {
2412
			continue;
2413
		}
2414
		int tile_size = props->tilew * props->tileh * props->bits_per_pixel / 8;
2415
		if (addr >= props->tile_base + tile_size * (props->bits_per_pixel == 1 ? 256 : 1024)) {
2416
			continue;
2417
		}
2418
2419
		return true;
2420
	}
2421
	return false;
2422
}
2423
2424
bool video_is_special_address(int addr)
2425
{
2426
	return addr >= 0x1F9C0;
2427
}
2428
2429
void
2430
stop6502(uint16_t address, uint8_t bank) {
2431
	if (debugger_enabled) {
2432
		DEBUGBreakToDebugger();
2433
	} else if (testbench) {
2434
		printf("STP\n");
2435
        fflush(stdout);
2436
	} else {
2437
		int return_btn;
2438
		char error_message[80];
2439
		const SDL_MessageBoxButtonData btns[2] = {
2440
			{SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Reset Machine"},
2441
			{SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, -1, "Ignore"}
2442
		};
2443
		const SDL_MessageBoxData msg_box = {
2444
			SDL_MESSAGEBOX_ERROR, window, "Error", error_message,
2445
			2, btns, NULL
2446
		};
2447
2448
		if (is_gen2) {
2449
			sprintf(error_message, "Encountered stop instruction at address $%06X. CPU cannot continue.", (regs.k << 16) | address);
2450
		} else {
2451
			sprintf(error_message, "Encountered stop instruction at address $%04X. CPU cannot continue.", address);
2452
		}
2453
		if (SDL_ShowMessageBox(&msg_box, &return_btn) == 0 && return_btn == 0) {
2454
			machine_reset();
2455
		};
2456
	}
2457
}