[Scummvm-git-logs] scummvm master -> e833307eb308bdf1de1d530ffd92028d333a3cea

whoozle noreply at scummvm.org
Mon Jul 13 08:01:57 UTC 2026


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

Summary:
e833307eb3 PHOENIXVR: Use RGB565 for DCT VR rendering


Commit: e833307eb308bdf1de1d530ffd92028d333a3cea
    https://github.com/scummvm/scummvm/commit/e833307eb308bdf1de1d530ffd92028d333a3cea
Author: Scorp (scorp at mrs.mn)
Date: 2026-07-13T09:01:53+01:00

Commit Message:
PHOENIXVR: Use RGB565 for DCT VR rendering

Decode PhoenixVR static VR images directly to RGB565 so their DCT color conversion matches the original renderer more closely.

This requests an RGB565 screen format, keeps cursors and overlays in the screen format, redraws rollover text after VR frames, and removes the extra runtime color conversion paths.

Changed paths:
    engines/phoenixvr/phoenixvr.cpp
    engines/phoenixvr/phoenixvr.h
    engines/phoenixvr/vr.cpp


diff --git a/engines/phoenixvr/phoenixvr.cpp b/engines/phoenixvr/phoenixvr.cpp
index 05f4dd28fc5..1a7477b3463 100644
--- a/engines/phoenixvr/phoenixvr.cpp
+++ b/engines/phoenixvr/phoenixvr.cpp
@@ -998,7 +998,7 @@ void PhoenixVREngine::showImageOverlay(const Common::String &image, int x, int y
 
 	_imageOverlay.reset(new Graphics::ManagedSurface());
 	_imageOverlay->convertFrom(*surface, _pixelFormat);
-	_imageOverlay->setTransparentColor(surface->getPixel(surface->w - 1, surface->h - 1));
+	_imageOverlay->setTransparentColor(_imageOverlay->getPixel(_imageOverlay->w - 1, _imageOverlay->h - 1));
 	_imageOverlayPos = Common::Point(x, y);
 }
 
@@ -1184,10 +1184,27 @@ void PhoenixVREngine::renderTimer() {
 
 void PhoenixVREngine::renderVR(float dt) {
 	_vr.render(_screen, _angleX.angle(), _angleY.angle(), _fov, dt, _showRegions ? _regSet.get() : nullptr);
-	if (_text) {
-		int16 x = _textRect.left + (_textRect.width() - _text->w) / 2;
-		int16 y = _textRect.top + (_textRect.height() - _text->h) / 2;
-		_screen->simpleBlitFrom(*_text, {x, y}, Graphics::FLIP_NONE, true);
+	if (_textId >= 0 && _textes.contains(_textId)) {
+		const Graphics::Font *font = getFont(_textSize, _textBold);
+		if (font) {
+			Common::Array<Common::U32String> lines;
+			font->wordWrapText(_textes.getVal(_textId), _textRect.width(), lines, Graphics::kWordWrapDefault);
+
+			const int fontH = font->getFontHeight();
+			int textW = 0;
+			for (const Common::U32String &line : lines)
+				textW = MAX(textW, font->getStringWidth(line));
+
+			byte r, g, b;
+			_rgb565.colorToRGB(_textColor, r, g, b);
+			const uint32 textColor = _screen->format.RGBToColor(r, g, b);
+			const int16 textX = _textRect.left + (_textRect.width() - textW) / 2;
+			const int16 textY = _textRect.top + (_textRect.height() - fontH * lines.size()) / 2;
+			for (uint i = 0; i < lines.size(); ++i) {
+				const int16 lineX = textX + (textW - font->getStringWidth(lines[i])) / 2;
+				font->drawString(_screen, lines[i], lineX, textY + i * fontH, _textRect.right - lineX, textColor, Graphics::kTextAlignLeft);
+			}
+		}
 	}
 	renderImageOverlay();
 	renderTimer();
@@ -1279,43 +1296,22 @@ void PhoenixVREngine::rollover(int textId, RolloverType type) {
 	}
 
 	auto *font = getFont(size, bold);
-
 	if (!font)
 		return;
 
 	if (!_textes.contains(textId)) {
-		debug("rollover reset");
-		_text.reset();
+		debug("text cleared");
+		_textId = -1;
 		return;
 	}
 	auto &text = _textes.getVal(textId);
 	debug("rollover %s, %s font size: %d, bold: %d, color: %02x", dstRect.toString().c_str(), text.encode(Common::kUtf8).c_str(), size, bold, color);
 
-	Common::Array<Common::U32String> lines;
-	font->wordWrapText(text, dstRect.width(), lines, Graphics::kWordWrapDefault);
-
-	auto fontH = font->getFontHeight();
-	int textW = 0;
-	Common::Array<int> widths(lines.size());
-	for (uint i = 0, n = lines.size(); i != n; ++i) {
-		auto w = font->getStringWidth(lines[i]);
-		widths[i] = w;
-		textW = MAX(textW, w);
-	}
-
-	auto numLines = static_cast<int>(lines.size());
-	auto textH = fontH * numLines;
-	debug("text %dx%d", textW, textH);
-	_text.reset(new Graphics::ManagedSurface(textW, textH, Graphics::PixelFormat::createFormatRGBA32()));
-	_text->clear();
-	byte r, g, b;
-	_rgb565.colorToRGB(color, r, g, b);
-	auto textColor = _text->format.RGBToColor(r, g, b);
-	for (int i = 0; i != numLines; ++i) {
-		int dw = (textW - widths[i]) / 2;
-		font->drawAlphaString(_text.get(), lines[i], dw, i * fontH, textW, textColor, Graphics::kTextAlignLeft);
-	}
+	_textId = textId;
 	_textRect = dstRect;
+	_textSize = size;
+	_textBold = bold;
+	_textColor = color;
 }
 
 void PhoenixVREngine::tick(float dt) {
@@ -1361,7 +1357,7 @@ void PhoenixVREngine::tick(float dt) {
 		goToWarp(_script->getInitScript()->vrFile);
 	}
 	if (_nextWarp >= 0) {
-		_text.reset();
+		_textId = -1;
 		_warpIdx = _nextWarp;
 		_warp = _script->getWarp(_nextWarp);
 		debug("warp %d -> %s %s", _nextWarp, _warp->vrFile.c_str(), _warp->testFile.c_str());
@@ -1372,7 +1368,9 @@ void PhoenixVREngine::tick(float dt) {
 			Common::ScopedPtr<Common::SeekableReadStream> stream(open(_warp->vrFile, &origName));
 			bool isVr = origName.empty() || origName.hasSuffixIgnoreCase(".vr");
 			if (stream && isVr) {
-				_vr = VR::loadStatic(_pixelFormat, *stream);
+				_vr = VR::loadStatic(_rgb565, *stream);
+				if (_pixelFormat != _rgb565)
+					_vr.getSurface().convertToInPlace(_pixelFormat);
 				if (_vr.isVR()) {
 					_mousePos = _screenCenter;
 					_mouseRel = {};
@@ -1419,6 +1417,7 @@ void PhoenixVREngine::tick(float dt) {
 
 		if (_vr.isVR() ? region->contains3D(currentVRPos()) : region->contains2D(_mousePos.x, _mousePos.y)) {
 			anyMatched = true;
+
 			auto test = _warp->getTest(i);
 			if (test && test->hover == 1 && _hoverIndex < 0) {
 				debug("executing hover test %d", i);
@@ -1439,6 +1438,7 @@ void PhoenixVREngine::tick(float dt) {
 			_hoverIndex = -1;
 		}
 	}
+
 	if (!cursor)
 		cursor = loadCursor(anyMatched ? _defaultCursor[1] : _defaultCursor[0]);
 	if (cursor) {
@@ -1458,7 +1458,9 @@ void PhoenixVREngine::drawAudioSubtitles() {
 }
 
 Common::Error PhoenixVREngine::run() {
-	initGraphics(640, 480, nullptr);
+	Common::List<Graphics::PixelFormat> formats;
+	formats.push_back(_rgb565);
+	initGraphics(640, 480, formats);
 
 	_pixelFormat = g_system->getScreenFormat();
 	if (_pixelFormat.isCLUT8())
diff --git a/engines/phoenixvr/phoenixvr.h b/engines/phoenixvr/phoenixvr.h
index cc5bb8b8802..10a44b013fe 100644
--- a/engines/phoenixvr/phoenixvr.h
+++ b/engines/phoenixvr/phoenixvr.h
@@ -300,8 +300,11 @@ private:
 	Common::ScopedPtr<Graphics::Font> _regularFonts[kFontSizeCount];
 	Common::ScopedPtr<Graphics::Font> _boldFonts[kFontSizeCount];
 
-	Common::ScopedPtr<Graphics::ManagedSurface> _text;
+	int _textId = -1;
 	Common::Rect _textRect;
+	int _textSize = 0;
+	bool _textBold = false;
+	uint16 _textColor = 0;
 	Common::ScopedPtr<Graphics::ManagedSurface> _imageOverlay;
 	Common::Point _imageOverlayPos;
 	bool _cibleActive = false;
diff --git a/engines/phoenixvr/vr.cpp b/engines/phoenixvr/vr.cpp
index 299e287bbd0..5740f9ffca6 100644
--- a/engines/phoenixvr/vr.cpp
+++ b/engines/phoenixvr/vr.cpp
@@ -48,6 +48,15 @@ namespace PhoenixVR {
 
 namespace {
 
+constexpr int kStaticImageWidth = 640;
+constexpr int kStaticImageHeight = 480;
+constexpr int kVRTileSize = 256;
+constexpr int kVRTilesPerFace = 4;
+constexpr int kVRCubeFaceCount = 6;
+constexpr int kVRFaceSize = kVRTileSize * 2;
+constexpr int kVRImageWidth = kVRTileSize;
+constexpr int kVRImageHeight = kVRTileSize * kVRTilesPerFace * kVRCubeFaceCount;
+
 template<typename T>
 T clip(T a) {
 	return a >= 0 ? a <= 255 ? a : 255 : 0;
@@ -65,13 +74,70 @@ unsigned reverseBits(unsigned value, unsigned n) {
 }
 
 uint32 YCbCr2RGB(const Graphics::PixelFormat &format, int16 y, int16 cb, int16 cr) {
-	int r = clip(y + ((cr * 91881 + 32768) >> 16));
-	int g = clip(y - ((cb * 22553 + cr * 46801 + 32768) >> 16));
-	int b = clip(y + ((cb * 116129 + 32768) >> 16));
+	int r = clip(y + (cr << 4) / 10);
+	int g = clip(y - cb / 3 - (cr * 8) / 10);
+	int b = clip(y + cb * 2);
 
 	return format.RGBToColor(r, g, b);
 }
 
+void putRGB565DitheredBlock(Graphics::Surface &pic, int dstX, int dstY, int16 block[3][64]) {
+	int64 r[64], g[64], b[64];
+	for (int i = 0; i < 64; ++i) {
+		const int64 yFixed = static_cast<int64>(block[0][i] + 128) << 16;
+		r[i] = yFixed + ((block[2][i] << 4) / 10 << 16);
+		g[i] = yFixed + ((-block[1][i] / 3 - (block[2][i] * 8) / 10) << 16);
+		b[i] = yFixed + (block[1][i] * 2 << 16);
+	}
+
+	const int dstPitch = pic.pitch / pic.format.bytesPerPixel;
+	uint16 *dstPixels = static_cast<uint16 *>(pic.getBasePtr(dstX, dstY));
+
+	for (int y = 0; y < 8; ++y) {
+		const bool reverse = (y & 1) != 0;
+		for (int x = reverse ? 7 : 0; reverse ? x >= 0 : x < 8; reverse ? --x : ++x) {
+			const int index = y * 8 + x;
+			r[index] = CLIP<int64>(r[index], 0, 0xff0000);
+			g[index] = CLIP<int64>(g[index], 0, 0xff0000);
+			b[index] = CLIP<int64>(b[index], 0, 0xff0000);
+
+			dstPixels[y * dstPitch + x] = ((r[index] >> 8) & 0xf800) | ((g[index] >> 13) & 0x07e0) | ((b[index] >> 19) & 0x001f);
+
+			const int64 redError = r[index] - (r[index] & 0xfff80000);
+			const int64 greenError = g[index] - (g[index] & 0xfffc0000);
+			const int64 blueError = b[index] - (b[index] & 0xfff80000);
+
+			auto spread = [&](int dstIndex, int weight) {
+				r[dstIndex] += (redError * weight) >> 4;
+				g[dstIndex] += (greenError * weight) >> 4;
+				b[dstIndex] += (blueError * weight) >> 4;
+			};
+
+			if (!reverse) {
+				if (x < 7)
+					spread(index + 1, 7);
+				if (y < 7) {
+					if (x > 0)
+						spread(index + 7, 3);
+					spread(index + 8, 5);
+					if (x < 7)
+						spread(index + 9, 1);
+				}
+			} else {
+				if (x > 0)
+					spread(index - 1, 7);
+				if (y < 7) {
+					if (x < 7)
+						spread(index + 9, 3);
+					spread(index + 8, 5);
+					if (x > 0)
+						spread(index + 7, 1);
+				}
+			}
+		}
+	}
+}
+
 uint32 debugRegionColor(const Graphics::PixelFormat &format, float phase) {
 	phase = fmodf(phase, 6.0f);
 	if (phase < 0.0f)
@@ -151,10 +217,8 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
 	Common::BitStreamMemoryStream dcMs(dcPtr, dcSize);
 	Common::BitStreamMemory8MSB acBs(&acMs), dcBs(&dcMs);
 
-	const auto dstPitch = pic.pitch / pic.format.bytesPerPixel - 8;
-	unsigned numBlocks = prefix ? prefix->size() : ((pic.w + 7) / 8) * ((pic.h + 7) / 8);
-	for (uint blockIdx = 0; blockIdx < numBlocks; ++blockIdx) {
-		int16 block[3][64] = {};
+	auto decodeBlock = [&](int16 block[3][64]) {
+		Common::fill(&block[0][0], &block[0][0] + 3 * 64, 0);
 		for (unsigned channel = 0; channel != 3; ++channel) {
 			int16 *ac = block[channel];
 			int8 dc8 = static_cast<int8>(reverseBits(dcBs.getBits<8>(), 8));
@@ -182,6 +246,81 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
 
 			Video::FourXM::idct(ac, 4);
 		}
+	};
+
+	if (!prefix && pic.format.bytesPerPixel == 2 &&
+		((pic.w == kVRImageWidth && pic.h == kVRImageHeight) || (pic.w == kStaticImageWidth && pic.h == kStaticImageHeight))) {
+		const int bandSize = pic.w * 8;
+		Common::Array<int64> red, green, blue, nextRed, nextGreen, nextBlue;
+		red.resize(bandSize);
+		green.resize(bandSize);
+		blue.resize(bandSize);
+		nextRed.resize(bandSize);
+		nextGreen.resize(bandSize);
+		nextBlue.resize(bandSize);
+
+		for (int dstY = 0; dstY < pic.h; dstY += 8) {
+			for (int dstX = 0; dstX < pic.w; dstX += 8) {
+				int16 block[3][64];
+				decodeBlock(block);
+				for (int y = 0; y < 8; ++y) {
+					for (int x = 0; x < 8; ++x) {
+						const int src = y * 8 + x;
+						const int dst = y * pic.w + dstX + x;
+						const int64 yFixed = static_cast<int64>(block[0][src] + 128) << 16;
+						red[dst] += yFixed + ((block[2][src] << 4) / 10 << 16);
+						green[dst] += yFixed + ((-block[1][src] / 3 - (block[2][src] * 8) / 10) << 16);
+						blue[dst] += yFixed + (block[1][src] * 2 << 16);
+					}
+				}
+			}
+
+			for (int y = 0; y < 8; ++y) {
+				uint16 *dstPixels = static_cast<uint16 *>(pic.getBasePtr(0, dstY + y));
+				for (int x = 0; x < pic.w; ++x) {
+					const int index = y * pic.w + x;
+					red[index] = CLIP<int64>(red[index], 0, 0xff0000);
+					green[index] = CLIP<int64>(green[index], 0, 0xff0000);
+					blue[index] = CLIP<int64>(blue[index], 0, 0xff0000);
+
+					dstPixels[x] = ((red[index] >> 8) & 0xf800) | ((green[index] >> 13) & 0x07e0) | ((blue[index] >> 19) & 0x001f);
+					if (x == pic.w - 1)
+						continue;
+
+					const int64 redError = (red[index] - (red[index] & 0xf80000)) >> 1;
+					const int64 greenError = (green[index] - (green[index] & 0xfc0000)) >> 1;
+					const int64 blueError = (blue[index] - (blue[index] & 0xf80000)) >> 1;
+					red[index + 1] += redError;
+					green[index + 1] += greenError;
+					blue[index + 1] += blueError;
+
+					if (y == 7) {
+						nextRed[x] += redError;
+						nextGreen[x] += greenError;
+						nextBlue[x] += blueError;
+					} else {
+						red[index + pic.w] += redError;
+						green[index + pic.w] += greenError;
+						blue[index + pic.w] += blueError;
+					}
+				}
+			}
+
+			red.swap(nextRed);
+			green.swap(nextGreen);
+			blue.swap(nextBlue);
+			Common::fill(nextRed.begin(), nextRed.end(), 0);
+			Common::fill(nextGreen.begin(), nextGreen.end(), 0);
+			Common::fill(nextBlue.begin(), nextBlue.end(), 0);
+		}
+		return;
+	}
+
+	const auto dstPitch = pic.pitch / pic.format.bytesPerPixel - 8;
+	unsigned numBlocks = prefix ? prefix->size() : ((pic.w + 7) / 8) * ((pic.h + 7) / 8);
+	for (uint blockIdx = 0; blockIdx < numBlocks; ++blockIdx) {
+		int16 block[3][64];
+		decodeBlock(block);
 		int dstY;
 		int dstX;
 		if (prefix) {
@@ -209,17 +348,7 @@ void unpack(Graphics::Surface &pic, const byte *huff, uint huffSize, const byte
 			}
 		} break;
 		case 2: {
-			auto *dstPixel = static_cast<uint16 *>(pic.getBasePtr(dstX, dstY));
-			uint srcIdx = 0;
-			for (uint by = 0; by < 8; ++by, dstPixel += dstPitch) {
-				for (uint bx = 0; bx < 8; ++bx, ++srcIdx) {
-					int16 y = block[0][srcIdx];
-					int16 cb = block[1][srcIdx];
-					int16 cr = block[2][srcIdx];
-
-					*dstPixel++ = YCbCr2RGB(pic.format, y + 128, cb, cr);
-				}
-			}
+			putRGB565DitheredBlock(pic, dstX, dstY, block);
 		} break;
 		}
 	}
@@ -253,9 +382,9 @@ VR VR::loadStatic(const Graphics::PixelFormat &format, Common::SeekableReadStrea
 			auto *pic = vr._pic.get();
 			if (pic3d) {
 				vr._vr = true;
-				pic->create(256, 6144, format);
+				pic->create(kVRImageWidth, kVRImageHeight, format);
 			} else
-				pic->create(640, 480, format);
+				pic->create(kStaticImageWidth, kStaticImageHeight, format);
 			auto *huff = vrData.data() + 8;
 			uint acOffset = huffSize + 12;
 			auto *acPtr = vrData.data() + acOffset;
@@ -508,12 +637,12 @@ void VR::renderVR(Graphics::Screen *screen, float ax, float ay, float fov, float
 
 		for (int dstX = 0; dstX != w; ++dstX, ray += incrementX, ++dst) {
 			auto cube = toCube(ray.x(), ray.y(), ray.z());
-			int srcX = static_cast<int>(512 * cube.x);
-			int srcY = static_cast<int>(512 * cube.y);
+			int srcX = static_cast<int>(kVRFaceSize * cube.x);
+			int srcY = static_cast<int>(kVRFaceSize * cube.y);
 			int tileId = cube.faceIdx << 2;
-			tileId += (srcY < 256) ? (srcX < 256 ? 0 : 1) : (srcX < 256 ? 3 : 2);
-			srcX &= 0xff;
-			srcY &= 0xff;
+			tileId += (srcY < kVRTileSize) ? (srcX < kVRTileSize ? 0 : 1) : (srcX < kVRTileSize ? 3 : 2);
+			srcX &= kVRTileSize - 1;
+			srcY &= kVRTileSize - 1;
 			srcY += (tileId << 8);
 			auto color = _pic->getPixel(srcX, srcY);
 			if (regSet) {
@@ -573,6 +702,8 @@ void VR::render(Graphics::Screen *screen, float ax, float ay, float fov, float d
 		case 4:
 			renderVR<uint32>(screen, ax, ay, fov, dt, regSet);
 			break;
+		default:
+			error("Unsupported screen format for VR rendering");
 		}
 	}
 }




More information about the Scummvm-git-logs mailing list