From f15f9e3b7c9dd7594a60aa230fed05b965a7a587 Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Wed, 5 Jul 2017 00:30:30 -0500 Subject: SCI32: Refactor Video32 code to reduce code & feature duplication --- engines/sci/detection_tables.h | 20 +- engines/sci/graphics/video32.cpp | 657 +++++++++++++++++---------------------- engines/sci/graphics/video32.h | 429 +++++++++++++------------ engines/sci/sci.cpp | 5 +- 4 files changed, 532 insertions(+), 579 deletions(-) diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h index 4230a3bb0f..506187659a 100644 --- a/engines/sci/detection_tables.h +++ b/engines/sci/detection_tables.h @@ -724,10 +724,13 @@ static const struct ADGameDescription SciGameDescriptions[] = { #ifdef ENABLE_SCI32 #define GUIO_GK1_FLOPPY GUIO2(GUIO_NOSPEECH, \ GAMEOPTION_ORIGINAL_SAVELOAD) -#define GUIO_GK1_CD GUIO4(GUIO_LINKSPEECHTOSFX, \ +#define GUIO_GK1_CD_DOS GUIO4(GUIO_LINKSPEECHTOSFX, \ GAMEOPTION_ORIGINAL_SAVELOAD, \ GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, \ GAMEOPTION_HQ_VIDEO) +#define GUIO_GK1_CD_WIN GUIO3(GUIO_LINKSPEECHTOSFX, \ + GAMEOPTION_ORIGINAL_SAVELOAD, \ + GAMEOPTION_HQ_VIDEO) #define GUIO_GK1_MAC GUIO_GK1_FLOPPY // Gabriel Knight - English DOS Floppy @@ -776,7 +779,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.map", 0, "372d059f75856afa6d73dd84cbb8913d", 10996}, {"resource.000", 0, "69b7516962510f780d38519cc15fcc7c", 12581736}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_TESTING, GUIO_GK1_CD }, + Common::EN_ANY, Common::kPlatformDOS, ADGF_CD | ADGF_TESTING, GUIO_GK1_CD_DOS }, // Gabriel Knight - English Windows CD (from jvprat) // Executable scanning reports "2.000.000", VERSION file reports "01.100.000" @@ -784,7 +787,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.map", 0, "372d059f75856afa6d73dd84cbb8913d", 10996}, {"resource.000", 0, "69b7516962510f780d38519cc15fcc7c", 12581736}, AD_LISTEND}, - Common::EN_ANY, Common::kPlatformWindows, ADGF_CD | ADGF_TESTING, GUIO_GK1_CD }, + Common::EN_ANY, Common::kPlatformWindows, ADGF_CD | ADGF_TESTING, GUIO_GK1_CD_WIN }, // Gabriel Knight - German DOS CD (from Tobis87) // SCI interpreter version 2.000.000 @@ -792,7 +795,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.map", 0, "a7d3e55114c65647310373cb390815ba", 11392}, {"resource.000", 0, "091cf08910780feabc56f8551b09cb36", 13400497}, AD_LISTEND}, - Common::DE_DEU, Common::kPlatformDOS, ADGF_CD | ADGF_TESTING, GUIO_GK1_CD }, + Common::DE_DEU, Common::kPlatformDOS, ADGF_CD | ADGF_TESTING, GUIO_GK1_CD_DOS }, // Gabriel Knight - Spanish DOS CD (from jvprat) // Executable scanning reports "2.000.000", VERSION file reports "1.000.000, April 13, 1995" @@ -800,7 +803,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.map", 0, "7cb6e9bba15b544ec7a635c45bde9953", 11404}, {"resource.000", 0, "091cf08910780feabc56f8551b09cb36", 13381599}, AD_LISTEND}, - Common::ES_ESP, Common::kPlatformDOS, ADGF_CD | ADGF_TESTING, GUIO_GK1_CD }, + Common::ES_ESP, Common::kPlatformDOS, ADGF_CD | ADGF_TESTING, GUIO_GK1_CD_DOS }, // Gabriel Knight - French DOS CD (from Hkz) // VERSION file reports "1.000.000, May 3, 1994" @@ -808,7 +811,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.map", 0, "55f909ba93a2515042a08d8a2da8414e", 11392}, {"resource.000", 0, "091cf08910780feabc56f8551b09cb36", 13325145}, AD_LISTEND}, - Common::FR_FRA, Common::kPlatformDOS, ADGF_CD | ADGF_TESTING, GUIO_GK1_CD }, + Common::FR_FRA, Common::kPlatformDOS, ADGF_CD | ADGF_TESTING, GUIO_GK1_CD_DOS }, // Gabriel Knight - Spanish Windows CD (from jvprat) // Executable scanning reports "2.000.000", VERSION file reports "1.000.000, April 13, 1995" @@ -816,7 +819,7 @@ static const struct ADGameDescription SciGameDescriptions[] = { {"resource.map", 0, "7cb6e9bba15b544ec7a635c45bde9953", 11404}, {"resource.000", 0, "091cf08910780feabc56f8551b09cb36", 13381599}, AD_LISTEND}, - Common::ES_ESP, Common::kPlatformWindows, ADGF_CD | ADGF_TESTING, GUIO_GK1_CD }, + Common::ES_ESP, Common::kPlatformWindows, ADGF_CD | ADGF_TESTING, GUIO_GK1_CD_WIN }, // Gabriel Knight - English Macintosh (Floppy!) // This version is hi-res ONLY, so it should NOT get GAMEOPTION_HIGH_RESOLUTION_GRAPHICS @@ -831,7 +834,8 @@ static const struct ADGameDescription SciGameDescriptions[] = { Common::EN_ANY, Common::kPlatformMacintosh, ADGF_MACRESFORK | ADGF_UNSTABLE, GUIO_GK1_MAC }, #undef GUIO_GK1_FLOPPY -#undef GUIO_GK1_CD +#undef GUIO_GK1_CD_DOS +#undef GUIO_GK1_CD_WIN #undef GUIO_GK1_MAC #define GUIO_GK2_DEMO GUIO8(GUIO_NOSUBTITLES, \ diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp index 7d2daf787a..ec37d1c292 100644 --- a/engines/sci/graphics/video32.cpp +++ b/engines/sci/graphics/video32.cpp @@ -56,80 +56,191 @@ namespace Graphics { struct Surface; } namespace Sci { -/** - * @returns true if the player should quit - */ -static bool flushEvents(EventManager *eventMan) { +static void flushEvents(EventManager *eventMan) { // Flushing all the keyboard and mouse events out of the event manager // keeps events queued from before the start of playback from accidentally // activating a video stop flag for (;;) { - const SciEvent event = eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_MOUSE_PRESS | SCI_EVENT_MOUSE_RELEASE | SCI_EVENT_HOT_RECTANGLE | SCI_EVENT_QUIT); + const SciEvent event = eventMan->getSciEvent(SCI_EVENT_ANY & ~SCI_EVENT_QUIT); if (event.type == SCI_EVENT_NONE) { break; - } else if (event.type == SCI_EVENT_QUIT) { - return true; } } +} + +bool VideoPlayer::open(const Common::String &fileName) { + if (!_decoder->loadFile(fileName)) { + warning("Failed to load %s", fileName.c_str()); + return false; + } + +#ifndef USE_RGB_COLOR + // KQ7 2.00b videos are compressed in 24bpp Cinepak, so cannot play on + // a system with no RGB support + if (_decoder->getPixelFormat().bytesPerPixel != 1) { + void showScummVMDialog(const Common::String &message); + showScummVMDialog(Common::String::format(_("Cannot play back %dbpp video on a system with maximum color depth of 8bpp"), _decoder->getPixelFormat().bpp())); + _decoder->close(); + return false; + } +#endif + + return true; +} + +bool VideoPlayer::startHQVideo() { +#ifdef USE_RGB_COLOR + // Optimize rendering performance for unscaled videos, and allow + // better-than-NN interpolation for videos that are scaled + if (shouldStartHQVideo()) { + // TODO: Search for and use the best supported format (which may be + // lower than 32bpp) once the scaling code in Graphics supports + // 16bpp/24bpp, and once the SDL backend can correctly communicate + // supported pixel formats above whatever format is currently used by + // _hwsurface. Right now, this will either show an error dialog (OpenGL) + // or just crash entirely (SDL) if the backend does not support this + // 32bpp pixel format, which sucks since this code really ought to be + // able to fall back to NN scaling for games with 256-color videos + // without any error. + const Graphics::PixelFormat format(4, 8, 8, 8, 8, 24, 16, 8, 0); + g_sci->_gfxFrameout->setPixelFormat(format); + _hqVideoMode = (g_system->getScreenFormat() == format); + return _hqVideoMode; + } else { + _hqVideoMode = false; + } +#endif return false; } -static void directWriteToSystem(Video::VideoDecoder *decoder, const Common::Rect &drawRect, const bool setSystemPalette, const Graphics::Surface *nextFrame = nullptr) { +bool VideoPlayer::endHQVideo() { +#ifdef USE_RGB_COLOR + if (g_system->getScreenFormat().bytesPerPixel != 1) { + const Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8(); + g_sci->_gfxFrameout->setPixelFormat(format); + assert(g_system->getScreenFormat() == format); + _hqVideoMode = false; + return true; + } +#endif - // VMDPlayer needs to decode the frame early so it can submit palette - // updates; calling decodeNextFrame again loses frames - if (!nextFrame) { - nextFrame = decoder->decodeNextFrame(); + return false; +} + +VideoPlayer::EventFlags VideoPlayer::playUntilEvent(const EventFlags flags, const uint32 maxSleepMs) { + flushEvents(_eventMan); + _decoder->start(); + + EventFlags stopFlag = kEventFlagNone; + for (;;) { + g_sci->sleep(MIN(_decoder->getTimeToNextFrame(), maxSleepMs)); + const Graphics::Surface *nextFrame = nullptr; + // If a decoder needs more than one update per loop, this means we are + // running behind and should skip rendering these frames (but must still + // submit any palettes from skipped frames) + while (_decoder->needsUpdate()) { + nextFrame = _decoder->decodeNextFrame(); + if (_decoder->hasDirtyPalette()) { + submitPalette(_decoder->getPalette()); + } + } + + // Some frames may contain only audio and/or palette data; this occurs + // with Duck videos and is not an error + if (nextFrame) { + renderFrame(*nextFrame); + } + + stopFlag = checkForEvent(flags); + if (stopFlag != kEventFlagNone) { + break; + } } - assert(nextFrame); - if (setSystemPalette && - g_system->getScreenFormat().bytesPerPixel == 1 && - decoder->hasDirtyPalette()) { + return stopFlag; +} - const uint8 *palette = decoder->getPalette(); - assert(palette); - g_system->getPaletteManager()->setPalette(palette, 0, 256); +VideoPlayer::EventFlags VideoPlayer::checkForEvent(const EventFlags flags) { + if (g_engine->shouldQuit() || _decoder->endOfVideo()) { + return kEventFlagEnd; + } - // KQ7 1.x has videos encoded using Microsoft Video 1 where palette 0 is - // white and 255 is black, which is basically the opposite of DOS/Win - // SCI palettes. So, when drawing to an 8bpp hwscreen, whenever a new - // palette is seen, the screen must be re-filled with the new black - // entry to ensure areas outside the video are always black and not some - // other color - for (int color = 0; color < 256; ++color) { - if (palette[0] == 0 && palette[1] == 0 && palette[2] == 0) { - g_system->fillScreen(color); - break; + SciEvent event = _eventMan->getSciEvent(SCI_EVENT_MOUSE_PRESS | SCI_EVENT_PEEK); + if ((flags & kEventFlagMouseDown) && event.type == SCI_EVENT_MOUSE_PRESS) { + return kEventFlagMouseDown; + } + + event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK); + if ((flags & kEventFlagEscapeKey) && event.type == SCI_EVENT_KEYBOARD) { + if (getSciVersion() < SCI_VERSION_3) { + while ((event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD)), + event.type != SCI_EVENT_NONE) { + if (event.character == SCI_KEY_ESC) { + return kEventFlagEscapeKey; + } } - palette += 3; + } else if (event.character == SCI_KEY_ESC) { + return kEventFlagEscapeKey; } } + return kEventFlagNone; +} + +void VideoPlayer::submitPalette(const uint8 palette[256 * 3]) const { +#ifdef USE_RGB_COLOR + if (g_system->getScreenFormat().bytesPerPixel != 1) { + return; + } +#endif + + assert(palette); + g_system->getPaletteManager()->setPalette(palette, 0, 256); + + // KQ7 1.x has videos encoded using Microsoft Video 1 where palette 0 is + // white and 255 is black, which is basically the opposite of DOS/Win + // SCI palettes. So, when drawing to an 8bpp hwscreen, whenever a new + // palette is seen, the screen must be re-filled with the new black + // entry to ensure areas outside the video are always black and not some + // other color + for (int color = 0; color < 256; ++color) { + if (palette[0] == 0 && palette[1] == 0 && palette[2] == 0) { + g_system->fillScreen(color); + break; + } + palette += 3; + } +} + +void VideoPlayer::renderFrame(const Graphics::Surface &nextFrame) const { bool freeConvertedFrame; Graphics::Surface *convertedFrame; // Avoid creating a duplicate copy of the surface when it is not necessary - if (decoder->getPixelFormat() == g_system->getScreenFormat()) { + if (_decoder->getPixelFormat() == g_system->getScreenFormat()) { freeConvertedFrame = false; - convertedFrame = const_cast(nextFrame); + convertedFrame = const_cast(&nextFrame); } else { freeConvertedFrame = true; - convertedFrame = nextFrame->convertTo(g_system->getScreenFormat(), decoder->getPalette()); + convertedFrame = nextFrame.convertTo(g_system->getScreenFormat(), _decoder->getPalette()); } assert(convertedFrame); - if (decoder->getWidth() != drawRect.width() || decoder->getHeight() != drawRect.height()) { + if (_decoder->getWidth() != _drawRect.width() || _decoder->getHeight() != _drawRect.height()) { Graphics::Surface *const unscaledFrame(convertedFrame); + // TODO: The only reason TransparentSurface is used here because it is + // where common scaler code is right now. const Graphics::TransparentSurface tsUnscaledFrame(*unscaledFrame); #ifdef USE_RGB_COLOR - if (g_system->getScreenFormat().bytesPerPixel != 1) { - convertedFrame = tsUnscaledFrame.scaleT(drawRect.width(), drawRect.height()); + if (_hqVideoMode) { + convertedFrame = tsUnscaledFrame.scaleT(_drawRect.width(), _drawRect.height()); } else { -#else +#elif 1 { +#else + } #endif - convertedFrame = tsUnscaledFrame.scaleT(drawRect.width(), drawRect.height()); + convertedFrame = tsUnscaledFrame.scaleT(_drawRect.width(), _drawRect.height()); } assert(convertedFrame); if (freeConvertedFrame) { @@ -139,29 +250,50 @@ static void directWriteToSystem(Video::VideoDecoder *decoder, const Common::Rect freeConvertedFrame = true; } - g_system->copyRectToScreen(convertedFrame->getPixels(), convertedFrame->pitch, drawRect.left, drawRect.top, convertedFrame->w, convertedFrame->h); + g_system->copyRectToScreen(convertedFrame->getPixels(), convertedFrame->pitch, _drawRect.left, _drawRect.top, convertedFrame->w, convertedFrame->h); g_sci->_gfxFrameout->updateScreen(); + if (freeConvertedFrame) { convertedFrame->free(); delete convertedFrame; } } +template +void VideoPlayer::renderLQToSurface(Graphics::Surface &out, const Graphics::Surface &nextFrame, const bool doublePixels, const bool blackLines) const { + + const int lineCount = blackLines ? 2 : 1; + if (doublePixels) { + for (int16 y = 0; y < nextFrame.h * 2; y += lineCount) { + const PixelType *source = (const PixelType *)nextFrame.getBasePtr(0, y >> 1); + PixelType *target = (PixelType *)out.getBasePtr(0, y); + for (int16 x = 0; x < nextFrame.w; ++x) { + *target++ = *source; + *target++ = *source++; + } + } + } else if (blackLines) { + for (int16 y = 0; y < nextFrame.h; y += lineCount) { + const PixelType *source = (const PixelType *)nextFrame.getBasePtr(0, y); + PixelType *target = (PixelType *)out.getBasePtr(0, y); + memcpy(target, source, out.w * sizeof(PixelType)); + } + } else { + out.copyRectToSurface(nextFrame.getPixels(), nextFrame.pitch, 0, 0, nextFrame.w, nextFrame.h); + } +} + #pragma mark SEQPlayer -SEQPlayer::SEQPlayer(SegManager *segMan, EventManager *eventMan) : - _segMan(segMan), - _eventMan(eventMan), - _decoder(nullptr) {} +SEQPlayer::SEQPlayer(EventManager *eventMan) : + VideoPlayer(eventMan) {} -void SEQPlayer::play(const Common::String &fileName, const int16 numTicks, const int16 x, const int16 y) { +void SEQPlayer::play(const Common::String &fileName, const int16 numTicks, const int16, const int16) { - close(); + _decoder.reset(new SEQDecoder(numTicks)); - _decoder = new SEQDecoder(numTicks); - if (!_decoder->loadFile(fileName)) { - warning("[SEQPlayer::play]: Failed to load %s", fileName.c_str()); - delete _decoder; + if (!VideoPlayer::open(fileName)) { + _decoder.reset(); return; } @@ -183,113 +315,34 @@ void SEQPlayer::play(const Common::String &fileName, const int16 numTicks, const _drawRect.setWidth(scaledWidth); _drawRect.setHeight(scaledHeight); -#ifdef USE_RGB_COLOR - // Optimize rendering performance for unscaled videos, and allow - // better-than-NN interpolation for videos that are scaled - if (ConfMan.getBool("enable_hq_video") && - (_decoder->getWidth() != scaledWidth || _decoder->getHeight() != scaledHeight)) { - // TODO: Search for and use the best supported format (which may be - // lower than 32bpp) once the scaling code in Graphics supports - // 16bpp/24bpp, and once the SDL backend can correctly communicate - // supported pixel formats above whatever format is currently used by - // _hwsurface. Right now, this will just crash ScummVM if the backend - // does not support a 32bpp pixel format, which sucks since this code - // really ought to be able to fall back to NN scaling for games with - // 256-color videos. - const Graphics::PixelFormat format = Graphics::createPixelFormat<8888>(); - g_sci->_gfxFrameout->setPixelFormat(format); - } -#endif - - _decoder->start(); - - while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) { - g_sci->sleep(_decoder->getTimeToNextFrame()); - while (_decoder->needsUpdate()) { - renderFrame(); - } - - // SSCI did not allow SEQ animations to be bypassed like this - SciEvent event = _eventMan->getSciEvent(SCI_EVENT_MOUSE_PRESS | SCI_EVENT_PEEK); - if (event.type == SCI_EVENT_MOUSE_PRESS) { - break; - } - - event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK); - if (event.type == SCI_EVENT_KEYBOARD) { - bool stop = false; - while ((event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD)), - event.type != SCI_EVENT_NONE) { - if (event.character == SCI_KEY_ESC) { - stop = true; - break; - } - } - - if (stop) { - break; - } - } - } - - close(); -} - -void SEQPlayer::renderFrame() const { - directWriteToSystem(_decoder, _drawRect, true); -} - -void SEQPlayer::close() { -#ifdef USE_RGB_COLOR - if (g_system->getScreenFormat().bytesPerPixel != 1) { - const Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8(); - g_sci->_gfxFrameout->setPixelFormat(format); - } -#endif - + startHQVideo(); + playUntilEvent(kEventFlagMouseDown | kEventFlagEscapeKey); + endHQVideo(); g_system->fillScreen(0); - delete _decoder; - _decoder = nullptr; + _decoder.reset(); } #pragma mark - #pragma mark AVIPlayer AVIPlayer::AVIPlayer(EventManager *eventMan) : - _eventMan(eventMan), - _decoder(new Video::AVIDecoder(Audio::Mixer::kSFXSoundType)), + VideoPlayer(eventMan, new Video::AVIDecoder(Audio::Mixer::kSFXSoundType)), _status(kAVINotOpen) {} -AVIPlayer::~AVIPlayer() { - close(); - delete _decoder; -} - AVIPlayer::IOStatus AVIPlayer::open(const Common::String &fileName) { if (_status != kAVINotOpen) { close(); } - if (!_decoder->loadFile(fileName)) { + if (!VideoPlayer::open(fileName)) { return kIOFileNotFound; } -#ifndef USE_RGB_COLOR - // KQ7 2.00b videos are compressed in 24bpp Cinepak, so cannot play on - // a system with no RGB support - if (_decoder->getPixelFormat().bytesPerPixel != 1) { - void showScummVMDialog(const Common::String &message); - showScummVMDialog(Common::String::format(_("Cannot play back %dbpp video on a system with maximum color depth of 8bpp"), _decoder->getPixelFormat().bpp())); - _decoder->close(); - return kIOFileNotFound; - } -#endif - _status = kAVIOpen; return kIOSuccess; } -AVIPlayer::IOStatus AVIPlayer::init(const bool pixelDouble) { +AVIPlayer::IOStatus AVIPlayer::init(const bool doublePixels) { // Calls to initialize the AVI player in SCI can be made in a few ways: // // * kShowMovie(WinInit, x, y) to render the video at (x,y) using its @@ -322,7 +375,7 @@ AVIPlayer::IOStatus AVIPlayer::init(const bool pixelDouble) { int16 width = _decoder->getWidth(); int16 height = _decoder->getHeight(); - if (pixelDouble) { + if (doublePixels) { width *= 2; height *= 2; } @@ -342,28 +395,8 @@ AVIPlayer::IOStatus AVIPlayer::init(const bool pixelDouble) { _drawRect.setWidth(width); _drawRect.setHeight(height); -#ifdef USE_RGB_COLOR - // Optimize rendering performance for unscaled videos, and allow - // better-than-NN interpolation for videos that are scaled - if (ConfMan.getBool("enable_hq_video") && - (_decoder->getWidth() != width || _decoder->getHeight() != height)) { - - // TODO: Search for and use the best supported format (which may be - // lower than 32bpp) once the scaling code in Graphics supports - // 16bpp/24bpp, and once the SDL backend can correctly communicate - // supported pixel formats above whatever format is currently used by - // _hwsurface. Right now, this will just crash ScummVM if the backend - // does not support a 32bpp pixel format, which sucks since this code - // really ought to be able to fall back to NN scaling for games with - // 256-color videos. - const Graphics::PixelFormat format = Graphics::createPixelFormat<8888>(); - g_sci->_gfxFrameout->setPixelFormat(format); - } else { -#else - { -#endif - const Graphics::PixelFormat format = _decoder->getPixelFormat(); - g_sci->_gfxFrameout->setPixelFormat(format); + if (!startHQVideo() && _decoder->getPixelFormat().bytesPerPixel != 1) { + g_sci->_gfxFrameout->setPixelFormat(_decoder->getPixelFormat()); } return kIOSuccess; @@ -379,10 +412,8 @@ AVIPlayer::IOStatus AVIPlayer::play(const int16 from, const int16 to, const int1 _decoder->setEndFrame(to); } - if (!async) { - renderVideo(); - } else if (getSciVersion() == SCI_VERSION_2_1_EARLY) { - playUntilEvent((EventFlags)(kEventFlagEnd | kEventFlagEscapeKey)); + if (!async || getSciVersion() == SCI_VERSION_2_1_EARLY) { + playUntilEvent(kEventFlagNone); } else { _status = kAVIPlaying; } @@ -390,15 +421,11 @@ AVIPlayer::IOStatus AVIPlayer::play(const int16 from, const int16 to, const int1 return kIOSuccess; } -void AVIPlayer::renderVideo() const { - _decoder->start(); - - while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) { - g_sci->sleep(_decoder->getTimeToNextFrame()); - while (_decoder->needsUpdate()) { - renderFrame(); - } - } +AVIPlayer::EventFlags AVIPlayer::playUntilEvent(const EventFlags flags, const uint32 maxSleepMs) { + // NOTE: In SSCI, whether or not a video could be skipped was controlled by + // game scripts; here, we always allow skipping video with the mouse or + // escape key, to improve the user experience + return VideoPlayer::playUntilEvent(flags | kEventFlagMouseDown | kEventFlagEscapeKey, maxSleepMs); } AVIPlayer::IOStatus AVIPlayer::close() { @@ -406,16 +433,15 @@ AVIPlayer::IOStatus AVIPlayer::close() { return kIOSuccess; } -#ifdef USE_RGB_COLOR - if (g_system->getScreenFormat().bytesPerPixel != 1) { - const Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8(); - g_sci->_gfxFrameout->setPixelFormat(format); + if (!endHQVideo()) { + // This fixes a single-frame white flash after playback of the KQ7 1.x + // videos, which replace palette entry 0 with white + const uint8 black[3] = { 0, 0, 0 }; + g_system->getPaletteManager()->setPalette(black, 0, 1); } -#endif g_system->fillScreen(0); g_sci->_gfxCursor32->unhide(); - _decoder->close(); _status = kAVINotOpen; return kIOSuccess; @@ -438,59 +464,12 @@ uint16 AVIPlayer::getDuration() const { return _decoder->getFrameCount(); } -void AVIPlayer::renderFrame() const { - directWriteToSystem(_decoder, _drawRect, true); -} - -AVIPlayer::EventFlags AVIPlayer::playUntilEvent(EventFlags flags) { - _decoder->start(); - - EventFlags stopFlag = kEventFlagNone; - while (!g_engine->shouldQuit()) { - if (_decoder->endOfVideo()) { - stopFlag = kEventFlagEnd; - break; - } - - g_sci->sleep(_decoder->getTimeToNextFrame()); - while (_decoder->needsUpdate()) { - renderFrame(); - } - - SciEvent event = _eventMan->getSciEvent(SCI_EVENT_MOUSE_PRESS | SCI_EVENT_PEEK); - if ((flags & kEventFlagMouseDown) && event.type == SCI_EVENT_MOUSE_PRESS) { - stopFlag = kEventFlagMouseDown; - break; - } - - event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK); - if ((flags & kEventFlagEscapeKey) && event.type == SCI_EVENT_KEYBOARD) { - bool stop = false; - while ((event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD)), - event.type != SCI_EVENT_NONE) { - if (event.character == SCI_KEY_ESC) { - stop = true; - break; - } - } - - if (stop) { - stopFlag = kEventFlagEscapeKey; - break; - } - } - } - - return stopFlag; -} - #pragma mark - #pragma mark VMDPlayer -VMDPlayer::VMDPlayer(SegManager *segMan, EventManager *eventMan) : +VMDPlayer::VMDPlayer(EventManager *eventMan, SegManager *segMan) : + VideoPlayer(eventMan, new Video::AdvancedVMDDecoder(Audio::Mixer::kSFXSoundType)), _segMan(segMan), - _eventMan(eventMan), - _decoder(new Video::AdvancedVMDDecoder(Audio::Mixer::kSFXSoundType)), _isOpen(false), _isInitialized(false), @@ -525,7 +504,6 @@ VMDPlayer::VMDPlayer(SegManager *segMan, EventManager *eventMan) : VMDPlayer::~VMDPlayer() { close(); - delete _decoder; } #pragma mark - @@ -673,14 +651,11 @@ VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, co return playUntilEvent(flags); } -VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) { - if (flushEvents(_eventMan)) { - return kEventFlagEnd; - } - +VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags, const uint32) { 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. + warning("VMD reverse playback flag was set. Please report this event to the bug tracker"); const bool success = _decoder->setReverse(true); assert(success); _decoder->setVolume(0); @@ -700,86 +675,53 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) { if (shouldUseCompositing()) { _isComposited = true; - _usingHighColor = false; initComposited(); } else { _isComposited = false; - _usingHighColor = shouldUseHighColor(); initOverlay(); } - - _decoder->start(); } - EventFlags stopFlag = kEventFlagNone; - while (!g_engine->shouldQuit()) { - if (_decoder->endOfVideo()) { - stopFlag = kEventFlagEnd; - break; - } - - // Sleeping any more than 1/60th of a second will make the mouse feel - // very sluggish during VMD action sequences because the frame rate of - // VMDs is usually only 15fps - g_sci->sleep(MIN(10, _decoder->getTimeToNextFrame())); - while (_decoder->needsUpdate()) { - renderFrame(); - } + // Sleeping any more than 1/60th of a second will make the mouse feel + // very sluggish during VMD action sequences because the frame rate of + // VMDs is usually only 15fps + return VideoPlayer::playUntilEvent(flags, 10); +} - const int currentFrameNo = _decoder->getCurFrame(); +VMDPlayer::EventFlags VMDPlayer::checkForEvent(const EventFlags flags) { + const int currentFrameNo = _decoder->getCurFrame(); - if (currentFrameNo == _yieldFrame) { - stopFlag = kEventFlagEnd; - break; - } + if (currentFrameNo == _yieldFrame) { + return kEventFlagEnd; + } - if (_yieldInterval > 0 && - currentFrameNo != _lastYieldedFrameNo && - (currentFrameNo % _yieldInterval) == 0 - ) { - _lastYieldedFrameNo = currentFrameNo; - stopFlag = kEventFlagYieldToVM; - break; - } + if (_yieldInterval > 0 && + currentFrameNo != _lastYieldedFrameNo && + (currentFrameNo % _yieldInterval) == 0) { - SciEvent event = _eventMan->getSciEvent(SCI_EVENT_MOUSE_PRESS | SCI_EVENT_PEEK); - if ((flags & kEventFlagMouseDown) && event.type == SCI_EVENT_MOUSE_PRESS) { - stopFlag = kEventFlagMouseDown; - break; - } - - event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK); - if ((flags & kEventFlagEscapeKey) && event.type == SCI_EVENT_KEYBOARD) { - bool stop = false; - if (getSciVersion() < SCI_VERSION_3) { - while ((event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD)), - event.type != SCI_EVENT_NONE) { - if (event.character == SCI_KEY_ESC) { - stop = true; - break; - } - } - } else { - stop = (event.character == SCI_KEY_ESC); - } + _lastYieldedFrameNo = currentFrameNo; + return kEventFlagYieldToVM; + } - if (stop) { - stopFlag = kEventFlagEscapeKey; - break; - } - } + EventFlags stopFlag = VideoPlayer::checkForEvent(flags); + if (stopFlag) { + return stopFlag; + } - event = _eventMan->getSciEvent(SCI_EVENT_HOT_RECTANGLE | SCI_EVENT_PEEK); - if ((flags & kEventFlagHotRectangle) && event.type == SCI_EVENT_HOT_RECTANGLE) { - stopFlag = kEventFlagHotRectangle; - break; - } + const SciEvent event = _eventMan->getSciEvent(SCI_EVENT_HOT_RECTANGLE | SCI_EVENT_PEEK); + if ((flags & kEventFlagHotRectangle) && event.type == SCI_EVENT_HOT_RECTANGLE) { + return kEventFlagHotRectangle; } - return stopFlag; + return kEventFlagNone; } void VMDPlayer::initOverlay() { + // Make sure that any pending graphics changes from the game are submitted + // before starting playback, since if they aren't, and the video player + // yields back to the VM in the middle of playback, there may be a flash of + // content that draws over the video. (This happens when subtitles are + // enabled in Shivers.) g_sci->_gfxFrameout->frameOut(true); #ifdef USE_RGB_COLOR @@ -787,11 +729,7 @@ void VMDPlayer::initOverlay() { // writing to an intermediate 4bpp surface and using that surface during // cursor drawing, or by promoting the cursor code to use CursorMan, if // possible - if (_usingHighColor) { - // TODO: 8888 is used here because 4bpp is the only format currently - // supported by the common scaling code - const Graphics::PixelFormat format = Graphics::createPixelFormat<8888>(); - g_sci->_gfxFrameout->setPixelFormat(format); + if (startHQVideo()) { redrawGameScreen(); } #endif @@ -799,7 +737,12 @@ void VMDPlayer::initOverlay() { #ifdef USE_RGB_COLOR void VMDPlayer::redrawGameScreen() const { + if (!_hqVideoMode) { + return; + } + Graphics::Surface *game = g_sci->_gfxFrameout->getCurrentBuffer().convertTo(g_system->getScreenFormat(), g_sci->_gfxPalette32->getHardwarePalette()); + assert(game); Common::Rect rects[4]; int splitCount = splitRects(Common::Rect(game->w, game->h), _drawRect, rects); @@ -815,51 +758,22 @@ void VMDPlayer::redrawGameScreen() const { } #endif -void VMDPlayer::renderOverlay() const { - const Graphics::Surface *nextFrame = _decoder->decodeNextFrame(); - +void VMDPlayer::renderOverlay(const Graphics::Surface &nextFrame) const { #ifdef USE_RGB_COLOR - if (_usingHighColor) { - if (updatePalette()) { - redrawGameScreen(); - } - - directWriteToSystem(_decoder, _drawRect, false, nextFrame); - } else { -#else - { + if (_hqVideoMode) { + VideoPlayer::renderFrame(nextFrame); + return; + } #endif - updatePalette(); - - Graphics::Surface out = g_sci->_gfxFrameout->getCurrentBuffer().getSubArea(_drawRect); - - const int lineCount = _blackLines ? 2 : 1; - if (_doublePixels) { - for (int16 y = 0; y < _drawRect.height(); y += lineCount) { - const uint8 *source = (uint8 *)nextFrame->getBasePtr(0, y >> 1); - uint8 *target = (uint8 *)out.getBasePtr(0, y); - for (int16 x = 0; x < _decoder->getWidth(); ++x) { - *target++ = *source; - *target++ = *source++; - } - } - } else if (_blackLines) { - for (int16 y = 0; y < _drawRect.height(); y += lineCount) { - const uint8 *source = (uint8 *)nextFrame->getBasePtr(0, y); - uint8 *target = (uint8 *)out.getBasePtr(0, y); - memcpy(target, source, _drawRect.width()); - } - } else { - out.copyRectToSurface(nextFrame->getPixels(), nextFrame->pitch, 0, 0, nextFrame->w, nextFrame->h); - } - g_sci->_gfxFrameout->directFrameOut(_drawRect); - } + Graphics::Surface out = g_sci->_gfxFrameout->getCurrentBuffer().getSubArea(_drawRect); + renderLQToSurface(out, nextFrame, _doublePixels, _blackLines); + g_sci->_gfxFrameout->directFrameOut(_drawRect); } -bool VMDPlayer::updatePalette() const { - if (_ignorePalettes || !_decoder->hasDirtyPalette()) { - return false; +void VMDPlayer::submitPalette(const uint8 rawPalette[256 * 3]) const { + if (_ignorePalettes) { + return; } Palette palette; @@ -877,13 +791,15 @@ bool VMDPlayer::updatePalette() const { } } else #endif - fillPalette(palette); + fillPalette(rawPalette, palette); if (_isComposited) { SciBitmap *bitmap = _segMan->lookupBitmap(_bitmapId); bitmap->setPalette(palette); - g_sci->_gfxFrameout->updateScreenItem(*_screenItem); - g_sci->_gfxFrameout->frameOut(true); + // NOTE: SSCI calls updateScreenItem and frameOut here, but this should + // not be necessary in ScummVM since the new palette gets submitted + // before the next frame is rendered, and the frame rendering call will + // perform the same operations. } else { g_sci->_gfxPalette32->submit(palette); g_sci->_gfxPalette32->updateForFrame(); @@ -892,7 +808,7 @@ bool VMDPlayer::updatePalette() const { #if SCI_VMD_BLACK_PALETTE if (_blackPalette) { - fillPalette(palette); + fillPalette(rawPalette, palette); if (_isComposited) { SciBitmap *bitmap = _segMan->lookupBitmap(_bitmapId); bitmap->setPalette(palette); @@ -903,20 +819,25 @@ bool VMDPlayer::updatePalette() const { } #endif - return true; +#ifdef USE_RGB_COLOR + // Changes to the palette may affect areas outside of the video; when the + // engine is rendering video in high color, palette changes will only take + // effect once the entire screen is redrawn to the high color surface + redrawGameScreen(); +#endif } void VMDPlayer::closeOverlay() { #ifdef USE_RGB_COLOR - if (_usingHighColor) { - g_sci->_gfxFrameout->setPixelFormat(Graphics::PixelFormat::createFormatCLUT8()); - g_sci->_gfxFrameout->resetHardware(); - } else { -#else - { -#endif - g_sci->_gfxFrameout->frameOut(true, _drawRect); + if (_hqVideoMode) { + if (endHQVideo()) { + g_sci->_gfxFrameout->resetHardware(); + } + return; } +#endif + + g_sci->_gfxFrameout->frameOut(true, _drawRect); } void VMDPlayer::initComposited() { @@ -940,7 +861,10 @@ void VMDPlayer::initComposited() { CelInfo32 vmdCelInfo; vmdCelInfo.bitmap = _bitmapId; - _decoder->setSurfaceMemory(vmdBitmap.getPixels(), vmdBitmap.getWidth(), vmdBitmap.getHeight(), 1); + + Video::AdvancedVMDDecoder *decoder = dynamic_cast(_decoder.get()); + assert(decoder); + decoder->setSurfaceMemory(vmdBitmap.getPixels(), vmdBitmap.getWidth(), vmdBitmap.getHeight(), 1); if (_planeIsOwned) { _plane = new Plane(_drawRect, kPlanePicColored); @@ -967,13 +891,8 @@ void VMDPlayer::initComposited() { } void VMDPlayer::renderComposited() const { - // This writes directly to the CelObjMem we already created, - // so no need to take its return value - _decoder->decodeNextFrame(); - if (!updatePalette()) { - g_sci->_gfxFrameout->updateScreenItem(*_screenItem); - g_sci->_gfxFrameout->frameOut(true); - } + g_sci->_gfxFrameout->updateScreenItem(*_screenItem); + g_sci->_gfxFrameout->frameOut(true); } void VMDPlayer::closeComposited() { @@ -999,16 +918,16 @@ void VMDPlayer::closeComposited() { #pragma mark - #pragma mark VMDPlayer - Rendering -void VMDPlayer::renderFrame() const { +void VMDPlayer::renderFrame(const Graphics::Surface &nextFrame) const { if (_isComposited) { renderComposited(); } else { - renderOverlay(); + renderOverlay(nextFrame); } } -void VMDPlayer::fillPalette(Palette &palette) const { - const byte *vmdPalette = _decoder->getPalette() + _startColor * 3; +void VMDPlayer::fillPalette(const uint8 rawPalette[256 * 3], Palette &outPalette) const { + const byte *vmdPalette = rawPalette + _startColor * 3; for (uint16 i = _startColor; i <= _endColor; ++i) { uint8 r = *vmdPalette++; uint8 g = *vmdPalette++; @@ -1020,10 +939,10 @@ void VMDPlayer::fillPalette(Palette &palette) const { b = CLIP(b * _boostPercent / 100, 0, 255); } - palette.colors[i].r = r; - palette.colors[i].g = g; - palette.colors[i].b = b; - palette.colors[i].used = true; + outPalette.colors[i].r = r; + outPalette.colors[i].g = g; + outPalette.colors[i].b = b; + outPalette.colors[i].used = true; } } @@ -1051,7 +970,7 @@ void VMDPlayer::restrictPalette(const uint8 startColor, const int16 endColor) { #pragma mark DuckPlayer DuckPlayer::DuckPlayer(SegManager *segMan, EventManager *eventMan) : - _eventMan(eventMan), + VideoPlayer(eventMan), _decoder(new Video::AVIDecoder(Audio::Mixer::kSFXSoundType)), _plane(nullptr), _status(kDuckClosed), diff --git a/engines/sci/graphics/video32.h b/engines/sci/graphics/video32.h index 474851cdf5..bf3cc84123 100644 --- a/engines/sci/graphics/video32.h +++ b/engines/sci/graphics/video32.h @@ -26,6 +26,7 @@ #ifdef USE_RGB_COLOR #include "common/config-manager.h" // for ConfMan #endif +#include "common/ptr.h" #include "common/rect.h" // for Rect #include "common/scummsys.h" // for int16, uint8, uint16, int32 #include "common/str.h" // for String @@ -45,53 +46,160 @@ class SegManager; class SEQDecoder; struct Palette; -#pragma mark SEQPlayer - /** - * SEQPlayer is used to play SEQ animations. - * Used by DOS versions of GK1 and QFG4CD. + * An abstract class implementing common video playback functionality for SCI + * engine. */ -class SEQPlayer { +class VideoPlayer { public: - SEQPlayer(SegManager *segMan, EventManager *eventMan); + enum EventFlags { + kEventFlagNone = 0, + kEventFlagEnd = 1, + kEventFlagEscapeKey = 2, + kEventFlagMouseDown = 4, + kEventFlagHotRectangle = 8, + kEventFlagToFrame = 0x10, + kEventFlagYieldToVM = 0x20, + kEventFlagReverse = 0x80 + }; + + friend EventFlags operator|(const EventFlags a, const EventFlags b) { + return static_cast((int)a | (int)b); + } + + VideoPlayer(EventManager *eventMan, Video::VideoDecoder *decoder = nullptr) : + _eventMan(eventMan), + _decoder(decoder) +#ifdef USE_RGB_MODE + , + _hqVideoMode(false) +#endif + {} + + virtual ~VideoPlayer() {} + +protected: + EventManager *_eventMan; /** - * Plays a SEQ animation with the given - * file name, with each frame being displayed - * for `numTicks` ticks. + * The video decoder to use for video playback by this player. */ - void play(const Common::String &fileName, const int16 numTicks, const int16 x, const int16 y); + Common::ScopedPtr _decoder; -private: - SegManager *_segMan; - EventManager *_eventMan; - SEQDecoder *_decoder; + /** + * Attempts to open a video by filename and performs basic validation to + * ensure that the current system is actually capable of playing back the + * video. + */ + bool open(const Common::String &fileName); /** - * Renders a single frame of video. + * Reinitializes the system hardware surface for playback of high-quality + * scaled video if the current video meets the necessary criteria for this + * playback mode. + * + * @returns whether or not the system surface was reinitialized for + * high-quality scaled video. */ - void renderFrame() const; + bool startHQVideo(); /** - * Stops playback and closes the currently open SEQ stream. + * Determines whether or not the currently loaded video meets the criteria + * for high-quality scaled output. */ - void close(); + virtual bool shouldStartHQVideo() const { +#ifdef USE_RGB_COLOR + if (!ConfMan.getBool("enable_hq_video")) { + return false; + } + + if (_decoder->getWidth() == _drawRect.width() && + _decoder->getHeight() == _drawRect.height()) { + return false; + } + + return true; +#else + return false; +#endif + } /** - * The rectangle where the video will be drawn, - * in screen coordinates. + * Restores the hardware surface back to CLUT8 after video playback. + */ + bool endHQVideo(); + + /** + * Plays a video until an event in the given `flags` is encountered, or + * until the end of the video is reached. + * + * @param maxSleepMs An optional parameter defining the maximum number of + * milliseconds that the video player should sleep between video frames. + */ + virtual EventFlags playUntilEvent(const EventFlags flags, const uint32 maxSleepMs = 0xFFFFFFFF); + + /** + * Checks to see if an event has occurred that should cause the video player + * to yield back to the VM. + */ + virtual EventFlags checkForEvent(const EventFlags flags); + + /** + * Submits a palette from the video to the system. + */ + virtual void submitPalette(const uint8 palette[256 * 3]) const; + + /** + * Renders a video frame to the system. + */ + virtual void renderFrame(const Graphics::Surface &nextFrame) const; + + /** + * Renders a video frame to an intermediate surface using low-quality + * scaling, black-lining, or direct copy, depending upon the passed flags. + */ + template + void renderLQToSurface(Graphics::Surface &out, const Graphics::Surface &nextFrame, const bool doublePixels, const bool blackLines) const; + + /** + * The rectangle where the video will be drawn, in screen coordinates. */ Common::Rect _drawRect; + +#ifdef USE_RGB_COLOR + /** + * Whether or not the player is currently in high-quality video rendering + * mode. + */ + bool _hqVideoMode; +#endif +}; + +#pragma mark SEQPlayer + +/** + * SEQPlayer is used to play SEQ animations. + * Used by DOS versions of GK1 and QFG4CD. + */ +class SEQPlayer : public VideoPlayer { +public: + SEQPlayer(EventManager *eventMan); + + /** + * Plays a SEQ animation with the given file name, with each frame being + * displayed for `numTicks` ticks. + */ + void play(const Common::String &fileName, const int16 numTicks, const int16 x, const int16 y); }; #pragma mark - #pragma mark AVIPlayer /** - * AVIPlayer is used to play AVI videos. Used by - * Windows versions of GK1CD, KQ7, and QFG4CD. + * AVIPlayer is used to play AVI videos. + * Used by Windows versions of GK1CD, KQ7, and QFG4CD. */ -class AVIPlayer { +class AVIPlayer : public VideoPlayer { public: enum IOStatus { kIOSuccess = 0, @@ -106,16 +214,7 @@ public: kAVIPaused = 3 }; - enum EventFlags { - kEventFlagNone = 0, - kEventFlagEnd = 1, - kEventFlagEscapeKey = 2, - kEventFlagMouseDown = 4, - kEventFlagHotRectangle = 8 - }; - AVIPlayer(EventManager *eventMan); - ~AVIPlayer(); /** * Opens a stream to an AVI resource. @@ -123,16 +222,18 @@ public: IOStatus open(const Common::String &fileName); /** - * Initializes the AVI rendering parameters for the - * current AVI. This must be called after `open`. + * Initializes the AVI rendering parameters for the current AVI. This must + * be called after `open`. */ - IOStatus init(const bool pixelDouble); + IOStatus init(const bool doublePixels); /** * Begins playback of the current AVI. */ IOStatus play(const int16 from, const int16 to, const int16 showStyle, const bool cue); + virtual EventFlags playUntilEvent(const EventFlags flags, const uint32 maxSleepMs = 0xFFFFFFFF) override; + /** * Stops playback and closes the currently open AVI stream. */ @@ -148,39 +249,11 @@ public: */ uint16 getDuration() const; - /** - * Plays the AVI until an event occurs (e.g. user - * presses escape, clicks, etc.). - */ - EventFlags playUntilEvent(const EventFlags flags); - private: - typedef Common::HashMap StatusMap; - - EventManager *_eventMan; - Video::AVIDecoder *_decoder; - /** * Playback status of the player. */ AVIStatus _status; - - /** - * The rectangle where the video will be drawn, - * in screen coordinates. - */ - Common::Rect _drawRect; - - /** - * Renders video without event input until the - * video is complete. - */ - void renderVideo() const; - - /** - * Renders a single frame of video. - */ - void renderFrame() const; }; #pragma mark - @@ -188,10 +261,10 @@ private: /** * VMDPlayer is used to play VMD videos. - * Used by Phant1, GK2, PQ:SWAT, Shivers, SQ6, - * Torin, and Lighthouse. + * Used by LSL7, Phant1, GK2, PQ:SWAT, Shivers, SQ6, Rama, Torin, and + * Lighthouse. */ -class VMDPlayer { +class VMDPlayer : public VideoPlayer { public: enum OpenFlags { kOpenFlagNone = 0, @@ -214,17 +287,6 @@ public: kPlayFlagStretchVertical = 0x100 }; - enum EventFlags { - kEventFlagNone = 0, - kEventFlagEnd = 1, - kEventFlagEscapeKey = 2, - kEventFlagMouseDown = 4, - kEventFlagHotRectangle = 8, - kEventFlagToFrame = 0x10, - kEventFlagYieldToVM = 0x20, - kEventFlagReverse = 0x80 - }; - enum VMDStatus { kVMDNotOpen = 0, kVMDOpen = 1, @@ -234,13 +296,11 @@ public: kVMDFinished = 5 }; - VMDPlayer(SegManager *segMan, EventManager *eventMan); - ~VMDPlayer(); + VMDPlayer(EventManager *eventMan, SegManager *segMan); + virtual ~VMDPlayer(); private: SegManager *_segMan; - EventManager *_eventMan; - Video::AdvancedVMDDecoder *_decoder; #pragma mark - #pragma mark VMDPlayer - Playback @@ -251,8 +311,8 @@ public: IOStatus open(const Common::String &fileName, const OpenFlags flags); /** - * Initializes the VMD rendering parameters for the - * current VMD. This must be called after `open`. + * 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); @@ -271,64 +331,48 @@ public: private: /** - * Whether or not a VMD stream has been opened with - * `open`. + * Whether or not a VMD stream has been opened with `open`. */ bool _isOpen; /** - * Whether or not a VMD player has been initialised - * with `init`. + * Whether or not a VMD player has been initialized with `init`. */ bool _isInitialized; /** - * The Resource object for VMDs that are read out - * of a resource bundle instead of being streamed - * from the filesystem. + * The Resource object for VMDs that are read out of a resource bundle + * instead of being streamed from the filesystem. */ Resource *_bundledVmd; /** - * For VMDs played with the `kEventFlagToFrame` flag, - * the target frame for yielding back to the SCI VM. + * For VMDs played with the `kEventFlagToFrame` flag, the target frame for + * yielding back to the SCI VM. */ int32 _yieldFrame; /** - * For VMDs played with the `kEventFlagYieldToVM` flag, - * the number of frames that should be rendered until - * yielding back to the SCI VM. + * For VMDs played with the `kEventFlagYieldToVM` flag, the number of frames + * that should be rendered 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. + * 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; - void initOverlay(); - void renderOverlay() const; - void closeOverlay(); - - void initComposited(); - void renderComposited() const; - void closeComposited(); - - /** - * Plays the VMD until an event occurs (e.g. user - * presses escape, clicks, etc.). - */ - EventFlags playUntilEvent(const EventFlags flags); + virtual EventFlags playUntilEvent(const EventFlags flags, const uint32 = 0xFFFFFFFF) override; + virtual EventFlags checkForEvent(const EventFlags flags) override; #pragma mark - #pragma mark VMDPlayer - Rendering public: /** - * Causes the VMD player to ignore all palettes in - * the currently playing video. + * Causes the VMD player to ignore all palettes in the currently playing + * video. */ void ignorePalettes() { _ignorePalettes = true; } @@ -337,12 +381,18 @@ public: */ void setPlane(const int16 priority, const reg_t planeId); -private: +protected: /** - * The rectangle where the video will be drawn, in screen coordinates. + * Renders a frame of video to the output bitmap. */ - Common::Rect _drawRect; + virtual void renderFrame(const Graphics::Surface &nextFrame) const override; + + /** + * Updates the system with palette data from the video. + */ + virtual void submitPalette(const uint8 palette[256 * 3]) const override; +private: /** * The plane where the VMD will be drawn. */ @@ -358,11 +408,9 @@ private: */ reg_t _bitmapId; - // TODO: planeIsOwned and priority are used in SCI3+ only - /** - * If true, the plane for this VMD was set - * externally and is not owned by this VMDPlayer. + * If true, the plane for this VMD was set externally and is not owned by + * this VMDPlayer. */ bool _planeIsOwned; @@ -378,26 +426,24 @@ private: bool _doublePixels; /** - * Whether or not the video should be pixel doubled - * vertically only. + * Whether or not the video should be pixel doubled vertically only. */ bool _stretchVertical; /** - * Whether or not black lines should be rendered - * across the video. + * Whether or not black lines should be rendered across the video. */ bool _blackLines; /** - * Whether or not the playback area of the VMD - * should be left black at the end of playback. + * 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. + * Whether or not the area of the VMD should be left displaying the final + * frame of the video. */ bool _leaveLastFrame; @@ -412,35 +458,18 @@ private: bool _isComposited; /** - * Whether or not rendering of the video is being performed in high color or - * better. - */ - bool _usingHighColor; - - /** - * Renders a frame of video to the output bitmap. - */ - void renderFrame() const; - - /** - * Updates the system with palette data from the video. + * Fills the given palette with RGB values from the VMD palette, applying + * brightness boost if it is enabled. */ - bool updatePalette() const; - - /** - * Fills the given palette with RGB values from - * the VMD palette, applying brightness boost if - * it is enabled. - */ - void fillPalette(Palette &palette) const; + void fillPalette(const uint8 rawPalette[256 * 3], Palette &outPalette) const; #ifdef USE_RGB_COLOR /** * Redraws areas of the screen outside of the video to the system buffer. - * This is used when + * This is used whenever palette changes occur and the video is rendering in + * high color mode. */ void redrawGameScreen() const; -#endif /** * Determines whether or not the VMD player should upgrade the renderer to @@ -450,18 +479,18 @@ private: * video, but this will require additional work in GfxFrameout and * GfxCursor32 since the internal buffer and cursor code are 8bpp only. */ - bool shouldUseHighColor() const { -#ifdef USE_RGB_COLOR - return ConfMan.getBool("enable_hq_video") && - _priority == 0 && - (_doublePixels || _stretchVertical) && - !_leaveLastFrame && - !_showCursor && - !_blackLines; -#else - return false; -#endif + virtual bool shouldStartHQVideo() const override { + if (!VideoPlayer::shouldStartHQVideo()) { + return false; + } + + if (_priority != 0 || _leaveLastFrame || _showCursor || _blackLines) { + return false; + } + + return true; } +#endif /** * Determines whether or not the video should use the compositing renderer @@ -469,18 +498,26 @@ private: */ bool shouldUseCompositing() const { #ifdef USE_RGB_COLOR - return getSciVersion() == SCI_VERSION_3 && !shouldUseHighColor(); + return getSciVersion() == SCI_VERSION_3 && !shouldStartHQVideo(); #else return getSciVersion() == SCI_VERSION_3; #endif } + void initOverlay(); + void renderOverlay(const Graphics::Surface &nextFrame) const; + void closeOverlay(); + + void initComposited(); + void renderComposited() const; + void closeComposited(); + #pragma mark - #pragma mark VMDPlayer - Blackout public: /** - * Sets the area of the screen that should be blacked out - * during VMD playback. + * Sets the area of the screen that should be blacked out during VMD + * playback. */ void setBlackoutArea(const Common::Rect &rect) { _blackoutRect = rect; } @@ -491,8 +528,8 @@ private: Common::Rect _blackoutRect; /** - * An optional plane that will be used to black out - * areas of the screen outside of the VMD surface. + * An optional plane that will be used to black out areas of the screen + * outside of the VMD surface. */ Plane *_blackoutPlane; @@ -500,37 +537,33 @@ private: #pragma mark VMDPlayer - Palette public: /** - * Restricts use of the system palette by VMD playback to - * the given range of palette indexes. + * Restricts use of the system palette by VMD playback to the given range of + * palette indexes. */ void restrictPalette(const uint8 startColor, const int16 endColor); private: /** - * The first color in the system palette that the VMD - * can write to. + * 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. + * 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. + * 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. * - * This functionality is currently disabled because it seems like - * it was designed for a different graphics architecture where - * pixel data could be rendered before the video card's palette - * had been updated. This is not possible in ScummVM because the - * palette & pixel data are rendered simultaneously when - * OSystem::updateScreen is called, rather than immediately - * after they are sent to the backend. + * This functionality is currently disabled because it seems like it was + * designed for a different graphics architecture where pixel data could be + * rendered before the video card's palette had been updated. This is not + * possible in ScummVM because the palette & pixel data are rendered + * simultaneously when OSystem::updateScreen is called, rather than + * immediately after they are sent to the backend. */ #ifdef SCI_VMD_BLACK_PALETTE bool _blackPalette; @@ -540,21 +573,18 @@ private: #pragma mark VMDPlayer - Brightness boost private: /** - * The amount of brightness boost for the video. - * Values above 100 increase brightness; values below - * 100 reduce it. + * 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. + * The first color in the palette that should be brightness boosted. */ uint8 _boostStartColor; /** - * The last color in the palette that should be - * brightness boosted. + * The last color in the palette that should be brightness boosted. */ uint8 _boostEndColor; @@ -562,17 +592,15 @@ private: #pragma mark VMDPlayer - Mouse cursor public: /** - * 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 - * again after the video has finished. + * 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 again after the video has finished. */ void setShowCursor(const bool shouldShow) { _showCursor = shouldShow; } private: /** - * Whether or not the mouse cursor should be shown - * during playback. + * Whether or not the mouse cursor should be shown during playback. */ bool _showCursor; }; @@ -580,7 +608,7 @@ private: #pragma mark - #pragma mark DuckPlayer -class DuckPlayer { +class DuckPlayer : public VideoPlayer { public: enum DuckStatus { kDuckClosed = 0, @@ -674,15 +702,14 @@ private: #pragma mark Video32 /** - * Video32 provides facilities for playing back - * video in SCI engine. + * Video32 provides facilities for playing back video in SCI engine. */ class Video32 : public Common::Serializable { public: Video32(SegManager *segMan, EventManager *eventMan) : - _SEQPlayer(segMan, eventMan), + _SEQPlayer(eventMan), _AVIPlayer(eventMan), - _VMDPlayer(segMan, eventMan), + _VMDPlayer(eventMan, segMan), _robotPlayer(segMan), _duckPlayer(segMan, eventMan) {} diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp index 634652cd3f..f6bc48f1fa 100644 --- a/engines/sci/sci.cpp +++ b/engines/sci/sci.cpp @@ -838,6 +838,10 @@ int SciEngine::inQfGImportRoom() const { } void SciEngine::sleep(uint32 msecs) { + if (!msecs) { + return; + } + uint32 time; const uint32 wakeUpTime = g_system->getMillis() + msecs; @@ -860,7 +864,6 @@ void SciEngine::sleep(uint32 msecs) { g_system->delayMillis(wakeUpTime - time); break; } - } } -- cgit v1.2.3