BACKENDS: ATARI: Implement aspect ratio correction

This commit is contained in:
Miro Kropacek 2023-08-12 23:03:38 +02:00
parent fc3995f351
commit b510eff336
3 changed files with 155 additions and 14 deletions

View File

@ -78,7 +78,6 @@ static void VblHandler() {
: (2 * MAX_HZ_SHAKE * bitsPerPixel / 8) / 2 - bitsPerPixel;
}
union { byte c[4]; uintptr p; } sptr;
sptr.p = p;
@ -122,6 +121,34 @@ static uint32 UninstallVblHandler() {
return uninstalled;
}
static void shrinkVidelVisibleArea() {
// Active VGA screen area consists of 960 half-lines, i.e. 480 raster lines.
// In case of 320x240, the number is still 480 but data is fetched
// only for 240 lines so it doesn't make a difference to us.
Vsync();
if (hasSuperVidel()) {
const int vOffset = ((480 - 400) / 2) * 2; // *2 because of half-lines
// VDB = VBE = VDB + paddding/2
*((volatile uint16*)0xFFFF82A8) = *((volatile uint16*)0xFFFF82A6) = *((volatile uint16*)0xFFFF82A8) + vOffset;
// VDE = VBB = VDE - padding/2
*((volatile uint16*)0xFFFF82AA) = *((volatile uint16*)0xFFFF82A4) = *((volatile uint16*)0xFFFF82AA) - vOffset;
} else {
// 31500/60.1 = 524 raster lines
// vft = 524 * 2 + 1 = 1049 half-lines
// 480 visible lines = 960 half-lines
// 1049 - 960 = 89 half-lines reserved for borders
// we want 400 visible lines = 800 half-lines
// vft = 800 + 89 = 889 half-lines in total ~ 70.1 Hz vertical frequency
int16 vft = *((volatile int16*)0xFFFF82A2);
int16 vss = *((volatile int16*)0xFFFF82AC); // vss = vft - vss_sync
vss -= vft; // -vss_sync
*((volatile int16*)0xFFFF82A2) = 889;
*((volatile int16*)0xFFFF82AC) = 889 + vss;
}
}
static int s_oldRez = -1;
static int s_oldMode = -1;
static void *s_oldPhysbase = nullptr;
@ -134,6 +161,8 @@ void AtariGraphicsShutdown() {
} else if (s_oldMode != -1) {
// prevent setting video base address just on the VDB line
Vsync();
if (hasSuperVidel())
VsetMode(SVEXT | SVEXT_BASERES(0) | COL80 | BPS8C); // resync to proper 640x480
VsetMode(s_oldMode);
VsetScreen(SCR_NOCHANGE, s_oldPhysbase, SCR_NOCHANGE, SCR_NOCHANGE);
}
@ -227,7 +256,7 @@ bool AtariGraphicsManager::hasFeature(OSystem::Feature f) const {
switch (f) {
case OSystem::Feature::kFeatureAspectRatioCorrection:
//debug("hasFeature(kFeatureAspectRatioCorrection): %d", !_vgaMonitor);
return !_tt && !_vgaMonitor;
return !_tt;
case OSystem::Feature::kFeatureCursorPalette:
// FIXME: pretend to have cursor palette at all times, this function
// can get (and it is) called any time, before and after showOverlay()
@ -249,7 +278,7 @@ void AtariGraphicsManager::setFeatureState(OSystem::Feature f, bool enable) {
_aspectRatioCorrection = enable;
break;
default:
[[fallthrough]];
break;
}
}
@ -574,7 +603,7 @@ void AtariGraphicsManager::updateScreen() {
_pendingScreenChange = kPendingScreenChangeNone;
if (_oldAspectRatioCorrection != _aspectRatioCorrection) {
if (!isOverlayVisible()) {
if (!isOverlayVisible() && _currentState.height == 200) {
if (!_vgaMonitor) {
short mode = VsetMode(VM_INQUIRE);
if (_aspectRatioCorrection) {
@ -587,15 +616,82 @@ void AtariGraphicsManager::updateScreen() {
mode |= PAL;
}
VsetMode(mode);
} else if (hasSuperVidel()) {
// TODO: reduce to 200 scan lines?
} else if (!_tt) {
// TODO: increase vertical frequency?
} else if (hasSuperVidel() || !_tt) {
if (_aspectRatioCorrection) {
for (int screenId : { FRONT_BUFFER, BACK_BUFFER1, BACK_BUFFER2 }) {
Screen *screen = _screen[screenId];
Graphics::Surface *offsettedSurf = screen->offsettedSurf;
// erase old screen
offsettedSurf->fillRect(Common::Rect(offsettedSurf->w, offsettedSurf->h), 0);
// setup new screen
screen->oldScreenSurfaceWidth = screen->surf.w;
screen->oldScreenSurfaceHeight = screen->surf.h;
screen->oldScreenSurfacePitch = screen->surf.pitch;
screen->oldOffsettedSurfaceWidth = offsettedSurf->w;
screen->oldOffsettedSurfaceHeight = offsettedSurf->h;
screen->surf.w = 320 + 2 * MAX_HZ_SHAKE;
screen->surf.h = 200 + 2 * MAX_V_SHAKE;
screen->surf.pitch = screen->surf.w;
offsettedSurf->init(
320, 200, screen->surf.pitch,
screen->surf.getBasePtr((screen->surf.w - 320) / 2, (screen->surf.h - 200) / 2),
screen->surf.format);
screen->addDirtyRect(*lockScreen(), Common::Rect(offsettedSurf->w, offsettedSurf->h), _currentState.mode == GraphicsMode::DirectRendering);
}
Supexec(shrinkVidelVisibleArea);
} else {
for (int screenId : { FRONT_BUFFER, BACK_BUFFER1, BACK_BUFFER2 }) {
Screen *screen = _screen[screenId];
Graphics::Surface *offsettedSurf = screen->offsettedSurf;
assert(screen->oldScreenSurfaceWidth != -1);
assert(screen->oldScreenSurfaceHeight != -1);
assert(screen->oldScreenSurfacePitch != -1);
assert(screen->oldOffsettedSurfaceWidth != -1);
assert(screen->oldOffsettedSurfaceHeight != -1);
// erase old screen
offsettedSurf->fillRect(Common::Rect(offsettedSurf->w, offsettedSurf->h), 0);
// setup new screen
screen->surf.w = screen->oldScreenSurfaceWidth;
screen->surf.h = screen->oldScreenSurfaceHeight;
screen->surf.pitch = screen->oldScreenSurfacePitch;
offsettedSurf->init(
screen->oldOffsettedSurfaceWidth, screen->oldOffsettedSurfaceHeight, screen->surf.pitch,
screen->surf.getBasePtr(
(screen->surf.w - screen->oldOffsettedSurfaceWidth) / 2,
(screen->surf.h - screen->oldOffsettedSurfaceHeight) / 2),
screen->surf.format);
screen->oldScreenSurfaceWidth = -1;
screen->oldScreenSurfaceHeight = -1;
screen->oldScreenSurfacePitch = -1;
screen->oldOffsettedSurfaceWidth = -1;
screen->oldOffsettedSurfaceHeight = -1;
screen->addDirtyRect(*lockScreen(), Common::Rect(offsettedSurf->w, offsettedSurf->h), _currentState.mode == GraphicsMode::DirectRendering);
}
if (hasSuperVidel())
VsetMode(SVEXT | SVEXT_BASERES(0) | COL80 | BPS8C); // resync to proper 640x480
VsetMode(_workScreen->mode);
}
} else {
// TODO: some tricks with TT's 480 lines?
}
_oldAspectRatioCorrection = _aspectRatioCorrection;
_pendingScreenChange |= kPendingScreenChangeScreen;
updateScreen();
} else {
// ignore new value in overlay
_aspectRatioCorrection = _oldAspectRatioCorrection;

View File

@ -294,6 +294,12 @@ private:
int mode = -1;
Graphics::Surface *const offsettedSurf = &_offsettedSurf;
int oldScreenSurfaceWidth = -1;
int oldScreenSurfaceHeight = -1;
int oldScreenSurfacePitch = -1;
int oldOffsettedSurfaceWidth = -1;
int oldOffsettedSurfaceHeight = -1;
private:
static constexpr size_t ALIGN = 16; // 16 bytes

View File

@ -213,6 +213,39 @@ means that if the SuperVidel is detected, it does the following:
and makes a *huge* difference for 640x480 fullscreen updates.
Aspect ratio correction
-----------------------
Please refer to the official documentation about its usage. Normally ScummVM
implements this functionality using yet another fullscreen transformation of
320x200 surface into a 320x240 one (there is even a selection of algorithms
for this task, varying in performance and quality).
Naturally, this would pose a terrible performance anchor on our backend so some
cheating has been used:
- on RGB, the vertical refresh rate frequency is set to 60 Hz, creating an
illusion of creating non-square pixels. Works best on CRT monitors.
- on VGA, the vertical refresh rate frequency is set to 70 Hz, with more or
less the same effect as on RGB. Works best on CRT monitors.
- on SuperVidel, video output is modified in such way that the DVI/HDMI monitor
receives a 320x200 image and if properly set/supported, it will automatically
stretch the image to 320x240 (this is usually a setting called "picture
expansion" or "picture stretch" -- make sure it isn't set to something like
"1:1" or "dot by dot")
Yes, it's a hack. :) Owners of a CRT monitor can achieve the same effect by the
analog knobs -- stretch and move the 320x200 picture unless black borders are
no longer visible. This hack provides a more elegant and per-game
functionality.
Realtime aspect ratio correction (CTRL+ALT+a) should be used with caution in
Direct rendering mode because there's no way to refresh the screen. So if you
change the setting and there isn't any game screen update coming, screen will
stay black.
Audio mixing
------------
@ -295,8 +328,9 @@ music (and therefore avoiding the expensive synthesis emulation) but beware, it
doesn't affect CD (*.wav) playback at all! Same applies for speech and sfx.
The least amount of cycles is spent when:
- "No music" (or keep it default and choose a native MIDI device) is set in the
GUI options; this prevents MIDI sythesis of any kind
- "No music" as "Preferred device": this prevents MIDI sythesis of any kind
- "Subtitles" as "Text and speech": this prevents any sampled speech to be
mixed
- all external audio files are deleted (typically *.wav); that way the mixer
wont have anything to mix. However beware, this is not allowed in every game!
@ -336,8 +370,6 @@ restricts features but also improves performance:
Known issues
------------
- aspect ratio correction works on RGB only (yet)
- adding a game in TOS and loading it in FreeMiNT (and vice versa) generates
incompatible paths. Either use only one system or edit scummvm.ini and set
there only relative paths (mintlib bug/limitation).
@ -349,6 +381,9 @@ Known issues
- horizontal screen shaking doesn't work on TT because TT Shifter doesn't
support fine scrolling. However it is "emulated" via vertical shaking.
- aspect ratio correction has no effect on TT because is not possible to alter
its vertical screen refresh frequency.
- tooltips in overlay are sometimes drawn with corrupted background.
- the talkie version of MI1 needs to be merged from two sources: first generate
@ -373,11 +408,15 @@ Known issues
- Engine GUI (for save/load/etc) does not support 8-bit screens
- https://wiki.scummvm.org/index.php?title=Hugo
- Indy4 (the adventure) may have a bug in the screen when you K.O. the bouncer.
I was able to get a freeze when he fell to the ground but currently I am
unable to reproduce it. It may be related to the intensive mouse clicking
during that scene so feel free to use keypad for the fight and report whether
it has improved the situation.
Future plans
------------
- aspect ratio correction for TT/VGA/SuperVidel
- unified file paths in scummvm.ini
- DSP-based sample mixer (WAV, FLAC, MP2)