diff --git a/libretro.c b/libretro.c index ac43e06..e5e0cae 100644 --- a/libretro.c +++ b/libretro.c @@ -79,6 +79,35 @@ const float framerates[2][2] = { { MODE_NORM_COMPAT, MODE_HIGH_COMPAT } }; +/* + * Refresh rate follows the CRTC timing (R00, R04, R20) instead of the two + * fixed 61.46/55.46 Hz values: real modes run at e.g. 61.23, 60.46, 59.59, + * 57.89 or 53.49 Hz (verified against XM6 type G). + * + * "actual" is the true rate; with Adjust Frame Rates enabled the frontend is + * given a nearby friendlier rate and the audio clock is scaled to match. + */ +static float px68k_fps_actual(void) +{ + return floorf(1e9f / (float)CRTC_FrameClocks()) / 100.0f; +} + +static float px68k_fps_compat(float actual) +{ + if (actual >= 58.0f) + return 60.0f; + return floorf(actual * 2.0f + 0.5f) / 2.0f; +} + +static float px68k_current_framerate(void) +{ + float a = px68k_fps_actual(); + return Config.AdjustFrameRates ? px68k_fps_compat(a) : a; +} + +/* frame period the frontend was last told about, 100ns units */ +static uint32_t applied_frame_clk = 0; + char winx68k_dir[2048]; char winx68k_ini[2048]; @@ -143,12 +172,38 @@ static int FrameSkipQueue = 0; static int ClkUsed = 0; uint32_t retrow = 800; +/* Display aspect of the active picture, derived from CRTC timing + * (see WinDraw_Draw). 4:3 means the window fills the whole screen. */ +float retro_aspect = 4.0f / 3.0f; +/* core option px68k_aspect_ratio: 0 = X68000 (CRT timing), 1 = 4:3 (default), 2 = 1:1 */ +int px68k_aspect_mode = 1; uint32_t retroh = 600; /* Keep the full OSD keyboard visible even when the emulated mode is low-res. */ static bool osd_video_geometry_active = false; static uint16_t osd_video_buffer[800 * 512]; #define OSD_VIDEO_WIDTH 768 #define OSD_VIDEO_HEIGHT 512 + +/* Geometry the frontend should currently use: the keyboard's fixed 768x512 + * 4:3 reference while it is visible, otherwise the emulated mode with the + * aspect ratio chosen by the "Aspect Ratio" option / CRTC timing. */ +static void px68k_fill_geometry(struct retro_game_geometry *g) +{ + g->max_width = 800; + g->max_height = 600; + if (osd_video_geometry_active) + { + g->base_width = OSD_VIDEO_WIDTH; + g->base_height = OSD_VIDEO_HEIGHT; + g->aspect_ratio = 4.0f / 3.0f; + } + else + { + g->base_width = retrow; + g->base_height = retroh; + g->aspect_ratio = retro_aspect; + } +} int CHANGEAV = 0; int CHANGEAV_TIMING = 0; /* Separate change of geometry from change of refresh rate */ int VID_MODE = MODE_NORM; /* what framerate we start in */ @@ -170,20 +225,21 @@ static int fb_audio_clock(int clock) { double ratio, scaled; int result; + float actual = px68k_fps_actual(); if (!Config.AdjustFrameRates) { fb_audio_clock_remainder = 0.0; - fb_audio_last_mode = VID_MODE; + fb_audio_last_mode = (int)(actual * 100.0f); fb_audio_last_adjust = 0; return clock; } - if (fb_audio_last_mode != VID_MODE || fb_audio_last_adjust != Config.AdjustFrameRates) + if (fb_audio_last_mode != (int)(actual * 100.0f) || fb_audio_last_adjust != Config.AdjustFrameRates) { fb_audio_clock_remainder = 0.0; - fb_audio_last_mode = VID_MODE; + fb_audio_last_mode = (int)(actual * 100.0f); fb_audio_last_adjust = Config.AdjustFrameRates; } - ratio = (double)framerates[0][VID_MODE] / (double)framerates[1][VID_MODE]; + ratio = (double)actual / (double)px68k_fps_compat(actual); scaled = (double)clock * ratio + fb_audio_clock_remainder; result = (int)scaled; fb_audio_clock_remainder = scaled - (double)result; @@ -1875,6 +1931,20 @@ static void update_variables(int running) Config.FrameRate = 1; } + var.key = "px68k_aspect_ratio"; + var.value = NULL; + + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (!strcmp(var.value, "x68000")) + px68k_aspect_mode = 0; + else if (!strcmp(var.value, "1:1")) + px68k_aspect_mode = 2; + else + px68k_aspect_mode = 1; /* "4:3" */ + /* WinDraw_Draw notices the new aspect and updates the geometry */ + } + var.key = "px68k_adjust_frame_rates"; var.value = NULL; @@ -1950,8 +2020,9 @@ void retro_get_system_info(struct retro_system_info *info) void retro_get_system_av_info(struct retro_system_av_info *info) { /* FIXME handle PAL/NTSC */ - struct retro_game_geometry geom = { retrow, retroh, 800, 600, 4.0 / 3.0 }; + struct retro_game_geometry geom; struct retro_system_timing timing = { FRAMERATE, SOUNDRATE }; + px68k_fill_geometry(&geom); info->geometry = geom; info->timing = timing; } @@ -2335,7 +2406,8 @@ void retro_init(void) memset(Core_Key_State, 0, 512); memset(Core_old_Key_State, 0, sizeof(Core_old_Key_State)); - FRAMERATE = framerates[Config.AdjustFrameRates][VID_MODE]; + FRAMERATE = px68k_current_framerate(); + applied_frame_clk = CRTC_FrameClocks(); } @@ -2663,7 +2735,7 @@ static void WinX68k_Exec(void) vline = 0; clk_count = -ICount; - clk_total = (CRTC_Regs[0x29] & 0x10) ? VSYNC_HIGH : VSYNC_NORM; + clk_total = (int)CRTC_FrameClocks(); clk_total = (clk_total*Config.clockmhz)/10; clkdiv = Config.clockmhz; @@ -2825,9 +2897,9 @@ static void WinX68k_Exec(void) WinDraw_Draw(); t_end = timeGetTime(); - if ((int)(t_end - t_start) > ((CRTC_Regs[0x29] & 0x10) ? 14 : 16)) + if ((int)(t_end - t_start) > (CRTC_HighFreq() ? 14 : 16)) { - FrameSkipQueue += ((t_end - t_start) / ((CRTC_Regs[0x29] & 0x10) ? 14 : 16)) + 1; + FrameSkipQueue += ((t_end - t_start) / (CRTC_HighFreq() ? 14 : 16)) + 1; if (FrameSkipQueue > 100) FrameSkipQueue = 100; } @@ -2992,12 +3064,39 @@ void retro_run(void) audio_batch_cb((const int16_t*)soundbuf, soundbuf_size); /* TODO/FIXME - hardcoded pitch here */ + { + /* Refresh-rate change detection: the new rate must persist for 3 + * frames (mode switches write several registers, sometimes across + * frames) and differ by more than 0.1% before the frontend is told. */ + static uint32_t pending_clk = 0; + static int stable_frames = 0; + uint32_t c = CRTC_FrameClocks(); + uint32_t d = c > applied_frame_clk ? c - applied_frame_clk : applied_frame_clk - c; + if (applied_frame_clk && d * 1000u > applied_frame_clk) + { + if (c == pending_clk) + stable_frames++; + else + { + pending_clk = c; + stable_frames = 1; + } + if (stable_frames >= 3) + { + CHANGEAV_TIMING = 1; + stable_frames = 0; + } + } + else + stable_frames = 0; + } if (CHANGEAV || CHANGEAV_TIMING) { struct retro_system_av_info av; if (CHANGEAV_TIMING) { - FRAMERATE = framerates[Config.AdjustFrameRates][VID_MODE]; + FRAMERATE = px68k_current_framerate(); + applied_frame_clk = CRTC_FrameClocks(); audio_frame_fraction = 0.0; retro_get_system_av_info(&av); environ_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO, &av); @@ -3008,13 +3107,13 @@ void retro_run(void) else { struct retro_game_geometry geom; - geom.base_width = retrow; - geom.base_height = retroh; - geom.max_width = 800; - geom.max_height = 600; - geom.aspect_ratio = 4.0f / 3.0f; - if (!(menu_mode == menu_out && Keyboard_IsSwKeyboard())) + /* While the keyboard is up its own geometry stays in force; the + * latest game size/aspect is applied when it closes. */ + if (!osd_video_geometry_active) + { + px68k_fill_geometry(&geom); environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &geom); + } CHANGEAV = 0; } } @@ -3109,11 +3208,8 @@ void retro_run(void) { struct retro_game_geometry geom; osd_video_geometry_active = osd_visible; - geom.base_width = osd_visible ? OSD_VIDEO_WIDTH : retrow; - geom.base_height = osd_visible ? OSD_VIDEO_HEIGHT : retroh; - geom.max_width = 800; - geom.max_height = 600; - geom.aspect_ratio = 4.0f / 3.0f; + /* closing the keyboard restores the current game size AND aspect */ + px68k_fill_geometry(&geom); environ_cb(RETRO_ENVIRONMENT_SET_GEOMETRY, &geom); } diff --git a/libretro/timer.c b/libretro/timer.c index 18294f6..8e08eda 100644 --- a/libretro/timer.c +++ b/libretro/timer.c @@ -30,7 +30,7 @@ uint16_t Timer_GetCount(void) { uint32_t ticknow = timeGetUsec(); uint32_t dif = ticknow-tick; - uint32_t TIMEBASE = ((CRTC_Regs[0x29]&0x10)?VSYNC_HIGH:VSYNC_NORM); + uint32_t TIMEBASE = CRTC_FrameClocks(); timercnt += dif*10; /* switch from msec to usec */ tick = ticknow; if (timercnt >= TIMEBASE) diff --git a/libretro/windraw.c b/libretro/windraw.c index badd7d4..0cd032d 100644 --- a/libretro/windraw.c +++ b/libretro/windraw.c @@ -69,20 +69,93 @@ void WinDraw_Cleanup(void) /* Forward declarations */ extern uint32_t retrow, retroh; +extern float retro_aspect; extern int CHANGEAV; +/* Set to 0 to get the old behaviour (every window stretched to 4:3). */ +#ifndef PX68K_TIMING_ASPECT +#define PX68K_TIMING_ASPECT 1 +#endif + +/* + * Physical size of the active picture on a 4:3 monitor, as a fraction of the + * full screen, derived from the CRTC and dot clock timing (see CRTC_Model in + * crtc.c). This is what the "X68000" aspect ratio option uses: + * + * width = window time / visible scan time of the monitor + * height = lines / 256 (or / 512 for real 512-line frames) + * + * so e.g. Cotton "512*256 Hi" (a 384 dot window on a 512 dot clock) fills 75% + * of the width, and a 224 line picture fills 87.5% of the height. Checked + * against XM6 type G for 22 modes of 8 games. + */ +extern int px68k_aspect_mode; + +static float WinDraw_TimingAspect(uint32_t height) +{ + float wf, hf; + + if (px68k_aspect_mode == 1) + return 4.0f / 3.0f; + if (px68k_aspect_mode == 2) + return (TextDotX && height) ? (float)TextDotX / (float)height : 4.0f / 3.0f; +#if PX68K_TIMING_ASPECT + /* Width: share of the monitor's visible scan time that the picture + * occupies, from the dot clock (see CRTC_Model). */ + wf = CRTC_WidthFraction(); + if (wf <= 0.0f) + { + /* timing not modelled: fall back to window / total line length */ + uint32_t htotal = ((((uint32_t)CRTC_Regs[0]) << 8) | CRTC_Regs[1]) + 1; + if (htotal < 8 || TextDotX < 8) + return 4.0f / 3.0f; + wf = ((float)(TextDotX / 8) / (float)htotal) / + ((VLINE_TOTAL < 400) ? (32.0f / 38.0f) : (32.0f / 46.0f)); + } + if (wf > 1.0f) wf = 1.0f; /* overscan: fill the screen */ + if (wf < 0.25f) wf = 0.25f; /* transitional register values */ + + /* Height: a picture of N lines fills N/256 of the screen height + * (N/512 for real 512-line frames); e.g. 224 lines = 87.5%. */ + hf = (float)height / CRTC_FullHeightLines(); + if (hf > 1.0f) hf = 1.0f; + if (hf < 0.25f) hf = 0.25f; + + return (4.0f / 3.0f) * wf / hf; +#else + return 4.0f / 3.0f; +#endif +} + void FASTCALL WinDraw_Draw(void) { static int oldtextx = -1, oldtexty = -1; + static int oldaspect = -1; + uint32_t ey; + int asp; - if (oldtextx != TextDotX) + /* Lines the frame loop can really produce: it stops at VLINE_TOTAL, so a + * window whose VEND lies beyond VTOTAL (Cotton "448*240Low": VEND=275, + * VTOTAL=259) is shorter than VEND-VSTART. */ + ey = TextDotY; + if (CRTC_VEND > VLINE_TOTAL && VLINE_TOTAL > CRTC_VSTART) + ey = (((uint32_t)VLINE_TOTAL - CRTC_VSTART) * CRTC_VStep) / 2; + + if (oldtextx != (int)TextDotX) { oldtextx = TextDotX; CHANGEAV=1; } - if (oldtexty != TextDotY) + if (oldtexty != (int)ey) { - oldtexty = TextDotY; + oldtexty = ey; + CHANGEAV=1; + } + retro_aspect = WinDraw_TimingAspect(ey); + asp = (int)(retro_aspect * 1000.0f); + if (oldaspect != asp) + { + oldaspect = asp; CHANGEAV=1; } @@ -90,7 +163,7 @@ void FASTCALL WinDraw_Draw(void) { /* Clamp only frontend presentation dimensions; do not alter CRTC state. */ retrow = TextDotX > 800 ? 800 : TextDotX; - retroh = TextDotY > 600 ? 600 : TextDotY; + retroh = ey > 600 ? 600 : ey; if (!retrow) retrow = 1; if (!retroh) retroh = 1; } diff --git a/libretro_core_options.h b/libretro_core_options.h index dd951e6..e57b0d1 100644 --- a/libretro_core_options.h +++ b/libretro_core_options.h @@ -30,6 +30,7 @@ extern "C" { struct retro_core_option_v2_category option_cats_us[] = { { "system", "System", "Change emulated hardware settings." }, + { "video", "Video", "Change video output and aspect ratio settings." }, { "input", "Input", "Change controller types and button mapping." }, { "p1_joy2key", "P1 Joy2Key", "Map Player 1 RetroPad inputs to X68000 keyboard keys." }, { "p2_joy2key", "P2 Joy2Key", "Map Player 2 RetroPad inputs to X68000 keyboard keys." }, @@ -288,6 +289,7 @@ struct retro_core_option_v2_definition option_defs_us[] = { * Changing "Show layer 1" is fine, but don't change "layer_1"/etc or the possible values ("Yes|No"). * Adding more variables and rearranging them is safe. */ + { "px68k_cpu_emulation", "CPU Emulation", @@ -344,6 +346,58 @@ struct retro_core_option_v2_definition option_defs_us[] = { }, "2MB" }, + { + "px68k_aspect_ratio", + "Aspect Ratio", + NULL, + "Aspect ratio of the displayed picture. '4:3' stretches every picture to fill a 4:3 screen. 'X68000' reproduces what a real X68000 and its 4:3 monitor show: the size of the picture is computed from the CRTC and dot clock timing, so modes that use only part of the scan line or of the screen height (e.g. Cotton 512*256 Hi uses 75% of the width) get black borders. '1:1' shows square pixels at the core's raw resolution.", + NULL, + "video", + { + { "4:3", "4:3 (fill screen)" }, + { "x68000", "X68000 (CRT timing)" }, + { "1:1", "1:1 (square pixels)" }, + { NULL, NULL }, + }, + "4:3" + }, + { + "px68k_adjust_frame_rates", + "Adjust Frame Rates", + NULL, + "For compatibility with modern displays, changes the 15KHz mode from 61.46 to 60Hz. Disable to get the real machine timing.", + NULL, + "video", + { + { "disabled", NULL}, + { "enabled", NULL}, + { NULL, NULL }, + }, + "enabled" + }, + { + "px68k_frameskip", + "Frames Skip", + NULL, + "Choose how many frames will be skipped to improve performance at the expense of visual smoothness.", + NULL, + "video", + { + { "Full Frame", NULL }, + { "1/2 Frame", NULL }, + { "1/3 Frame", NULL }, + { "1/4 Frame", NULL }, + { "1/5 Frame", NULL }, + { "1/6 Frame", NULL }, + { "1/8 Frame", NULL }, + { "1/16 Frame", NULL }, + { "1/32 Frame", NULL }, + { "1/60 Frame", NULL }, + { "Auto Frame Skip", NULL }, + { NULL, NULL }, + }, + "Full Frame" + }, /* from PX68K Menu */ { "px68k_joy_mouse", @@ -1269,43 +1323,6 @@ struct retro_core_option_v2_definition option_defs_us[] = { }, "10" }, - { - "px68k_frameskip", - "Frames Skip", - NULL, - "Choose how many frames will be skipped to improve performance at the expense of visual smoothness.", - NULL, - "advanced", - { - { "Full Frame", NULL }, - { "1/2 Frame", NULL }, - { "1/3 Frame", NULL }, - { "1/4 Frame", NULL }, - { "1/5 Frame", NULL }, - { "1/6 Frame", NULL }, - { "1/8 Frame", NULL }, - { "1/16 Frame", NULL }, - { "1/32 Frame", NULL }, - { "1/60 Frame", NULL }, - { "Auto Frame Skip", NULL }, - { NULL, NULL }, - }, - "Full Frame" - }, - { - "px68k_adjust_frame_rates", - "Adjust Frame Rates", - NULL, - "For compatibility with modern displays, changes the 15KHz mode from 61.46 to 60Hz. Disable to get the real machine timing.", - NULL, - "advanced", - { - { "disabled", NULL}, - { "enabled", NULL}, - { NULL, NULL }, - }, - "enabled" - }, { "px68k_text_off", "Text Off", diff --git a/libretro_core_options_intl.h b/libretro_core_options_intl.h index 3f23ebc..2029da3 100644 --- a/libretro_core_options_intl.h +++ b/libretro_core_options_intl.h @@ -49,6 +49,11 @@ struct retro_core_option_v2_category option_cats_fr[] = { NULL, NULL, }, + { + "video", + NULL, + NULL, + }, { "input", NULL, diff --git a/x68k/crtc.c b/x68k/crtc.c index 3c56832..e62fe3f 100644 --- a/x68k/crtc.c +++ b/x68k/crtc.c @@ -12,6 +12,8 @@ #include "bg.h" #include "m68000.h" #include "crtc.h" +#include +#include "sysport.h" uint8_t CRTC_Regs[24*2]; uint8_t CRTC_Mode = 0; @@ -37,6 +39,7 @@ uint8_t CRTC_RCFlag[2] = {0, 0}; int HSYNC_CLK = 324; extern int VID_MODE, CHANGEAV_TIMING; + int CRTC_StateAction(StateMem *sm, int load, int data_only) { int vidmode, ret; @@ -185,6 +188,121 @@ void CRTC_Init(void) TextScrollY = 0; } +/* + * Which frame-rate class the emulated monitor timing belongs to. + * R20 bit 4 selects the *dot clock* family, not strictly the line rate: + * Cotton "448*240 Low" sets it while programming 15kHz timing + * (VTOTAL=259) and runs at 61.46Hz. A 31kHz-class mode has VTOTAL ~568 + * (24kHz ~440), so require a long frame as well as the bit. + */ +int CRTC_HighFreq(void) +{ + return (CRTC_Regs[0x29] & 0x10) && VLINE_TOTAL >= 300; +} + + +/* + * Dot clock / line timing model. + * + * line period = (R00+1) * 8 / dot clock + * field period = (R04 + 1 [+ 0.5 if interlaced]) * line period + * + * Dot clock, from the oscillator table of the real-hardware "CRTC operator" + * tool (Taka2, 1994; 38.86363 and 69.55199 MHz oscillators, 50.35 MHz on + * later models), indexed by R20 HF (bit 4), R20 HD (bits 0-1) and HRL: + * + * HF=0 HD=0 : 38.86363 / 8 HF=1 HD=0 : 69.55199 / 8 (HRL=1) or / 6 (HRL=0) + * HF=0 HD=1 : 38.86363 / 4 HF=1 HD=1 : 69.55199 / 4 (HRL=1) or / 3 (HRL=0) + * HF=1 HD=2 : 69.55199 / 2 + * HF=1 HD=3 : 50.35 / 2 (later models) + * + * HRL is system port #4 ($E8E007) bit 1; the programmer's manual describes + * it as selecting dividers 1/2,1/4,1/8 (1) or 1/2,1/3,1/6 (0), and both the + * IOCS ROM (it writes $0C/$0E to $E8E006) and the tool above set/clear + * bit 1 of $E8E007. + * + * Checked against XM6 type G's CRTC viewer for 22 modes of 8 games (Cotton, + * Chourensha, Akazucha, Baraduke, Dive On): frame rates agree to 0.01Hz and + * picture widths to <1%. Real modes run at e.g. 61.46, 61.23, 60.46, 59.59, + * 57.89, 54.66 and 53.49Hz, NOT just "15kHz = 61.46 / 31kHz = 55.46". + * + * If the HRL-selected clock gives an implausible refresh rate (e.g. the game + * has not written HRL yet in the middle of a mode switch) the other divider + * is tried before giving up and using the legacy fixed rates. + */ +static int CRTC_Model(double *fdot_mhz, double *line_us, double *fps) +{ + unsigned r20 = CRTC_Regs[0x29]; + unsigned hd = r20 & 3; + unsigned hf = (r20 >> 4) & 1; + unsigned chars = ((((unsigned)CRTC_Regs[0]) << 8) | CRTC_Regs[1]) & 1023; + double lines = (double)VLINE_TOTAL + 1.0; + int hrl = SysPort_HRL(), pass; + int interlace = hf ? ((r20 & 0x0c) == 0x08) : ((r20 & 0x04) != 0); + + chars += 1; + if (chars < 16 || VLINE_TOTAL < 100) + return 0; + if (interlace) + lines += 0.5; /* odd number of lines per frame, R04 counts a field */ + + for (pass = 0; pass < 2; pass++) + { + int h = pass ? !hrl : hrl; + double f, t, r; + if (!hf) + { + if (hd == 0) f = 38.86363 / 8.0; + else if (hd == 1) f = 38.86363 / 4.0; + else return 0; /* no 15kHz 768/1024 dot clock */ + } + else if (hd == 0) f = 69.55199 / (h ? 8.0 : 6.0); + else if (hd == 1) f = 69.55199 / (h ? 4.0 : 3.0); + else if (hd == 2) f = 69.55199 / 2.0; + else f = 50.35 / 2.0; + t = (double)chars * 8.0 / f; /* microseconds */ + r = 1e6 / (t * lines); + if (r >= 45.0 && r <= 72.0) + { + *fdot_mhz = f; *line_us = t; *fps = r; + return 1; + } + } + return 0; +} + +uint32_t CRTC_FrameClocks(void) +{ + double f, t, fps; + if (CRTC_Model(&f, &t, &fps)) + return (uint32_t)(1e7 / fps + 0.5); + return CRTC_HighFreq() ? VSYNC_HIGH : VSYNC_NORM; /* unknown timing */ +} + +/* A 4:3 monitor's visible scan time: ~52.7us on a 15.98kHz line, ~22.07us + * on a 31.5kHz line (XM6 draws 14.57 and 34.8 pixels/us on a 768 wide + * canvas); in-between line rates are interpolated. */ +float CRTC_WidthFraction(void) +{ + double f, t, fps, hkhz, full, w; + if (!CRTC_Model(&f, &t, &fps) || TextDotX < 8) + return 0.0f; + hkhz = 1000.0 / t; + if (hkhz <= 20.0) full = 52.71; + else if (hkhz >= 28.0) full = 22.07; + else full = 52.71 + (22.07 - 52.71) * (hkhz - 20.0) / 8.0; + w = ((double)TextDotX / f) / full; + return (float)w; +} + +/* Lines that fill the whole screen height: 256, or 512 for real 512-line + * frames. (R20 VD alone is not enough: Baraduke/Cotton set it on 261-line + * 15kHz frames and XM6 still draws 2 pixels per line.) */ +float CRTC_FullHeightLines(void) +{ + return ((CRTC_Regs[0x29] & 4) && VLINE_TOTAL >= 400) ? 512.0f : 256.0f; +} + static void CRTC_ScreenChanged(void) { static int last_vidmode = -1; @@ -202,13 +320,11 @@ static void CRTC_ScreenChanged(void) else CRTC_VStep = 2; - VID_MODE = !!(CRTC_Regs[0x29]&0x10); + VID_MODE = CRTC_HighFreq() ? 1 : 0; - if (VID_MODE != last_vidmode) - { - last_vidmode = VID_MODE; - CHANGEAV_TIMING = 1; - } + last_vidmode = VID_MODE; + /* The refresh rate is re-evaluated (and debounced) once per frame in + * retro_run(), because it now depends on R00/R04/R20 together. */ } uint8_t FASTCALL CRTC_Read(uint32_t adr) @@ -247,7 +363,8 @@ void FASTCALL CRTC_Write(uint32_t adr, uint8_t data) { case 0x00: case 0x01: - /* HTOTAL */ + /* HTOTAL: changes the line period, hence the frame period */ + HSYNC_CLK = CRTC_FrameClocks()/(VLINE_TOTAL ? VLINE_TOTAL : 1); break; case 0x02: case 0x03: @@ -269,7 +386,10 @@ void FASTCALL CRTC_Write(uint32_t adr, uint8_t data) case 0x08: case 0x09: VLINE_TOTAL = (((uint16_t)CRTC_Regs[8]<<8)+CRTC_Regs[9]) & 1023; - HSYNC_CLK = ((CRTC_Regs[0x29]&0x10)?VSYNC_HIGH:VSYNC_NORM)/VLINE_TOTAL; + HSYNC_CLK = CRTC_FrameClocks()/(VLINE_TOTAL ? VLINE_TOTAL : 1); + /* the frame-rate class depends on VTOTAL too */ + TextDotY = CRTC_VEND-CRTC_VSTART; + CRTC_ScreenChanged(); break; case 0x0a: case 0x0b: @@ -292,7 +412,7 @@ void FASTCALL CRTC_Write(uint32_t adr, uint8_t data) TVRAM_SetAllDirty(); break; case 0x29: - HSYNC_CLK = ((CRTC_Regs[0x29]&0x10)?VSYNC_HIGH:VSYNC_NORM)/VLINE_TOTAL; + HSYNC_CLK = CRTC_FrameClocks()/(VLINE_TOTAL ? VLINE_TOTAL : 1); TextDotY = CRTC_VEND-CRTC_VSTART; CRTC_ScreenChanged(); break; diff --git a/x68k/crtc.h b/x68k/crtc.h index a7ab45d..1b95bf0 100644 --- a/x68k/crtc.h +++ b/x68k/crtc.h @@ -28,6 +28,10 @@ extern uint32_t GrphScrollX[4]; extern uint32_t GrphScrollY[4]; void CRTC_Init(void); +int CRTC_HighFreq(void); +uint32_t CRTC_FrameClocks(void); /* frame period in 100ns units (10MHz clocks) */ +float CRTC_WidthFraction(void); /* picture width / full screen width, 0 = unknown */ +float CRTC_FullHeightLines(void); /* lines that fill the screen height: 256 or 512 */ void CRTC_RasterCopy(void); diff --git a/x68k/sysport.c b/x68k/sysport.c index c1d437c..56280c9 100644 --- a/x68k/sysport.c +++ b/x68k/sysport.c @@ -35,6 +35,13 @@ int SysPort_StateAction(StateMem *sm, int load, int data_only) return ret; } +/* System port #4 ($E8E007) bit 1 = HRL: 0 -> dot clock dividers 1/2,1/3,1/6; + * 1 -> 1/2,1/4,1/8 (X68000 programmer's manual). */ +int SysPort_HRL(void) +{ + return (sysport.keyctrl >> 1) & 1; +} + void SysPort_Init(void) { static uint8_t cputype[] = { diff --git a/x68k/sysport.h b/x68k/sysport.h index 945e31f..3fcbbd4 100644 --- a/x68k/sysport.h +++ b/x68k/sysport.h @@ -7,6 +7,7 @@ extern uint8_t SysPort[7]; void SysPort_Init(void); +int SysPort_HRL(void); /* $E8E007 bit 1: dot clock divider set */ uint8_t FASTCALL SysPort_Read(uint32_t adr); void FASTCALL SysPort_Write(uint32_t adr, uint8_t data); int SysPort_StateAction(StateMem *sm, int load, int data_only);