[Scummvm-git-logs] scummvm master -> 0e2cb19de787a8cc2e41a22960d22250b253df28

mikrosk noreply at scummvm.org
Sat Mar 25 21:02:14 UTC 2023


This automated email contains information about 4 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
6c5e3dbfd5 BACKENDS: ATARI: Refactor
52f8bd45e2 BACKENDS: ATARI: Use RGB332 for the overlay
a98ce2c211 BACKENDS: ATARI: Double/triple buffering doesn't enforce fullscreen redraws
0e2cb19de7 ATARI: Use dlmalloc for final release


Commit: 6c5e3dbfd5d4f412b4280159029c64fdcf7f4cf7
    https://github.com/scummvm/scummvm/commit/6c5e3dbfd5d4f412b4280159029c64fdcf7f4cf7
Author: Miro Kropacek (miro.kropacek at gmail.com)
Date: 2023-03-25T21:45:50+01:00

Commit Message:
BACKENDS: ATARI: Refactor

Changed paths:
    backends/events/atari/atari-events.cpp
    backends/graphics/atari/atari-graphics-superblitter.h
    backends/graphics/atari/atari-graphics-supervidel.h
    backends/graphics/atari/atari-graphics-videl.h
    backends/graphics/atari/atari-graphics.cpp
    backends/graphics/atari/atari-graphics.h
    backends/platform/atari/osystem_atari.cpp
    backends/platform/atari/osystem_atari.h
    backends/platform/atari/readme.txt
    configure
    graphics/blit-atari.cpp
    graphics/blit.cpp
    graphics/module.mk
    graphics/surface.cpp


diff --git a/backends/events/atari/atari-events.cpp b/backends/events/atari/atari-events.cpp
index 65f8f773c32..1eeccb7f7e0 100644
--- a/backends/events/atari/atari-events.cpp
+++ b/backends/events/atari/atari-events.cpp
@@ -19,8 +19,6 @@
  *
  */
 
-#define FORBIDDEN_SYMBOL_EXCEPTION_time_h
-
 #include "backends/events/atari/atari-events.h"
 
 #include <mint/osbind.h>
@@ -122,7 +120,7 @@ bool AtariEventSource::pollEvent(Common::Event &event) {
 
 		if (curMillis - startMillis >= 1000) {
 			float avgFps = avgFpsSum / avgFpsCount;
-			debug("*** Average FPS in 1s: %f ***", avgFps);
+			//debug("*** Average FPS in 1s: %f ***", avgFps);
 			startMillis = curMillis;
 			avgFpsSum = 0;
 			avgFpsCount = 0;
diff --git a/backends/graphics/atari/atari-graphics-superblitter.h b/backends/graphics/atari/atari-graphics-superblitter.h
index fb295b554a0..8d7855b5ee8 100644
--- a/backends/graphics/atari/atari-graphics-superblitter.h
+++ b/backends/graphics/atari/atari-graphics-superblitter.h
@@ -22,32 +22,20 @@
 #ifndef BACKENDS_GRAPHICS_ATARI_SUPERBLITTER_H
 #define BACKENDS_GRAPHICS_ATARI_SUPERBLITTER_H
 
-#include <mint/trap14.h>
+#include <mint/cookie.h>
+#include <mint/falcon.h>
 
-#define ct60_vm(mode, value) (long)trap_14_wwl((short)0xc60e, (short)(mode), (long)(value))
-#define ct60_vmalloc(value) ct60_vm(0, value)
-#define ct60_vmfree(value)  ct60_vm(1, value)
-
-// bits 26:0
-#define SV_BLITTER_SRC1           ((volatile long*)0x80010058)
-#define SV_BLITTER_SRC2           ((volatile long*)0x8001005C)
-#define SV_BLITTER_DST            ((volatile long*)0x80010060)
-// The amount of bytes that are to be copied in a horizontal line, minus 1
-#define SV_BLITTER_COUNT          ((volatile long*)0x80010064)
-// The amount of bytes that are to be added to the line start adress after a line has been copied, in order to reach the next one
-#define SV_BLITTER_SRC1_OFFSET    ((volatile long*)0x80010068)
-#define SV_BLITTER_SRC2_OFFSET    ((volatile long*)0x8001006C)
-#define SV_BLITTER_DST_OFFSET     ((volatile long*)0x80010070)
-// bits 11:0 - The amount of horizontal lines to do
-#define SV_BLITTER_MASK_AND_LINES ((volatile long*)0x80010074)
-// bit    0 - busy / start
-// bits 4:1 - blit mode
-#define SV_BLITTER_CONTROL        ((volatile long*)0x80010078)
 // bits 9:0
-#define SV_VERSION                ((volatile long*)0x8001007C)
-// bit 0 - empty (read only)
-// bit 1 - full (read only)
-// bits 31:0 - data (write only)
-#define SV_BLITTER_FIFO           ((volatile long*)0x80010080)
+#define SV_VERSION	((volatile long*)0x8001007C)
+
+inline static bool hasSuperVidel() {
+	static bool hasSuperVidel = VgetMonitor() == MON_VGA && Getcookie(C_SupV, NULL) == C_FOUND;
+	return hasSuperVidel;
+}
+
+static int superVidelFwVersion = hasSuperVidel() ? *SV_VERSION & 0x01ff : 0;
+
+void lockSuperBlitter();
+void unlockSuperBlitter();
 
 #endif
diff --git a/backends/graphics/atari/atari-graphics-supervidel.h b/backends/graphics/atari/atari-graphics-supervidel.h
index e7e98b38b5d..e230bd4fc72 100644
--- a/backends/graphics/atari/atari-graphics-supervidel.h
+++ b/backends/graphics/atari/atari-graphics-supervidel.h
@@ -22,75 +22,42 @@
 #ifndef BACKENDS_GRAPHICS_ATARI_SUPERVIDEL_H
 #define BACKENDS_GRAPHICS_ATARI_SUPERVIDEL_H
 
-#define USE_SV_BLITTER	// TODO: into configure?
-
 #include "backends/graphics/atari/atari-graphics.h"
 
-#include <cstring>
 #include <mint/osbind.h>
 
-#ifdef USE_SV_BLITTER
 #include "backends/graphics/atari/atari-graphics-superblitter.h"
-#endif
-
 #include "backends/graphics/atari/videl-resolutions.h"
+#include "common/debug.h"	// error() & warning()
 #include "common/scummsys.h"
-#include "common/textconsole.h"	// for error()
 
 class AtariSuperVidelManager : public AtariGraphicsManager {
 public:
 	AtariSuperVidelManager() {
 #ifdef USE_SV_BLITTER
-		_fwVersion = *SV_VERSION & 0x01ff;
-		debug("SuperVidel FW Revision: %d, using %s", _fwVersion, _fwVersion >= 9
-			  ? "fast async FIFO" : "slower sync blitting" );
+		debug("SuperVidel FW Revision: %d, using %s", superVidelFwVersion, superVidelFwVersion >= 9
+			? "fast async FIFO" : "slower sync blitting");
+#else
+		debug("SuperVidel FW Revision: %d, SuperBlitter not used", superVidelFwVersion);
 #endif
-
-		for (int i = 0; i < SCREENS; ++i) {
-			if (!allocateAtariSurface(_screen[i], _screenSurface,
-					SCREEN_WIDTH, SCREEN_HEIGHT, PIXELFORMAT8,
-					MX_STRAM, 0xA0000000))
-				error("Failed to allocate screen memory in ST RAM");
-			_screenAligned[i] = (byte*)_screenSurface.getPixels();
-		}
-		_screenSurface.setPixels(_screenAligned[getDefaultGraphicsMode() <= 1 ? FRONT_BUFFER : BACK_BUFFER1]);
-
-		if (!allocateAtariSurface(_chunkyBuffer, _chunkySurface,
-				SCREEN_WIDTH, SCREEN_HEIGHT, PIXELFORMAT8,
-				MX_PREFTTRAM))
-			error("Failed to allocate chunky buffer memory in ST/TT RAM");
-
-		if (!allocateAtariSurface(_overlayScreen, _screenOverlaySurface,
-				getOverlayWidth(), getOverlayHeight(), getOverlayFormat(),
-				MX_STRAM, 0xA0000000))
-			error("Failed to allocate overlay memory in ST RAM");
-
-		if (!allocateAtariSurface(_overlayBuffer, _overlaySurface,
-				getOverlayWidth(), getOverlayHeight(), getOverlayFormat(),
-				MX_PREFTTRAM))
-			error("Failed to allocate overlay buffer memory in ST/TT RAM");
+		if (Supexec(hasSvRamBoosted))
+			debug("SV_XBIOS has the pmmu boost enabled");
+		else
+			warning("SV_XBIOS has the pmmu boost disabled, set 'pmmu_boost = true' in C:\\SV.INF");
 
 		// patch SPSHIFT for SuperVidel's BPS8C
 		for (byte *p : {scp_320x200x8_vga, scp_320x240x8_vga, scp_640x400x8_vga, scp_640x480x8_vga}) {
 			uint16 *p16 = (uint16*)(p + 122 + 30);
 			*p16 |= 0x1000;
 		}
+
+		// using virtual methods so must be done here
+		allocateSurfaces();
 	}
 
 	~AtariSuperVidelManager() {
-#ifdef USE_SV_BLITTER
-		ct60_vmfree(_chunkyBuffer);
-#else
-		Mfree(_chunkyBuffer);
-#endif
-		_chunkyBuffer = nullptr;
-
-#ifdef USE_SV_BLITTER
-		ct60_vmfree(_overlayBuffer);
-#else
-		Mfree(_overlayBuffer);
-#endif
-		_overlayBuffer = nullptr;
+		// using virtual methods so must be done here
+		freeSurfaces();
 	}
 
 	virtual const OSystem::GraphicsMode *getSupportedGraphicsModes() const override {
@@ -107,95 +74,56 @@ public:
 	int16 getOverlayHeight() const override { return 2 * OVERLAY_HEIGHT; }
 	int16 getOverlayWidth() const override { return 2 * OVERLAY_WIDTH; }
 
-private:
-	virtual void* allocFast(size_t bytes) const override {
-#ifdef USE_SV_BLITTER
-		return (void*)ct60_vmalloc(bytes);
-#else
-		return (void*)Mxalloc(bytes, MX_PREFTTRAM);
-#endif
-	}
+protected:
+	AtariMemAlloc getStRamAllocFunc() const override {
+		return [](size_t bytes) {
+			uintptr ptr = Mxalloc(bytes, MX_STRAM);
 
-	void copySurfaceToSurface(const Graphics::Surface &srcSurface, Graphics::Surface &dstSurface) const override {
-#ifdef USE_SV_BLITTER
-		if (_fwVersion >= 9) {
-			*SV_BLITTER_FIFO = (long)srcSurface.getPixels();	// SV_BLITTER_SRC1
-			*SV_BLITTER_FIFO = 0x00000000;						// SV_BLITTER_SRC2
-			*SV_BLITTER_FIFO = (long)dstSurface.getPixels();	// SV_BLITTER_DST
-			*SV_BLITTER_FIFO = srcSurface.w - 1;				// SV_BLITTER_COUNT
-			*SV_BLITTER_FIFO = srcSurface.pitch;				// SV_BLITTER_SRC1_OFFSET
-			*SV_BLITTER_FIFO = 0x00000000;						// SV_BLITTER_SRC2_OFFSET
-			*SV_BLITTER_FIFO = dstSurface.pitch;				// SV_BLITTER_DST_OFFSET
-			*SV_BLITTER_FIFO = srcSurface.h;					// SV_BLITTER_MASK_AND_LINES
-			*SV_BLITTER_FIFO = 0x01;							// SV_BLITTER_CONTROL
-		} else {
-			sync();
-
-			*SV_BLITTER_SRC1           = (long)srcSurface.getPixels();
-			*SV_BLITTER_SRC2           = 0x00000000;
-			*SV_BLITTER_DST            = (long)dstSurface.getPixels();
-			*SV_BLITTER_COUNT          = srcSurface.w - 1;
-			*SV_BLITTER_SRC1_OFFSET    = srcSurface.pitch;
-			*SV_BLITTER_SRC2_OFFSET    = 0x00000000;
-			*SV_BLITTER_DST_OFFSET     = dstSurface.pitch;
-			*SV_BLITTER_MASK_AND_LINES = srcSurface.h;
-			*SV_BLITTER_CONTROL        = 0x01;
-		}
-#else
-		memcpy(dstSurface.getPixels(), srcSurface.getPixels(), srcSurface.h * srcSurface.pitch);
-#endif
+			if (ptr != 0)
+				ptr |= 0xA0000000;
+
+			return (void*)ptr;
+		};
+	}
+	AtariMemFree getStRamFreeFunc() const override {
+		return [](void *ptr) { Mfree((uintptr)ptr & 0x00FFFFFF); };
 	}
 
-	void copyRectToSurface(const Graphics::Surface &srcSurface, int destX, int destY, Graphics::Surface &dstSurface,
+private:
+	void copyRectToSurface(Graphics::Surface &dstSurface,
+						   const Graphics::Surface &srcSurface, int destX, int destY,
 						   const Common::Rect &subRect) const override {
-#ifdef USE_SV_BLITTER
-		if (_fwVersion >= 9) {
-			*SV_BLITTER_FIFO = (long)srcSurface.getBasePtr(subRect.left, subRect.top);	// SV_BLITTER_SRC1
-			*SV_BLITTER_FIFO = 0x00000000;												// SV_BLITTER_SRC2
-			*SV_BLITTER_FIFO = (long)dstSurface.getBasePtr(destX, destY);				// SV_BLITTER_DST
-			*SV_BLITTER_FIFO = subRect.width() - 1;										// SV_BLITTER_COUNT
-			*SV_BLITTER_FIFO = srcSurface.pitch;										// SV_BLITTER_SRC1_OFFSET
-			*SV_BLITTER_FIFO = 0x00000000;												// SV_BLITTER_SRC2_OFFSET
-			*SV_BLITTER_FIFO = dstSurface.pitch;										// SV_BLITTER_DST_OFFSET
-			*SV_BLITTER_FIFO = subRect.height();										// SV_BLITTER_MASK_AND_LINES
-			*SV_BLITTER_FIFO = 0x01;													// SV_BLITTER_CONTROL
-		} else {
-			sync();
-
-			*SV_BLITTER_SRC1           = (long)srcSurface.getBasePtr(subRect.left, subRect.top);
-			*SV_BLITTER_SRC2           = 0x00000000;
-			*SV_BLITTER_DST            = (long)dstSurface.getBasePtr(destX, destY);
-			*SV_BLITTER_COUNT          = subRect.width() - 1;
-			*SV_BLITTER_SRC1_OFFSET    = srcSurface.pitch;
-			*SV_BLITTER_SRC2_OFFSET    = 0x00000000;
-			*SV_BLITTER_DST_OFFSET     = dstSurface.pitch;
-			*SV_BLITTER_MASK_AND_LINES = subRect.height();
-			*SV_BLITTER_CONTROL        = 0x01;
-		}
-#else
 		dstSurface.copyRectToSurface(srcSurface, destX, destY, subRect);
-#endif
 	}
 
-	void copyRectToSurfaceWithKey(const Graphics::Surface &srcSurface, int destX, int destY, Graphics::Surface &dstSurface,
+	void copyRectToSurfaceWithKey(Graphics::Surface &dstSurface,
+								  const Graphics::Surface &srcSurface, int destX, int destY,
 								  const Common::Rect &subRect, uint32 key) const override {
-		sync();
 		dstSurface.copyRectToSurfaceWithKey(srcSurface, destX, destY, subRect, key);
 	}
 
-	virtual void sync() const override {
-#ifdef USE_SV_BLITTER
-		// while FIFO not empty...
-		if (_fwVersion >= 9)
-			while (!(*SV_BLITTER_FIFO & 1));
-		// while busy blitting...
-		while (*SV_BLITTER_CONTROL & 1);
-#endif
-	}
+	static long hasSvRamBoosted() {
+		register long ret __asm__ ("d0") = 0;
 
-#ifdef USE_SV_BLITTER
-	int _fwVersion = 0;
-#endif
+		__asm__ volatile(
+		"\tmovec	%%itt0,%%d1\n"
+		"\tcmp.l	#0xA007E060,%%d1\n"
+		"\tbne.s	1f\n"
+
+		"\tmovec	%%dtt0,%%d1\n"
+		"\tcmp.l	#0xA007E060,%%d1\n"
+		"\tbne.s	1f\n"
+
+		"\tmoveq	#1,%%d0\n"
+
+	"1:\n"
+		: "=g"(ret)	/* outputs */
+		:			/* inputs  */
+		: __CLOBBER_RETURN("d0") "d1", "cc"
+		);
+
+		return ret;
+	}
 };
 
 #endif
diff --git a/backends/graphics/atari/atari-graphics-videl.h b/backends/graphics/atari/atari-graphics-videl.h
index 6771085e79d..fea77089527 100644
--- a/backends/graphics/atari/atari-graphics-videl.h
+++ b/backends/graphics/atari/atari-graphics-videl.h
@@ -24,40 +24,19 @@
 
 #include "backends/graphics/atari/atari-graphics.h"
 
-#include <mint/osbind.h>
-
 #include "backends/graphics/atari/atari_c2p-asm.h"
 #include "common/system.h"
-#include "common/textconsole.h"	// for error()
 
 class AtariVidelManager : public AtariGraphicsManager {
 public:
 	AtariVidelManager() {
-		for (int i = 0; i < SCREENS; ++i) {
-			if (!allocateAtariSurface(_screen[i], _screenSurface, SCREEN_WIDTH, SCREEN_HEIGHT, PIXELFORMAT8, MX_STRAM))
-				error("Failed to allocate screen memory in ST RAM");
-			_screenAligned[i] = (byte*)_screenSurface.getPixels();
-		}
-		_screenSurface.setPixels(_screenAligned[getDefaultGraphicsMode() <= 1 ? FRONT_BUFFER : BACK_BUFFER1]);
-
-		if (!allocateAtariSurface(_chunkyBuffer, _chunkySurface, SCREEN_WIDTH, SCREEN_HEIGHT, PIXELFORMAT8, MX_PREFTTRAM))
-			error("Failed to allocate chunky buffer memory in ST/TT RAM");
-
-		if (!allocateAtariSurface(_overlayScreen, _screenOverlaySurface, getOverlayWidth(), getOverlayHeight(),
-								  getOverlayFormat(), MX_STRAM))
-			error("Failed to allocate overlay memory in ST RAM");
-
-		if (!allocateAtariSurface(_overlayBuffer, _overlaySurface, getOverlayWidth(), getOverlayHeight(),
-								  getOverlayFormat(), MX_PREFTTRAM))
-			error("Failed to allocate overlay buffer memory in ST/TT RAM");
+		// using virtual methods so must be done here
+		allocateSurfaces();
 	}
 
 	~AtariVidelManager() {
-		Mfree(_chunkyBuffer);
-		_chunkyBuffer = nullptr;
-
-		Mfree(_overlayBuffer);
-		_overlayBuffer = nullptr;
+		// using virtual methods so must be done here
+		freeSurfaces();
 	}
 
 	virtual const OSystem::GraphicsMode *getSupportedGraphicsModes() const override {
@@ -89,35 +68,35 @@ public:
 	int16 getOverlayWidth() const override { return _vgaMonitor ? OVERLAY_WIDTH : 2 * OVERLAY_WIDTH; }
 
 private:
-	virtual void* allocFast(size_t bytes) const override {
-		return (void*)Mxalloc(bytes, MX_PREFTTRAM);
-	}
-
-	void copySurfaceToSurface(const Graphics::Surface &srcSurface, Graphics::Surface &dstSurface) const override {
-		asm_c2p1x1_8(
-			(const byte*)srcSurface.getPixels(),
-			(const byte*)srcSurface.getBasePtr(srcSurface.w, srcSurface.h-1),
-			(byte*)dstSurface.getPixels());
-	}
-
-	void copyRectToSurface(const Graphics::Surface &srcSurface, int destX, int destY,
-						   Graphics::Surface &dstSurface, const Common::Rect &subRect) const override {
+	void copyRectToSurface(Graphics::Surface &dstSurface,
+						   const Graphics::Surface &srcSurface, int destX, int destY,
+						   const Common::Rect &subRect) const override {
 		// 'pChunkyEnd' is a delicate parameter: the c2p routine compares it to the address register
 		// used for pixel reading; two common mistakes:
 		// 1. (subRect.left, subRect.bottom) = beginning of the next line *including the offset*
 		// 2. (subRect.right, subRect.bottom) = even worse, end of the *next* line, not current one
-		asm_c2p1x1_8_rect(
-			(const byte*)srcSurface.getBasePtr(subRect.left, subRect.top),
-			(const byte*)srcSurface.getBasePtr(subRect.right, subRect.bottom-1),
-			subRect.width(),
-			srcSurface.pitch,
-			(byte*)dstSurface.getBasePtr(destX, destY),
-			dstSurface.pitch);
+		if (subRect.width() == dstSurface.w) {
+			asm_c2p1x1_8(
+				(const byte*)srcSurface.getBasePtr(subRect.left, subRect.top),
+				(const byte*)srcSurface.getBasePtr(subRect.right, subRect.bottom-1),
+				(byte*)dstSurface.getBasePtr(destX, destY));
+		} else {
+			asm_c2p1x1_8_rect(
+				(const byte*)srcSurface.getBasePtr(subRect.left, subRect.top),
+				(const byte*)srcSurface.getBasePtr(subRect.right, subRect.bottom-1),
+				subRect.width(),
+				srcSurface.pitch,
+				(byte*)dstSurface.getBasePtr(destX, destY),
+				dstSurface.pitch);
+		}
 	}
 
 	// TODO: allow specifying different background than _chunkySurface?
-	void copyRectToSurfaceWithKey(const Graphics::Surface &srcSurface, int destX, int destY,
-								  Graphics::Surface &dstSurface, const Common::Rect &subRect, uint32 key) const override {
+	// TODO: alignRect and this function could be perhaps better if we know that all surfaces
+	// are aligned on 16px and their pitch is % 16 as well?
+	void copyRectToSurfaceWithKey(Graphics::Surface &dstSurface,
+								  const Graphics::Surface &srcSurface, int destX, int destY,
+								  const Common::Rect &subRect, uint32 key) const override {
 		Common::Rect backgroundRect(destX, destY, destX + subRect.width(), destY + subRect.height());
 
 		// ensure that background's left and right lie on a 16px boundary and double the width if needed
@@ -144,9 +123,9 @@ private:
 		cachedSurface.copyRectToSurfaceWithKey(srcSurface, deltaX, 0, subRect, key);
 
 		copyRectToSurface(
+			dstSurface,
 			cachedSurface,
 			backgroundRect.left, backgroundRect.top,
-			dstSurface,
 			Common::Rect(cachedSurface.w, cachedSurface.h));
 	}
 
diff --git a/backends/graphics/atari/atari-graphics.cpp b/backends/graphics/atari/atari-graphics.cpp
index f325a6f4fe4..33a4bfc93f6 100644
--- a/backends/graphics/atari/atari-graphics.cpp
+++ b/backends/graphics/atari/atari-graphics.cpp
@@ -26,6 +26,7 @@
 #include <mint/osbind.h>
 
 #include "backends/graphics/atari/atari-graphics-asm.h"
+#include "backends/graphics/atari/atari-graphics-superblitter.h"
 #include "backends/graphics/atari/videl-resolutions.h"
 #include "backends/keymapper/action.h"
 #include "backends/keymapper/keymap.h"
@@ -67,14 +68,6 @@ AtariGraphicsManager::AtariGraphicsManager() {
 
 AtariGraphicsManager::~AtariGraphicsManager() {
 	g_system->getEventManager()->getEventDispatcher()->unregisterObserver(this);
-
-	for (int i = 0; i < SCREENS; ++i) {
-		Mfree(_screen[i]);
-		_screen[i] = _screenAligned[i] = nullptr;
-	}
-
-	Mfree(_overlayScreen);
-	_overlayScreen = nullptr;
 }
 
 bool AtariGraphicsManager::hasFeature(OSystem::Feature f) const {
@@ -293,6 +286,8 @@ void AtariGraphicsManager::updateScreen() {
 
 	bool screenUpdated = false;
 
+	lockSuperBlitter();
+
 	if (isOverlayVisible()) {
 		screenUpdated = updateOverlay();
 	} else {
@@ -310,14 +305,14 @@ void AtariGraphicsManager::updateScreen() {
 		}
 	}
 
+	unlockSuperBlitter();
+
 	//if (_cursor.outOfScreen)
 	//	warning("mouse out of screen");
 
 	bool vsync = _vsync;
 
 	if (_screenModified) {
-		sync();
-
 		if (_currentState.mode == GraphicsMode::DoubleBuffering) {
 			byte *tmp = _screenAligned[FRONT_BUFFER];
 			_screenAligned[FRONT_BUFFER] = _screenAligned[BACK_BUFFER1];
@@ -532,6 +527,9 @@ void AtariGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int h
 										  bool dontScale, const Graphics::PixelFormat *format, const byte *mask) {
 	//debug("setMouseCursor: %d, %d, %d, %d, %d, %d", w, h, hotspotX, hotspotY, keycolor, format ? format->bytesPerPixel : 1);
 
+	if (mask)
+		warning("AtariGraphicsManager::setMouseCursor: Masks are not supported");
+
 	const Graphics::PixelFormat cursorFormat = format ? *format : PIXELFORMAT8;
 	_cursor.setSurface(buf, (int)w, (int)h, hotspotX, hotspotY, keycolor, cursorFormat);
 }
@@ -575,21 +573,38 @@ Common::Keymap *AtariGraphicsManager::getKeymap() const {
 	return keymap;
 }
 
-bool AtariGraphicsManager::allocateAtariSurface(
-		byte *&buf, Graphics::Surface &surface, int width, int height,
-		const Graphics::PixelFormat &format, int mode, uintptr mask) {
-	buf = (mode == MX_STRAM)
-		? (byte*)Mxalloc(width * height * format.bytesPerPixel + 15, mode)
-		: (byte*)allocFast(width * height * format.bytesPerPixel + 15);
+void AtariGraphicsManager::allocateSurfaces() {
+	for (int i = 0; i < SCREENS; ++i) {
+		if (!(_screen[i] = allocateAtariSurface(_screenSurface,
+												SCREEN_WIDTH, SCREEN_HEIGHT, PIXELFORMAT8,
+												getStRamAllocFunc())))
+			error("Failed to allocate screen memory in ST RAM");
+		_screenAligned[i] = (byte*)_screenSurface.getPixels();
+	}
+	_screenSurface.setPixels(_screenAligned[getDefaultGraphicsMode() <= 1 ? FRONT_BUFFER : BACK_BUFFER1]);
 
-	if (!buf)
-		return false;
+	_chunkySurface.create(SCREEN_WIDTH, SCREEN_HEIGHT, PIXELFORMAT8);
 
-	byte *bufAligned = (byte*)((((uintptr)buf + 15) | mask) & 0xfffffff0);
-	memset(bufAligned, 0, width * height * format.bytesPerPixel);
+	if (!(_overlayScreen = allocateAtariSurface(_screenOverlaySurface,
+												getOverlayWidth(), getOverlayHeight(), getOverlayFormat(),
+												getStRamAllocFunc())))
+		error("Failed to allocate overlay memory in ST RAM");
 
-	surface.init(width, height, width * format.bytesPerPixel, bufAligned, format);
-	return true;
+	_overlaySurface.create(getOverlayWidth(), getOverlayHeight(), getOverlayFormat());
+}
+
+void AtariGraphicsManager::freeSurfaces() {
+	for (int i = 0; i < SCREENS; ++i) {
+		freeAtariSurface(_screen[i], _screenSurface, getStRamFreeFunc());
+		_screen[i] = _screenAligned[i] = nullptr;
+	}
+
+	_chunkySurface.free();
+
+	freeAtariSurface(_overlayScreen, _screenOverlaySurface, getStRamFreeFunc());
+	_overlayScreen = nullptr;
+
+	_overlaySurface.free();
 }
 
 void AtariGraphicsManager::setVidelResolution() const {
@@ -755,10 +770,7 @@ bool AtariGraphicsManager::updateSingleBuffer() {
 		if (!drawCursor && !_cursor.outOfScreen && _cursor.visible)
 			drawCursor = rect.intersects(_cursor.dstRect);
 
-		if (rect.width() == _screenSurface.w && rect.height() == _screenSurface.h)
-			copySurfaceToSurface(_chunkySurface, _screenSurface);
-		else
-			copyRectToSurface(_chunkySurface, rect.left, rect.top, _screenSurface, rect);
+		copyRectToSurface(_screenSurface, _chunkySurface, rect.left, rect.top, rect);
 
 		_modifiedChunkyRects.pop_back();
 
@@ -770,8 +782,10 @@ bool AtariGraphicsManager::updateSingleBuffer() {
 
 	if ((_cursor.positionChanged || !_cursor.visible) && !_oldCursorRect.isEmpty()) {
 		alignRect(_chunkySurface, _oldCursorRect);
-		copyRectToSurface(_chunkySurface, _oldCursorRect.left, _oldCursorRect.top,
-			_screenSurface, _oldCursorRect);
+		copyRectToSurface(
+			_screenSurface, _chunkySurface,
+			_oldCursorRect.left, _oldCursorRect.top,
+			_oldCursorRect);
 
 		_oldCursorRect = Common::Rect();
 
@@ -780,8 +794,10 @@ bool AtariGraphicsManager::updateSingleBuffer() {
 
 	if (drawCursor && _cursor.visible) {
 		//debug("Redraw cursor (single): %d %d %d %d", _cursor.dstRect.left, _cursor.dstRect.top, _cursor.dstRect.width(), _cursor.dstRect.height());
-		copyRectToSurfaceWithKey(_cursor.surface, _cursor.dstRect.left, _cursor.dstRect.top,
-			_screenSurface, _cursor.srcRect, _cursor.keycolor);
+		copyRectToSurfaceWithKey(
+			_screenSurface, _cursor.surface,
+			_cursor.dstRect.left, _cursor.dstRect.top,
+			_cursor.srcRect, _cursor.keycolor);
 
 		_cursor.positionChanged = _cursor.surfaceChanged = false;
 		_oldCursorRect = _cursor.dstRect;
@@ -799,7 +815,10 @@ bool AtariGraphicsManager::updateDoubleAndTripleBuffer() {
 	if (_screenModified) {
 		drawCursor = true;
 
-		copySurfaceToSurface(_chunkySurface, _screenSurface);
+		copyRectToSurface(
+			_screenSurface, _chunkySurface,
+			0, 0,
+			Common::Rect(_chunkySurface.w, _chunkySurface.h));
 
 		// updated in screen swapping
 		//_screenModified = false;
@@ -816,8 +835,10 @@ bool AtariGraphicsManager::updateDoubleAndTripleBuffer() {
 
 	if ((_cursor.positionChanged || !_cursor.visible) && !_oldCursorRect.isEmpty() && !_screenModified) {
 		alignRect(_chunkySurface, _oldCursorRect);
-		copyRectToSurface(_chunkySurface, _oldCursorRect.left, _oldCursorRect.top,
-			frontBufferScreenSurface, _oldCursorRect);
+		copyRectToSurface(
+			frontBufferScreenSurface, _chunkySurface,
+			_oldCursorRect.left, _oldCursorRect.top,
+			_oldCursorRect);
 
 		_oldCursorRect = Common::Rect();
 
@@ -827,8 +848,10 @@ bool AtariGraphicsManager::updateDoubleAndTripleBuffer() {
 	if (drawCursor && _cursor.visible) {
 		//debug("Redraw cursor (double/triple): %d %d %d %d", _cursor.dstRect.left, _cursor.dstRect.top, _cursor.dstRect.width(), _cursor.dstRect.height());
 
-		copyRectToSurfaceWithKey(_cursor.surface, _cursor.dstRect.left, _cursor.dstRect.top,
-			frontBufferScreenSurface, _cursor.srcRect, _cursor.keycolor);
+		copyRectToSurfaceWithKey(
+			frontBufferScreenSurface, _cursor.surface,
+			_cursor.dstRect.left, _cursor.dstRect.top,
+			_cursor.srcRect, _cursor.keycolor);
 
 		_cursor.positionChanged = _cursor.surfaceChanged = false;
 		_oldCursorRect = _cursor.dstRect;
@@ -839,6 +862,26 @@ bool AtariGraphicsManager::updateDoubleAndTripleBuffer() {
 	return updated;
 }
 
+byte *AtariGraphicsManager::allocateAtariSurface(Graphics::Surface &surface,
+		int width, int height, const Graphics::PixelFormat &format,
+		const AtariMemAlloc &allocFunc) {
+	byte *buf = (byte*)allocFunc(width * height * format.bytesPerPixel + 15);
+	if (!buf)
+		return buf;
+
+	byte *bufAligned = (byte*)(((uintptr)buf + 15) & 0xfffffff0);
+	memset(bufAligned, 0, width * height * format.bytesPerPixel);
+
+	surface.init(width, height, width * format.bytesPerPixel, bufAligned, format);
+	return buf;
+}
+
+void AtariGraphicsManager::freeAtariSurface(byte *ptr, Graphics::Surface &surface,
+		const AtariMemFree &freeFunc) {
+	surface = Graphics::Surface();
+	freeFunc(ptr);
+}
+
 void AtariGraphicsManager::copySurface8ToSurface16(
 		const Graphics::Surface &srcSurface, const byte *srcPalette,
 		Graphics::Surface &dstSurface, int destX, int destY,
diff --git a/backends/graphics/atari/atari-graphics.h b/backends/graphics/atari/atari-graphics.h
index 277e2d6a269..9941972e3d7 100644
--- a/backends/graphics/atari/atari-graphics.h
+++ b/backends/graphics/atari/atari-graphics.h
@@ -24,6 +24,8 @@
 
 #include "backends/graphics/graphics.h"
 
+#include <mint/osbind.h>
+
 #include "common/array.h"
 #include "common/events.h"
 #include "common/rect.h"
@@ -92,11 +94,20 @@ public:
 
 protected:
 	const Graphics::PixelFormat PIXELFORMAT8 = Graphics::PixelFormat::createFormatCLUT8();
-	const Graphics::PixelFormat PIXELFORMAT16 = getOverlayFormat();
 
-	bool allocateAtariSurface(byte *&buf, Graphics::Surface &surface,
-							  int width, int height, const Graphics::PixelFormat &format,
-							  int mode, uintptr mask = 0x00000000);
+	typedef void* (*AtariMemAlloc)(size_t bytes);
+	typedef void (*AtariMemFree)(void *ptr);
+
+	virtual AtariMemAlloc getStRamAllocFunc() const {
+		return [](size_t bytes) { return (void*)Mxalloc(bytes, MX_STRAM); };
+	}
+
+	virtual AtariMemFree getStRamFreeFunc() const {
+		return [](void *ptr) { Mfree(ptr); };
+	}
+
+	void allocateSurfaces();
+	void freeSurfaces();
 
 	bool _vgaMonitor = true;
 
@@ -129,18 +140,8 @@ protected:
 	static const int FRONT_BUFFER = 0;
 	static const int BACK_BUFFER1 = 1;
 	static const int BACK_BUFFER2 = 2;
-	byte *_screen[SCREENS] = {};	// for Mfree() purposes only
-	byte *_screenAligned[SCREENS] = {};
-	Graphics::Surface _screenSurface;
-
-	byte *_chunkyBuffer = nullptr;	// for Mfree() purposes only
-	Graphics::Surface _chunkySurface;
-
-	byte *_overlayScreen = nullptr;	// for Mfree() purposes only
-	Graphics::Surface _screenOverlaySurface;
 
-	byte *_overlayBuffer = nullptr;	// for Mfree() purposes only
-	Graphics::Surface _overlaySurface;
+	Graphics::Surface _chunkySurface;	// for Videl's copyRectToSurfaceWithKey
 
 private:
 	enum CustomEventAction {
@@ -155,15 +156,19 @@ private:
 	bool updateSingleBuffer();
 	bool updateDoubleAndTripleBuffer();
 
-	virtual void* allocFast(size_t bytes) const = 0;
+	byte *allocateAtariSurface(Graphics::Surface &surface,
+							  int width, int height, const Graphics::PixelFormat &format,
+							  const AtariMemAlloc &allocFunc);
+
+	void freeAtariSurface(byte *ptr, Graphics::Surface &surface, const AtariMemFree &freeFunc);
 
-	virtual void copySurfaceToSurface(const Graphics::Surface &srcSurface, Graphics::Surface &dstSurface) const = 0;
-	virtual void copyRectToSurface(const Graphics::Surface &srcSurface, int destX, int destY,
-								   Graphics::Surface &dstSurface, const Common::Rect &subRect) const = 0;
-	virtual void copyRectToSurfaceWithKey(const Graphics::Surface &srcSurface, int destX, int destY,
-										  Graphics::Surface &dstSurface, const Common::Rect &subRect, uint32 key) const = 0;
+	virtual void copyRectToSurface(Graphics::Surface &dstSurface,
+								   const Graphics::Surface &srcSurface, int destX, int destY,
+								   const Common::Rect &subRect) const = 0;
+	virtual void copyRectToSurfaceWithKey(Graphics::Surface &dstSurface,
+										  const Graphics::Surface &srcSurface, int destX, int destY,
+										  const Common::Rect &subRect, uint32 key) const = 0;
 	virtual void alignRect(const Graphics::Surface &srcSurface, Common::Rect &rect) const {}
-	virtual void sync() const {}
 
 	enum class ScaleMode {
 		NONE,
@@ -195,12 +200,19 @@ private:
 	};
 	int _pendingScreenChange = kPendingScreenChangeNone;
 
-	bool _screenModified = false;	// double/triple buffering only
+	byte *_screen[SCREENS] = {};	// for Mfree() purposes only
+	byte *_screenAligned[SCREENS] = {};
+	Graphics::Surface _screenSurface;
 	Common::Rect _modifiedScreenRect;	// direct rendering only
+	bool _screenModified = false;	// double/triple buffering only
 
 	Common::Array<Common::Rect> _modifiedChunkyRects;
 
+	byte *_overlayScreen = nullptr;	// for Mfree() purposes only
+	Graphics::Surface _screenOverlaySurface;
 	bool _overlayVisible = false;
+
+	Graphics::Surface _overlaySurface;
 	Common::Array<Common::Rect> _modifiedOverlayRects;
 
 	struct Cursor {
diff --git a/backends/platform/atari/osystem_atari.cpp b/backends/platform/atari/osystem_atari.cpp
index 4c7d504a859..0107288fb24 100644
--- a/backends/platform/atari/osystem_atari.cpp
+++ b/backends/platform/atari/osystem_atari.cpp
@@ -20,15 +20,12 @@
  */
 
 #include <stdio.h>
+#include <time.h>
 #include <unistd.h>
 
 #include <mint/cookie.h>
-#include <mint/falcon.h>
 #include <mint/osbind.h>
 
-// We use some stdio.h functionality here thus we need to allow some
-// symbols. Alternatively, we could simply allow everything by defining
-// FORBIDDEN_SYMBOL_ALLOW_ALL
 #define FORBIDDEN_SYMBOL_EXCEPTION_FILE
 #define FORBIDDEN_SYMBOL_EXCEPTION_stdout
 #define FORBIDDEN_SYMBOL_EXCEPTION_stderr
@@ -51,6 +48,7 @@
 #include "backends/events/default/default-events.h"
 #include "backends/mixer/atari/atari-mixer.h"
 #include "backends/graphics/atari/atari-graphics.h"
+#include "backends/graphics/atari/atari-graphics-superblitter.h"
 #include "backends/graphics/atari/atari-graphics-supervidel.h"
 #include "backends/graphics/atari/atari-graphics-videl.h"
 #include "gui/debugger.h"
@@ -115,8 +113,6 @@ void OSystem_Atari::initBackend() {
 
 	_startTime = clock();
 
-	bool superVidel = VgetMonitor() == MON_VGA && Getcookie(C_SupV, NULL) == C_FOUND;
-
 	_timerManager = new DefaultTimerManager();
 	_savefileManager = new DefaultSaveFileManager("saves");
 
@@ -125,7 +121,7 @@ void OSystem_Atari::initBackend() {
 
 	// AtariGraphicsManager needs _eventManager ready
 	AtariGraphicsManager *atariGraphicsManager;
-	if (superVidel)
+	if (hasSuperVidel())
 		atariGraphicsManager = new AtariSuperVidelManager();
 	else
 		atariGraphicsManager = new AtariVidelManager();
diff --git a/backends/platform/atari/osystem_atari.h b/backends/platform/atari/osystem_atari.h
index c27e207135b..2f999057128 100644
--- a/backends/platform/atari/osystem_atari.h
+++ b/backends/platform/atari/osystem_atari.h
@@ -24,8 +24,6 @@
 
 #include "backends/modular-backend.h"
 
-#include <time.h>
-
 class OSystem_Atari : public ModularMixerBackend, public ModularGraphicsBackend {
 public:
 	OSystem_Atari();
@@ -51,7 +49,7 @@ public:
 	void update();
 
 private:
-	clock_t _startTime;
+	long _startTime;
 
 	bool _video_initialized = false;
 	bool _ikbd_initialized = false;
diff --git a/backends/platform/atari/readme.txt b/backends/platform/atari/readme.txt
index f235c595132..291dd004f25 100644
--- a/backends/platform/atari/readme.txt
+++ b/backends/platform/atari/readme.txt
@@ -254,9 +254,7 @@ means that if the SuperVidel is detected, it does the following:
 
 - when SuperVidel FW version >= 9 is detected, the async FIFO buffer is used
   instead of the slower sync blitting (where one has to wait for every
-  rectangle blit to finish). This applies only for chunky buffer -> screen
-  surfaces copy (as the generic surface copy can't rely on this behavior) but
-  despite this limitation it sometimes leads to nearly zero-cost rendering
+  rectangle blit to finish), this sometimes leads to nearly zero-cost rendering
   and makes a *huge* difference for 640x480 fullscreen updates.
 
 
@@ -269,7 +267,7 @@ to avoid unpleasant playing experiences.
 Game engines with unexpected performance hit
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-A typical example from this category is the Gobliins engine (and its
+A typical example from this category is the Gobliiins engine (and its
 sequels). At first it looks like our machine / backend is doing something
 terribly wrong but the truth is it is the engine itself which is doing a lot of
 unnecessary redraws and updates, sometimes even before reaching the backend.
@@ -383,8 +381,7 @@ Future plans
 
 - avoid loading music/speech files (and thus slowing down everything) if muted
 
-- assembly copy routines for screen/chunky surfaces (even with SuperVidel
-  present it is not possible to use the SuperBlitter for every surface)
+- assembly copy routines when SuperVidel is not present or can't be used
 
 - cached audio/video streams (i.e. don't load only "output_samples" number of
   samples but cache, say, 1 second so disk i/o wont be so stressed)
@@ -408,6 +405,12 @@ Future plans
 
 - OPL2LPT and Retrowave support (if I manage to purchase it somewhere)
 
+- engines based on Graphics::Screen don't have to use my chunky buffer (however
+  it may be tricky to detect this situation)
+
+- C2P could support 4- and 6-bit depth
+
+
 Closing words
 —------------
 
diff --git a/configure b/configure
index ffce29c9b1b..3ff1c91b508 100755
--- a/configure
+++ b/configure
@@ -3967,6 +3967,7 @@ case $_backend in
 		;;
 	atari)
 		define_in_config_if_yes yes "ATARI"
+		define_in_config_if_yes yes "USE_SV_BLITTER"
 		append_var DEFINES "-DDISABLE_LAUNCHERDISPLAY_GRID"
 		#append_var DEFINES "-DDISABLE_FANCY_THEMES"
 		#append_var DEFINES "-DDISABLE_SID"
diff --git a/graphics/blit-atari.cpp b/graphics/blit-atari.cpp
index ca54c7306ce..7585a8d1c59 100644
--- a/graphics/blit-atari.cpp
+++ b/graphics/blit-atari.cpp
@@ -27,15 +27,68 @@
 #include <mint/cookie.h>
 #include <mint/falcon.h>
 
+#include <mint/trap14.h>
+#define ct60_vm(mode, value) (long)trap_14_wwl((short)0xc60e, (short)(mode), (long)(value))
+#define ct60_vmalloc(value) ct60_vm(0, value)
+#define ct60_vmfree(value)  ct60_vm(1, value)
+
 #include "backends/graphics/atari/atari-graphics-superblitter.h"
 #include "common/textconsole.h"	// error
 
+// bits 26:0
+#define SV_BLITTER_SRC1           ((volatile long*)0x80010058)
+#define SV_BLITTER_SRC2           ((volatile long*)0x8001005C)
+#define SV_BLITTER_DST            ((volatile long*)0x80010060)
+// The amount of bytes that are to be copied in a horizontal line, minus 1
+#define SV_BLITTER_COUNT          ((volatile long*)0x80010064)
+// The amount of bytes that are to be added to the line start adress after a line has been copied, in order to reach the next one
+#define SV_BLITTER_SRC1_OFFSET    ((volatile long*)0x80010068)
+#define SV_BLITTER_SRC2_OFFSET    ((volatile long*)0x8001006C)
+#define SV_BLITTER_DST_OFFSET     ((volatile long*)0x80010070)
+// bits 11:0 - The amount of horizontal lines to do
+#define SV_BLITTER_MASK_AND_LINES ((volatile long*)0x80010074)
+// bit    0 - busy / start
+// bits 4:1 - blit mode
+#define SV_BLITTER_CONTROL        ((volatile long*)0x80010078)
+// bit 0 - empty (read only)
+// bit 1 - full (read only)
+// bits 31:0 - data (write only)
+#define SV_BLITTER_FIFO           ((volatile long*)0x80010080)
+
+static bool isSuperBlitterLocked;
+
+static void syncSuperBlitter() {
+	// if externally locked, let the owner decide when to sync (unlock)
+	if (isSuperBlitterLocked)
+		return;
+
+	// while FIFO not empty...
+	if (superVidelFwVersion >= 9)
+		while (!(*SV_BLITTER_FIFO & 1));
+	// while busy blitting...
+	while (*SV_BLITTER_CONTROL & 1);
+}
+
 static inline bool hasMove16() {
 	long val;
 	static bool hasMove16 = Getcookie(C__CPU, &val) == C_FOUND && val >= 40;
 	return hasMove16;
 }
 
+void lockSuperBlitter() {
+	assert(!isSuperBlitterLocked);
+
+	isSuperBlitterLocked = true;
+}
+
+void unlockSuperBlitter() {
+	assert(isSuperBlitterLocked);
+
+	isSuperBlitterLocked = false;
+	if (hasSuperVidel())
+		syncSuperBlitter();
+}
+
 namespace Graphics {
 
 constexpr size_t ALIGN = 16;	// 16 bytes
@@ -53,7 +106,7 @@ void Surface::create(int16 width, int16 height, const PixelFormat &f) {
 	pitch = (w * format.bytesPerPixel + ALIGN - 1) & (-ALIGN);
 
 	if (width && height) {
-		if (VgetMonitor() == MON_VGA && Getcookie(C_SupV, NULL) == C_FOUND) {
+		if (hasSuperVidel()) {
 			pixels = (void *)ct60_vmalloc(height * pitch);
 
 			if (!pixels)
@@ -97,21 +150,32 @@ void copyBlit(byte *dst, const byte *src,
 		return;
 
 	if (((uintptr)src & 0xFF000000) >= 0xA0000000 && ((uintptr)dst & 0xFF000000) >= 0xA0000000) {
-		// while busy blitting...
-		while (*SV_BLITTER_CONTROL & 1);
-
-		*SV_BLITTER_SRC1           = (long)src;
-		*SV_BLITTER_SRC2           = 0x00000000;
-		*SV_BLITTER_DST            = (long)dst;
-		*SV_BLITTER_COUNT          = w * bytesPerPixel - 1;
-		*SV_BLITTER_SRC1_OFFSET    = srcPitch;
-		*SV_BLITTER_SRC2_OFFSET    = 0x00000000;
-		*SV_BLITTER_DST_OFFSET     = dstPitch;
-		*SV_BLITTER_MASK_AND_LINES = h;
-		*SV_BLITTER_CONTROL        = 0x01;
-
-		// wait until we finish otherwise we may overwrite pixels written manually afterwards
-		while (*SV_BLITTER_CONTROL & 1);
+		if (superVidelFwVersion >= 9) {
+			*SV_BLITTER_FIFO = (long)src;				// SV_BLITTER_SRC1
+			*SV_BLITTER_FIFO = 0x00000000;				// SV_BLITTER_SRC2
+			*SV_BLITTER_FIFO = (long)dst;				// SV_BLITTER_DST
+			*SV_BLITTER_FIFO = w * bytesPerPixel - 1;	// SV_BLITTER_COUNT
+			*SV_BLITTER_FIFO = srcPitch;				// SV_BLITTER_SRC1_OFFSET
+			*SV_BLITTER_FIFO = 0x00000000;				// SV_BLITTER_SRC2_OFFSET
+			*SV_BLITTER_FIFO = dstPitch;				// SV_BLITTER_DST_OFFSET
+			*SV_BLITTER_FIFO = h;						// SV_BLITTER_MASK_AND_LINES
+			*SV_BLITTER_FIFO = 0x01;					// SV_BLITTER_CONTROL
+		}  else {
+			// make sure the blitter is idle
+			while (*SV_BLITTER_CONTROL & 1);
+
+			*SV_BLITTER_SRC1           = (long)src;
+			*SV_BLITTER_SRC2           = 0x00000000;
+			*SV_BLITTER_DST            = (long)dst;
+			*SV_BLITTER_COUNT          = w * bytesPerPixel - 1;
+			*SV_BLITTER_SRC1_OFFSET    = srcPitch;
+			*SV_BLITTER_SRC2_OFFSET    = 0x00000000;
+			*SV_BLITTER_DST_OFFSET     = dstPitch;
+			*SV_BLITTER_MASK_AND_LINES = h;
+			*SV_BLITTER_CONTROL        = 0x01;
+		}
+
+		syncSuperBlitter();
 	} else if (dstPitch == srcPitch && ((w * bytesPerPixel) == dstPitch)) {
 		if (hasMove16() && ((uintptr)src & (ALIGN - 1)) == 0 && ((uintptr)dst & (ALIGN - 1)) == 0) {
 			__asm__ volatile(
diff --git a/graphics/blit.cpp b/graphics/blit.cpp
index cb2d353f825..59154005149 100644
--- a/graphics/blit.cpp
+++ b/graphics/blit.cpp
@@ -25,7 +25,7 @@
 namespace Graphics {
 
 // see graphics/blit-atari.cpp, Atari Falcon's SuperVidel addon allows accelerated blitting
-#ifndef ATARI
+#ifndef USE_SV_BLITTER
 // Function to blit a rect
 void copyBlit(byte *dst, const byte *src,
 			   const uint dstPitch, const uint srcPitch,
diff --git a/graphics/module.mk b/graphics/module.mk
index 33ddb8527e7..3c8071559a7 100644
--- a/graphics/module.mk
+++ b/graphics/module.mk
@@ -63,7 +63,7 @@ MODULE_OBJS += \
 	scaler/downscalerARM.o
 endif
 
-ifdef ATARI
+ifdef USE_SV_BLITTER
 MODULE_OBJS += \
 	blit-atari.o
 endif
diff --git a/graphics/surface.cpp b/graphics/surface.cpp
index 6da32b87d63..5e9b656dbf6 100644
--- a/graphics/surface.cpp
+++ b/graphics/surface.cpp
@@ -64,7 +64,7 @@ void Surface::drawThickLine(int x0, int y0, int x1, int y1, int penX, int penY,
 }
 
 // see graphics/blit-atari.cpp, Atari Falcon's SuperVidel addon allows accelerated blitting
-#ifndef ATARI
+#ifndef USE_SV_BLITTER
 void Surface::create(int16 width, int16 height, const PixelFormat &f) {
 	assert(width >= 0 && height >= 0);
 	free();


Commit: 52f8bd45e20a1c86d6d7fbc2b1af36d5e354ee73
    https://github.com/scummvm/scummvm/commit/52f8bd45e20a1c86d6d7fbc2b1af36d5e354ee73
Author: Miro Kropacek (miro.kropacek at gmail.com)
Date: 2023-03-25T21:45:50+01:00

Commit Message:
BACKENDS: ATARI: Use RGB332 for the overlay

Changed paths:
    backends/graphics/atari/atari-graphics-asm.S
    backends/graphics/atari/atari-graphics-asm.h
    backends/graphics/atari/atari-graphics-supervidel.h
    backends/graphics/atari/atari-graphics-videl.h
    backends/graphics/atari/atari-graphics.cpp
    backends/graphics/atari/atari-graphics.h
    backends/graphics/atari/videl-resolutions.cpp
    backends/graphics/atari/videl-resolutions.h
    backends/platform/atari/readme.txt


diff --git a/backends/graphics/atari/atari-graphics-asm.S b/backends/graphics/atari/atari-graphics-asm.S
index aa2a40a0e46..7da4549a8ed 100644
--- a/backends/graphics/atari/atari-graphics-asm.S
+++ b/backends/graphics/atari/atari-graphics-asm.S
@@ -213,16 +213,19 @@ set_tt_palette_loop:
 	dbra	d0,set_tt_palette_loop
 	rts
 
-| extern void asm_screen_set_falcon_palette(const uint32 pPalette[256]);
+| extern void asm_screen_set_falcon_palette(const byte pPalette[256*3]);
 |
 _asm_screen_set_falcon_palette:
 	move.l	(4,sp),a0
 	lea	pending_palette,a1
-	moveq	#256/2-1,d0
+	move.w	#256-1,d0
 
 set_falcon_palette_loop:
-	move.l	(a0)+,(a1)+
-	move.l	(a0)+,(a1)+
+	clr.l	d1
+	move.w	(a0)+,d1
+	swap	d1
+	move.b	(a0)+,d1
+	move.l	d1,(a1)+
 	dbra	d0,set_falcon_palette_loop
 
 	addq.w	#1,has_pending_palette
diff --git a/backends/graphics/atari/atari-graphics-asm.h b/backends/graphics/atari/atari-graphics-asm.h
index a13f6c29d93..0bdd48e6be8 100644
--- a/backends/graphics/atari/atari-graphics-asm.h
+++ b/backends/graphics/atari/atari-graphics-asm.h
@@ -51,9 +51,9 @@ void asm_screen_falcon_restore(void);
 void asm_screen_set_tt_palette(const uint16 pPalette[256]);
 /**
  * Set Atari Falcon palette.
- * @param pPalette 256 palette entries (RRRRRRrr GGGGGGgg 00000000 BBBBBBbb)
+ * @param pPalette 256 palette entries (RRRRRRRR GGGGGGGG BBBBBBBB)
  */
-void asm_screen_set_falcon_palette(const uint32 pPalette[256]);
+void asm_screen_set_falcon_palette(const byte pPalette[256*3]);
 
 /**
  * Set Atari TT/Falcon video base.
diff --git a/backends/graphics/atari/atari-graphics-supervidel.h b/backends/graphics/atari/atari-graphics-supervidel.h
index e230bd4fc72..de9cc0561e9 100644
--- a/backends/graphics/atari/atari-graphics-supervidel.h
+++ b/backends/graphics/atari/atari-graphics-supervidel.h
@@ -71,9 +71,6 @@ public:
 		return graphicsModes;
 	}
 
-	int16 getOverlayHeight() const override { return 2 * OVERLAY_HEIGHT; }
-	int16 getOverlayWidth() const override { return 2 * OVERLAY_WIDTH; }
-
 protected:
 	AtariMemAlloc getStRamAllocFunc() const override {
 		return [](size_t bytes) {
@@ -96,9 +93,9 @@ private:
 		dstSurface.copyRectToSurface(srcSurface, destX, destY, subRect);
 	}
 
-	void copyRectToSurfaceWithKey(Graphics::Surface &dstSurface,
+	void copyRectToSurfaceWithKey(Graphics::Surface &dstSurface, const Graphics::Surface &bgSurface,
 								  const Graphics::Surface &srcSurface, int destX, int destY,
-								  const Common::Rect &subRect, uint32 key) const override {
+								  const Common::Rect &subRect, uint32 key, const byte srcPalette[256*3]) const override {
 		dstSurface.copyRectToSurfaceWithKey(srcSurface, destX, destY, subRect, key);
 	}
 
diff --git a/backends/graphics/atari/atari-graphics-videl.h b/backends/graphics/atari/atari-graphics-videl.h
index fea77089527..4b14db6e81b 100644
--- a/backends/graphics/atari/atari-graphics-videl.h
+++ b/backends/graphics/atari/atari-graphics-videl.h
@@ -64,9 +64,6 @@ public:
 		return AtariGraphicsManager::endGFXTransaction();
 	}
 
-	int16 getOverlayHeight() const override { return _vgaMonitor ? OVERLAY_HEIGHT : 2 * OVERLAY_HEIGHT; }
-	int16 getOverlayWidth() const override { return _vgaMonitor ? OVERLAY_WIDTH : 2 * OVERLAY_WIDTH; }
-
 private:
 	void copyRectToSurface(Graphics::Surface &dstSurface,
 						   const Graphics::Surface &srcSurface, int destX, int destY,
@@ -91,12 +88,9 @@ private:
 		}
 	}
 
-	// TODO: allow specifying different background than _chunkySurface?
-	// TODO: alignRect and this function could be perhaps better if we know that all surfaces
-	// are aligned on 16px and their pitch is % 16 as well?
-	void copyRectToSurfaceWithKey(Graphics::Surface &dstSurface,
+	void copyRectToSurfaceWithKey(Graphics::Surface &dstSurface, const Graphics::Surface &bgSurface,
 								  const Graphics::Surface &srcSurface, int destX, int destY,
-								  const Common::Rect &subRect, uint32 key) const override {
+								  const Common::Rect &subRect, uint32 key, const byte srcPalette[256*3]) const override {
 		Common::Rect backgroundRect(destX, destY, destX + subRect.width(), destY + subRect.height());
 
 		// ensure that background's left and right lie on a 16px boundary and double the width if needed
@@ -105,22 +99,52 @@ private:
 		const int deltaX = destX - backgroundRect.left;
 
 		backgroundRect.right = (backgroundRect.right + deltaX + 15) & 0xfff0;
-		if (backgroundRect.right > _chunkySurface.w)
-			backgroundRect.right = _chunkySurface.w;
+		if (backgroundRect.right > bgSurface.w)
+			backgroundRect.right = bgSurface.w;
 
 		static Graphics::Surface cachedSurface;
 
-		if (cachedSurface.w != backgroundRect.width() || cachedSurface.h != backgroundRect.height()) {
+		if (cachedSurface.w != backgroundRect.width()
+				|| cachedSurface.h != backgroundRect.height()
+				|| cachedSurface.format != bgSurface.format) {
 			cachedSurface.create(
 				backgroundRect.width(),
 				backgroundRect.height(),
-				_chunkySurface.format);
+				bgSurface.format);
 		}
 
 		// copy background
-		cachedSurface.copyRectToSurface(_chunkySurface, 0, 0, backgroundRect);
+		cachedSurface.copyRectToSurface(bgSurface, 0, 0, backgroundRect);
+
 		// copy cursor
-		cachedSurface.copyRectToSurfaceWithKey(srcSurface, deltaX, 0, subRect, key);
+		if (cachedSurface.format == PIXELFORMAT_RGB332) {
+			assert(srcSurface.format == PIXELFORMAT_CLUT8);
+
+			// Convert CLUT8 to RGB332 palette and do copyRectToSurfaceWithKey() at the same time
+			const byte *src = (const byte*)srcSurface.getBasePtr(subRect.left, subRect.top);
+			byte *dst = (byte*)cachedSurface.getBasePtr(deltaX, 0);
+
+			const int16 w = subRect.width();
+			const int16 h = subRect.height();
+
+			for (int16 y = 0; y < h; ++y) {
+				for (int16 x = 0; x < w; ++x) {
+					const uint32 color = *src++;
+					if (color != key) {
+						*dst++ = (srcPalette[color*3 + 0] & 0xe0)
+							  | ((srcPalette[color*3 + 1] >> 3) & 0x1c)
+							  | ((srcPalette[color*3 + 2] >> 6) & 0x03);
+					} else {
+						dst++;
+					}
+				}
+
+				src += (srcSurface.pitch - w);
+				dst += (cachedSurface.pitch - w);
+			}
+		} else {
+			cachedSurface.copyRectToSurfaceWithKey(srcSurface, deltaX, 0, subRect, key);
+		}
 
 		copyRectToSurface(
 			dstSurface,
diff --git a/backends/graphics/atari/atari-graphics.cpp b/backends/graphics/atari/atari-graphics.cpp
index 33a4bfc93f6..b533c9ea34e 100644
--- a/backends/graphics/atari/atari-graphics.cpp
+++ b/backends/graphics/atari/atari-graphics.cpp
@@ -63,6 +63,13 @@ AtariGraphicsManager::AtariGraphicsManager() {
 
 	ConfMan.flushToDisk();
 
+	// Generate RGB332 palette for the overlay
+	for (uint i = 0; i < 256; i++) {
+		_overlayPalette[i*3 + 0] = ((i >> 5) & 7) << 5;
+		_overlayPalette[i*3 + 1] = ((i >> 2) & 7) << 5;
+		_overlayPalette[i*3 + 2] = (i & 3) << 6;
+	}
+
 	g_system->getEventManager()->getEventDispatcher()->registerObserver(this, 10, false);
 }
 
@@ -76,8 +83,13 @@ bool AtariGraphicsManager::hasFeature(OSystem::Feature f) const {
 		debug("hasFeature(kFeatureAspectRatioCorrection): %d", !_vgaMonitor);
 		return !_vgaMonitor;
 	case OSystem::Feature::kFeatureCursorPalette:
-		debug("hasFeature(kFeatureCursorPalette): %d", isOverlayVisible());
-		return isOverlayVisible();
+		// XXX: pretend to have cursor palette all the time, this function
+		// can get called (and it is) any time, before and after showOverlay()
+		// (overlay cursor uses the cross if kFeatureCursorPalette returns false
+		// here too soon)
+		//debug("hasFeature(kFeatureCursorPalette): %d", isOverlayVisible());
+		//return isOverlayVisible();
+		return true;
 	case OSystem::Feature::kFeatureVSync:
 		debug("hasFeature(kFeatureVSync): %d", _vsync);
 		return true;
@@ -109,7 +121,8 @@ bool AtariGraphicsManager::getFeatureState(OSystem::Feature f) const {
 		return _aspectRatioCorrection;
 	case OSystem::Feature::kFeatureCursorPalette:
 		//debug("getFeatureState(kFeatureCursorPalette): %d", isOverlayVisible());
-		return isOverlayVisible();
+		//return isOverlayVisible();
+		return true;
 	case OSystem::Feature::kFeatureVSync:
 		//debug("getFeatureState(kFeatureVSync): %d", _vsync);
 		return _vsync;
@@ -134,7 +147,7 @@ void AtariGraphicsManager::initSize(uint width, uint height, const Graphics::Pix
 
 	_pendingState.width = width;
 	_pendingState.height = height;
-	_pendingState.format = format ? *format : PIXELFORMAT8;
+	_pendingState.format = format ? *format : PIXELFORMAT_CLUT8;
 }
 
 void AtariGraphicsManager::beginGFXTransaction() {
@@ -150,7 +163,7 @@ OSystem::TransactionError AtariGraphicsManager::endGFXTransaction() {
 	//if (_pendingState == _currentState)
 	//	return static_cast<OSystem::TransactionError>(error);
 
-	if (_pendingState.format != PIXELFORMAT8)
+	if (_pendingState.format != PIXELFORMAT_CLUT8)
 		error |= OSystem::TransactionError::kTransactionFormatNotSupported;
 
 	// TODO: Several engines support unusual resolutions like 256x240 (NES Maniac Mansion),
@@ -354,17 +367,13 @@ void AtariGraphicsManager::updateScreen() {
 	bool resolutionChanged = false;
 
 	if (_pendingScreenChange & kPendingScreenChangeOverlay) {
-		if (_vgaMonitor) {
-			if (getOverlayWidth() == 640 && getOverlayHeight() == 480)
-				asm_screen_set_scp_res(scp_640x480x16_vga);
-			else
-				asm_screen_set_scp_res(scp_320x240x16_vga);
-		} else {
-			//asm_screen_set_scp_res(scp_320x240x16_rgb);
-			asm_screen_set_scp_res(scp_640x480x16_rgb);
-		}
+		if (_vgaMonitor)
+			asm_screen_set_scp_res(scp_640x480x8_vga);
+		else
+			asm_screen_set_scp_res(scp_640x480x8_rgb);
 
 		asm_screen_set_vram(_screenOverlaySurface.getPixels());
+		asm_screen_set_falcon_palette(_overlayPalette);
 		resolutionChanged = true;
 	}
 
@@ -374,16 +383,8 @@ void AtariGraphicsManager::updateScreen() {
 		resolutionChanged = true;
 	}
 
-	if ((_pendingScreenChange & kPendingScreenChangePalette) && !isOverlayVisible()) {
-		static uint falconPalette[256];
-
-		for (uint i = 0; i < 256; ++i) {
-			// RRRRRRRR GGGGGGGG BBBBBBBB -> RRRRRRrr GGGGGGgg 00000000 BBBBBBbb
-			falconPalette[i] = (_palette[i * 3 + 0] << 24) | (_palette[i * 3 + 1] << 16) | _palette[i * 3 + 2];
-		}
-#ifdef SCREEN_ACTIVE
-		asm_screen_set_falcon_palette(falconPalette);
-#endif
+	if (_pendingScreenChange & kPendingScreenChangePalette) {
+		asm_screen_set_falcon_palette(_palette);
 	}
 
 	_pendingScreenChange = kPendingScreenChangeNone;
@@ -411,7 +412,7 @@ void AtariGraphicsManager::showOverlay(bool inGUI) {
 	if (_overlayVisible)
 		return;
 
-	_pendingScreenChange &= ~kPendingScreenChangeScreen;
+	_pendingScreenChange &= ~(kPendingScreenChangeScreen | kPendingScreenChangePalette);
 	_pendingScreenChange |= kPendingScreenChangeOverlay;
 
 	_cursor.swap();
@@ -433,7 +434,7 @@ void AtariGraphicsManager::hideOverlay() {
 		return;
 
 	_pendingScreenChange &= ~kPendingScreenChangeOverlay;
-	_pendingScreenChange |= kPendingScreenChangeScreen;
+	_pendingScreenChange |= (kPendingScreenChangeScreen | kPendingScreenChangePalette);
 
 	_cursor.swap();
 	// don't fool game cursor logic (especially direct rendering)
@@ -463,29 +464,44 @@ void AtariGraphicsManager::clearOverlay() {
 		vOffset = (480 - 400) / 2;
 	}
 
-	ScaleMode scaleMode;
-	if (w == _overlaySurface.w && h == _overlaySurface.h) {
-		scaleMode = ScaleMode::NONE;
-	} else if (w / _overlaySurface.w == 2 && h / _overlaySurface.h == 2) {
-		scaleMode = ScaleMode::DOWNSCALE;
-		vOffset /= 2;
-	} else if (_overlaySurface.w / w == 2 && _overlaySurface.h / h == 2) {
-		scaleMode = ScaleMode::UPSCALE;
+	bool upscale = false;
+
+	if (_overlaySurface.w / w == 2 && _overlaySurface.h / h == 2) {
+		upscale = true;
 		vOffset *= 2;
-	} else {
-		warning("Unknown overlay (%d, %d) / screen (%d, %d) ratio: ",
+	} else if (vOffset != 0) {
+		warning("Unknown overlay (%d, %d) / screen (%d, %d) ratio",
 			_overlaySurface.w, _overlaySurface.h, w, h);
 		return;
 	}
 
 	memset(_overlaySurface.getBasePtr(0, 0), 0, _overlaySurface.pitch * vOffset);
-	copySurface8ToSurface16(
-		sourceSurface,
-		_palette,
-		_overlaySurface,
-		0, vOffset,
-		Common::Rect(sourceSurface.w, sourceSurface.h),
-		scaleMode);
+
+	// Transpose from game palette to RGB332 (overlay palette)
+	const byte *src = (const byte*)sourceSurface.getPixels();
+	byte *dst = (byte*)_overlaySurface.getBasePtr(0, vOffset);
+
+	for (int y = 0; y < sourceSurface.h; y++) {
+		for (int x = 0; x < sourceSurface.w; x++) {
+			const byte col = *src++;
+			const byte pixel = (_palette[3*col + 0] & 0xe0)
+							| ((_palette[3*col + 1] >> 3) & 0x1c)
+							| ((_palette[3*col + 2] >> 6) & 0x03);
+
+			if (upscale) {
+				*(dst + _overlaySurface.pitch) = pixel;
+				*dst++ = pixel;
+				*(dst + _overlaySurface.pitch) = pixel;
+				*dst++ = pixel;
+			} else {
+				*dst++ = pixel;
+			}
+		}
+
+		if (upscale)
+			dst += _overlaySurface.pitch;
+	}
+
 	memset(_overlaySurface.getBasePtr(0, _overlaySurface.h - vOffset), 0, _overlaySurface.pitch * vOffset);
 
 	handleModifiedRect(_overlaySurface, Common::Rect(_overlaySurface.w, _overlaySurface.h), _modifiedOverlayRects);
@@ -530,7 +546,7 @@ void AtariGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int h
 	if (mask)
 		warning("AtariGraphicsManager::setMouseCursor: Masks are not supported");
 
-	const Graphics::PixelFormat cursorFormat = format ? *format : PIXELFORMAT8;
+	const Graphics::PixelFormat cursorFormat = format ? *format : PIXELFORMAT_CLUT8;
 	_cursor.setSurface(buf, (int)w, (int)h, hotspotX, hotspotY, keycolor, cursorFormat);
 }
 
@@ -576,14 +592,14 @@ Common::Keymap *AtariGraphicsManager::getKeymap() const {
 void AtariGraphicsManager::allocateSurfaces() {
 	for (int i = 0; i < SCREENS; ++i) {
 		if (!(_screen[i] = allocateAtariSurface(_screenSurface,
-												SCREEN_WIDTH, SCREEN_HEIGHT, PIXELFORMAT8,
+												SCREEN_WIDTH, SCREEN_HEIGHT, PIXELFORMAT_CLUT8,
 												getStRamAllocFunc())))
 			error("Failed to allocate screen memory in ST RAM");
 		_screenAligned[i] = (byte*)_screenSurface.getPixels();
 	}
 	_screenSurface.setPixels(_screenAligned[getDefaultGraphicsMode() <= 1 ? FRONT_BUFFER : BACK_BUFFER1]);
 
-	_chunkySurface.create(SCREEN_WIDTH, SCREEN_HEIGHT, PIXELFORMAT8);
+	_chunkySurface.create(SCREEN_WIDTH, SCREEN_HEIGHT, PIXELFORMAT_CLUT8);
 
 	if (!(_overlayScreen = allocateAtariSurface(_screenOverlaySurface,
 												getOverlayWidth(), getOverlayHeight(), getOverlayFormat(),
@@ -658,7 +674,7 @@ bool AtariGraphicsManager::updateOverlay() {
 		if (!drawCursor && !_cursor.outOfScreen && _cursor.visible)
 			drawCursor = rect.intersects(_cursor.dstRect);
 
-		_screenOverlaySurface.copyRectToSurface(_overlaySurface, rect.left, rect.top, rect);
+		copyRectToSurface(_screenOverlaySurface, _overlaySurface, rect.left, rect.top, rect);
 
 		_modifiedOverlayRects.pop_back();
 
@@ -669,7 +685,8 @@ bool AtariGraphicsManager::updateOverlay() {
 		return updated;
 
 	if ((_cursor.positionChanged || !_cursor.visible) && !_oldCursorRect.isEmpty()) {
-		_screenOverlaySurface.copyRectToSurface(_overlaySurface, _oldCursorRect.left, _oldCursorRect.top, _oldCursorRect);
+		alignRect(_screenOverlaySurface, _oldCursorRect);
+		copyRectToSurface(_screenOverlaySurface, _overlaySurface, _oldCursorRect.left, _oldCursorRect.top, _oldCursorRect);
 		_oldCursorRect = Common::Rect();
 
 		updated = true;
@@ -677,14 +694,10 @@ bool AtariGraphicsManager::updateOverlay() {
 
 	if (drawCursor && _cursor.visible) {
 		//debug("Redraw cursor (overlay): %d %d %d %d", _cursor.dstRect.left, _cursor.dstRect.top, _cursor.dstRect.width(), _cursor.dstRect.height());
-
-		copySurface8ToSurface16WithKey(
-			_cursor.surface,
-			_cursor.palette,
-			_screenOverlaySurface,
+		copyRectToSurfaceWithKey(
+			_screenOverlaySurface, _overlaySurface, _cursor.surface,
 			_cursor.dstRect.left, _cursor.dstRect.top,
-			_cursor.srcRect,
-			_cursor.keycolor);
+			_cursor.srcRect, _cursor.keycolor, _cursor.palette);
 
 		_cursor.positionChanged = _cursor.surfaceChanged = false;
 		_oldCursorRect = _cursor.dstRect;
@@ -795,9 +808,9 @@ bool AtariGraphicsManager::updateSingleBuffer() {
 	if (drawCursor && _cursor.visible) {
 		//debug("Redraw cursor (single): %d %d %d %d", _cursor.dstRect.left, _cursor.dstRect.top, _cursor.dstRect.width(), _cursor.dstRect.height());
 		copyRectToSurfaceWithKey(
-			_screenSurface, _cursor.surface,
+			_screenSurface, _chunkySurface, _cursor.surface,
 			_cursor.dstRect.left, _cursor.dstRect.top,
-			_cursor.srcRect, _cursor.keycolor);
+			_cursor.srcRect, _cursor.keycolor, _cursor.palette);
 
 		_cursor.positionChanged = _cursor.surfaceChanged = false;
 		_oldCursorRect = _cursor.dstRect;
@@ -849,9 +862,9 @@ bool AtariGraphicsManager::updateDoubleAndTripleBuffer() {
 		//debug("Redraw cursor (double/triple): %d %d %d %d", _cursor.dstRect.left, _cursor.dstRect.top, _cursor.dstRect.width(), _cursor.dstRect.height());
 
 		copyRectToSurfaceWithKey(
-			frontBufferScreenSurface, _cursor.surface,
+			frontBufferScreenSurface, _chunkySurface, _cursor.surface,
 			_cursor.dstRect.left, _cursor.dstRect.top,
-			_cursor.srcRect, _cursor.keycolor);
+			_cursor.srcRect, _cursor.keycolor, _cursor.palette);
 
 		_cursor.positionChanged = _cursor.surfaceChanged = false;
 		_oldCursorRect = _cursor.dstRect;
@@ -882,100 +895,10 @@ void AtariGraphicsManager::freeAtariSurface(byte *ptr, Graphics::Surface &surfac
 	freeFunc(ptr);
 }
 
-void AtariGraphicsManager::copySurface8ToSurface16(
-		const Graphics::Surface &srcSurface, const byte *srcPalette,
-		Graphics::Surface &dstSurface, int destX, int destY,
-		const Common::Rect subRect, ScaleMode scaleMode) const {
-	assert(srcSurface.format.bytesPerPixel == 1);
-	assert(dstSurface.format.bytesPerPixel == 2);
-
-	// faster (no memory (re-)allocation) version of Surface::convertTo()
-	const int w = subRect.width();
-	const int h = subRect.height();
-
-	const int srcScale = scaleMode == ScaleMode::DOWNSCALE ? 2 : 1;
-	const byte *srcRow = (const byte*)srcSurface.getBasePtr(subRect.left * srcScale, subRect.top * srcScale);
-	uint16 *dstRow = (uint16*)dstSurface.getBasePtr(destX, destY);	// already upscaled if needed
-
-	static uint32 srcPaletteMap[256];
-	Graphics::convertPaletteToMap(srcPaletteMap, srcPalette, 256, dstSurface.format);
-
-	// optimized paths for each case...
-	if (scaleMode == ScaleMode::NONE) {
-		for (int y = 0; y < h; y++) {
-			for (int x = 0; x < w; x++) {
-				*dstRow++ = srcPaletteMap[*srcRow++];
-			}
-
-			srcRow += srcSurface.w - w;
-			dstRow += dstSurface.w - w;
-		}
-	} else if (scaleMode == ScaleMode::DOWNSCALE) {
-		for (int y = 0; y < h / 2; y++) {
-			for (int x = 0; x < w / 2; x++) {
-				*dstRow++ = srcPaletteMap[*srcRow];
-				srcRow += 2;
-			}
-
-			srcRow += srcSurface.w - w + srcSurface.w;
-			dstRow += dstSurface.w - w / 2;
-		}
-	} else if (scaleMode == ScaleMode::UPSCALE) {
-		for (int y = 0; y < h; y++) {
-			for (int x = 0; x < w; x++) {
-				const uint16 pixel = srcPaletteMap[*srcRow++];
-
-				*(dstRow + dstSurface.w) = pixel;
-				*dstRow++ = pixel;
-				*(dstRow + dstSurface.w) = pixel;
-				*dstRow++ = pixel;
-			}
-
-			srcRow += srcSurface.w - w;
-			dstRow += dstSurface.w - w * 2 + dstSurface.w;
-		}
-	}
-}
-
-void AtariGraphicsManager::copySurface8ToSurface16WithKey(
-		const Graphics::Surface &srcSurface, const byte *srcPalette,
-		Graphics::Surface &dstSurface, int destX, int destY,
-		const Common::Rect subRect, uint32 key) const {
-	assert(srcSurface.format.bytesPerPixel == 1);
-	assert(dstSurface.format.bytesPerPixel == 2);
-
-	// faster (no memory (re-)allocation) version of Surface::convertTo()
-	const int w = subRect.width();
-	const int h = subRect.height();
-
-	const byte *srcRow = (const byte *)srcSurface.getBasePtr(subRect.left, subRect.top);
-	uint16 *dstRow = (uint16 *)dstSurface.getBasePtr(destX, destY);
-
-	static uint32 srcPaletteMap[256];
-	Graphics::convertPaletteToMap(srcPaletteMap, srcPalette, 256, dstSurface.format);
-
-	const uint16 keyColor = srcPaletteMap[key];
-
-	for (int y = 0; y < h; y++) {
-		for (int x = 0; x < w; x++) {
-			const uint16 pixel = srcPaletteMap[*srcRow++];
-
-			if (pixel != keyColor) {
-				*dstRow++ = pixel;
-			} else {
-				dstRow++;
-			}
-		}
-
-		srcRow += srcSurface.w - w;
-		dstRow += dstSurface.w - w;
-	}
-}
-
 void AtariGraphicsManager::handleModifiedRect(
 		const Graphics::Surface &surface,
 		Common::Rect rect, Common::Array<Common::Rect> &rects) const {
-	if (_currentState.mode == GraphicsMode::SingleBuffering)
+	if (_currentState.mode == GraphicsMode::SingleBuffering || isOverlayVisible())
 		alignRect(surface, rect);
 
 	if (rect.width() == surface.w && rect.height() == surface.h) {
@@ -1034,7 +957,8 @@ void AtariGraphicsManager::Cursor::updatePosition(int deltaX, int deltaY, const
 	positionChanged = true;
 }
 
-void AtariGraphicsManager::Cursor::setSurface(const void *buf, int w, int h, int _hotspotX, int _hotspotY, uint32 _keycolor, const Graphics::PixelFormat &format) {
+void AtariGraphicsManager::Cursor::setSurface(const void *buf, int w, int h, int hotspotX_, int hotspotY_, uint32 keycolor_,
+											  const Graphics::PixelFormat &format) {
 	if (w == 0 || h == 0 || buf == nullptr) {
 		if (surface.getPixels())
 			surface.free();
@@ -1044,11 +968,11 @@ void AtariGraphicsManager::Cursor::setSurface(const void *buf, int w, int h, int
 	if (surface.w != w || surface.h != h || surface.format != format)
 		surface.create(w, h, format);
 
-	surface.copyRectToSurface(buf, w * format.bytesPerPixel, 0, 0, w, h);
+	surface.copyRectToSurface(buf, w * surface.format.bytesPerPixel, 0, 0, w, h);
 
-	hotspotX = _hotspotX;
-	hotspotY = _hotspotY;
-	keycolor = _keycolor;
+	hotspotX = hotspotX_;
+	hotspotY = hotspotY_;
+	keycolor = keycolor_;
 
 	surfaceChanged = true;
 }
diff --git a/backends/graphics/atari/atari-graphics.h b/backends/graphics/atari/atari-graphics.h
index 9941972e3d7..f5e40ad9d81 100644
--- a/backends/graphics/atari/atari-graphics.h
+++ b/backends/graphics/atari/atari-graphics.h
@@ -35,10 +35,6 @@
 constexpr int SCREEN_WIDTH = 640;
 constexpr int SCREEN_HEIGHT = 480;
 
-// minimum overlay dimensions
-constexpr int OVERLAY_WIDTH = 320;
-constexpr int OVERLAY_HEIGHT = 240;
-
 class AtariGraphicsManager : public GraphicsManager, Common::EventObserver {
 public:
 	AtariGraphicsManager();
@@ -75,10 +71,12 @@ public:
 	void showOverlay(bool inGUI) override;
 	void hideOverlay() override;
 	bool isOverlayVisible() const override { return _overlayVisible; }
-	Graphics::PixelFormat getOverlayFormat() const override { return Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0); }
+	Graphics::PixelFormat getOverlayFormat() const override { return PIXELFORMAT_RGB332; }
 	void clearOverlay() override;
 	void grabOverlay(Graphics::Surface &surface) const override;
 	void copyRectToOverlay(const void *buf, int pitch, int x, int y, int w, int h) override;
+	int16 getOverlayHeight() const override { return 480; }
+	int16 getOverlayWidth() const override { return 640; }
 
 	bool showMouse(bool visible) override;
 	void warpMouse(int x, int y) override;
@@ -93,7 +91,8 @@ public:
 	Common::Keymap *getKeymap() const;
 
 protected:
-	const Graphics::PixelFormat PIXELFORMAT8 = Graphics::PixelFormat::createFormatCLUT8();
+	const Graphics::PixelFormat PIXELFORMAT_CLUT8 = Graphics::PixelFormat::createFormatCLUT8();
+	const Graphics::PixelFormat PIXELFORMAT_RGB332 = Graphics::PixelFormat(1, 3, 3, 2, 0, 5, 2, 0, 0);
 
 	typedef void* (*AtariMemAlloc)(size_t bytes);
 	typedef void (*AtariMemFree)(void *ptr);
@@ -141,8 +140,6 @@ protected:
 	static const int BACK_BUFFER1 = 1;
 	static const int BACK_BUFFER2 = 2;
 
-	Graphics::Surface _chunkySurface;	// for Videl's copyRectToSurfaceWithKey
-
 private:
 	enum CustomEventAction {
 		kActionToggleAspectRatioCorrection = 100,
@@ -165,27 +162,13 @@ private:
 	virtual void copyRectToSurface(Graphics::Surface &dstSurface,
 								   const Graphics::Surface &srcSurface, int destX, int destY,
 								   const Common::Rect &subRect) const = 0;
-	virtual void copyRectToSurfaceWithKey(Graphics::Surface &dstSurface,
+	virtual void copyRectToSurfaceWithKey(Graphics::Surface &dstSurface, const Graphics::Surface &bgSurface,
 										  const Graphics::Surface &srcSurface, int destX, int destY,
-										  const Common::Rect &subRect, uint32 key) const = 0;
+										  const Common::Rect &subRect, uint32 key, const byte srcPalette[256*3]) const = 0;
 	virtual void alignRect(const Graphics::Surface &srcSurface, Common::Rect &rect) const {}
 
-	enum class ScaleMode {
-		NONE,
-		UPSCALE,
-		DOWNSCALE
-	};
-	void copySurface8ToSurface16(const Graphics::Surface &srcSurface, const byte *srcPalette,
-								 Graphics::Surface &dstSurface, int destX, int destY,
-								 const Common::Rect subRect, ScaleMode scaleMode) const;
-	void copySurface8ToSurface16WithKey(const Graphics::Surface &srcSurface, const byte* srcPalette,
-										Graphics::Surface &dstSurface, int destX, int destY,
-										const Common::Rect subRect, uint32 key) const;
-
 	void handleModifiedRect(const Graphics::Surface &surface, Common::Rect rect, Common::Array<Common::Rect> &rects) const;
 
-	void updateCursorRect();
-
 	bool _aspectRatioCorrection = false;
 	bool _oldAspectRatioCorrection = false;
 	bool _vsync = true;
@@ -206,6 +189,7 @@ private:
 	Common::Rect _modifiedScreenRect;	// direct rendering only
 	bool _screenModified = false;	// double/triple buffering only
 
+	Graphics::Surface _chunkySurface;
 	Common::Array<Common::Rect> _modifiedChunkyRects;
 
 	byte *_overlayScreen = nullptr;	// for Mfree() purposes only
@@ -265,7 +249,7 @@ private:
 		Common::Rect srcRect;
 		Common::Rect dstRect;
 
-		// palette (only used for 16bpp screen surfaces)
+		// palette (only used for the overlay)
 		byte palette[256*3] = {};
 
 	private:
@@ -279,6 +263,7 @@ private:
 	Common::Rect _oldCursorRect;
 
 	byte _palette[256*3] = {};
+	byte _overlayPalette[256*3] = {};
 };
 
 #endif
diff --git a/backends/graphics/atari/videl-resolutions.cpp b/backends/graphics/atari/videl-resolutions.cpp
index 337a1b4f3a8..e676a174747 100644
--- a/backends/graphics/atari/videl-resolutions.cpp
+++ b/backends/graphics/atari/videl-resolutions.cpp
@@ -108,40 +108,6 @@ byte scp_320x240x8_vga[] = {
   0x00, 0xa0
 };
 
-const byte scp_320x240x16_rgb[] = {
-  0x53, 0x43, 0x50, 0x4e, 0x00, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x48, 0x7a, 0xb0, 0x00, 0xa9, 0xc1, 0x08, 0x00, 0xc2, 0x8c, 0xb0,
-  0x01, 0x64, 0x05, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x3d, 0x09, 0x00, 0x00,
-  0x00, 0xc0, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x2c,
-  0x00, 0xf0, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x01, 0xe8, 0x48, 0x00,
-  0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0xfe, 0x00, 0x99, 0x00, 0x59, 0x00, 0x38, 0x00, 0x99,
-  0x00, 0xd9, 0x02, 0x71, 0x02, 0x39, 0x00, 0x59, 0x00, 0x59, 0x02, 0x39,
-  0x02, 0x6b, 0x02, 0x00, 0x01, 0x81, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
-  0x01, 0x40
-};
-
-const byte scp_320x240x16_vga[] = {
-  0x53, 0x43, 0x50, 0x4e, 0x00, 0x09, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x3a, 0x2f, 0x00, 0x00, 0x19, 0x74, 0x90, 0x00, 0x46, 0x4e, 0x20,
-  0x00, 0x2a, 0x02, 0x80, 0x02, 0x58, 0x00, 0x00, 0x7a, 0xee, 0x00, 0x00,
-  0x00, 0x3e, 0x00, 0x00, 0x03, 0xc1, 0x00, 0x00, 0x01, 0xb2, 0x00, 0x1e,
-  0x01, 0xe0, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x01, 0xe8, 0x48, 0x00,
-  0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0xc6, 0x00, 0x8d, 0x00, 0x15, 0x02, 0xac, 0x00, 0x8d,
-  0x00, 0x97, 0x04, 0x19, 0x03, 0xfd, 0x00, 0x3f, 0x00, 0x3d, 0x03, 0xfd,
-  0x04, 0x15, 0x02, 0x00, 0x01, 0x86, 0x00, 0x00, 0x01, 0x00, 0x00, 0x05,
-  0x01, 0x40
-};
-
 const byte scp_640x400x8_rgb[] = {
   0x53, 0x43, 0x50, 0x4e, 0x00, 0x09, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -226,37 +192,3 @@ byte scp_640x480x8_vga[] = {
   0x04, 0x15, 0x02, 0x00, 0x01, 0x86, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08,
   0x01, 0x40
 };
-
-const byte scp_640x480x16_rgb[] = {
-  0x53, 0x43, 0x50, 0x4e, 0x00, 0x09, 0x00, 0x01, 0x36, 0x34, 0x30, 0x2a,
-  0x34, 0x38, 0x30, 0x2c, 0x20, 0x54, 0x72, 0x75, 0x65, 0x20, 0x43, 0x6f,
-  0x6c, 0x6f, 0x72, 0x2c, 0x20, 0x35, 0x30, 0x2e, 0x30, 0x20, 0x48, 0x7a,
-  0x20, 0x28, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6c, 0x2e, 0x29, 0x2c, 0x20,
-  0x31, 0x35, 0x36, 0x32, 0x35, 0x20, 0x48, 0x7a, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x48, 0x7a, 0xb0, 0x00, 0xa9, 0xc1, 0x08, 0x00, 0xc3, 0x80, 0xd4,
-  0x01, 0x64, 0x05, 0x00, 0x01, 0xf4, 0x00, 0x00, 0x3d, 0x09, 0x00, 0x00,
-  0x00, 0xc0, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x2c,
-  0x00, 0xf0, 0x00, 0x03, 0x00, 0x01, 0x00, 0x00, 0x01, 0xe8, 0x48, 0x00,
-  0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x01, 0xfe, 0x01, 0x32, 0x00, 0xb2, 0x00, 0x81, 0x01, 0x32,
-  0x01, 0xb3, 0x02, 0x70, 0x02, 0x39, 0x00, 0x59, 0x00, 0x58, 0x02, 0x38,
-  0x02, 0x6b, 0x02, 0x00, 0x01, 0x81, 0x00, 0x00, 0x01, 0x00, 0x00, 0x06,
-  0x02, 0x80
-};
-
-const byte scp_640x480x16_vga[] = {
-  0x53, 0x43, 0x50, 0x4e, 0x00, 0x09, 0x00, 0x02, 0x36, 0x34, 0x30, 0x2a,
-  0x34, 0x38, 0x30, 0x2c, 0x20, 0x54, 0x72, 0x75, 0x65, 0x20, 0x43, 0x6f,
-  0x6c, 0x6f, 0x72, 0x2c, 0x20, 0x36, 0x30, 0x2e, 0x30, 0x20, 0x48, 0x7a,
-  0x2c, 0x20, 0x33, 0x31, 0x34, 0x37, 0x30, 0x20, 0x48, 0x7a, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x3a, 0x2f, 0x00, 0x00, 0x1a, 0xaa, 0xe0, 0x00, 0x45, 0x17, 0xd0,
-  0x00, 0x2c, 0x02, 0x80, 0x02, 0x58, 0x00, 0x00, 0x7a, 0xee, 0x00, 0x00,
-  0x00, 0x3e, 0x00, 0x00, 0x03, 0xc1, 0x00, 0x00, 0x01, 0x93, 0x00, 0x1f,
-  0x01, 0xe0, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x01, 0xe8, 0x48, 0x00,
-  0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00,
-  0x00, 0x00, 0x00, 0xc6, 0x00, 0x8e, 0x00, 0x16, 0x02, 0xb5, 0x00, 0x8e,
-  0x00, 0x97, 0x04, 0x19, 0x03, 0xff, 0x00, 0x3f, 0x00, 0x3f, 0x03, 0xff,
-  0x04, 0x15, 0x02, 0x00, 0x01, 0x86, 0x00, 0x00, 0x01, 0x00, 0x00, 0x08,
-  0x02, 0x80
-};
diff --git a/backends/graphics/atari/videl-resolutions.h b/backends/graphics/atari/videl-resolutions.h
index 2dbae53374b..d67d8b42b2e 100644
--- a/backends/graphics/atari/videl-resolutions.h
+++ b/backends/graphics/atari/videl-resolutions.h
@@ -32,8 +32,6 @@ extern       byte scp_320x200x8_vga[SCP_SIZE];
 
 extern const byte scp_320x240x8_rgb[SCP_SIZE];
 extern       byte scp_320x240x8_vga[SCP_SIZE];
-extern const byte scp_320x240x16_rgb[SCP_SIZE];
-extern const byte scp_320x240x16_vga[SCP_SIZE];
 
 extern const byte scp_640x400x8_rgb[SCP_SIZE];
 extern const byte scp_640x400x8_rgb60[SCP_SIZE];
@@ -41,7 +39,5 @@ extern       byte scp_640x400x8_vga[SCP_SIZE];
 
 extern const byte scp_640x480x8_rgb[SCP_SIZE];
 extern       byte scp_640x480x8_vga[SCP_SIZE];
-extern const byte scp_640x480x16_rgb[SCP_SIZE];
-extern const byte scp_640x480x16_vga[SCP_SIZE];
 
 #endif
diff --git a/backends/platform/atari/readme.txt b/backends/platform/atari/readme.txt
index 291dd004f25..de0ab0788ec 100644
--- a/backends/platform/atari/readme.txt
+++ b/backends/platform/atari/readme.txt
@@ -58,8 +58,8 @@ less hungry games even a CT2/DFB at 50 MHz or the AfterBurner040 could be enough).
 - Custom (and optimal) drawing routines (especially for the cursor).
 
 - Custom (Super)Videl resolutions for the best possible performance and visual
-  experience (320x240 in RGB, chunky modes with SuperVidel, 640x480 at 16bpp for
-  the overlay in RGB/SuperVidel, ...)
+  experience (320x240 in RGB, chunky modes with SuperVidel, 640x480 at 8bpp for
+  the overlay, ...)
 
 - Custom (hardware based) aspect ratio correction (!)
 
@@ -373,8 +373,6 @@ Future plans
 
 - unified file paths in scummvm.ini
 
-- 8bpp overlay (and get rid of all that 16bpp handling code)
-
 - profiling :) (see also https://github.com/scummvm/scummvm/pull/2382)
 
 - DSP-based sample mixer
@@ -412,7 +410,7 @@ Future plans
 
 
 Closing words
-—------------
+-------------
 
 I have opened a pull request with all of my code
 (https://github.com/scummvm/scummvm/pull/4687) so who knows, maybe ScummVM


Commit: a98ce2c211b7058700682dba4a53e10ff6e8f44b
    https://github.com/scummvm/scummvm/commit/a98ce2c211b7058700682dba4a53e10ff6e8f44b
Author: Miro Kropacek (miro.kropacek at gmail.com)
Date: 2023-03-25T21:45:50+01:00

Commit Message:
BACKENDS: ATARI: Double/triple buffering doesn't enforce fullscreen redraws

Also, massively refactored, single/double/triple/overlay rendering is
finally united.

Changed paths:
    backends/graphics/atari/atari-graphics-supervidel.h
    backends/graphics/atari/atari-graphics.cpp
    backends/graphics/atari/atari-graphics.h
    backends/platform/atari/readme.txt


diff --git a/backends/graphics/atari/atari-graphics-supervidel.h b/backends/graphics/atari/atari-graphics-supervidel.h
index de9cc0561e9..ff612aa3da7 100644
--- a/backends/graphics/atari/atari-graphics-supervidel.h
+++ b/backends/graphics/atari/atari-graphics-supervidel.h
@@ -87,18 +87,6 @@ protected:
 	}
 
 private:
-	void copyRectToSurface(Graphics::Surface &dstSurface,
-						   const Graphics::Surface &srcSurface, int destX, int destY,
-						   const Common::Rect &subRect) const override {
-		dstSurface.copyRectToSurface(srcSurface, destX, destY, subRect);
-	}
-
-	void copyRectToSurfaceWithKey(Graphics::Surface &dstSurface, const Graphics::Surface &bgSurface,
-								  const Graphics::Surface &srcSurface, int destX, int destY,
-								  const Common::Rect &subRect, uint32 key, const byte srcPalette[256*3]) const override {
-		dstSurface.copyRectToSurfaceWithKey(srcSurface, destX, destY, subRect, key);
-	}
-
 	static long hasSvRamBoosted() {
 		register long ret __asm__ ("d0") = 0;
 
diff --git a/backends/graphics/atari/atari-graphics.cpp b/backends/graphics/atari/atari-graphics.cpp
index b533c9ea34e..c6afc297419 100644
--- a/backends/graphics/atari/atari-graphics.cpp
+++ b/backends/graphics/atari/atari-graphics.cpp
@@ -24,6 +24,7 @@
 #include <mint/cookie.h>
 #include <mint/falcon.h>
 #include <mint/osbind.h>
+#include <utility>
 
 #include "backends/graphics/atari/atari-graphics-asm.h"
 #include "backends/graphics/atari/atari-graphics-superblitter.h"
@@ -80,18 +81,18 @@ AtariGraphicsManager::~AtariGraphicsManager() {
 bool AtariGraphicsManager::hasFeature(OSystem::Feature f) const {
 	switch (f) {
 	case OSystem::Feature::kFeatureAspectRatioCorrection:
-		debug("hasFeature(kFeatureAspectRatioCorrection): %d", !_vgaMonitor);
+		//debug("hasFeature(kFeatureAspectRatioCorrection): %d", !_vgaMonitor);
 		return !_vgaMonitor;
 	case OSystem::Feature::kFeatureCursorPalette:
-		// XXX: pretend to have cursor palette all the time, this function
-		// can get called (and it is) any time, before and after showOverlay()
+		// FIXME: pretend to have cursor palette at all times, this function
+		// can get (and it is) called any time, before and after showOverlay()
 		// (overlay cursor uses the cross if kFeatureCursorPalette returns false
 		// here too soon)
 		//debug("hasFeature(kFeatureCursorPalette): %d", isOverlayVisible());
 		//return isOverlayVisible();
 		return true;
 	case OSystem::Feature::kFeatureVSync:
-		debug("hasFeature(kFeatureVSync): %d", _vsync);
+		//debug("hasFeature(kFeatureVSync): %d", _vsync);
 		return true;
 	default:
 		return false;
@@ -101,13 +102,13 @@ bool AtariGraphicsManager::hasFeature(OSystem::Feature f) const {
 void AtariGraphicsManager::setFeatureState(OSystem::Feature f, bool enable) {
 	switch (f) {
 	case OSystem::Feature::kFeatureAspectRatioCorrection:
-		debug("setFeatureState(kFeatureAspectRatioCorrection): %d", enable);
+		//debug("setFeatureState(kFeatureAspectRatioCorrection): %d", enable);
 		_oldAspectRatioCorrection = _aspectRatioCorrection;
 		_aspectRatioCorrection = enable;
 		break;
 	case OSystem::Feature::kFeatureVSync:
 		debug("setFeatureState(kFeatureVSync): %d", enable);
-		_vsync = enable;
+		_guiVsync = enable;
 		break;
 	default:
 		[[fallthrough]];
@@ -124,8 +125,8 @@ bool AtariGraphicsManager::getFeatureState(OSystem::Feature f) const {
 		//return isOverlayVisible();
 		return true;
 	case OSystem::Feature::kFeatureVSync:
-		//debug("getFeatureState(kFeatureVSync): %d", _vsync);
-		return _vsync;
+		//debug("getFeatureState(kFeatureVSync): %d", _currentState.vsync);
+		return _currentState.vsync;
 	default:
 		return false;
 	}
@@ -136,6 +137,23 @@ bool AtariGraphicsManager::setGraphicsMode(int mode, uint flags) {
 
 	if (mode >= 0 && mode <= 3) {
 		_pendingState.mode = (GraphicsMode)mode;
+
+		switch (_pendingState.mode) {
+		case GraphicsMode::DirectRendering:
+		case GraphicsMode::SingleBuffering:
+			_pendingState.vsync = _guiVsync;
+			break;
+		case GraphicsMode::DoubleBuffering:
+			_pendingState.vsync = true;
+			break;
+		case GraphicsMode::TripleBuffering:
+			_pendingState.vsync = false;
+			break;
+		}
+
+		// overwrite in case GUI is open again
+		ConfMan.setBool("vsync", _pendingState.vsync);
+
 		return true;
 	}
 
@@ -143,7 +161,7 @@ bool AtariGraphicsManager::setGraphicsMode(int mode, uint flags) {
 }
 
 void AtariGraphicsManager::initSize(uint width, uint height, const Graphics::PixelFormat *format) {
-	debug("initSize: %d, %d, %d (vsync: %d, mode: %d)", width, height, format ? format->bytesPerPixel : 1, _vsync, (int)_pendingState.mode);
+	debug("initSize: %d, %d, %d (vsync: %d, mode: %d)", width, height, format ? format->bytesPerPixel : 1, _pendingState.vsync, (int)_pendingState.mode);
 
 	_pendingState.width = width;
 	_pendingState.height = height;
@@ -180,19 +198,21 @@ OSystem::TransactionError AtariGraphicsManager::endGFXTransaction() {
 
 	_chunkySurface.init(_pendingState.width, _pendingState.height, _pendingState.width,
 		_chunkySurface.getPixels(), _pendingState.format);
+
+	_buffer[FRONT_BUFFER]->reset();
+	_buffer[BACK_BUFFER1]->reset();
+	_buffer[BACK_BUFFER2]->reset();
+
+	_workScreen = _buffer[(int)_pendingState.mode <= 1 ? FRONT_BUFFER : BACK_BUFFER1];
 	_screenSurface.init(_pendingState.width, _pendingState.height, _pendingState.width,
-		_screenAligned[(int)_pendingState.mode <= 1 ? FRONT_BUFFER : BACK_BUFFER1], _screenSurface.format);
+		_workScreen->p, _screenSurface.format);
 
 	// some games do not initialize their viewport entirely
 	if (_pendingState.mode != GraphicsMode::DirectRendering) {
-		memset(_chunkySurface.getPixels(), 0, _chunkySurface.pitch * _chunkySurface.h);
-
-		if (_pendingState.mode == GraphicsMode::SingleBuffering)
-			handleModifiedRect(_chunkySurface, Common::Rect(_chunkySurface.w, _chunkySurface.h), _modifiedChunkyRects);
-		else
-			_screenModified = true;
+		memset(_chunkySurface.getPixels(), 0, _chunkySurface.h * _chunkySurface.pitch);
+		addDirtyRect(_chunkySurface, _workScreen->dirtyRects, Common::Rect(_chunkySurface.w, _chunkySurface.h));
 	} else {
-		memset(_screenSurface.getPixels(), 0, _screenSurface.pitch * _screenSurface.h);
+		memset(_screenSurface.getPixels(), 0, _screenSurface.h * _screenSurface.pitch);
 	}
 
 	memset(_palette, 0, sizeof(_palette));
@@ -200,15 +220,11 @@ OSystem::TransactionError AtariGraphicsManager::endGFXTransaction() {
 
 	static bool firstRun = true;
 	if (firstRun) {
-		_cursor.setPosition(getOverlayWidth() / 2, getOverlayHeight() / 2, true);
+		_cursor.setPosition(getOverlayWidth() / 2, getOverlayHeight() / 2);
 		_cursor.swap();
 		firstRun = false;
 	}
 
-	// reinitialize old cursor position, there's no use for it anymore and it's dangerous
-	//  to let it set to possibly bigger values then the upcoming resolution
-	_oldCursorRect = Common::Rect();
-
 	warpMouse(_pendingState.width / 2, _pendingState.height / 2);
 
 	_currentState = _pendingState;
@@ -234,21 +250,17 @@ void AtariGraphicsManager::copyRectToScreen(const void *buf, int pitch, int x, i
 
 	if (_currentState.mode != GraphicsMode::DirectRendering) {
 		_chunkySurface.copyRectToSurface(buf, pitch, x, y, w, h);
-
-		if (_currentState.mode == GraphicsMode::SingleBuffering)
-			handleModifiedRect(_chunkySurface, Common::Rect(x, y, x + w, y + h), _modifiedChunkyRects);
-		else
-			_screenModified = true;
+		addDirtyRect(_chunkySurface, _workScreen->dirtyRects, Common::Rect(x, y, x + w, y + h));
 	} else {
 		// TODO: c2p with 16pix align
 		_screenSurface.copyRectToSurface(buf, pitch, x, y, w, h);
 
-		_modifiedScreenRect = Common::Rect(x, y, x + w, y + h);
+		_dirtyScreenRect = Common::Rect(x, y, x + w, y + h);
 
-		bool vsync = _vsync;
-		_vsync = false;
+		bool vsync = _currentState.vsync;
+		_currentState.vsync = false;
 		updateScreen();
-		_vsync = vsync;
+		_currentState.vsync = vsync;
 	}
 }
 
@@ -260,109 +272,100 @@ Graphics::Surface *AtariGraphicsManager::lockScreen() {
 
 void AtariGraphicsManager::unlockScreen() {
 	if (_currentState.mode != GraphicsMode::DirectRendering) {
-		if (_currentState.mode == GraphicsMode::SingleBuffering)
-			handleModifiedRect(_chunkySurface, Common::Rect(_chunkySurface.w, _chunkySurface.h), _modifiedChunkyRects);
-		else
-			_screenModified = true;
+		addDirtyRect(_chunkySurface, _workScreen->dirtyRects, Common::Rect(_chunkySurface.w, _chunkySurface.h));
 	} else {
-		_modifiedScreenRect = Common::Rect(_screenSurface.w, _screenSurface.h);
+		_dirtyScreenRect = Common::Rect(_screenSurface.w, _screenSurface.h);
 
-		bool vsync = _vsync;
-		_vsync = false;
+		bool vsync = _currentState.vsync;
+		_currentState.vsync = false;
 		updateScreen();
-		_vsync = vsync;
+		_currentState.vsync = vsync;
 	}
 }
 
 void AtariGraphicsManager::fillScreen(uint32 col) {
 	debug("fillScreen: %d", col);
 
-	if (_currentState.mode != GraphicsMode::DirectRendering) {
-		const Common::Rect rect = Common::Rect(_chunkySurface.w, _chunkySurface.h);
-		_chunkySurface.fillRect(rect, col);
-
-		if (_currentState.mode == GraphicsMode::SingleBuffering)
-			handleModifiedRect(_chunkySurface, rect, _modifiedChunkyRects);
-		else
-			_screenModified = true;
-	} else {
-		const Common::Rect rect = Common::Rect(_screenSurface.w, _screenSurface.h);
-		_screenSurface.fillRect(rect, col);
-	}
+	Graphics::Surface *screen = lockScreen();
+	screen->fillRect(Common::Rect(screen->w, screen->h), col);
+	unlockScreen();
 }
 
 void AtariGraphicsManager::updateScreen() {
 	//debug("updateScreen");
 
 	// updates outOfScreen OR srcRect/dstRect (only if visible/needed)
-	_cursor.update(isOverlayVisible() ? _screenOverlaySurface : _screenSurface);
+	_cursor.update(isOverlayVisible() ? _screenOverlaySurface : _screenSurface,
+				   _workScreen->cursorPositionChanged || _workScreen->cursorSurfaceChanged);
 
-	bool screenUpdated = false;
+	bool screenUpdated;
 
 	lockSuperBlitter();
 
+	assert(_currentState.mode >= GraphicsMode::DirectRendering && _currentState.mode <= GraphicsMode::TripleBuffering);
+
 	if (isOverlayVisible()) {
-		screenUpdated = updateOverlay();
+		screenUpdated = updateBuffered(_overlaySurface, _screenOverlaySurface, _workScreen->dirtyRects);
+		assert(_workScreen == _buffer[OVERLAY_BUFFER]);
+
+		_workScreen->dirtyRects.clear();
+		unlockSuperBlitter();
+	} else if (_currentState.mode == GraphicsMode::DirectRendering) {
+		screenUpdated = updateDirect();
+		assert(_workScreen == _buffer[FRONT_BUFFER]);
+
+		unlockSuperBlitter();
+	} else if (_currentState.mode == GraphicsMode::SingleBuffering) {
+		screenUpdated = updateBuffered(_chunkySurface, _screenSurface, _workScreen->dirtyRects);
+		assert(_workScreen == _buffer[FRONT_BUFFER]);
+
+		_workScreen->dirtyRects.clear();
+		unlockSuperBlitter();
 	} else {
-		switch(_currentState.mode) {
-		case GraphicsMode::DirectRendering:
-			screenUpdated = updateDirectBuffer();
-			break;
-		case GraphicsMode::SingleBuffering:
-			screenUpdated = updateSingleBuffer();
-			break;
-		case GraphicsMode::DoubleBuffering:
-		case GraphicsMode::TripleBuffering:
-			screenUpdated = updateDoubleAndTripleBuffer();
-			break;
-		}
-	}
+		screenUpdated = updateBuffered(_chunkySurface, _screenSurface, _workScreen->dirtyRects);
+		assert(_workScreen == _buffer[BACK_BUFFER1]);
 
-	unlockSuperBlitter();
-
-	//if (_cursor.outOfScreen)
-	//	warning("mouse out of screen");
-
-	bool vsync = _vsync;
-
-	if (_screenModified) {
 		if (_currentState.mode == GraphicsMode::DoubleBuffering) {
-			byte *tmp = _screenAligned[FRONT_BUFFER];
-			_screenAligned[FRONT_BUFFER] = _screenAligned[BACK_BUFFER1];
-			_screenAligned[BACK_BUFFER1] = tmp;
+			// apply dirty rects from previous frame
+			if (!_buffer[FRONT_BUFFER]->dirtyRects.empty()) {
+				screenUpdated |= updateBuffered(_chunkySurface, _screenSurface, _buffer[FRONT_BUFFER]->dirtyRects);
+				// clear the least recent dirty rects
+				_buffer[FRONT_BUFFER]->dirtyRects.clear();
+			}
 
-			// always wait for vbl
-			vsync = true;
+			if (screenUpdated) {
+				ScreenInfo *tmp = _buffer[FRONT_BUFFER];
+				_buffer[FRONT_BUFFER] = _buffer[BACK_BUFFER1];
+				_buffer[BACK_BUFFER1] = tmp;
+			}
 		} else if (_currentState.mode == GraphicsMode::TripleBuffering) {
-			if (vsync) {
-				// render into BACK_BUFFER1 and/or BACK_BUFFER2 and set the most recent one
-				_screenAligned[FRONT_BUFFER] = _screenAligned[BACK_BUFFER1];
-
-				byte *tmp = _screenAligned[BACK_BUFFER1];
-				_screenAligned[BACK_BUFFER1] = _screenAligned[BACK_BUFFER2];
-				_screenAligned[BACK_BUFFER2] = tmp;
-			} else {
-				// render into BACK_BUFFER1 and/or BACK_BUFFER2 and/or FRONT_BUFFER
-				byte *tmp = _screenAligned[FRONT_BUFFER];
-				_screenAligned[FRONT_BUFFER] = _screenAligned[BACK_BUFFER1];
-				_screenAligned[BACK_BUFFER1] = _screenAligned[BACK_BUFFER2];
-				_screenAligned[BACK_BUFFER2] = tmp;
+			// apply dirty rects from previous frame
+			if (!_buffer[BACK_BUFFER2]->dirtyRects.empty()) {
+				screenUpdated |= updateBuffered(_chunkySurface, _screenSurface, _buffer[BACK_BUFFER2]->dirtyRects);
+				// clear the least recent dirty rects
+				_buffer[BACK_BUFFER2]->dirtyRects.clear();
 			}
 
-			// never wait for vbl (use it only as a flag for the modes above)
-			vsync = false;
+			// render into BACK_BUFFER1 and/or BACK_BUFFER2 and set the most recent one
+			if (screenUpdated) {
+				_buffer[FRONT_BUFFER] = _buffer[BACK_BUFFER1];
+
+				ScreenInfo *tmp = _buffer[BACK_BUFFER1];
+				_buffer[BACK_BUFFER1] = _buffer[BACK_BUFFER2];
+				_buffer[BACK_BUFFER2] = tmp;
+			}
 		}
 
+		// finish blitting before setting new screen address
+		unlockSuperBlitter();
+
 #ifdef SCREEN_ACTIVE
-		asm_screen_set_vram(_screenAligned[FRONT_BUFFER]);
+		asm_screen_set_vram(_buffer[FRONT_BUFFER]->p);
 #endif
-		_screenSurface.setPixels(_screenAligned[BACK_BUFFER1]);
-		_screenModified = false;
+		_workScreen = _buffer[BACK_BUFFER1];
+		_screenSurface.setPixels(_workScreen->p);
 	}
 
-	// everything below this line is done in VBL so don't wait if nothing has been updated!
-	vsync &= screenUpdated;
-
 #ifdef SCREEN_ACTIVE
 	bool resolutionChanged = false;
 
@@ -379,7 +382,7 @@ void AtariGraphicsManager::updateScreen() {
 
 	if (_pendingScreenChange & kPendingScreenChangeScreen) {
 		setVidelResolution();
-		asm_screen_set_vram(_screenAligned[FRONT_BUFFER]);
+		asm_screen_set_vram(_buffer[FRONT_BUFFER]->p);
 		resolutionChanged = true;
 	}
 
@@ -396,7 +399,7 @@ void AtariGraphicsManager::updateScreen() {
 		_oldAspectRatioCorrection = _aspectRatioCorrection;
 	}
 
-	if (vsync)
+	if (_currentState.vsync & screenUpdated)
 		waitForVbl();
 #endif
 	//debug("end of updateScreen");
@@ -415,15 +418,18 @@ void AtariGraphicsManager::showOverlay(bool inGUI) {
 	_pendingScreenChange &= ~(kPendingScreenChangeScreen | kPendingScreenChangePalette);
 	_pendingScreenChange |= kPendingScreenChangeOverlay;
 
-	_cursor.swap();
 	if (_currentState.mode == GraphicsMode::DirectRendering) {
 		// make sure that _oldCursorRect is used to restore the original game graphics
 		// (but only if resolution hasn't changed, see endGFXTransaction())
 		bool wasVisible = showMouse(false);
-		updateDirectBuffer();
+		updateDirect();
 		showMouse(wasVisible);
 	}
 
+	_cursor.swap();
+	_oldWorkScreen = _workScreen;
+	_workScreen = _buffer[OVERLAY_BUFFER];
+
 	_overlayVisible = true;
 }
 
@@ -436,10 +442,12 @@ void AtariGraphicsManager::hideOverlay() {
 	_pendingScreenChange &= ~kPendingScreenChangeOverlay;
 	_pendingScreenChange |= (kPendingScreenChangeScreen | kPendingScreenChangePalette);
 
+	// do not cache dirtyRects and oldCursorRect
+	_workScreen->reset();
+
+	_workScreen = _oldWorkScreen;
+	_oldWorkScreen = nullptr;
 	_cursor.swap();
-	// don't fool game cursor logic (especially direct rendering)
-	// (the overlay doesn't need any restoration upon re-entering)
-	_oldCursorRect = Common::Rect();
 
 	_overlayVisible = false;
 }
@@ -504,7 +512,7 @@ void AtariGraphicsManager::clearOverlay() {
 
 	memset(_overlaySurface.getBasePtr(0, _overlaySurface.h - vOffset), 0, _overlaySurface.pitch * vOffset);
 
-	handleModifiedRect(_overlaySurface, Common::Rect(_overlaySurface.w, _overlaySurface.h), _modifiedOverlayRects);
+	addDirtyRect(_overlaySurface, _buffer[OVERLAY_BUFFER]->dirtyRects, Common::Rect(_overlaySurface.w, _overlaySurface.h));
 }
 
 void AtariGraphicsManager::grabOverlay(Graphics::Surface &surface) const {
@@ -518,7 +526,7 @@ void AtariGraphicsManager::copyRectToOverlay(const void *buf, int pitch, int x,
 
 	_overlaySurface.copyRectToSurface(buf, pitch, x, y, w, h);
 
-	handleModifiedRect(_overlaySurface, Common::Rect(x, y, x + w, y + h), _modifiedOverlayRects);
+	addDirtyRect(_overlaySurface, _buffer[OVERLAY_BUFFER]->dirtyRects, Common::Rect(x, y, x + w, y + h));
 }
 
 bool AtariGraphicsManager::showMouse(bool visible) {
@@ -536,7 +544,8 @@ bool AtariGraphicsManager::showMouse(bool visible) {
 void AtariGraphicsManager::warpMouse(int x, int y) {
 	//debug("warpMouse: %d, %d", x, y);
 
-	_cursor.setPosition(x, y, true);
+	_cursor.setPosition(x, y);
+	cursorPositionChanged();
 }
 
 void AtariGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor,
@@ -548,17 +557,19 @@ void AtariGraphicsManager::setMouseCursor(const void *buf, uint w, uint h, int h
 
 	const Graphics::PixelFormat cursorFormat = format ? *format : PIXELFORMAT_CLUT8;
 	_cursor.setSurface(buf, (int)w, (int)h, hotspotX, hotspotY, keycolor, cursorFormat);
+	cursorSurfaceChanged();
 }
 
 void AtariGraphicsManager::setCursorPalette(const byte *colors, uint start, uint num) {
 	debug("setCursorPalette: %d, %d", start, num);
 
 	memcpy(&_cursor.palette[start * 3], colors, num * 3);
-	_cursor.surfaceChanged = true;
+	cursorSurfaceChanged();
 }
 
 void AtariGraphicsManager::updateMousePosition(int deltaX, int deltaY) {
 	_cursor.updatePosition(deltaX, deltaY, isOverlayVisible() ? _screenOverlaySurface : _screenSurface);
+	cursorPositionChanged();
 }
 
 bool AtariGraphicsManager::notifyEvent(const Common::Event &event) {
@@ -590,36 +601,30 @@ Common::Keymap *AtariGraphicsManager::getKeymap() const {
 }
 
 void AtariGraphicsManager::allocateSurfaces() {
-	for (int i = 0; i < SCREENS; ++i) {
-		if (!(_screen[i] = allocateAtariSurface(_screenSurface,
-												SCREEN_WIDTH, SCREEN_HEIGHT, PIXELFORMAT_CLUT8,
-												getStRamAllocFunc())))
-			error("Failed to allocate screen memory in ST RAM");
-		_screenAligned[i] = (byte*)_screenSurface.getPixels();
+	for (int i : { FRONT_BUFFER, BACK_BUFFER1, BACK_BUFFER2 }) {
+		allocateAtariSurface(_screenSurface, SCREEN_WIDTH, SCREEN_HEIGHT, PIXELFORMAT_CLUT8, getStRamAllocFunc());
+		_buffer[i] = new ScreenInfo((byte *)_screenSurface.getPixels());
 	}
-	_screenSurface.setPixels(_screenAligned[getDefaultGraphicsMode() <= 1 ? FRONT_BUFFER : BACK_BUFFER1]);
 
 	_chunkySurface.create(SCREEN_WIDTH, SCREEN_HEIGHT, PIXELFORMAT_CLUT8);
 
-	if (!(_overlayScreen = allocateAtariSurface(_screenOverlaySurface,
-												getOverlayWidth(), getOverlayHeight(), getOverlayFormat(),
-												getStRamAllocFunc())))
-		error("Failed to allocate overlay memory in ST RAM");
+	allocateAtariSurface(_screenOverlaySurface, getOverlayWidth(), getOverlayHeight(), getOverlayFormat(), getStRamAllocFunc());
+	_buffer[OVERLAY_BUFFER] = new ScreenInfo((byte *)_screenOverlaySurface.getPixels());
 
 	_overlaySurface.create(getOverlayWidth(), getOverlayHeight(), getOverlayFormat());
 }
 
 void AtariGraphicsManager::freeSurfaces() {
-	for (int i = 0; i < SCREENS; ++i) {
-		freeAtariSurface(_screen[i], _screenSurface, getStRamFreeFunc());
-		_screen[i] = _screenAligned[i] = nullptr;
+	for (int i : { FRONT_BUFFER, BACK_BUFFER1, BACK_BUFFER2, OVERLAY_BUFFER }) {
+		freeAtariSurface(_buffer[i]->p, getStRamFreeFunc());
+		delete _buffer[i];
+		_buffer[i] = nullptr;
 	}
+	_screenSurface = Graphics::Surface();
+	_screenOverlaySurface = Graphics::Surface();
+	_workScreen = nullptr;
 
 	_chunkySurface.free();
-
-	freeAtariSurface(_overlayScreen, _screenOverlaySurface, getStRamFreeFunc());
-	_overlayScreen = nullptr;
-
 	_overlaySurface.free();
 }
 
@@ -664,85 +669,43 @@ void AtariGraphicsManager::waitForVbl() const {
 	while (counter == vbl_counter);
 }
 
-bool AtariGraphicsManager::updateOverlay() {
+bool AtariGraphicsManager::updateDirect() {
 	bool updated = false;
-	bool drawCursor = _cursor.isModified();
-
-	while (!_modifiedOverlayRects.empty()) {
-		const Common::Rect &rect = _modifiedOverlayRects.back();
-
-		if (!drawCursor && !_cursor.outOfScreen && _cursor.visible)
-			drawCursor = rect.intersects(_cursor.dstRect);
-
-		copyRectToSurface(_screenOverlaySurface, _overlaySurface, rect.left, rect.top, rect);
-
-		_modifiedOverlayRects.pop_back();
-
-		updated = true;
-	}
 
 	if (_cursor.outOfScreen)
 		return updated;
 
-	if ((_cursor.positionChanged || !_cursor.visible) && !_oldCursorRect.isEmpty()) {
-		alignRect(_screenOverlaySurface, _oldCursorRect);
-		copyRectToSurface(_screenOverlaySurface, _overlaySurface, _oldCursorRect.left, _oldCursorRect.top, _oldCursorRect);
-		_oldCursorRect = Common::Rect();
+	bool drawCursor = _workScreen->cursorPositionChanged || _workScreen->cursorSurfaceChanged;
 
-		updated = true;
-	}
-
-	if (drawCursor && _cursor.visible) {
-		//debug("Redraw cursor (overlay): %d %d %d %d", _cursor.dstRect.left, _cursor.dstRect.top, _cursor.dstRect.width(), _cursor.dstRect.height());
-		copyRectToSurfaceWithKey(
-			_screenOverlaySurface, _overlaySurface, _cursor.surface,
-			_cursor.dstRect.left, _cursor.dstRect.top,
-			_cursor.srcRect, _cursor.keycolor, _cursor.palette);
-
-		_cursor.positionChanged = _cursor.surfaceChanged = false;
-		_oldCursorRect = _cursor.dstRect;
-
-		updated = true;
-	}
-
-	return updated;
-}
-
-bool AtariGraphicsManager::updateDirectBuffer() {
-	bool updated = false;
-
-	if (_cursor.outOfScreen)
-		return updated;
-
-	bool drawCursor = _cursor.isModified();
-
-	if (!drawCursor && _cursor.visible && !_modifiedScreenRect.isEmpty())
-		drawCursor = _modifiedScreenRect.intersects(_cursor.dstRect);
+	if (!drawCursor && _cursor.visible && !_dirtyScreenRect.isEmpty())
+		drawCursor = _dirtyScreenRect.intersects(_cursor.dstRect);
 
 	static Graphics::Surface cachedCursorSurface;
 
-	if (!_oldCursorRect.isEmpty() && !_modifiedScreenRect.isEmpty()) {
-		const Common::Rect intersectingRect = _modifiedScreenRect.findIntersectingRect(_oldCursorRect);
+	Common::Rect &oldCursorRect = _workScreen->oldCursorRect;
+
+	if (!oldCursorRect.isEmpty() && !_dirtyScreenRect.isEmpty()) {
+		const Common::Rect intersectingRect = _dirtyScreenRect.findIntersectingRect(oldCursorRect);
 		if (!intersectingRect.isEmpty()) {
 			// update cached surface
 			const Graphics::Surface intersectingScreenSurface = _screenSurface.getSubArea(intersectingRect);
 			cachedCursorSurface.copyRectToSurface(
 				intersectingScreenSurface,
-				intersectingRect.left - _oldCursorRect.left,
-				intersectingRect.top - _oldCursorRect.top,
+				intersectingRect.left - oldCursorRect.left,
+				intersectingRect.top - oldCursorRect.top,
 				Common::Rect(intersectingScreenSurface.w, intersectingScreenSurface.h));
 		}
 	}
 
-	_modifiedScreenRect = Common::Rect();
+	_dirtyScreenRect = Common::Rect();
 
-	if ((_cursor.positionChanged || !_cursor.visible) && !_oldCursorRect.isEmpty()) {
+	if ((_workScreen->cursorPositionChanged || !_cursor.visible) && !oldCursorRect.isEmpty()) {
 		_screenSurface.copyRectToSurface(
 			cachedCursorSurface,
-			_oldCursorRect.left, _oldCursorRect.top,
-			Common::Rect(_oldCursorRect.width(), _oldCursorRect.height()));
+			oldCursorRect.left, oldCursorRect.top,
+			Common::Rect(oldCursorRect.width(), oldCursorRect.height()));
 
-		_oldCursorRect = Common::Rect();
+		oldCursorRect = Common::Rect();
 
 		updated = true;
 	}
@@ -755,7 +718,7 @@ bool AtariGraphicsManager::updateDirectBuffer() {
 		}
 
 		// background has been restored, so it's safe to read _screenSurface
-		if (_oldCursorRect.isEmpty())
+		if (oldCursorRect.isEmpty())
 			cachedCursorSurface.copyRectToSurface(_screenSurface, 0, 0, _cursor.dstRect);
 
 		_screenSurface.copyRectToSurfaceWithKey(
@@ -764,8 +727,8 @@ bool AtariGraphicsManager::updateDirectBuffer() {
 			_cursor.srcRect,
 			_cursor.keycolor);
 
-		_cursor.positionChanged = _cursor.surfaceChanged = false;
-		_oldCursorRect = _cursor.dstRect;
+		_workScreen->cursorPositionChanged = _workScreen->cursorSurfaceChanged = false;
+		oldCursorRect = _cursor.dstRect;
 
 		updated = true;
 	}
@@ -773,19 +736,15 @@ bool AtariGraphicsManager::updateDirectBuffer() {
 	return updated;
 }
 
-bool AtariGraphicsManager::updateSingleBuffer() {
+bool AtariGraphicsManager::updateBuffered(const Graphics::Surface &srcSurface, Graphics::Surface &dstSurface, const DirtyRects &dirtyRects) {
 	bool updated = false;
-	bool drawCursor = _cursor.isModified();
-
-	while (!_modifiedChunkyRects.empty()) {
-		const Common::Rect &rect = _modifiedChunkyRects.back();
+	bool drawCursor = _workScreen->cursorPositionChanged || _workScreen->cursorSurfaceChanged;;
 
+	for (auto it = dirtyRects.begin(); it != dirtyRects.end(); ++it) {
 		if (!drawCursor && !_cursor.outOfScreen && _cursor.visible)
-			drawCursor = rect.intersects(_cursor.dstRect);
-
-		copyRectToSurface(_screenSurface, _chunkySurface, rect.left, rect.top, rect);
+			drawCursor = it->intersects(_cursor.dstRect);
 
-		_modifiedChunkyRects.pop_back();
+		copyRectToSurface(dstSurface, srcSurface, it->left, it->top, *it);
 
 		updated = true;
 	}
@@ -793,27 +752,29 @@ bool AtariGraphicsManager::updateSingleBuffer() {
 	if (_cursor.outOfScreen)
 		return updated;
 
-	if ((_cursor.positionChanged || !_cursor.visible) && !_oldCursorRect.isEmpty()) {
-		alignRect(_chunkySurface, _oldCursorRect);
+	Common::Rect &oldCursorRect = _workScreen->oldCursorRect;
+
+	if ((_workScreen->cursorPositionChanged || !_cursor.visible) && !oldCursorRect.isEmpty()) {
+		alignRect(dstSurface, oldCursorRect);
 		copyRectToSurface(
-			_screenSurface, _chunkySurface,
-			_oldCursorRect.left, _oldCursorRect.top,
-			_oldCursorRect);
+			dstSurface, srcSurface,
+			oldCursorRect.left, oldCursorRect.top,
+			oldCursorRect);
 
-		_oldCursorRect = Common::Rect();
+		oldCursorRect = Common::Rect();
 
 		updated = true;
 	}
 
 	if (drawCursor && _cursor.visible) {
-		//debug("Redraw cursor (single): %d %d %d %d", _cursor.dstRect.left, _cursor.dstRect.top, _cursor.dstRect.width(), _cursor.dstRect.height());
+		//debug("Redraw cursor: %d %d %d %d", _cursor.dstRect.left, _cursor.dstRect.top, _cursor.dstRect.width(), _cursor.dstRect.height());
 		copyRectToSurfaceWithKey(
-			_screenSurface, _chunkySurface, _cursor.surface,
+			dstSurface, srcSurface, _cursor.surface,
 			_cursor.dstRect.left, _cursor.dstRect.top,
 			_cursor.srcRect, _cursor.keycolor, _cursor.palette);
 
-		_cursor.positionChanged = _cursor.surfaceChanged = false;
-		_oldCursorRect = _cursor.dstRect;
+		_workScreen->cursorPositionChanged = _workScreen->cursorSurfaceChanged = false;
+		oldCursorRect = _cursor.dstRect;
 
 		updated = true;
 	}
@@ -821,88 +782,36 @@ bool AtariGraphicsManager::updateSingleBuffer() {
 	return updated;
 }
 
-bool AtariGraphicsManager::updateDoubleAndTripleBuffer() {
-	bool updated = false;
-	bool drawCursor = _cursor.isModified();
-
-	if (_screenModified) {
-		drawCursor = true;
-
-		copyRectToSurface(
-			_screenSurface, _chunkySurface,
-			0, 0,
-			Common::Rect(_chunkySurface.w, _chunkySurface.h));
-
-		// updated in screen swapping
-		//_screenModified = false;
-		updated = true;
-	}
-
-	if (_cursor.outOfScreen)
-		return updated;
-
-	// render directly to the screen to be swapped (so we don't have to refresh full screen when only cursor moves)
-	Graphics::Surface frontBufferScreenSurface;
-	frontBufferScreenSurface.init(_screenSurface.w, _screenSurface.h, _screenSurface.pitch,
-		_screenAligned[_screenModified ? BACK_BUFFER1 : FRONT_BUFFER], _screenSurface.format);
-
-	if ((_cursor.positionChanged || !_cursor.visible) && !_oldCursorRect.isEmpty() && !_screenModified) {
-		alignRect(_chunkySurface, _oldCursorRect);
-		copyRectToSurface(
-			frontBufferScreenSurface, _chunkySurface,
-			_oldCursorRect.left, _oldCursorRect.top,
-			_oldCursorRect);
-
-		_oldCursorRect = Common::Rect();
-
-		updated = true;
-	}
-
-	if (drawCursor && _cursor.visible) {
-		//debug("Redraw cursor (double/triple): %d %d %d %d", _cursor.dstRect.left, _cursor.dstRect.top, _cursor.dstRect.width(), _cursor.dstRect.height());
-
-		copyRectToSurfaceWithKey(
-			frontBufferScreenSurface, _chunkySurface, _cursor.surface,
-			_cursor.dstRect.left, _cursor.dstRect.top,
-			_cursor.srcRect, _cursor.keycolor, _cursor.palette);
+void AtariGraphicsManager::allocateAtariSurface(Graphics::Surface &surface,
+		int width, int height, const Graphics::PixelFormat &format,
+		const AtariMemAlloc &allocFunc) {
+	constexpr size_t ALIGN = 16;	// 16 bytes
 
-		_cursor.positionChanged = _cursor.surfaceChanged = false;
-		_oldCursorRect = _cursor.dstRect;
+	surface.init(width, height, (width * format.bytesPerPixel + ALIGN - 1) & (-ALIGN), nullptr, format);
 
-		updated = true;
+	void *pixelsUnaligned = allocFunc(sizeof(uintptr) + (surface.h * surface.pitch) + ALIGN - 1);
+	if (!pixelsUnaligned) {
+		error("Failed to allocate memory in ST RAM");
 	}
 
-	return updated;
-}
-
-byte *AtariGraphicsManager::allocateAtariSurface(Graphics::Surface &surface,
-		int width, int height, const Graphics::PixelFormat &format,
-		const AtariMemAlloc &allocFunc) {
-	byte *buf = (byte*)allocFunc(width * height * format.bytesPerPixel + 15);
-	if (!buf)
-		return buf;
+	surface.setPixels((void *)(((uintptr)pixelsUnaligned + sizeof(uintptr) + ALIGN - 1) & (-ALIGN)));
 
-	byte *bufAligned = (byte*)(((uintptr)buf + 15) & 0xfffffff0);
-	memset(bufAligned, 0, width * height * format.bytesPerPixel);
+	// store the unaligned pointer for later free()
+	*((uintptr *)surface.getPixels() - 1) = (uintptr)pixelsUnaligned;
 
-	surface.init(width, height, width * format.bytesPerPixel, bufAligned, format);
-	return buf;
+	memset(surface.getPixels(), 0, surface.h * surface.pitch);
 }
 
-void AtariGraphicsManager::freeAtariSurface(byte *ptr, Graphics::Surface &surface,
-		const AtariMemFree &freeFunc) {
-	surface = Graphics::Surface();
-	freeFunc(ptr);
+void AtariGraphicsManager::freeAtariSurface(byte *ptr, const AtariMemFree &freeFunc) {
+	freeFunc((void *)*((uintptr *)ptr - 1));
 }
 
-void AtariGraphicsManager::handleModifiedRect(
-		const Graphics::Surface &surface,
-		Common::Rect rect, Common::Array<Common::Rect> &rects) const {
-	if (_currentState.mode == GraphicsMode::SingleBuffering || isOverlayVisible())
-		alignRect(surface, rect);
+void AtariGraphicsManager::addDirtyRect(const Graphics::Surface &surface,
+		DirtyRects &rects, Common::Rect rect) const {
+	alignRect(surface, rect);
 
 	if (rect.width() == surface.w && rect.height() == surface.h) {
-		//debug("handleModifiedRect: purge");
+		//debug("addDirtyRect: purge");
 
 		rects.clear();
 		rects.push_back(rect);
@@ -910,20 +819,24 @@ void AtariGraphicsManager::handleModifiedRect(
 	}
 
 	for (const Common::Rect &r : rects) {
-		if (r.contains(rect))
+		if (r.contains(rect)) {
 			return;
+		}
 	}
 
+	// TODO: what is r.rect contains some rect from rects => delete that rect instead
+	// (it is costly in Common::Array...)
+
 	rects.push_back(rect);
 }
 
-void AtariGraphicsManager::Cursor::update(const Graphics::Surface &screen) {
+void AtariGraphicsManager::Cursor::update(const Graphics::Surface &screen, bool isModified) {
 	if (!surface.getPixels()) {
 		outOfScreen = true;
 		return;
 	}
 
-	if (!visible || (!surfaceChanged && !positionChanged))
+	if (!visible || !isModified)
 		return;
 
 	srcRect = Common::Rect(surface.w, surface.h);
@@ -953,8 +866,6 @@ void AtariGraphicsManager::Cursor::updatePosition(int deltaX, int deltaY, const
 		y = 0;
 	else if (y >= screen.h)
 		y = screen.h - 1;
-
-	positionChanged = true;
 }
 
 void AtariGraphicsManager::Cursor::setSurface(const void *buf, int w, int h, int hotspotX_, int hotspotY_, uint32 keycolor_,
@@ -973,6 +884,4 @@ void AtariGraphicsManager::Cursor::setSurface(const void *buf, int w, int h, int
 	hotspotX = hotspotX_;
 	hotspotY = hotspotY_;
 	keycolor = keycolor_;
-
-	surfaceChanged = true;
 }
diff --git a/backends/graphics/atari/atari-graphics.h b/backends/graphics/atari/atari-graphics.h
index f5e40ad9d81..04017d71089 100644
--- a/backends/graphics/atari/atari-graphics.h
+++ b/backends/graphics/atari/atari-graphics.h
@@ -25,16 +25,12 @@
 #include "backends/graphics/graphics.h"
 
 #include <mint/osbind.h>
+#include <vector>
 
-#include "common/array.h"
 #include "common/events.h"
 #include "common/rect.h"
 #include "graphics/surface.h"
 
-// maximum screen dimensions
-constexpr int SCREEN_WIDTH = 640;
-constexpr int SCREEN_HEIGHT = 480;
-
 class AtariGraphicsManager : public GraphicsManager, Common::EventObserver {
 public:
 	AtariGraphicsManager();
@@ -108,8 +104,6 @@ protected:
 	void allocateSurfaces();
 	void freeSurfaces();
 
-	bool _vgaMonitor = true;
-
 	enum class GraphicsMode : int {
 		DirectRendering,
 		SingleBuffering,
@@ -118,29 +112,24 @@ protected:
 	};
 
 	struct GraphicsState {
-		bool operator==(const GraphicsState &other) const {
-			return mode == other.mode
-				&& width == other.width
-				&& height == other.height
-				&& format == other.format;
-		}
-		bool operator!=(const GraphicsState &other) const {
-			return !(*this == other);
-		}
-
 		GraphicsMode mode;
 		int width;
 		int height;
 		Graphics::PixelFormat format;
+		bool vsync;
 	};
 	GraphicsState _pendingState = {};
 
-	static const int SCREENS = 3;
-	static const int FRONT_BUFFER = 0;
-	static const int BACK_BUFFER1 = 1;
-	static const int BACK_BUFFER2 = 2;
-
 private:
+	enum {
+		// maximum screen dimensions
+		SCREEN_WIDTH = 640,
+		SCREEN_HEIGHT = 480
+	};
+
+	// use std::vector as its clear() doesn't reset capacity
+	using DirtyRects = std::vector<Common::Rect>;
+
 	enum CustomEventAction {
 		kActionToggleAspectRatioCorrection = 100,
 	};
@@ -148,30 +137,55 @@ private:
 	void setVidelResolution() const;
 	void waitForVbl() const;
 
-	bool updateOverlay();
-	bool updateDirectBuffer();
-	bool updateSingleBuffer();
-	bool updateDoubleAndTripleBuffer();
+	bool updateDirect();
+	bool updateBuffered(const Graphics::Surface &srcSurface, Graphics::Surface &dstSurface, const DirtyRects &dirtyRects);
 
-	byte *allocateAtariSurface(Graphics::Surface &surface,
+	void allocateAtariSurface(Graphics::Surface &surface,
 							  int width, int height, const Graphics::PixelFormat &format,
 							  const AtariMemAlloc &allocFunc);
 
-	void freeAtariSurface(byte *ptr, Graphics::Surface &surface, const AtariMemFree &freeFunc);
+	void freeAtariSurface(byte *ptr, const AtariMemFree &freeFunc);
 
 	virtual void copyRectToSurface(Graphics::Surface &dstSurface,
 								   const Graphics::Surface &srcSurface, int destX, int destY,
-								   const Common::Rect &subRect) const = 0;
+								   const Common::Rect &subRect) const {
+		dstSurface.copyRectToSurface(srcSurface, destX, destY, subRect);
+	}
 	virtual void copyRectToSurfaceWithKey(Graphics::Surface &dstSurface, const Graphics::Surface &bgSurface,
 										  const Graphics::Surface &srcSurface, int destX, int destY,
-										  const Common::Rect &subRect, uint32 key, const byte srcPalette[256*3]) const = 0;
+										  const Common::Rect &subRect, uint32 key, const byte srcPalette[256*3]) const {
+		dstSurface.copyRectToSurfaceWithKey(srcSurface, destX, destY, subRect, key);
+	}
 	virtual void alignRect(const Graphics::Surface &srcSurface, Common::Rect &rect) const {}
 
-	void handleModifiedRect(const Graphics::Surface &surface, Common::Rect rect, Common::Array<Common::Rect> &rects) const;
+	void addDirtyRect(const Graphics::Surface &surface, DirtyRects &rects, Common::Rect rect) const;
+
+	void cursorPositionChanged() {
+		if (_overlayVisible) {
+			_buffer[OVERLAY_BUFFER]->cursorPositionChanged = true;
+		} else {
+			_buffer[FRONT_BUFFER]->cursorPositionChanged
+				= _buffer[BACK_BUFFER1]->cursorPositionChanged
+				= _buffer[BACK_BUFFER2]->cursorPositionChanged
+				= true;
+		}
+	}
+
+	void cursorSurfaceChanged() {
+		if (_overlayVisible) {
+			_buffer[OVERLAY_BUFFER]->cursorSurfaceChanged = true;
+		} else {
+			_buffer[FRONT_BUFFER]->cursorSurfaceChanged
+				= _buffer[BACK_BUFFER1]->cursorSurfaceChanged
+				= _buffer[BACK_BUFFER2]->cursorSurfaceChanged
+				= true;
+		}
+	}
 
+	bool _vgaMonitor = true;
 	bool _aspectRatioCorrection = false;
 	bool _oldAspectRatioCorrection = false;
-	bool _vsync = true;
+	bool _guiVsync = true;
 
 	GraphicsState _currentState = {};
 
@@ -183,28 +197,46 @@ private:
 	};
 	int _pendingScreenChange = kPendingScreenChangeNone;
 
-	byte *_screen[SCREENS] = {};	// for Mfree() purposes only
-	byte *_screenAligned[SCREENS] = {};
-	Graphics::Surface _screenSurface;
-	Common::Rect _modifiedScreenRect;	// direct rendering only
-	bool _screenModified = false;	// double/triple buffering only
+	enum {
+		FRONT_BUFFER,
+		BACK_BUFFER1,
+		BACK_BUFFER2,
+		OVERLAY_BUFFER,
+		BUFFER_COUNT
+	};
+
+	struct ScreenInfo {
+		ScreenInfo(byte *p_)
+			: p(p_) {
+		}
+
+		void reset() {
+			cursorPositionChanged = true;
+			cursorSurfaceChanged = false;
+			dirtyRects.clear();
+			oldCursorRect = Common::Rect();
+		}
+
+		byte *p;
+		bool cursorPositionChanged = true;
+		bool cursorSurfaceChanged = false;
+		DirtyRects dirtyRects = DirtyRects(100);	// reserve 100 rects
+		Common::Rect oldCursorRect;
+	};
+	ScreenInfo *_buffer[BUFFER_COUNT] = {};
+	ScreenInfo *_workScreen = nullptr;
+	ScreenInfo *_oldWorkScreen = nullptr;	// used in hideOverlay()
 
+	Graphics::Surface _screenSurface;
+	Common::Rect _dirtyScreenRect;	// direct rendering only
 	Graphics::Surface _chunkySurface;
-	Common::Array<Common::Rect> _modifiedChunkyRects;
 
-	byte *_overlayScreen = nullptr;	// for Mfree() purposes only
 	Graphics::Surface _screenOverlaySurface;
 	bool _overlayVisible = false;
-
 	Graphics::Surface _overlaySurface;
-	Common::Array<Common::Rect> _modifiedOverlayRects;
 
 	struct Cursor {
-		void update(const Graphics::Surface &screen);
-
-		bool isModified() const {
-			return surfaceChanged || positionChanged;
-		}
+		void update(const Graphics::Surface &screen, bool isModified);
 
 		bool visible = false;
 
@@ -212,19 +244,11 @@ private:
 		Common::Point getPosition() const {
 			return Common::Point(x, y);
 		}
-		void setPosition(int x_, int y_, bool override = false) {
-			if (x == x_ && y == y_)
-				return;
-
-			if (!visible && !override)
-				return;
-
+		void setPosition(int x_, int y_) {
 			x = x_;
 			y = y_;
-			positionChanged = true;
 		}
 		void updatePosition(int deltaX, int deltaY, const Graphics::Surface &screen);
-		bool positionChanged = false;
 		void swap() {
 			const int tmpX = oldX;
 			const int tmpY = oldY;
@@ -234,13 +258,10 @@ private:
 
 			x = tmpX;
 			y = tmpY;
-
-			positionChanged = false;
 		}
 
 		// surface
 		void setSurface(const void *buf, int w, int h, int _hotspotX, int _hotspotY, uint32 _keycolor, const Graphics::PixelFormat &format);
-		bool surfaceChanged = false;
 		Graphics::Surface surface;
 		uint32 keycolor;
 
@@ -260,8 +281,6 @@ private:
 		int hotspotY;
 	} _cursor;
 
-	Common::Rect _oldCursorRect;
-
 	byte _palette[256*3] = {};
 	byte _overlayPalette[256*3] = {};
 };
diff --git a/backends/platform/atari/readme.txt b/backends/platform/atari/readme.txt
index de0ab0788ec..d2fc0095be5 100644
--- a/backends/platform/atari/readme.txt
+++ b/backends/platform/atari/readme.txt
@@ -66,8 +66,6 @@ less hungry games even a CT2/DFB at 50 MHz or the AfterBurner040 could be enough).
 - Support for PC keys (page up, page down, pause, F11/F12, ...) and mouse wheel
   (Eiffel/Aranym only)
 
-- Still without any assembly optimizations...
-
 This makes such games as The Curse of Monkey Island better playable (on
 SuperVidel nearly always also with CD (WAV) music and speech). Also, AdLib
 emulation works nicely with many games without noticeable slow downs.
@@ -153,14 +151,11 @@ Pros:
 
 - second fastest possible rendering
 
-- doesn't update the whole screen (works best with a moderate amount of
-  rectangles to update)
-
 Cons:
 
 - screen tearing in most cases
 
-- if there are too many smaller rectangles, it can be less efficient than
+- if there is too many smaller rectangles, it can be less efficient than
   updating the whole buffer at once
 
 SuperBlitter used: yes, for rectangle blitting to screen and cursor restoration.
@@ -185,9 +180,8 @@ Pros:
 
 Cons:
 
-- since two buffers are present, the buffer is always blitted into the screen
-  surface as whole, even if only one tiny little rectangle is changed (excluding
-  the cursor)
+- if there is too many smaller rectangles, it can be less efficient than
+  single buffering
 
 - frame rate is set to 60/30/15/etc FPS so you can see big irregular jumps
   between 30 and 15 FPS for example; this is happening when screen updates take
@@ -202,15 +196,14 @@ Triple buffering:
 ~~~~~~~~~~~~~~~~~
 
 Best of both worlds - screen tearing is avoided thanks to using of multiple
-buffers and the rendering pipeline doesn't have to wait until Vsync(). The vsync
-flag is used only to differentiate between two (very similar) modes of
-operation:
-
-1. "True triple buffering" as described in
-https://en.wikipedia.org/wiki/Multiple_buffering#Triple_buffering (vsync on)
+buffers and the rendering pipeline doesn't have to wait until Vsync() (therefore
+this flag is ignored).
 
-2. "Swap chain" as described in https://en.wikipedia.org/wiki/Swap_chain (vsync
-off)
+Please note that Atari backend uses "true" triple buffering as described in
+https://en.wikipedia.org/wiki/Multiple_buffering#Triple_buffering and not "swap
+chain" as described in https://en.wikipedia.org/wiki/Swap_chain. The latter
+would be slightly slower as three buffers would need to be updated instead of
+two.
 
 Pros:
 
@@ -220,23 +213,19 @@ Pros:
 
 Cons:
 
-- since three buffers are present, the buffer is always blitted into the screen
-  surface as whole, even if only one tiny little rectangle is changed (excluding
-  the cursor)
+- if there is too many smaller rectangles, it can be less efficient than
+  single buffering
 
 - slightly irregular frame rate (depends solely on the game's complexity)
 
-- in case of extremely fast rendering in 1.), one or more buffers are
-  dropped in favor of showing only the most recent one (unlikely)
-
-- in case of extremely fast rendering in 2.), screen tearing is possible
-  because the rendering pipeline starts overwriting the buffer which is
-  currently displayed (unlikely)
+- in case of extremely fast rendering, one or more frames are dropped in favor
+  of showing only the most recent one (unlikely; double buffer guaranties that
+  every frame is shown, no matter how insignificant)
 
 SuperBlitter used: yes, for rectangle blitting to screen and cursor restoration.
 Sometimes also for generic copying between buffers (see above).
 
-Triple buffering with vsync on is the default mode for this port.
+Triple buffering is the default mode for this port.
 
 
 SuperVidel and SuperBlitter
@@ -267,18 +256,19 @@ to avoid unpleasant playing experiences.
 Game engines with unexpected performance hit
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-A typical example from this category is the Gobliiins engine (and its
-sequels). At first it looks like our machine / backend is doing something
-terribly wrong but the truth is it is the engine itself which is doing a lot of
-unnecessary redraws and updates, sometimes even before reaching the backend.
-The only real solution is to profile and fix the engine.
+A typical example from this category is Gobliiins (and its sequels) and SCI
+engine games (Gabriel Knight, Larry 2/7, ...). At first it looks like our
+machine or Atari backend is doing something terribly wrong but the truth is
+that it is the engine itself which is doing a lot of unnecessary redraws and
+updates, sometimes even before reaching the backend. The only solution is to
+profile and fix those engines.
 
 Too many fullscreen updates
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Somewhat related to the previous point - sometimes the engine authors didn't
 realize the impact of every update on the overall performance and instead of
-updating only the rectangles that really had changed, they ask for a full screen
+updating only the rectangles that really had changed, they ask for a fullscreen
 update. Not a problem on a >1 GHz machine but very visible on Atari! Also, this
 is (by definition) the case of animated intros, especially those in 640x480.
 
@@ -289,7 +279,7 @@ It could seem that sample music replay must be the most demanding one but on the
 contrary! _Always_ choose a CD version of a game (with *.wav tracks) to any
 other version. With one exception: if you have a native MIDI device able to
 replay the given game's MIDI notes (using the STMIDI plugin). MIDI emulation
-(synthesis) can easily take down as many as 10 FPS.
+(synthesis) can easily eat as much as 50% of all used CPU time.
 
 CD music slows everything down
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -332,9 +322,6 @@ rendering by 10-15 FPS if not used with caution. That happens if a game takes,
 say, 1.2 frames per update (so causing screen tearing anyway and rendering the
 option useless) but Vsync() forces it to wait 2 full frames instead.
 
-By the way, the vsync flag in Global Options affects also the overlay rendering
-(with all the pitfalls which apply to the single buffering mode)
-
 Slow GUI
 ~~~~~~~~
 
@@ -373,23 +360,16 @@ Future plans
 
 - unified file paths in scummvm.ini
 
-- profiling :) (see also https://github.com/scummvm/scummvm/pull/2382)
-
 - DSP-based sample mixer
 
 - avoid loading music/speech files (and thus slowing down everything) if muted
 
-- assembly copy routines when SuperVidel is not present or can't be used
-
 - cached audio/video streams (i.e. don't load only "output_samples" number of
   samples but cache, say, 1 second so disk i/o wont be so stressed)
 
 - using LDG or Thorsten Otto's sharedlibs: https://tho-otto.de/sharedlibs.php
   for game engine plugins to relieve the huge binary size
 
-- reuse modified rects in double/triple buffer in the next frame - that way we
-  wouldn't need to refresh the whole screen in every case
-
 - add support for the TT030; this would be easily possible when I rewrite the
   renderer with a more flexible resolution switching
 
@@ -408,14 +388,15 @@ Future plans
 
 - C2P could support 4- and 6-bit depth
 
+- increase file buffer size with setvbuf or by using
+  Common::wrapBufferedWriteStream and disabling stdio buffering
+
 
 Closing words
 -------------
 
-I have opened a pull request with all of my code
-(https://github.com/scummvm/scummvm/pull/4687) so who knows, maybe ScummVM
-2.8.0 for Atari will be already present on the official website. :-)
-
+This backend is part of ScummVM 2.8.0 onwards. Let's see whether we can make it
+to the official website. :-)
 
 MiKRO / Mystic Bytes, XX.XX.2023
 Kosice / Slovakia


Commit: 0e2cb19de787a8cc2e41a22960d22250b253df28
    https://github.com/scummvm/scummvm/commit/0e2cb19de787a8cc2e41a22960d22250b253df28
Author: Miro Kropacek (miro.kropacek at gmail.com)
Date: 2023-03-25T21:45:50+01:00

Commit Message:
ATARI: Use dlmalloc for final release

Changed paths:
    configure


diff --git a/configure b/configure
index 3ff1c91b508..875762c9ff7 100755
--- a/configure
+++ b/configure
@@ -3673,7 +3673,9 @@ if test -n "$_host"; then
 			# auto -> no
 			if test "$_release_build" = "yes"; then
 				# --enable-release
-				append_var DEFINES "-DNDEBUG"
+				append_var DEFINES  "-DNDEBUG"
+				append_var CXXFLAGS "-I$HOME/gnu-tools/m68000/m68k-atari-mint/sys-root/opt/mintlib-dlmalloc/include"
+				append_var LDFLAGS  "-L$HOME/gnu-tools/m68000/m68k-atari-mint/sys-root/opt/mintlib-dlmalloc/lib/m68020-60"
 			fi
 
 			_seq_midi=no




More information about the Scummvm-git-logs mailing list