[Scummvm-git-logs] scummvm master -> 2ab9ba353a2d877bb43dcb2c83cba687d75ec03c

mikrosk noreply at scummvm.org
Sat Aug 12 15:37:11 UTC 2023


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

Summary:
7e3b9cceec BACKENDS: ATARI: Direct rendering support
18dafa1742 BACKENDS: ATARI: Disable director & remove unused dat files
5a756fe2f4 BACKENDS: ATARI: Restore key click
c1354b8589 BACKENDS: ATARI: Fix crash when exit() is called
f8f22c6e85 BACKENDS: ATARI: Fix a stuck key repeat
87f1cd25be BACKENDS: ATARI: Don't initialize GEM before initBackend()
46939cca9b BACKENDS: ATARI: Fix regression in unlockScreen()
2ab9ba353a BACKENDS: ATARI: Remove the SCI hack


Commit: 7e3b9cceec9a263063227183b49e81c45443ca45
    https://github.com/scummvm/scummvm/commit/7e3b9cceec9a263063227183b49e81c45443ca45
Author: Miro Kropacek (miro.kropacek at gmail.com)
Date: 2023-08-12T17:38:49+02:00

Commit Message:
BACKENDS: ATARI: Direct rendering support

Similar to SuperVidel's but requires an additional C2P pass.

Also, a few optimizations added like cursor is being assumed to be in
a persistent memory so we don't copy it over. And it's using a mask
instead of key.

This graphics mode is not perfect though and works reliably only with
source surfaces allocated via Surface::init() otherwise wrong memory
location is being read.

Still, can gain quite a few CPU cycles, especially on 640x480 screens.

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/platform/atari/readme.txt


diff --git a/backends/graphics/atari/atari-graphics-asm.S b/backends/graphics/atari/atari-graphics-asm.S
index 733aadcdbb8..8629fc27f14 100644
--- a/backends/graphics/atari/atari-graphics-asm.S
+++ b/backends/graphics/atari/atari-graphics-asm.S
@@ -1,5 +1,5 @@
 /* ScummVM - Graphic Adventure Engine
-*
+ *
  * ScummVM is the legal property of its developers, whose names
  * are too numerous to list here. Please refer to the COPYRIGHT
  * file distributed with this source distribution.
@@ -25,6 +25,9 @@
 	.global	_asm_screen_tt_restore
 	.global	_asm_screen_falcon_restore
 
+	.global	_asm_draw_4bpl_sprite
+	.global	_asm_draw_8bpl_sprite
+
 	.text
 
 | extern void asm_screen_tt_save(void);
@@ -182,6 +185,226 @@ wait_vbl:
 	addq.l	#2,sp				|
 	rts
 
+| extern void asm_draw_4bpl_sprite(uint16 *dstBuffer, const uint16 *srcBuffer, const uint16 *srcMask,
+|				   uint destX, uint destY, uint dstPitch, uint w, uint h);
+|
+_asm_draw_4bpl_sprite:
+	movem.l	d0-d7/a0-a2,-(sp)		| 11 longs
+
+	move.l	(4+11*4,sp),a2			| a2: dstBuffer
+	move.l	(8+11*4,sp),a1			| a1: srcBuffer
+	move.l	(12+11*4,sp),a0			| a0: srcMask
+	move.l	(16+11*4,sp),d0			| d0.w: destX
+	move.l	(20+11*4,sp),d1			| d1.w: destY
+	move.l	(24+11*4,sp),d3			| d3.w: dstPitch
+	ext.l	d3				| d3.l: dstPitch
+	move.l	(28+11*4,sp),d6			| d6.w: w
+	lsr.w	#4,d6				| d6.w: w/16
+	move.l	(32+11*4,sp),d7			| d7.w: h
+
+| Draws a 4 bitplane sprite at any position on screen.
+| (c) 1999 Pieter van der Meer (EarX)
+
+| INPUT: d0.w: x position of sprite on screen (left side)
+|        d1.w: y position of sprite on screen (top side)
+|        d6.w: number of 16pixel X blocks to do
+|        d7.w: number of Y lines to to
+|        a0: address of maskdata
+|        a1: address of bitmapdata
+|        a2: screen start address
+
+	move.w  d0,d2				| / Calculate the
+	andi.w  #0b111111110000,d0		| | number of bits
+	sub.w   d0,d2				| \ to shift right.
+	lsr.w   #1,d0				| / Add x-position to
+	adda.w  d0,a2				| \ screenaddress.
+	mulu.w  d3,d1				| / Add y-position to
+	adda.l  d1,a2				| \ screenaddress.
+	move.w  d6,d1				| / Prepare
+	lsl.w   #3,d1				| | offset
+	move.l  d3,d4				| | to next
+	sub.w   d1,d4				| \ screenline.
+	subq.w  #1,d7				| Adjust for dbra.
+	subq.w  #1,d6				| Adjust for dbra.
+	move.w  d6,d5				| Backup xloopcount in d5.w.
+	moveq   #16,d1				| Size of two chunks.
+
+sprite4_yloop:
+	move.w  d5,d6				| Restore xloop counter.
+
+sprite4_xloop:
+	moveq   #0xffffffff,d0			| Prepare for maskshifting.
+	move.w  (a0)+,d0			| Get 16pixel mask in d0.w.
+	ror.l   d2,d0				| Shift it!
+	and.w   d0,(a2)+			| Mask bitplane 0.
+	and.w   d0,(a2)+			| Mask bitplane 1.
+	and.w   d0,(a2)+			| Mask bitplane 2.
+	and.w   d0,(a2)+			| Mask bitplane 3.
+	swap    d0				| Get overspill in loword.
+	and.w   d0,(a2)+			| Mask overspill bitplane 0.
+	and.w   d0,(a2)+			| Mask overspill bitplane 1.
+	and.w   d0,(a2)+			| Mask overspill bitplane 2.
+	and.w   d0,(a2)+			| Mask overspill bitplane 3.
+	suba.l  d1,a2				| Return to blockstart.
+
+	moveq   #0,d0				| Prepare for bitmapshifting.
+	move.w  (a1)+,d0			| Get bitplaneword in d0.w.
+	ror.l   d2,d0				| Shift it.
+	or.w    d0,(a2)+			| Paint bitplane 0.
+	swap    d0				| Get overspill in loword.
+	or.w    d0,6(a2)			| Paint overspill bitplane 0.
+
+	moveq   #0,d0				| Prepare for bitmapshifting.
+	move.w  (a1)+,d0			| Get bitplaneword in d0.w.
+	ror.l   d2,d0				| Shift it.
+	or.w    d0,(a2)+			| Paint bitplane 1.
+	swap    d0				| Get overspill in loword.
+	or.w    d0,6(a2)			| Paint overspill bitplane 1.
+
+	moveq   #0,d0				| Prepare for bitmapshifting.
+	move.w  (a1)+,d0			| Get bitplaneword in d0.w.
+	ror.l   d2,d0				| Shift it.
+	or.w    d0,(a2)+			| Paint bitplane 2.
+	swap    d0				| Get overspill in loword.
+	or.w    d0,6(a2)			| Paint overspill bitplane 2.
+
+	moveq   #0,d0				| Prepare for bitmapshifting.
+	move.w  (a1)+,d0			| Get bitplaneword in d0.w.
+	ror.l   d2,d0				| Shift it.
+	or.w    d0,(a2)+			| Paint bitplane 3.
+	swap    d0				| Get overspill in loword.
+	or.w    d0,6(a2)			| Paint overspill bitplane 3.
+
+	dbra    d6,sprite4_xloop		| Loop until blocks done.
+
+	adda.l  d4,a2				| Goto next screenline.
+	dbra    d7,sprite4_yloop		| Loop until lines done.
+
+	movem.l	(sp)+,d0-d7/a0-a2
+	rts
+
+| extern void asm_draw_8bpl_sprite(uint16 *dstBuffer, const uint16 *srcBuffer, const uint16 *srcMask,
+|				   uint destX, uint destY, uint dstPitch, uint w, uint h);
+|
+_asm_draw_8bpl_sprite:
+	movem.l	d0-d7/a0-a2,-(sp)		| 11 longs
+
+	move.l	(4+11*4,sp),a2			| a2: dstBuffer
+	move.l	(8+11*4,sp),a1			| a1: srcBuffer
+	move.l	(12+11*4,sp),a0			| a0: srcMask
+	move.l	(16+11*4,sp),d0			| d0.w: destX
+	move.l	(20+11*4,sp),d1			| d1.w: destY
+	move.l	(24+11*4,sp),d3			| d3.w: dstPitch
+	ext.l	d3				| d3.l: dstPitch
+	move.l	(28+11*4,sp),d6			| d6.w: w
+	lsr.w	#4,d6				| d6.w: w/16
+	move.l	(32+11*4,sp),d7			| d7.w: h
+
+	move.w  d0,d2				| / Calculate the
+	andi.w  #0b111111110000,d0		| | number of bits
+	sub.w   d0,d2				| \ to shift right.
+	adda.w  d0,a2				| Add x-position to screenaddress.
+	mulu.w  d3,d1				| / Add y-position to
+	adda.l  d1,a2				| \ screenaddress.
+	move.w  d6,d1				| / Prepare
+	lsl.w   #4,d1				| | offset
+	move.l  d3,d4				| | to next
+	sub.w   d1,d4				| \ screenline.
+	subq.w  #1,d7				| Adjust for dbra.
+	subq.w  #1,d6				| Adjust for dbra.
+	move.w  d6,d5				| Backup xloopcount in d5.w.
+	moveq   #32,d1				| Size of two chunks.
+
+sprite8_yloop:
+	move.w  d5,d6				| Restore xloop counter.
+
+sprite8_xloop:
+	moveq   #0xffffffff,d0			| Prepare for maskshifting.
+	move.w  (a0)+,d0			| Get 16pixel mask in d0.w.
+	ror.l   d2,d0				| Shift it!
+	and.w   d0,(a2)+			| Mask bitplane 0.
+	and.w   d0,(a2)+			| Mask bitplane 1.
+	and.w   d0,(a2)+			| Mask bitplane 2.
+	and.w   d0,(a2)+			| Mask bitplane 3.
+	and.w   d0,(a2)+			| Mask bitplane 4.
+	and.w   d0,(a2)+			| Mask bitplane 5.
+	and.w   d0,(a2)+			| Mask bitplane 6.
+	and.w   d0,(a2)+			| Mask bitplane 7.
+	swap    d0				| Get overspill in loword.
+	and.w   d0,(a2)+			| Mask overspill bitplane 0.
+	and.w   d0,(a2)+			| Mask overspill bitplane 1.
+	and.w   d0,(a2)+			| Mask overspill bitplane 2.
+	and.w   d0,(a2)+			| Mask overspill bitplane 3.
+	and.w   d0,(a2)+			| Mask overspill bitplane 4.
+	and.w   d0,(a2)+			| Mask overspill bitplane 5.
+	and.w   d0,(a2)+			| Mask overspill bitplane 6.
+	and.w   d0,(a2)+			| Mask overspill bitplane 7.
+	suba.l  d1,a2				| Return to blockstart.
+
+	moveq   #0,d0				| Prepare for bitmapshifting.
+	move.w  (a1)+,d0			| Get bitplaneword in d0.w.
+	ror.l   d2,d0				| Shift it.
+	or.w    d0,(a2)+			| Paint bitplane 0.
+	swap    d0				| Get overspill in loword.
+	or.w    d0,14(a2)			| Paint overspill bitplane 0.
+
+	moveq   #0,d0				| Prepare for bitmapshifting.
+	move.w  (a1)+,d0			| Get bitplaneword in d0.w.
+	ror.l   d2,d0				| Shift it.
+	or.w    d0,(a2)+			| Paint bitplane 1.
+	swap    d0				| Get overspill in loword.
+	or.w    d0,14(a2)			| Paint overspill bitplane 1.
+
+	moveq   #0,d0				| Prepare for bitmapshifting.
+	move.w  (a1)+,d0			| Get bitplaneword in d0.w.
+	ror.l   d2,d0				| Shift it.
+	or.w    d0,(a2)+			| Paint bitplane 2.
+	swap    d0				| Get overspill in loword.
+	or.w    d0,14(a2)			| Paint overspill bitplane 2.
+
+	moveq   #0,d0				| Prepare for bitmapshifting.
+	move.w  (a1)+,d0			| Get bitplaneword in d0.w.
+	ror.l   d2,d0				| Shift it.
+	or.w    d0,(a2)+			| Paint bitplane 3.
+	swap    d0				| Get overspill in loword.
+	or.w    d0,14(a2)			| Paint overspill bitplane 3.
+
+	moveq   #0,d0				| Prepare for bitmapshifting.
+	move.w  (a1)+,d0			| Get bitplaneword in d0.w.
+	ror.l   d2,d0				| Shift it.
+	or.w    d0,(a2)+			| Paint bitplane 4.
+	swap    d0				| Get overspill in loword.
+	or.w    d0,14(a2)			| Paint overspill bitplane 4.
+
+	moveq   #0,d0				| Prepare for bitmapshifting.
+	move.w  (a1)+,d0			| Get bitplaneword in d0.w.
+	ror.l   d2,d0				| Shift it.
+	or.w    d0,(a2)+			| Paint bitplane 5.
+	swap    d0				| Get overspill in loword.
+	or.w    d0,14(a2)			| Paint overspill bitplane 5.
+
+	moveq   #0,d0				| Prepare for bitmapshifting.
+	move.w  (a1)+,d0			| Get bitplaneword in d0.w.
+	ror.l   d2,d0				| Shift it.
+	or.w    d0,(a2)+			| Paint bitplane 6.
+	swap    d0				| Get overspill in loword.
+	or.w    d0,14(a2)			| Paint overspill bitplane 6.
+
+	moveq   #0,d0				| Prepare for bitmapshifting.
+	move.w  (a1)+,d0			| Get bitplaneword in d0.w.
+	ror.l   d2,d0				| Shift it.
+	or.w    d0,(a2)+			| Paint bitplane 7.
+	swap    d0				| Get overspill in loword.
+	or.w    d0,14(a2)			| Paint overspill bitplane 7.
+
+	dbra    d6,sprite8_xloop		| Loop until blocks done.
+
+	adda.l  d4,a2				| Goto next screenline.
+	dbra    d7,sprite8_yloop		| Loop until lines done.
+
+	movem.l	(sp)+,d0-d7/a0-a2
+	rts
+
 
 	.bss
 	.even
diff --git a/backends/graphics/atari/atari-graphics-asm.h b/backends/graphics/atari/atari-graphics-asm.h
index 93fce38f414..26812443583 100644
--- a/backends/graphics/atari/atari-graphics-asm.h
+++ b/backends/graphics/atari/atari-graphics-asm.h
@@ -44,6 +44,35 @@ void asm_screen_tt_restore(void);
  */
 void asm_screen_falcon_restore(void);
 
+/**
+ * Copy 4bpl sprite into 4bpl buffer. Sprite's width must be multiply of 16.
+ *
+ * @param dstBuffer destination buffer (four bitplanes)
+ * @param srcBuffer source buffer (four bitplanes)
+ * @param srcMask source mask (one bitplane)
+ * @param destX sprite's X position (in pixels)
+ * @param destY sprite's Y position (in pixels)
+ * @param dstPitch destination buffer's pitch (in bytes)
+ * @param w sprite's width (in pixels)
+ * @param h sprite's height (in pixels)
+ */
+void asm_draw_4bpl_sprite(uint16 *dstBuffer, const uint16 *srcBuffer, const uint16 *srcMask,
+						  uint destX, uint destY, uint dstPitch, uint w, uint h);
+/**
+ * Copy 8bpl sprite into 8bpl buffer. Sprite's width must be multiply of 16.
+ *
+ * @param dstBuffer destination buffer (eight bitplanes)
+ * @param srcBuffer source buffer (eight bitplanes)
+ * @param srcMask source mask (one bitplane)
+ * @param destX sprite's X position (in pixels)
+ * @param destY sprite's Y position (in pixels)
+ * @param dstPitch destination buffer's pitch (in bytes)
+ * @param w sprite's width (in pixels)
+ * @param h sprite's height (in pixels)
+ */
+void asm_draw_8bpl_sprite(uint16 *dstBuffer, const uint16 *srcBuffer, const uint16 *srcMask,
+						  uint destX, uint destY, uint dstPitch, uint w, uint h);
+
 }
 
 #endif
diff --git a/backends/graphics/atari/atari-graphics-supervidel.h b/backends/graphics/atari/atari-graphics-supervidel.h
index afa1026b725..ca7816a80a5 100644
--- a/backends/graphics/atari/atari-graphics-supervidel.h
+++ b/backends/graphics/atari/atari-graphics-supervidel.h
@@ -55,16 +55,6 @@ public:
 		freeSurfaces();
 	}
 
-	virtual const OSystem::GraphicsMode *getSupportedGraphicsModes() const override {
-		static const OSystem::GraphicsMode graphicsModes[] = {
-			{"direct", "Direct rendering", (int)GraphicsMode::DirectRendering},
-			{"single", "Single buffering", (int)GraphicsMode::SingleBuffering},
-			{"triple", "Triple buffering", (int)GraphicsMode::TripleBuffering},
-			{nullptr, nullptr, 0 }
-		};
-		return graphicsModes;
-	}
-
 private:
 	AtariMemAlloc getStRamAllocFunc() const override {
 		return [](size_t bytes) {
@@ -80,6 +70,50 @@ private:
 		return [](void *ptr) { Mfree((uintptr)ptr & 0x00FFFFFF); };
 	}
 
+	void drawMaskedSprite(Graphics::Surface &dstSurface, int dstBitsPerPixel,
+						  const Graphics::Surface &srcSurface, const Graphics::Surface &srcMask,
+						  int destX, int destY,
+						  const Common::Rect &subRect) override {
+		assert(dstBitsPerPixel == 8);
+		assert(subRect.width() % 16 == 0);
+		assert(subRect.width() == srcSurface.w);
+
+		const byte *src = (const byte *)srcSurface.getBasePtr(subRect.left, subRect.top);
+		const uint16 *mask = (const uint16 *)srcMask.getBasePtr(subRect.left, subRect.top);
+		byte *dst = (byte *)dstSurface.getBasePtr(destX, destY);
+
+		const int h = subRect.height();
+		const int w = subRect.width();
+		const int dstOffset = dstSurface.pitch - w;
+
+		for (int j = 0; j < h; ++j) {
+			for (int i = 0; i < w; i += 16, mask++) {
+				const uint16 m = *mask;
+
+				if (m == 0xFFFF) {
+					// all 16 pixels transparentm6
+					src += 16;
+					dst += 16;
+					continue;
+				}
+
+				for (int k = 0; k < 16; ++k) {
+					const uint16 bit = 1 << (15 - k);
+
+					if (m & bit) {
+						// transparent
+						src++;
+						dst++;
+					} else {
+						*dst++ = *src++;
+					}
+				}
+			}
+
+			dst += dstOffset;
+		}
+	}
+
 	Common::Rect alignRect(int x, int y, int w, int h) const override {
 		return Common::Rect(x, y, x + w, y + h);
 	}
diff --git a/backends/graphics/atari/atari-graphics-videl.h b/backends/graphics/atari/atari-graphics-videl.h
index 3da10048576..584b699c01f 100644
--- a/backends/graphics/atari/atari-graphics-videl.h
+++ b/backends/graphics/atari/atari-graphics-videl.h
@@ -24,6 +24,7 @@
 
 #include "backends/graphics/atari/atari-graphics.h"
 
+#include "backends/graphics/atari/atari-graphics-asm.h"
 #include "backends/graphics/atari/atari_c2p-asm.h"
 #include "common/system.h"
 
@@ -39,30 +40,6 @@ public:
 		freeSurfaces();
 	}
 
-	virtual const OSystem::GraphicsMode *getSupportedGraphicsModes() const override {
-		static const OSystem::GraphicsMode graphicsModes[] = {
-			{"single", "Single buffering", (int)GraphicsMode::SingleBuffering},
-			{"triple", "Triple buffering", (int)GraphicsMode::TripleBuffering},
-			{nullptr, nullptr, 0 }
-		};
-		return graphicsModes;
-	}
-
-	OSystem::TransactionError endGFXTransaction() override {
-		int error = OSystem::TransactionError::kTransactionSuccess;
-
-		if (_pendingState.mode == GraphicsMode::DirectRendering)
-			error |= OSystem::TransactionError::kTransactionModeSwitchFailed;
-
-		if (error != OSystem::TransactionError::kTransactionSuccess) {
-			// all our errors are fatal but engine.cpp takes only this one seriously
-			error |= OSystem::TransactionError::kTransactionSizeChangeFailed;
-			return static_cast<OSystem::TransactionError>(error);
-		}
-
-		return AtariGraphicsManager::endGFXTransaction();
-	}
-
 private:
 	void copyRectToSurface(Graphics::Surface &dstSurface, int dstBitsPerPixel, const Graphics::Surface &srcSurface,
 						   int destX, int destY,
@@ -108,37 +85,27 @@ private:
 		}
 	}
 
-	void copyRectToSurfaceWithKey(Graphics::Surface &dstSurface, int dstBitsPerPixel, const Graphics::Surface &srcSurface,
-								  int destX, int destY,
-								  const Common::Rect &subRect, uint32 key,
-								  const Graphics::Surface &bgSurface, const byte srcPalette[256*3]) const override {
-		const Common::Rect backgroundRect = alignRect(destX, destY, subRect.width(), subRect.height());
-
-		static Graphics::Surface cachedSurface;
-
-		if (cachedSurface.w != backgroundRect.width()
-				|| cachedSurface.h != backgroundRect.height()
-				|| cachedSurface.format != bgSurface.format) {
-			cachedSurface.create(
-				backgroundRect.width(),
-				backgroundRect.height(),
-				bgSurface.format);
+	void drawMaskedSprite(Graphics::Surface &dstSurface, int dstBitsPerPixel,
+						  const Graphics::Surface &srcSurface, const Graphics::Surface &srcMask,
+						  int destX, int destY,
+						  const Common::Rect &subRect) override {
+		if (dstBitsPerPixel == 4) {
+			asm_draw_4bpl_sprite(
+				(uint16 *)dstSurface.getPixels(), (const uint16 *)srcSurface.getBasePtr(subRect.left, subRect.top),
+				(const uint16 *)srcMask.getBasePtr(subRect.left, subRect.top),
+				destX, destY,
+				dstSurface.pitch, subRect.width(), subRect.height());
+		} else if (dstBitsPerPixel == 8) {
+			asm_draw_8bpl_sprite(
+				(uint16 *)dstSurface.getPixels(), (const uint16 *)srcSurface.getBasePtr(subRect.left, subRect.top),
+				(const uint16 *)srcMask.getBasePtr(subRect.left, subRect.top),
+				destX, destY,
+				dstSurface.pitch, subRect.width(), subRect.height());
 		}
-
-		// copy background
-		cachedSurface.copyRectToSurface(bgSurface, 0, 0, backgroundRect);
-
-		// copy cursor
-		convertRectToSurfaceWithKey(cachedSurface, srcSurface, destX - backgroundRect.left, 0, subRect, key, srcPalette);
-
-		copyRectToSurface(
-			dstSurface, dstBitsPerPixel, cachedSurface,
-			backgroundRect.left, backgroundRect.top,
-			Common::Rect(cachedSurface.w, cachedSurface.h));
 	}
 
 	Common::Rect alignRect(int x, int y, int w, int h) const override {
-		return Common::Rect(x & 0xfff0, y, (x + w + 15) & 0xfff0, y + h);
+		return Common::Rect(x & (-16), y, (x + w + 15) & (-16), y + h);
 	}
 };
 
diff --git a/backends/graphics/atari/atari-graphics.cpp b/backends/graphics/atari/atari-graphics.cpp
index 54e583b19c2..42fa7cd5b8d 100644
--- a/backends/graphics/atari/atari-graphics.cpp
+++ b/backends/graphics/atari/atari-graphics.cpp
@@ -397,23 +397,10 @@ void AtariGraphicsManager::grabPalette(byte *colors, uint start, uint num) const
 void AtariGraphicsManager::copyRectToScreen(const void *buf, int pitch, int x, int y, int w, int h) {
 	//debug("copyRectToScreen: %d, %d, %d(%d), %d", x, y, w, pitch, h);
 
-	Graphics::Surface &dstSurface = *lockScreen();
-
-	const Common::Rect rect = alignRect(x, y, w, h);
-	_workScreen->addDirtyRect(dstSurface, rect);
-
-	if (_currentState.mode == GraphicsMode::TripleBuffering) {
-		_screen[BACK_BUFFER2]->addDirtyRect(dstSurface, rect);
-		_screen[FRONT_BUFFER]->addDirtyRect(dstSurface, rect);
-	}
-
-	// no need to align so far...
-	dstSurface.copyRectToSurface(buf, pitch, x, y, w, h);
-
-	if (_currentState.mode == GraphicsMode::DirectRendering) {
-		// TODO: c2p with 16pix align
-		updateScreen();
-	}
+	copyRectToScreenInternal(buf, pitch, x, y, w, h,
+		PIXELFORMAT_CLUT8,
+		_currentState.mode == GraphicsMode::DirectRendering,
+		_currentState.mode == GraphicsMode::TripleBuffering);
 }
 
 // this is not really locking anything but it's an useful function
@@ -421,12 +408,12 @@ void AtariGraphicsManager::copyRectToScreen(const void *buf, int pitch, int x, i
 Graphics::Surface *AtariGraphicsManager::lockScreen() {
 	//debug("lockScreen");
 
-	if (isOverlayVisible())
-		return &_overlaySurface;	// used also for cursor clipping
-	else if (_currentState.mode != GraphicsMode::DirectRendering)
-		return &_chunkySurface;
-	else
+	if (isOverlayVisible() && !isOverlayDirectRendering())
+		return &_overlaySurface;
+	else if ((isOverlayVisible() && isOverlayDirectRendering()) || _currentState.mode == GraphicsMode::DirectRendering)
 		return _workScreen->offsettedSurf;
+	else
+		return &_chunkySurface;
 }
 
 void AtariGraphicsManager::unlockScreen() {
@@ -434,12 +421,13 @@ void AtariGraphicsManager::unlockScreen() {
 
 	const Graphics::Surface &dstSurface = *lockScreen();
 
+	const bool directRendering = (dstSurface.getPixels() != _chunkySurface.getPixels());
 	const Common::Rect rect = alignRect(0, 0, dstSurface.w, dstSurface.h);
-	_workScreen->addDirtyRect(dstSurface, rect);
+	_workScreen->addDirtyRect(dstSurface, rect, directRendering);
 
 	if (_currentState.mode == GraphicsMode::TripleBuffering) {
-		_screen[BACK_BUFFER2]->addDirtyRect(dstSurface, rect);
-		_screen[FRONT_BUFFER]->addDirtyRect(dstSurface, rect);
+		_screen[BACK_BUFFER2]->addDirtyRect(dstSurface, rect, directRendering);
+		_screen[FRONT_BUFFER]->addDirtyRect(dstSurface, rect, directRendering);
 	}
 
 	updateScreen();
@@ -497,7 +485,10 @@ void AtariGraphicsManager::updateScreen() {
 
 	if (isOverlayVisible()) {
 		assert(_workScreen == _screen[OVERLAY_BUFFER]);
-		screenUpdated = updateScreenInternal<false>(_overlaySurface);
+		if (isOverlayDirectRendering())
+			screenUpdated = updateScreenInternal<true>(Graphics::Surface());
+		else
+			screenUpdated = updateScreenInternal<false>(_overlaySurface);
 	} else {
 		switch (_currentState.mode) {
 		case GraphicsMode::DirectRendering:
@@ -684,12 +675,14 @@ Graphics::PixelFormat AtariGraphicsManager::getOverlayFormat() const {
 }
 
 void AtariGraphicsManager::clearOverlay() {
+	if (isOverlayDirectRendering())
+		return;
+
 	debug("clearOverlay");
 
 	if (!_overlayVisible)
 		return;
 
-#ifndef DISABLE_FANCY_THEMES
 	const Graphics::Surface &sourceSurface =
 		_currentState.mode == GraphicsMode::DirectRendering ? *_screen[FRONT_BUFFER]->offsettedSurf : _chunkySurface;
 
@@ -701,7 +694,8 @@ void AtariGraphicsManager::clearOverlay() {
 	const int hzOffset = (_overlaySurface.w - w) / 2;
 	const int vOffset  = (_overlaySurface.h - h) / 2;
 
-	const int pitch = hzOffset * 2 + (upscale ? _overlaySurface.pitch : 0);
+	const int srcPadding = sourceSurface.pitch - sourceSurface.w;
+	const int dstPadding = hzOffset * 2 + (upscale ? _overlaySurface.pitch : 0);
 
 	// Transpose from game palette to RGB332/RGB121 (overlay palette)
 	const byte *src = (const byte*)sourceSurface.getPixels();
@@ -745,7 +739,8 @@ void AtariGraphicsManager::clearOverlay() {
 			*dst++ = pixel;
 		}
 
-		dst += pitch;
+		src += srcPadding;
+		dst += dstPadding;
 	}
 
 	// top rect
@@ -757,40 +752,33 @@ void AtariGraphicsManager::clearOverlay() {
 	// right rect
 	_overlaySurface.fillRect(Common::Rect(_overlaySurface.w - hzOffset, vOffset, _overlaySurface.w, _overlaySurface.h - vOffset), 0);
 
-	_screen[OVERLAY_BUFFER]->addDirtyRect(_overlaySurface, Common::Rect(_overlaySurface.w, _overlaySurface.h));
-#else
-	// for cursor background and non-aligned rects
-	memset(_overlaySurface.getPixels(), 0, _overlaySurface.h * _overlaySurface.pitch);
-#endif
+	_screen[OVERLAY_BUFFER]->addDirtyRect(_overlaySurface, Common::Rect(_overlaySurface.w, _overlaySurface.h), false);
 }
 
 void AtariGraphicsManager::grabOverlay(Graphics::Surface &surface) const {
 	debug("grabOverlay: %d(%d), %d", surface.w, surface.pitch, surface.h);
 
-	assert(surface.w >= _overlaySurface.w);
-	assert(surface.h >= _overlaySurface.h);
-	assert(surface.format.bytesPerPixel == _overlaySurface.format.bytesPerPixel);
-
-#ifndef DISABLE_FANCY_THEMES
-	const byte *src = (const byte *)_overlaySurface.getPixels();
-	byte *dst = (byte *)surface.getPixels();
-	Graphics::copyBlit(dst, src, surface.pitch,
-		_overlaySurface.pitch, _overlaySurface.w, _overlaySurface.h, _overlaySurface.format.bytesPerPixel);
-#else
-	// assumes that the first dirty rect in copyRectToOverlay is always fullscreen
-	memset(surface.getPixels(), 0, surface.h * surface.pitch);
-#endif
+	if (isOverlayDirectRendering()) {
+		memset(surface.getPixels(), 0, surface.h * surface.pitch);
+	} else {
+		assert(surface.w >= _overlaySurface.w);
+		assert(surface.h >= _overlaySurface.h);
+		assert(surface.format.bytesPerPixel == _overlaySurface.format.bytesPerPixel);
+
+		const byte *src = (const byte *)_overlaySurface.getPixels();
+		byte *dst = (byte *)surface.getPixels();
+		Graphics::copyBlit(dst, src, surface.pitch,
+			_overlaySurface.pitch, _overlaySurface.w, _overlaySurface.h, _overlaySurface.format.bytesPerPixel);
+	}
 }
 
 void AtariGraphicsManager::copyRectToOverlay(const void *buf, int pitch, int x, int y, int w, int h) {
 	//debug("copyRectToOverlay: %d, %d, %d(%d), %d", x, y, w, pitch, h);
 
-	Graphics::Surface *dstSurface = lockScreen();
-
-	const Common::Rect rect = alignRect(x, y, w, h);
-	_workScreen->addDirtyRect(*dstSurface, rect);
-
-	dstSurface->copyRectToSurface(buf, pitch, x, y, w, h);
+	copyRectToScreenInternal(buf, pitch, x, y, w, h,
+		getOverlayFormat(),
+		isOverlayDirectRendering(),
+		false);
 }
 
 bool AtariGraphicsManager::showMouse(bool visible) {
@@ -824,8 +812,10 @@ 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 : PIXELFORMAT_CLUT8;
-	_cursor.setSurface(buf, (int)w, (int)h, hotspotX, hotspotY, keycolor, cursorFormat);
+	if (format)
+		assert(*format == PIXELFORMAT_CLUT8);
+
+	_cursor.setSurface(buf, (int)w, (int)h, hotspotX, hotspotY, keycolor);
 	cursorSurfaceChanged();
 }
 
@@ -891,59 +881,6 @@ void AtariGraphicsManager::freeSurfaces() {
 	_overlaySurface.free();
 }
 
-void AtariGraphicsManager::convertRectToSurfaceWithKey(Graphics::Surface &dstSurface, const Graphics::Surface &srcSurface,
-													   int destX, int destY, const Common::Rect &subRect, uint32 key,
-													   const byte srcPalette[256*3]) const {
-	if (!dstSurface.format.isCLUT8()) {
-		assert(srcSurface.format == PIXELFORMAT_CLUT8);
-
-		static int rShift, gShift, bShift;
-		static int rMask, gMask, bMask;
-		static Graphics::PixelFormat format;
-
-		if (dstSurface.format != format) {
-			format = dstSurface.format;
-
-			rShift = format.rLoss - format.rShift;
-			gShift = format.gLoss - format.gShift;
-			bShift = format.bLoss - format.bShift;
-
-			rMask = format.rMax() << format.rShift;
-			gMask = format.gMax() << format.gShift;
-			bMask = format.bMax() << format.bShift;
-		}
-
-		// Convert CLUT8 to RGB332/RGB121 palette and do copyRectToSurfaceWithKey() at the same time
-		const byte *src = (const byte*)srcSurface.getBasePtr(subRect.left, subRect.top);
-		byte *dst = (byte*)dstSurface.getBasePtr(destX, destY);
-
-		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] >> rShift) & rMask)
-						   | ((srcPalette[color*3 + 1] >> gShift) & gMask)
-						   | ((srcPalette[color*3 + 2] >> bShift) & bMask);
-				} else {
-					dst++;
-				}
-			}
-
-			src += (srcSurface.pitch - w);
-			dst += (dstSurface.pitch - w);
-		}
-	} else {
-		dstSurface.copyRectToSurfaceWithKey(srcSurface, destX, destY, subRect, key);
-	}
-}
-
-int AtariGraphicsManager::getBitsPerPixel(const Graphics::PixelFormat &format) const {
-	return format == PIXELFORMAT_RGB121 ? 4 : 8;
-}
-
 template <bool directRendering>	// hopefully compiler optimizes all the branching out
 bool AtariGraphicsManager::updateScreenInternal(const Graphics::Surface &srcSurface) {
 	//debug("updateScreenInternal");
@@ -964,10 +901,10 @@ bool AtariGraphicsManager::updateScreenInternal(const Graphics::Surface &srcSurf
 	bool drawCursor = cursorDrawEnabled
 		&& (cursorPositionChanged || cursorSurfaceChanged || cursorVisibilityChanged || fullRedraw);
 
-	bool restoreCursor = !fullRedraw && !oldCursorRect.isEmpty()
-		&& (cursorPositionChanged || cursorSurfaceChanged || (cursorVisibilityChanged && !_cursor.visible));
+	assert(!fullRedraw || oldCursorRect.isEmpty());
 
-	static Graphics::Surface cachedCursorSurface;
+	bool restoreCursor = !oldCursorRect.isEmpty()
+		&& (cursorPositionChanged || cursorSurfaceChanged || (cursorVisibilityChanged && !_cursor.visible));
 
 	lockSuperBlitter();
 
@@ -975,44 +912,25 @@ bool AtariGraphicsManager::updateScreenInternal(const Graphics::Surface &srcSurf
 		if (cursorDrawEnabled && !drawCursor)
 			drawCursor = it->intersects(_cursor.dstRect);
 
-		if (restoreCursor)
-			restoreCursor = !it->contains(oldCursorRect);
-
 		if (!directRendering) {
 			copyRectToSurface(*dstSurface, dstBitsPerPixel, srcSurface, it->left, it->top, *it);
 			updated = true;
-		} else if (!oldCursorRect.isEmpty()) {
-			const Common::Rect intersectingRect = it->findIntersectingRect(oldCursorRect);
-			if (!intersectingRect.isEmpty()) {
-				// new content has been drawn over stored surface => update
-				// TODO: if restoreCursor == true, update only the NON-intersecting part
-				const Graphics::Surface intersectingScreenSurface = dstSurface->getSubArea(intersectingRect);
-				cachedCursorSurface.copyRectToSurface(
-					intersectingScreenSurface,
-					intersectingRect.left - oldCursorRect.left,
-					intersectingRect.top - oldCursorRect.top,
-					Common::Rect(intersectingScreenSurface.w, intersectingScreenSurface.h));
-			}
 		}
 	}
 
 	if (restoreCursor) {
 		//debug("Restore cursor: %d %d %d %d", oldCursorRect.left, oldCursorRect.top, oldCursorRect.width(), oldCursorRect.height());
 
-		if (!directRendering) {
-			// align on 16px (i.e. 16 bytes -> optimize for C2P, MOVE16 or just 16-byte cache lines)
-			oldCursorRect.left &= 0xfff0;
-			oldCursorRect.right = (oldCursorRect.right + 15) & 0xfff0;
+		// always restore aligned oldCursorRect
+		oldCursorRect = alignRect(oldCursorRect);
 
+		if (!directRendering) {
 			copyRectToSurface(
 				*dstSurface, dstBitsPerPixel, srcSurface,
 				oldCursorRect.left, oldCursorRect.top,
 				oldCursorRect);
 		} else {
-			copyRectToSurface(
-				*dstSurface, dstBitsPerPixel, cachedCursorSurface,
-				oldCursorRect.left, oldCursorRect.top,
-				Common::Rect(oldCursorRect.width(), oldCursorRect.height()));
+			_workScreen->restoreBackground(oldCursorRect);
 		}
 
 		oldCursorRect = Common::Rect();
@@ -1025,21 +943,36 @@ bool AtariGraphicsManager::updateScreenInternal(const Graphics::Surface &srcSurf
 	if (drawCursor) {
 		//debug("Redraw cursor: %d %d %d %d", _cursor.dstRect.left, _cursor.dstRect.top, _cursor.dstRect.width(), _cursor.dstRect.height());
 
-		if (directRendering) {
-			if (cachedCursorSurface.w != _cursor.dstRect.width() || cachedCursorSurface.h != _cursor.dstRect.height())
-				cachedCursorSurface.create(_cursor.dstRect.width(), _cursor.dstRect.height(), _cursor.surface.format);
-
-			// background has been restored, so it's safe to read from destination surface again
-			if (oldCursorRect.isEmpty())
-				cachedCursorSurface.copyRectToSurface(*dstSurface, 0, 0, _cursor.dstRect);
+		if (cursorSurfaceChanged || _cursor.isClipped()) {
+			if (dstSurface->format.isCLUT8())
+				_cursor.convertTo<true>(dstSurface->format);
+			else
+				_cursor.convertTo<false>(dstSurface->format);
+			{
+				// copy in-place (will do nothing on regular Surface::copyRectToSurface)
+				Graphics::Surface surf;
+				surf.init(
+					_cursor.surface.w,
+					_cursor.surface.h,
+					_cursor.surface.pitch * dstBitsPerPixel / 8,	// 4bpp is not byte per pixel anymore
+					_cursor.surface.getPixels(),
+					_cursor.surface.format);
+				copyRectToSurface(
+					surf, dstBitsPerPixel, _cursor.surface,
+					0, 0,
+					Common::Rect(_cursor.surface.w, _cursor.surface.h));
+			}
 		}
 
-		copyRectToSurfaceWithKey(
-			*dstSurface, dstBitsPerPixel, _cursor.surface,
+		if (directRendering)
+			_workScreen->storeBackground(alignRect(_cursor.dstRect));
+
+		// don't use _cursor.srcRect for width as this must be aligned first
+		// (_cursor.surface.w is recalculated thanks to _cursor.isClipped())
+		drawMaskedSprite(
+			*dstSurface, dstBitsPerPixel, _cursor.surface, _cursor.surfaceMask,
 			_cursor.dstRect.left, _cursor.dstRect.top,
-			_cursor.srcRect,
-			_cursor.keycolor,
-			srcSurface, _cursor.palette);
+			Common::Rect(0, _cursor.srcRect.top, _cursor.surface.w, _cursor.srcRect.bottom));
 
 		cursorPositionChanged = cursorSurfaceChanged = false;
 		oldCursorRect = _cursor.dstRect;
@@ -1052,6 +985,48 @@ bool AtariGraphicsManager::updateScreenInternal(const Graphics::Surface &srcSurf
 	return updated;
 }
 
+void AtariGraphicsManager::copyRectToScreenInternal(const void *buf, int pitch, int x, int y, int w, int h,
+													const Graphics::PixelFormat &format, bool directRendering, bool tripleBuffer) {
+	Graphics::Surface &dstSurface = *lockScreen();
+
+	const Common::Rect rect = alignRect(x, y, w, h);
+	_workScreen->addDirtyRect(dstSurface, rect, directRendering);
+
+	if (tripleBuffer) {
+		_screen[BACK_BUFFER2]->addDirtyRect(dstSurface, rect, directRendering);
+		_screen[FRONT_BUFFER]->addDirtyRect(dstSurface, rect, directRendering);
+	}
+
+	if (directRendering) {
+		// TODO: mask the unaligned parts and copy the rest
+		Graphics::Surface srcSurface;
+		byte *srcBuf = (byte *)const_cast<void *>(buf);
+		srcBuf -= (x - rect.left);	// HACK: this assumes pointer to a complete buffer
+		srcSurface.init(rect.width(), rect.height(), pitch, srcBuf, format);
+
+		copyRectToSurface(
+			dstSurface, getBitsPerPixel(format), srcSurface,
+			rect.left, rect.top,
+			Common::Rect(rect.width(), rect.height()));
+	} else {
+		dstSurface.copyRectToSurface(buf, pitch, x, y, w, h);
+	}
+}
+
+int AtariGraphicsManager::getBitsPerPixel(const Graphics::PixelFormat &format) const {
+	return format == PIXELFORMAT_RGB121 ? 4 : 8;
+}
+
+bool AtariGraphicsManager::isOverlayDirectRendering() const {
+	// overlay is direct rendered if in the launcher or if game is directly rendered
+	// (on SuperVidel we always want to use shading/transparency but its direct rendering is fine and supported)
+	return !hasSuperVidel()
+#ifndef DISABLE_FANCY_THEMES
+		   && (ConfMan.getActiveDomain() == nullptr || _currentState.mode == GraphicsMode::DirectRendering)
+#endif
+		;
+}
+
 AtariGraphicsManager::Screen::Screen(AtariGraphicsManager *manager, int width, int height, const Graphics::PixelFormat &format, const Palette *palette_)
 	: _manager(manager) {
 	const AtariMemAlloc &allocFunc = _manager->getStRamAllocFunc();
@@ -1088,7 +1063,7 @@ AtariGraphicsManager::Screen::~Screen() {
 
 void AtariGraphicsManager::Screen::reset(int width, int height, int bitsPerPixel) {
 	cursorPositionChanged = true;
-	cursorSurfaceChanged = false;
+	cursorSurfaceChanged = true;
 	cursorVisibilityChanged = false;
 	clearDirtyRects();
 	oldCursorRect = Common::Rect();
@@ -1158,7 +1133,7 @@ void AtariGraphicsManager::Screen::reset(int width, int height, int bitsPerPixel
 		surf.format);
 }
 
-void AtariGraphicsManager::Screen::addDirtyRect(const Graphics::Surface &srcSurface, const Common::Rect &rect) {
+void AtariGraphicsManager::Screen::addDirtyRect(const Graphics::Surface &srcSurface, const Common::Rect &rect, bool directRendering) {
 	if (fullRedraw)
 		return;
 
@@ -1169,15 +1144,69 @@ void AtariGraphicsManager::Screen::addDirtyRect(const Graphics::Surface &srcSurf
 		dirtyRects.clear();
 		dirtyRects.emplace(srcSurface.w, srcSurface.h);
 
+		oldCursorRect = Common::Rect();
+
 		fullRedraw = true;
 		return;
 	}
 
 	dirtyRects.insert(rect);
+
+	if (!oldCursorRect.isEmpty()) {
+		const Common::Rect alignedOldCursorRect = _manager->alignRect(oldCursorRect);
+
+		// we have to check *aligned* oldCursorRect because it is background which gets copied,
+		// i.e. it has to be up to date even outside the cursor rectangle.
+		// do it now to avoid complex checking in updateScreenInternal()
+		if (rect.contains(alignedOldCursorRect)) {
+			oldCursorRect = Common::Rect();
+		} else if (rect.intersects(alignedOldCursorRect)) {
+			if (!directRendering) {
+				_manager->copyRectToSurface(
+					*offsettedSurf, _manager->getBitsPerPixel(offsettedSurf->format), srcSurface,
+					alignedOldCursorRect.left, alignedOldCursorRect.top,
+					alignedOldCursorRect);
+			} else {
+				restoreBackground(alignedOldCursorRect);
+			}
+
+			oldCursorRect = Common::Rect();
+		}
+	}
 }
 
+void AtariGraphicsManager::Screen::storeBackground(const Common::Rect &rect) {
+	const int bitsPerPixel = _manager->getBitsPerPixel(offsettedSurf->format);
+
+	if (_cursorBackgroundSurf.w != rect.width()
+		|| _cursorBackgroundSurf.h != rect.height()
+		|| _cursorBackgroundSurf.format != offsettedSurf->format) {
+		_cursorBackgroundSurf.create(rect.width(), rect.height(), offsettedSurf->format);
+		_cursorBackgroundSurf.pitch = _cursorBackgroundSurf.pitch * bitsPerPixel / 8;
+	}
+
+	Graphics::copyBlit(
+		(byte *)_cursorBackgroundSurf.getPixels(),
+		(const byte *)offsettedSurf->getPixels() + rect.top * offsettedSurf->pitch + rect.left * bitsPerPixel / 8,
+		_cursorBackgroundSurf.pitch, offsettedSurf->pitch,
+		rect.width() * bitsPerPixel / 8, rect.height(),	// fake 4bpp by 8bpp's width/2
+		offsettedSurf->format.bytesPerPixel);
+}
+
+void AtariGraphicsManager::Screen::restoreBackground(const Common::Rect &rect) {
+	const int bitsPerPixel = _manager->getBitsPerPixel(offsettedSurf->format);
+
+	Graphics::copyBlit(
+		(byte *)offsettedSurf->getPixels() + rect.top * offsettedSurf->pitch + rect.left * bitsPerPixel / 8,
+		(const byte *)_cursorBackgroundSurf.getPixels(),
+		offsettedSurf->pitch, _cursorBackgroundSurf.pitch,
+		rect.width() * bitsPerPixel / 8, rect.height(),	// fake 4bpp by 8bpp's width/2
+		offsettedSurf->format.bytesPerPixel);
+}
+
+
 void AtariGraphicsManager::Cursor::update(const Graphics::Surface &screen, bool isModified) {
-	if (!surface.getPixels()) {
+	if (!_buf) {
 		outOfScreen = true;
 		return;
 	}
@@ -1185,46 +1214,117 @@ void AtariGraphicsManager::Cursor::update(const Graphics::Surface &screen, bool
 	if (!visible || !isModified)
 		return;
 
-	srcRect = Common::Rect(surface.w, surface.h);
+	srcRect = Common::Rect(_width, _height);
 
 	dstRect = Common::Rect(
-		x - hotspotX,	// left
-		y - hotspotY,	// top
-		x - hotspotX + surface.w,	// right
-		y - hotspotY + surface.h);	// bottom
+		_x - _hotspotX,	// left
+		_y - _hotspotY,	// top
+		_x - _hotspotX + _width,	// right
+		_y - _hotspotY + _height);	// bottom
 
 	outOfScreen = !screen.clip(srcRect, dstRect);
+
+	assert(srcRect.width() == dstRect.width());
+	assert(srcRect.height() == dstRect.height());
 }
 
 void AtariGraphicsManager::Cursor::updatePosition(int deltaX, int deltaY, const Graphics::Surface &screen) {
-	x += deltaX;
-	y += deltaY;
-
-	if (x < 0)
-		x = 0;
-	else if (x >= screen.w)
-		x = screen.w - 1;
-
-	if (y < 0)
-		y = 0;
-	else if (y >= screen.h)
-		y = screen.h - 1;
+	_x += deltaX;
+	_y += deltaY;
+
+	if (_x < 0)
+		_x = 0;
+	else if (_x >= screen.w)
+		_x = screen.w - 1;
+
+	if (_y < 0)
+		_y = 0;
+	else if (_y >= screen.h)
+		_y = screen.h - 1;
 }
 
-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) {
 	if (w == 0 || h == 0 || buf == nullptr) {
-		if (surface.getPixels())
-			surface.free();
+		_buf = nullptr;
 		return;
 	}
 
-	if (surface.w != w || surface.h != h || surface.format != format)
-		surface.create(w, h, format);
+	_buf = (const byte *)buf;
+	_width = w;
+	_height = h;
+	_hotspotX = hotspotX;
+	_hotspotY = hotspotY;
+	_keycolor = keycolor;
+}
+
+template <bool isClut8>	// hopefully compiler optimizes all the branching out
+void AtariGraphicsManager::Cursor::convertTo(const Graphics::PixelFormat &format) {
+	const int cursorWidth = (srcRect.width() + 15) & (-16);
+	const int cursorHeight = _height;
+
+	if (surface.w != cursorWidth || surface.h != cursorHeight || surface.format != format) {
+		if (!isClut8 && surface.format != format) {
+			_rShift = format.rLoss - format.rShift;
+			_gShift = format.gLoss - format.gShift;
+			_bShift = format.bLoss - format.bShift;
+
+			_rMask = format.rMax() << format.rShift;
+			_gMask = format.gMax() << format.gShift;
+			_bMask = format.bMax() << format.bShift;
+		}
+
+		surface.create(cursorWidth, cursorHeight, format);
+
+		const bool old_unalignedPitch = g_unalignedPitch;
+		g_unalignedPitch = true;
+		surfaceMask.create(surface.w / 8, surface.h, format);	// 1 bpl
+		g_unalignedPitch = old_unalignedPitch;
+	}
+
+	const int srcRectWidth = srcRect.width();
+
+	const byte *src = _buf + srcRect.left;
+	byte *dst = (byte *)surface.getPixels();
+	uint16 *dstMask = (uint16 *)surfaceMask.getPixels();
+	const int srcPadding = _width - srcRectWidth;
+	const int dstPadding = surface.w - srcRectWidth;
+
+	for (int j = 0; j < cursorHeight; ++j) {
+		for (int i = 0; i < srcRectWidth; ++i) {
+			const uint32 color = *src++;
+			const uint16 bit = 1 << (15 - (i % 16));
+
+			if (color != _keycolor) {
+				if (!isClut8) {
+					// Convert CLUT8 to RGB332/RGB121 palette
+					*dst++ = ((palette[color*3 + 0] >> _rShift) & _rMask)
+						   | ((palette[color*3 + 1] >> _gShift) & _gMask)
+						   | ((palette[color*3 + 2] >> _bShift) & _bMask);
+				} else {
+					*dst++ = color;
+				}
 
-	surface.copyRectToSurface(buf, w * surface.format.bytesPerPixel, 0, 0, w, h);
+				// clear bit
+				*dstMask &= ~bit;
+			} else {
+				*dst++ = 0x00;
 
-	hotspotX = hotspotX_;
-	hotspotY = hotspotY_;
-	keycolor = keycolor_;
+				// set bit
+				*dstMask |= bit;
+			}
+
+			if (bit == 0x0001)
+				dstMask++;
+		}
+
+		src += srcPadding;
+
+		if (dstPadding) {
+			memset(dst, 0x00, dstPadding);
+			dst += dstPadding;
+
+			*dstMask |= ((1 << dstPadding) - 1);
+			dstMask++;
+		}
+	}
 }
diff --git a/backends/graphics/atari/atari-graphics.h b/backends/graphics/atari/atari-graphics.h
index 8010087c144..e15805edb34 100644
--- a/backends/graphics/atari/atari-graphics.h
+++ b/backends/graphics/atari/atari-graphics.h
@@ -52,6 +52,15 @@ public:
 	void setFeatureState(OSystem::Feature f, bool enable) override;
 	bool getFeatureState(OSystem::Feature f) const override;
 
+	const OSystem::GraphicsMode *getSupportedGraphicsModes() const override {
+		static const OSystem::GraphicsMode graphicsModes[] = {
+			{ "direct", "Direct rendering", (int)GraphicsMode::DirectRendering },
+			{ "single", "Single buffering", (int)GraphicsMode::SingleBuffering },
+			{ "triple", "Triple buffering", (int)GraphicsMode::TripleBuffering },
+			{ nullptr, nullptr, 0 }
+		};
+		return graphicsModes;
+	}
 	int getDefaultGraphicsMode() const override { return (int)GraphicsMode::TripleBuffering; }
 	bool setGraphicsMode(int mode, uint flags = OSystem::kGfxModeNoFlags) override;
 	int getGraphicsMode() const override { return (int)_currentState.mode; }
@@ -105,9 +114,6 @@ protected:
 
 	void allocateSurfaces();
 	void freeSurfaces();
-	void convertRectToSurfaceWithKey(Graphics::Surface &dstSurface, const Graphics::Surface &srcSurface,
-									 int destX, int destY, const Common::Rect &subRect, uint32 key,
-									 const byte srcPalette[256*3]) const;
 
 	enum class GraphicsMode : int {
 		DirectRendering = 0,
@@ -151,7 +157,12 @@ private:
 	template <bool directRendering>
 	bool updateScreenInternal(const Graphics::Surface &srcSurface);
 
-	inline int getBitsPerPixel(const Graphics::PixelFormat &format) const;
+	void copyRectToScreenInternal(const void *buf, int pitch, int x, int y, int w, int h,
+								  const Graphics::PixelFormat &format, bool directRendering, bool tripleBuffer);
+
+	int getBitsPerPixel(const Graphics::PixelFormat &format) const;
+
+	bool isOverlayDirectRendering() const;
 
 	virtual AtariMemAlloc getStRamAllocFunc() const {
 		return [](size_t bytes) { return (void*)Mxalloc(bytes, MX_STRAM); };
@@ -165,15 +176,18 @@ private:
 								   const Common::Rect &subRect) const {
 		dstSurface.copyRectToSurface(srcSurface, destX, destY, subRect);
 	}
-	virtual void copyRectToSurfaceWithKey(Graphics::Surface &dstSurface, int dstBitsPerPixel, const Graphics::Surface &srcSurface,
-										  int destX, int destY,
-										  const Common::Rect &subRect, uint32 key,
-										  const Graphics::Surface &bgSurface, const byte srcPalette[256*3]) const {
-		convertRectToSurfaceWithKey(dstSurface, srcSurface, destX, destY, subRect, key, srcPalette);
-	}
+
+	virtual void drawMaskedSprite(Graphics::Surface &dstSurface, int dstBitsPerPixel,
+								  const Graphics::Surface &srcSurface, const Graphics::Surface &srcMask,
+								  int destX, int destY,
+								  const Common::Rect &subRect) = 0;
 
 	virtual Common::Rect alignRect(int x, int y, int w, int h) const = 0;
 
+	Common::Rect alignRect(const Common::Rect &rect) const {
+		return alignRect(rect.left, rect.top, rect.width(), rect.height());
+	}
+
 	void cursorPositionChanged() {
 		if (_overlayVisible) {
 			_screen[OVERLAY_BUFFER]->cursorPositionChanged = true;
@@ -219,6 +233,7 @@ private:
 	bool _tt = false;
 	bool _aspectRatioCorrection = false;
 	bool _oldAspectRatioCorrection = false;
+	bool _checkUnalignedPitch = false;
 
 	GraphicsState _currentState{ (GraphicsMode)getDefaultGraphicsMode() };
 
@@ -257,17 +272,20 @@ private:
 
 		void reset(int width, int height, int bitsPerPixel);
 		// must be called before any rectangle drawing
-		void addDirtyRect(const Graphics::Surface &srcSurface, const Common::Rect &rect);
+		void addDirtyRect(const Graphics::Surface &srcSurface, const Common::Rect &rect, bool directRendering);
 
 		void clearDirtyRects() {
 			dirtyRects.clear();
 			fullRedraw = false;
 		}
 
+		void storeBackground(const Common::Rect &rect);
+		void restoreBackground(const Common::Rect &rect);
+
 		Graphics::Surface surf;
 		const Palette *palette;
 		bool cursorPositionChanged = true;
-		bool cursorSurfaceChanged = false;
+		bool cursorSurfaceChanged = true;
 		bool cursorVisibilityChanged = false;
 		DirtyRects dirtyRects;
 		bool fullRedraw = false;
@@ -279,8 +297,11 @@ private:
 	private:
 		static constexpr size_t ALIGN = 16;	// 16 bytes
 
-		AtariGraphicsManager *_manager;
+		const AtariGraphicsManager *_manager;
+
 		Graphics::Surface _offsettedSurf;
+		// used by direct rendering
+		Graphics::Surface _cursorBackgroundSurf;
 	};
 	Screen *_screen[BUFFER_COUNT] = {};
 	Screen *_workScreen = nullptr;
@@ -291,8 +312,6 @@ private:
 	bool _overlayVisible = false;
 	Graphics::Surface _overlaySurface;
 
-	bool _checkUnalignedPitch = false;
-
 	struct Cursor {
 		void update(const Graphics::Surface &screen, bool isModified);
 
@@ -300,30 +319,35 @@ private:
 
 		// position
 		Common::Point getPosition() const {
-			return Common::Point(x, y);
+			return Common::Point(_x, _y);
 		}
-		void setPosition(int x_, int y_) {
-			x = x_;
-			y = y_;
+		void setPosition(int x, int y) {
+			_x = x;
+			_y = y;
 		}
 		void updatePosition(int deltaX, int deltaY, const Graphics::Surface &screen);
 		void swap() {
-			const int tmpX = oldX;
-			const int tmpY = oldY;
+			const int tmpX = _oldX;
+			const int tmpY = _oldY;
 
-			oldX = x;
-			oldY = y;
+			_oldX = _x;
+			_oldY = _y;
 
-			x = tmpX;
-			y = tmpY;
+			_x = tmpX;
+			_y = tmpY;
 		}
 
 		// surface
-		void setSurface(const void *buf, int w, int h, int _hotspotX, int _hotspotY, uint32 _keycolor, const Graphics::PixelFormat &format);
+		void setSurface(const void *buf, int w, int h, int hotspotX, int hotspotY, uint32 keycolor);
+		template <bool isClut8>
+		void convertTo(const Graphics::PixelFormat &format);
 		Graphics::Surface surface;
-		uint32 keycolor;
+		Graphics::Surface surfaceMask;
 
 		// rects (valid only if !outOfScreen)
+		bool isClipped() const {
+			return outOfScreen ? false : _width != srcRect.width();
+		}
 		bool outOfScreen = true;
 		Common::Rect srcRect;
 		Common::Rect dstRect;
@@ -332,12 +356,21 @@ private:
 		byte palette[256*3] = {};
 
 	private:
-		int x = -1, y = -1;
-		int oldX = -1, oldY = -1;
-
-		int hotspotX;
-		int hotspotY;
-	} _cursor;
+		int _x = -1, _y = -1;
+		int _oldX = -1, _oldY = -1;
+
+		// related to 'surface'
+		const byte *_buf = nullptr;
+		int _width;
+		int _height;
+		int _hotspotX;
+		int _hotspotY;
+		uint32 _keycolor;
+
+		int _rShift, _gShift, _bShift;
+		int _rMask, _gMask, _bMask;
+	};
+	Cursor _cursor;
 
 	Palette _palette;
 	Palette _overlayPalette;
diff --git a/backends/platform/atari/readme.txt b/backends/platform/atari/readme.txt
index bf2e6120743..d56d3901675 100644
--- a/backends/platform/atari/readme.txt
+++ b/backends/platform/atari/readme.txt
@@ -44,10 +44,9 @@ And I could!
 Hardware requirements
 ---------------------
 
-This port requires an Atari computer with TT or Falcon compatible video modes.
-Ideally accelerated with at least 4+32 MB of RAM. It runs fine also in Hatari
-and ARAnyM but in case of ARAnyM don't forget to disable fVDI to show Videl
-output.
+This port requires an Atari computer with TT or Falcon compatible video modes
+with at least 4+32 MB of RAM. It runs fine also in Hatari and ARAnyM but in
+case of ARAnyM don't forget to disable fVDI to show Videl output.
 
 
 Main features
@@ -75,6 +74,8 @@ Main features
 - Support for PC keys (page up, page down, pause, F11/F12, ...) and mouse wheel
   (Eiffel/Aranym only)
 
+- Native MIDI output (if present).
+
 - AdLib emulation works nicely with many games without noticeable slow downs.
 
 
@@ -104,46 +105,46 @@ Graphics modes
 This topic is more complex than it looks. ScummVM renders game graphics using
 rectangles and this port offers following options to render them:
 
-Direct rendering (present only with the SuperVidel)
-Single buffering
-Triple buffering
-
-Direct rendering:
-~~~~~~~~~~~~~~~~~
+Direct rendering
+~~~~~~~~~~~~~~~~
 
-This is direct writing of the pixels into (SuperVidel's) screen buffer.
+This is direct writing of the pixels into the screen buffer. On SuperVidel
+it is done natively, on Videl a chunky to planar conversion takes place
+beforehand.
 
 Pros:
 
-- fastest possible rendering (especially in 640x480 with a lot of small
-  rectangle updates where the buffer copying drags performance down)
+- on SuperVidel this offers fastest possible rendering (especially in 640x480
+  with a lot of small rectangle updates where the buffer copying drags
+  performance down)
+
+- on Videl this _may_ offer fastest possible rendering if the rendering
+  pipeline isn't flooded with too many small rectangles (C2P setup isn't for
+  free). However with fullscreen intro sequences this is a no-brainer.
 
 Cons:
 
 - screen tearing in most cases
 
-- SuperVidel only: using C2P would be not only suboptimal (every rectangle
-  would be C2P'ed instead of multiple copying and just one C2P of the final
-  screen) but poses an additional problem as C2P requires data aligned on a
-  16px boundary and ScummVM supplies arbitrarily-sized rectangles (this is
-  solvable by custom Surface allocation but it's not bullet-proof). In theory I
-  could implement direct rendering for the Falcon hicolor (320x240 at 16bpp) but
-  this creates another set of issues like when palette would be updated but not
-  the whole screen - so some rectangles would be rendered in old palette and
-  some in new.
+- on Videl, this may not work properly if a game engine uses its own buffers
+  instead of surfaces (which are aligned on a 16pix boundary). Another source
+  of danger is if an engine draws directly to the screen surface. Fortunately,
+  each game can have its own graphics mode set separately so for games which
+  do not work properly one can still leave the default graphics mode set.
+
+- on Videl overlay background isn't rendered (the gui code can't work with
+  bitplanes)
 
 SuperBlitter used: sometimes (when ScummVM allocates surface via its create()
 function; custom/small buffers originating in the engine code are still copied
 using the CPU).
 
-Single buffering:
-~~~~~~~~~~~~~~~~~
+Single buffering
+~~~~~~~~~~~~~~~~
 
 This is very similar to the previous mode with the difference that the engine
 uses an intermediate buffer for storing the rectangles but yet it remembers
-which ones they were. It works also on plain Videl and applies the chunky to
-planar process to each one of the rectangles separately, avoiding fullscreen
-updates (but if such is needed, there is an optimized code path for it).
+which ones they were.
 
 Pros:
 
@@ -159,8 +160,8 @@ Cons:
 SuperBlitter used: yes, for rectangle blitting to screen and cursor restoration.
 Sometimes also for generic copying between buffers (see above).
 
-Triple buffering:
-~~~~~~~~~~~~~~~~~
+Triple buffering
+~~~~~~~~~~~~~~~~
 
 This is the "true" triple buffering as described in
 https://en.wikipedia.org/wiki/Multiple_buffering#Triple_buffering and not "swap


Commit: 18dafa17427fcdba05788a63cfb6885636472a22
    https://github.com/scummvm/scummvm/commit/18dafa17427fcdba05788a63cfb6885636472a22
Author: Miro Kropacek (miro.kropacek at gmail.com)
Date: 2023-08-12T17:38:49+02:00

Commit Message:
BACKENDS: ATARI: Disable director & remove unused dat files

Changed paths:
    backends/platform/atari/build-release.sh
    backends/platform/atari/build-release030.sh


diff --git a/backends/platform/atari/build-release.sh b/backends/platform/atari/build-release.sh
index a40485f66b3..56af52f8c64 100755
--- a/backends/platform/atari/build-release.sh
+++ b/backends/platform/atari/build-release.sh
@@ -27,13 +27,17 @@ then
 	--disable-bink \
 	--opengl-mode=none \
 	--enable-verbose-build \
-	--enable-text-console
+	--enable-text-console \
+	--disable-engine=director
 fi
 
 make -j 16
 rm -rf dist-generic
 make dist-generic
 
+# remove unused files; absent gui-icons.dat massively speeds up startup time (used for the grid mode)
+rm -f dist-generic/scummvm/data/{gui-icons,achievements,macgui,shaders}.dat
+
 # move themes into 'themes' folder (with compression level zero for faster depacking)
 mkdir -p dist-generic/scummvm/themes
 cd dist-generic/scummvm/themes
@@ -43,8 +47,6 @@ do
 	unzip -d tmp "$f" && cd tmp && zip -0 ../$(basename "$f") * && cd .. && rm -r tmp && rm "$f"
 done
 )
-# absent gui-icons.dat massively speeds up startup time (used for the grid mode)
-rm ../data/gui-icons.dat
 cd -
 
 # readme.txt
diff --git a/backends/platform/atari/build-release030.sh b/backends/platform/atari/build-release030.sh
index d0d9746d0e1..2dfd0510de2 100755
--- a/backends/platform/atari/build-release030.sh
+++ b/backends/platform/atari/build-release030.sh
@@ -29,15 +29,15 @@ then
 	--opengl-mode=none \
 	--enable-verbose-build \
 	--enable-text-console \
-	--disable-engine=hugo
+	--disable-engine=hugo,director
 fi
 
 make -j 16
 rm -rf dist-generic
 make dist-generic
 
-# remove themes
-rm -f dist-generic/scummvm/data/*.zip dist-generic/scummvm/data/gui-icons.dat
+# remove unused files
+rm -f dist-generic/scummvm/data/*.zip dist-generic/scummvm/data/{gui-icons,achievements,macgui,shaders}.dat
 
 # readme.txt
 cp ../backends/platform/atari/readme.txt dist-generic/scummvm


Commit: 5a756fe2f4708bb731a3547c5e895498a6b716bb
    https://github.com/scummvm/scummvm/commit/5a756fe2f4708bb731a3547c5e895498a6b716bb
Author: Miro Kropacek (miro.kropacek at gmail.com)
Date: 2023-08-12T17:38:49+02:00

Commit Message:
BACKENDS: ATARI: Restore key click

Changed paths:
    backends/mixer/atari/atari-mixer.cpp


diff --git a/backends/mixer/atari/atari-mixer.cpp b/backends/mixer/atari/atari-mixer.cpp
index b7009e5547c..67d889ccbbb 100644
--- a/backends/mixer/atari/atari-mixer.cpp
+++ b/backends/mixer/atari/atari-mixer.cpp
@@ -33,8 +33,8 @@
 #define DEFAULT_OUTPUT_RATE 24585
 
 void AtariAudioShutdown() {
-	Buffoper(0x00);
 	Sndstatus(SND_RESET);
+	Soundcmd(ADDERIN, ADCIN);	// restore key click
 	Unlocksnd();
 }
 


Commit: c1354b8589b2dbcdb63726e777d1371a4739ee44
    https://github.com/scummvm/scummvm/commit/c1354b8589b2dbcdb63726e777d1371a4739ee44
Author: Miro Kropacek (miro.kropacek at gmail.com)
Date: 2023-08-12T17:38:49+02:00

Commit Message:
BACKENDS: ATARI: Fix crash when exit() is called

exit() can be handled at user level via atexit(), there's no need to use
the critical handler routine.

Removed GEM restore from the critical handler as this uncovered its
instability while being in the critical handler.

Changed paths:
    backends/platform/atari/osystem_atari.cpp


diff --git a/backends/platform/atari/osystem_atari.cpp b/backends/platform/atari/osystem_atari.cpp
index 06a5575da5d..b7f66555391 100644
--- a/backends/platform/atari/osystem_atari.cpp
+++ b/backends/platform/atari/osystem_atari.cpp
@@ -20,6 +20,7 @@
  */
 
 #include <stdio.h>
+#include <stdlib.h>
 #include <time.h>
 
 #include <gem.h>
@@ -84,6 +85,7 @@ static void (*s_vkbderr)(void) = nullptr;
 static KBDVEC s_mousevec = nullptr;
 
 static void (*s_old_procterm)(void) = nullptr;
+static bool exit_already_called = false;
 
 static void exit_gem() {
 	if (s_app_id != -1) {
@@ -119,7 +121,25 @@ static void critical_restore() {
 		s_kbdvec = s_mousevec = nullptr;
 	}
 
-	exit_gem();
+	// don't call GEM cleanup in the critical handler: it seems that v_clsvwk()
+	// somehow manipulates the same memory area used for the critical handler's stack
+	// what causes v_clsvwk() never returning and leading to a bus error (and another
+	// critical_restore() called...)
+	//exit_gem();
+}
+
+// called on normal program termination (via exit() or returning from main())
+static void exit_restore() {
+	if (!exit_already_called) {
+		exit_already_called = true;
+
+		g_system->destroy();
+
+		// graceful exit
+		(void)Setexc(VEC_PROCTERM, s_old_procterm);
+
+		exit_gem();
+	}
 }
 
 OSystem_Atari::OSystem_Atari() {
@@ -182,6 +202,9 @@ OSystem_Atari::OSystem_Atari() {
 
 	_videoInitialized = true;
 
+	// protect against sudden exit()
+	atexit(exit_restore);
+	// protect against sudden crash
 	s_old_procterm = Setexc(VEC_PROCTERM, -1);
 	(void)Setexc(VEC_PROCTERM, critical_restore);
 }
@@ -332,6 +355,7 @@ void OSystem_Atari::quit() {
 	g_system->destroy();
 
 	// graceful exit
+	exit_already_called = true;
 	(void)Setexc(VEC_PROCTERM, s_old_procterm);
 
 	exit_gem();
@@ -449,6 +473,7 @@ int main(int argc, char *argv[]) {
 	g_system->destroy();
 
 	// graceful exit
+	exit_already_called = true;
 	(void)Setexc(VEC_PROCTERM, s_old_procterm);
 
 	exit_gem();


Commit: f8f22c6e85830e1bf26b0dfe96a01098b015fe62
    https://github.com/scummvm/scummvm/commit/f8f22c6e85830e1bf26b0dfe96a01098b015fe62
Author: Miro Kropacek (miro.kropacek at gmail.com)
Date: 2023-08-12T17:38:49+02:00

Commit Message:
BACKENDS: ATARI: Fix a stuck key repeat

Sometimes the new IKBD handler would take over before TOS got a message
about key release event, leading to an infinite key stuck sound.

Changed paths:
    backends/platform/atari/atari_ikbd.S
    backends/platform/atari/osystem_atari.cpp


diff --git a/backends/platform/atari/atari_ikbd.S b/backends/platform/atari/atari_ikbd.S
index 07b51af12f4..57b44687175 100644
--- a/backends/platform/atari/atari_ikbd.S
+++ b/backends/platform/atari/atari_ikbd.S
@@ -31,12 +31,39 @@
 	.extern	_g_atari_ikbd_scancodes_mask
 	.extern	_g_atari_ikbb_scancodes_head
 
+	.extern	_g_atari_old_kbdvec
+
 	.text
 
 _atari_kbdvec:
 	tst.w	(vkbderr_count,pc)
 	bne.b	kbdvec_end
 
+	lea	pressed_keys,a0
+	clr.l	d1
+	move.b	d0,d1
+	bpl.b	key_pressed				| bit 7 cleared
+
+key_released:
+	and.b	#0x7f,d1
+	tst.b	(a0,d1.l)				| pressed before?
+	bne.b	key_released_ok
+
+	| if we get a sudden release key event,
+	| let the original handler process it
+	move.l	_g_atari_old_kbdvec,a0
+	tst.l	a0
+	beq.b	kbdvec_end
+	jmp	(a0)
+
+key_released_ok:
+	clr.b	(a0,d1.l)				| mark as released
+	bra.b	kbdvec_process
+
+key_pressed:
+	addq.b	#1,(a0,d1.l)				| mark as pressed
+
+kbdvec_process:
 	lea	_g_atari_ikbd_scancodes,a0
 	move.w	_g_atari_ikbb_scancodes_head,d1
 
@@ -74,3 +101,9 @@ _atari_mousevec:
 // place it within reach of 32K (PC relative)
 vkbderr_count:
 	dc.w	0
+
+
+	.bss
+
+pressed_keys:
+	ds.b	128
diff --git a/backends/platform/atari/osystem_atari.cpp b/backends/platform/atari/osystem_atari.cpp
index b7f66555391..2b4c8d5cf45 100644
--- a/backends/platform/atari/osystem_atari.cpp
+++ b/backends/platform/atari/osystem_atari.cpp
@@ -80,7 +80,7 @@ static int s_vdi_width, s_vdi_height;
 
 static bool s_tt = false;
 typedef void (*KBDVEC)(void *);
-static KBDVEC s_kbdvec = nullptr;
+KBDVEC g_atari_old_kbdvec = nullptr;
 static void (*s_vkbderr)(void) = nullptr;
 static KBDVEC s_mousevec = nullptr;
 
@@ -113,12 +113,12 @@ static void critical_restore() {
 		Supexec(asm_screen_falcon_restore);
 	Supexec(atari_200hz_shutdown);
 
-	if (s_kbdvec && s_vkbderr && s_mousevec) {
+	if (g_atari_old_kbdvec && s_vkbderr && s_mousevec) {
 		_KBDVECS *kbdvecs = Kbdvbase();
-		((uintptr *)kbdvecs)[-1] = (uintptr)s_kbdvec;
+		((uintptr *)kbdvecs)[-1] = (uintptr)g_atari_old_kbdvec;
 		kbdvecs->vkbderr = s_vkbderr;
 		kbdvecs->mousevec = s_mousevec;
-		s_kbdvec = s_mousevec = nullptr;
+		g_atari_old_kbdvec = s_mousevec = nullptr;
 	}
 
 	// don't call GEM cleanup in the critical handler: it seems that v_clsvwk()
@@ -184,7 +184,7 @@ OSystem_Atari::OSystem_Atari() {
 	}
 
 	_KBDVECS *kbdvecs = Kbdvbase();
-	s_kbdvec = (KBDVEC)(((uintptr *)kbdvecs)[-1]);
+	g_atari_old_kbdvec = (KBDVEC)(((uintptr *)kbdvecs)[-1]);
 	s_vkbderr = kbdvecs->vkbderr;
 	s_mousevec = kbdvecs->mousevec;
 
@@ -249,12 +249,12 @@ OSystem_Atari::~OSystem_Atari() {
 		_timerInitialized = false;
 	}
 
-	if (s_kbdvec && s_vkbderr && s_mousevec) {
+	if (g_atari_old_kbdvec && s_vkbderr && s_mousevec) {
 		_KBDVECS *kbdvecs = Kbdvbase();
-		((uintptr *)kbdvecs)[-1] = (uintptr)s_kbdvec;
+		((uintptr *)kbdvecs)[-1] = (uintptr)g_atari_old_kbdvec;
 		kbdvecs->vkbderr = s_vkbderr;
 		kbdvecs->mousevec = s_mousevec;
-		s_kbdvec = s_mousevec = nullptr;
+		g_atari_old_kbdvec = s_mousevec = nullptr;
 	}
 }
 


Commit: 87f1cd25be12799d585c99f511fc40cb8e229220
    https://github.com/scummvm/scummvm/commit/87f1cd25be12799d585c99f511fc40cb8e229220
Author: Miro Kropacek (miro.kropacek at gmail.com)
Date: 2023-08-12T17:38:49+02:00

Commit Message:
BACKENDS: ATARI: Don't initialize GEM before initBackend()

This caused unnecessary desktop redraw, clearing text output from the
console.

Changed paths:
    backends/platform/atari/osystem_atari.cpp
    backends/platform/atari/osystem_atari.h


diff --git a/backends/platform/atari/osystem_atari.cpp b/backends/platform/atari/osystem_atari.cpp
index 2b4c8d5cf45..af67e1d8ae8 100644
--- a/backends/platform/atari/osystem_atari.cpp
+++ b/backends/platform/atari/osystem_atari.cpp
@@ -74,32 +74,14 @@ extern "C" volatile uint32 counter_200hz;
 extern void nf_init(void);
 extern void nf_print(const char* msg);
 
-static int s_app_id = -1;
-static int16 s_vdi_handle;
-static int s_vdi_width, s_vdi_height;
-
 static bool s_tt = false;
 typedef void (*KBDVEC)(void *);
 KBDVEC g_atari_old_kbdvec = nullptr;
 static void (*s_vkbderr)(void) = nullptr;
 static KBDVEC s_mousevec = nullptr;
 
-static void (*s_old_procterm)(void) = nullptr;
 static bool exit_already_called = false;
 
-static void exit_gem() {
-	if (s_app_id != -1) {
-		//wind_update(END_UPDATE);
-
-		// redraw screen
-		form_dial(FMD_FINISH, 0, 0, 0, 0, 0, 0, s_vdi_width, s_vdi_height);
-		graf_mouse(M_ON, NULL);
-
-		v_clsvwk(s_vdi_handle);
-		appl_exit();
-	}
-}
-
 static void critical_restore() {
 	extern void AtariAudioShutdown();
 	extern void AtariGraphicsShutdown();
@@ -125,21 +107,12 @@ static void critical_restore() {
 	// somehow manipulates the same memory area used for the critical handler's stack
 	// what causes v_clsvwk() never returning and leading to a bus error (and another
 	// critical_restore() called...)
-	//exit_gem();
 }
 
 // called on normal program termination (via exit() or returning from main())
 static void exit_restore() {
-	if (!exit_already_called) {
-		exit_already_called = true;
-
+	if (!exit_already_called)
 		g_system->destroy();
-
-		// graceful exit
-		(void)Setexc(VEC_PROCTERM, s_old_procterm);
-
-		exit_gem();
-	}
 }
 
 OSystem_Atari::OSystem_Atari() {
@@ -205,7 +178,7 @@ OSystem_Atari::OSystem_Atari() {
 	// protect against sudden exit()
 	atexit(exit_restore);
 	// protect against sudden crash
-	s_old_procterm = Setexc(VEC_PROCTERM, -1);
+	_old_procterm = Setexc(VEC_PROCTERM, -1);
 	(void)Setexc(VEC_PROCTERM, critical_restore);
 }
 
@@ -256,9 +229,53 @@ OSystem_Atari::~OSystem_Atari() {
 		kbdvecs->mousevec = s_mousevec;
 		g_atari_old_kbdvec = s_mousevec = nullptr;
 	}
+
+	if (_app_id != -1) {
+		//wind_update(END_UPDATE);
+
+		// redraw screen
+		form_dial(FMD_FINISH, 0, 0, 0, 0, 0, 0, _vdi_width, _vdi_height);
+		graf_mouse(M_ON, NULL);
+
+		v_clsvwk(_vdi_handle);
+		appl_exit();
+	}
+
+	// graceful exit
+	exit_already_called = true;
+	(void)Setexc(VEC_PROCTERM, _old_procterm);
 }
 
 void OSystem_Atari::initBackend() {
+	_app_id = appl_init();
+	if (_app_id != -1) {
+		// get the ID of the current physical screen workstation
+		int16 dummy;
+		_vdi_handle = graf_handle(&dummy, &dummy, &dummy, &dummy);
+		if (_vdi_handle < 1) {
+			appl_exit();
+			error("graf_handle() failed");
+		}
+
+		int16 work_in[16] = {};
+		int16 work_out[57] = {};
+
+		// open a virtual screen workstation
+		v_opnvwk(work_in, &_vdi_handle, work_out);
+
+		if (_vdi_handle == 0) {
+			appl_exit();
+			error("v_opnvwk() failed");
+		}
+
+		_vdi_width = work_out[0] + 1;
+		_vdi_height = work_out[1] + 1;
+
+		graf_mouse(M_OFF, NULL);
+		// see https://github.com/freemint/freemint/issues/312
+		//wind_update(BEG_UPDATE);
+	}
+
 	_timerManager = new DefaultTimerManager();
 	_savefileManager = new DefaultSaveFileManager("saves");
 
@@ -353,12 +370,6 @@ void OSystem_Atari::quit() {
 	debug("OSystem_Atari::quit()");
 
 	g_system->destroy();
-
-	// graceful exit
-	exit_already_called = true;
-	(void)Setexc(VEC_PROCTERM, s_old_procterm);
-
-	exit_gem();
 }
 
 void OSystem_Atari::logMessage(LogMessageType::Type type, const char *message) {
@@ -436,35 +447,6 @@ OSystem *OSystem_Atari_create() {
 }
 
 int main(int argc, char *argv[]) {
-	s_app_id = appl_init();
-	if (s_app_id != -1) {
-		// get the ID of the current physical screen workstation
-		int16 dummy;
-		s_vdi_handle = graf_handle(&dummy, &dummy, &dummy, &dummy);
-		if (s_vdi_handle < 1) {
-			appl_exit();
-			error("graf_handle() failed");
-		}
-
-		int16 work_in[16] = {};
-		int16 work_out[57] = {};
-
-		// open a virtual screen workstation
-		v_opnvwk(work_in, &s_vdi_handle, work_out);
-
-		if (s_vdi_handle == 0) {
-			appl_exit();
-			error("v_opnvwk() failed");
-		}
-
-		s_vdi_width = work_out[0] + 1;
-		s_vdi_height = work_out[1] + 1;
-
-		graf_mouse(M_OFF, NULL);
-		// see https://github.com/freemint/freemint/issues/312
-		//wind_update(BEG_UPDATE);
-	}
-
 	g_system = OSystem_Atari_create();
 	assert(g_system);
 
@@ -472,12 +454,6 @@ int main(int argc, char *argv[]) {
 	int res = scummvm_main(argc, argv);
 	g_system->destroy();
 
-	// graceful exit
-	exit_already_called = true;
-	(void)Setexc(VEC_PROCTERM, s_old_procterm);
-
-	exit_gem();
-
 	return res;
 }
 
diff --git a/backends/platform/atari/osystem_atari.h b/backends/platform/atari/osystem_atari.h
index f6d3708da44..cb02c3f849c 100644
--- a/backends/platform/atari/osystem_atari.h
+++ b/backends/platform/atari/osystem_atari.h
@@ -55,6 +55,13 @@ private:
 	bool _timerInitialized = false;
 	bool _useNullMixer = false;
 	bool _inTimer = false;
+
+	int _app_id = -1;
+	int16 _vdi_handle;
+	int _vdi_width;
+	int _vdi_height;
+
+	void (*_old_procterm)(void) = nullptr;
 };
 
 #endif


Commit: 46939cca9b77609944efc49111ef372d0675e7dc
    https://github.com/scummvm/scummvm/commit/46939cca9b77609944efc49111ef372d0675e7dc
Author: Miro Kropacek (miro.kropacek at gmail.com)
Date: 2023-08-12T17:38:49+02:00

Commit Message:
BACKENDS: ATARI: Fix regression in unlockScreen()

Changed paths:
    backends/graphics/atari/atari-graphics.cpp


diff --git a/backends/graphics/atari/atari-graphics.cpp b/backends/graphics/atari/atari-graphics.cpp
index 42fa7cd5b8d..17c522a5057 100644
--- a/backends/graphics/atari/atari-graphics.cpp
+++ b/backends/graphics/atari/atari-graphics.cpp
@@ -430,7 +430,11 @@ void AtariGraphicsManager::unlockScreen() {
 		_screen[FRONT_BUFFER]->addDirtyRect(dstSurface, rect, directRendering);
 	}
 
-	updateScreen();
+	// doc says:
+	// Unlock the screen framebuffer, and mark it as dirty, i.e. during the
+	// next updateScreen() call, the whole screen will be updated.
+	//
+	// ... so no updateScreen() from here (otherwise Eco Quest's intro is crawling!)
 }
 
 void AtariGraphicsManager::fillScreen(uint32 col) {


Commit: 2ab9ba353a2d877bb43dcb2c83cba687d75ec03c
    https://github.com/scummvm/scummvm/commit/2ab9ba353a2d877bb43dcb2c83cba687d75ec03c
Author: Miro Kropacek (miro.kropacek at gmail.com)
Date: 2023-08-12T17:38:49+02:00

Commit Message:
BACKENDS: ATARI: Remove the SCI hack

It was wrong (other engines are allowed to do infinite recursions?) and
Future Wars still isn't working properly at all times. I've decided to
remove it (i.e. Future Wars and Operation Stealth are not supported
anymore).

Small updates in readme.txt.

Changed paths:
    backends/platform/atari/build-release.sh
    backends/platform/atari/build-release030.sh
    backends/platform/atari/osystem_atari.cpp
    backends/platform/atari/osystem_atari.h
    backends/platform/atari/readme.txt


diff --git a/backends/platform/atari/build-release.sh b/backends/platform/atari/build-release.sh
index 56af52f8c64..f3d0fdc935b 100755
--- a/backends/platform/atari/build-release.sh
+++ b/backends/platform/atari/build-release.sh
@@ -28,7 +28,7 @@ then
 	--opengl-mode=none \
 	--enable-verbose-build \
 	--enable-text-console \
-	--disable-engine=director
+	--disable-engine=director,cine
 fi
 
 make -j 16
diff --git a/backends/platform/atari/build-release030.sh b/backends/platform/atari/build-release030.sh
index 2dfd0510de2..05398ed9b6b 100755
--- a/backends/platform/atari/build-release030.sh
+++ b/backends/platform/atari/build-release030.sh
@@ -29,7 +29,7 @@ then
 	--opengl-mode=none \
 	--enable-verbose-build \
 	--enable-text-console \
-	--disable-engine=hugo,director
+	--disable-engine=hugo,director,cine
 fi
 
 make -j 16
diff --git a/backends/platform/atari/osystem_atari.cpp b/backends/platform/atari/osystem_atari.cpp
index af67e1d8ae8..3001ce4b7aa 100644
--- a/backends/platform/atari/osystem_atari.cpp
+++ b/backends/platform/atari/osystem_atari.cpp
@@ -428,12 +428,28 @@ Common::String OSystem_Atari::getDefaultConfigFileName() {
 }
 
 void OSystem_Atari::update() {
-	// FIXME: SCI MIDI calls delayMillis() from a timer leading to an infitite recursion loop here
-	const Common::ConfigManager::Domain *activeDomain = ConfMan.getActiveDomain();
-	if (!activeDomain || activeDomain->getValOrDefault("engineid") != "sci" || !_inTimer) {
-		_inTimer = true;
+	// avoid a recursion loop if a timer callback decides to call OSystem::delayMillis()
+	static bool inTimer = false;
+	// flag to print the warning only once
+	static bool checkGameDomain = true;
+
+	if (!checkGameDomain) {
+		checkGameDomain = g_system->isOverlayVisible();
+	}
+
+	if (!inTimer) {
+		inTimer = true;
 		((DefaultTimerManager *)_timerManager)->checkTimers();
-		_inTimer = false;
+		inTimer = false;
+	} else if (checkGameDomain) {
+		const Common::ConfigManager::Domain *activeDomain = ConfMan.getActiveDomain();
+		if (activeDomain) {
+			warning("%s/%s calls update() from timer",
+				activeDomain->getValOrDefault("engineid").c_str(),
+				activeDomain->getValOrDefault("gameid").c_str());
+
+			checkGameDomain = false;
+		}
 	}
 
 	if (_useNullMixer)
diff --git a/backends/platform/atari/osystem_atari.h b/backends/platform/atari/osystem_atari.h
index cb02c3f849c..00ad5af1945 100644
--- a/backends/platform/atari/osystem_atari.h
+++ b/backends/platform/atari/osystem_atari.h
@@ -54,7 +54,6 @@ private:
 	bool _videoInitialized = false;
 	bool _timerInitialized = false;
 	bool _useNullMixer = false;
-	bool _inTimer = false;
 
 	int _app_id = -1;
 	int16 _vdi_handle;
diff --git a/backends/platform/atari/readme.txt b/backends/platform/atari/readme.txt
index d56d3901675..4984e216602 100644
--- a/backends/platform/atari/readme.txt
+++ b/backends/platform/atari/readme.txt
@@ -45,14 +45,15 @@ Hardware requirements
 ---------------------
 
 This port requires an Atari computer with TT or Falcon compatible video modes
-with at least 4+32 MB of RAM. It runs fine also in Hatari and ARAnyM but in
+with at least 4+32 MB of RAM (this applies to the "slim" version, the "fat" one
+requires more due its file size). It runs fine also in Hatari and ARAnyM but in
 case of ARAnyM don't forget to disable fVDI to show Videl output.
 
 
 Main features
 -------------
 
-- Optimized for the Atari TT/Falcon: ideally the CT60/CT63/CT60e but some games
+- Optimised for the Atari TT/Falcon: ideally the CT60/CT63/CT60e but some games
   run fine on the AfterBurner040, CT2/DFB at 50 MHz, Speedy at 48 MHz or even less!
 
 - Full support for the SuperVidel, incl. the SuperBlitter (!)
@@ -76,7 +77,8 @@ Main features
 
 - Native MIDI output (if present).
 
-- AdLib emulation works nicely with many games without noticeable slow downs.
+- AdLib emulation works nicely with many games without noticeable slow downs
+  (on a poweful machine, ideally 68060/66 MHz).
 
 
 Platform-specific features outside the GUI
@@ -345,7 +347,7 @@ Known issues
   B interrupt.
 
 - horizontal screen shaking doesn't work on TT because TT Shifter doesn't
-  support fine scrolling.
+  support fine scrolling. However it is "emulated" via vertical shaking.
 
 - tooltips in overlay are sometimes drawn with corrupted background.
 
@@ -356,6 +358,20 @@ Known issues
   folder where *.wav files are located! For MI2 just use the DOS version,
   there are no CD tracks available. :(
 
+- following engines have been explicitly disabled:
+	- Cine (2 games)
+		- incompatible with other engines / prone to freezes
+		- https://wiki.scummvm.org/index.php?title=Cine
+	- Director (many games)
+		- huge game list slows detection for other games, and would require
+		  require (currently missing) localization support
+		- only small subset of games actually supported by upstream, but none
+		  of them detected on TOS 8+3 file system
+		- https://wiki.scummvm.org/index.php?title=Director
+	- Hugo (3 games)
+		- Uses (lot of) overlay dialogs which are problematic for Atari backend
+		- Engine GUI (for save/load/etc) does not support 8-bit screens
+		- https://wiki.scummvm.org/index.php?title=Hugo
 
 Future plans
 ------------
@@ -385,7 +401,10 @@ Closing words
 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
+Many optimisations and improvements wouldn't be possible without help of Eero
+Tamminen, so thank you for all the help.
+
+Miro Kropacek a.k.a. MiKRO / Mystic Bytes, XX.XX.2023
 Kosice / Slovakia
 miro.kropacek at gmail.com
 http://mikro.atari.org




More information about the Scummvm-git-logs mailing list