OXYGENHUB
# 9120e87480a6 enhanced-x16-emulator / src / video.c
111.2 KiB · C · last changed chore(): initial commit 25 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
#ifndef __APPLE__
7
#ifndef _MSC_VER
8
#define _XOPEN_SOURCE   600
9
#define _POSIX_C_SOURCE 1
10
#endif
11
#endif
12
13
#include "video.h"
14
#include "memory.h"
15
#include "glue.h"
16
#include "debugger.h"
17
#ifdef HAS_IMGUI
18
#include "smc.h" // smc_requested_reset (Ctrl+Shift+F5 restart)
19
#include "dbg_load.h" // dbg_load_note_debugger, when the UI fails to open
20
#include "debug_ui/debug_ui.h"
21
#endif
22
#include "keyboard.h"
23
#include "gif.h"
24
#include "joystick.h"
25
#include "vera_spi.h"
26
#include "vera_psg.h"
27
#include "vera_pcm.h"
28
#include "sprite_trace.h"
29
#include "vera_bandwidth.h"
30
#include "icon.h"
31
#include "sdcard.h"
32
#include "i2c.h"
33
#include "audio.h"
34
35
#include <stdbool.h>
36
#include <limits.h>
37
#include <stdint.h>
38
#include <time.h>
39
#include "compat.h"
40
41
#ifdef __EMSCRIPTEN__
42
#include "emscripten.h"
43
#endif
44
45
#ifndef __EMSCRIPTEN__
46
#define STB_IMAGE_WRITE_IMPLEMENTATION
47
#include "stb_image_write.h"
48
#endif
49
50
#define APPROX_TITLEBAR_HEIGHT 30
51
52
#define VERA_VERSION_MAJOR  47
53
#define VERA_VERSION_MINOR  0
54
#define VERA_VERSION_PATCH  2
55
56
#define ADDR_VRAM_START     0x00000
57
#define ADDR_VRAM_END       0x20000
58
#define ADDR_PSG_START      0x1F9C0
59
#define ADDR_PSG_END        0x1FA00
60
#define ADDR_PALETTE_START  0x1FA00
61
#define ADDR_PALETTE_END    0x1FC00
62
#define ADDR_SPRDATA_START  0x1FC00
63
#define ADDR_SPRDATA_END    0x20000
64
65
#define NUM_SPRITES 128
66
67
// Clocks the sprite renderer gets per scanline before both of its state
68
// machines are forced to DONE.
69
//
70
//   sprite_renderer.v:44  wire render_time_done = (render_time_r == 'd798);
71
//
72
// render_time_r is cleared by line_render_start and counts every clock after,
73
// so this is elapsed time for the line rather than an amount of work. The
74
// comment above it in the RTL gives the reason for the number: "Limit render
75
// time so that VGA and composite mode get the same amount of render time".
76
#define SPRITE_RENDER_TIME 798
77
78
// both VGA and NTSC
79
#define SCAN_HEIGHT 525
80
#define PIXEL_FREQ 25.0
81
82
// VGA
83
#define VGA_SCAN_WIDTH 800
84
#define VGA_Y_OFFSET 0
85
86
// NTSC: 262.5 lines per frame, lower field first
87
#define NTSC_HALF_SCAN_WIDTH 794
88
#define NTSC_X_OFFSET 270
89
#define NTSC_Y_OFFSET_LOW 42
90
#define NTSC_Y_OFFSET_HIGH 568
91
#define TITLE_SAFE_X 0.067
92
#define TITLE_SAFE_Y 0.05
93
94
// visible area we're drawing
95
#define SCREEN_WIDTH 640
96
#define SCREEN_HEIGHT 480
97
98
#define SCREEN_RAM_OFFSET 0x00000
99
100
#ifdef __APPLE__
101
#define LSHORTCUT_KEY SDL_SCANCODE_LGUI
102
#define RSHORTCUT_KEY SDL_SCANCODE_RGUI
103
#else
104
#define LSHORTCUT_KEY SDL_SCANCODE_LCTRL
105
#define RSHORTCUT_KEY SDL_SCANCODE_RCTRL
106
#endif
107
108
// When rendering a layer line, we can amortize some of the cost by calculating multiple pixels at a time.
109
#define LAYER_PIXELS_PER_ITERATION 8
110
111
#define MAX(a,b) ((a) > (b) ? a : b)
112
113
static SDL_Window *window;
114
static SDL_Renderer *renderer;
115
static SDL_Texture *sdlTexture;
116
static bool is_fullscreen = false;
117
#ifdef HAS_IMGUI
118
// ImGui debugger window (separate; created only when -imgui is passed).
119
static SDL_Window *imgui_window = NULL;
120
static SDL_Renderer *imgui_renderer = NULL;
121
static Uint32 imgui_window_id = 0;
122
#endif
123
bool mouse_grabbed = false;
124
bool no_keyboard_capture = false;
125
bool kernal_mouse_enabled = false;
126
127
static uint8_t video_ram[0x20000];
128
static uint8_t palette[256 * 2];
129
static uint8_t sprite_data[128][8];
130
131
// I/O registers
132
static uint32_t io_addr[2];
133
static uint8_t io_rddata[2];
134
static uint8_t io_inc[2];
135
static uint8_t io_addrsel;
136
static uint8_t io_dcsel;
137
138
static uint8_t ien;
139
static uint8_t isr;
140
141
static uint16_t irq_line;
142
143
static uint8_t reg_layer[2][7];
144
145
// Per-scanline record of the layer registers that were actually used to render
146
// each line, so debug views can reproduce raster-split effects -- programs
147
// routinely rewrite MAPBASE/TILEBASE/scroll part-way down a frame from a line
148
// IRQ. Without this a debugger can only ever show one register snapshot for the
149
// whole frame, which describes just one band of the screen.
150
static uint8_t  line_reg_layer[SCREEN_HEIGHT][2][7];
151
static uint16_t line_eff_y[SCREEN_HEIGHT];
152
// The layer row each line actually displayed. Recorded rather than left for a
153
// consumer to recompute from the registers: the renderers take the layout from
154
// prev_layer_properties[1] but VSCROLL and the layer-height mask from [0]
155
// (video.c: render_layer_line_text/tile via calc_layer_eff_y(props0, ...)), so
156
// no single register generation describes the result. Storing the answer makes
157
// the contract true instead of approximately true.
158
static uint16_t line_layer_row[SCREEN_HEIGHT][2];
159
static uint8_t  line_layer_enabled[SCREEN_HEIGHT];
160
static bool     line_state_valid[SCREEN_HEIGHT];
161
// The raw registers on the same two-stage delay the layer properties use, so
162
// the history above can record the generation that actually rendered a line.
163
static uint8_t  prev_reg_layer[2][2][7];
164
165
#define COMPOSER_SLOTS 4*64
166
static uint8_t reg_composer[COMPOSER_SLOTS];
167
static uint8_t prev_reg_composer[2][COMPOSER_SLOTS];
168
169
static uint8_t layer_line[2][SCREEN_WIDTH];
170
static uint8_t sprite_line_col[SCREEN_WIDTH];
171
static uint8_t sprite_line_z[SCREEN_WIDTH];
172
static uint8_t sprite_line_mask[SCREEN_WIDTH];
173
static uint8_t sprite_line_collisions;
174
static bool layer_line_enable[2];
175
static bool old_layer_line_enable[2];
176
static bool old_sprite_line_enable;
177
static bool sprite_line_enable;
178
179
////////////////////////////////////////////////////////////
180
// FX registers
181
////////////////////////////////////////////////////////////
182
static uint8_t fx_addr1_mode;
183
184
// These are all 16.16 fixed point in the emulator
185
// even though the VERA uses smaller bit widths
186
// for the whole and fractional parts.
187
//
188
// Sign extension is done manually when assigning negative numbers
189
//
190
// Native VERA bit widths are shown below.
191
static uint32_t fx_x_pixel_increment;  // 11.9 fixed point (6.9 without 32x multiplier, 11.4 with 32x multiplier on)
192
static uint32_t fx_y_pixel_increment;  // 11.9 fixed point (6.9 without 32x multiplier, 11.4 with 32x multiplier on)
193
static uint32_t fx_x_pixel_position;   // 11.9 fixed point
194
static uint32_t fx_y_pixel_position;   // 11.9 fixed point
195
196
static uint16_t fx_poly_fill_length;      // 10 bits
197
198
static uint32_t fx_affine_tile_base;
199
static uint32_t fx_affine_map_base;
200
201
static uint8_t fx_affine_map_size;
202
203
static bool fx_4bit_mode;
204
static bool fx_16bit_hop;
205
static bool fx_cache_byte_cycling;
206
static bool fx_cache_fill;
207
static bool fx_cache_write;
208
static bool fx_trans_writes;
209
210
static bool fx_2bit_poly;
211
static bool fx_2bit_poking;
212
213
static bool fx_cache_increment_mode;
214
static bool fx_cache_nibble_index;
215
static uint8_t fx_cache_byte_index;
216
static bool fx_multiplier;
217
static bool fx_subtract;
218
219
static bool fx_affine_clip;
220
221
static uint8_t fx_16bit_hop_align;
222
223
static bool fx_nibble_bit[2];
224
static bool fx_nibble_incr[2];
225
226
static uint8_t fx_cache[4];
227
228
static int32_t fx_mult_accumulator;
229
230
static const uint8_t vera_version_string[] = {'V',
231
	VERA_VERSION_MAJOR,
232
	VERA_VERSION_MINOR,
233
	VERA_VERSION_PATCH
234
};
235
236
float vga_scan_pos_x;
237
uint16_t vga_scan_pos_y;
238
float ntsc_half_cnt;
239
uint16_t ntsc_scan_pos_y;
240
int frame_count = 0;
241
242
// CPU cycles elapsed, accumulated from the counts video_step() is handed. Kept
243
// here rather than read from clockticks6502 so that video.c does not pull the
244
// CPU in: the eight test targets that link this file supply a host fixture, not
245
// a processor. The sum is exact -- main.c passes each instruction's cycle count
246
// once, and the mid-line raster calls pass zero.
247
static uint32_t sprite_trace_cpu_cycles = 0;
248
249
// The display line the beam is on right now, for stamping attribute writes.
250
// SPRITE_TRACE_NO_LINE outside the active display, where a write is an ordinary
251
// full-frame update rather than a raster-timed one.
252
static uint16_t
253
sprite_trace_current_line(void)
254
{
255
	int y;
256
	if (reg_composer[0] & 2) {
257
		y = (ntsc_scan_pos_y < SCAN_HEIGHT) ? (int)ntsc_scan_pos_y - NTSC_Y_OFFSET_LOW
258
		                                    : (int)ntsc_scan_pos_y - NTSC_Y_OFFSET_HIGH;
259
	} else {
260
		y = (int)vga_scan_pos_y - VGA_Y_OFFSET;
261
	}
262
	if (y < 0 || y >= SPRITE_TRACE_LINES) {
263
		return SPRITE_TRACE_NO_LINE;
264
	}
265
	return (uint16_t)y;
266
}
267
268
static uint8_t framebuffer[SCREEN_WIDTH * SCREEN_HEIGHT * 4];
269
#ifndef __EMSCRIPTEN__
270
static uint8_t png_buffer[SCREEN_WIDTH * SCREEN_HEIGHT * 3];
271
#endif
272
273
static GifWriter gif_writer;
274
275
static const uint16_t default_palette[] = {
276
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
277
};
278
279
uint8_t video_space_read(uint32_t address);
280
static void video_space_read_range(uint8_t* dest, uint32_t address, uint32_t size);
281
282
static void refresh_palette();
283
284
void
285
mousegrab_toggle() {
286
	mouse_grabbed = !mouse_grabbed;
287
	SDL_SetWindowGrab(window, mouse_grabbed && !no_keyboard_capture);
288
	SDL_SetRelativeMouseMode(mouse_grabbed);
289
	SDL_ShowCursor((mouse_grabbed || kernal_mouse_enabled) ? SDL_DISABLE : SDL_ENABLE);
290
	sprintf(window_title, WINDOW_TITLE "%s", mouse_grabbed ? MOUSE_GRAB_MSG : "");
291
	video_update_title(window_title);
292
}
293
294
// Re-derive the layer properties from the raw layer registers and seed both
295
// generations of the render pipeline from them. Called on reset: the derived
296
// properties are otherwise refreshed only when the guest writes a layer
297
// register, so clearing reg_layer alone left the derived state describing the
298
// pre-reset machine while the registers read zero -- and a debug view
299
// correlating the two would pair zeroed registers with stale geometry.
300
static void video_reset_layer_pipeline(void);
301
302
void
303
video_reset()
304
{
305
	// Drop the per-scanline debug history: it describes a machine that no
306
	// longer exists. Both pipeline generations are re-seeded from the cleared
307
	// registers below, so raw and derived state stay matched rather than one
308
	// side reading zero while the other still holds pre-reset geometry.
309
	memset(line_state_valid, 0, sizeof(line_state_valid));
310
311
	// init I/O registers
312
	memset(io_addr, 0, sizeof(io_addr));
313
	memset(io_inc, 0, sizeof(io_inc));
314
	io_addrsel = 0;
315
	io_dcsel = 0;
316
	io_rddata[0] = 0;
317
	io_rddata[1] = 0;
318
319
	ien = 0;
320
	isr = 0;
321
	irq_line = 0;
322
323
	// init Layer registers
324
	memset(reg_layer, 0, sizeof(reg_layer));
325
	video_reset_layer_pipeline();
326
327
	// init composer registers
328
	memset(reg_composer, 0, sizeof(reg_composer));
329
	reg_composer[1] = 128; // hscale = 1.0
330
	reg_composer[2] = 128; // vscale = 1.0
331
	reg_composer[5] = 640 >> 2;
332
	reg_composer[7] = 480 >> 1;
333
334
	// Initialize FX registers
335
	fx_addr1_mode = 0;
336
	fx_x_pixel_position = 0x8000;
337
	fx_y_pixel_position = 0x8000;
338
	fx_x_pixel_increment = 0;
339
	fx_y_pixel_increment = 0;
340
341
	fx_cache_write = false;
342
	fx_cache_fill = false;
343
	fx_4bit_mode = false;
344
	fx_16bit_hop = false;
345
	fx_subtract = false;
346
	fx_cache_byte_cycling = false;
347
	fx_trans_writes = false;
348
	fx_multiplier = false;
349
350
	fx_mult_accumulator = 0;
351
352
	fx_2bit_poly = false;
353
	fx_2bit_poking = false;
354
355
	fx_cache_nibble_index = 0;
356
	fx_cache_byte_index = 0;
357
	fx_cache_increment_mode = 0;
358
359
	fx_cache[0] = 0;
360
	fx_cache[1] = 0;
361
	fx_cache[2] = 0;
362
	fx_cache[3] = 0;
363
364
	fx_16bit_hop_align = 0;
365
366
	fx_nibble_bit[0] = false;
367
	fx_nibble_bit[1] = false;
368
	fx_nibble_incr[0] = false;
369
	fx_nibble_incr[1] = false;
370
371
	fx_poly_fill_length = 0;
372
	fx_affine_tile_base = 0;
373
	fx_affine_map_base = 0;
374
	fx_affine_map_size = 2;
375
	fx_affine_clip = false;
376
377
	// init sprite data
378
	memset(sprite_data, 0, sizeof(sprite_data));
379
380
	// copy palette
381
	memcpy(palette, default_palette, sizeof(palette));
382
	for (int i = 0; i < 256; i++) {
383
		palette[i * 2 + 0] = default_palette[i] & 0xff;
384
		palette[i * 2 + 1] = default_palette[i] >> 8;
385
	}
386
387
	refresh_palette();
388
389
	// fill video RAM with random data
390
	for (int i = 0; i < 128 * 1024; i++) {
391
		video_ram[i] = rand();
392
	}
393
394
	sprite_line_collisions = 0;
395
396
	vga_scan_pos_x = 0;
397
	vga_scan_pos_y = 0;
398
	ntsc_half_cnt = 0;
399
	ntsc_scan_pos_y = 0;
400
401
	psg_reset();
402
	pcm_reset();
403
}
404
405
// Repaint the emulator window straight from the last rendered frame. On
406
// Windows a window drag/resize spins the OS in a modal message loop that
407
// blocks our main loop, so no new frames are produced; without this the
408
// window freezes or tears while being moved. Registered as an SDL event
409
// watch, this fires synchronously from inside that modal loop and keeps the
410
// last good frame on screen. (The matching timing re-base in timing.c stops
411
// the post-drag fast-forward.)
412
static int SDLCALL
413
video_move_resize_watch(void *userdata, SDL_Event *event)
414
{
415
	(void)userdata;
416
	static bool in_watch = false; // guard against present -> pump -> watch recursion
417
	if (in_watch) {
418
		return 1;
419
	}
420
	if (event->type == SDL_WINDOWEVENT && window && renderer && sdlTexture &&
421
	    event->window.windowID == SDL_GetWindowID(window)) {
422
		switch (event->window.event) {
423
			case SDL_WINDOWEVENT_MOVED:
424
			case SDL_WINDOWEVENT_RESIZED:
425
			case SDL_WINDOWEVENT_SIZE_CHANGED:
426
			case SDL_WINDOWEVENT_EXPOSED:
427
				in_watch = true;
428
				SDL_RenderClear(renderer);
429
				SDL_RenderCopy(renderer, sdlTexture, NULL, NULL);
430
				SDL_RenderPresent(renderer);
431
				in_watch = false;
432
				break;
433
		}
434
	}
435
	return 1; // leave the event in the queue for normal handling
436
}
437
438
bool
439
video_init(int window_scale, float screen_x_scale, char *quality, bool fullscreen, float opacity)
440
{
441
	uint32_t window_flags = SDL_WINDOW_ALLOW_HIGHDPI;
442
443
#ifdef __EMSCRIPTEN__
444
	// Setting this flag would render the web canvas outside of its bounds on high dpi screens
445
	window_flags &= ~SDL_WINDOW_ALLOW_HIGHDPI;
446
#endif
447
448
	video_reset();
449
450
	SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, quality);
451
	SDL_SetHint(SDL_HINT_GRAB_KEYBOARD, "1"); // Grabs keyboard shortcuts from the system during window grab
452
	SDL_CreateWindowAndRenderer(SCREEN_WIDTH * window_scale * screen_x_scale, SCREEN_HEIGHT * window_scale, window_flags, &window, &renderer);
453
#ifndef __MORPHOS__
454
	SDL_SetWindowResizable(window, true);
455
#endif
456
	SDL_RenderSetLogicalSize(renderer, SCREEN_WIDTH * screen_x_scale, SCREEN_HEIGHT);
457
458
	sdlTexture = SDL_CreateTexture(renderer,
459
									SDL_PIXELFORMAT_RGB888,
460
									SDL_TEXTUREACCESS_STREAMING,
461
									SCREEN_WIDTH, SCREEN_HEIGHT);
462
463
	// Keep the window painted while the OS holds us in a modal move/resize loop.
464
	SDL_AddEventWatch(video_move_resize_watch, NULL);
465
466
	SDL_SetWindowTitle(window, WINDOW_TITLE);
467
	SDL_SetWindowIcon(window, CommanderX16Icon());
468
	if (fullscreen) {
469
		is_fullscreen = true;
470
		SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN);
471
	} else {
472
		int winX, winY;
473
		SDL_GetWindowPosition(window, &winX, &winY);
474
		if (winX < 0 || winY < APPROX_TITLEBAR_HEIGHT) {
475
			winX = winX < 0 ? 0 : winX;
476
			winY = winY < APPROX_TITLEBAR_HEIGHT ? APPROX_TITLEBAR_HEIGHT : winY;
477
			SDL_SetWindowPosition(window, winX, winY);
478
		}
479
	}
480
481
	SDL_SetWindowOpacity(window, opacity);
482
483
#ifdef _WIN32
484
	extern void video_win32_set_rounded_corners(SDL_Window *window);
485
	video_win32_set_rounded_corners(window);
486
	// Subclass the window proc so emulation keeps running during a drag; see
487
	// video_win32.c for why the SDL message hook is not enough.
488
	extern void video_win32_install_move_hook(SDL_Window *window);
489
	video_win32_install_move_hook(window);
490
#endif
491
492
	if (record_gif != RECORD_GIF_DISABLED) {
493
		if (!strcmp(gif_path+strlen(gif_path)-5, ",wait")) {
494
			// wait for POKE
495
			record_gif = RECORD_GIF_PAUSED;
496
			// move the string terminator to remove the ",wait"
497
			gif_path[strlen(gif_path)-5] = 0;
498
		} else {
499
			// start now
500
			record_gif = RECORD_GIF_ACTIVE;
501
		}
502
		if (!GifBegin(&gif_writer, gif_path, SCREEN_WIDTH, SCREEN_HEIGHT, 1, 8, false)) {
503
			record_gif = RECORD_GIF_DISABLED;
504
		}
505
	}
506
507
	if (debugger_enabled) {
508
		DEBUGInitUI(renderer);
509
	}
510
511
#ifdef HAS_IMGUI
512
	// ImGui debugger window -- additive, independent of the -debug SDL debugger.
513
	if (imgui_debugger_enabled) {
514
		int mainX, mainY, mainW;
515
		SDL_GetWindowPosition(window, &mainX, &mainY);
516
		SDL_GetWindowSize(window, &mainW, NULL);
517
518
		imgui_window = SDL_CreateWindow("Commander X16 - ImGui Debugger",
519
			mainX + mainW + 4, mainY,
520
			960, 720,
521
			SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
522
		if (imgui_window) {
523
			// Deliberately NOT SDL_RENDERER_PRESENTVSYNC. This renderer is
524
			// presented from the emulator thread, so a vsync'd present would
525
			// block it for up to a refresh interval every frame -- capping the
526
			// machine at the monitor's rate and defeating warp mode entirely.
527
			// The main window does not vsync either; timing.c does the
528
			// throttling. debug_ui_render() is rate-limited instead (see
529
			// video_render_all_ex).
530
			imgui_renderer = SDL_CreateRenderer(imgui_window, -1, SDL_RENDERER_ACCELERATED);
531
		}
532
		if (imgui_window && imgui_renderer) {
533
			imgui_window_id = SDL_GetWindowID(imgui_window);
534
			debug_ui_init(imgui_window, imgui_renderer);
535
#ifdef _WIN32
536
			// Keep both windows painted while the OS holds us in a modal
537
			// move/resize loop dragging the debugger window. Emulation does
538
			// *not* continue through a drag once a debugger is up --
539
			// emulator_step_during_move() returns early for exactly that
540
			// reason, so breakpoints cannot be stepped past behind the UI's
541
			// back -- but without this the windows would freeze mid-drag.
542
			video_win32_install_move_hook(imgui_window);
543
#endif
544
		} else {
545
			printf("Warning: Failed to create debugger window: %s\n", SDL_GetError());
546
			if (imgui_renderer) { SDL_DestroyRenderer(imgui_renderer); imgui_renderer = NULL; }
547
			if (imgui_window) { SDL_DestroyWindow(imgui_window); imgui_window = NULL; }
548
			// Stop claiming the UI exists. Left set, -imgui alone would still
549
			// route the emulator loop through DEBUGGetCurrentStatus() and stop
550
			// at breakpoints, with no window to resume from and no text
551
			// debugger either -- the machine would simply hang.
552
			imgui_debugger_enabled = false;
553
			printf("The graphical debugger is disabled for this session.\n");
554
			// Same reasoning as the headless path in main.c: the .dbg policy
555
			// was noted from a flag that has just been cleared.
556
			dbg_load_note_debugger(debugger_enabled);
557
		}
558
	}
559
#endif
560
561
	if (grab_mouse && !mouse_grabbed)
562
		mousegrab_toggle();
563
564
	return true;
565
}
566
567
struct video_layer_properties
568
{
569
	uint8_t color_depth;
570
	uint32_t map_base;
571
	uint32_t tile_base;
572
573
	bool text_mode;
574
	bool text_mode_256c;
575
	bool tile_mode;
576
	bool bitmap_mode;
577
578
	uint16_t hscroll;
579
	uint16_t vscroll;
580
581
	uint8_t  mapw_log2;
582
	uint8_t  maph_log2;
583
	uint16_t tilew;
584
	uint16_t tileh;
585
	uint8_t  tilew_log2;
586
	uint8_t  tileh_log2;
587
588
	uint16_t mapw_max;
589
	uint16_t maph_max;
590
	uint16_t tilew_max;
591
	uint16_t tileh_max;
592
	uint16_t layerw_max;
593
	uint16_t layerh_max;
594
595
	uint8_t tile_size_log2;
596
597
	int min_eff_x;
598
	int max_eff_x;
599
600
	uint8_t bits_per_pixel;
601
	uint8_t first_color_pos;
602
	uint8_t color_mask;
603
	uint8_t color_fields_max;
604
};
605
606
#define NUM_LAYERS 2
607
struct video_layer_properties layer_properties[NUM_LAYERS];
608
struct video_layer_properties prev_layer_properties[2][NUM_LAYERS];
609
610
static int
611
calc_layer_eff_x(const struct video_layer_properties *props, const int x)
612
{
613
	return (x + props->hscroll) & (props->layerw_max);
614
}
615
616
static int
617
calc_layer_eff_y(const struct video_layer_properties *props, const int y)
618
{
619
	return (y + props->vscroll) & (props->layerh_max);
620
}
621
622
static uint32_t
623
calc_layer_map_addr_base2(const struct video_layer_properties *props, const int eff_x, const int eff_y)
624
{
625
	// Slightly faster on some platforms because we know that tilew and tileh are powers of 2.
626
	return props->map_base + ((((eff_y >> props->tileh_log2) << props->mapw_log2) + (eff_x >> props->tilew_log2)) << 1);
627
}
628
629
//TODO: Unused in all current cases. Delete? Or leave commented as a reminder?
630
//static uint32_t
631
//calc_layer_map_addr(struct video_layer_properties *props, int eff_x, int eff_y)
632
//{
633
//	return props->map_base + ((eff_y / props->tileh) * props->mapw + (eff_x / props->tilew)) * 2;
634
//}
635
static void
636
refresh_layer_properties(const uint8_t layer)
637
{
638
	struct video_layer_properties* props = &layer_properties[layer];
639
640
	uint16_t prev_layerw_max = props->layerw_max;
641
	uint16_t prev_hscroll = props->hscroll;
642
643
	props->color_depth    = reg_layer[layer][0] & 0x3;
644
	props->map_base       = reg_layer[layer][1] << 9;
645
	props->tile_base      = (reg_layer[layer][2] & 0xFC) << 9;
646
	props->bitmap_mode    = (reg_layer[layer][0] & 0x4) != 0;
647
	props->text_mode      = (props->color_depth == 0) && !props->bitmap_mode;
648
	props->text_mode_256c = (reg_layer[layer][0] & 8) != 0;
649
	props->tile_mode      = !props->bitmap_mode && !props->text_mode;
650
651
	if (!props->bitmap_mode) {
652
		props->hscroll = reg_layer[layer][3] | (reg_layer[layer][4] & 0xf) << 8;
653
		props->vscroll = reg_layer[layer][5] | (reg_layer[layer][6] & 0xf) << 8;
654
	} else {
655
		props->hscroll = 0;
656
		props->vscroll = 0;
657
	}
658
659
	uint16_t mapw = 0;
660
	uint16_t maph = 0;
661
	props->tilew = 0;
662
	props->tileh = 0;
663
664
	if (props->tile_mode || props->text_mode) {
665
		props->mapw_log2 = 5 + ((reg_layer[layer][0] >> 4) & 3);
666
		props->maph_log2 = 5 + ((reg_layer[layer][0] >> 6) & 3);
667
		mapw      = 1 << props->mapw_log2;
668
		maph      = 1 << props->maph_log2;
669
670
		// Scale the tiles or text characters according to TILEW and TILEH.
671
		props->tilew_log2 = 3 + (reg_layer[layer][2] & 1);
672
		props->tileh_log2 = 3 + ((reg_layer[layer][2] >> 1) & 1);
673
		props->tilew      = 1 << props->tilew_log2;
674
		props->tileh      = 1 << props->tileh_log2;
675
	} else if (props->bitmap_mode) {
676
		// bitmap mode is basically tiled mode with a single huge tile
677
		props->tilew = (reg_layer[layer][2] & 1) ? 640 : 320;
678
		props->tileh = SCREEN_HEIGHT;
679
	}
680
681
	// 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,
682
	// so there's no need to modulo against them if we have bitmasks we can bitwise-and against.
683
684
	props->mapw_max = mapw - 1;
685
	props->maph_max = maph - 1;
686
	props->tilew_max = props->tilew - 1;
687
	props->tileh_max = props->tileh - 1;
688
	props->layerw_max = (mapw * props->tilew) - 1;
689
	props->layerh_max = (maph * props->tileh) - 1;
690
691
	// Find min/max eff_x for bulk reading in tile data during draw.
692
	if (prev_layerw_max != props->layerw_max || prev_hscroll != props->hscroll) {
693
		int min_eff_x = INT_MAX;
694
		int max_eff_x = INT_MIN;
695
		for (int x = 0; x < SCREEN_WIDTH; ++x) {
696
			int eff_x = calc_layer_eff_x(props, x);
697
			if (eff_x < min_eff_x) {
698
				min_eff_x = eff_x;
699
			}
700
			if (eff_x > max_eff_x) {
701
				max_eff_x = eff_x;
702
			}
703
		}
704
		props->min_eff_x = min_eff_x;
705
		props->max_eff_x = max_eff_x;
706
	}
707
708
	props->bits_per_pixel = 1 << props->color_depth;
709
	props->tile_size_log2 = props->tilew_log2 + props->tileh_log2 + props->color_depth - 3;
710
711
	props->first_color_pos  = 8 - props->bits_per_pixel;
712
	props->color_mask       = (1 << props->bits_per_pixel) - 1;
713
	props->color_fields_max = (8 >> props->color_depth) - 1;
714
}
715
716
// See the forward declaration above video_reset() for why this exists.
717
static void
718
video_reset_layer_pipeline(void)
719
{
720
	refresh_layer_properties(0);
721
	refresh_layer_properties(1);
722
	memcpy(prev_layer_properties[0], layer_properties, sizeof(*layer_properties) * NUM_LAYERS);
723
	memcpy(prev_layer_properties[1], layer_properties, sizeof(*layer_properties) * NUM_LAYERS);
724
	memset(prev_reg_layer, 0, sizeof(prev_reg_layer));
725
}
726
727
struct video_sprite_properties
728
{
729
	int8_t sprite_zdepth;
730
	uint8_t sprite_collision_mask;
731
732
	int16_t sprite_x;
733
	int16_t sprite_y;
734
	uint8_t sprite_width_log2;
735
	uint8_t sprite_height_log2;
736
	uint8_t sprite_width;
737
	uint8_t sprite_height;
738
739
	bool hflip;
740
	bool vflip;
741
742
	uint8_t color_mode;
743
	uint32_t sprite_address;
744
745
	uint16_t palette_offset;
746
};
747
748
#ifndef __EMSCRIPTEN__
749
static void
750
screenshot(void)
751
{
752
	char path[PATH_MAX];
753
	const time_t now = time(NULL);
754
	strftime(path, PATH_MAX, "x16emu-%Y-%m-%d-%H-%M-%S.png", localtime(&now));
755
756
	memset(png_buffer, 0, SCREEN_WIDTH * SCREEN_HEIGHT * 3);
757
758
	// The framebuffer stores pixels in BRGA but we want RGB:
759
	for (int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; i++) {
760
		png_buffer[(i*3)+0] = framebuffer[(i*4)+2];
761
		png_buffer[(i*3)+1] = framebuffer[(i*4)+1];
762
		png_buffer[(i*3)+2] = framebuffer[(i*4)+0];
763
	}
764
765
	if (stbi_write_png(path, SCREEN_WIDTH, SCREEN_HEIGHT, 3, png_buffer, SCREEN_WIDTH*3)) {
766
		printf("Wrote screenshot to %s\n", path);
767
	} else {
768
		printf("WARNING: Couldn't write screenshot to %s\n", path);
769
	}
770
}
771
#endif
772
773
struct video_sprite_properties sprite_properties[128];
774
775
static void
776
refresh_sprite_properties(const uint16_t sprite)
777
{
778
	struct video_sprite_properties* props = &sprite_properties[sprite];
779
780
	props->sprite_zdepth = (sprite_data[sprite][6] >> 2) & 3;
781
	props->sprite_collision_mask = sprite_data[sprite][6] & 0xf0;
782
783
	props->sprite_x = sprite_data[sprite][2] | (sprite_data[sprite][3] & 3) << 8;
784
	props->sprite_y = sprite_data[sprite][4] | (sprite_data[sprite][5] & 3) << 8;
785
	props->sprite_width_log2  = (((sprite_data[sprite][7] >> 4) & 3) + 3);
786
	props->sprite_height_log2 = ((sprite_data[sprite][7] >> 6) + 3);
787
	props->sprite_width       = 1 << props->sprite_width_log2;
788
	props->sprite_height      = 1 << props->sprite_height_log2;
789
790
	// fix up negative coordinates
791
	if (props->sprite_x >= 0x400 - props->sprite_width) {
792
		props->sprite_x -= 0x400;
793
	}
794
	if (props->sprite_y >= 0x400 - props->sprite_height) {
795
		props->sprite_y -= 0x400;
796
	}
797
798
	props->hflip = sprite_data[sprite][6] & 1;
799
	props->vflip = (sprite_data[sprite][6] >> 1) & 1;
800
801
	props->color_mode     = (sprite_data[sprite][1] >> 7) & 1;
802
	props->sprite_address = sprite_data[sprite][0] << 5 | (sprite_data[sprite][1] & 0xf) << 13;
803
804
	props->palette_offset = (sprite_data[sprite][7] & 0x0f) << 4;
805
}
806
807
// Store one byte of a sprite's attributes, telling the trace what it replaced.
808
// Both VRAM write paths funnel through here so the two cannot drift apart: a
809
// multiplexer driven by VERA FX would otherwise go unrecorded.
810
static void
811
sprite_attr_write(uint32_t address, uint8_t value)
812
{
813
	const uint8_t slot   = (address >> 3) & 0x7f;
814
	const uint8_t offset = address & 0x7;
815
	if (sprite_trace_active) {
816
		sprite_trace_note_write(slot, offset, sprite_data[slot][offset], value,
817
		                        sprite_trace_current_line(), sprite_trace_cpu_cycles);
818
	}
819
	sprite_data[slot][offset] = value;
820
	refresh_sprite_properties(slot);
821
}
822
823
struct video_palette
824
{
825
	uint32_t entries[256];
826
	bool dirty;
827
};
828
829
struct video_palette video_palette;
830
831
static void
832
refresh_palette() {
833
	const uint8_t out_mode = reg_composer[0] & 3;
834
	const bool chroma_disable = ((reg_composer[0] & 0x07) == 6);
835
	for (int i = 0; i < 256; ++i) {
836
		uint8_t r;
837
		uint8_t g;
838
		uint8_t b;
839
		if (out_mode == 0) {
840
			// video generation off
841
			// -> show blue screen
842
			r = 0;
843
			g = 0;
844
			b = 255;
845
		} else {
846
			uint16_t entry = palette[i * 2] | palette[i * 2 + 1] << 8;
847
			r = ((entry >> 8) & 0xf) << 4 | ((entry >> 8) & 0xf);
848
			g = ((entry >> 4) & 0xf) << 4 | ((entry >> 4) & 0xf);
849
			b = (entry & 0xf) << 4 | (entry & 0xf);
850
			if (chroma_disable) {
851
				r = g = b = (r + b + g) / 3;
852
			}
853
		}
854
855
		video_palette.entries[i] = (uint32_t)(r << 16) | ((uint32_t)g << 8) | ((uint32_t)b);
856
	}
857
	video_palette.dirty = false;
858
}
859
860
static void
861
expand_4bpp_data(uint8_t *dst, const uint8_t *src, int dst_size)
862
{
863
	while (dst_size >= 2) {
864
		*dst = (*src) >> 4;
865
		++dst;
866
		*dst = (*src) & 0xf;
867
		++dst;
868
869
		++src;
870
		dst_size -= 2;
871
	}
872
}
873
874
static void
875
render_sprite_line(const uint16_t y)
876
{
877
	memset(sprite_line_col, 0, SCREEN_WIDTH);
878
	memset(sprite_line_z, 0, SCREEN_WIDTH);
879
	memset(sprite_line_mask, 0, SCREEN_WIDTH);
880
881
	// Timing follows sprite_renderer.v, which runs two state machines against
882
	// one shared line timer:
883
	//
884
	//   sprite_renderer.v:44   wire render_time_done = (render_time_r == 'd798);
885
	//   sprite_renderer.v:171  if (render_time_done) sf_state_next = SF_DONE;
886
	//   sprite_renderer.v:349  if (render_time_done) state_next = STATE_DONE;
887
	//
888
	// render_time_r is reset by line_render_start and counts every clock
889
	// afterwards, so the limit is elapsed time for the line, not a quantity of
890
	// work. When it expires both machines are forced to DONE and stay there
891
	// until the next line: the search stops, and the sprite in flight stops
892
	// part-drawn. Sprites are never free -- once the line is out of time,
893
	// everything after it is simply not on screen.
894
	//
895
	// SF_FIND_SPRITE (sprite_renderer.v:146-160) advances one slot per clock
896
	// whenever the slot is disabled or off the line, and it does so whether or
897
	// not the line renderer is busy, so stepping over empty slots overlaps with
898
	// drawing. Only a slot that needs rendering stalls, waiting on !render_busy.
899
	// That overlap is why `t` and `render_free_at` are tracked separately;
900
	// charging all 128 lookups serially would drop sprites the hardware draws.
901
	//
902
	// Known divergence: STATE_WAIT_FETCH is charged one clock, its best case.
903
	// Real VERA shares the VRAM bus with the layer renderers, so a contended
904
	// fetch takes longer and slightly fewer sprites fit than modelled here.
905
	const bool trace = sprite_trace_active;
906
	uint16_t   traced_evaluated = 0;
907
	uint16_t   traced_drawn     = 0;
908
	uint16_t   traced_cut_slot  = SPRITE_TRACE_NO_LINE;
909
	uint16_t   traced_dropped   = 0;
910
	uint32_t   traced_tail      = 0; // cost of the part-drawn sprite left unfinished
911
	if (trace) {
912
		sprite_trace_line_begin(y, sprite_trace_cpu_cycles);
913
	}
914
915
	uint32_t t              = 0; // render_time_r: clocks used on this line
916
	uint32_t render_free_at = 0; // clock the line renderer returns to STATE_IDLE
917
	int      stopped_at     = NUM_SPRITES; // first slot the line never reached
918
919
	for (int i = 0; i < NUM_SPRITES; i++) {
920
		if (t >= SPRITE_RENDER_TIME) {
921
			// render_time_done: SF_DONE, so this slot is never looked at.
922
			stopped_at = i;
923
			break;
924
		}
925
926
		const uint32_t t_before = t;
927
		uint8_t        tflags   = SPRITE_TRACE_EVALUATED;
928
		const struct video_sprite_properties *props = &sprite_properties[i];
929
930
		const bool on_line = props->sprite_zdepth != 0 && y >= props->sprite_y &&
931
		                     y < props->sprite_y + props->sprite_height;
932
933
		if (!on_line) {
934
			t++; // one clock to step over the slot, overlapped with rendering
935
			if (trace) {
936
				traced_evaluated++;
937
				sprite_trace_note_slot(y, (uint8_t)i, sprite_data[i],
938
				                       (uint16_t)(t - t_before), tflags);
939
			}
940
			continue;
941
		}
942
943
		tflags |= SPRITE_TRACE_ONSCREEN;
944
945
		// SF_FIND_SPRITE holds here until the line renderer is idle, then takes
946
		// two clocks to hand the sprite over (SF_FIND_SPRITE, SF_START_RENDER).
947
		if (t < render_free_at) {
948
			t = render_free_at;
949
		}
950
		if (t >= SPRITE_RENDER_TIME) {
951
			stopped_at = i;
952
			break;
953
		}
954
		t += 2;
955
956
		const uint16_t eff_sy = props->vflip ? ((props->sprite_height - 1) - (y - props->sprite_y)) : (y - props->sprite_y);
957
958
		int16_t       eff_sx      = (props->hflip ? (props->sprite_width - 1) : 0);
959
		const int16_t eff_sx_incr = props->hflip ? -1 : 1;
960
961
		const uint8_t *bitmap_data = video_ram + props->sprite_address + (eff_sy << (props->sprite_width_log2 - (1 - props->color_mode)));
962
963
		uint8_t unpacked_sprite_line[64];
964
		const uint16_t width = (props->sprite_width<64? props->sprite_width : 64);
965
		const uint8_t vram_fetch_mask = ((2 - props->color_mode) << 2) - 1;
966
		if (props->color_mode == 0) {
967
			// 4bpp
968
			expand_4bpp_data(unpacked_sprite_line, bitmap_data, width);
969
		} else {
970
			// 8bpp
971
			memcpy(unpacked_sprite_line, bitmap_data, width);
972
		}
973
974
		// The line renderer's own clock, running from STATE_IDLE's single clock
975
		// into the fetch/render loop.
976
		uint32_t rt        = t + 1;
977
		bool     truncated = false;
978
		uint16_t sx        = 0;
979
980
		for (; sx < props->sprite_width; ++sx) {
981
			// one clock per fetched 32 bits
982
			if (!(sx & vram_fetch_mask)) {
983
				if (rt >= SPRITE_RENDER_TIME) { truncated = true; break; }
984
				rt++;
985
			}
986
987
			// one clock per pixel, charged whether or not the pixel lands on
988
			// screen: STATE_RENDER steps linebuf_idx_r past the edge just the
989
			// same (sprite_renderer.v:317-319), so a sprite hanging off the
990
			// side costs its full width.
991
			if (rt >= SPRITE_RENDER_TIME) { truncated = true; break; }
992
			rt++;
993
994
			const uint16_t line_x = props->sprite_x + sx;
995
			if (line_x >= SCREEN_WIDTH) {
996
				eff_sx += eff_sx_incr;
997
				continue;
998
			}
999
1000
			uint8_t col_index = unpacked_sprite_line[eff_sx];
1001
			eff_sx += eff_sx_incr;
1002
1003
			// palette offset
1004
			if (col_index > 0) {
1005
				sprite_line_collisions |= sprite_line_mask[line_x] & props->sprite_collision_mask;
1006
				sprite_line_mask[line_x] |= props->sprite_collision_mask;
1007
1008
				if (props->sprite_zdepth > sprite_line_z[line_x]) {
1009
					if (col_index < 16) {
1010
						col_index += props->palette_offset;
1011
					}
1012
					sprite_line_col[line_x] = col_index;
1013
					sprite_line_z[line_x] = props->sprite_zdepth;
1014
					tflags |= SPRITE_TRACE_DREW;
1015
				}
1016
			}
1017
		}
1018
1019
		render_free_at = rt;
1020
1021
		if (trace) {
1022
			traced_evaluated++;
1023
			if (tflags & SPRITE_TRACE_DREW) {
1024
				traced_drawn++;
1025
			}
1026
			if (truncated) {
1027
				tflags |= SPRITE_TRACE_CUT;
1028
				if (traced_cut_slot == SPRITE_TRACE_NO_LINE) {
1029
					traced_cut_slot = (uint16_t)i;
1030
				}
1031
				// Time can run out before this sprite lands a single pixel, in
1032
				// which case it is as dropped as the ones the search never
1033
				// reached, and has to be counted with them.
1034
				if (!(tflags & SPRITE_TRACE_DREW)) {
1035
					traced_dropped++;
1036
				}
1037
				const uint32_t left = (uint32_t)(props->sprite_width - sx);
1038
				traced_tail = left + left / (vram_fetch_mask + 1u);
1039
			}
1040
			sprite_trace_note_slot(y, (uint8_t)i, sprite_data[i],
1041
			                       (uint16_t)(rt - t_before), tflags);
1042
		}
1043
1044
		if (truncated) {
1045
			// render_time_done fired mid-sprite: the rest of the line is gone.
1046
			stopped_at = i + 1;
1047
			break;
1048
		}
1049
	}
1050
1051
	if (!trace) {
1052
		return;
1053
	}
1054
1055
	uint32_t used = (t > render_free_at ? t : render_free_at);
1056
	if (used > SPRITE_RENDER_TIME) {
1057
		used = SPRITE_RENDER_TIME;
1058
	}
1059
	uint32_t demand = used + traced_tail;
1060
1061
	// What the line would have needed had it not run out. Pure arithmetic --
1062
	// no VRAM is read and nothing is drawn -- so "how far over was this line"
1063
	// survives the sprites being dropped, which is the number a multiplexer is
1064
	// actually tuning against.
1065
	for (int i = stopped_at; i < NUM_SPRITES; i++) {
1066
		const struct video_sprite_properties *p = &sprite_properties[i];
1067
		const bool on_line = p->sprite_zdepth != 0 && y >= p->sprite_y &&
1068
		                     y < p->sprite_y + p->sprite_height;
1069
		if (!on_line) {
1070
			demand++;
1071
			sprite_trace_note_slot(y, (uint8_t)i, sprite_data[i], 0, SPRITE_TRACE_EVALUATED);
1072
			continue;
1073
		}
1074
		const uint16_t per_fetch = (uint16_t)((2 - p->color_mode) << 2);
1075
		demand += 3u + p->sprite_width + (uint32_t)(p->sprite_width / per_fetch);
1076
		traced_dropped++;
1077
		if (traced_cut_slot == SPRITE_TRACE_NO_LINE) {
1078
			traced_cut_slot = (uint16_t)i;
1079
		}
1080
		sprite_trace_note_slot(y, (uint8_t)i, sprite_data[i], 0,
1081
		                       (uint8_t)(SPRITE_TRACE_EVALUATED | SPRITE_TRACE_ONSCREEN |
1082
		                                 SPRITE_TRACE_CUT));
1083
	}
1084
1085
	sprite_line_stat_t st;
1086
	memset(&st, 0, sizeof(st));
1087
	st.budget_used = (uint16_t)used;
1088
	st.demand      = demand;
1089
	st.evaluated   = traced_evaluated;
1090
	st.drawn       = traced_drawn;
1091
	st.dropped     = traced_dropped;
1092
	st.cut_slot    = traced_cut_slot;
1093
	st.exhausted   = (stopped_at < NUM_SPRITES);
1094
	sprite_trace_line_end(y, &st);
1095
}
1096
static void
1097
render_layer_line_text(uint8_t layer, uint16_t y)
1098
{
1099
	const struct video_layer_properties *props = &prev_layer_properties[1][layer];
1100
	const struct video_layer_properties *props0 = &prev_layer_properties[0][layer];
1101
1102
	const uint8_t max_pixels_per_byte = (8 >> props->color_depth) - 1;
1103
	const int     eff_y               = calc_layer_eff_y(props0, y);
1104
	const int     yy                  = eff_y & props->tileh_max;
1105
1106
	// additional bytes to reach the correct line of the tile
1107
	const uint32_t y_add = (yy << props->tilew_log2) >> 3;
1108
1109
	const uint32_t map_addr_begin = calc_layer_map_addr_base2(props, props->min_eff_x, eff_y);
1110
	const uint32_t map_addr_end   = calc_layer_map_addr_base2(props, props->max_eff_x, eff_y);
1111
	const int      size           = (map_addr_end - map_addr_begin) + 2;
1112
1113
	uint8_t tile_bytes[512]; // max 256 tiles, 2 bytes each.
1114
	video_space_read_range(tile_bytes, map_addr_begin, size);
1115
1116
	uint32_t tile_start;
1117
1118
	uint8_t  fg_color;
1119
	uint8_t  bg_color;
1120
	uint8_t  s;
1121
	uint8_t  color_shift;
1122
1123
	{
1124
		const int eff_x = calc_layer_eff_x(props, 0);
1125
		const int xx    = eff_x & props->tilew_max;
1126
1127
		// extract all information from the map
1128
		const uint32_t map_addr = calc_layer_map_addr_base2(props, eff_x, eff_y) - map_addr_begin;
1129
1130
		const uint8_t tile_index = tile_bytes[map_addr];
1131
		const uint8_t byte1      = tile_bytes[map_addr + 1];
1132
1133
		if (!props->text_mode_256c) {
1134
			fg_color = byte1 & 15;
1135
			bg_color = byte1 >> 4;
1136
		} else {
1137
			fg_color = byte1;
1138
			bg_color = 0;
1139
		}
1140
1141
		// offset within tilemap of the current tile
1142
		tile_start = tile_index << props->tile_size_log2;
1143
1144
		// additional bytes to reach the correct column of the tile
1145
		const uint16_t x_add       = xx >> 3;
1146
		const uint32_t tile_offset = tile_start + y_add + x_add;
1147
1148
		s           = video_space_read(props->tile_base + tile_offset);
1149
		color_shift = max_pixels_per_byte - (xx & 0x7);
1150
	}
1151
1152
	// Render tile line.
1153
	for (int x = 0; x < SCREEN_WIDTH; x++) {
1154
		// Scrolling
1155
		const int eff_x = calc_layer_eff_x(props, x);
1156
		const int xx = eff_x & props->tilew_max;
1157
1158
		if ((eff_x & 0x7) == 0) {
1159
			if ((eff_x & props->tilew_max) == 0) {
1160
				// extract all information from the map
1161
				const uint32_t map_addr = calc_layer_map_addr_base2(props, eff_x, eff_y) - map_addr_begin;
1162
1163
				const uint8_t tile_index = tile_bytes[map_addr];
1164
				const uint8_t byte1      = tile_bytes[map_addr + 1];
1165
1166
				if (!props->text_mode_256c) {
1167
					fg_color = byte1 & 15;
1168
					bg_color = byte1 >> 4;
1169
				} else {
1170
					fg_color = byte1;
1171
					bg_color = 0;
1172
				}
1173
1174
				// offset within tilemap of the current tile
1175
				tile_start = tile_index << props->tile_size_log2;
1176
			}
1177
1178
			// additional bytes to reach the correct column of the tile
1179
			const uint16_t x_add       = xx >> 3;
1180
			const uint32_t tile_offset = tile_start + y_add + x_add;
1181
1182
			s           = video_space_read(props->tile_base + tile_offset);
1183
			color_shift = max_pixels_per_byte;
1184
		}
1185
1186
		// convert tile byte to indexed color
1187
		const uint8_t col_index = (s >> color_shift) & 1;
1188
		--color_shift;
1189
		layer_line[layer][x] = col_index ? fg_color : bg_color;
1190
	}
1191
}
1192
1193
static void
1194
render_layer_line_tile(uint8_t layer, uint16_t y)
1195
{
1196
	const struct video_layer_properties *props = &prev_layer_properties[1][layer];
1197
	const struct video_layer_properties *props0 = &prev_layer_properties[0][layer];
1198
1199
	const uint8_t max_pixels_per_byte = (8 >> props->color_depth) - 1;
1200
	const int     eff_y               = calc_layer_eff_y(props0, y);
1201
	const uint8_t yy                  = eff_y & props->tileh_max;
1202
	const uint8_t yy_flip             = yy ^ props->tileh_max;
1203
	const uint32_t y_add              = (yy << ((props->tilew_log2 + props->color_depth - 3) & 31));
1204
	const uint32_t y_add_flip         = (yy_flip << ((props->tilew_log2 + props->color_depth - 3) & 31));
1205
1206
	const uint32_t map_addr_begin = calc_layer_map_addr_base2(props, props->min_eff_x, eff_y);
1207
	const uint32_t map_addr_end   = calc_layer_map_addr_base2(props, props->max_eff_x, eff_y);
1208
	const int      size           = (map_addr_end - map_addr_begin) + 2;
1209
1210
	uint8_t tile_bytes[512]; // max 256 tiles, 2 bytes each.
1211
	video_space_read_range(tile_bytes, map_addr_begin, size);
1212
1213
	uint8_t  palette_offset;
1214
	bool     vflip;
1215
	bool     hflip;
1216
	uint32_t tile_start;
1217
	uint8_t  s;
1218
	uint8_t  color_shift;
1219
	int8_t   color_shift_incr;
1220
1221
	{
1222
		const int eff_x = calc_layer_eff_x(props, 0);
1223
1224
		// extract all information from the map
1225
		const uint32_t map_addr = calc_layer_map_addr_base2(props, eff_x, eff_y) - map_addr_begin;
1226
1227
		const uint8_t byte0 = tile_bytes[map_addr];
1228
		const uint8_t byte1 = tile_bytes[map_addr + 1];
1229
1230
		// Tile Flipping
1231
		vflip = (byte1 >> 3) & 1;
1232
		hflip = (byte1 >> 2) & 1;
1233
1234
		palette_offset = byte1 & 0xf0;
1235
1236
		// offset within tilemap of the current tile
1237
		const uint16_t tile_index = byte0 | ((byte1 & 3) << 8);
1238
		tile_start                = tile_index << props->tile_size_log2;
1239
1240
		color_shift_incr = hflip ? props->bits_per_pixel : -props->bits_per_pixel;
1241
1242
		int xx = eff_x & props->tilew_max;
1243
		if (hflip) {
1244
			xx          = xx ^ (props->tilew_max);
1245
			color_shift = 0;
1246
		} else {
1247
			color_shift = props->first_color_pos;
1248
		}
1249
1250
		// additional bytes to reach the correct column of the tile
1251
		uint16_t x_add       = (xx << props->color_depth) >> 3;
1252
		uint32_t tile_offset = tile_start + (vflip ? y_add_flip : y_add) + x_add;
1253
1254
		s = video_space_read(props->tile_base + tile_offset);
1255
	}
1256
1257
1258
	// Render tile line.
1259
	for (int x = 0; x < SCREEN_WIDTH; x++) {
1260
		const int eff_x = calc_layer_eff_x(props, x);
1261
1262
		if ((eff_x & max_pixels_per_byte) == 0) {
1263
			if ((eff_x & props->tilew_max) == 0) {
1264
				// extract all information from the map
1265
				const uint32_t map_addr = calc_layer_map_addr_base2(props, eff_x, eff_y) - map_addr_begin;
1266
1267
				const uint8_t byte0 = tile_bytes[map_addr];
1268
				const uint8_t byte1 = tile_bytes[map_addr + 1];
1269
1270
				// Tile Flipping
1271
				vflip = (byte1 >> 3) & 1;
1272
				hflip = (byte1 >> 2) & 1;
1273
1274
				palette_offset = byte1 & 0xf0;
1275
1276
				// offset within tilemap of the current tile
1277
				const uint16_t tile_index = byte0 | ((byte1 & 3) << 8);
1278
				tile_start                = tile_index << props->tile_size_log2;
1279
1280
				color_shift_incr = hflip ? props->bits_per_pixel : -props->bits_per_pixel;
1281
			}
1282
1283
			int xx = eff_x & props->tilew_max;
1284
			if (hflip) {
1285
				xx = xx ^ (props->tilew_max);
1286
				color_shift = 0;
1287
			} else {
1288
				color_shift = props->first_color_pos;
1289
			}
1290
1291
			// additional bytes to reach the correct column of the tile
1292
			const uint16_t x_add       = (xx << props->color_depth) >> 3;
1293
			const uint32_t tile_offset = tile_start + (vflip ? y_add_flip : y_add) + x_add;
1294
1295
			s = video_space_read(props->tile_base + tile_offset);
1296
		}
1297
1298
		// convert tile byte to indexed color
1299
		uint8_t col_index = (s >> color_shift) & props->color_mask;
1300
		color_shift += color_shift_incr;
1301
1302
		// Apply Palette Offset
1303
		if (col_index > 0 && col_index < 16) {
1304
			col_index += palette_offset;
1305
			if (props->text_mode_256c) {
1306
				col_index |= 0x80;
1307
			}
1308
		}
1309
		layer_line[layer][x] = col_index;
1310
	}
1311
}
1312
1313
1314
static void
1315
render_layer_line_bitmap(uint8_t layer, uint16_t y)
1316
{
1317
	const struct video_layer_properties *props = &prev_layer_properties[1][layer];
1318
//	const struct video_layer_properties *props0 = &prev_layer_properties[0][layer];
1319
1320
	int yy = y % props->tileh;
1321
	// additional bytes to reach the correct line of the tile
1322
	uint32_t y_add = (yy * props->tilew * props->bits_per_pixel) >> 3;
1323
1324
	// Render tile line.
1325
	for (int x = 0; x < SCREEN_WIDTH; x++) {
1326
		int xx = x % props->tilew;
1327
1328
		// extract all information from the map
1329
		uint8_t palette_offset = reg_layer[layer][4] & 0xf;
1330
1331
		// additional bytes to reach the correct column of the tile
1332
		uint16_t x_add = (xx * props->bits_per_pixel) >> 3;
1333
		uint32_t tile_offset = y_add + x_add;
1334
		uint8_t s = video_space_read(props->tile_base + tile_offset);
1335
1336
		// convert tile byte to indexed color
1337
		uint8_t col_index = (s >> (props->first_color_pos - ((xx & props->color_fields_max) << props->color_depth))) & props->color_mask;
1338
1339
		// Apply Palette Offset
1340
		if (col_index > 0 && col_index < 16) {
1341
			col_index += palette_offset << 4;
1342
			if (props->text_mode_256c) {
1343
				col_index |= 0x80;
1344
			}
1345
		}
1346
		layer_line[layer][x] = col_index;
1347
	}
1348
}
1349
1350
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)
1351
{
1352
	uint8_t col_index = 0;
1353
	switch (spr_zindex) {
1354
		case 3:
1355
			col_index = spr_col_index ? spr_col_index : (l2_col_index ? l2_col_index : l1_col_index);
1356
			break;
1357
		case 2:
1358
			col_index = l2_col_index ? l2_col_index : (spr_col_index ? spr_col_index : l1_col_index);
1359
			break;
1360
		case 1:
1361
			col_index = l2_col_index ? l2_col_index : (l1_col_index ? l1_col_index : spr_col_index);
1362
			break;
1363
		case 0:
1364
			col_index = l2_col_index ? l2_col_index : l1_col_index;
1365
			break;
1366
	}
1367
	return col_index;
1368
}
1369
1370
// The five things VERA's layer renderer decodes a mode from, lifted out of the
1371
// emulator's much larger property struct. vera_bandwidth.c is unit-tested
1372
// without video.c, so it takes these rather than a pointer to something only
1373
// this file knows how to build.
1374
//
1375
// Read from prev_layer_properties[1], the same latched copy the renderers use,
1376
// so a raster split is followed rather than averaged away.
1377
//
1378
// `scrolled` is the one that is not a straight copy: the RTL starts the line
1379
// buffer at 0 - subtile_hscroll (layer_renderer.v:499-501), so a layer scrolled
1380
// off a tile boundary renders one tile more than the screen strictly needs.
1381
// Bitmap modes force hscroll to zero (refresh_layer_properties()), so they
1382
// never pay it.
1383
static vera_bw_layer_t
1384
vera_bw_layer_of(uint8_t layer)
1385
{
1386
	const struct video_layer_properties *p = &prev_layer_properties[1][layer];
1387
	vera_bw_layer_t out;
1388
1389
	out.enabled     = layer_line_enable[layer] != 0;
1390
	out.bitmap_mode = p->bitmap_mode;
1391
	out.color_depth = p->color_depth;
1392
	// tile_width means "the wide one" in both families: 16px tiles, or the
1393
	// 640-wide bitmap.
1394
	out.tile_width  = (uint8_t)(p->bitmap_mode ? (p->tilew > 320) : (p->tilew_log2 > 3));
1395
	out.scrolled    = !p->bitmap_mode && (p->hscroll & p->tilew_max) != 0;
1396
1397
	return out;
1398
}
1399
1400
// Set while the DEBUGGER is writing, so its pokes at $9F23/$9F24 are not
1401
// charged to the guest.
1402
//
1403
// video_read() is told directly, as a debugOn argument, and returns before any
1404
// accounting. video_write() has no such parameter and some eighty call sites
1405
// that would all have to change to give it one, so memory.c raises this flag
1406
// around a debug write instead. The rule it enforces is the same one the read
1407
// path already follows: if opening a memory view moved the guest's numbers,
1408
// the numbers would be worthless.
1409
static bool debug_port_write = false;
1410
1411
void
1412
video_set_debug_write(bool on)
1413
{
1414
	debug_port_write = on;
1415
}
1416
1417
// One byte through the data port. Wrapped rather than called inline because
1418
// the enabled test has to happen BEFORE sprite_trace_current_line() runs: C
1419
// evaluates arguments before the callee can decline them, and that helper is a
1420
// branch and three global loads on VERA's hot path.
1421
static void
1422
note_port_traffic(bool write, uint8_t vram_bytes)
1423
{
1424
	if (!vera_bandwidth_active || debug_port_write)
1425
		return;
1426
	vera_bandwidth_note_port(write, vram_bytes, sprite_trace_current_line());
1427
}
1428
1429
static void
1430
note_port_fx_cache_write(void)
1431
{
1432
	if (!vera_bandwidth_active || debug_port_write)
1433
		return;
1434
	vera_bandwidth_note_fx_cache_write(sprite_trace_current_line());
1435
}
1436
1437
static void
1438
note_port_fx_affine(void)
1439
{
1440
	if (!vera_bandwidth_active || debug_port_write)
1441
		return;
1442
	vera_bandwidth_note_fx_affine(sprite_trace_current_line());
1443
}
1444
1445
static void
1446
render_line(uint16_t y, float scan_pos_x)
1447
{
1448
	static uint16_t y_prev;
1449
	static uint16_t s_pos_x_p;
1450
	static uint32_t eff_y_fp; // 16.16 fixed point
1451
	static uint32_t eff_x_fp; // 16.16 fixed point
1452
1453
	static uint8_t col_line[SCREEN_WIDTH];
1454
1455
	uint8_t dc_video = reg_composer[0];
1456
	uint16_t vstart = reg_composer[6] << 1;
1457
	uint16_t vstop = reg_composer[7] << 1;
1458
1459
	if (y != y_prev) {
1460
		y_prev = y;
1461
		s_pos_x_p = 0;
1462
1463
		// Copy the composer array to 2-line history buffer
1464
		// so that the raster effects that happen on a delay take effect
1465
		// at exactly the right time
1466
1467
		// This simulates different effects happening at render,
1468
		// render but delayed until the next line, or applied mid-line
1469
		// at scan-out
1470
1471
		memcpy(prev_reg_composer[1], prev_reg_composer[0], sizeof(*reg_composer) * COMPOSER_SLOTS);
1472
		memcpy(prev_reg_composer[0], reg_composer, sizeof(*reg_composer) * COMPOSER_SLOTS);
1473
1474
		// Same with the layer properties
1475
1476
		memcpy(prev_layer_properties[1], prev_layer_properties[0], sizeof(*layer_properties) * NUM_LAYERS);
1477
		memcpy(prev_layer_properties[0], layer_properties, sizeof(*layer_properties) * NUM_LAYERS);
1478
1479
		// The raw layer registers travel down the same two-stage pipeline, so
1480
		// the debug history can record what a line was actually rendered with.
1481
		// The renderers read prev_layer_properties[1], two generations behind
1482
		// live: snapshotting reg_layer directly would attribute a mid-frame
1483
		// register change to a line two scanlines before the one it affected,
1484
		// which is precisely the raster-split case this history exists for.
1485
		memcpy(prev_reg_layer[1], prev_reg_layer[0], sizeof(prev_reg_layer[0]));
1486
		memcpy(prev_reg_layer[0], reg_layer, sizeof(reg_layer));
1487
1488
		if ((dc_video & 3) > 1) { // 480i or 240p
1489
			if ((y >> 1) == 0) {
1490
				eff_y_fp = y*(prev_reg_composer[1][2] << 9);
1491
			} else if ( ((y & 0xfffe) >= vstart) && ((y & 0xfffe) < vstop) ) {
1492
				eff_y_fp += (prev_reg_composer[1][2] << 10);
1493
			}
1494
		} else {
1495
			if (y == 0) {
1496
				eff_y_fp = 0;
1497
			} else if ( (y >= vstart) && (y < vstop) ) {
1498
				eff_y_fp += (prev_reg_composer[1][2] << 9);
1499
			}
1500
		}
1501
	}
1502
1503
	if ((dc_video & 8) && (dc_video & 3) > 1) { // progressive NTSC/RGB mode
1504
		y &= 0xfffe;
1505
	}
1506
1507
	// refresh palette for next entry
1508
	if (video_palette.dirty) {
1509
		refresh_palette();
1510
	}
1511
1512
	if (y >= SCREEN_HEIGHT) {
1513
		return;
1514
	}
1515
1516
	uint16_t s_pos_x = round(scan_pos_x);
1517
	if (s_pos_x > SCREEN_WIDTH) {
1518
		s_pos_x = SCREEN_WIDTH;
1519
	}
1520
1521
	if (s_pos_x_p == 0) {
1522
		eff_x_fp = 0;
1523
	}
1524
1525
	uint8_t out_mode = reg_composer[0] & 3;
1526
1527
	uint8_t border_color = reg_composer[3];
1528
	uint16_t hstart = reg_composer[4] << 2;
1529
	uint16_t hstop = reg_composer[5] << 2;
1530
1531
	uint16_t eff_y = (eff_y_fp >> 16);
1532
	if (eff_y >= 480) eff_y = 480 - (y & 1);
1533
1534
	layer_line_enable[0] = dc_video & 0x10;
1535
	layer_line_enable[1] = dc_video & 0x20;
1536
	sprite_line_enable   = dc_video & 0x40;
1537
1538
1539
	// clear layer_line if layer gets disabled
1540
	for (uint8_t layer = 0; layer < 2; layer++) {
1541
		if (!layer_line_enable[layer] && old_layer_line_enable[layer]) {
1542
			for (uint16_t i = s_pos_x_p; i < SCREEN_WIDTH; i++) {
1543
				layer_line[layer][i] = 0;
1544
			}
1545
		}
1546
		if (s_pos_x_p == 0)
1547
			old_layer_line_enable[layer] = layer_line_enable[layer];
1548
	}
1549
1550
	// clear sprite_line if sprites get disabled
1551
	if (!sprite_line_enable && old_sprite_line_enable) {
1552
		for (uint16_t i = s_pos_x_p; i < SCREEN_WIDTH; i++) {
1553
			sprite_line_col[i] = 0;
1554
			sprite_line_z[i] = 0;
1555
			sprite_line_mask[i] = 0;
1556
		}
1557
	}
1558
1559
	if (s_pos_x_p == 0)
1560
		old_sprite_line_enable = sprite_line_enable;
1561
1562
1563
1564
	if (sprite_line_enable) {
1565
		render_sprite_line(eff_y);
1566
	}
1567
1568
	// Charge this line's VERA fetch traffic. Deliberately ABOVE the warp guard
1569
	// below, unlike everything else here.
1570
	//
1571
	// Under -warp the emulator skips the layer rendering for 63 frames in 64,
1572
	// but VERA would not: the guest's registers say what the chip fetches, and
1573
	// the chip does not know the host is in a hurry. Charging below the guard
1574
	// reported a warping machine as using no VRAM bandwidth at all, which is
1575
	// how this ended up here -- it is exactly the kind of thing only a test
1576
	// against a real running machine finds.
1577
	//
1578
	// Affordable precisely because the count is MODELLED from the registers
1579
	// rather than counted off the render_layer_line_* calls: it is a few
1580
	// arithmetic ops whether or not anything is drawn. See vera_bandwidth.h.
1581
	//
1582
	// Indexed by the DISPLAY line y, not by eff_y. eff_y is the source row
1583
	// after DC_VSCALE, so in a 320x240 mode it advances once per two display
1584
	// lines -- and the hardware fetches once per DISPLAY line regardless
1585
	// (composer.v raises line_render_start per scanline). Charging eff_y both
1586
	// halved the frame's totals and filed layer traffic under a different index
1587
	// from the CPU port traffic on the same physical scanline, which is what
1588
	// the peak-line figure is built out of.
1589
	if (vera_bandwidth_active) {
1590
		const vera_bw_layer_t l0 = vera_bw_layer_of(0);
1591
		const vera_bw_layer_t l1 = vera_bw_layer_of(1);
1592
		vera_bandwidth_line(y, &l0, &l1);
1593
	}
1594
1595
	if (warp_mode && (frame_count & 63)) {
1596
		// sprites were needed for the collision IRQ, but we can skip
1597
		// everything else if we're in warp mode, most of the time
1598
		return;
1599
	}
1600
1601
	// Snapshot what this line actually displayed, for debug views. Placed after
1602
	// the warp guard above and immediately before the layer rendering, so it
1603
	// describes only lines that are really drawn: under -warp 63 frames in 64
1604
	// return early, and publishing there would hand the VERA panel state for
1605
	// scanlines whose pixels were never produced.
1606
	if (y < SCREEN_HEIGHT) {
1607
		// "Enabled" has to mean "this layer actually reached the screen on this
1608
		// line", not just "the layer bit is set in DC_VIDEO". Outside the
1609
		// active window the compositor fills the line with the border colour
1610
		// and ignores the layers entirely (see the border branch below), and
1611
		// with out_mode 0 there is no output at all. Publishing those lines as
1612
		// enabled let a border line win the panel's "first scanline to show
1613
		// this row wins" race against the active line that really displayed it,
1614
		// so a register change during the top border decoded the visible row
1615
		// with the wrong registers.
1616
		const bool line_displayed = (out_mode != 0) && (y >= vstart) && (y < vstop);
1617
1618
		memcpy(line_reg_layer[y], prev_reg_layer[1], sizeof(line_reg_layer[y]));
1619
		line_eff_y[y]         = eff_y;
1620
		line_layer_enabled[y] = line_displayed
1621
			? (uint8_t)((layer_line_enable[0] ? 1 : 0) | (layer_line_enable[1] ? 2 : 0))
1622
			: 0;
1623
		for (uint8_t l = 0; l < 2; l++) {
1624
			// The row this line showed. Recorded rather than left for the panel
1625
			// to recompute, because the renderers take VSCROLL and the
1626
			// layer-height mask from generation [0] while the layout registers
1627
			// above come from [1] -- so recomputing from the snapshot alone is
1628
			// off by whatever the scroll changed mid-frame. Bitmap mode has no
1629
			// scroll (VERA forces it to 0), so there the row is eff_y.
1630
			line_layer_row[y][l] = prev_layer_properties[1][l].bitmap_mode
1631
				? eff_y
1632
				: (uint16_t)calc_layer_eff_y(&prev_layer_properties[0][l], eff_y);
1633
		}
1634
		line_state_valid[y] = true;
1635
		// Progressive modes draw only the even line of each pair, so the odd
1636
		// one was never rendered. Marked invalid rather than filled in: the
1637
		// contract is "what this scanline showed", and inventing a row for a
1638
		// line the beam never drew is how a debug view starts lying.
1639
		if ((dc_video & 8) && (dc_video & 3) > 1 && y + 1 < SCREEN_HEIGHT) {
1640
			line_state_valid[y + 1] = false;
1641
		}
1642
	}
1643
1644
	if (layer_line_enable[0]) {
1645
		if (prev_layer_properties[1][0].text_mode) {
1646
			render_layer_line_text(0, eff_y);
1647
		} else if (prev_layer_properties[1][0].bitmap_mode) {
1648
			render_layer_line_bitmap(0, eff_y);
1649
		} else {
1650
			render_layer_line_tile(0, eff_y);
1651
		}
1652
	}
1653
	if (layer_line_enable[1]) {
1654
		if (prev_layer_properties[1][1].text_mode) {
1655
			render_layer_line_text(1, eff_y);
1656
		} else if (prev_layer_properties[1][1].bitmap_mode) {
1657
			render_layer_line_bitmap(1, eff_y);
1658
		} else {
1659
			render_layer_line_tile(1, eff_y);
1660
		}
1661
	}
1662
1663
	// If video output is enabled, calculate color indices for line.
1664
	if (out_mode != 0) {
1665
		// Add border after if required.
1666
		if (y < vstart || y >= vstop) {
1667
			uint32_t border_fill = border_color;
1668
			border_fill = border_fill | (border_fill << 8);
1669
			border_fill = border_fill | (border_fill << 16);
1670
			memset(col_line, border_fill, SCREEN_WIDTH);
1671
		} else {
1672
			hstart = hstart < 640 ? hstart : 640;
1673
			hstop = hstop < 640 ? hstop : 640;
1674
1675
			for (uint16_t x = s_pos_x_p; x < hstart && x < s_pos_x; ++x) {
1676
				col_line[x] = border_color;
1677
			}
1678
1679
			const uint32_t scale = reg_composer[1];
1680
			for (uint16_t x = MAX(hstart, s_pos_x_p); x < hstop && x < s_pos_x; ++x) {
1681
				uint16_t eff_x = eff_x_fp >> 16;
1682
				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;
1683
				eff_x_fp += (scale << 9);
1684
			}
1685
			for (uint16_t x = hstop; x < s_pos_x; ++x) {
1686
				col_line[x] = border_color;
1687
			}
1688
		}
1689
	}
1690
1691
	// Look up all color indices.
1692
	uint32_t* framebuffer4_begin = ((uint32_t*)framebuffer) + (y * SCREEN_WIDTH) + s_pos_x_p;
1693
	{
1694
		uint32_t* framebuffer4 = framebuffer4_begin;
1695
		for (uint16_t x = s_pos_x_p; x < s_pos_x; x++) {
1696
			*framebuffer4++ = video_palette.entries[col_line[x]];
1697
		}
1698
	}
1699
1700
	// NTSC overscan
1701
	if (out_mode == 2) {
1702
		uint32_t* framebuffer4 = framebuffer4_begin;
1703
		for (uint16_t x = s_pos_x_p; x < s_pos_x; x++)
1704
		{
1705
			if (x < SCREEN_WIDTH * TITLE_SAFE_X ||
1706
				x > SCREEN_WIDTH * (1 - TITLE_SAFE_X) ||
1707
				y < SCREEN_HEIGHT * TITLE_SAFE_Y ||
1708
				y > SCREEN_HEIGHT * (1 - TITLE_SAFE_Y)) {
1709
1710
				// Divide RGB elements by 4.
1711
				*framebuffer4 &= 0x00fcfcfc;
1712
				*framebuffer4 >>= 2;
1713
			}
1714
			framebuffer4++;
1715
		}
1716
	}
1717
1718
	s_pos_x_p = s_pos_x;
1719
}
1720
1721
static void
1722
update_isr_and_coll(uint16_t y, uint16_t compare)
1723
{
1724
	if (y == SCREEN_HEIGHT) {
1725
		if (sprite_line_collisions != 0) {
1726
			isr |= 4;
1727
		}
1728
		isr = (isr & 0xf) | sprite_line_collisions;
1729
		sprite_line_collisions = 0;
1730
		isr |= 1; // VSYNC IRQ
1731
	}
1732
	if (y == compare) { // LINE IRQ
1733
		isr |= 2;
1734
	}
1735
}
1736
1737
bool
1738
video_step(float mhz, float steps, bool midline)
1739
{
1740
	uint16_t y = 0;
1741
	bool ntsc_mode = reg_composer[0] & 2;
1742
	bool new_frame = false;
1743
	sprite_trace_cpu_cycles += (uint32_t)steps;
1744
	vga_scan_pos_x += PIXEL_FREQ * steps / mhz;
1745
	if (vga_scan_pos_x > VGA_SCAN_WIDTH) {
1746
		vga_scan_pos_x -= VGA_SCAN_WIDTH;
1747
		if (!ntsc_mode) {
1748
			render_line(vga_scan_pos_y - VGA_Y_OFFSET, VGA_SCAN_WIDTH);
1749
		}
1750
		vga_scan_pos_y++;
1751
		if (vga_scan_pos_y == SCAN_HEIGHT) {
1752
			vga_scan_pos_y = 0;
1753
			if (!ntsc_mode) {
1754
				new_frame = true;
1755
				frame_count++;
1756
			}
1757
		}
1758
		if (!ntsc_mode) {
1759
			update_isr_and_coll(vga_scan_pos_y - VGA_Y_OFFSET, irq_line);
1760
		}
1761
	} else if (midline) {
1762
		if (!ntsc_mode) {
1763
			render_line(vga_scan_pos_y - VGA_Y_OFFSET, vga_scan_pos_x);
1764
		}
1765
	}
1766
	ntsc_half_cnt += PIXEL_FREQ * steps / mhz;
1767
	if (ntsc_half_cnt > NTSC_HALF_SCAN_WIDTH) {
1768
		ntsc_half_cnt -= NTSC_HALF_SCAN_WIDTH;
1769
		if (ntsc_mode) {
1770
			if (ntsc_scan_pos_y < SCAN_HEIGHT) {
1771
				y = ntsc_scan_pos_y - NTSC_Y_OFFSET_LOW;
1772
				if ((y & 1) == 0) {
1773
					render_line(y, NTSC_HALF_SCAN_WIDTH);
1774
				}
1775
			} else {
1776
				y = ntsc_scan_pos_y - NTSC_Y_OFFSET_HIGH;
1777
				if ((y & 1) == 0) {
1778
					render_line(y | 1, NTSC_HALF_SCAN_WIDTH);
1779
				}
1780
			}
1781
		}
1782
		ntsc_scan_pos_y++;
1783
		if (ntsc_scan_pos_y == SCAN_HEIGHT) {
1784
			reg_composer[0] |= 0x80;
1785
			if (ntsc_mode) {
1786
				new_frame = true;
1787
				frame_count++;
1788
			}
1789
		}
1790
		if (ntsc_scan_pos_y == SCAN_HEIGHT*2) {
1791
			reg_composer[0] &= ~0x80;
1792
			ntsc_scan_pos_y = 0;
1793
			if (ntsc_mode) {
1794
				new_frame = true;
1795
				frame_count++;
1796
			}
1797
		}
1798
		if (ntsc_mode) {
1799
			// this is correct enough for even screen heights
1800
			if (ntsc_scan_pos_y < SCAN_HEIGHT) {
1801
				update_isr_and_coll(ntsc_scan_pos_y - NTSC_Y_OFFSET_LOW, irq_line & ~1);
1802
			} else {
1803
				update_isr_and_coll(ntsc_scan_pos_y - NTSC_Y_OFFSET_HIGH, irq_line & ~1);
1804
			}
1805
		}
1806
	} else if (midline) {
1807
		if (ntsc_mode) {
1808
			if (ntsc_scan_pos_y < SCAN_HEIGHT) {
1809
				y = ntsc_scan_pos_y - NTSC_Y_OFFSET_LOW;
1810
				if ((y & 1) == 0) {
1811
					render_line(y, ntsc_half_cnt);
1812
				}
1813
			} else {
1814
				y = ntsc_scan_pos_y - NTSC_Y_OFFSET_HIGH;
1815
				if ((y & 1) == 0) {
1816
					render_line(y | 1, ntsc_half_cnt);
1817
				}
1818
			}
1819
		}
1820
	}
1821
1822
	// Roll the sprite trace at the frame boundary: finish the frame just ended,
1823
	// publish it for the debugger, and seed the next one from the attribute
1824
	// table as it stands now, which is what the beam will start out drawing.
1825
	if (new_frame && sprite_trace_active) {
1826
		sprite_trace_frame_advance((uint32_t)frame_count, sprite_trace_cpu_cycles, sprite_data);
1827
	}
1828
1829
	// And the bandwidth frame, on the same boundary and for the same reason:
1830
	// this is VERA's own frame, which is the one its bus traffic belongs to.
1831
	// perf_budget closes its frame from main.c instead, because CPU cycles are
1832
	// the CPU's business -- the two are driven by the same vsync, so the panel
1833
	// can show them side by side.
1834
	//
1835
	// frame_count has already been incremented above, and this closes the frame
1836
	// that just ENDED, so it is stamped with the ordinal one below -- matching
1837
	// sprite_trace, which takes frame_count as the frame it is about to start.
1838
	if (new_frame) {
1839
		vera_bandwidth_frame_end((uint32_t)(frame_count - 1));
1840
	}
1841
1842
	return new_frame;
1843
}
1844
1845
bool
1846
video_get_irq_out()
1847
{
1848
	uint8_t tmp_isr = isr | (pcm_is_fifo_almost_empty() ? 8 : 0);
1849
	return (tmp_isr & ien) != 0;
1850
}
1851
1852
//
1853
// saves the video memory and register content into a file
1854
//
1855
1856
void
1857
video_save(SDL_RWops *f)
1858
{
1859
	SDL_RWwrite(f, &video_ram[0], sizeof(uint8_t), sizeof(video_ram));
1860
	SDL_RWwrite(f, &reg_composer[0], sizeof(uint8_t), sizeof(reg_composer));
1861
	SDL_RWwrite(f, &palette[0], sizeof(uint8_t), sizeof(palette));
1862
	SDL_RWwrite(f, &reg_layer[0][0], sizeof(uint8_t), sizeof(reg_layer));
1863
	SDL_RWwrite(f, &sprite_data[0], sizeof(uint8_t), sizeof(sprite_data));
1864
}
1865
1866
// Render the current framebuffer and present it. Deliberately does not touch the
1867
// SDL event queue, so it is safe to call from inside the OS modal move/resize
1868
// loop.
1869
//
1870
// `new_frame` says whether this is a newly produced frame rather than a repaint
1871
// of the one already there. Both the activity-LED blend and the GIF write are
1872
// once-per-frame effects -- the LED is blended into the framebuffer in place,
1873
// and the recorder builds its timeline from the frames it is handed -- so a
1874
// repaint must do neither.
1875
static void
1876
video_render_all_ex(bool new_frame)
1877
{
1878
	// for activity LED, overlay red 8x4 square into top right of framebuffer
1879
	// for progressive modes, draw LED only on even scanlines
1880
	//
1881
	// Blended into the framebuffer in place, so it must happen once per frame
1882
	// and not once per present. A repaint between throttle slices re-presents
1883
	// the frame that is already there, LED included; blending again would drive
1884
	// the square toward pure red a slice at a time.
1885
	for (int y = 0; new_frame && y < 4; y+=1+!!((reg_composer[0] & 0x0b) > 0x09)) {
1886
		for (int x = SCREEN_WIDTH - 8; x < SCREEN_WIDTH; x++) {
1887
			uint8_t b = framebuffer[(y * SCREEN_WIDTH + x) * 4 + 0];
1888
			uint8_t g = framebuffer[(y * SCREEN_WIDTH + x) * 4 + 1];
1889
			uint8_t r = framebuffer[(y * SCREEN_WIDTH + x) * 4 + 2];
1890
			r = (uint32_t)r * (255 - activity_led) / 255 + activity_led;
1891
			g = (uint32_t)g * (255 - activity_led) / 255;
1892
			b = (uint32_t)b * (255 - activity_led) / 255;
1893
			framebuffer[(y * SCREEN_WIDTH + x) * 4 + 0] = b;
1894
			framebuffer[(y * SCREEN_WIDTH + x) * 4 + 1] = g;
1895
			framebuffer[(y * SCREEN_WIDTH + x) * 4 + 2] = r;
1896
			framebuffer[(y * SCREEN_WIDTH + x) * 4 + 3] = 0x00;
1897
		}
1898
	}
1899
1900
	SDL_UpdateTexture(sdlTexture, NULL, framebuffer, SCREEN_WIDTH * 4);
1901
1902
	// Skipped for a repaint that is not a new emulated frame. The throttle
1903
	// repaints between sleep slices to keep the window alive, and there can be
1904
	// hundreds of those per frame at a slow target; recording them would bloat
1905
	// the file and stretch its timeline against the emulated one.
1906
	if (new_frame && record_gif > RECORD_GIF_PAUSED) {
1907
		if(!GifWriteFrame(&gif_writer, framebuffer, SCREEN_WIDTH, SCREEN_HEIGHT, 2, 8, false)) {
1908
			// if that failed, stop recording
1909
			GifEnd(&gif_writer);
1910
			record_gif = RECORD_GIF_DISABLED;
1911
			printf("Unexpected end of recording.\n");
1912
		}
1913
		if (record_gif == RECORD_GIF_SINGLE) { // if single-shot stop recording
1914
			record_gif = RECORD_GIF_PAUSED;  // need to close in video_end()
1915
		}
1916
	}
1917
1918
	SDL_RenderClear(renderer);
1919
	SDL_RenderCopy(renderer, sdlTexture, NULL, NULL);
1920
1921
	if (debugger_enabled && showDebugOnRender != 0) {
1922
		DEBUGRenderDisplay(SCREEN_WIDTH, SCREEN_HEIGHT);
1923
	}
1924
1925
	SDL_RenderPresent(renderer);
1926
1927
#ifdef HAS_IMGUI
1928
	// Rebuild the debugger window's frame. Deliberately decoupled from the
1929
	// emulated frame rate, which spans four orders of magnitude here: under
1930
	// -warp or a 100MHz target this is reached far more often than a display
1931
	// can show, and at 25kHz a new frame may be seconds apart -- so pacing the
1932
	// UI off it would either burn the CPU rebuilding invisible frames or leave
1933
	// the debugger unresponsive. Wall-clock 60Hz is right at both ends. The
1934
	// throttle's repaint path (new_frame == false) calls in too, which is what
1935
	// keeps the UI live while the machine is running slowly.
1936
	if (imgui_renderer != NULL) {
1937
		static Uint32 last_ui_ticks = 0;
1938
		const Uint32 now = SDL_GetTicks();
1939
		// Unsigned subtraction, so this stays correct across the 49-day wrap.
1940
		if (now - last_ui_ticks >= 16) {
1941
			last_ui_ticks = now;
1942
			debug_ui_render();
1943
		}
1944
	}
1945
#endif
1946
}
1947
1948
// Present a newly completed frame without pumping the event queue. Called from
1949
// the Windows modal move/resize loop, which owns the message queue for the
1950
// duration of a drag; polling events from there would re-enter SDL's own
1951
// dispatch. This is a real frame, so it records and blends the activity LED
1952
// like any other -- only the event handling differs.
1953
void
1954
video_present_no_input(void)
1955
{
1956
	if (renderer == NULL || sdlTexture == NULL) {
1957
		return;
1958
	}
1959
	video_render_all_ex(true);
1960
}
1961
1962
// Re-present the frame that is already there. No new frame has been produced,
1963
// so this must not record one or blend the activity LED again -- the throttle
1964
// calls it between sleep slices, hundreds of times per frame at a slow target.
1965
void
1966
video_repaint_only(void)
1967
{
1968
	if (renderer == NULL || sdlTexture == NULL) {
1969
		return;
1970
	}
1971
	video_render_all_ex(false);
1972
}
1973
1974
static void
1975
video_render_all(void)
1976
{
1977
	video_render_all_ex(true);
1978
}
1979
1980
#ifdef HAS_IMGUI
1981
// True only once the debugger window AND renderer were actually created.
1982
//
1983
// This is the authority on whether the UI exists; imgui_debugger_enabled is the
1984
// authority on whether it was *asked* for. The two agree from video_init()
1985
// onward -- both the creation-failure path below and the headless path in
1986
// main.c clear the flag -- but callers that must not park the machine still
1987
// test this one, so the answer does not depend on that agreement holding.
1988
bool
1989
video_debug_ui_available(void)
1990
{
1991
	return imgui_window != NULL && imgui_renderer != NULL;
1992
}
1993
1994
static void video_imgui_debug_key(SDL_Keysym ks);
1995
1996
// Whether an SDL window id belongs to the debugger window. Callers outside
1997
// video.c use this to tell "close the debugger" from "close the emulator".
1998
bool
1999
video_is_debug_ui_window(Uint32 window_id)
2000
{
2001
	return imgui_window != NULL && window_id == imgui_window_id;
2002
}
2003
2004
// Whether an event is addressed to the debugger window, and so must not also
2005
// reach the emulated machine or the text debugger's command line. Events with
2006
// no window id (SDL_QUIT, joystick, audio) are never the debugger's.
2007
//
2008
// Shared by both event loops rather than copied into each: the running loop in
2009
// video_update() and the paused loop in DEBUGGetCurrentStatus() have to agree
2010
// about which events belong to the debugger, and two copies of this switch
2011
// would be two places to forget an event type.
2012
bool
2013
video_event_targets_debug_ui(const SDL_Event *ev)
2014
{
2015
	if (imgui_window == NULL) {
2016
		return false;
2017
	}
2018
	switch (ev->type) {
2019
		case SDL_WINDOWEVENT:      return ev->window.windowID == imgui_window_id;
2020
		case SDL_KEYDOWN:
2021
		case SDL_KEYUP:            return ev->key.windowID    == imgui_window_id;
2022
		case SDL_TEXTINPUT:        return ev->text.windowID   == imgui_window_id;
2023
		case SDL_TEXTEDITING:      return ev->edit.windowID   == imgui_window_id;
2024
		case SDL_MOUSEBUTTONDOWN:
2025
		case SDL_MOUSEBUTTONUP:    return ev->button.windowID == imgui_window_id;
2026
		case SDL_MOUSEMOTION:      return ev->motion.windowID == imgui_window_id;
2027
		case SDL_MOUSEWHEEL:       return ev->wheel.windowID  == imgui_window_id;
2028
		default:                   return false;
2029
	}
2030
}
2031
2032
// Hand an event to the debugger UI. Wrapped here so debugger.c can feed the
2033
// graphical debugger without including its C++ headers -- video.c already owns
2034
// the window and is the only place that knows the UI exists.
2035
void
2036
video_debug_ui_feed_event(const SDL_Event *ev)
2037
{
2038
	if (imgui_window != NULL) {
2039
		debug_ui_process_event(ev);
2040
	}
2041
}
2042
2043
// Apply the VS-style debug shortcuts for a keydown already known to target the
2044
// debugger window. A focused text field (goto/search) suppresses them; a merely
2045
// focused panel does not.
2046
void
2047
video_debug_ui_shortcut_key(const SDL_Event *ev)
2048
{
2049
	if (ev->type == SDL_KEYDOWN && !debug_ui_want_text_input()) {
2050
		video_imgui_debug_key(ev->key.keysym);
2051
	}
2052
}
2053
2054
// Map a keydown on the debugger window to a Visual-Studio-style debug action.
2055
// Shared by the running event loop (video_update) and the paused pump
2056
// (video_debug_ui_pump_paused) so the shortcuts behave identically whether the
2057
// machine is running or halted. The caller has already confirmed the event
2058
// targets the debugger window and that no text field has focus.
2059
//   F5   continue          Ctrl+Shift+F5  restart (reset)
2060
//   F9   toggle bp at PC   Ctrl+F10       run to cursor
2061
//   F10  step over         F11            step into
2062
//   Shift+F11 step out     Break/Pause    pause
2063
// Step/continue are inert while running; pause is inert while halted.
2064
static void
2065
video_imgui_debug_key(SDL_Keysym ks)
2066
{
2067
	const bool shift = (ks.mod & KMOD_SHIFT) != 0;
2068
	const bool ctrl  = (ks.mod & KMOD_CTRL) != 0;
2069
	switch (ks.scancode) {
2070
		case SDL_SCANCODE_F5:
2071
			if (ctrl && shift) {
2072
				smc_requested_reset = true;      // Ctrl+Shift+F5 = restart
2073
			} else if (DEBUGIsPaused()) {
2074
				DEBUGContinue();                 // F5 = continue
2075
			}
2076
			break;
2077
		case SDL_SCANCODE_F9:
2078
			debug_ui_toggle_breakpoint_at_pc();
2079
			break;
2080
		case SDL_SCANCODE_F10:
2081
			if (ctrl) {
2082
				debug_ui_run_to_cursor();        // Ctrl+F10 = run to cursor
2083
			} else if (DEBUGIsPaused()) {
2084
				DEBUGStepOverAuto(DEBUG_OWNER_UI);
2085
			}
2086
			break;
2087
		case SDL_SCANCODE_F11:
2088
			if (DEBUGIsPaused()) {
2089
				if (shift) {
2090
					DEBUGStepOut(DEBUG_OWNER_UI);
2091
				} else {
2092
					DEBUGStepIntoAuto();
2093
				}
2094
			}
2095
			break;
2096
		case SDL_SCANCODE_PAUSE:
2097
			if (DEBUGIsRunning()) {
2098
				DEBUGPause();
2099
			}
2100
			break;
2101
		default:
2102
			break;
2103
	}
2104
}
2105
2106
// Keep the debugger window live and interactive while the machine is halted
2107
// under -imgui. Called from DEBUGGetCurrentStatus() when it owns the pause loop.
2108
// Mirrors the SDL debugger's stopped behaviour -- pump events, re-render,
2109
// throttle -- but drives the ImGui window instead. Returns the
2110
// DEBUGGetCurrentStatus contract:
2111
//   -1 = quit, 1 = stay paused, 0 = a control-bar action resumed/stepped the CPU.
2112
int
2113
video_debug_ui_pump_paused(void)
2114
{
2115
	SDL_Event event;
2116
	while (SDL_PollEvent(&event)) {
2117
		// Feed everything to ImGui so its buttons and inputs work while paused.
2118
		debug_ui_process_event(&event);
2119
2120
		if (event.type == SDL_QUIT) {
2121
			return -1;
2122
		}
2123
		if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE) {
2124
			// Closing the debugger window must not kill the emulator; only a
2125
			// close of the main emulator window quits.
2126
			if (imgui_window && event.window.windowID == imgui_window_id) {
2127
				continue;
2128
			}
2129
			return -1;
2130
		}
2131
2132
		// Only an active text field (goto/search) suppresses the shortcuts; a
2133
		// merely focused panel does not, so F5/F10/F11 work while just viewing.
2134
		if (event.type == SDL_KEYDOWN && imgui_window &&
2135
		    event.key.windowID == imgui_window_id &&
2136
		    !debug_ui_want_text_input()) {
2137
			video_imgui_debug_key(event.key.keysym);
2138
		}
2139
	}
2140
2141
	// Re-present the main window's existing frame and rebuild the debugger's.
2142
	// The machine is halted, so no new emulated frame exists -- rendering one
2143
	// would re-blend the activity LED and record a duplicate GIF frame. This is
2144
	// also where the control-bar buttons are evaluated and may change
2145
	// currentMode.
2146
	video_repaint_only();
2147
2148
	SDL_Delay(16); // ~60fps; don't spin hot while parked
2149
2150
	// If a control action resumed or single-stepped, tell emulator_loop to
2151
	// execute rather than stay parked. DMODE_STOP is the only mode that keeps
2152
	// us here; run and step both mean "go".
2153
	if (!DEBUGIsPaused()) {
2154
		return 0;
2155
	}
2156
	return 1;
2157
}
2158
#else
2159
bool video_debug_ui_available(void) { return false; }
2160
int  video_debug_ui_pump_paused(void) { return 1; }
2161
bool video_is_debug_ui_window(Uint32 window_id) { (void)window_id; return false; }
2162
bool video_event_targets_debug_ui(const SDL_Event *ev) { (void)ev; return false; }
2163
void video_debug_ui_shortcut_key(const SDL_Event *ev) { (void)ev; }
2164
void video_debug_ui_feed_event(const SDL_Event *ev) { (void)ev; }
2165
#endif
2166
2167
bool
2168
video_update()
2169
{
2170
	static bool cmd_down = false;
2171
	static bool alt_down = false;
2172
	bool mouse_changed = false;
2173
2174
	// The debug overlay path historically skipped input handling entirely; the
2175
	// debugger owns the keyboard while it is on screen.
2176
	bool debug_overlay = (debugger_enabled && showDebugOnRender != 0);
2177
2178
	video_render_all();
2179
2180
	if (debug_overlay) {
2181
		return true;
2182
	}
2183
2184
	SDL_Event event;
2185
	while (SDL_PollEvent(&event)) {
2186
#ifdef HAS_IMGUI
2187
		// Feed every event to the debugger UI first. If it targets the debugger
2188
		// window, consume it here so it never reaches the emulated machine --
2189
		// otherwise typing in a search box would also type into BASIC.
2190
		if (imgui_window) {
2191
			debug_ui_process_event(&event);
2192
			if (video_event_targets_debug_ui(&event)) {
2193
				// Closing the debugger window must not kill the emulator.
2194
				if (event.type == SDL_WINDOWEVENT &&
2195
				    event.window.event == SDL_WINDOWEVENT_CLOSE) {
2196
					continue;
2197
				}
2198
				// The VS-style shortcuts also work while the machine is running
2199
				// and the debugger window has focus (F9 to set a breakpoint,
2200
				// Break to pause). Step/continue are inert until paused.
2201
				video_debug_ui_shortcut_key(&event);
2202
				continue;
2203
			}
2204
		}
2205
#endif
2206
		if (event.type == SDL_QUIT) {
2207
			return false;
2208
		}
2209
#ifdef HAS_IMGUI
2210
		// SDL2 only synthesises SDL_QUIT for a window close when that window is
2211
		// the last one open. With the debugger window up there are two, so
2212
		// closing the emulator window delivers a bare SDL_WINDOWEVENT_CLOSE and
2213
		// nothing else -- which used to fall through here and be dropped,
2214
		// leaving the [X] button inert. A close of the debugger window has
2215
		// already been consumed above, so anything reaching this point is a
2216
		// close of the emulator window and does mean quit.
2217
		if (event.type == SDL_WINDOWEVENT &&
2218
		    event.window.event == SDL_WINDOWEVENT_CLOSE) {
2219
			return false;
2220
		}
2221
#endif
2222
		if (event.type == SDL_KEYDOWN) {
2223
			bool consumed = false;
2224
			if (cmd_down && !(disable_emu_cmd_keys || mouse_grabbed)) {
2225
				if (event.key.keysym.sym == SDLK_s) {
2226
					machine_dump("user keyboard request");
2227
					consumed = true;
2228
				} else if (event.key.keysym.sym == SDLK_r) {
2229
					machine_reset();
2230
					consumed = true;
2231
				} else if (event.key.keysym.sym == SDLK_BACKSPACE) {
2232
					machine_nmi();
2233
					consumed = true;
2234
				} else if (event.key.keysym.sym == SDLK_v) {
2235
					machine_paste(SDL_GetClipboardText(), true);
2236
					consumed = true;
2237
				} else if (event.key.keysym.sym == SDLK_f || event.key.keysym.sym == SDLK_RETURN) {
2238
					is_fullscreen = !is_fullscreen;
2239
					SDL_SetWindowFullscreen(window, is_fullscreen ? SDL_WINDOW_FULLSCREEN : 0);
2240
					consumed = true;
2241
				} else if (event.key.keysym.sym == SDLK_PLUS || event.key.keysym.sym == SDLK_EQUALS) {
2242
					machine_toggle_warp();
2243
					consumed = true;
2244
				} else if (event.key.keysym.sym == SDLK_a) {
2245
					sdcard_attach();
2246
					consumed = true;
2247
				} else if (event.key.keysym.sym == SDLK_d) {
2248
					sdcard_detach();
2249
					consumed = true;
2250
#ifndef __EMSCRIPTEN__
2251
				} else if (event.key.keysym.sym == SDLK_p) {
2252
					screenshot();
2253
					consumed = true;
2254
#endif
2255
				}
2256
			}
2257
			if (cmd_down) {
2258
				if (event.key.keysym.sym == SDLK_m) {
2259
					mousegrab_toggle();
2260
					consumed = true;
2261
				}
2262
			}
2263
			if (alt_down && !disable_emu_cmd_keys) {
2264
				// Capture ALT+F4 key sequence to exit emulator if it is not handled by SDL
2265
				if (event.key.keysym.sym == SDLK_F4) {
2266
					printf("Shutdown requested by ALT+F4\n");
2267
					main_shutdown();
2268
#ifdef __EMSCRIPTEN__
2269
					emscripten_force_exit(0);
2270
#endif
2271
					exit(0);
2272
				}
2273
			}
2274
			if (!consumed) {
2275
				if (event.key.keysym.scancode == LSHORTCUT_KEY || event.key.keysym.scancode == RSHORTCUT_KEY) {
2276
					cmd_down = true;
2277
				}
2278
				if (event.key.keysym.scancode == SDL_SCANCODE_LALT || event.key.keysym.scancode == SDL_SCANCODE_RALT) {
2279
					alt_down = true;
2280
				}
2281
				handle_keyboard(true, event.key.keysym.sym, event.key.keysym.scancode);
2282
			}
2283
			continue;
2284
		}
2285
		if (event.type == SDL_KEYUP) {
2286
			if (event.key.keysym.scancode == LSHORTCUT_KEY || event.key.keysym.scancode == RSHORTCUT_KEY) {
2287
				cmd_down = false;
2288
			}
2289
			if (event.key.keysym.scancode == SDL_SCANCODE_LALT || event.key.keysym.scancode == SDL_SCANCODE_RALT) {
2290
				alt_down = false;
2291
			}
2292
			handle_keyboard(false, event.key.keysym.sym, event.key.keysym.scancode);
2293
			continue;
2294
		}
2295
		if (event.type == SDL_MOUSEBUTTONDOWN) {
2296
			switch (event.button.button) {
2297
				case SDL_BUTTON_LEFT:
2298
					mouse_button_down(0);
2299
					mouse_changed = true;
2300
					break;
2301
				case SDL_BUTTON_RIGHT:
2302
					mouse_button_down(1);
2303
					mouse_changed = true;
2304
					break;
2305
				case SDL_BUTTON_MIDDLE:
2306
					mouse_button_down(2);
2307
					mouse_changed = true;
2308
					break;
2309
			}
2310
		}
2311
		if (event.type == SDL_MOUSEBUTTONUP) {
2312
			switch (event.button.button) {
2313
				case SDL_BUTTON_LEFT:
2314
					mouse_button_up(0);
2315
					mouse_changed = true;
2316
					break;
2317
				case SDL_BUTTON_RIGHT:
2318
					mouse_button_up(1);
2319
					mouse_changed = true;
2320
					break;
2321
				case SDL_BUTTON_MIDDLE:
2322
					mouse_button_up(2);
2323
					mouse_changed = true;
2324
					break;
2325
			}
2326
		}
2327
		if (event.type == SDL_MOUSEMOTION) {
2328
			static int mouse_x;
2329
			static int mouse_y;
2330
			if (mouse_grabbed) {
2331
				mouse_move(event.motion.xrel, event.motion.yrel);
2332
			} else {
2333
				mouse_move(event.motion.x - mouse_x, event.motion.y - mouse_y);
2334
			}
2335
			mouse_x = event.motion.x;
2336
			mouse_y = event.motion.y;
2337
			mouse_changed = true;
2338
		}
2339
		if (event.type == SDL_MOUSEWHEEL) {
2340
			mouse_set_wheel(event.wheel.y);
2341
			mouse_changed = true;
2342
		}
2343
2344
		if (event.type == SDL_JOYDEVICEADDED) {
2345
			joystick_add(event.jdevice.which);
2346
		}
2347
	    if (event.type == SDL_JOYDEVICEREMOVED) {
2348
		    joystick_remove(event.jdevice.which);
2349
	    }
2350
	    if (event.type == SDL_CONTROLLERBUTTONDOWN) {
2351
		    joystick_button_down(event.cbutton.which, event.cbutton.button);
2352
	    }
2353
		if (event.type == SDL_CONTROLLERBUTTONUP) {
2354
		    joystick_button_up(event.cbutton.which, event.cbutton.button);
2355
	    }
2356
2357
	}
2358
	if (mouse_changed) {
2359
		mouse_send_state();
2360
	}
2361
	return true;
2362
}
2363
2364
// ─── Debug view accessors ──────────────────────────────────────────────────
2365
2366
// Report the layer registers and effective layer Y that were used to render a
2367
// given display scanline. Debug views use this to reproduce mid-frame register
2368
// changes (raster splits) instead of assuming one snapshot covers the frame.
2369
bool
2370
video_get_layer_line_state(uint8_t layer, uint16_t line, uint8_t out_regs[7],
2371
                           uint16_t *out_eff_y, bool *out_enabled,
2372
                           uint16_t *out_layer_row)
2373
{
2374
	if (layer >= 2 || line >= SCREEN_HEIGHT || !line_state_valid[line]) {
2375
		return false;
2376
	}
2377
	if (out_regs) {
2378
		memcpy(out_regs, line_reg_layer[line][layer], 7);
2379
	}
2380
	if (out_eff_y) {
2381
		*out_eff_y = line_eff_y[line];
2382
	}
2383
	if (out_enabled) {
2384
		*out_enabled = (line_layer_enabled[line] & (1 << layer)) != 0;
2385
	}
2386
	if (out_layer_row) {
2387
		*out_layer_row = line_layer_row[line][layer];
2388
	}
2389
	return true;
2390
}
2391
2392
uint16_t
2393
video_get_scanline_count(void)
2394
{
2395
	return SCREEN_HEIGHT;
2396
}
2397
2398
uint32_t
2399
video_frame_dot_clocks(void)
2400
{
2401
	const bool ntsc_mode = reg_composer[0] & 2;
2402
	// The counters video_step() compares against: a VGA line is a whole
2403
	// scan width, an NTSC line is a half-scan, and either way a frame is
2404
	// signalled after SCAN_HEIGHT of them.
2405
	return ntsc_mode ? (uint32_t)NTSC_HALF_SCAN_WIDTH * SCAN_HEIGHT
2406
	                 : (uint32_t)VGA_SCAN_WIDTH * SCAN_HEIGHT;
2407
}
2408
2409
uint16_t
2410
video_frame_scanlines(void)
2411
{
2412
	return SCAN_HEIGHT;
2413
}
2414
2415
// Size, in layer pixels, of the image the composer is actually putting on
2416
// screen: the active display window (DC_HSTART/HSTOP, DC_VSTART/VSTOP) scaled
2417
// by DC_HSCALE/DC_VSCALE. The composer advances the layer by scale/128 of a
2418
// pixel per output pixel, so this is what a 320x200-style mode really shows.
2419
// Debug viewers use it to size themselves to the current video mode.
2420
void
2421
video_get_active_layer_size(int *out_w, int *out_h)
2422
{
2423
	int hstart = reg_composer[4] << 2;
2424
	int hstop  = reg_composer[5] << 2;
2425
	int vstart = reg_composer[6] << 1;
2426
	int vstop  = reg_composer[7] << 1;
2427
2428
	if (hstart > SCREEN_WIDTH)  hstart = SCREEN_WIDTH;
2429
	if (hstop  > SCREEN_WIDTH)  hstop  = SCREEN_WIDTH;
2430
	if (vstart > SCREEN_HEIGHT) vstart = SCREEN_HEIGHT;
2431
	if (vstop  > SCREEN_HEIGHT) vstop  = SCREEN_HEIGHT;
2432
2433
	const int screen_w = hstop > hstart ? hstop - hstart : 0;
2434
	const int screen_h = vstop > vstart ? vstop - vstart : 0;
2435
2436
	if (out_w) {
2437
		*out_w = (screen_w * reg_composer[1]) >> 7;
2438
	}
2439
	if (out_h) {
2440
		*out_h = (screen_h * reg_composer[2]) >> 7;
2441
	}
2442
}
2443
2444
// Predict the next VERA interrupt.
2445
//
2446
// VERA raises VSYNC when the scan reaches line SCREEN_HEIGHT and LINE when it
2447
// reaches IRQLINE, so with the current scan position both are predictable. The
2448
// debugger uses this for its "cycles until the next IRQ" readout. Returns false
2449
// when neither a line nor a vsync interrupt is enabled in IEN. out_cycles
2450
// receives CPU cycles (at `mhz`) until the next one; out_source receives the
2451
// ISR bit that will be set (1 = VSYNC, 2 = LINE).
2452
bool
2453
video_next_irq(float mhz, uint32_t *out_cycles, uint8_t *out_source)
2454
{
2455
	const bool ntsc_mode = reg_composer[0] & 2;
2456
	const bool vsync_en  = (ien & 1) != 0;
2457
	const bool line_en   = (ien & 2) != 0;
2458
2459
	if (!vsync_en && !line_en) {
2460
		return false;
2461
	}
2462
2463
	// Scanline the renderer is currently on, in the same space as the compares
2464
	// in update_isr_and_coll().
2465
	const int cur = ntsc_mode ? (int)(ntsc_scan_pos_y % SCAN_HEIGHT) - NTSC_Y_OFFSET_LOW
2466
	                          : (int)vga_scan_pos_y - VGA_Y_OFFSET;
2467
2468
	// Lines from `cur` to `target`, wrapping through the end of the frame.
2469
	#define LINES_UNTIL(target) \
2470
		((((int)(target) - cur) + SCAN_HEIGHT) % SCAN_HEIGHT)
2471
2472
	int     best_lines  = SCAN_HEIGHT + 1;
2473
	uint8_t best_source = 0;
2474
2475
	if (vsync_en) {
2476
		const int d = LINES_UNTIL(SCREEN_HEIGHT);
2477
		if (d < best_lines) {
2478
			best_lines  = d;
2479
			best_source = 1;
2480
		}
2481
	}
2482
	if (line_en) {
2483
		const int d = LINES_UNTIL(irq_line);
2484
		if (d < best_lines) {
2485
			best_lines  = d;
2486
			best_source = 2;
2487
		}
2488
	}
2489
	#undef LINES_UNTIL
2490
2491
	if (!best_source) {
2492
		return false;
2493
	}
2494
2495
	// video_step() advances the scan by PIXEL_FREQ * cycles / mhz pixels, so a
2496
	// whole scanline costs scan_width * mhz / PIXEL_FREQ CPU cycles. Subtract
2497
	// how far into the current line we already are.
2498
	const float scan_width      = ntsc_mode ? NTSC_HALF_SCAN_WIDTH : VGA_SCAN_WIDTH;
2499
	const float cycles_per_line = scan_width * mhz / (float)PIXEL_FREQ;
2500
	const float into_line       = ntsc_mode ? ntsc_half_cnt : vga_scan_pos_x;
2501
	float       cycles          = best_lines * cycles_per_line
2502
	                              - into_line * mhz / (float)PIXEL_FREQ;
2503
	if (cycles < 0) {
2504
		cycles = 0;
2505
	}
2506
2507
	if (out_cycles) {
2508
		*out_cycles = (uint32_t)cycles;
2509
	}
2510
	if (out_source) {
2511
		*out_source = best_source;
2512
	}
2513
	return true;
2514
}
2515
2516
// Raw VERA interrupt state, for debug views: enable mask, pending mask and the
2517
// programmed IRQ line.
2518
void
2519
video_get_irq_state(uint8_t *out_ien, uint8_t *out_isr, uint16_t *out_irq_line)
2520
{
2521
	if (out_ien)
2522
		*out_ien = ien;
2523
	if (out_isr)
2524
		*out_isr = isr | (pcm_is_fifo_almost_empty() ? 8 : 0);
2525
	if (out_irq_line)
2526
		*out_irq_line = irq_line;
2527
}
2528
2529
void
2530
video_end()
2531
{
2532
	if (debugger_enabled) {
2533
		DEBUGFreeUI();
2534
	}
2535
2536
#ifdef HAS_IMGUI
2537
	// Tear the UI down before its renderer and window: the ImGui SDL backends
2538
	// hold both and free renderer-owned textures on shutdown. Safe no-ops if
2539
	// -imgui was never passed or the window failed to open.
2540
	if (imgui_window && imgui_renderer) {
2541
		debug_ui_shutdown();
2542
	}
2543
	if (imgui_renderer) {
2544
		SDL_DestroyRenderer(imgui_renderer);
2545
		imgui_renderer = NULL;
2546
	}
2547
	if (imgui_window) {
2548
#ifdef _WIN32
2549
		extern void video_win32_remove_move_hook(SDL_Window *window);
2550
		video_win32_remove_move_hook(imgui_window);
2551
#endif
2552
		SDL_DestroyWindow(imgui_window);
2553
		imgui_window = NULL;
2554
	}
2555
#endif
2556
2557
	if (record_gif != RECORD_GIF_DISABLED) {
2558
		GifEnd(&gif_writer);
2559
		record_gif = RECORD_GIF_DISABLED;
2560
	}
2561
2562
	is_fullscreen = false;
2563
	SDL_SetWindowFullscreen(window, 0);
2564
	SDL_DelEventWatch(video_move_resize_watch, NULL);
2565
#ifdef _WIN32
2566
	extern void video_win32_remove_move_hook(SDL_Window *window);
2567
	video_win32_remove_move_hook(window);
2568
#endif
2569
	SDL_DestroyRenderer(renderer);
2570
	SDL_DestroyWindow(window);
2571
}
2572
2573
2574
static const int increments[32] = {
2575
	0,   0,
2576
	1,   -1,
2577
	2,   -2,
2578
	4,   -4,
2579
	8,   -8,
2580
	16,  -16,
2581
	32,  -32,
2582
	64,  -64,
2583
	128, -128,
2584
	256, -256,
2585
	512, -512,
2586
	40,  -40,
2587
	80,  -80,
2588
	160, -160,
2589
	320, -320,
2590
	640, -640,
2591
};
2592
2593
uint32_t
2594
video_get_address(uint8_t sel)
2595
{
2596
	uint32_t address = io_addr[sel];
2597
	return address & 0x1ffff;
2598
}
2599
2600
uint32_t
2601
get_and_inc_address(uint8_t sel, bool write)
2602
{
2603
	uint32_t address = io_addr[sel];
2604
	int16_t incr = increments[io_inc[sel]];
2605
2606
	if (fx_4bit_mode && fx_nibble_incr[sel] && !incr) {
2607
		if (fx_nibble_bit[sel]) {
2608
			if ((io_inc[sel] & 1) == 0) io_addr[sel] += 1;
2609
			fx_nibble_bit[sel] = 0;
2610
		} else {
2611
			if (io_inc[sel] & 1) io_addr[sel] -= 1;
2612
			fx_nibble_bit[sel] = 1;
2613
		}
2614
	}
2615
2616
	if (sel == 1 && fx_16bit_hop) {
2617
		if (incr == 4) {
2618
			if (fx_16bit_hop_align == (address & 0x3))
2619
				incr = 1;
2620
			else
2621
				incr = 3;
2622
		} else if (incr == 320) {
2623
			if (fx_16bit_hop_align == (address & 0x3))
2624
				incr = 1;
2625
			else
2626
				incr = 319;
2627
		}
2628
	}
2629
2630
	io_addr[sel] += incr;
2631
2632
	if (sel == 1 && fx_addr1_mode == 1) { // FX line draw mode
2633
		fx_x_pixel_position += fx_x_pixel_increment;
2634
		if (fx_x_pixel_position & 0x10000) {
2635
			fx_x_pixel_position &= ~0x10000;
2636
			if (fx_4bit_mode && fx_nibble_incr[0]) {
2637
				if (fx_nibble_bit[1]) {
2638
					if ((io_inc[0] & 1) == 0) io_addr[1] += 1;
2639
					fx_nibble_bit[1] = 0;
2640
				} else {
2641
					if (io_inc[0] & 1) io_addr[1] -= 1;
2642
					fx_nibble_bit[1] = 1;
2643
				}
2644
			}
2645
			io_addr[1] += increments[io_inc[0]];
2646
		}
2647
	} else if (fx_addr1_mode == 2 && write == false) { // FX polygon fill mode
2648
		fx_x_pixel_position += fx_x_pixel_increment;
2649
		fx_y_pixel_position += fx_y_pixel_increment;
2650
		fx_poly_fill_length = ((int32_t) fx_y_pixel_position >> 16) - ((int32_t) fx_x_pixel_position >> 16);
2651
		if (sel == 0 && fx_cache_byte_cycling && !fx_cache_fill) {
2652
			fx_cache_byte_index = (fx_cache_byte_index + 1) & 3;
2653
		}
2654
		if (sel == 1) {
2655
			if (fx_4bit_mode) {
2656
				io_addr[1] = io_addr[0] + (fx_x_pixel_position >> 17);
2657
				fx_nibble_bit[1] = (fx_x_pixel_position >> 16) & 1;
2658
			} else {
2659
				io_addr[1] = io_addr[0] + (fx_x_pixel_position >> 16);
2660
			}
2661
		}
2662
	} else if (sel == 1 && fx_addr1_mode == 3 && write == false) { // FX affine mode
2663
		fx_x_pixel_position += fx_x_pixel_increment;
2664
		fx_y_pixel_position += fx_y_pixel_increment;
2665
	}
2666
	return address;
2667
}
2668
2669
void
2670
fx_affine_prefetch(void)
2671
{
2672
	if (fx_addr1_mode != 3) return; // only if affine mode is selected
2673
2674
	uint32_t address;
2675
	uint8_t affine_x_tile = (fx_x_pixel_position >> 19) & 0xff;
2676
	uint8_t affine_y_tile = (fx_y_pixel_position >> 19) & 0xff;
2677
	uint8_t affine_x_sub_tile = (fx_x_pixel_position >> 16) & 0x07;
2678
	uint8_t affine_y_sub_tile = (fx_y_pixel_position >> 16) & 0x07;
2679
2680
	if (!fx_affine_clip) { // wrap
2681
		affine_x_tile &= fx_affine_map_size - 1;
2682
		affine_y_tile &= fx_affine_map_size - 1;
2683
	}
2684
2685
	if (affine_x_tile >= fx_affine_map_size || affine_y_tile >= fx_affine_map_size) {
2686
		// We clipped, return value for tile 0
2687
		address = fx_affine_tile_base + (affine_y_sub_tile << (3 - fx_4bit_mode)) + (affine_x_sub_tile >> (uint8_t)fx_4bit_mode);
2688
		fx_nibble_bit[1] = (affine_x_sub_tile & 1) >> (1 - fx_4bit_mode);
2689
	} else {
2690
		// Get the address within the tile map
2691
		address = fx_affine_map_base + (affine_y_tile * fx_affine_map_size) + affine_x_tile;
2692
		// Now translate that to the tile base address
2693
		uint8_t affine_tile_idx = video_space_read(address);
2694
		address = fx_affine_tile_base + (affine_tile_idx << (6 - fx_4bit_mode));
2695
		// Now add the sub-tile address
2696
		address += (affine_y_sub_tile << (3 - fx_4bit_mode)) + (affine_x_sub_tile >> (uint8_t)fx_4bit_mode);
2697
		fx_nibble_bit[1] = (affine_x_sub_tile & 1) >> (1 - fx_4bit_mode);
2698
	}
2699
	io_addr[1] = address;
2700
	io_rddata[1] = video_space_read(address);
2701
}
2702
2703
//
2704
// Vera: Internal Video Address Space
2705
//
2706
2707
uint8_t
2708
video_space_read(uint32_t address)
2709
{
2710
	return video_ram[address & 0x1FFFF];
2711
}
2712
2713
static void
2714
video_space_read_range(uint8_t* dest, uint32_t address, uint32_t size)
2715
{
2716
	if (address >= ADDR_VRAM_START && (address+size) <= ADDR_VRAM_END) {
2717
		memcpy(dest, &video_ram[address], size);
2718
	} else {
2719
		for(int i = 0; i < size; ++i) {
2720
			*dest++ = video_space_read(address + i);
2721
		}
2722
	}
2723
}
2724
2725
void
2726
video_space_write(uint32_t address, uint8_t value)
2727
{
2728
	video_ram[address & 0x1FFFF] = value;
2729
2730
	if (address >= ADDR_PSG_START && address < ADDR_PSG_END) {
2731
		audio_render();
2732
		psg_writereg(address & 0x3f, value);
2733
	} else if (address >= ADDR_PALETTE_START && address < ADDR_PALETTE_END) {
2734
		palette[address & 0x1ff] = value;
2735
		video_palette.dirty = true;
2736
	} else if (address >= ADDR_SPRDATA_START && address < ADDR_SPRDATA_END) {
2737
		sprite_attr_write(address, value);
2738
	}
2739
}
2740
2741
2742
void
2743
fx_video_space_write(uint32_t address, bool nibble, uint8_t value)
2744
{
2745
	if (fx_4bit_mode) {
2746
		if (nibble) {
2747
			if (!fx_trans_writes || (value & 0x0f) > 0) {
2748
				video_ram[address & 0x1FFFF] = (video_ram[address & 0x1FFFF] & 0xf0) | (value & 0x0f);
2749
			}
2750
		} else {
2751
			if (!fx_trans_writes || (value & 0xf0) > 0) {
2752
				video_ram[address & 0x1FFFF] = (video_ram[address & 0x1FFFF] & 0x0f) | (value & 0xf0);
2753
			}
2754
		}
2755
	} else {
2756
		if (!fx_trans_writes || value > 0) video_ram[address & 0x1FFFF] = value;
2757
	}
2758
	if (address >= ADDR_PSG_START && address < ADDR_PSG_END) {
2759
		audio_render();
2760
		psg_writereg(address & 0x3f, value);
2761
	} else if (address >= ADDR_PALETTE_START && address < ADDR_PALETTE_END) {
2762
		palette[address & 0x1ff] = value;
2763
		video_palette.dirty = true;
2764
	} else if (address >= ADDR_SPRDATA_START && address < ADDR_SPRDATA_END) {
2765
		sprite_attr_write(address, value);
2766
	}
2767
}
2768
2769
void
2770
fx_vram_cache_write(uint32_t address, uint8_t value, uint8_t mask)
2771
{
2772
	if (!fx_trans_writes || value > 0) {
2773
		switch (mask) {
2774
			case 0:
2775
				video_ram[address & 0x1FFFF] = value;
2776
				break;
2777
			case 1:
2778
				video_ram[address & 0x1FFFF] = (video_ram[address & 0x1FFFF] & 0x0f) | (value & 0xf0);
2779
				break;
2780
			case 2:
2781
				video_ram[address & 0x1FFFF] = (video_ram[address & 0x1FFFF] & 0xf0) | (value & 0x0f);
2782
				break;
2783
			case 3:
2784
				// Do nothing
2785
				break;
2786
		}
2787
	}
2788
}
2789
2790
uint32_t video_get_fx_accum() {
2791
	return fx_mult_accumulator;
2792
}
2793
uint8_t video_get_dc_value(uint8_t reg) {
2794
	switch (reg & 0x1F) {
2795
		case 0x00:
2796
		case 0x01:
2797
		case 0x02:
2798
		case 0x03:
2799
		case 0x04:
2800
		case 0x05:
2801
		case 0x06:
2802
		case 0x07:
2803
		case 0x08:
2804
		case 0x09:
2805
		case 0x0a:
2806
		case 0x0c:
2807
		case 0x0d:
2808
		case 0x0e:
2809
		case 0x0f:
2810
			return reg_composer[reg];
2811
			break;
2812
		case 0x0b:
2813
			return reg_composer[reg] & 0x3f;
2814
			break;
2815
		case 0x10: // DCSEL=4, $9F29
2816
			return (fx_x_pixel_position >> 16) & 0xff;
2817
			break;
2818
		case 0x11: // DCSEL=4, $9F2A
2819
			return ((fx_x_pixel_position >> 24) & 0x07) | (fx_x_pixel_position & 0x80);
2820
			break;
2821
		case 0x12: // DCSEL=4, $9F2B
2822
			return (fx_y_pixel_position >> 16) & 0xff;
2823
			break;
2824
		case 0x13: // DCSEL=4, $9F2C
2825
			return ((fx_y_pixel_position >> 24) & 0x07) | (fx_y_pixel_position & 0x80);
2826
			break;
2827
		case 0x14: // DCSEL=5, $9F29
2828
			return (fx_x_pixel_position >> 8) & 0xff;
2829
			break;
2830
		case 0x15: // DCSEL=4, $9F2A
2831
			return (fx_y_pixel_position >> 8) & 0xff;
2832
			break;
2833
		case 0x16: // DCSEL=5, 0x9F2B
2834
			if (fx_poly_fill_length >= 768) {
2835
				return ((fx_2bit_poly && fx_addr1_mode == 2) ? 0x00 : 0x80);
2836
			}
2837
			if (fx_4bit_mode) {
2838
				if (fx_2bit_poly && fx_addr1_mode == 2) {
2839
					return ((fx_y_pixel_position & 0x00008000) >> 8) |
2840
						((fx_x_pixel_position >> 11) & 0x60) |
2841
						((fx_x_pixel_position >> 14) & 0x10) |
2842
						((fx_poly_fill_length & 0x0007) << 1) |
2843
						((fx_x_pixel_position & 0x00008000) >> 15);
2844
				} else {
2845
					return ((!!(fx_poly_fill_length & 0xfff8)) << 7) |
2846
						((fx_x_pixel_position >> 11) & 0x60) |
2847
						((fx_x_pixel_position >> 14) & 0x10) |
2848
						((fx_poly_fill_length & 0x0007) << 1);
2849
				}
2850
			} else {
2851
				return ((!!(fx_poly_fill_length & 0xfff0)) << 7) |
2852
					((fx_x_pixel_position >> 11) & 0x60) |
2853
					((fx_poly_fill_length & 0x000f) << 1);
2854
			}
2855
			break;
2856
		case 0x17: // DCSEL=5, 0x9F2C
2857
			return ((fx_poly_fill_length & 0x03f8) >> 2);
2858
			break;
2859
		case 0x18: // DCSEL=6, 0x9F29
2860
			return fx_cache[0];
2861
			break;
2862
		case 0x19: // DCSEL=6, 0x9F2A
2863
			return fx_cache[1];
2864
			break;
2865
		case 0x1a: // DCSEL=6, 0x9F2B
2866
			return fx_cache[2];
2867
			break;
2868
		case 0x1b: // DCSEL=6, 0x9F2C
2869
			return fx_cache[3];
2870
			break;
2871
		default:
2872
			break;
2873
	}
2874
2875
	return vera_version_string[reg % 4];
2876
}
2877
2878
//
2879
// Vera: 6502 I/O Interface
2880
//
2881
// if debugOn, read without any side effects (registers & memory unchanged)
2882
2883
static void check_not_readonly(uint8_t reg) {
2884
	bool wrong = false;
2885
	switch(io_dcsel) {
2886
		case 5: {
2887
			switch(reg) {
2888
				case 0x0b:		// DCSEL=5 FX_POLY_FILL_L
2889
				case 0x0c:		// DCSEL=5 FX_POLY_FILL_H
2890
					wrong=true;
2891
					break;
2892
			}
2893
			break;
2894
		}
2895
		case 63: {
2896
			switch(reg) {
2897
				case 0x09:		// DCSEL=63 DC_VER0
2898
				case 0x0a:		// DCSEL=63 DC_VER1
2899
				case 0x0b:		// DCSEL=63 DC_VER2
2900
				case 0x0c:		// DCSEL=63 DC_VER3
2901
					wrong=true;
2902
					break;
2903
			}
2904
			break;
2905
		}
2906
	}
2907
2908
	if(wrong)
2909
		printf("Warning: %04X wrote to read-only VERA register at 9F%02X (DCSEL=%d)\n", opcode_addr, reg+0x20, io_dcsel);
2910
}
2911
2912
static void check_not_writeonly(uint8_t reg) {
2913
	bool wrong = false;
2914
	switch(io_dcsel) {
2915
		case 2: {
2916
			switch(reg) {
2917
				case 0x0a:		// DCSEL=2 FX_TILEBASE
2918
				case 0x0b:		// DCSEL=2 FX_MAPBASE
2919
				case 0x0c:		// DCSEL=2 FX_MULT
2920
					wrong=true;
2921
					break;
2922
			}
2923
			break;
2924
		}
2925
		case 3:
2926
		case 4: {
2927
			switch(reg) {
2928
				case 0x09:		// DCSEL=3/4 FX_X_INCR_L/FX_X_POS_L
2929
				case 0x0a:		// DCSEL=3/4 FX_X_INCR_H/FX_X_POS_H
2930
				case 0x0b:		// DCSEL=3/4 FX_Y_INCR_L/FX_Y_POS_L
2931
				case 0x0c:		// DCSEL=3/4 FX_Y_INCR_H/FX_Y_POS_H
2932
					wrong=true;
2933
					break;
2934
			}
2935
			break;
2936
		}
2937
		case 5: {
2938
			switch(reg) {
2939
				case 0x09:		// DCSEL=5 FX_X_POS_S
2940
				case 0x0a:		// DCSEL=5 FX_Y_POS_S
2941
					wrong=true;
2942
					break;
2943
			}
2944
			break;
2945
		}
2946
		case 6: {
2947
			switch(reg) {
2948
				case 0x0b:		// DCSEL=6 FX_CACHE_H
2949
				case 0x0c:		// DCSEL=6 FX_CACHE_U
2950
					wrong=true;
2951
					break;
2952
			}
2953
			break;
2954
		}
2955
	}
2956
2957
	if(wrong)
2958
		printf("Warning: %04X read from write-only VERA register at 9F%02X (DCSEL=%d)\n", opcode_addr, reg+0x20, io_dcsel);
2959
}
2960
2961
uint8_t video_read(uint8_t reg, bool debugOn) {
2962
	bool ntsc_mode = reg_composer[0] & 2;
2963
	uint16_t scanline = ntsc_mode ? ntsc_scan_pos_y % SCAN_HEIGHT : vga_scan_pos_y;
2964
	if (scanline >= 512) scanline=511;
2965
2966
	// A debug read is the debugger looking, not the program reading: it must
2967
	// neither warn about the guest's behaviour nor change any state. The rest
2968
	// of this function already honours debugOn for the data ports; SPI did not,
2969
	// and the warning fired for the debugger's own reads.
2970
	if (!debugOn) {
2971
		check_not_writeonly(reg);
2972
	}
2973
2974
	switch (reg & 0x1F) {
2975
		case 0x00: return io_addr[io_addrsel] & 0xff;
2976
		case 0x01: return (io_addr[io_addrsel] >> 8) & 0xff;
2977
		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);
2978
		case 0x03:
2979
		case 0x04: {
2980
			if (debugOn) {
2981
				return io_rddata[reg - 3];
2982
			}
2983
2984
			bool addr_nibble = fx_nibble_bit[reg - 3];
2985
			uint32_t address = get_and_inc_address(reg - 3, false);
2986
2987
			uint8_t value = io_rddata[reg - 3];
2988
2989
			// One byte off the data port, charged to VERA's bus. The debugOn
2990
			// return above means the debugger's own reads never reach here,
2991
			// which matters: opening a memory view must not change the number
2992
			// the developer is trying to read.
2993
			note_port_traffic(false, 1);
2994
2995
			if (reg == 4 && fx_addr1_mode == 3) {
2996
				// Affine mode translates through the tile map first, so this
2997
				// read costs a second fetch the guest never asked for.
2998
				note_port_fx_affine();
2999
				fx_affine_prefetch();
3000
			} else
3001
				io_rddata[reg - 3] = video_space_read(io_addr[reg - 3]);
3002
3003
			if (fx_cache_fill) {
3004
				if (fx_4bit_mode) {
3005
					uint8_t nibble_read = (addr_nibble ? ((value & 0x0f) << 4) : (value & 0xf0));
3006
3007
					if (fx_cache_nibble_index) {
3008
						fx_cache[fx_cache_byte_index] = (fx_cache[fx_cache_byte_index] & 0xf0) | (nibble_read >> 4);
3009
						fx_cache_nibble_index = 0;
3010
						fx_cache_byte_index = ((fx_cache_byte_index + 1) & 0x3);
3011
					} else {
3012
						fx_cache[fx_cache_byte_index] = (fx_cache[fx_cache_byte_index] & 0x0f) | (nibble_read);
3013
						fx_cache_nibble_index = 1;
3014
					}
3015
				} else {
3016
					fx_cache[fx_cache_byte_index] = value;
3017
					if (fx_cache_increment_mode)
3018
						fx_cache_byte_index = (fx_cache_byte_index & 0x2) | ((fx_cache_byte_index + 1) & 0x1);
3019
					else
3020
						fx_cache_byte_index = ((fx_cache_byte_index + 1) & 0x3);
3021
				}
3022
			}
3023
3024
			if (log_video) {
3025
				printf("READ  video_space[$%X] = $%02X\n", address, value);
3026
			}
3027
			return value;
3028
		}
3029
		case 0x05: return (io_dcsel << 1) | io_addrsel;
3030
		case 0x06: return ((irq_line & 0x100) >> 1) | ((scanline & 0x100) >> 2) | (ien & 0xF);
3031
		case 0x07: return isr | (pcm_is_fifo_almost_empty() ? 8 : 0);
3032
		case 0x08: return scanline & 0xFF;
3033
3034
		case 0x09:
3035
		case 0x0A:
3036
		case 0x0B:
3037
		case 0x0C: {
3038
			int i = reg - 0x09 + (io_dcsel << 2);
3039
			if (debugOn) return video_get_dc_value(i);
3040
			switch (i) {
3041
				case 0x00:
3042
				case 0x01:
3043
				case 0x02:
3044
				case 0x03:
3045
				case 0x04:
3046
				case 0x05:
3047
				case 0x06:
3048
				case 0x07:
3049
				case 0x08:
3050
				case 0x16: // DCSEL=5, 0x9F2B
3051
				case 0x17: // DCSEL=5, 0x9F2C
3052
					return video_get_dc_value(i);
3053
					break;
3054
				case 0x18: // DCSEL=6, 0x9F29
3055
					fx_mult_accumulator = 0;
3056
					// fall out of the switch
3057
					break;
3058
				case 0x19: // DCSEL=6, 0x9F2A
3059
					; // <- avoids the error in some compilers about a declaration after a label
3060
					int32_t m_result = (int16_t)((fx_cache[1] << 8) | fx_cache[0]) * (int16_t)((fx_cache[3] << 8) | fx_cache[2]);
3061
					if (fx_subtract)
3062
						fx_mult_accumulator -= m_result;
3063
					else
3064
						fx_mult_accumulator += m_result;
3065
					// fall out of the switch
3066
					break;
3067
				default:
3068
					// The rest of the space is write-only,
3069
					// so reading the values out instead returns the version string.
3070
					// fall out of the switch
3071
					break;
3072
			}
3073
			return vera_version_string[i % 4];
3074
			break;
3075
		}
3076
		case 0x0D:
3077
		case 0x0E:
3078
		case 0x0F:
3079
		case 0x10:
3080
		case 0x11:
3081
		case 0x12:
3082
		case 0x13: return reg_layer[0][reg - 0x0D];
3083
3084
		case 0x14:
3085
		case 0x15:
3086
		case 0x16:
3087
		case 0x17:
3088
		case 0x18:
3089
		case 0x19:
3090
		case 0x1A: return reg_layer[1][reg - 0x14];
3091
3092
		case 0x1B:
3093
			// Same rule as the YM status read: a debug read must not flush
3094
			// audio, so the debugger sees the control byte as of the last
3095
			// render rather than advancing the render position itself.
3096
			if (!debugOn) {
3097
				audio_render();
3098
			}
3099
			return pcm_read_ctrl();
3100
		case 0x1C: return pcm_read_rate();
3101
		case 0x1D: return 0;
3102
3103
		case 0x1E:
3104
		case 0x1F:
3105
			// Reading $9F3E is not passive: with autotx armed and SS asserted
3106
			// it clocks another byte out to the SD card. A debug read must not,
3107
			// or simply having the debugger's memory view open steals bytes
3108
			// from the block the KERNAL is in the middle of reading.
3109
			if (debugOn) {
3110
				return vera_spi_peek(reg & 1);
3111
			}
3112
			return vera_spi_read(reg & 1);
3113
	}
3114
	return 0;
3115
}
3116
3117
void video_write(uint8_t reg, uint8_t value) {
3118
	// if (reg > 4) {
3119
	// 	printf("ioregisters[0x%02X] = 0x%02X\n", reg, value);
3120
	// }
3121
	//	printf("ioregisters[%d] = $%02X\n", reg, value);
3122
3123
	check_not_readonly(reg);
3124
3125
	switch (reg & 0x1F) {
3126
		case 0x00:
3127
			if (fx_2bit_poly && fx_4bit_mode && fx_addr1_mode == 2 && io_addrsel == 1) {
3128
				fx_2bit_poking = true;
3129
				io_addr[1] = (io_addr[1] & 0x1fffc) | (value & 0x3);
3130
			} else {
3131
				io_addr[io_addrsel] = (io_addr[io_addrsel] & 0x1ff00) | value;
3132
				if (fx_16bit_hop && io_addrsel == 1)
3133
					fx_16bit_hop_align = value & 3;
3134
			}
3135
			io_rddata[io_addrsel] = video_space_read(io_addr[io_addrsel]);
3136
			break;
3137
		case 0x01:
3138
			io_addr[io_addrsel] = (io_addr[io_addrsel] & 0x100ff) | (value << 8);
3139
			io_rddata[io_addrsel] = video_space_read(io_addr[io_addrsel]);
3140
			break;
3141
		case 0x02:
3142
			io_addr[io_addrsel] = (io_addr[io_addrsel] & 0x0ffff) | ((value & 0x1) << 16);
3143
			fx_nibble_bit[io_addrsel] = (value >> 1) & 0x1;
3144
			fx_nibble_incr[io_addrsel] = (value >> 2) & 0x1;
3145
			io_inc[io_addrsel]  = value >> 3;
3146
			io_rddata[io_addrsel] = video_space_read(io_addr[io_addrsel]);
3147
			break;
3148
		case 0x03:
3149
		case 0x04: {
3150
			if (fx_2bit_poking && fx_addr1_mode) {
3151
				fx_2bit_poking = false;
3152
				// Still a guest byte through the port reaching VRAM, even
3153
				// though it takes its own path out of this switch below.
3154
				note_port_traffic(true, 1);
3155
				uint8_t mask = value >> 6;
3156
				switch (mask) {
3157
					case 0x00:
3158
						video_ram[io_addr[1] & 0x1FFFF] = (fx_cache[fx_cache_byte_index] & 0xc0) | (io_rddata[1] & 0x3f);
3159
						break;
3160
					case 0x01:
3161
						video_ram[io_addr[1] & 0x1FFFF] = (fx_cache[fx_cache_byte_index] & 0x30) | (io_rddata[1] & 0xcf);
3162
						break;
3163
					case 0x02:
3164
						video_ram[io_addr[1] & 0x1FFFF] = (fx_cache[fx_cache_byte_index] & 0x0c) | (io_rddata[1] & 0xf3);
3165
						break;
3166
					case 0x03:
3167
						video_ram[io_addr[1] & 0x1FFFF] = (fx_cache[fx_cache_byte_index] & 0x03) | (io_rddata[1] & 0xfc);
3168
						break;
3169
				}
3170
				break; // break out of the enclosing switch statement early, too
3171
			}
3172
3173
			if (enable_midline)
3174
				video_step(MHZ, 0, true); // potential midline raster effect
3175
			bool nibble = fx_nibble_bit[reg - 3];
3176
			uint32_t address = get_and_inc_address(reg - 3, true);
3177
			if (log_video) {
3178
				printf("WRITE video_space[$%X] = $%02X\n", address, value);
3179
			}
3180
3181
			uint8_t wrdata_to_use;
3182
			uint8_t ram_wrdata[4];
3183
			uint8_t nibble_mask[4];
3184
			uint8_t cache_to_use[4];
3185
3186
			if (fx_multiplier) {
3187
				int32_t m_result = (int16_t)((fx_cache[1] << 8) | fx_cache[0]) * (int16_t)((fx_cache[3] << 8) | fx_cache[2]);
3188
				if (fx_subtract)
3189
					m_result = fx_mult_accumulator - m_result;
3190
				else
3191
					m_result = fx_mult_accumulator + m_result;
3192
				cache_to_use[0] = (m_result) & 0xff;
3193
				cache_to_use[1] = (m_result >> 8) & 0xff;
3194
				cache_to_use[2] = (m_result >> 16) & 0xff;
3195
				cache_to_use[3] = (m_result >> 24) & 0xff;
3196
			} else {
3197
				memcpy(cache_to_use, fx_cache, sizeof(fx_cache));
3198
			}
3199
3200
			if (fx_cache_byte_cycling) {
3201
				wrdata_to_use = fx_cache[fx_cache_byte_index];
3202
			} else {
3203
				wrdata_to_use = value;
3204
			}
3205
3206
			if (fx_cache_write && !fx_cache_byte_cycling) {
3207
				ram_wrdata[0] = cache_to_use[0];
3208
				ram_wrdata[1] = cache_to_use[1];
3209
				ram_wrdata[2] = cache_to_use[2];
3210
				ram_wrdata[3] = cache_to_use[3];
3211
			} else {
3212
				ram_wrdata[0] = wrdata_to_use;
3213
				ram_wrdata[1] = wrdata_to_use;
3214
				ram_wrdata[2] = wrdata_to_use;
3215
				ram_wrdata[3] = wrdata_to_use;
3216
			}
3217
3218
			if (fx_cache_write) {
3219
				address &= 0x1fffc;
3220
				// One store, four bytes into VRAM. This is the whole reason FX
3221
				// is worth charging separately: the CPU issued a single write
3222
				// and the bus moved four times as much as an ordinary one.
3223
				note_port_traffic(true, 4);
3224
				note_port_fx_cache_write();
3225
				if (fx_trans_writes) {
3226
					if (fx_4bit_mode) {
3227
						nibble_mask[0] = (((ram_wrdata[0] & 0xf0) == 0) << 1) | ((ram_wrdata[0] & 0x0f) == 0);
3228
						nibble_mask[1] = (((ram_wrdata[1] & 0xf0) == 0) << 1) | ((ram_wrdata[1] & 0x0f) == 0);
3229
						nibble_mask[2] = (((ram_wrdata[2] & 0xf0) == 0) << 1) | ((ram_wrdata[2] & 0x0f) == 0);
3230
						nibble_mask[3] = (((ram_wrdata[3] & 0xf0) == 0) << 1) | ((ram_wrdata[3] & 0x0f) == 0);
3231
					} else {
3232
						nibble_mask[0] = (ram_wrdata[0] != 0) ? 0 : 3;
3233
						nibble_mask[1] = (ram_wrdata[1] != 0) ? 0 : 3;
3234
						nibble_mask[2] = (ram_wrdata[2] != 0) ? 0 : 3;
3235
						nibble_mask[3] = (ram_wrdata[3] != 0) ? 0 : 3;
3236
					}
3237
				} else {
3238
					nibble_mask[0] = value & 0x3;
3239
					nibble_mask[1] = (value >> 2) & 0x3;
3240
					nibble_mask[2] = (value >> 4) & 0x3;
3241
					nibble_mask[3] = (value >> 6) & 0x3;
3242
				}
3243
3244
				fx_vram_cache_write(address+0, ram_wrdata[0], nibble_mask[0]);
3245
				fx_vram_cache_write(address+1, ram_wrdata[1], nibble_mask[1]);
3246
				fx_vram_cache_write(address+2, ram_wrdata[2], nibble_mask[2]);
3247
				fx_vram_cache_write(address+3, ram_wrdata[3], nibble_mask[3]);
3248
			} else {
3249
				note_port_traffic(true, 1);
3250
				fx_video_space_write(address, nibble, wrdata_to_use); // Normal write
3251
			}
3252
3253
			io_rddata[reg - 3] = video_space_read(io_addr[reg - 3]);
3254
			break;
3255
		}
3256
		case 0x05:
3257
			if (value & 0x80) {
3258
				video_reset();
3259
			}
3260
			io_dcsel = (value >> 1) & 0x3f;
3261
			io_addrsel = value & 1;
3262
			break;
3263
		case 0x06:
3264
			irq_line = (irq_line & 0xFF) | ((value >> 7) << 8);
3265
			ien = value & 0xF;
3266
			break;
3267
		case 0x07:
3268
			isr &= value ^ 0xff;
3269
			break;
3270
		case 0x08:
3271
			irq_line = (irq_line & 0x100) | value;
3272
			break;
3273
3274
		case 0x09:
3275
		case 0x0A:
3276
		case 0x0B:
3277
		case 0x0C: {
3278
			video_step(MHZ, 0, true); // potential midline raster effect
3279
			int i = reg - 0x09 + (io_dcsel << 2);
3280
			if (i == 0) {
3281
				// if progressive mode field goes from 0 to 1
3282
				// or if mode goes from vga to something else with
3283
				// progressive mode on, clear the framebuffer
3284
				if (((reg_composer[0] & 0x8) == 0 && (value & 0x8)) ||
3285
					((reg_composer[0] & 0x3) == 1 && (value & 0x3) > 1 && (value & 0x8))) {
3286
					memset(framebuffer, 0x00, SCREEN_WIDTH * SCREEN_HEIGHT * 4);
3287
					// The picture is gone, so the per-scanline history no
3288
					// longer describes anything on screen. Dropped with it,
3289
					// or a debugger paused right after the mode change would
3290
					// decode rows for a blank framebuffer.
3291
					memset(line_state_valid, 0, sizeof(line_state_valid));
3292
				}
3293
3294
				// interlace field bit is read-only
3295
				reg_composer[0] = (reg_composer[0] & ~0x7f) | (value & 0x7f);
3296
				video_palette.dirty = true;
3297
			} else {
3298
				reg_composer[i] = value;
3299
			}
3300
3301
			switch (i) {
3302
				case 0x08: // DCSEL=2, $9F29
3303
					fx_addr1_mode = value & 0x03;
3304
					fx_4bit_mode = (value & 0x04) >> 2;
3305
					fx_16bit_hop = (value & 0x08) >> 3;
3306
					fx_cache_byte_cycling = (value & 0x10) >> 4;
3307
					fx_cache_fill = (value & 0x20) >> 5;
3308
					fx_cache_write = (value & 0x40) >> 6;
3309
					fx_trans_writes = (value & 0x80) >> 7;
3310
					break;
3311
				case 0x09: // DCSEL=2, $9F2A
3312
					fx_affine_tile_base = (value & 0xfc) << 9;
3313
					fx_affine_clip = (value & 0x02) >> 1;
3314
					fx_2bit_poly = (value & 0x01);
3315
					break;
3316
				case 0x0a: // DCSEL=2, $9F2B
3317
					fx_affine_map_base = (value & 0xfc) << 9;
3318
					fx_affine_map_size = 2 << ((value & 0x03) << 1);
3319
					break;
3320
				case 0x0b: // DCSEL=2, $9F2C
3321
					fx_cache_increment_mode = value & 0x01;
3322
					fx_cache_nibble_index = (value & 0x02) >> 1;
3323
					fx_cache_byte_index = (value & 0x0c) >> 2;
3324
					fx_multiplier = (value & 0x10) >> 4;
3325
					fx_subtract = (value & 0x20) >> 5;
3326
					if (value & 0x40) { // accumulate
3327
						int32_t m_result = (int16_t)((fx_cache[1] << 8) | fx_cache[0]) * (int16_t)((fx_cache[3] << 8) | fx_cache[2]);
3328
						if (fx_subtract)
3329
							fx_mult_accumulator -= m_result;
3330
						else
3331
							fx_mult_accumulator += m_result;
3332
					}
3333
					if (value & 0x80) { // reset accumulator
3334
						fx_mult_accumulator = 0;
3335
					}
3336
					break;
3337
				case 0x0c: // DCSEL=3, $9F29
3338
					fx_x_pixel_increment = ((((reg_composer[0x0d] & 0x7f) << 15) + (reg_composer[0x0c] << 7)) // base value
3339
						| ((reg_composer[0x0d] & 0x40) ? 0xffc00000 : 0)) // sign extend if negative
3340
						<< 5*(!!(reg_composer[0x0d] & 0x80)); // multiply by 32 if flag set
3341
					break;
3342
				case 0x0d: // DCSEL=3, $9F2A
3343
					fx_x_pixel_increment = ((((reg_composer[0x0d] & 0x7f) << 15) + (reg_composer[0x0c] << 7)) // base value
3344
						| ((reg_composer[0x0d] & 0x40) ? 0xffc00000 : 0)) // sign extend if negative
3345
						<< 5*(!!(reg_composer[0x0d] & 0x80)); // multiply by 32 if flag set
3346
					if (fx_addr1_mode == 1 || fx_addr1_mode == 2) {
3347
						// Reset subpixel to 0.5
3348
						fx_x_pixel_position = (fx_x_pixel_position & 0x07ff0000) | 0x00008000;
3349
					}
3350
					break;
3351
				case 0x0e: // DCSEL=3, $9F2B
3352
					fx_y_pixel_increment = ((((reg_composer[0x0f] & 0x7f) << 15) + (reg_composer[0x0e] << 7)) // base value
3353
						| ((reg_composer[0x0f] & 0x40) ? 0xffc00000 : 0)) // sign extend if negative
3354
						<< 5*(!!(reg_composer[0x0f] & 0x80)); // multiply by 32 if flag set
3355
					break;
3356
				case 0x0f: // DCSEL=3, $9F2C
3357
					fx_y_pixel_increment = ((((reg_composer[0x0f] & 0x7f) << 15) + (reg_composer[0x0e] << 7)) // base value
3358
						| ((reg_composer[0x0f] & 0x40) ? 0xffc00000 : 0)) // sign extend if negative
3359
						<< 5*(!!(reg_composer[0x0f] & 0x80)); // multiply by 32 if flag set
3360
					if (fx_addr1_mode == 1 || fx_addr1_mode == 2) {
3361
						// Reset subpixel to 0.5
3362
						fx_y_pixel_position = (fx_y_pixel_position & 0x07ff0000) | 0x00008000;
3363
					}
3364
					break;
3365
				case 0x10: // DCSEL=4, $9F29
3366
					fx_x_pixel_position = (fx_x_pixel_position & 0x0700ff80) | (value << 16);
3367
					fx_affine_prefetch();
3368
					break;
3369
				case 0x11: // DCSEL=4, $9F2A
3370
					fx_x_pixel_position = (fx_x_pixel_position & 0x00ffff00) | ((value & 0x7) << 24) | (value & 0x80);
3371
					fx_affine_prefetch();
3372
					break;
3373
				case 0x12: // DCSEL=4, $9F2B
3374
					fx_y_pixel_position = (fx_y_pixel_position & 0x0700ff80) | (value << 16);
3375
					fx_affine_prefetch();
3376
					break;
3377
				case 0x13: // DCSEL=4, $9F2C
3378
					fx_y_pixel_position = (fx_y_pixel_position & 0x00ffff00) | ((value & 0x7) << 24) | (value & 0x80);
3379
					fx_affine_prefetch();
3380
					break;
3381
				case 0x14: // DCSEL=5, $9F29
3382
					fx_x_pixel_position = (fx_x_pixel_position & 0x07ff0080) | (value << 8);
3383
					break;
3384
				case 0x15: // DCSEL=5, $9F2A
3385
					fx_y_pixel_position = (fx_y_pixel_position & 0x07ff0080) | (value << 8);
3386
					break;
3387
				case 0x18: // DCSEL=6, $9F29
3388
					fx_cache[0] = value;
3389
					break;
3390
				case 0x19: // DCSEL=6, $9F2A
3391
					fx_cache[1] = value;
3392
					break;
3393
				case 0x1a: // DCSEL=6, $9F2B
3394
					fx_cache[2] = value;
3395
					break;
3396
				case 0x1b: // DCSEL=6, $9F2C
3397
					fx_cache[3] = value;
3398
					break;
3399
			}
3400
			break;
3401
		}
3402
3403
		case 0x0D:
3404
		case 0x0E:
3405
		case 0x0F:
3406
		case 0x10:
3407
		case 0x11:
3408
		case 0x12:
3409
		case 0x13:
3410
			video_step(MHZ, 0, true); // potential midline raster effect
3411
			reg_layer[0][reg - 0x0D] = value;
3412
			refresh_layer_properties(0);
3413
			break;
3414
3415
		case 0x14:
3416
		case 0x15:
3417
		case 0x16:
3418
		case 0x17:
3419
		case 0x18:
3420
		case 0x19:
3421
		case 0x1A:
3422
			video_step(MHZ, 0, true); // potential midline raster effect
3423
			reg_layer[1][reg - 0x14] = value;
3424
			refresh_layer_properties(1);
3425
			break;
3426
3427
		case 0x1B: audio_render(); pcm_write_ctrl(value); break;
3428
		case 0x1C: audio_render(); pcm_write_rate(value); break;
3429
		case 0x1D: audio_render(); pcm_write_fifo(value); break;
3430
3431
		case 0x1E:
3432
		case 0x1F:
3433
			vera_spi_write(reg & 1, value);
3434
			break;
3435
	}
3436
}
3437
3438
void
3439
video_update_title(const char* window_title)
3440
{
3441
	SDL_SetWindowTitle(window, window_title);
3442
}
3443
3444
bool video_is_tilemap_address(int addr)
3445
{
3446
	for (int l = 0; l < 2; ++l) {
3447
		struct video_layer_properties *props = &layer_properties[l];
3448
		if (addr < props->map_base) {
3449
			continue;
3450
		}
3451
		if (addr >= props->map_base + (2 << (props->mapw_log2 + props->maph_log2))) {
3452
			continue;
3453
		}
3454
3455
		return true;
3456
	}
3457
	return false;
3458
}
3459
3460
bool video_is_tiledata_address(int addr)
3461
{
3462
	for (int l = 0; l < 2; ++l) {
3463
		struct video_layer_properties *props = &layer_properties[l];
3464
		if (addr < props->tile_base) {
3465
			continue;
3466
		}
3467
		int tile_size = props->tilew * props->tileh * props->bits_per_pixel / 8;
3468
		if (addr >= props->tile_base + tile_size * (props->bits_per_pixel == 1 ? 256 : 1024)) {
3469
			continue;
3470
		}
3471
3472
		return true;
3473
	}
3474
	return false;
3475
}
3476
3477
bool video_is_special_address(int addr)
3478
{
3479
	return addr >= 0x1F9C0;
3480
}
3481
3482
void
3483
stop6502(uint16_t address, uint8_t bank) {
3484
	// Testbench first: it is headless and scripted, so an STP there must report
3485
	// the result on stdout, not stop for a human. Putting the debugger check
3486
	// ahead of it made -testbench -imgui park invisibly with nothing to resume
3487
	// it and no "STP" line for the harness to read.
3488
	if (testbench) {
3489
		printf("STP\n");
3490
		fflush(stdout);
3491
	} else if (debugger_enabled || imgui_debugger_enabled) {
3492
		// STP is the 6502-side "break into the debugger". Either debugger will
3493
		// do: with -imgui alone this used to fall through to the modal message
3494
		// box below, which blocks inside instruction dispatch and whose "Reset
3495
		// Machine" button resets the machine mid-instruction.
3496
		DEBUGBreakToDebugger();
3497
		// Store only. BreakToDebugger has already announced the stop to a DAP
3498
		// client if this was a transition; this just refines the reason the
3499
		// local UI shows, which is what the source view uses to decide whether
3500
		// to re-centre.
3501
		DEBUGSetStopReason("breakpoint");
3502
	} else {
3503
		int return_btn;
3504
		char error_message[80];
3505
		const SDL_MessageBoxButtonData btns[2] = {
3506
			{SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 0, "Reset Machine"},
3507
			{SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, -1, "Ignore"}
3508
		};
3509
		const SDL_MessageBoxData msg_box = {
3510
			SDL_MESSAGEBOX_ERROR, window, "Error", error_message,
3511
			2, btns, NULL
3512
		};
3513
3514
		if (is_gen2) {
3515
			sprintf(error_message, "Encountered stop instruction at address $%06X. CPU cannot continue.", (regs.k << 16) | address);
3516
		} else {
3517
			sprintf(error_message, "Encountered stop instruction at address $%04X. CPU cannot continue.", address);
3518
		}
3519
		if (SDL_ShowMessageBox(&msg_box, &return_btn) == 0 && return_btn == 0) {
3520
			machine_reset();
3521
		};
3522
	}
3523
}