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