[Scummvm-git-logs] scummvm master -> 975ba55f8b7156fe821b24e822263fa1cc724725

bluegr noreply at scummvm.org
Thu Jul 11 05:02:55 UTC 2024


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

Summary:
fc1bc6a9fd SCI: move rgb and custom palette rendering into GfxDriver classes
bc6573237d SCI: add SCI 1 video mode detection for EGA and VGA grey scale
975ba55f8b SCI: add SCI 1 video modes for EGA and VGA grey scale


Commit: fc1bc6a9fd7018c74b4875d26ea43952beb1f427
    https://github.com/scummvm/scummvm/commit/fc1bc6a9fd7018c74b4875d26ea43952beb1f427
Author: athrxx (athrxx at scummvm.org)
Date: 2024-07-11T08:02:49+03:00

Commit Message:
SCI: move rgb and custom palette rendering into GfxDriver classes

This enables the rgb rendering for all video modes and cleans up the
engine a lot, since the engine can now be more or less agnostic of the
screen pixel format.

Changed paths:
    engines/sci/engine/kvideo.cpp
    engines/sci/graphics/gfxdrivers.cpp
    engines/sci/graphics/gfxdrivers.h
    engines/sci/graphics/palette.cpp
    engines/sci/graphics/screen.cpp
    engines/sci/graphics/screen.h
    engines/sci/sci.cpp


diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp
index 74372731931..5423a6e24ac 100644
--- a/engines/sci/engine/kvideo.cpp
+++ b/engines/sci/engine/kvideo.cpp
@@ -19,11 +19,11 @@
  *
  */
 
-#include "engines/util.h"
 #include "sci/engine/kernel.h"
 #include "sci/engine/state.h"
 #include "sci/graphics/helpers.h"
 #include "sci/graphics/cursor.h"
+#include "sci/graphics/gfxdrivers.h"
 #include "sci/graphics/palette.h"
 #include "sci/graphics/screen.h"
 #include "sci/util.h"
@@ -87,9 +87,9 @@ void playVideo(Video::VideoDecoder &videoDecoder) {
 					const SciSpan<const byte> input((const byte *)frame->getPixels(), frame->w * frame->h * bytesPerPixel);
 					// TODO: Probably should do aspect ratio correction in KQ6
 					g_sci->_gfxScreen->scale2x(input, *scaleBuffer, videoDecoder.getWidth(), videoDecoder.getHeight(), bytesPerPixel);
-					g_sci->_gfxScreen->copyVideoFrameToScreen(scaleBuffer->getUnsafeDataAt(0, pitch * height), pitch, rect, bytesPerPixel == 1);
+					g_sci->_gfxScreen->copyVideoFrameToScreen(scaleBuffer->getUnsafeDataAt(0, pitch * height), pitch, rect);
 				} else {
-					g_sci->_gfxScreen->copyVideoFrameToScreen((const byte *)frame->getPixels(), frame->pitch, rect, bytesPerPixel == 1);
+					g_sci->_gfxScreen->copyVideoFrameToScreen((const byte *)frame->getPixels(), frame->pitch, rect);
 				}
 
 				if (videoDecoder.hasDirtyPalette()) {
@@ -122,9 +122,6 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) {
 	if (reshowCursor)
 		g_sci->_gfxCursor->kernelHide();
 
-	uint16 screenWidth = g_system->getWidth();
-	uint16 screenHeight = g_system->getHeight();
-
 	Common::ScopedPtr<Video::VideoDecoder> videoDecoder;
 
 	bool switchedGraphicsMode = false;
@@ -143,7 +140,7 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) {
 				for (it = supportedFormats.begin(); it != supportedFormats.end(); ++it) {
 					if (it->bytesPerPixel == 2) {
 						const Graphics::PixelFormat format = *it;
-						initGraphics(screenWidth, screenHeight, &format);
+						g_sci->_gfxScreen->gfxDriver()->initScreen(&format);
 						switchedGraphicsMode = true;
 						break;
 					}
@@ -199,7 +196,7 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) {
 		// HACK: Switch back to 8bpp if we played a true color video.
 		// We also won't be copying the screen to the SCI screen...
 		if (switchedGraphicsMode)
-			initGraphics(screenWidth, screenHeight);
+			g_sci->_gfxScreen->gfxDriver()->initScreen();
 		else if (is8bit) {
 			g_sci->_gfxScreen->kernelSyncWithFramebuffer();
 			g_sci->_gfxPalette16->kernelSyncScreenPalette();
diff --git a/engines/sci/graphics/gfxdrivers.cpp b/engines/sci/graphics/gfxdrivers.cpp
index 0eb323ffbdc..4e69a8bc6c4 100644
--- a/engines/sci/graphics/gfxdrivers.cpp
+++ b/engines/sci/graphics/gfxdrivers.cpp
@@ -22,25 +22,54 @@
 #include "common/events.h"
 #include "common/system.h"
 
+#include "engines/util.h"
+
 #include "graphics/cursorman.h"
 #include "graphics/paletteman.h"
 
 #include "sci/graphics/gfxdrivers.h"
 #include "sci/resource/resource.h"
 #include "sci/sci.h"
-#include "sci/version.h"
 
 namespace Sci {
 
+#define GFXDRV_ASSERT_READY \
+	if (!_ready) \
+		error("%s: initScreen() must be called before using this method", __FUNCTION__)
+#define GFXDRV_ASSERT_ALIGNED \
+	assert(!(w & _hAlign) && !(x & _hAlign))
+
 Common::Point GfxDriver::getMousePos() const {
 	return g_system->getEventManager()->getMousePos();
 }
 
 void GfxDriver::clearRect(const Common::Rect &r) const {
+	GFXDRV_ASSERT_READY;
 	g_system->fillScreen(r, 0);
 }
 
-GfxDefaultDriver::GfxDefaultDriver(uint16 screenWidth, uint16 screenHeight) : GfxDriver(screenWidth, screenHeight, 0, 1) {
+void GfxDriver::copyCurrentPalette(byte *dest, int start, int num) const {
+	assert(dest);
+	assert(start + num <= 256);
+	g_system->getPaletteManager()->grabPalette(dest, start, num);
+}
+
+bool GfxDriver::checkDriver(const char *const *driverNames, int listSize) {
+	Common::String missing;
+	while (listSize-- && *driverNames) {
+		if (Common::File::exists(*driverNames))
+			return true;
+		if (!missing.empty())
+			missing += " or ";
+		missing += "'" + Common::String(*driverNames) + "'";
+		++driverNames;
+	}
+	warning("Driver file %s not found. Starting game in EGA mode", missing.c_str());
+	return false;
+}
+
+GfxDefaultDriver::GfxDefaultDriver(uint16 screenWidth, uint16 screenHeight, bool rgbRendering) : GfxDriver(screenWidth, screenHeight, 0, 1),
+	_srcPixelSize(1), _requestRGBMode(rgbRendering), _compositeBuffer(nullptr), _currentBitmap(nullptr), _internalPalette(nullptr), _currentPalette(nullptr) {
 	switch (g_sci->getResMan()->getViewType()) {
 	case kViewEga:
 		_numColors = 16;	// QFG PC-98 with 8 colors also reports 16 here
@@ -63,52 +92,277 @@ GfxDefaultDriver::GfxDefaultDriver(uint16 screenWidth, uint16 screenHeight) : Gf
 		error("GfxDefaultDriver: Unknown view type");
 }
 
-void GfxDefaultDriver::setPalette(const byte *colors, uint start, uint num) {
-	g_system->getPaletteManager()->setPalette(colors, start, num);
+GfxDefaultDriver::~GfxDefaultDriver() {
+	delete[] _compositeBuffer;
+	delete[] _currentBitmap;
+	delete[] _internalPalette;
+	delete[] _currentPalette;
+}
+
+void GfxDefaultDriver::initScreen(const Graphics::PixelFormat *srcRGBFormat) {
+	Graphics::PixelFormat format8bt(Graphics::PixelFormat::createFormatCLUT8());
+	initGraphics(_screenW, _screenH, srcRGBFormat ? srcRGBFormat : (_requestRGBMode ? nullptr : &format8bt));
+	_format = g_system->getScreenFormat();
+
+	int srcPixelSize = srcRGBFormat ? _format.bytesPerPixel : 1;
+	if (srcPixelSize != _srcPixelSize || _pixelSize != _format.bytesPerPixel) {
+		delete[] _compositeBuffer;
+		delete[] _currentBitmap;
+		delete[] _internalPalette;
+		delete[] _currentPalette;
+		_compositeBuffer = _currentBitmap = _internalPalette = _currentPalette = nullptr;
+	}
+
+	_pixelSize = _format.bytesPerPixel;
+	_srcPixelSize = srcPixelSize;
+
+	if (_requestRGBMode && _pixelSize == 1)
+		warning("GfxDefaultDriver::initScreen(): RGB rendering not available in this ScummVM build");
+
+	if (_pixelSize != _srcPixelSize) {
+		uint32 bufferSize = _screenW * _screenH * _pixelSize;
+		_compositeBuffer = new byte[bufferSize]();
+		assert(_compositeBuffer);
+	}
+
+	if (_numColors > 16 || _pixelSize > 1) {
+		// Not needed for SCI0, except for rgb rendering
+		_currentBitmap = new byte[_screenW * _screenH * _srcPixelSize]();
+		assert(_currentBitmap);
+		_currentPalette = new byte[256 * 3]();
+		assert(_currentPalette);
+		if (_pixelSize != _srcPixelSize) {
+			_internalPalette = new byte[256 * _pixelSize]();
+			assert(_internalPalette);
+		}
+	}
+
+	_ready = true;
+}
+
+void GfxDefaultDriver::setPalette(const byte *colors, uint start, uint num, bool update, const PaletteMod *palMods, const byte *palModMapping) {
+	GFXDRV_ASSERT_READY;
+	if (_pixelSize > 1) {
+		updatePalette(colors, start, num, (_srcPixelSize == _pixelSize) || (palMods != nullptr && palModMapping != nullptr));
+		if (update)
+			copyRectToScreen(_currentBitmap, _screenW, 0, 0, _screenW, _screenH, palMods, palModMapping);
+		CursorMan.replaceCursorPalette(_currentPalette, 0, 256);
+	} else {
+		g_system->getPaletteManager()->setPalette(colors, start, num);
+	}
+}
+
+
+void updateBitmapBuffer(byte *dst, int dstPitch, const byte *src, int srcPitch, int x, int y, int w, int h) {
+	if (!dst)
+		return;
+
+	if (w == srcPitch && w == dstPitch) {
+		memcpy(dst + y * w, src, w * h);
+	} else {
+		const byte *s = src;
+		byte *d = dst + y * dstPitch + x;
+		for (int i = 0; i < h; ++i) {
+			memcpy(d, s, w);
+			s += srcPitch;
+			d += dstPitch;
+		}
+	}
 }
 
-void GfxDefaultDriver::copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h) {
+void GfxDefaultDriver::copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h, const PaletteMod *palMods, const byte *palModMapping) {
+	GFXDRV_ASSERT_READY;
+	GFXDRV_ASSERT_ALIGNED; // We can't fix the boundaries here, it has to happen before this call
+
+	if (src != _currentBitmap)
+		updateBitmapBuffer(_currentBitmap, _screenW * _srcPixelSize, src, pitch, x * _srcPixelSize, y, w * _srcPixelSize, h);
+
+	if (_pixelSize != _srcPixelSize) {
+		generateOutput(_compositeBuffer, src, pitch, w, h, palMods, palModMapping + y * pitch + x);
+		src = _compositeBuffer;
+		pitch = w * _pixelSize;
+	}
+
 	g_system->copyRectToScreen(src, pitch, x, y, w, h);
 }
 
 void GfxDefaultDriver::replaceCursor(const void *cursor, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor) {
+	GFXDRV_ASSERT_READY;
 	CursorMan.replaceCursor(cursor, w, h, hotspotX, hotspotY, keycolor);
 }
 
-SCI0_DOSPreVGADriver::SCI0_DOSPreVGADriver(int numColors, int screenW, int screenH, int horizontalAlignment) : GfxDriver(screenW, screenH, numColors, horizontalAlignment), _palNeedUpdate(true), _colors(nullptr), _compositeBuffer(nullptr) {
-	_compositeBuffer = new byte[screenW * screenH]();
+void GfxDefaultDriver::copyCurrentBitmap(byte *dest, uint32 size) const {
+	GFXDRV_ASSERT_READY;
+	assert(dest);
+	assert(size <= (uint32)(_screenW * _screenH));
+
+	// SCI 0 should not make calls to this method (except when using palette mods), but we have to know if it does...
+	if (!_currentBitmap)
+		error("GfxDefaultDriver::copyDataFromCurrentBitmap(): unexpected call");
+
+	// I have changed the implementation a bit from what the engine did before. For non-rgb rendering
+	// it would call OSystem::lockScreen() and then memcpy the data from there (which avoided the need
+	// for the extra bitmap buffer). However, OSystem::lockScreen() is meant more as an update method
+	// for the screen, the call to OSystem::unlockScreen() will turn the whole screen dirty (to be
+	// updated on the next OSysten::updateScreen() call. This is not what we need here, so I rather use
+	// the extra bitmap buffer (which is required for rgb rendering anyway).
+	memcpy(dest, _currentBitmap, size);
 }
 
-SCI0_DOSPreVGADriver::~SCI0_DOSPreVGADriver() {
-	delete[] _compositeBuffer;
+void GfxDefaultDriver::copyCurrentPalette(byte *dest, int start, int num) const {
+	GFXDRV_ASSERT_READY;
+
+	if (_pixelSize == 1) {
+		GfxDriver::copyCurrentPalette(dest, start, num);
+		return;
+	}
+
+	assert(dest);
+	assert(_currentPalette);
+	assert(start + num <= 256);
+	memcpy(dest + start * 3, _currentPalette + start * 3, num * 3);
 }
 
-bool SCI0_DOSPreVGADriver::checkDriver(const char *const *driverNames, int listSize) {
-	Common::String missing;
-	while (listSize-- && *driverNames) {
-		if (Common::File::exists(*driverNames))
-			return true;
-		if (!missing.empty())
-			missing += " or ";
-		missing += "'" + Common::String(*driverNames) + "'";
-		++driverNames;
+template <typename T> void updateRGBPalette(byte *dest, const byte *src, uint start, uint num, Graphics::PixelFormat &f) {
+	T *dst = &reinterpret_cast<T*>(dest)[start];
+	for (uint i = 0; i < num; ++i) {
+		*dst++ = f.RGBToColor(src[0], src[1], src[2]);
+		src += 3;
+	}
+}
+
+void GfxDefaultDriver::updatePalette(const byte *colors, uint start, uint num, bool skipRGBPalette) {
+	memcpy(_currentPalette + start * 3, colors, num * 3);
+	// If palette mods are on we don't need to update the internal palette,
+	// since the colors have to be generated on the fly anyway.
+	if (skipRGBPalette)
+		return;
+	if (_pixelSize == 4)
+		updateRGBPalette<uint32>(_internalPalette, colors, start, num, _format);
+	else if (_pixelSize == 2)
+		updateRGBPalette<uint16>(_internalPalette, colors, start, num, _format);
+	else
+		error("GfxDefaultDriver::updatePalette(): Unsupported pixel size %d", _pixelSize);
+}
+
+template <typename T> void render(byte *dst, const byte *src, int pitch, int w, int h, const byte *pal) {
+	T *d = reinterpret_cast<T*>(dst);
+	const T *p = reinterpret_cast<const T*>(pal);
+	const byte *s = src;
+	pitch -= w;
+
+	while (h--) {
+		for (int i = 0; i < w; ++i)
+			*d++ = p[*s++];
+		s += pitch;
 	}
-	warning("Driver file %s not found. Starting game in EGA mode", missing.c_str());
-	return false;
+}
+
+#define applyMod(a, b) MIN<uint>(a * (128 + b) / 128, 255)
+template <typename T> void renderMod(byte *dst, const byte *src, int pitch, int w, int h, const byte *pal, Graphics::PixelFormat &f, const PaletteMod *mods, const byte *modMapping) {
+	T *d = reinterpret_cast<T*>(dst);
+	const byte *s1 = src;
+	const byte *s2 = modMapping;
+	pitch -= w;
+
+	while (h--) {
+		for (int i = 0; i < w; ++i) {
+			const byte *col = &pal[*s1++ * 3];
+			byte m = *s2++;
+			if (m)
+				*d++ = f.RGBToColor(applyMod(col[0], mods[m].r), applyMod(col[1], mods[m].g), applyMod(col[2], mods[m].b));
+			else
+				*d++ = f.RGBToColor(col[0], col[1], col[2]);
+		}
+		s1 += pitch;
+		s2 += pitch;
+	}
+}
+#undef applyMod
+
+void GfxDefaultDriver::generateOutput(byte *dst, const byte *src, int pitch, int w, int h, const PaletteMod *palMods, const byte *palModMapping) {
+	if (palMods && palModMapping) {
+		if (_pixelSize == 2)
+			renderMod<uint16>(dst, src, pitch, w, h, _currentPalette, _format, palMods, palModMapping);
+		else if (_pixelSize == 4)
+			renderMod<uint32>(dst, src, pitch, w, h, _currentPalette, _format, palMods, palModMapping);
+		else
+			error("GfxDefaultDriver::generateOutput(): Unsupported pixel size %d", _pixelSize);
+	} else {
+		if (_pixelSize == 2)
+			render<uint16>(dst, src, pitch, w, h, _internalPalette);
+		else if (_pixelSize == 4)
+			render<uint32>(dst, src, pitch, w, h, _internalPalette);
+		else
+			error("GfxDefaultDriver::generateOutput(): Unsupported pixel size %d", _pixelSize);
+	}
+}
+
+SCI0_DOSPreVGADriver::SCI0_DOSPreVGADriver(int numColors, int screenW, int screenH, int horizontalAlignment, bool rgbRendering) :
+	GfxDriver(screenW, screenH, numColors, horizontalAlignment), _requestRGBMode(rgbRendering), _colors(nullptr), _compositeBuffer(nullptr), _internalPalette(nullptr) {
+}
+
+SCI0_DOSPreVGADriver::~SCI0_DOSPreVGADriver() {
+	delete[] _compositeBuffer;
+	delete[] _internalPalette;
 }
 
 void SCI0_DOSPreVGADriver::assignPalette(const byte *colors) {
 	_colors = colors;
 }
 
-void SCI0_DOSPreVGADriver::setPalette(const byte*, uint, uint) {
-	if (!_palNeedUpdate || !_colors)
+void SCI0_DOSPreVGADriver::initScreen(const Graphics::PixelFormat*) {
+	Graphics::PixelFormat format(Graphics::PixelFormat::createFormatCLUT8());
+	initGraphics(_screenW, _screenH, _requestRGBMode ? nullptr : &format);
+	format = g_system->getScreenFormat();
+	_pixelSize = format.bytesPerPixel;
+
+	if (_requestRGBMode && _pixelSize == 1)
+		warning("SCI0_DOSPreVGADriver::initScreen(): RGB rendering not available in this ScummVM build");
+
+	assert(_colors);
+	if (_pixelSize == 1) {
+		g_system->getPaletteManager()->setPalette(_colors, 0, _numColors);
+	} else {
+		byte *rgbpal = new byte[_numColors * _pixelSize]();
+		assert(rgbpal);
+
+		if (_pixelSize == 2)
+			updateRGBPalette<uint16>(rgbpal, _colors, 0, _numColors, format);
+		else if (_pixelSize == 4)
+			updateRGBPalette<uint32>(rgbpal, _colors, 0, _numColors, format);
+		else
+			error("SCI0_DOSPreVGADriver::initScreen(): Unsupported screen format");
+		_internalPalette = rgbpal;
+		CursorMan.replaceCursorPalette(_colors, 0, _numColors);
+	}
+
+	_compositeBuffer = new byte[_screenW * _screenH * _pixelSize]();
+	assert(_compositeBuffer);
+
+	setupRenderProc();
+
+	_ready = true;
+}
+
+void SCI0_DOSPreVGADriver::copyCurrentBitmap(byte*, uint32) const {
+	// This is not needed for SCI0
+	error("SCI0_DOSPreVGADriver::copyCurrentBitmap(): Not implemented");
+}
+
+void SCI0_DOSPreVGADriver::copyCurrentPalette(byte *dest, int start, int num) const {
+	GFXDRV_ASSERT_READY;
+
+	if (_pixelSize == 1) {
+		GfxDriver::copyCurrentPalette(dest, start, num);
 		return;
-	_palNeedUpdate = false;;
-	g_system->getPaletteManager()->setPalette(_colors, 0, _numColors);
+	}
+
+	assert(dest);
+	memcpy(dest + start * 3, _colors + start * 3, MIN<int>(num, _numColors) * 3);
 }
 
-SCI0_CGADriver::SCI0_CGADriver(bool emulateCGAModeOnEGACard) : SCI0_DOSPreVGADriver(4, 320, 200, 1), _cgaPatterns(nullptr), _disableMode5(emulateCGAModeOnEGACard) {
+SCI0_CGADriver::SCI0_CGADriver(bool emulateCGAModeOnEGACard, bool rgbRendering) : SCI0_DOSPreVGADriver(4, 320, 200, 1, rgbRendering), _cgaPatterns(nullptr), _disableMode5(emulateCGAModeOnEGACard) {
 	static const byte cgaColors[48] = {
 		/*
 		// Canonical CGA palette
@@ -207,39 +461,25 @@ SCI0_CGADriver::~SCI0_CGADriver() {
 	delete[] _cgaPatterns;
 }
 
-void SCI0_CGADriver::copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h) {
-	// We can't really properly fix the boundaries here, we have to do that before calling this function.
-	assert(!(w & _hAlign));
-	assert(!(x & _hAlign));
+void SCI0_CGADriver::copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h, const PaletteMod*, const byte*) {
+	GFXDRV_ASSERT_READY;
+	GFXDRV_ASSERT_ALIGNED; // We can't fix the boundaries here, it has to happen before this call
 
 	byte *dst = _compositeBuffer;
-	pitch -= w;
 	int ty = y;
 
 	for (int i = 0; i < h; ++i) {
-		int tx = x & 3;
-		++ty;
-		for (int ii = 0; ii < (w >> 1); ++ii) {
-			uint16 pattern = _cgaPatterns[((src[0] & 0x0f) << 4) | (src[1] & 0x0f)];
-			src += 2;
-			byte sh = (ty & 3) << 1;
-			byte lo = ((pattern & 0xff) >> sh) | ((pattern & 0xff) << (8 - sh));
-			byte hi = (pattern >> (8 + sh)) | ((pattern >> 8) << (8 - sh));
-			*dst++ = (lo >> (6 - (tx << 1))) & 3;
-			*dst++ = (hi >> (4 - (tx << 1))) & 3;
-			tx ^= 2;
-		}
+		_renderLine(dst, src, w, x & 3, ++ty, _cgaPatterns, _internalPalette);
 		src += pitch;
 	}
 
-	g_system->copyRectToScreen(_compositeBuffer, w, x, y, w, h);
+	g_system->copyRectToScreen(_compositeBuffer, w * _pixelSize, x, y, w, h);
 }
 
 void SCI0_CGADriver::replaceCursor(const void *cursor, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor) {
-	// Instead of implementing the original cursor rendering code, we rely on the 8 bit cursor that
-	// has already been generated by the engine. Of course, that code could be moved to the EGA
-	// driver class and implemented again for each mode, but I don't see the benefit. Instead,
-	// we simply convert the colors as needed...
+	GFXDRV_ASSERT_READY;
+	// Instead of implementing the original cursor rendering code, we rely on the 8 bit cursor
+	// that has already been generated by the engine. We simply convert the colors as needed...
 	assert(keycolor == 1);
 	const byte *s = reinterpret_cast<const byte*>(cursor);
 	byte *d = _compositeBuffer;
@@ -249,6 +489,42 @@ void SCI0_CGADriver::replaceCursor(const void *cursor, uint w, uint h, int hotsp
 	CursorMan.replaceCursor(_compositeBuffer, w, h, hotspotX, hotspotY, keycolor);
 }
 
+
+template <typename T> void cgaRenderLine(byte *&dst, const byte *src, int w, int tx, int ty, const uint16 *patterns, const byte *pal) {
+	T *d = reinterpret_cast<T*>(dst);
+	const T *p = reinterpret_cast<const T*>(pal);
+	w >>= 1;
+
+	for (int i = 0; i < w; ++i) {
+		uint16 pattern = patterns[((src[0] & 0x0f) << 4) | (src[1] & 0x0f)];
+		src += 2;
+		byte sh = (ty & 3) << 1;
+		byte lo = ((pattern & 0xff) >> sh) | ((pattern & 0xff) << (8 - sh));
+		byte hi = (pattern >> (8 + sh)) | ((pattern >> 8) << (8 - sh));
+		if (sizeof(T) == 1) {
+			*d++ = (lo >> (6 - (tx << 1))) & 3;
+			*d++ = (hi >> (4 - (tx << 1))) & 3;
+		} else {
+			*d++ = p[(lo >> (6 - (tx << 1))) & 3];
+			*d++ = p[(hi >> (4 - (tx << 1))) & 3];
+		}
+		tx ^= 2;
+	}
+
+	dst = reinterpret_cast<byte*>(d);
+}
+
+void SCI0_CGADriver::setupRenderProc() {
+	static const LineProc lineProcs[] = {
+		&cgaRenderLine<byte>,
+		&cgaRenderLine<uint16>,
+		&cgaRenderLine<uint32>
+	};
+
+	assert((_pixelSize >> 1) < ARRAYSIZE(lineProcs));
+	_renderLine = lineProcs[_pixelSize >> 1];
+}
+
 const char *SCI0_CGADriver::_driverFile = "CGA320C.DRV";
 
 const byte *monochrInit(const char *drvFile, bool &earlyVersion) {
@@ -295,7 +571,7 @@ const byte *monochrInit(const char *drvFile, bool &earlyVersion) {
 	return result;
 }
 
-SCI0_CGABWDriver::SCI0_CGABWDriver(uint32 monochromeColor) : SCI0_DOSPreVGADriver(2, 640, 400, 1), _monochromePatterns(nullptr), _earlyVersion(false) {
+SCI0_CGABWDriver::SCI0_CGABWDriver(uint32 monochromeColor, bool rgbRendering) : SCI0_DOSPreVGADriver(2, 640, 400, 1, rgbRendering), _monochromePatterns(nullptr), _earlyVersion(false) {
 	_monochromePalette[0] = _monochromePalette[1] = _monochromePalette[2] = 0;
 	_monochromePalette[3] = (monochromeColor >> 16) & 0xff;
 	_monochromePalette[4] = (monochromeColor >> 8) & 0xff;
@@ -310,54 +586,28 @@ SCI0_CGABWDriver::~SCI0_CGABWDriver() {
 	delete[] _monochromePatterns;
 }
 
-void SCI0_CGABWDriver::copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h) {
-	// We can't really properly fix the boundaries here, we have to do that before calling this function.
-	assert(!(w & _hAlign));
-	assert(!(x & _hAlign));
+void SCI0_CGABWDriver::copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h, const PaletteMod*, const byte*) {
+	GFXDRV_ASSERT_READY;
+	GFXDRV_ASSERT_ALIGNED; // We can't fix the boundaries here, it has to happen before this call
 
-	byte *dst1 = _compositeBuffer;
-	byte *dst2 = _compositeBuffer + (w << 1);
+	byte *dst = _compositeBuffer;
 	int ty = y & 7;
-	pitch -= w;
+	if (_earlyVersion)
+		++ty;
 
 	for (int i = 0; i < h; ++i) {
-		int tx = x & 3;
-		if (_earlyVersion) {
-			++ty;
-			for (int ii = 0; ii < (w >> 1); ++ii) {
-				uint16 p16 = reinterpret_cast<const uint16*>(_monochromePatterns)[((src[0] & 0x0f) << 4) | (src[1] & 0x0f)];
-				src += 2;
-				byte sh = (ty & 3) << 1;
-				byte lo = ((p16 & 0xff) >> sh) | ((p16 & 0xff) << (8 - sh));
-				byte hi = (p16 >> (8 + sh)) | ((p16 >> 8) << (8 - sh));
-				*dst1++ = *dst2++ = ((lo >> (6 - (tx << 1))) >> 1) & 1;
-				*dst1++ = *dst2++ = (lo >> (6 - (tx << 1))) & 1;
-				*dst1++ = *dst2++ = ((hi >> (4 - (tx << 1))) >> 1) & 1;
-				*dst1++ = *dst2++ = (hi >> (4 - (tx << 1))) & 1;
-				tx ^= 2;
-			}
-		} else {
-			for (int ii = 0; ii < w; ++ii) {
-				byte p = _monochromePatterns[((*src++ & 0x0f) << 3) + ty] >> (6 - (tx << 1));
-				*dst1++ = *dst2++ = (p >> 1) & 1;
-				*dst1++ = *dst2++ = p & 1;
-				tx = (tx + 1) & 3;
-			}
-			ty = (ty + 1) & 7;
-		}
+		_renderLine(dst, src, w, x & 3, ty, _monochromePatterns, _internalPalette);
+		ty = (ty + 1) & 7;
 		src += pitch;
-		dst1 += (w << 1);
-		dst2 += (w << 1);
 	}
 
-	g_system->copyRectToScreen(_compositeBuffer, w << 1, x << 1, y << 1, w << 1, h << 1);
+	g_system->copyRectToScreen(_compositeBuffer, (w << 1) * _pixelSize, x << 1, y << 1, w << 1, h << 1);
 }
 
 void SCI0_CGABWDriver::replaceCursor(const void *cursor, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor) {
+	GFXDRV_ASSERT_READY;
 	// Instead of implementing the original cursor rendering code, we rely on the 8 bit cursor that
-	// has already been generated by the engine. Of course, that code could be moved to the EGA
-	// driver class and implemented again for each mode, but I don't see the benefit. Instead,
-	// we simply convert the colors as needed and scale the cursor...
+	// has already been generated by the engine. We simply convert the colors as needed and scale the cursor...
 	assert(keycolor == 1);
 	keycolor = 0x0f;
 	w <<= 1;
@@ -390,9 +640,77 @@ void SCI0_CGABWDriver::clearRect(const Common::Rect &r) const {
 	GfxDriver::clearRect(r2);
 }
 
+template <typename T> void cgabwRenderLine_v1(byte *&dst, const byte *src, int w, int tx, int ty, const byte *patterns, const byte *pal) {
+	const T *p = reinterpret_cast<const T*>(pal);
+	const uint16 *patterns16 = reinterpret_cast<const uint16*>(patterns);
+	T *d1 = reinterpret_cast<T*>(dst);
+	T *d2 = d1 + (w << 1);
+	w >>= 1;
+
+	for (int i = 0; i < w; ++i) {
+		uint16 pt = patterns16[((src[0] & 0x0f) << 4) | (src[1] & 0x0f)];
+		src += 2;
+		byte sh = (ty & 3) << 1;
+		byte lo = ((pt & 0xff) >> sh) | ((pt & 0xff) << (8 - sh));
+		byte hi = (pt >> (8 + sh)) | ((pt >> 8) << (8 - sh));
+		if (sizeof(T) == 1) {
+			*d1++ = *d2++ = ((lo >> (6 - (tx << 1))) >> 1) & 1;
+			*d1++ = *d2++ = (lo >> (6 - (tx << 1))) & 1;
+			*d1++ = *d2++ = ((hi >> (4 - (tx << 1))) >> 1) & 1;
+			*d1++ = *d2++ = (hi >> (4 - (tx << 1))) & 1;
+		} else {
+			*d1++ = *d2++ = p[((lo >> (6 - (tx << 1))) >> 1) & 1];
+			*d1++ = *d2++ = p[(lo >> (6 - (tx << 1))) & 1];
+			*d1++ = *d2++ = p[((hi >> (4 - (tx << 1))) >> 1) & 1];
+			*d1++ = *d2++ = p[(hi >> (4 - (tx << 1))) & 1];
+		}
+		tx ^= 2;
+	}
+
+	dst = reinterpret_cast<byte*>(d2);
+}
+
+template <typename T> void cgabwRenderLine_v2(byte *&dst, const byte *src, int w, int tx, int ty, const byte *patterns, const byte *pal) {
+	const T *p = reinterpret_cast<const T*>(pal);
+	T *d1 = reinterpret_cast<T*>(dst);
+	T *d2 = d1 + (w << 1);
+
+	for (int i = 0; i < w; ++i) {
+		byte pt = patterns[((*src++ & 0x0f) << 3) + ty] >> (6 - (tx << 1));
+		if (sizeof(T) == 1) {
+			*d1++ = *d2++ = (pt >> 1) & 1;
+			*d1++ = *d2++ = pt & 1;
+		} else {
+			*d1++ = *d2++ = p[(pt >> 1) & 1];
+			*d1++ = *d2++ = p[pt & 1];
+		}
+		tx = (tx + 1) & 3;
+	}
+
+	dst = reinterpret_cast<byte*>(d2);
+}
+
+void SCI0_CGABWDriver::setupRenderProc() {
+	static const LineProc lineProcs[] = {
+		&cgabwRenderLine_v1<byte>,
+		&cgabwRenderLine_v1<uint16>,
+		&cgabwRenderLine_v1<uint32>,
+		&cgabwRenderLine_v2<byte>,
+		&cgabwRenderLine_v2<uint16>,
+		&cgabwRenderLine_v2<uint32>
+	};
+
+	int t = _pixelSize >> 1;
+	if (!_earlyVersion)
+		t += 3;
+
+	assert(t < ARRAYSIZE(lineProcs));
+	_renderLine = lineProcs[t];
+}
+
 const char *SCI0_CGABWDriver::_driverFiles[2] = { "CGA320BW.DRV", "CGA320M.DRV" };
 
-SCI0_HerculesDriver::SCI0_HerculesDriver(uint32 monochromeColor, bool cropImage) : SCI0_DOSPreVGADriver(2, cropImage ? 640 : 720, cropImage ? 300 : 350, 0),
+SCI0_HerculesDriver::SCI0_HerculesDriver(uint32 monochromeColor, bool rgbRendering, bool cropImage) : SCI0_DOSPreVGADriver(2, cropImage ? 640 : 720, cropImage ? 300 : 350, 0, rgbRendering),
 	_centerX(cropImage ? 0 : 40), _centerY(cropImage ? 0 : 25), _monochromePatterns(nullptr) {
 	_monochromePalette[0] = _monochromePalette[1] = _monochromePalette[2] = 0;
 	_monochromePalette[3] = (monochromeColor >> 16) & 0xff;
@@ -409,10 +727,9 @@ SCI0_HerculesDriver::~SCI0_HerculesDriver() {
 	delete[] _monochromePatterns;
 }
 
-void SCI0_HerculesDriver::copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h) {
-	// We can't really properly fix the boundaries here, we have to do that before calling this function.
-	assert(!(w & _hAlign));
-	assert(!(x & _hAlign));
+void SCI0_HerculesDriver::copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h, const PaletteMod*, const byte*) {
+	GFXDRV_ASSERT_READY;
+	GFXDRV_ASSERT_ALIGNED; // We can't fix the boundaries here, it has to happen before this call
 
 	byte *dst = _compositeBuffer;
 	byte sw = y & 1;
@@ -422,14 +739,7 @@ void SCI0_HerculesDriver::copyRectToScreen(const byte *src, int pitch, int x, in
 
 	for (int i = 0; i < h; ++i) {
 		const byte *src2 = src;
-		int tx = x & 3;
-		for (int ii = 0; ii < w; ++ii) {
-			byte p = _monochromePatterns[((*src2++ & 0x0f) << 3) + ty] >> (6 - (tx << 1));
-			*dst++ = (p >> 1) & 1;
-			*dst++ = p & 1;
-			tx = (tx + 1) & 3;
-		}
-
+		_renderLine(dst, src2, w, x & 3, ty, _monochromePatterns, _internalPalette);
 		ty = (ty + 1) & 7;
 		++rh;
 
@@ -444,14 +754,13 @@ void SCI0_HerculesDriver::copyRectToScreen(const byte *src, int pitch, int x, in
 		}
 	}
 
-	g_system->copyRectToScreen(_compositeBuffer, w << 1, (x << 1) + _centerX, y + _centerY, w << 1, rh);
+	g_system->copyRectToScreen(_compositeBuffer, (w << 1) * _pixelSize, (x << 1) + _centerX, y + _centerY, w << 1, rh);
 }
 
 void SCI0_HerculesDriver::replaceCursor(const void *cursor, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor) {
+	GFXDRV_ASSERT_READY;
 	// Instead of implementing the original cursor rendering code, we rely on the 8 bit cursor that
-	// has already been generated by the engine. Of course, that code could be moved to the EGA
-	// driver class and implemented again for each mode, but I don't see the benefit. Instead,
-	// we simply convert the colors as needed and scale the cursor...
+	// has already been generated by the engine. We simply convert the colors as needed and scale the cursor...
 	assert(keycolor == 1);
 	keycolor = 0x0f;
 	int alt = 0;
@@ -488,6 +797,39 @@ void SCI0_HerculesDriver::clearRect(const Common::Rect &r) const {
 	GfxDriver::clearRect(r2);
 }
 
+template <typename T> void herculesRenderLine(byte *&dst, const byte *src, int w, int tx, int ty, const byte *patterns, const byte *pal) {
+	T *d = reinterpret_cast<T*>(dst);
+	const T *p = reinterpret_cast<const T*>(pal);
+
+	for (int i = 0; i < w; ++i) {
+		byte pt = patterns[((*src++ & 0x0f) << 3) + ty] >> (6 - (tx << 1));
+		if (sizeof(T) == 1) {
+			*d++ = (pt >> 1) & 1;
+			*d++ = pt & 1;
+		} else {
+			*d++ = p[(pt >> 1) & 1];
+			*d++ = p[pt & 1];
+		}
+		tx = (tx + 1) & 3;
+	}
+
+	dst = reinterpret_cast<byte*>(d);
+}
+
+void SCI0_HerculesDriver::setupRenderProc() {
+	static const LineProc lineProcs[] = {
+		&herculesRenderLine<byte>,
+		&herculesRenderLine<uint16>,
+		&herculesRenderLine<uint32>
+	};
+
+	assert((_pixelSize >> 1) < ARRAYSIZE(lineProcs));
+	_renderLine = lineProcs[_pixelSize >> 1];
+}
+
 const char *SCI0_HerculesDriver::_driverFile = "HERCMONO.DRV";
 
+#undef GFXDRV_ASSERT_READY
+#undef GFXDRV_ASEERT_ALIGNED
+
 } // End of namespace Sci
diff --git a/engines/sci/graphics/gfxdrivers.h b/engines/sci/graphics/gfxdrivers.h
index 89010329671..7d616c9cda8 100644
--- a/engines/sci/graphics/gfxdrivers.h
+++ b/engines/sci/graphics/gfxdrivers.h
@@ -23,102 +23,134 @@
 #define SCI_GRAPHICS_GFXDRIVERS_H
 
 #include "common/rect.h"
+#include "graphics//pixelformat.h"
 
 namespace Sci {
 
+struct PaletteMod;
+
 class GfxDriver {
 public:
-	GfxDriver(uint16 screenWidth, uint16 screenHeight, int numColors, int horizontalAlignment) : _screenW(screenWidth), _screenH(screenHeight), _numColors(numColors), _hAlign(horizontalAlignment) {}
+	GfxDriver(uint16 screenWidth, uint16 screenHeight, int numColors, int horizontalAlignment) : _screenW(screenWidth), _screenH(screenHeight), _numColors(numColors), _hAlign(horizontalAlignment), _ready(false), _pixelSize(1) {}
 	virtual ~GfxDriver() {}
-
-	uint16 screenWidth() const { return _screenW; }
-	uint16 screenHeight() const { return _screenH; }
-	uint16 numColors() const { return _numColors; }
-	byte hAlignment() const { return _hAlign; }
-	
-	virtual bool allowRGBRendering() const = 0;
-	virtual void setPalette(const byte *colors, uint start, uint num) = 0;
-	virtual void copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h) = 0;
+	virtual void initScreen(const Graphics::PixelFormat *srcRGBFormat = nullptr) = 0; // srcRGBFormat: expect incoming data to have the specified rgb pixel format (used for Mac hicolor videos)
+	virtual void setPalette(const byte *colors, uint start, uint num, bool update, const PaletteMod *palMods, const byte *palModMapping) = 0;
+	virtual void copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h, const PaletteMod *palMods, const byte *palModMapping) = 0;
 	virtual void replaceCursor(const void *cursor, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor) = 0;
 	virtual Common::Point getMousePos() const;
 	virtual void clearRect(const Common::Rect &r) const;
-
+	virtual void copyCurrentBitmap(byte *dest, uint32 size) const = 0;
+	virtual void copyCurrentPalette(byte *dest, int start, int num) const;
+	virtual bool supportsPalIntensity() const = 0;
+	uint16 numColors() const { return _numColors; }
+	byte hAlignment() const { return _hAlign; }
+	byte pixelSize() const { return _pixelSize; }
 protected:
+	bool _ready;
+	static bool checkDriver(const char *const *driverNames, int listSize);
 	const uint16 _screenW;
 	const uint16 _screenH;
 	uint16 _numColors;
+	byte _pixelSize;
 	const byte _hAlign;
 };
 
-class GfxDefaultDriver final : public GfxDriver {
+class GfxDefaultDriver : public GfxDriver {
 public:
-	GfxDefaultDriver(uint16 screenWidth, uint16 screenHeight);
-	~GfxDefaultDriver() override {}
-	bool allowRGBRendering() const override { return true; }
-	void setPalette(const byte *colors, uint start, uint num) override;
-	void copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h) override;
+	GfxDefaultDriver(uint16 screenWidth, uint16 screenHeight, bool rgbRendering);
+	~GfxDefaultDriver() override;
+	void initScreen(const Graphics::PixelFormat *srcRGBFormat) override; // srcRGBFormat: expect incoming data to have the specified rgb pixel format (used for Mac hicolor videos)
+	void setPalette(const byte *colors, uint start, uint num, bool update, const PaletteMod *palMods, const byte *palModMapping) override;
+	void copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h, const PaletteMod *palMods, const byte *palModMapping) override;
 	void replaceCursor(const void *cursor, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor) override;
+	void copyCurrentBitmap(byte *dest, uint32 size) const override;
+	void copyCurrentPalette(byte *dest, int start, int num) const override;
+	bool supportsPalIntensity() const override { return true; }
+protected:
+	void updatePalette(const byte *colors, uint start, uint num, bool skipRGBPalette);
+	byte *_compositeBuffer;
+	byte *_currentBitmap;
+	byte *_currentPalette;
+	byte *_internalPalette;
+	Graphics::PixelFormat _format;
+	byte _srcPixelSize;
+	const bool _requestRGBMode;
+private:
+	void generateOutput(byte *dst, const byte *src, int pitch, int w, int h, const PaletteMod *palMods, const byte *palModMapping);
 };
 
 class SCI0_DOSPreVGADriver : public GfxDriver {
 public:
-	SCI0_DOSPreVGADriver(int numColors, int screenW, int screenH, int horizontalAlignment);
+	SCI0_DOSPreVGADriver(int numColors, int screenW, int screenH, int horizontalAlignment, bool rgbRendering);
 	~SCI0_DOSPreVGADriver() override;
-	bool allowRGBRendering() const override { return false; }
-	void setPalette(const byte*, uint, uint) override;
+	void initScreen(const Graphics::PixelFormat*) override;
+	void setPalette(const byte*, uint, uint, bool, const PaletteMod*, const byte*) {}
+	void copyCurrentBitmap(byte*, uint32) const override;
+	void copyCurrentPalette(byte *dest, int start, int num) const override;
+	bool supportsPalIntensity() const override { return false; }
 protected:
-	static bool checkDriver(const char *const *driverNames, int listSize);
 	void assignPalette(const byte *colors);
 	byte *_compositeBuffer;
+	const byte *_internalPalette;
 private:
-	bool _palNeedUpdate;
+	virtual void setupRenderProc() = 0;
+	const bool _requestRGBMode;
 	const byte *_colors;
 };
 
 class SCI0_CGADriver final : public SCI0_DOSPreVGADriver {
 public:
-	SCI0_CGADriver(bool emulateCGAModeOnEGACard);
+	SCI0_CGADriver(bool emulateCGAModeOnEGACard, bool rgbRendering);
 	~SCI0_CGADriver() override;
-	void copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h) override;
+	void copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h, const PaletteMod*, const byte*) override;
 	void replaceCursor(const void *cursor, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor) override;
 	static bool validateMode() { return checkDriver(&_driverFile, 1); }
 private:
+	void setupRenderProc() override;
 	uint16 *_cgaPatterns;
 	byte _palette[12];
 	const bool _disableMode5;
+	typedef void (*LineProc)(byte*&, const byte*, int, int, int, const uint16*, const byte*);
+	LineProc _renderLine;
 	static const char *_driverFile;
 };
 
 class SCI0_CGABWDriver final : public SCI0_DOSPreVGADriver {
 public:
-	SCI0_CGABWDriver(uint32 monochromeColor);
+	SCI0_CGABWDriver(uint32 monochromeColor, bool rgbRendering);
 	~SCI0_CGABWDriver() override;
-	void copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h) override;
+	void copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h, const PaletteMod*, const byte*) override;
 	void replaceCursor(const void *cursor, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor) override;
 	Common::Point getMousePos() const override;
 	void clearRect(const Common::Rect &r) const override;
 	static bool validateMode() { return checkDriver(_driverFiles, 2); }
 private:
+	void setupRenderProc() override;
 	byte _monochromePalette[6];
 	const byte *_monochromePatterns;
 	bool _earlyVersion;
+	typedef void (*LineProc)(byte*&, const byte*, int, int, int, const byte*, const byte*);
+	LineProc _renderLine;
 	static const char *_driverFiles[2];
 };
 
 class SCI0_HerculesDriver final : public SCI0_DOSPreVGADriver {
 public:
-	SCI0_HerculesDriver(uint32 monochromeColor, bool cropImage);
+	SCI0_HerculesDriver(uint32 monochromeColor, bool rgbRendering, bool cropImage);
 	~SCI0_HerculesDriver() override;
-	void copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h) override;
+	void copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h, const PaletteMod*, const byte*) override;
 	void replaceCursor(const void *cursor, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor) override;
 	Common::Point getMousePos() const override;
 	void clearRect(const Common::Rect &r) const override;
 	static bool validateMode() { return checkDriver(&_driverFile, 1); }
 private:
+	void setupRenderProc() override;
 	const uint16 _centerX;
 	const uint16 _centerY;
 	byte _monochromePalette[6];
 	const byte *_monochromePatterns;
+	typedef void (*LineProc)(byte*&, const byte*, int, int, int, const byte*, const byte*);
+	LineProc _renderLine;
 	static const char *_driverFile;
 };
 
diff --git a/engines/sci/graphics/palette.cpp b/engines/sci/graphics/palette.cpp
index 995c6ea8fd0..96f8bc67ede 100644
--- a/engines/sci/graphics/palette.cpp
+++ b/engines/sci/graphics/palette.cpp
@@ -537,10 +537,10 @@ void GfxPalette::kernelUnsetFlag(uint16 fromColor, uint16 toColor, uint16 flag)
 }
 
 void GfxPalette::kernelSetIntensity(uint16 fromColor, uint16 toColor, uint16 intensity, bool setPalette) {
-	memset(&_sysPalette.intensity[0] + fromColor, intensity, toColor - fromColor);
-	if (setPalette) {
+	if (_screen->gfxDriver()->supportsPalIntensity())
+		memset(&_sysPalette.intensity[0] + fromColor, intensity, toColor - fromColor);
+	if (setPalette)
 		setOnScreen();
-	}
 }
 
 int16 GfxPalette::kernelFindColor(uint16 r, uint16 g, uint16 b, bool force16BitColorMatch) {
diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp
index 532aa065351..c0bb3b0e3e6 100644
--- a/engines/sci/graphics/screen.cpp
+++ b/engines/sci/graphics/screen.cpp
@@ -148,18 +148,43 @@ GfxScreen::GfxScreen(ResourceManager *resMan, Common::RenderMode renderMode) : _
 		break;
 	}
 
+	int extraHeight = 0;
+	if (g_sci->hasMacIconBar()) {
+		// For SCI1.1 Mac games with the custom icon bar, we need to expand the screen
+		// to accommodate for the icon bar. Of course, both KQ6 and Freddy Pharkas differ in size.
+		// We add 2 to the height of the icon bar to add a buffer between the screen and the
+		// icon bar (as did the original interpreter).
+		switch (g_sci->getGameId()) {
+		case GID_KQ6: 
+			extraHeight = 26 + 2;
+			break;
+		case GID_FREDDYPHARKAS:
+			extraHeight = 28 + 2;
+			break;
+		default:
+			error("Unknown SCI1.1 Mac game");
+		}
+
+		if (_upscaledHires == GFX_SCREEN_UPSCALED_640x400) {
+			extraHeight *= 2;
+		}
+	}
+
+	bool enablePaletteMods = ConfMan.hasKey("palette_mods") && ConfMan.getBool("palette_mods");
+	bool requestRGB = enablePaletteMods || (ConfMan.hasKey("rgb_rendering") && ConfMan.getBool("rgb_rendering"));
+
 	_gfxDrv = nullptr;
 	if (getSciVersion() <= SCI_VERSION_0_LATE || getSciVersion() == SCI_VERSION_1_EGA_ONLY) {
 		switch (renderMode) {
 		case Common::kRenderCGA:
-			_gfxDrv = new SCI0_CGADriver(false);
+			_gfxDrv = new SCI0_CGADriver(false, requestRGB);
 			break;
 		case Common::kRenderCGA_BW:
-			_gfxDrv = new SCI0_CGABWDriver(0xffffff);
+			_gfxDrv = new SCI0_CGABWDriver(0xffffff, requestRGB);
 			break;
 		case Common::kRenderHercA:
 		case Common::kRenderHercG:
-			_gfxDrv = new SCI0_HerculesDriver(renderMode == Common::kRenderHercG ? 0x66ff66 : 0xffbf66, false);
+			_gfxDrv = new SCI0_HerculesDriver(renderMode == Common::kRenderHercG ? 0x66ff66 : 0xffbf66, requestRGB, false);
 			break;
 		default:
 			break;
@@ -167,7 +192,7 @@ GfxScreen::GfxScreen(ResourceManager *resMan, Common::RenderMode renderMode) : _
 	}
 
 	if (_gfxDrv == nullptr)
-		_gfxDrv = new GfxDefaultDriver(_displayWidth, _displayHeight);
+		_gfxDrv = new GfxDefaultDriver(_displayWidth, _displayHeight + extraHeight, requestRGB);
 	assert(_gfxDrv);
 
 	_displayPixels = _displayWidth * _displayHeight;
@@ -208,60 +233,17 @@ GfxScreen::GfxScreen(ResourceManager *resMan, Common::RenderMode renderMode) : _
 	}
 
 	// Set up palette mods if requested
-	if (ConfMan.getBool("palette_mods")) {
+	if (enablePaletteMods)
 		setupCustomPaletteMods(this);
-		ConfMan.setBool("rgb_rendering", true);
-	}
 
 	// Initialize the actual screen
-	const Graphics::PixelFormat *format = &format8;
-	if (_gfxDrv->allowRGBRendering() && ConfMan.getBool("rgb_rendering"))
-		format = nullptr; // Backend's preferred mode; RGB if available
+	_gfxDrv->initScreen();
 
-	if (g_sci->hasMacIconBar()) {
-		// For SCI1.1 Mac games with the custom icon bar, we need to expand the screen
-		// to accommodate for the icon bar. Of course, both KQ6 and Freddy Pharkas differ in size.
-		// We add 2 to the height of the icon bar to add a buffer between the screen and the
-		// icon bar (as did the original interpreter).
-		int macIconBarBuffer = 0;
-		switch (g_sci->getGameId()) {
-		case GID_KQ6: 
-			macIconBarBuffer = 26 + 2;
-			break;
-		case GID_FREDDYPHARKAS:
-			macIconBarBuffer = 28 + 2;
-			break;
-		default:
-			error("Unknown SCI1.1 Mac game");
-		}
-
-		if (_upscaledHires == GFX_SCREEN_UPSCALED_640x400) {
-			macIconBarBuffer *= 2;
-		}
-
-		initGraphics(_displayWidth, _displayHeight + macIconBarBuffer, format);
-	} else
-		initGraphics(_gfxDrv->screenWidth(), _gfxDrv->screenHeight(), format);
-
-
-	_format = g_system->getScreenFormat();
-
-	// If necessary, allocate buffers for RGB mode
-	if (_format.bytesPerPixel != 1) {
-		_displayedScreen = (byte *)calloc(_displayPixels, 1);
-		_rgbScreen = (byte *)calloc(_format.bytesPerPixel*_displayPixels, 1);
-		_palette = new byte[3*256];
-
-		if (_paletteModsEnabled)
-			_paletteMapScreen = (byte *)calloc(_displayPixels, 1);
-		else
-			_paletteMapScreen = nullptr;
-	} else {
-		_displayedScreen = nullptr;
-		_palette = nullptr;
-		_rgbScreen = nullptr;
+	if (_gfxDrv->pixelSize() != 1 && _paletteModsEnabled)
+		_paletteMapScreen = (byte *)calloc(_displayPixels, 1);
+	else
 		_paletteMapScreen = nullptr;
-	}
+
 	_backupScreen = nullptr;
 }
 
@@ -270,132 +252,16 @@ GfxScreen::~GfxScreen() {
 	free(_priorityScreen);
 	free(_controlScreen);
 	free(_displayScreen);
-
 	free(_paletteMapScreen);
-	free(_displayedScreen);
-	free(_rgbScreen);
-	delete[] _palette;
 	delete[] _backupScreen;
 	delete _gfxDrv;
 }
 
-void GfxScreen::convertToRGB(const Common::Rect &rect) {
-	assert(_format.bytesPerPixel != 1);
-
-	for (int y = rect.top; y < rect.bottom; ++y) {
-
-		const byte *in = _displayedScreen + y * _displayWidth + rect.left;
-		byte *out = _rgbScreen + (y * _displayWidth + rect.left) * _format.bytesPerPixel;
-
-		// TODO: Reduce code duplication here
-
-		if (_format.bytesPerPixel == 2) {
-
-			if (_paletteMapScreen) {
-				const byte *mod = _paletteMapScreen + y * _displayWidth + rect.left;
-				for (int x = 0; x < rect.width(); ++x) {
-					byte i = *in;
-					byte r = _palette[3*i + 0];
-					byte g = _palette[3*i + 1];
-					byte b = _palette[3*i + 2];
-
-					if (*mod) {
-						r = MIN(r * (128 + _paletteMods[*mod].r) / 128, 255);
-						g = MIN(g * (128 + _paletteMods[*mod].g) / 128, 255);
-						b = MIN(b * (128 + _paletteMods[*mod].b) / 128, 255);
-					}
-
-					uint16 c = (uint16)_format.RGBToColor(r, g, b);
-					WRITE_UINT16(out, c);
-					in += 1;
-					out += 2;
-					mod += 1;
-				}
-			} else {
-				for (int x = 0; x < rect.width(); ++x) {
-					byte i = *in;
-					byte r = _palette[3*i + 0];
-					byte g = _palette[3*i + 1];
-					byte b = _palette[3*i + 2];
-					uint16 c = (uint16)_format.RGBToColor(r, g, b);
-					WRITE_UINT16(out, c);
-					in += 1;
-					out += 2;
-				}
-			}
-
-		} else {
-			assert(_format.bytesPerPixel == 4);
-
-			if (_paletteMapScreen) {
-				const byte *mod = _paletteMapScreen + y * _displayWidth + rect.left;
-				for (int x = 0; x < rect.width(); ++x) {
-					byte i = *in;
-					byte r = _palette[3*i + 0];
-					byte g = _palette[3*i + 1];
-					byte b = _palette[3*i + 2];
-
-					if (*mod) {
-						r = MIN(r * (128 + _paletteMods[*mod].r) / 128, 255);
-						g = MIN(g * (128 + _paletteMods[*mod].g) / 128, 255);
-						b = MIN(b * (128 + _paletteMods[*mod].b) / 128, 255);
-					}
-
-					uint32 c = _format.RGBToColor(r, g, b);
-					WRITE_UINT32(out, c);
-					in += 1;
-					out += 4;
-					mod += 1;
-				}
-			} else {
-				for (int x = 0; x < rect.width(); ++x) {
-					byte i = *in;
-					byte r = _palette[3*i + 0];
-					byte g = _palette[3*i + 1];
-					byte b = _palette[3*i + 2];
-					uint32 c = _format.RGBToColor(r, g, b);
-					WRITE_UINT32(out, c);
-					in += 1;
-					out += 4;
-				}
-			}
-		}
-	}
-}
-
-void GfxScreen::displayRectRGB(const Common::Rect &rect, int x, int y) {
-	// Display rect from _activeScreen to screen location x, y.
-	// Clipping is assumed to be done already.
-
-	Common::Rect targetRect;
-	targetRect.left = x;
-	targetRect.setWidth(rect.width());
-	targetRect.top = y;
-	targetRect.setHeight(rect.height());
-
-	// 1. Update _displayedScreen
-	for (int i = 0; i < rect.height(); ++i) {
-		int offset = (rect.top + i) * _displayWidth + rect.left;
-		int targetOffset = (targetRect.top + i) * _displayWidth + targetRect.left;
-		memcpy(_displayedScreen + targetOffset, _activeScreen + offset, rect.width());
-	}
-
-	// 2. Convert to RGB
-	convertToRGB(targetRect);
-
-	// 3. Copy to screen
-	g_system->copyRectToScreen(_rgbScreen + (targetRect.top * _displayWidth + targetRect.left) * _format.bytesPerPixel, _displayWidth * _format.bytesPerPixel, targetRect.left, targetRect.top, targetRect.width(), targetRect.height());
-}
-
 void GfxScreen::displayRect(const Common::Rect &rect, int x, int y) {
 	// Display rect from _activeScreen to screen location x, y.
 	// Clipping is assumed to be done already.
-
-	if (_format.bytesPerPixel == 1) {
-		_gfxDrv->copyRectToScreen(_activeScreen + rect.top * _displayWidth + rect.left, _displayWidth, x, y, rect.width(), rect.height());
-	} else {
-		displayRectRGB(rect, x, y);
-	}
+	_gfxDrv->copyRectToScreen(_activeScreen + rect.top * _displayWidth + rect.left,
+		_displayWidth, x, y, rect.width(), rect.height(), _paletteModsEnabled ? _paletteMods : nullptr, _paletteMapScreen);
 }
 
 
@@ -406,12 +272,6 @@ void GfxScreen::clearForRestoreGame() {
 	memset(_priorityScreen, 0, _pixels);
 	memset(_controlScreen, 0, _pixels);
 	memset(_displayScreen, 0, _displayPixels);
-	if (_displayedScreen) {
-		memset(_displayedScreen, 0, _displayPixels);
-		memset(_rgbScreen, 0, _format.bytesPerPixel*_displayPixels);
-		if (_paletteMapScreen)
-			memset(_paletteMapScreen, 0, _displayPixels);
-	}
 	memset(&_ditheredPicColors, 0, sizeof(_ditheredPicColors));
 	_fontIsUpscaled = false;
 	copyToScreen();
@@ -422,30 +282,14 @@ void GfxScreen::copyToScreen() {
 	displayRect(r, 0, 0);
 }
 
-void GfxScreen::copyVideoFrameToScreen(const byte *buffer, int pitch, const Common::Rect &rect, bool is8bit) {
-	if (_format.bytesPerPixel == 1 || !is8bit) {
-		g_system->copyRectToScreen(buffer, pitch, rect.left, rect.top, rect.width(), rect.height());
-	} else {
-		for (int i = 0; i < rect.height(); ++i) {
-			int offset = i * pitch;
-			int targetOffset = (rect.top + i) * _displayWidth + rect.left;
-			memcpy(_displayedScreen + targetOffset, buffer + offset, rect.width());
-		}
-		convertToRGB(rect);
-		g_system->copyRectToScreen(_rgbScreen + (rect.top * _displayWidth + rect.left) * _format.bytesPerPixel, _displayWidth * _format.bytesPerPixel, rect.left, rect.top, rect.width(), rect.height());
-	}
+void GfxScreen::copyVideoFrameToScreen(const byte *buffer, int pitch, const Common::Rect &rect) {
+	uint8 align = _gfxDrv->hAlignment();
+	Common::Rect r(rect.left & ~align, rect.top, (rect.right + align) & ~align, rect.bottom);
+	_gfxDrv->copyRectToScreen(buffer, pitch, r.left, r.top, r.width(), r.height(), _paletteModsEnabled ? _paletteMods : nullptr, _paletteMapScreen);
 }
 
 void GfxScreen::kernelSyncWithFramebuffer() {
-	if (_format.bytesPerPixel == 1) {
-		Graphics::Surface *screen = g_system->lockScreen();
-		const byte *pix = (const byte *)screen->getPixels();
-		for (int y = 0; y < _displayHeight; ++y)
-			memcpy(_displayScreen + y * _displayWidth, pix + y * screen->pitch, _displayWidth);
-		g_system->unlockScreen();
-	} else {
-		memcpy(_displayScreen, _displayedScreen, _displayPixels);
-	}
+	_gfxDrv->copyCurrentBitmap(_displayScreen, _displayPixels);
 }
 
 void GfxScreen::copyRectToScreen(const Common::Rect &rect) {
@@ -479,7 +323,9 @@ void GfxScreen::copyDisplayRectToScreen(const Common::Rect &rect) {
 
 void GfxScreen::copyRectToScreen(const Common::Rect &rect, int16 x, int16 y) {
 	if (!_upscaledHires)  {
-		displayRect(rect, x, y);
+		uint8 align = _gfxDrv->hAlignment();
+		Common::Rect r(rect.left & ~align, rect.top, (rect.right + align) & ~align, rect.bottom);
+		displayRect(r, x & ~align, y);
 	} else {
 		int rectHeight = _upscaledHeightMapping[rect.bottom] - _upscaledHeightMapping[rect.top];
 		int rectWidth  = _upscaledWidthMapping[rect.right] - _upscaledWidthMapping[rect.left];
@@ -1074,43 +920,19 @@ int16 GfxScreen::kernelPicNotValid(int16 newPicNotValid) {
 }
 
 void GfxScreen::grabPalette(byte *buffer, uint start, uint num) const {
-	assert(start + num <= 256);
-	if (_format.bytesPerPixel == 1) {
-		g_system->getPaletteManager()->grabPalette(buffer, start, num);
-	} else {
-		memcpy(buffer, _palette + 3*start, 3*num);
-	}
+	_gfxDrv->copyCurrentPalette(buffer, start, num);
 }
 
 void GfxScreen::setPalette(const byte *buffer, uint start, uint num, bool update) {
 	assert(start + num <= 256);
-	if (_format.bytesPerPixel == 1) {
-		_gfxDrv->setPalette(buffer, start, num);
-	} else {
-		memcpy(_palette + 3*start, buffer, 3*num);
-		if (update) {
-			// directly paint from _displayedScreen, not from _activeScreen
-			Common::Rect r(0, 0, _displayWidth, _displayHeight);
-			convertToRGB(r);
-			g_system->copyRectToScreen(_rgbScreen, _displayWidth * _format.bytesPerPixel, 0, 0, _displayWidth, _displayHeight);
-		}
-		// CHECKME: Inside or outside the if (update)?
-		// (The !update case only happens inside transitions.)
-		CursorMan.replaceCursorPalette(_palette, 0, 256);
-	}
+	_gfxDrv->setPalette(buffer, start, num, update, _paletteModsEnabled ? _paletteMods : nullptr, _paletteMapScreen);	
 }
 
 
 void GfxScreen::bakCreateBackup() {
 	assert(!_backupScreen);
-	_backupScreen = new byte[_format.bytesPerPixel * _displayPixels];
-	if (_format.bytesPerPixel == 1) {
-		Graphics::Surface *screen = g_system->lockScreen();
-		memcpy(_backupScreen, screen->getPixels(), _displayPixels);
-		g_system->unlockScreen();
-	} else {
-		memcpy(_backupScreen, _rgbScreen, _format.bytesPerPixel * _displayPixels);
-	}
+	_backupScreen = new byte[_displayPixels];
+	_gfxDrv->copyCurrentBitmap(_backupScreen, _displayPixels);
 }
 
 void GfxScreen::bakDiscard() {
@@ -1121,9 +943,9 @@ void GfxScreen::bakDiscard() {
 
 void GfxScreen::bakCopyRectToScreen(const Common::Rect &rect, int16 x, int16 y) {
 	assert(_backupScreen);
-	const byte *ptr = _backupScreen;
-	ptr += _format.bytesPerPixel * (rect.left + rect.top * _displayWidth);
-	g_system->copyRectToScreen(ptr, _format.bytesPerPixel * _displayWidth, x, y, rect.width(), rect.height());
+	uint8 align = _gfxDrv->hAlignment();
+	Common::Rect r(rect.left & ~align, rect.top, (rect.right + align) & ~align, rect.bottom);
+	_gfxDrv->copyRectToScreen(_backupScreen + r.left + r.top * _displayWidth, _displayWidth, x, y, r.width(), r.height(), _paletteModsEnabled ? _paletteMods : nullptr, _paletteMapScreen);
 }
 
 void GfxScreen::setPaletteMods(const PaletteMod *mods, unsigned int count) {
diff --git a/engines/sci/graphics/screen.h b/engines/sci/graphics/screen.h
index 4c7a38c7bf7..5bff720ec7f 100644
--- a/engines/sci/graphics/screen.h
+++ b/engines/sci/graphics/screen.h
@@ -96,7 +96,7 @@ public:
 	void bakDiscard();
 
 	// video frame displaying
-	void copyVideoFrameToScreen(const byte *buffer, int pitch, const Common::Rect &rect, bool is8bit);
+	void copyVideoFrameToScreen(const byte *buffer, int pitch, const Common::Rect &rect);
 
 	// Vector drawing
 private:
@@ -173,8 +173,6 @@ private:
 	uint16 _displayHeight;
 	uint _displayPixels;
 
-	Graphics::PixelFormat _format;
-
 	byte _colorWhite;
 	byte _colorDefaultVectorData;
 
@@ -207,14 +205,11 @@ private:
 	Graphics::Surface _displayScreenSurface;
 
 	/**
-	 * CGA and Hercules support
+	 * Support for CGA and Hercules and other graphic modes that the original
+	 * interpreters allowed. It also performs the rgb rendering if needed.
 	 */
 	GfxDriver *_gfxDrv;
 
-	// Screens for RGB mode support
-	byte *_displayedScreen;
-	byte *_rgbScreen;
-
 	// For RGB per-view/pic palette mods
 	byte *_paletteMapScreen;
 	byte _curPaletteMapValue;
@@ -223,10 +218,7 @@ private:
 
 	byte *_backupScreen; // for bak* functions
 
-	void convertToRGB(const Common::Rect &rect);
-	void displayRectRGB(const Common::Rect &rect, int x, int y);
 	void displayRect(const Common::Rect &rect, int x, int y);
-	byte *_palette;
 
 	ResourceManager *_resMan;
 
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index dffba4b481f..29a9d823a25 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -322,24 +322,20 @@ Common::Error SciEngine::run() {
 		Common::RenderMode renderMode = Common::kRenderDefault;
 
 		bool undither = ConfMan.getBool("disable_dithering");
-
-		if (getSciVersion() <= SCI_VERSION_0_LATE || getSciVersion() == SCI_VERSION_1_EGA_ONLY) {
-			if (ConfMan.hasKey("render_mode"))
-				renderMode = Common::parseRenderMode(ConfMan.get("render_mode"));
-
-			// Check if the selected render mode is available for the game. This is quite specific for
-			// each SCI0 game. Sometime it is only EGA, sometimes only CGA b/w without CGA 4 colors, etc.
-			// Also set default mode if undithering is enabled.
-			if ((renderMode == Common::kRenderEGA && undither) ||
-				(renderMode == Common::kRenderCGA && !SCI0_CGADriver::validateMode()) ||
-				(renderMode == Common::kRenderCGA_BW && !SCI0_CGABWDriver::validateMode()) ||
-				((renderMode == Common::kRenderHercA || renderMode == Common::kRenderHercG) && !SCI0_HerculesDriver::validateMode()))
-				renderMode = Common::kRenderDefault;
-
-			// Disable undithering for CGA and Hercules modes
-			if (renderMode != Common::kRenderDefault)
-				undither = false;
-		}
+		if (ConfMan.hasKey("render_mode"))
+			renderMode = Common::parseRenderMode(ConfMan.get("render_mode"));
+
+		// Check if the selected render mode is available for the game. This is quite specific for each game.
+		// Sometime it is only EGA, sometimes only CGA b/w without CGA 4 colors, etc. Also set default mode if undithering is enabled.
+		if ((renderMode == Common::kRenderEGA && ((getSciVersion() <= SCI_VERSION_0_LATE || getSciVersion() == SCI_VERSION_1_EGA_ONLY) && undither)) ||
+			(renderMode == Common::kRenderCGA && !SCI0_CGADriver::validateMode()) ||
+			(renderMode == Common::kRenderCGA_BW && !SCI0_CGABWDriver::validateMode()) ||
+			((renderMode == Common::kRenderHercA || renderMode == Common::kRenderHercG) && !SCI0_HerculesDriver::validateMode()))
+			renderMode = Common::kRenderDefault;
+
+		// Disable undithering for CGA and Hercules modes
+		if (renderMode != Common::kRenderDefault)
+			undither = false;
 
 		// Initialize the game screen
 		_gfxScreen = new GfxScreen(_resMan, renderMode);


Commit: bc6573237d184606b627b1141077e51c514ba8aa
    https://github.com/scummvm/scummvm/commit/bc6573237d184606b627b1141077e51c514ba8aa
Author: athrxx (athrxx at scummvm.org)
Date: 2024-07-11T08:02:49+03:00

Commit Message:
SCI: add SCI 1 video mode detection for EGA and VGA grey scale

Changed paths:
    common/gui_options.cpp
    common/gui_options.h
    common/rendermode.cpp
    common/rendermode.h
    engines/sci/detection_internal.h


diff --git a/common/gui_options.cpp b/common/gui_options.cpp
index e5280d7f1d4..61a9209b807 100644
--- a/common/gui_options.cpp
+++ b/common/gui_options.cpp
@@ -79,6 +79,7 @@ const struct GameOpt {
 	{ GUIO_RENDERCPC,			"cpc" },
 	{ GUIO_RENDERZX,			"zx" },
 	{ GUIO_RENDERC64,			"c64" },
+	{ GUIO_RENDERVGAGREY,		"vgaGray" },
 
 	{ GUIO_GAMEOPTIONS1, "gameOption1" },
 	{ GUIO_GAMEOPTIONS2, "gameOption2" },
diff --git a/common/gui_options.h b/common/gui_options.h
index 4da34344b4a..912e6f6587e 100644
--- a/common/gui_options.h
+++ b/common/gui_options.h
@@ -70,6 +70,7 @@
 #define GUIO_RENDERCPC      	"\x27"
 #define GUIO_RENDERZX	    	"\x28"
 #define GUIO_RENDERC64	    	"\x29"
+#define GUIO_RENDERVGAGREY    	"\x2A"
 
 #define GUIO_LINKSPEECHTOSFX "\x30"
 #define GUIO_LINKMUSICTOSFX  "\x31"
diff --git a/common/rendermode.cpp b/common/rendermode.cpp
index ba07e9a6769..8710fd8be99 100644
--- a/common/rendermode.cpp
+++ b/common/rendermode.cpp
@@ -53,6 +53,7 @@ const RenderModeDescription g_renderModes[] = {
 	{ "cpc", "Amstrad CPC", kRenderCPC },
 	{ "zx", "ZX Spectrum", kRenderZX },
 	{ "c64", "Commodore 64", kRenderC64 },
+	{ "vgaGrey", "VGA Grey Scale", kRenderVGAGrey },
 	{nullptr, nullptr, kRenderDefault}
 };
 
@@ -82,7 +83,8 @@ static const RenderGUIOMapping s_renderGUIOMapping[] = {
 	{ kRenderCGA_BW,	    GUIO_RENDERCGABW },
 	{ kRenderCPC,		    GUIO_RENDERCPC },
 	{ kRenderZX,			GUIO_RENDERZX },
-	{ kRenderC64,			GUIO_RENDERC64 }
+	{ kRenderC64,			GUIO_RENDERC64 },
+	{ kRenderVGAGrey,		GUIO_RENDERVGAGREY }
 };
 
 DECLARE_TRANSLATION_ADDITIONAL_CONTEXT("Hercules Green", "lowres")
diff --git a/common/rendermode.h b/common/rendermode.h
index 0e56c033e85..4a40bd347f4 100644
--- a/common/rendermode.h
+++ b/common/rendermode.h
@@ -64,7 +64,8 @@ enum RenderMode {
 	kRenderCGA_BW = 15,
 	kRenderCPC = 16,
 	kRenderZX = 17,
-	kRenderC64 = 18
+	kRenderC64 = 18,
+	kRenderVGAGrey = 19
 };
 
 struct RenderModeDescription {
diff --git a/engines/sci/detection_internal.h b/engines/sci/detection_internal.h
index 2c9fdee58ab..fbd6892ddb5 100644
--- a/engines/sci/detection_internal.h
+++ b/engines/sci/detection_internal.h
@@ -134,7 +134,10 @@ static Common::String customizeGuiOptions(Common::Path gamePath, Common::String
 		{ SCI_VERSION_0_EARLY,	SCI_VERSION_1_EGA_ONLY,		"CGA320BW.DRV",		GUIO_RENDERCGABW },
 		{ SCI_VERSION_0_EARLY,	SCI_VERSION_1_EGA_ONLY,		"CGA320M.DRV",		GUIO_RENDERCGABW },
 		{ SCI_VERSION_0_EARLY,	SCI_VERSION_1_EGA_ONLY,		"HERCMONO.DRV",		GUIO_RENDERHERCAMBER },
-		{ SCI_VERSION_0_EARLY,	SCI_VERSION_1_EGA_ONLY,		"HERCMONO.DRV",		GUIO_RENDERHERCGREEN }
+		{ SCI_VERSION_0_EARLY,	SCI_VERSION_1_EGA_ONLY,		"HERCMONO.DRV",		GUIO_RENDERHERCGREEN },
+		{ SCI_VERSION_1_EARLY,	SCI_VERSION_1_1,			"VGA320.DRV",		GUIO_RENDERVGA },
+		{ SCI_VERSION_1_EARLY,	SCI_VERSION_1_1,			"VGA320BW.DRV",		GUIO_RENDERVGAGREY },
+		{ SCI_VERSION_1_EARLY,	SCI_VERSION_1_1,			"EGA640.DRV",		GUIO_RENDEREGA }
 	};
 
 	Common::FSNode node(gamePath);


Commit: 975ba55f8b7156fe821b24e822263fa1cc724725
    https://github.com/scummvm/scummvm/commit/975ba55f8b7156fe821b24e822263fa1cc724725
Author: athrxx (athrxx at scummvm.org)
Date: 2024-07-11T08:02:49+03:00

Commit Message:
SCI: add SCI 1 video modes for EGA and VGA grey scale

Changed paths:
    engines/sci/graphics/gfxdrivers.cpp
    engines/sci/graphics/gfxdrivers.h
    engines/sci/graphics/screen.cpp
    engines/sci/sci.cpp


diff --git a/engines/sci/graphics/gfxdrivers.cpp b/engines/sci/graphics/gfxdrivers.cpp
index 4e69a8bc6c4..429b97088af 100644
--- a/engines/sci/graphics/gfxdrivers.cpp
+++ b/engines/sci/graphics/gfxdrivers.cpp
@@ -829,6 +829,249 @@ void SCI0_HerculesDriver::setupRenderProc() {
 
 const char *SCI0_HerculesDriver::_driverFile = "HERCMONO.DRV";
 
+
+SCI1_VGAGreyScaleDriver::SCI1_VGAGreyScaleDriver(bool rgbRendering) : GfxDefaultDriver(320, 200, rgbRendering), _greyScalePalette(nullptr) {
+	_greyScalePalette = new byte[_numColors * 3]();
+}
+
+SCI1_VGAGreyScaleDriver::~SCI1_VGAGreyScaleDriver() {
+	delete[] _greyScalePalette;
+}
+
+void SCI1_VGAGreyScaleDriver::setPalette(const byte *colors, uint start, uint num, bool update, const PaletteMod *palMods, const byte *palModMapping) {
+	GFXDRV_ASSERT_READY; 
+	byte *d = _greyScalePalette;
+	for (uint i = 0; i < num; ++i) {
+		// In the driver files I inspected there were never any other color distributions than this.
+		// So I guess it is safe to hardcode that instead of loading it from the driver file.
+		d[0] = d[1] = d[2] = (colors[0] * 77 + colors[1] * 150 + colors[2] * 28) >> 8;
+		colors += 3;
+		d += 3;
+	}
+
+	GfxDefaultDriver::setPalette(_greyScalePalette, start, num, update, palMods, palModMapping);
+}
+
+const char *SCI1_VGAGreyScaleDriver::_driverFile = "VGA320BW.DRV";
+
+SCI1_EGADriver::SCI1_EGADriver(bool rgbRendering) : GfxDriver(320, 200, 256, 1), _requestRGBMode(rgbRendering), _egaColorPatterns(nullptr), _egaMatchTable(nullptr),
+	_currentBitmap(nullptr), _compositeBuffer(nullptr), _currentPalette(nullptr), _internalPalette(nullptr), _colAdjust(0), _ready(false) {
+	Common::File drv;
+	if (!drv.open(_driverFile))
+		error("SCI1_EGADriver: Failed to open '%s'", _driverFile);
+
+	uint16 eprcOffs = 0;
+
+	uint32 cmd = drv.readUint32LE();
+	if ((cmd & 0xFF) == 0xE9)
+		eprcOffs = ((cmd >> 8) & 0xFFFF) + 3;
+
+	if (!eprcOffs || drv.readUint32LE() != 0x87654321 || !drv.skip(1) || !drv.seek(drv.readByte(), SEEK_CUR) || !drv.seek(drv.readByte(), SEEK_CUR) || drv.readUint32LE() != 0xFEDCBA98 || !drv.skip(4))
+		error("SCI1_EGADriver: Driver file '%s' unknown version", _driverFile);
+
+	drv.skip(drv.readByte() == 0x90 ? 20 : 19);
+
+	drv.seek(drv.readUint16LE());
+	byte *buff = new byte[128];
+	drv.read(buff, 128);
+
+	uint16 tableOffs = 0;
+	for (int i = 0; i < 120 && !tableOffs; ++i) {
+		uint32 c = READ_BE_UINT32(buff + i);
+		if (c == 0x8BD82E8A) {
+			if (buff[i + 4] == 0x87)
+				tableOffs = READ_LE_UINT16(buff + i + 5);
+		} else if (c == 0xD0E8D0E8) {
+			for (int ii = 4; ii < 14; ++ii) {
+				if (READ_BE_UINT16(buff + i + ii) == 0x83C0) {
+					c = READ_BE_UINT32(buff + i + ii + 2);
+					if ((c & 0xFFFFFF) == 0x83F83F)
+						_colAdjust = c >> 24;
+				}
+			}
+		}
+	}
+	delete[] buff;
+
+	if (!tableOffs)
+		error("SCI1_EGADriver: Failed to load color data from '%s'", _driverFile);
+
+	drv.seek(tableOffs);
+	byte *table = new byte[512]();
+	drv.read(table, 512);
+	_egaMatchTable = table;
+
+	if (drv.readUint16LE() != 152 || drv.readUint16LE() != 160)
+		error("SCI1_EGADriver: Driver file '%s' unknown version", _driverFile);
+
+	drv.close();
+}
+
+SCI1_EGADriver::~SCI1_EGADriver() {
+	delete[] _egaMatchTable;
+	delete[] _egaColorPatterns;
+	delete[] _compositeBuffer;
+	delete[] _currentBitmap;
+	delete[] _currentPalette;
+	delete[] _internalPalette;
+}
+
+template <typename T> void ega640RenderLine(byte *&dst, const byte *src, int w, const byte *patterns, const byte *pal) {
+	const T *p = reinterpret_cast<const T*>(pal);
+	T *d1 = reinterpret_cast<T*>(dst);
+	T *d2 = d1 + (w << 1);
+
+	for (int i = 0; i < w; ++i) {
+		byte pt = patterns[*src++];
+		if (sizeof(T) == 1) {
+			*d1++ = *d2++ = pt >> 4;
+			*d1++ = *d2++ = pt & 0x0f;
+		} else {
+			*d1++ = *d2++ = p[pt >> 4];
+			*d1++ = *d2++ = p[pt & 0x0f];
+		}
+	}
+	dst = reinterpret_cast<byte*>(d2);
+}
+
+void SCI1_EGADriver::initScreen(const Graphics::PixelFormat*) {
+	Graphics::PixelFormat format(Graphics::PixelFormat::createFormatCLUT8());
+	initGraphics(_screenW << 1, _screenH << 1, _requestRGBMode ? nullptr : &format);
+	format = g_system->getScreenFormat();
+	_pixelSize = format.bytesPerPixel;
+
+	if (_requestRGBMode && _pixelSize == 1)
+		warning("SCI1_EGADriver::initScreen(): RGB rendering not available in this ScummVM build");
+
+	static const byte egaColors[48] = {
+		0x00, 0x00, 0x00, 0x00, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0x00, 0xAA, 0xAA,
+		0xAA, 0x00, 0x00, 0xAA, 0x00, 0xAA, 0xAA, 0x55, 0x00, 0xAA, 0xAA, 0xAA,
+		0x55, 0x55, 0x55, 0x55, 0x55, 0xFF, 0x55, 0xFF, 0x55, 0x55, 0xFF, 0xFF,
+		0xFF, 0x55, 0x55, 0xFF, 0x55, 0xFF, 0xFF, 0xFF, 0x55, 0xFF, 0xFF, 0xFF
+	};
+
+	if (_pixelSize == 1) {
+		g_system->getPaletteManager()->setPalette(egaColors, 0, ARRAYSIZE(egaColors) / 3);
+	} else {
+		byte *rgbpal = new byte[_numColors * _pixelSize]();
+		assert(rgbpal);
+
+		if (_pixelSize == 2)
+			updateRGBPalette<uint16>(rgbpal, egaColors, 0, ARRAYSIZE(egaColors) / 3, format);
+		else if (_pixelSize == 4)
+			updateRGBPalette<uint32>(rgbpal, egaColors, 0, ARRAYSIZE(egaColors) / 3, format);
+		else
+			error("SCI1_EGADriver::initScreen(): Unsupported screen format");
+		_internalPalette = rgbpal;
+		CursorMan.replaceCursorPalette(egaColors, 0, ARRAYSIZE(egaColors) / 3);
+	}
+
+	_compositeBuffer = new byte[(_screenW << 1) * (_screenH << 1) * _pixelSize]();
+	assert(_compositeBuffer);
+	_currentBitmap = new byte[_screenW * _screenH]();
+	assert(_currentBitmap);
+	_currentPalette = new byte[256 * 3]();
+	assert(_currentPalette);
+	_egaColorPatterns = new byte[256]();
+	assert(_egaColorPatterns);
+
+	static const LineProc lineProcs[] = {
+		&ega640RenderLine<byte>,
+		&ega640RenderLine<uint16>,
+		&ega640RenderLine<uint32>
+	};
+
+	assert((_pixelSize >> 1) < ARRAYSIZE(lineProcs));
+	_renderLine = lineProcs[_pixelSize >> 1];
+
+	_ready = true;
+}
+
+void SCI1_EGADriver::setPalette(const byte *colors, uint start, uint num, bool update, const PaletteMod*, const byte*) {
+	GFXDRV_ASSERT_READY;
+	memcpy(_currentPalette + start * 3, colors, num * 3);
+	byte *d = &_egaColorPatterns[start];
+	for (uint i = 0; i < num; ++i) {
+		*d++ = _egaMatchTable[((MIN<byte>((colors[0] >> 2) + _colAdjust, 63) & 0x38) << 3) | (MIN<byte>((colors[1] >> 2) + _colAdjust, 63) & 0x38) | (MIN<byte>((colors[2] >> 2) + _colAdjust, 63) >> 3)];
+		colors += 3;
+	}
+	if (update)
+		copyRectToScreen(_currentBitmap, _screenW, 0, 0, _screenW, _screenH, nullptr, nullptr);
+}
+
+void SCI1_EGADriver::copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h, const PaletteMod*, const byte*) {
+	GFXDRV_ASSERT_READY;
+	GFXDRV_ASSERT_ALIGNED; // We can't fix the boundaries here, it has to happen before this call
+
+	if (src != _currentBitmap)
+		updateBitmapBuffer(_currentBitmap, _screenW, src, pitch, x, y, w, h);
+
+	byte *dst = _compositeBuffer;
+	for (int i = 0; i < h; ++i) {
+		_renderLine(dst, src, w, _egaColorPatterns, _internalPalette);
+		src += pitch;
+	}
+
+	g_system->copyRectToScreen(_compositeBuffer, (w << 1) * _pixelSize, x << 1, y << 1, w << 1, h << 1);
+}
+
+void SCI1_EGADriver::replaceCursor(const void *cursor, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor) {
+	GFXDRV_ASSERT_READY;
+	const byte *s = reinterpret_cast<const byte*>(cursor);
+	int dstPitch = (w << 1);
+	byte *d1 = _compositeBuffer;
+	byte *d2 = _compositeBuffer + dstPitch;
+	uint32 newKeyColor = 0xFF;
+
+	for (uint i = 0; i < h; ++i) {
+		for (uint ii = 0; ii < w; ++ii) {
+			byte col = *s++;
+			if (col == keycolor) {
+				*d1++ = *d2++ = newKeyColor;
+				*d1++ = *d2++ = newKeyColor;
+			} else {
+				byte pt = _egaColorPatterns[col];
+				*d1++ = *d2++ = pt >> 4;
+				*d1++ = *d2++ = pt & 0x0f;
+			}
+		}
+		d1 += dstPitch;
+		d2 += dstPitch;
+	}
+
+	keycolor = newKeyColor;
+
+	CursorMan.replaceCursor(_compositeBuffer, w << 1, h << 1, hotspotX << 1, hotspotY << 1, newKeyColor);
+}
+
+void SCI1_EGADriver::copyCurrentBitmap(byte *dest, uint32 size) const {
+	GFXDRV_ASSERT_READY;
+	assert(dest);
+	assert(size <= (uint32)(_screenW * _screenH));
+	memcpy(dest, _currentBitmap, size);
+}
+
+void SCI1_EGADriver::copyCurrentPalette(byte *dest, int start, int num) const {
+	GFXDRV_ASSERT_READY;
+	assert(dest);
+	assert(start + num <= 256);
+	memcpy(dest + start * 3, _currentPalette + start * 3, num * 3);
+}
+
+Common::Point SCI1_EGADriver::getMousePos() const {
+	Common::Point res = GfxDriver::getMousePos();
+	res.x >>= 1;
+	res.y >>= 1;
+	return res;
+}
+
+void SCI1_EGADriver::clearRect(const Common::Rect &r) const {
+	Common::Rect r2(r.left << 1, r.top << 1, r.right << 1, r.bottom << 1);
+	GfxDriver::clearRect(r2);
+}
+
+const char *SCI1_EGADriver::_driverFile = "EGA640.DRV";
+
 #undef GFXDRV_ASSERT_READY
 #undef GFXDRV_ASEERT_ALIGNED
 
diff --git a/engines/sci/graphics/gfxdrivers.h b/engines/sci/graphics/gfxdrivers.h
index 7d616c9cda8..4bb5ba10bfd 100644
--- a/engines/sci/graphics/gfxdrivers.h
+++ b/engines/sci/graphics/gfxdrivers.h
@@ -154,6 +154,46 @@ private:
 	static const char *_driverFile;
 };
 
+class SCI1_VGAGreyScaleDriver final : public GfxDefaultDriver {
+public:
+	SCI1_VGAGreyScaleDriver(bool rgbRendering);
+	~SCI1_VGAGreyScaleDriver() override;
+	void setPalette(const byte *colors, uint start, uint num, bool update, const PaletteMod *palMods, const byte *palModMapping) override;
+	static bool validateMode() { return checkDriver(&_driverFile, 1); }
+private:
+	byte *_greyScalePalette;
+	static const char *_driverFile;
+};
+
+class SCI1_EGADriver final : public GfxDriver {
+public:
+	SCI1_EGADriver(bool rgbRendering);
+	~SCI1_EGADriver() override;
+	void initScreen(const Graphics::PixelFormat*) override;
+	void setPalette(const byte *colors, uint start, uint num, bool update, const PaletteMod*, const byte*) override;
+	void copyRectToScreen(const byte *src, int pitch, int x, int y, int w, int h, const PaletteMod*, const byte*) override;
+	void replaceCursor(const void *cursor, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor) override;
+	void copyCurrentBitmap(byte *dest, uint32 size) const override;
+	void copyCurrentPalette(byte *dest, int start, int num) const override;
+	Common::Point getMousePos() const override;
+	void clearRect(const Common::Rect &r) const override;
+	bool supportsPalIntensity() const override { return false; }
+	static bool validateMode() { return checkDriver(&_driverFile, 1); }
+private:
+	bool _ready;
+	byte *_compositeBuffer;
+	byte *_currentBitmap;
+	byte *_currentPalette;
+	byte *_egaColorPatterns;
+	uint8 _colAdjust;
+	const byte *_internalPalette;
+	const byte *_egaMatchTable;
+	const bool _requestRGBMode;
+	typedef void (*LineProc)(byte*&, const byte*, int, const byte*, const byte*);
+	LineProc _renderLine;
+	static const char *_driverFile;
+};
+
 } // End of namespace Sci
 
 #endif // SCI_GRAPHICS_GFXDRIVERS_H
diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp
index c0bb3b0e3e6..4a25fe6414c 100644
--- a/engines/sci/graphics/screen.cpp
+++ b/engines/sci/graphics/screen.cpp
@@ -189,6 +189,17 @@ GfxScreen::GfxScreen(ResourceManager *resMan, Common::RenderMode renderMode) : _
 		default:
 			break;
 		}
+	} else {
+		switch (renderMode) {
+		case Common::kRenderEGA:
+			_gfxDrv = new SCI1_EGADriver(requestRGB);
+			break;
+		case Common::kRenderVGAGrey:
+			_gfxDrv = new SCI1_VGAGreyScaleDriver(requestRGB);
+			break;
+		default:
+			break;
+		}
 	}
 
 	if (_gfxDrv == nullptr)
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 29a9d823a25..49baa8fab72 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -327,7 +327,9 @@ Common::Error SciEngine::run() {
 
 		// Check if the selected render mode is available for the game. This is quite specific for each game.
 		// Sometime it is only EGA, sometimes only CGA b/w without CGA 4 colors, etc. Also set default mode if undithering is enabled.
-		if ((renderMode == Common::kRenderEGA && ((getSciVersion() <= SCI_VERSION_0_LATE || getSciVersion() == SCI_VERSION_1_EGA_ONLY) && undither)) ||
+		if ((renderMode == Common::kRenderEGA && (((getSciVersion() <= SCI_VERSION_0_LATE || getSciVersion() == SCI_VERSION_1_EGA_ONLY) && undither) ||
+			(getSciVersion() >= SCI_VERSION_1_EARLY && getSciVersion() <= SCI_VERSION_1_1 && !SCI1_EGADriver::validateMode()))) ||
+			(renderMode == Common::kRenderVGAGrey && !SCI1_VGAGreyScaleDriver::validateMode()) ||
 			(renderMode == Common::kRenderCGA && !SCI0_CGADriver::validateMode()) ||
 			(renderMode == Common::kRenderCGA_BW && !SCI0_CGABWDriver::validateMode()) ||
 			((renderMode == Common::kRenderHercA || renderMode == Common::kRenderHercG) && !SCI0_HerculesDriver::validateMode()))




More information about the Scummvm-git-logs mailing list