aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Snover2016-07-02 19:11:46 -0500
committerColin Snover2016-07-10 09:35:24 -0500
commit4d91b458e53ebda8726055d338da12c05a7879ea (patch)
treecaaaf88c74cb0009f88b5c5390b7a138892cb376
parent28d4f5b0e48780dbbee3343d3d41fdd79340a3f4 (diff)
downloadscummvm-rg350-4d91b458e53ebda8726055d338da12c05a7879ea.tar.gz
scummvm-rg350-4d91b458e53ebda8726055d338da12c05a7879ea.tar.bz2
scummvm-rg350-4d91b458e53ebda8726055d338da12c05a7879ea.zip
SCI32: Implement kPlayVMD
-rw-r--r--engines/sci/engine/kernel.h11
-rw-r--r--engines/sci/engine/kernel_tables.h18
-rw-r--r--engines/sci/engine/kvideo.cpp163
-rw-r--r--engines/sci/engine/state.h1
-rw-r--r--engines/sci/graphics/controls32.cpp2
-rw-r--r--engines/sci/graphics/cursor.h2
-rw-r--r--engines/sci/graphics/frameout.cpp52
-rw-r--r--engines/sci/graphics/frameout.h19
-rw-r--r--engines/sci/graphics/paint32.cpp2
-rw-r--r--engines/sci/graphics/screen_item32.h2
-rw-r--r--engines/sci/graphics/video32.cpp365
-rw-r--r--engines/sci/graphics/video32.h275
-rw-r--r--engines/sci/module.mk1
-rw-r--r--engines/sci/sci.cpp12
-rw-r--r--engines/sci/sci.h3
15 files changed, 814 insertions, 114 deletions
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index d95e228045..d49f0649c1 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -441,6 +441,17 @@ reg_t kDoAudioFade(EngineState *s, int argc, reg_t *argv);
reg_t kDoAudioHasSignal(EngineState *s, int argc, reg_t *argv);
reg_t kDoAudioSetLoop(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDOpen(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDInit(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDClose(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDPlayUntilEvent(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDShowCursor(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDStartBlob(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDStopBlobs(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDBlack(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDRestrictPalette(EngineState *s, int argc, reg_t *argv);
+
reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv);
reg_t kArray(EngineState *s, int argc, reg_t *argv);
reg_t kListAt(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index dacaafe757..6e5add15e2 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -412,6 +412,22 @@ static const SciKernelMapSubEntry kList_subops[] = {
SCI_SUBOPENTRY_TERMINATOR
};
+// There are a lot of subops to PlayVMD, but only a few of them are ever
+// actually used by games
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kPlayVMD_subops[] = {
+ { SIG_SINCE_SCI21, 0, MAP_CALL(PlayVMDOpen), "r(i)(i)", NULL },
+ { SIG_SINCE_SCI21, 1, MAP_CALL(PlayVMDInit), "ii(i)(i)(ii)", NULL },
+ { SIG_SINCE_SCI21, 6, MAP_CALL(PlayVMDClose), "", NULL },
+ { SIG_SINCE_SCI21, 14, MAP_CALL(PlayVMDPlayUntilEvent), "i(i)(i)", NULL },
+ { SIG_SINCE_SCI21, 16, MAP_CALL(PlayVMDShowCursor), "i", NULL },
+ { SIG_SINCE_SCI21, 17, MAP_DUMMY(PlayVMDStartBlob), "", NULL },
+ { SIG_SINCE_SCI21, 18, MAP_DUMMY(PlayVMDStopBlobs), "", NULL },
+ { SIG_SINCE_SCI21, 21, MAP_CALL(PlayVMDBlack), "iiii", NULL },
+ { SIG_SINCE_SCI21, 23, MAP_CALL(PlayVMDRestrictPalette), "ii", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kRemapColors_subops[] = {
{ SIG_SCI32, 0, MAP_CALL(RemapColorsOff), "(i)", NULL },
@@ -792,7 +808,7 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(IsOnMe), SIG_EVERYWHERE, "iioi", NULL, NULL },
{ MAP_CALL(List), SIG_SINCE_SCI21, SIGFOR_ALL, "(.*)", kList_subops, NULL },
{ MAP_CALL(MulDiv), SIG_EVERYWHERE, "iii", NULL, NULL },
- { MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", kPlayVMD_subops, NULL },
{ MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", NULL, NULL },
{ MAP_CALL(Save), SIG_EVERYWHERE, "i(.*)", kSave_subops, NULL },
{ MAP_CALL(Text), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)", kText_subops, NULL },
diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp
index 8db0c542eb..aa37da6ea0 100644
--- a/engines/sci/engine/kvideo.cpp
+++ b/engines/sci/engine/kvideo.cpp
@@ -40,7 +40,8 @@
#include "video/qt_decoder.h"
#include "sci/video/seq_decoder.h"
#ifdef ENABLE_SCI32
-#include "video/coktel_decoder.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/video32.h"
#include "sci/video/robot_decoder.h"
#endif
@@ -289,113 +290,83 @@ reg_t kRobot(EngineState *s, int argc, reg_t *argv) {
}
reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv) {
- uint16 operation = argv[0].toUint16();
- Video::VideoDecoder *videoDecoder = 0;
- bool reshowCursor = g_sci->_gfxCursor->isVisible();
- Common::String warningMsg;
-
- switch (operation) {
- case 0: // init
- s->_videoState.reset();
- s->_videoState.fileName = s->_segMan->derefString(argv[1]);
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
- if (argc > 2 && argv[2] != NULL_REG)
- warning("kPlayVMD: third parameter isn't 0 (it's %04x:%04x - %s)", PRINT_REG(argv[2]), s->_segMan->getObjectName(argv[2]));
- break;
- case 1:
- {
- // Set VMD parameters. Called with a maximum of 6 parameters:
- //
- // x, y, flags, gammaBoost, gammaFirst, gammaLast
- //
- // gammaBoost boosts palette colors in the range gammaFirst to
- // gammaLast, but only if bit 4 in flags is set. Percent value such that
- // 0% = no amplification These three parameters are optional if bit 4 is
- // clear. Also note that the x, y parameters play subtle games if used
- // with subfx 21. The subtleness has to do with creation of temporary
- // planes and positioning relative to such planes.
-
- uint16 flags = argv[3].getOffset();
- Common::String flagspec;
-
- if (argc > 3) {
- if (flags & kDoubled)
- flagspec += "doubled ";
- if (flags & kDropFrames)
- flagspec += "dropframes ";
- if (flags & kBlackLines)
- flagspec += "blacklines ";
- if (flags & kUnkBit3)
- flagspec += "bit3 ";
- if (flags & kGammaBoost)
- flagspec += "gammaboost ";
- if (flags & kHoldBlackFrame)
- flagspec += "holdblack ";
- if (flags & kHoldLastFrame)
- flagspec += "holdlast ";
- if (flags & kUnkBit7)
- flagspec += "bit7 ";
- if (flags & kStretch)
- flagspec += "stretch";
-
- warning("VMDFlags: %s", flagspec.c_str());
-
- s->_videoState.flags = flags;
- }
+reg_t kPlayVMDOpen(EngineState *s, int argc, reg_t *argv) {
+ const Common::String fileName = s->_segMan->getString(argv[0]);
+ // argv[1] is an optional cache size argument which we do not use
+ // const uint16 cacheSize = argc > 1 ? CLIP<int16>(argv[1].toSint16(), 16, 1024) : 0;
+ const VMDPlayer::OpenFlags flags = argc > 2 ? (VMDPlayer::OpenFlags)argv[2].toUint16() : VMDPlayer::kOpenFlagNone;
- warning("x, y: %d, %d", argv[1].getOffset(), argv[2].getOffset());
- s->_videoState.x = argv[1].getOffset();
- s->_videoState.y = argv[2].getOffset();
+ return make_reg(0, g_sci->_video32->getVMDPlayer().open(fileName, flags));
+}
- if (argc > 4 && flags & 16)
- warning("gammaBoost: %d%% between palette entries %d and %d", argv[4].getOffset(), argv[5].getOffset(), argv[6].getOffset());
- break;
+reg_t kPlayVMDInit(EngineState *s, int argc, reg_t *argv) {
+ const int16 x = argv[0].toSint16();
+ const int16 y = argv[1].toSint16();
+ const VMDPlayer::PlayFlags flags = argc > 2 ? (VMDPlayer::PlayFlags)argv[2].toUint16() : VMDPlayer::kPlayFlagNone;
+ int16 boostPercent;
+ int16 boostStartColor;
+ int16 boostEndColor;
+ if (argc > 5 && (flags & VMDPlayer::kPlayFlagBoost)) {
+ boostPercent = argv[3].toSint16();
+ boostStartColor = argv[4].toSint16();
+ boostEndColor = argv[5].toSint16();
+ } else {
+ boostPercent = 0;
+ boostStartColor = -1;
+ boostEndColor = -1;
}
- case 6: // Play
- videoDecoder = new Video::AdvancedVMDDecoder();
- if (s->_videoState.fileName.empty()) {
- // Happens in Lighthouse
- warning("kPlayVMD: Empty filename passed");
- return s->r_acc;
- }
+ g_sci->_video32->getVMDPlayer().init(x, y, flags, boostPercent, boostStartColor, boostEndColor);
- if (!videoDecoder->loadFile(s->_videoState.fileName)) {
- warning("Could not open VMD %s", s->_videoState.fileName.c_str());
- break;
- }
+ return make_reg(0, 0);
+}
- if (reshowCursor)
- g_sci->_gfxCursor->kernelHide();
+reg_t kPlayVMDClose(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_video32->getVMDPlayer().close());
+}
- playVideo(videoDecoder, s->_videoState);
+reg_t kPlayVMDPlayUntilEvent(EngineState *s, int argc, reg_t *argv) {
+ const VMDPlayer::EventFlags flags = (VMDPlayer::EventFlags)argv[0].toUint16();
+ const int16 lastFrameNo = argc > 1 ? argv[1].toSint16() : -1;
+ const int16 yieldInterval = argc > 2 ? argv[2].toSint16() : -1;
+ return make_reg(0, g_sci->_video32->getVMDPlayer().kernelPlayUntilEvent(flags, lastFrameNo, yieldInterval));
+}
- if (reshowCursor)
- g_sci->_gfxCursor->kernelShow();
- break;
- case 23: // set video palette range
- s->_vmdPalStart = argv[1].toUint16();
- s->_vmdPalEnd = argv[2].toUint16();
- break;
- case 14:
- // Takes an additional integer parameter (e.g. 3)
- case 16:
- // Takes an additional parameter, usually 0
- case 21:
- // Looks to be setting the video size and position. Called with 4 extra integer
- // parameters (e.g. 86, 41, 235, 106)
- default:
- warningMsg = Common::String::format("PlayVMD - unsupported subop %d. Params: %d (", operation, argc);
+reg_t kPlayVMDShowCursor(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_video32->getVMDPlayer().setShowCursor((bool)argv[0].toUint16());
+ return s->r_acc;
+}
- for (int i = 0; i < argc; i++) {
- warningMsg += Common::String::format("%04x:%04x", PRINT_REG(argv[i]));
- warningMsg += (i == argc - 1 ? ")" : ", ");
- }
+reg_t kPlayVMDStartBlob(EngineState *s, int argc, reg_t *argv) {
+ debug("kPlayVMDStartBlob");
+ return s->r_acc;
+}
- warning("%s", warningMsg.c_str());
- break;
- }
+reg_t kPlayVMDStopBlobs(EngineState *s, int argc, reg_t *argv) {
+ debug("kPlayVMDStopBlobs");
+ return s->r_acc;
+}
+
+reg_t kPlayVMDBlack(EngineState *s, int argc, reg_t *argv) {
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ Common::Rect blackoutArea;
+ blackoutArea.left = MAX((int16)0, argv[0].toSint16());
+ blackoutArea.top = MAX((int16)0, argv[1].toSint16());
+ blackoutArea.right = MIN(scriptWidth, (int16)(argv[2].toSint16() + 1));
+ blackoutArea.bottom = MIN(scriptHeight, (int16)(argv[3].toSint16() + 1));
+ g_sci->_video32->getVMDPlayer().setBlackoutArea(blackoutArea);
+ return s->r_acc;
+}
+reg_t kPlayVMDRestrictPalette(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_video32->getVMDPlayer().restrictPalette(argv[0].toUint16(), argv[1].toUint16());
return s->r_acc;
}
diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h
index baca4a503b..dd8d76f002 100644
--- a/engines/sci/engine/state.h
+++ b/engines/sci/engine/state.h
@@ -201,6 +201,7 @@ public:
uint16 _memorySegmentSize;
byte _memorySegment[kMemorySegmentMax];
+ // TODO: Excise video code from the state manager
VideoState _videoState;
uint16 _vmdPalStart, _vmdPalEnd;
bool _syncedAudioOptions;
diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp
index 4cbb4541df..6b91bb4679 100644
--- a/engines/sci/graphics/controls32.cpp
+++ b/engines/sci/graphics/controls32.cpp
@@ -459,7 +459,7 @@ void ScrollWindow::hide() {
return;
}
- g_sci->_gfxFrameout->deleteScreenItem(_screenItem, _plane);
+ g_sci->_gfxFrameout->deleteScreenItem(*_screenItem, _plane);
_screenItem = nullptr;
g_sci->_gfxFrameout->frameOut(true);
diff --git a/engines/sci/graphics/cursor.h b/engines/sci/graphics/cursor.h
index 8d125c45b3..5125469cfe 100644
--- a/engines/sci/graphics/cursor.h
+++ b/engines/sci/graphics/cursor.h
@@ -25,6 +25,8 @@
#include "common/array.h"
#include "common/hashmap.h"
+#include "sci/sci.h"
+#include "sci/graphics/helpers.h"
namespace Sci {
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index c0feea8999..ceab949969 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -278,20 +278,52 @@ bool GfxFrameout::checkForFred(const reg_t object) {
#pragma mark -
#pragma mark Screen items
-void GfxFrameout::deleteScreenItem(ScreenItem *screenItem, Plane *plane) {
- if (screenItem->_created == 0) {
- screenItem->_created = 0;
- screenItem->_updated = 0;
- screenItem->_deleted = getScreenCount();
+void GfxFrameout::addScreenItem(ScreenItem &screenItem) const {
+ Plane *plane = _planes.findByObject(screenItem._plane);
+ if (plane == nullptr) {
+ error("GfxFrameout::addScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(screenItem._plane), PRINT_REG(screenItem._object));
+ }
+ plane->_screenItemList.add(&screenItem);
+}
+
+void GfxFrameout::updateScreenItem(ScreenItem &screenItem) const {
+ // TODO: In SCI3+ this will need to go through Plane
+// Plane *plane = _planes.findByObject(screenItem._plane);
+// if (plane == nullptr) {
+// error("GfxFrameout::updateScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(screenItem._plane), PRINT_REG(screenItem._object));
+// }
+
+ screenItem.update();
+}
+
+void GfxFrameout::deleteScreenItem(ScreenItem &screenItem) {
+ Plane *plane = _planes.findByObject(screenItem._plane);
+ if (plane == nullptr) {
+ error("GfxFrameout::deleteScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(screenItem._plane), PRINT_REG(screenItem._object));
+ }
+ if (plane->_screenItemList.findByObject(screenItem._object) == nullptr) {
+ error("GfxFrameout::deleteScreenItem: Screen item %04x:%04x not found in plane %04x:%04x", PRINT_REG(screenItem._object), PRINT_REG(screenItem._plane));
+ }
+ deleteScreenItem(screenItem, *plane);
+}
+
+void GfxFrameout::deleteScreenItem(ScreenItem &screenItem, Plane &plane) {
+ if (screenItem._created == 0) {
+ screenItem._created = 0;
+ screenItem._updated = 0;
+ screenItem._deleted = getScreenCount();
} else {
- plane->_screenItemList.erase(screenItem);
- plane->_screenItemList.pack();
+ plane._screenItemList.erase(&screenItem);
+ plane._screenItemList.pack();
}
}
-void GfxFrameout::deleteScreenItem(ScreenItem *screenItem, const reg_t planeObject) {
+void GfxFrameout::deleteScreenItem(ScreenItem &screenItem, const reg_t planeObject) {
Plane *plane = _planes.findByObject(planeObject);
- deleteScreenItem(screenItem, plane);
+ if (plane == nullptr) {
+ error("GfxFrameout::deleteScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(screenItem._object));
+ }
+ deleteScreenItem(screenItem, *plane);
}
void GfxFrameout::kernelAddScreenItem(const reg_t object) {
@@ -364,7 +396,7 @@ void GfxFrameout::kernelDeleteScreenItem(const reg_t object) {
return;
}
- deleteScreenItem(screenItem, plane);
+ deleteScreenItem(*screenItem, *plane);
}
#pragma mark -
diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h
index cc62c61d22..99658ede6a 100644
--- a/engines/sci/graphics/frameout.h
+++ b/engines/sci/graphics/frameout.h
@@ -202,14 +202,29 @@ private:
public:
/**
+ * Adds a screen item.
+ */
+ void addScreenItem(ScreenItem &screenItem) const;
+
+ /**
+ * Updates a screen item.
+ */
+ void updateScreenItem(ScreenItem &screenItem) const;
+
+ /**
+ * Deletes a screen item.
+ */
+ void deleteScreenItem(ScreenItem &screenItem);
+
+ /**
* Deletes a screen item from the given plane.
*/
- void deleteScreenItem(ScreenItem *screenItem, Plane *plane);
+ void deleteScreenItem(ScreenItem &screenItem, Plane &plane);
/**
* Deletes a screen item from the given plane.
*/
- void deleteScreenItem(ScreenItem *screenItem, const reg_t plane);
+ void deleteScreenItem(ScreenItem &screenItem, const reg_t plane);
void kernelAddScreenItem(const reg_t object);
void kernelUpdateScreenItem(const reg_t object);
diff --git a/engines/sci/graphics/paint32.cpp b/engines/sci/graphics/paint32.cpp
index bfd46484e9..74eb1629d0 100644
--- a/engines/sci/graphics/paint32.cpp
+++ b/engines/sci/graphics/paint32.cpp
@@ -81,7 +81,7 @@ void GfxPaint32::kernelDeleteLine(const reg_t screenItemObject, const reg_t plan
}
_segMan->freeHunkEntry(screenItem->_celInfo.bitmap);
- g_sci->_gfxFrameout->deleteScreenItem(screenItem, plane);
+ g_sci->_gfxFrameout->deleteScreenItem(*screenItem, *plane);
}
void GfxPaint32::plotter(int x, int y, int color, void *data) {
diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h
index caa7a9d725..56f858dc74 100644
--- a/engines/sci/graphics/screen_item32.h
+++ b/engines/sci/graphics/screen_item32.h
@@ -64,12 +64,12 @@ private:
*/
static uint16 _nextObjectId;
+public:
/**
* The parent plane of this screen item.
*/
reg_t _plane;
-public:
/**
* Scaling data used to calculate the final screen
* dimensions of the screen item as well as the scaling
diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp
new file mode 100644
index 0000000000..0f0116e41c
--- /dev/null
+++ b/engines/sci/graphics/video32.cpp
@@ -0,0 +1,365 @@
+/* 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.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "audio/mixer.h"
+#include "sci/console.h"
+#include "sci/event.h"
+#include "sci/graphics/cursor.h"
+#include "sci/graphics/frameout.h"
+#include "sci/graphics/palette32.h"
+#include "sci/graphics/text32.h"
+#include "sci/graphics/video32.h"
+#include "sci/sci.h"
+#include "video/coktel_decoder.h"
+
+namespace Sci {
+VMDPlayer::VMDPlayer(SegManager *segMan, EventManager *eventMan) :
+ _segMan(segMan),
+ _eventMan(eventMan),
+ _decoder(new Video::AdvancedVMDDecoder(Audio::Mixer::kSFXSoundType)),
+ _isOpen(false),
+ _isInitialized(false),
+ _startColor(0),
+ _planeSet(false),
+ _endColor(255),
+ _blackLines(false),
+ _doublePixels(false),
+ _lastYieldedFrameNo(0),
+ _blackoutRect(),
+ _blackPalette(false),
+ _boostPercent(100),
+ _boostStartColor(0),
+ _boostEndColor(255),
+ _leaveLastFrame(false),
+ _leaveScreenBlack(false),
+ _plane(nullptr),
+ _screenItem(nullptr),
+ _stretchVertical(false),
+ _priority(0),
+ _blackoutPlane(nullptr),
+ _yieldInterval(0) {}
+
+VMDPlayer::~VMDPlayer() {
+ close();
+ delete _decoder;
+}
+
+VMDPlayer::IOStatus VMDPlayer::open(const Common::String &fileName, const OpenFlags flags) {
+ if (_isOpen) {
+ error("Attempted to play %s, but another VMD was loaded", fileName.c_str());
+ }
+
+ if (_decoder->loadFile(fileName)) {
+ if (flags & kOpenFlagMute) {
+ _decoder->setVolume(0);
+ }
+ _isOpen = true;
+ return kIOSuccess;
+ } else {
+ return kIOError;
+ }
+}
+
+void VMDPlayer::init(const int16 x, const int16 y, const PlayFlags flags, const int16 boostPercent, const int16 boostStartColor, const int16 boostEndColor) {
+ _x = getSciVersion() >= SCI_VERSION_3 ? x : (x & ~1);
+ _y = y;
+ _leaveScreenBlack = flags & kPlayFlagLeaveScreenBlack;
+ _leaveLastFrame = flags & kPlayFlagLeaveLastFrame;
+ _doublePixels = flags & kPlayFlagDoublePixels;
+ _blackLines = flags & kPlayFlagBlackLines;
+ _boostPercent = 100 + (flags & kPlayFlagBoost ? boostPercent : 0);
+ _blackPalette = flags & kPlayFlagBlackPalette;
+ _stretchVertical = flags & kPlayFlagStretchVertical;
+ _boostStartColor = CLIP<int16>(boostStartColor, 0, 255);
+ _boostEndColor = CLIP<int16>(boostEndColor, 0, 255);
+}
+
+void VMDPlayer::restrictPalette(const uint8 startColor, const uint8 endColor) {
+ _startColor = startColor;
+ _endColor = endColor;
+}
+
+VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, const int16 lastFrameNo, const int16 yieldInterval) {
+ assert(lastFrameNo >= -1);
+
+ const int32 maxFrameNo = (int32)(_decoder->getFrameCount() - 1);
+
+ if ((flags & kEventFlagToFrame) && lastFrameNo > 0) {
+ _decoder->setEndFrame(MIN((int32)lastFrameNo, maxFrameNo));
+ } else {
+ _decoder->setEndFrame(maxFrameNo);
+ }
+
+ if (flags & kEventFlagYieldToVM) {
+ _yieldInterval = 3;
+ if (yieldInterval == -1 && !(flags & kEventFlagToFrame)) {
+ _yieldInterval = lastFrameNo;
+ } else if (yieldInterval != -1) {
+ _yieldInterval = MIN((int32)yieldInterval, maxFrameNo);
+ }
+ } else {
+ _yieldInterval = maxFrameNo;
+ }
+
+ return playUntilEvent(flags);
+}
+
+VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) {
+ // Flushing all the keyboard and mouse events out of the event manager to
+ // avoid letting any events queued from before the video started from
+ // accidentally activating an event callback
+ for (;;) {
+ const SciEvent event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_MOUSE_PRESS | SCI_EVENT_MOUSE_RELEASE | SCI_EVENT_QUIT);
+ if (event.type == SCI_EVENT_NONE) {
+ break;
+ } else if (event.type == SCI_EVENT_QUIT) {
+ return kEventFlagEnd;
+ }
+ }
+
+ _decoder->pauseVideo(false);
+
+ if (flags & kEventFlagReverse) {
+ // NOTE: This flag may not work properly since SSCI does not care
+ // if a video has audio, but the VMD decoder does.
+ const bool success = _decoder->setReverse(true);
+ assert(success);
+ _decoder->setVolume(0);
+ }
+
+ if (!_isInitialized) {
+ _isInitialized = true;
+
+ if (!_showCursor) {
+ g_sci->_gfxCursor->kernelHide();
+ }
+
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ Common::Rect vmdRect(
+ _x,
+ _y,
+ _x + _decoder->getWidth(),
+ _y + _decoder->getHeight()
+ );
+ ScaleInfo vmdScaleInfo;
+
+ if (!_blackoutRect.isEmpty() && !_planeSet) {
+ _blackoutPlane = new Plane(_blackoutRect);
+ g_sci->_gfxFrameout->addPlane(*_blackoutPlane);
+ }
+
+ if (_doublePixels) {
+ vmdScaleInfo.x = 256;
+ vmdScaleInfo.y = 256;
+ vmdScaleInfo.signal = kScaleSignalDoScaling32;
+ vmdRect.right += vmdRect.width();
+ vmdRect.bottom += vmdRect.height();
+ } else if (_stretchVertical) {
+ vmdScaleInfo.y = 256;
+ vmdScaleInfo.signal = kScaleSignalDoScaling32;
+ vmdRect.bottom += vmdRect.height();
+ }
+
+ BitmapResource vmdBitmap(_segMan, vmdRect.width(), vmdRect.height(), 255, 0, 0, screenWidth, screenHeight, 0, false);
+
+ if (screenWidth != scriptWidth || screenHeight != scriptHeight) {
+ mulru(vmdRect, Ratio(scriptWidth, screenWidth), Ratio(scriptHeight, screenHeight), 1);
+ }
+
+ CelInfo32 vmdCelInfo;
+ vmdCelInfo.bitmap = vmdBitmap.getObject();
+ _decoder->setSurfaceMemory(vmdBitmap.getPixels(), vmdBitmap.getWidth(), vmdBitmap.getHeight(), 1);
+
+ if (!_planeSet) {
+ _x = 0;
+ _y = 0;
+ _plane = new Plane(vmdRect, kPlanePicColored);
+ if (_priority) {
+ _plane->_priority = _priority;
+ }
+ g_sci->_gfxFrameout->addPlane(*_plane);
+ _screenItem = new ScreenItem(_plane->_object, vmdCelInfo, Common::Point(), vmdScaleInfo);
+ } else {
+ _screenItem = new ScreenItem(_plane->_object, vmdCelInfo, Common::Point(_x, _y), vmdScaleInfo);
+ if (_priority) {
+ _screenItem->_priority = _priority;
+ }
+ }
+
+ // NOTE: There was code for positioning the screen item using insetRect
+ // here, but none of the game scripts seem to use this functionality.
+
+ g_sci->_gfxFrameout->addScreenItem(*_screenItem);
+
+ _decoder->start();
+ }
+
+ EventFlags stopFlag = kEventFlagNone;
+ while (!g_engine->shouldQuit()) {
+ if (_decoder->endOfVideo()) {
+ stopFlag = kEventFlagEnd;
+ break;
+ }
+
+ g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame());
+ g_sci->getEngineState()->_throttleTrigger = true;
+ if (_decoder->needsUpdate()) {
+ renderFrame();
+ }
+
+ const int currentFrameNo = _decoder->getCurFrame();
+
+ if (
+ _yieldInterval > 0 &&
+ currentFrameNo != _lastYieldedFrameNo &&
+ (currentFrameNo % _yieldInterval) == 0
+ ) {
+ _lastYieldedFrameNo = currentFrameNo;
+ stopFlag = kEventFlagYieldToVM;
+ break;
+ }
+
+ if (flags & kEventFlagMouseDown && _eventMan->getSciEvent(SCI_EVENT_MOUSE_PRESS | SCI_EVENT_PEEK).type != SCI_EVENT_NONE) {
+ stopFlag = kEventFlagMouseDown;
+ break;
+ }
+
+ if (flags & kEventFlagEscapeKey) {
+ const SciEvent event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK);
+ if (event.type != SCI_EVENT_NONE && event.character == SCI_KEY_ESC) {
+ stopFlag = kEventFlagEscapeKey;
+ break;
+ }
+ }
+
+ if (flags & kEventFlagHotRectangle) {
+ // TODO: Hot rectangles
+ warning("Hot rectangles not implemented in VMD player");
+ stopFlag = kEventFlagHotRectangle;
+ break;
+ }
+ }
+
+ _decoder->pauseVideo(true);
+ return stopFlag;
+}
+
+void VMDPlayer::renderFrame() {
+ // TODO: This is kind of different from the original implementation
+ // which has access to dirty rects from the decoder; we probably do
+ // not need to care to limit output this way
+
+ _decoder->decodeNextFrame();
+
+ // NOTE: Normally this would write a hunk palette at the end of the
+ // video bitmap that CelObjMem would read out and submit, but instead
+ // we are just submitting it directly here because the decoder exposes
+ // this information a little bit differently than the one in SSCI
+ const bool dirtyPalette = _decoder->hasDirtyPalette();
+ if (dirtyPalette) {
+ Palette palette;
+ if (_blackPalette) {
+ for (uint16 i = _startColor; i <= _endColor; ++i) {
+ palette.colors[i].r = palette.colors[i].g = palette.colors[i].b = 0;
+ palette.colors[i].used = true;
+ }
+ } else {
+ const byte *vmdPalette = _decoder->getPalette() + _startColor * 3;
+ for (uint16 i = _startColor; i <= _endColor; ++i) {
+ palette.colors[i].r = *vmdPalette++;
+ palette.colors[i].g = *vmdPalette++;
+ palette.colors[i].b = *vmdPalette++;
+ palette.colors[i].used = true;
+ }
+ }
+
+ g_sci->_gfxPalette32->submit(palette);
+ g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
+ g_sci->_gfxFrameout->frameOut(true);
+
+ if (_blackPalette) {
+ const byte *vmdPalette = _decoder->getPalette() + _startColor * 3;
+ for (uint16 i = _startColor; i <= _endColor; ++i) {
+ palette.colors[i].r = *vmdPalette++;
+ palette.colors[i].g = *vmdPalette++;
+ palette.colors[i].b = *vmdPalette++;
+ palette.colors[i].used = true;
+ }
+
+ g_sci->_gfxPalette32->submit(palette);
+ g_sci->_gfxPalette32->updateForFrame();
+ g_sci->_gfxPalette32->updateHardware();
+ }
+ } else {
+ g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
+ g_sci->getSciDebugger()->onFrame();
+ g_sci->_gfxFrameout->frameOut(true);
+ g_sci->_gfxFrameout->throttle();
+ }
+}
+
+VMDPlayer::IOStatus VMDPlayer::close() {
+ if (!_isOpen) {
+ return kIOSuccess;
+ }
+
+ _decoder->close();
+ _isOpen = false;
+ _isInitialized = false;
+
+ if (_planeSet && _screenItem != nullptr) {
+ g_sci->_gfxFrameout->deleteScreenItem(*_screenItem);
+ _screenItem = nullptr;
+ } else if (_plane != nullptr) {
+ g_sci->_gfxFrameout->deletePlane(*_plane);
+ _plane = nullptr;
+ }
+
+ if (!_leaveLastFrame && _leaveScreenBlack) {
+ // This call *actually* deletes the plane/screen item
+ g_sci->_gfxFrameout->frameOut(true);
+ }
+
+ if (_blackoutPlane != nullptr) {
+ g_sci->_gfxFrameout->deletePlane(*_blackoutPlane);
+ _blackoutPlane = nullptr;
+ }
+
+ if (!_leaveLastFrame && !_leaveScreenBlack) {
+ // This call *actually* deletes the blackout plane
+ g_sci->_gfxFrameout->frameOut(true);
+ }
+
+ if (!_showCursor) {
+ g_sci->_gfxCursor->kernelShow();
+ }
+
+ _planeSet = false;
+ _priority = 0;
+ return kIOSuccess;
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/graphics/video32.h b/engines/sci/graphics/video32.h
new file mode 100644
index 0000000000..481b222f6f
--- /dev/null
+++ b/engines/sci/graphics/video32.h
@@ -0,0 +1,275 @@
+/* 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.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef SCI_GRAPHICS_VIDEO32_H
+#define SCI_GRAPHICS_VIDEO32_H
+
+namespace Video { class AdvancedVMDDecoder; }
+namespace Sci {
+class Plane;
+class ScreenItem;
+class SegManager;
+
+class VMDPlayer {
+public:
+ enum OpenFlags {
+ kOpenFlagNone = 0,
+ kOpenFlagMute = 1
+ };
+
+ enum IOStatus {
+ kIOSuccess = 0,
+ kIOError = 0xFFFF
+ };
+
+ enum PlayFlags {
+ kPlayFlagNone = 0,
+ kPlayFlagDoublePixels = 1,
+ kPlayFlagNoFrameskip = 2, // NOTE: the current VMD decoder does not allow this
+ kPlayFlagBlackLines = 4,
+ kPlayFlagBoost = 0x10,
+ kPlayFlagLeaveScreenBlack = 0x20,
+ kPlayFlagLeaveLastFrame = 0x40,
+ kPlayFlagBlackPalette = 0x80,
+ kPlayFlagStretchVertical = 0x100
+ };
+
+ enum EventFlags {
+ kEventFlagNone = 0,
+ kEventFlagEnd = 1,
+ kEventFlagEscapeKey = 2,
+ kEventFlagMouseDown = 4,
+ kEventFlagHotRectangle = 8,
+ kEventFlagToFrame = 0x10,
+ kEventFlagYieldToVM = 0x20,
+ kEventFlagReverse = 0x80
+ };
+
+ VMDPlayer(SegManager *segMan, EventManager *eventMan);
+ ~VMDPlayer();
+
+ /**
+ * Opens a stream to a VMD resource.
+ */
+ IOStatus open(const Common::String &fileName, const OpenFlags flags);
+
+ /**
+ * Initializes the VMD rendering parameters for the
+ * current VMD. This must be called after `open`.
+ */
+ void init(const int16 x, const int16 y, const PlayFlags flags, const int16 boostPercent, const int16 boostStartColor, const int16 boostEndColor);
+
+ /**
+ * Stops playback and closes the currently open VMD stream.
+ */
+ IOStatus close();
+
+ /**
+ * Restricts use of the system palette by VMD playback to
+ * the given range of palette indexes.
+ */
+ void restrictPalette(const uint8 startColor, const uint8 endColor);
+
+ // NOTE: Was WaitForEvent in SSCI
+ EventFlags kernelPlayUntilEvent(const EventFlags flags, const int16 lastFrameNo, const int16 yieldInterval);
+
+ /**
+ * Sets the area of the screen that should be blacked out
+ * during VMD playback.
+ */
+ void setBlackoutArea(const Common::Rect &rect) { _blackoutRect = rect; }
+
+ /**
+ * Sets whether or not the mouse cursor should be drawn.
+ * This does not have any effect during playback, but can
+ * be used to prevent the mouse cursor from being shown
+ * after the video has finished.
+ */
+ void setShowCursor(const bool shouldShow) { _showCursor = shouldShow; }
+
+private:
+ SegManager *_segMan;
+ EventManager *_eventMan;
+ Video::AdvancedVMDDecoder *_decoder;
+
+ /**
+ * Plays the VMD until an event occurs (e.g. user
+ * presses escape, clicks, etc.).
+ */
+ EventFlags playUntilEvent(const EventFlags flags);
+
+ /**
+ * Renders a frame of video to the output bitmap.
+ */
+ void renderFrame();
+
+ /**
+ * Whether or not a VMD stream has been opened with
+ * `open`.
+ */
+ bool _isOpen;
+
+ /**
+ * Whether or not a VMD player has been initialised
+ * with `init`.
+ */
+ bool _isInitialized;
+
+ /**
+ * Whether or not the playback area of the VMD
+ * should be left black at the end of playback.
+ */
+ bool _leaveScreenBlack;
+
+ /**
+ * Whether or not the area of the VMD should be left
+ * displaying the final frame of the video.
+ */
+ bool _leaveLastFrame;
+
+ /**
+ * Whether or not the video should be pixel doubled.
+ */
+ bool _doublePixels;
+
+ /**
+ * Whether or not the video should be pixel doubled
+ * vertically only.
+ */
+ bool _stretchVertical;
+
+ /**
+ * Whether or not black lines should be rendered
+ * across the video.
+ */
+ bool _blackLines;
+
+ /**
+ * The amount of brightness boost for the video.
+ * Values above 100 increase brightness; values below
+ * 100 reduce it.
+ */
+ int16 _boostPercent;
+
+ /**
+ * The first color in the palette that should be
+ * brightness boosted.
+ */
+ uint8 _boostStartColor;
+
+ /**
+ * The last color in the palette that should be
+ * brightness boosted.
+ */
+ uint8 _boostEndColor;
+
+ /**
+ * The first color in the system palette that the VMD
+ * can write to.
+ */
+ uint8 _startColor;
+
+ /**
+ * The last color in the system palette that the VMD
+ * can write to.
+ */
+ uint8 _endColor;
+
+ /**
+ * If true, video frames are rendered after a blank
+ * palette is submitted to the palette manager,
+ * which is then restored after the video pixels
+ * have already been rendered.
+ */
+ bool _blackPalette;
+
+ // TODO: planeSet and priority are used in SCI3+ only
+ bool _planeSet;
+
+ /**
+ * The screen priority of the video.
+ * @see ScreenItem::_priority
+ */
+ int _priority;
+
+ /**
+ * The plane where the VMD will be drawn.
+ */
+ Plane *_plane;
+
+ /**
+ * The screen item representing the VMD surface.
+ */
+ ScreenItem *_screenItem;
+
+ /**
+ * An optional plane that will be used to black out
+ * areas of the screen outside the area of the VMD
+ * surface.
+ */
+ Plane *_blackoutPlane;
+
+ /**
+ * The dimensions of the blackout plane.
+ */
+ Common::Rect _blackoutRect;
+
+ /**
+ * Whether or not the mouse cursor should be shown
+ * during playback.
+ */
+ bool _showCursor;
+
+ /**
+ * The location of the VMD plane, in game script
+ * coordinates.
+ */
+ int16 _x, _y;
+
+ /**
+ * For VMDs played with the `kEventFlagYieldToVM` flag,
+ * the number of frames that should be drawn until
+ * yielding back to the SCI VM.
+ */
+ int32 _yieldInterval;
+
+ /**
+ * For VMDs played with the `kEventFlagYieldToVM` flag,
+ * the last frame when control of the main thread was
+ * yielded back to the SCI VM.
+ */
+ int _lastYieldedFrameNo;
+};
+
+class Video32 {
+public:
+ Video32(SegManager *segMan, EventManager *eventMan) :
+ _VMDPlayer(segMan, eventMan) {}
+
+ VMDPlayer &getVMDPlayer() { return _VMDPlayer; }
+
+private:
+ VMDPlayer _VMDPlayer;
+};
+} // End of namespace Sci
+
+#endif
diff --git a/engines/sci/module.mk b/engines/sci/module.mk
index 5d54e2a52c..1511356747 100644
--- a/engines/sci/module.mk
+++ b/engines/sci/module.mk
@@ -91,6 +91,7 @@ MODULE_OBJS += \
graphics/remap32.o \
graphics/screen_item32.o \
graphics/text32.o \
+ graphics/video32.o \
sound/audio32.o \
sound/decoders/sol.o \
video/robot_decoder.o
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 41fa144b06..0e63918442 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -69,7 +69,9 @@
#include "sci/graphics/palette32.h"
#include "sci/graphics/remap32.h"
#include "sci/graphics/text32.h"
+#include "sci/graphics/video32.h"
#include "sci/sound/audio32.h"
+// TODO: Move this to video32
#include "sci/video/robot_decoder.h"
#endif
@@ -92,6 +94,7 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam
_sync = nullptr;
#ifdef ENABLE_SCI32
_audio32 = nullptr;
+ _video32 = nullptr;
#endif
_features = 0;
_resMan = 0;
@@ -277,15 +280,20 @@ Common::Error SciEngine::run() {
if (getGameId() == GID_CHRISTMAS1990)
_vocabulary = new Vocabulary(_resMan, false);
+ _gamestate = new EngineState(segMan);
+ _eventMan = new EventManager(_resMan->detectFontExtended());
#ifdef ENABLE_SCI32
if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
_audio32 = new Audio32(_resMan);
} else
#endif
_audio = new AudioPlayer(_resMan);
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ _video32 = new Video32(segMan, _eventMan);
+ }
+#endif
_sync = new Sync(_resMan, segMan);
- _gamestate = new EngineState(segMan);
- _eventMan = new EventManager(_resMan->detectFontExtended());
// Create debugger console. It requires GFX and _gamestate to be initialized
_console = new Console(this);
diff --git a/engines/sci/sci.h b/engines/sci/sci.h
index 3216fa13b9..6481365936 100644
--- a/engines/sci/sci.h
+++ b/engines/sci/sci.h
@@ -80,9 +80,11 @@ class GfxText32;
class GfxTransitions;
#ifdef ENABLE_SCI32
+// TODO: Move RobotDecoder to Video32
class RobotDecoder;
class GfxFrameout;
class Audio32;
+class Video32;
#endif
// our engine debug levels
@@ -371,6 +373,7 @@ public:
#ifdef ENABLE_SCI32
Audio32 *_audio32;
+ Video32 *_video32;
RobotDecoder *_robotDecoder;
GfxFrameout *_gfxFrameout; // kFrameout and the like for 32-bit gfx
#endif