Skip to content

Commit

Permalink
SCUMM: (FM-TOWNS) - make scrolling more smooth for fast platforms
Browse files Browse the repository at this point in the history
The engine now measures whether it can perform one screen update within a 60Hz tick. Unfortunately the calls to OSystem::updateScreen() may take very long, depending on the backend and the filter setting. In this case the engine will start to catch up to the current frame. It should still look fine, unless the platform is way too slow for the selected filter setting (with the wrong settings it is not too difficult to achieve OSystem::updateScreen()  durations of over 100ms).
  • Loading branch information
athrxx committed May 28, 2021
1 parent 7413c50 commit d099ed5
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 15 deletions.
5 changes: 5 additions & 0 deletions engines/scumm/gfx.h
Expand Up @@ -469,6 +469,11 @@ class GdiHE16bit : public GdiHE {
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
// Helper class for FM-Towns output (required for specific hardware effects like switching graphics layers on and off).
class TownsScreen {
public:
enum {
kDirtyRectsMax = 20,
kFullRedraw = (kDirtyRectsMax + 1)
};
public:
TownsScreen(OSystem *system);
~TownsScreen();
Expand Down
30 changes: 19 additions & 11 deletions engines/scumm/gfx_towns.cpp
Expand Up @@ -185,7 +185,19 @@ void ScummEngine::towns_updateGfx() {
if (!_townsScreen)
return;

// Determine whether the smooth scrolling is likely to fall behind and needs to catch up (becoming more sloppy than smooth). It depends
// monstly on the hardware and the filter settings. Calls to _system->updateScreen() can be very expensive with the "wrong" filter setting.
// We simply check whether the average screen update duration would fit into a 60 Hz tick. If catchup mode is triggered once, it stays on
// permanently. Otherwise the scrolling can become very jerky when the engine keeps jumping between the settings (usually triggering it shortly
// after the start of a scrolling, resulting in a very visible jerk, and then falling back to non-catchup after the scrolling is done).
uint32 cur = _system->getMillis();
if (!_refreshNeedCatchUp) {
int dur = 0;
for (int i = 0; i < ARRAYSIZE(_refreshDuration); ++i)
dur += _refreshDuration[i];
_refreshNeedCatchUp = (dur / ARRAYSIZE(_refreshDuration)) > (1000 / 60);
}

while (_scrollTimer <= cur) {
if (!_scrollTimer)
_scrollTimer = cur;
Expand All @@ -194,6 +206,8 @@ void ScummEngine::towns_updateGfx() {
if (_townsScreen->isScrolling(0))
_scrollDeltaAdjust++;
_scrollRequest = 0;
if (!_refreshNeedCatchUp)
break;
}

_townsScreen->update();
Expand Down Expand Up @@ -315,9 +329,6 @@ const uint8 ScummEngine::_townsLayer2Mask[] = {
0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

#define FMTOWNS_DIRTY_RECTS_MAX 20
#define FMTOWNS_FULL_REDRAW (FMTOWNS_DIRTY_RECTS_MAX + 1)

TownsScreen::TownsScreen(OSystem *system) : _system(system), _width(0), _height(0), _pitch(0), _pixelFormat(system->getScreenFormat()), _scrollOffset(0), _scrollRemainder(0), _numDirtyRects(0) {
memset(&_layers[0], 0, sizeof(TownsScreenLayer));
memset(&_layers[1], 0, sizeof(TownsScreenLayer));
Expand Down Expand Up @@ -388,7 +399,7 @@ void TownsScreen::clearLayer(int layer) {

memset(l->pixels, 0, l->pitch * l->height);
_dirtyRects.push_back(Common::Rect(_width - 1, _height - 1));
_numDirtyRects = FMTOWNS_FULL_REDRAW;
_numDirtyRects = kFullRedraw;
}


Expand Down Expand Up @@ -431,10 +442,10 @@ uint8 *TownsScreen::getLayerPixels(int layer, int x, int y) const {
}

void TownsScreen::addDirtyRect(int x, int y, int w, int h) {
if (w <= 0 || h <= 0 || _numDirtyRects > FMTOWNS_DIRTY_RECTS_MAX)
if (w <= 0 || h <= 0 || _numDirtyRects > kDirtyRectsMax)
return;

if (_numDirtyRects == FMTOWNS_DIRTY_RECTS_MAX) {
if (_numDirtyRects == kDirtyRectsMax) {
// full redraw
_dirtyRects.clear();
_dirtyRects.push_back(Common::Rect(_width - 1, _height - 1));
Expand Down Expand Up @@ -501,7 +512,7 @@ void TownsScreen::toggleLayers(int flags) {

_dirtyRects.clear();
_dirtyRects.push_back(Common::Rect(_width - 1, _height - 1));
_numDirtyRects = FMTOWNS_FULL_REDRAW;
_numDirtyRects = kFullRedraw;

Graphics::Surface *s = _system->lockScreen();
assert(s);
Expand All @@ -524,7 +535,7 @@ void TownsScreen::scrollLayers(int flags, int offset) {

_dirtyRects.clear();
_dirtyRects.push_back(Common::Rect(_width - 1, _height - 1));
_numDirtyRects = FMTOWNS_FULL_REDRAW;
_numDirtyRects = kFullRedraw;

for (int i = 0; i < 2; ++i) {
if (!(flags & (1 << i)))
Expand Down Expand Up @@ -704,9 +715,6 @@ template void TownsScreen::updateScreenBuffer<uint16>();
template void TownsScreen::updateScreenBuffer<uint8>();
#endif

#undef FMTOWNS_DIRTY_RECTS_MAX
#undef FMTOWNS_FULL_REDRAW

} // End of namespace Scumm

#endif // DISABLE_TOWNS_DUAL_LAYER_MODE
21 changes: 17 additions & 4 deletions engines/scumm/scumm.cpp
Expand Up @@ -284,6 +284,9 @@ ScummEngine::ScummEngine(OSystem *syst, const DetectorResult &dr)
_townsScreen = 0;
_scrollRequest = _scrollDeltaAdjust = 0;
_scrollDestOffset = _scrollTimer = 0;
_refreshNeedCatchUp = false;
memset(_refreshDuration, 0, sizeof(_refreshDuration));
_refreshArrayPos = 0;
#ifdef USE_RGB_COLOR
_cjkFont = 0;
#endif
Expand Down Expand Up @@ -2264,10 +2267,9 @@ Common::Error ScummEngine::go() {
// before the main loop continues. We try to imitate that behaviour here to avoid glitches, but without making it
// overly complicated...
if (_scrollDeltaAdjust) {
int adj = MIN<int>(_scrollDeltaAdjust * 4 / 3 - _scrollDeltaAdjust, delta * 4 / 3 - delta);
delta += adj;
delta = MAX<int>(0, delta - _scrollDeltaAdjust) + (MIN<int>(_scrollDeltaAdjust, delta) << 2) / 3;
_scrollDeltaAdjust = 0;
}
_scrollDeltaAdjust = 0;
#endif
if (delta < 1) // Ensure we don't get into an endless loop
delta = 1; // by not decreasing sleepers.
Expand Down Expand Up @@ -2327,10 +2329,21 @@ void ScummEngine::waitForTimer(int msec_delay) {
_sound->updateCD(); // Loop CD Audio if needed
parseEvents();

#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
uint32 screenUpdateTimerStart = _system->getMillis();
towns_updateGfx();
#endif
_system->updateScreen();
uint32 cur = _system->getMillis();

if (_system->getMillis() >= start_time + msec_delay)
#ifndef DISABLE_TOWNS_DUAL_LAYER_MODE
// These measurements are used to determine whether the FM-Towns smooth scrolling is likely to fall behind and need to catch
// up (becoming more sloppy than smooth). Calls to _system->updateScreen() can require way longer than a 60Hz tick, depending
// on the hardware and the filter setting. In fact, these calls can take way over 100ms for some unfortunate configs.
_refreshDuration[_refreshArrayPos] = (int)(cur - screenUpdateTimerStart);
_refreshArrayPos = (_refreshArrayPos + 1) % ARRAYSIZE(_refreshDuration);
#endif
if (cur >= start_time + msec_delay)
break;
_system->delayMillis(10);
}
Expand Down
3 changes: 3 additions & 0 deletions engines/scumm/scumm.h
Expand Up @@ -1375,6 +1375,9 @@ class ScummEngine : public Engine, public Common::Serializable {
int _numCyclRects;
int _scrollRequest;
int _scrollDeltaAdjust;
int _refreshDuration[20];
int _refreshArrayPos;
bool _refreshNeedCatchUp;
uint32 _scrollTimer;
uint32 _scrollDestOffset;
uint16 _scrollFeedStrips[3];
Expand Down

0 comments on commit d099ed5

Please sign in to comment.