diff options
-rw-r--r-- | engines/sci/engine/kernel.h | 11 | ||||
-rw-r--r-- | engines/sci/engine/kernel_tables.h | 18 | ||||
-rw-r--r-- | engines/sci/engine/kvideo.cpp | 163 | ||||
-rw-r--r-- | engines/sci/engine/state.h | 1 | ||||
-rw-r--r-- | engines/sci/graphics/controls32.cpp | 2 | ||||
-rw-r--r-- | engines/sci/graphics/cursor.h | 2 | ||||
-rw-r--r-- | engines/sci/graphics/frameout.cpp | 52 | ||||
-rw-r--r-- | engines/sci/graphics/frameout.h | 19 | ||||
-rw-r--r-- | engines/sci/graphics/paint32.cpp | 2 | ||||
-rw-r--r-- | engines/sci/graphics/screen_item32.h | 2 | ||||
-rw-r--r-- | engines/sci/graphics/video32.cpp | 365 | ||||
-rw-r--r-- | engines/sci/graphics/video32.h | 275 | ||||
-rw-r--r-- | engines/sci/module.mk | 1 | ||||
-rw-r--r-- | engines/sci/sci.cpp | 12 | ||||
-rw-r--r-- | engines/sci/sci.h | 3 |
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 |