From 13297c19298c5ad73c9e996c5c31ca91de124911 Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Wed, 27 Jul 2016 21:00:37 -0500 Subject: SCI32: Implement kShowMovie --- engines/sci/engine/kernel.h | 11 ++ engines/sci/engine/kernel_tables.h | 29 ++- engines/sci/engine/kmisc.cpp | 16 +- engines/sci/engine/kvideo.cpp | 166 +++++++++------- engines/sci/engine/vm.cpp | 15 ++ engines/sci/graphics/video32.cpp | 390 ++++++++++++++++++++++++++++++++++++- engines/sci/graphics/video32.h | 204 ++++++++++++++++++- 7 files changed, 744 insertions(+), 87 deletions(-) (limited to 'engines/sci') diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h index 5ff4f932be..b02a7e545a 100644 --- a/engines/sci/engine/kernel.h +++ b/engines/sci/engine/kernel.h @@ -450,6 +450,17 @@ reg_t kPlayVMDShowCursor(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDSetBlackoutArea(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDRestrictPalette(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovie32(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWin(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinOpen(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinInit(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinPlay(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinClose(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinCue(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinGetDuration(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv); +reg_t kShowMovieWinInitDouble(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 e0e4dcc233..76c24b09e3 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -423,6 +423,27 @@ static const SciKernelMapSubEntry kList_subops[] = { SCI_SUBOPENTRY_TERMINATOR }; +// version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kShowMovieWin_subops[] = { + { SIG_SCI2, 0, MAP_CALL(ShowMovieWinOpen), "r", NULL }, + { SIG_SCI2, 1, MAP_CALL(ShowMovieWinInit), "ii(ii)", NULL }, + { SIG_SCI2, 2, MAP_CALL(ShowMovieWinPlay), "i", NULL }, + { SIG_SCI2, 6, MAP_CALL(ShowMovieWinClose), "", NULL }, + { SIG_SINCE_SCI21, 0, MAP_CALL(ShowMovieWinOpen), "ir", NULL }, + { SIG_SINCE_SCI21, 1, MAP_CALL(ShowMovieWinInit), "iii(ii)", NULL }, + { SIG_SINCE_SCI21, 2, MAP_CALL(ShowMovieWinPlay), "i(ii)(i)(i)", NULL }, + { SIG_SINCE_SCI21, 6, MAP_CALL(ShowMovieWinClose), "i", NULL }, + // Since movies are rendered within the graphics engine in ScummVM, + // it is not necessary to copy the palette from SCI to MCI, so this + // can be a no-op + { SIG_SINCE_SCI21, 7, MAP_EMPTY(ShowMovieWinSetPalette), "i", NULL }, + { SIG_SINCE_SCI21, 8, MAP_CALL(ShowMovieWinGetDuration), "i", NULL }, + { SIG_SINCE_SCI21, 11, MAP_CALL(ShowMovieWinCue), "ii", NULL }, + { SIG_SINCE_SCI21, 14, MAP_CALL(ShowMovieWinPlayUntilEvent), "i(i)", NULL }, + { SIG_SINCE_SCI21, 15, MAP_CALL(ShowMovieWinInitDouble), "iii", NULL }, + 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 @@ -688,7 +709,11 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(SetSynonyms), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(SetVideoMode), SIG_EVERYWHERE, "i", NULL, NULL }, { MAP_CALL(ShakeScreen), SIG_EVERYWHERE, "(i)(i)", NULL, NULL }, - { MAP_CALL(ShowMovie), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(ShowMovie), SIG_SCI16, SIGFOR_ALL, "(.*)", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "ShowMovie", kShowMovie32, SIG_SCI32, SIGFOR_DOS, "ri(i)(i)", NULL, NULL }, + { "ShowMovie", kShowMovieWin, SIG_SCI32, SIGFOR_WIN, "(.*)", kShowMovieWin_subops, NULL }, +#endif { MAP_CALL(Show), SIG_EVERYWHERE, "i", NULL, NULL }, { MAP_CALL(SinDiv), SIG_EVERYWHERE, "ii", NULL, NULL }, { MAP_CALL(Sort), SIG_EVERYWHERE, "ooo", NULL, NULL }, @@ -820,7 +845,7 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(List), SIG_SINCE_SCI21, SIGFOR_ALL, "(.*)", kList_subops, NULL }, { MAP_CALL(MulDiv), SIG_EVERYWHERE, "iii", NULL, NULL }, { MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", kPlayVMD_subops, NULL }, - { MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_EMPTY(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 }, { MAP_CALL(AddPicAt), SIG_EVERYWHERE, "oiii(i)(i)", NULL, NULL }, diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp index 1924848717..c99540967c 100644 --- a/engines/sci/engine/kmisc.cpp +++ b/engines/sci/engine/kmisc.cpp @@ -20,6 +20,7 @@ * */ +#include "common/config-manager.h" #include "common/system.h" #include "sci/sci.h" @@ -542,7 +543,7 @@ enum kSciPlatforms { enum kPlatformOps { kPlatformUnk0 = 0, kPlatformCDSpeed = 1, - kPlatformUnk2 = 2, + kPlatformColorDepth = 2, kPlatformCDCheck = 3, kPlatformGetPlatform = 4, kPlatformUnk5 = 5, @@ -563,11 +564,6 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } - if (g_sci->forceHiresGraphics()) { - // force Windows platform, so that hires-graphics are enabled - isWindows = true; - } - uint16 operation = (argc == 0) ? 0 : argv[0].toUint16(); switch (operation) { @@ -575,9 +571,9 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) { // TODO: Returns CD Speed? warning("STUB: kPlatform(CDSpeed)"); break; - case kPlatformUnk2: + case kPlatformColorDepth: // Always returns 2 - return make_reg(0, 2); + return make_reg(0, /* 256-color */ 2); case kPlatformCDCheck: // TODO: Some sort of CD check? warning("STUB: kPlatform(CDCheck)"); @@ -591,9 +587,9 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) { return make_reg(0, (isWindows) ? kSciPlatformWindows : kSciPlatformDOS); case kPlatformUnk5: // This case needs to return the opposite of case 6 to get hires graphics - return make_reg(0, !isWindows); + return make_reg(0, !ConfMan.getBool("enable_high_resolution_graphics")); case kPlatformIsHiRes: - return make_reg(0, isWindows); + return make_reg(0, ConfMan.getBool("enable_high_resolution_graphics")); case kPlatformIsItWindows: return make_reg(0, isWindows); default: diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp index de4d4a282c..86d8a4b817 100644 --- a/engines/sci/engine/kvideo.cpp +++ b/engines/sci/engine/kvideo.cpp @@ -61,33 +61,15 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) { uint16 screenWidth = g_sci->_gfxScreen->getDisplayWidth(); uint16 screenHeight = g_sci->_gfxScreen->getDisplayHeight(); - videoState.fileName.toLowercase(); - bool isVMD = videoState.fileName.hasSuffix(".vmd"); - - if (screenWidth == 640 && width <= 320 && height <= 240 && ((videoState.flags & kDoubled) || !isVMD)) { + if (screenWidth == 640 && width <= 320 && height <= 240) { width *= 2; height *= 2; pitch *= 2; scaleBuffer = new byte[width * height * bytesPerPixel]; } - uint16 x, y; - - // Sanity check... - if (videoState.x > 0 && videoState.y > 0 && isVMD) { - x = videoState.x; - y = videoState.y; - - if (x + width > screenWidth || y + height > screenHeight) { - // Happens in the Lighthouse demo - warning("VMD video won't fit on screen, centering it instead"); - x = (screenWidth - width) / 2; - y = (screenHeight - height) / 2; - } - } else { - x = (screenWidth - width) / 2; - y = (screenHeight - height) / 2; - } + uint16 x = (screenWidth - width) / 2; + uint16 y = (screenHeight - height) / 2; bool skipVideo = false; EngineState *s = g_sci->getEngineState(); @@ -181,16 +163,6 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { // TODO: This appears to be some sort of subop. case 0 contains the string // for the video, so we'll just play it from there for now. -#ifdef ENABLE_SCI32 - if (getSciVersion() >= SCI_VERSION_2_1_EARLY) { - // SCI2.1 always has argv[0] as 1, the rest of the arguments seem to - // follow SCI1.1/2. - if (argv[0].toUint16() != 1) - error("SCI2.1 kShowMovie argv[0] not 1"); - argv++; - argc--; - } -#endif switch (argv[0].toUint16()) { case 0: { Common::String filename = s->_segMan->getString(argv[1]); @@ -243,52 +215,102 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) { } #ifdef ENABLE_SCI32 +reg_t kShowMovie32(EngineState *s, int argc, reg_t *argv) { + Common::String fileName = s->_segMan->getString(argv[0]); + const int16 numTicks = argv[1].toSint16(); + const int16 x = argc > 3 ? argv[2].toSint16() : 0; + const int16 y = argc > 3 ? argv[3].toSint16() : 0; -reg_t kRobot(EngineState *s, int argc, reg_t *argv) { - int16 subop = argv[0].toUint16(); - - switch (subop) { - case 0: { // init - int id = argv[1].toUint16(); - reg_t obj = argv[2]; - int16 flag = argv[3].toSint16(); - int16 x = argv[4].toUint16(); - int16 y = argv[5].toUint16(); - warning("kRobot(init), id %d, obj %04x:%04x, flag %d, x=%d, y=%d", id, PRINT_REG(obj), flag, x, y); - g_sci->_robotDecoder->load(id); - g_sci->_robotDecoder->start(); - g_sci->_robotDecoder->setPos(x, y); - } - break; - case 1: // LSL6 hires (startup) - // TODO - return NULL_REG; // an integer is expected - case 4: { // start - we don't really have a use for this one - //int id = argv[1].toUint16(); - //warning("kRobot(start), id %d", id); - } - break; - case 7: // unknown, called e.g. by Phantasmagoria - warning("kRobot(%d)", subop); - break; - case 8: // sync - //if (true) { // debug: automatically skip all robot videos - if (g_sci->_robotDecoder->endOfVideo()) { - g_sci->_robotDecoder->close(); - // Signal the engine scripts that the video is done - writeSelector(s->_segMan, argv[1], SELECTOR(signal), SIGNAL_REG); - } else { - writeSelector(s->_segMan, argv[1], SELECTOR(signal), NULL_REG); - } - break; - default: - warning("kRobot(%d)", subop); - break; - } + g_sci->_video32->getSEQPlayer().play(fileName, numTicks, x, y); return s->r_acc; } +reg_t kShowMovieWin(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + +reg_t kShowMovieWinOpen(EngineState *s, int argc, reg_t *argv) { + // SCI2.1 adds a movie ID to the call, but the movie ID is broken, + // so just ignore it + if (getSciVersion() > SCI_VERSION_2) { + ++argv; + --argc; + } + + const Common::String fileName = s->_segMan->getString(argv[0]); + return make_reg(0, g_sci->_video32->getAVIPlayer().open(fileName)); +} + +reg_t kShowMovieWinInit(EngineState *s, int argc, reg_t *argv) { + // SCI2.1 adds a movie ID to the call, but the movie ID is broken, + // so just ignore it + if (getSciVersion() > SCI_VERSION_2) { + ++argv; + --argc; + } + + const int16 x = argv[0].toSint16(); + const int16 y = argv[1].toSint16(); + const int16 width = argc > 3 ? argv[2].toSint16() : 0; + const int16 height = argc > 3 ? argv[3].toSint16() : 0; + return make_reg(0, g_sci->_video32->getAVIPlayer().init1x(x, y, width, height)); +} + +reg_t kShowMovieWinPlay(EngineState *s, int argc, reg_t *argv) { + if (getSciVersion() == SCI_VERSION_2) { + AVIPlayer::EventFlags flags = (AVIPlayer::EventFlags)argv[0].toUint16(); + return make_reg(0, g_sci->_video32->getAVIPlayer().playUntilEvent(flags)); + } else { + // argv[0] is a broken movie ID + const int16 from = argc > 2 ? argv[1].toSint16() : 0; + const int16 to = argc > 2 ? argv[2].toSint16() : 0; + const int16 showStyle = argc > 3 ? argv[3].toSint16() : 0; + const bool cue = argc > 4 ? (bool)argv[4].toSint16() : false; + return make_reg(0, g_sci->_video32->getAVIPlayer().play(from, to, showStyle, cue)); + } +} + +reg_t kShowMovieWinClose(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getAVIPlayer().close()); +} + +reg_t kShowMovieWinGetDuration(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getAVIPlayer().getDuration()); +} + +reg_t kShowMovieWinCue(EngineState *s, int argc, reg_t *argv) { + // SCI2.1 adds a movie ID to the call, but the movie ID is broken, + // so just ignore it + if (getSciVersion() > SCI_VERSION_2) { + ++argv; + --argc; + } + + const uint16 frameNo = argv[0].toUint16(); + return make_reg(0, g_sci->_video32->getAVIPlayer().cue(frameNo)); +} + +reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv) { + const int defaultFlags = + AVIPlayer::kEventFlagEnd | + AVIPlayer::kEventFlagEscapeKey; + + // argv[0] is the movie number, which is not used by this method + const AVIPlayer::EventFlags flags = (AVIPlayer::EventFlags)(argc > 1 ? argv[1].toUint16() : defaultFlags); + + return make_reg(0, g_sci->_video32->getAVIPlayer().playUntilEvent(flags)); +} + +reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv) { + // argv[0] is a broken movie ID + const int16 x = argv[1].toSint16(); + const int16 y = argv[2].toSint16(); + return make_reg(0, g_sci->_video32->getAVIPlayer().init2x(x, y)); +} + reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv) { if (!s) return make_reg(0, getSciVersion()); diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp index 3e12084ed6..548fd477bf 100644 --- a/engines/sci/engine/vm.cpp +++ b/engines/sci/engine/vm.cpp @@ -405,6 +405,21 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) { error("[VM] k%s[%x]: no subfunction ID parameter given", kernelCall.name, kernelCallNr); if (argv[0].isPointer()) error("[VM] k%s[%x]: given subfunction ID is actually a pointer", kernelCall.name, kernelCallNr); + +#ifdef ENABLE_SCI32 + // The Windows version of kShowMovie has subops, but the subop number + // is put in the second parameter in SCI2.1+, even though every other + // kcall with subops puts the subop in the first parameter. To allow use + // of the normal subops system, we swap the arguments so the subop + // number is in the usual place. + if (getSciVersion() > SCI_VERSION_2 && + g_sci->getPlatform() == Common::kPlatformWindows && + strcmp(kernelCall.name, "ShowMovie") == 0) { + assert(argc > 1); + SWAP(argv[0], argv[1]); + } +#endif + const uint16 subId = argv[0].toUint16(); // Skip over subfunction-id argc--; diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp index dc2641c92a..ac4522c1a0 100644 --- a/engines/sci/graphics/video32.cpp +++ b/engines/sci/graphics/video32.cpp @@ -29,11 +29,397 @@ #include "sci/graphics/palette32.h" #include "sci/graphics/text32.h" #include "sci/graphics/video32.h" -#include "sci/sci.h" +#include "audio/mixer.h" // for Audio::Mixer::kSFXSoundType +#include "common/config-manager.h" // for ConfMan +#include "common/textconsole.h" // for error, warning +#include "common/system.h" +#include "engine.h" // for Engine, g_engine +#include "engines/util.h" +#include "graphics/scaler/scalebit.h" +#include "sci/console.h" // for Console +#include "sci/engine/state.h" // for EngineState +#include "sci/engine/vm_types.h" // for reg_t +#include "sci/event.h" // for SciEvent, EventManager, SCI_... +#include "sci/graphics/celobj32.h" // for CelInfo32 +#include "sci/graphics/cursor.h" // for GfxCursor +#include "sci/graphics/helpers.h" // for Color, Palette +#include "sci/graphics/palette32.h" // for GfxPalette32 +#include "sci/graphics/plane32.h" // for Plane, PlanePictureCodes::kP... +#include "sci/graphics/screen_item32.h" // for ScaleInfo, ScreenItem, Scale... +#include "sci/graphics/text32.h" // for BitmapResource +#include "sci/video/seq_decoder.h" +#include "sci/sci.h" // for SciEngine, g_sci, getSciVersion +#include "video/avi_decoder.h" #include "video/coktel_decoder.h" namespace Sci { +#pragma mark SEQPlayer + +SEQPlayer::SEQPlayer(SegManager *segMan) : + _segMan(segMan), + _decoder(nullptr), + _plane(nullptr), + _screenItem(nullptr) {} + +void SEQPlayer::play(const Common::String &fileName, const int16 numTicks, const int16 x, const int16 y) { + delete _decoder; + _decoder = new SEQDecoder(numTicks); + _decoder->loadFile(fileName); + + // NOTE: In the original engine, video was output directly to the hardware, + // bypassing the game's rendering engine. Instead of doing this, we use a + // mechanism that is very similar to that used by the VMD player, which + // allows the SEQ to be drawn into a bitmap ScreenItem and displayed using + // the normal graphics system. + _segMan->allocateBitmap(&_bitmap, _decoder->getWidth(), _decoder->getHeight(), kDefaultSkipColor, 0, 0, kLowResX, kLowResY, 0, false, false); + + CelInfo32 celInfo; + celInfo.type = kCelTypeMem; + celInfo.bitmap = _bitmap; + + _plane = new Plane(Common::Rect(kLowResX, kLowResY), kPlanePicColored); + g_sci->_gfxFrameout->addPlane(*_plane); + + // Normally we would use the x, y coordinates passed into the play function + // to position the screen item, but because the video frame bitmap is + // drawn in low-resolution coordinates, it gets automatically scaled up by + // the engine (pixel doubling with aspect ratio correction). As a result, + // the animation does not need the extra offsets from the game in order to + // be correctly positioned in the middle of the window, so we ignore them. + _screenItem = new ScreenItem(_plane->_object, celInfo, Common::Point(0, 0), ScaleInfo()); + g_sci->_gfxFrameout->addScreenItem(*_screenItem); + g_sci->_gfxFrameout->frameOut(true); + _decoder->start(); + + while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) { + renderFrame(); + g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame()); + g_sci->getEngineState()->_throttleTrigger = true; + } + + _segMan->freeBitmap(_screenItem->_celInfo.bitmap); + g_sci->_gfxFrameout->deletePlane(*_plane); + g_sci->_gfxFrameout->frameOut(true); + _screenItem = nullptr; + _plane = nullptr; +} + +void SEQPlayer::renderFrame() const { + const Graphics::Surface *surface = _decoder->decodeNextFrame(); + + SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap); + bitmap.getBuffer().copyRectToSurface(*surface, 0, 0, Common::Rect(surface->w, surface->h)); + + const bool dirtyPalette = _decoder->hasDirtyPalette(); + if (dirtyPalette) { + Palette palette; + const byte *rawPalette = _decoder->getPalette(); + for (int i = 0; i < ARRAYSIZE(palette.colors); ++i) { + palette.colors[i].r = *rawPalette++; + palette.colors[i].g = *rawPalette++; + palette.colors[i].b = *rawPalette++; + palette.colors[i].used = true; + } + + g_sci->_gfxPalette32->submit(palette); + } + + g_sci->_gfxFrameout->updateScreenItem(*_screenItem); + g_sci->getSciDebugger()->onFrame(); + g_sci->_gfxFrameout->frameOut(true); +} + +#pragma mark - +#pragma mark AVIPlayer + +AVIPlayer::AVIPlayer(SegManager *segMan, EventManager *eventMan) : + _segMan(segMan), + _eventMan(eventMan), + _decoder(new Video::AVIDecoder(Audio::Mixer::kSFXSoundType)), + _scaleBuffer(nullptr), + _plane(nullptr), + _screenItem(nullptr), + _status(kAVINotOpen) {} + +AVIPlayer::~AVIPlayer() { + close(); + delete _decoder; +} + +AVIPlayer::IOStatus AVIPlayer::open(const Common::String &fileName) { + if (_status != kAVINotOpen) { + close(); + } + + if (!_decoder->loadFile(fileName)) { + return kIOFileNotFound; + } + + _status = kAVIOpen; + return kIOSuccess; +} + +AVIPlayer::IOStatus AVIPlayer::init1x(const int16 x, const int16 y, int16 width, int16 height) { + if (!width || !height) { + width = _decoder->getWidth(); + height = _decoder->getHeight(); + } + + // QFG4CD gives non-multiple-of-2 values for width and height, + // which would normally be OK except the source video is a pixel bigger + // in each dimension + width = (width + 1) & ~1; + height = (height + 1) & ~1; + + _drawRect.left = x; + _drawRect.top = y; + _drawRect.right = x + width; + _drawRect.bottom = y + height; + + // SCI2.1 uses init2x to draw a pixel-doubled AVI, but SCI2 has only the + // one play routine which automatically pixel-doubles in hi-res mode + if (getSciVersion() == SCI_VERSION_2) { + // NOTE: This is somewhat of a hack; credits.avi from GK1 is not + // rendered correctly in SSCI because it is a 640x480 video, but the + // game script gives the wrong dimensions. Since this is the only + // high-resolution AVI ever used, just set the draw rectangle to draw + // the entire screen + if (_decoder->getWidth() > 320) { + _drawRect.left = 0; + _drawRect.top = 0; + _drawRect.right = 320; + _drawRect.bottom = 200; + } + + // In hi-res mode, video will be pixel doubled, so the origin (which + // corresponds to the correct position without pixel doubling) needs to + // be corrected + if (g_sci->_gfxFrameout->_isHiRes && _decoder->getWidth() <= 320) { + _drawRect.left /= 2; + _drawRect.top /= 2; + } + } + + _pixelDouble = false; + init(); + + return kIOSuccess; +} + +AVIPlayer::IOStatus AVIPlayer::init2x(const int16 x, const int16 y) { + _drawRect.left = x; + _drawRect.top = y; + _drawRect.right = x + _decoder->getWidth() * 2; + _drawRect.bottom = y + _decoder->getHeight() * 2; + + _pixelDouble = true; + init(); + + return kIOSuccess; +} + +void AVIPlayer::init() { + int16 xRes; + int16 yRes; + + if (g_sci->_gfxFrameout->_isHiRes && _decoder->getWidth() > 320) { + xRes = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + yRes = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + } else { + xRes = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + yRes = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + } + + _plane = new Plane(_drawRect); + g_sci->_gfxFrameout->addPlane(*_plane); + + if (_decoder->getPixelFormat().bytesPerPixel == 1) { + _segMan->allocateBitmap(&_bitmap, _decoder->getWidth(), _decoder->getHeight(), kDefaultSkipColor, 0, 0, xRes, yRes, 0, false, false); + + CelInfo32 celInfo; + celInfo.type = kCelTypeMem; + celInfo.bitmap = _bitmap; + + _screenItem = new ScreenItem(_plane->_object, celInfo, Common::Point(_drawRect.left, _drawRect.top), ScaleInfo()); + g_sci->_gfxFrameout->addScreenItem(*_screenItem); + g_sci->_gfxFrameout->frameOut(true); + } else { + const Buffer ¤tBuffer = g_sci->_gfxFrameout->getCurrentBuffer(); + const Graphics::PixelFormat format = _decoder->getPixelFormat(); + initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, g_sci->_gfxFrameout->_isHiRes, &format); + + if (_pixelDouble) { + const int16 width = _drawRect.width(); + const int16 height = _drawRect.height(); + _scaleBuffer = calloc(1, width * height * format.bytesPerPixel); + } + } +} + +AVIPlayer::IOStatus AVIPlayer::play(const int16 from, const int16 to, const int16, const bool async) { + if (from >= 0 && to > 0 && from <= to) { + _decoder->seekToFrame(from); + _decoder->setEndFrame(to); + } + + if (!async) { + renderVideo(); + } + + _status = kAVIPlaying; + return kIOSuccess; +} + +void AVIPlayer::renderVideo() const { + _decoder->start(); + while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) { + g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame()); + g_sci->getEngineState()->_throttleTrigger = true; + if (_decoder->needsUpdate()) { + renderFrame(); + } + } +} + +AVIPlayer::IOStatus AVIPlayer::close() { + if (_status == kAVINotOpen) { + return kIOSuccess; + } + + free(_scaleBuffer); + _scaleBuffer = nullptr; + + if (_decoder->getPixelFormat().bytesPerPixel != 1) { + const bool isHiRes = g_sci->_gfxFrameout->_isHiRes; + const Buffer ¤tBuffer = g_sci->_gfxFrameout->getCurrentBuffer(); + const Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8(); + initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, isHiRes, &format); + } + + _decoder->close(); + _status = kAVINotOpen; + g_sci->_gfxFrameout->deletePlane(*_plane); + _plane = nullptr; + _screenItem = nullptr; + return kIOSuccess; +} + +AVIPlayer::IOStatus AVIPlayer::cue(const uint16 frameNo) { + if (!_decoder->seekToFrame(frameNo)) { + return kIOSeekFailed; + } + + _status = kAVIPaused; + return kIOSuccess; +} + +uint16 AVIPlayer::getDuration() const { + if (_status == kAVINotOpen) { + return 0; + } + + return _decoder->getFrameCount(); +} + +void AVIPlayer::renderFrame() const { + const Graphics::Surface *surface = _decoder->decodeNextFrame(); + + if (surface->format.bytesPerPixel == 1) { + SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap); + if (surface->w > bitmap.getWidth() || surface->h > bitmap.getHeight()) { + warning("Attempted to draw a video frame larger than the destination bitmap"); + return; + } + + bitmap.getBuffer().copyRectToSurface(*surface, 0, 0, Common::Rect(surface->w, surface->h)); + + const bool dirtyPalette = _decoder->hasDirtyPalette(); + if (dirtyPalette) { + Palette palette; + const byte *rawPalette = _decoder->getPalette(); + for (int i = 0; i < ARRAYSIZE(palette.colors); ++i) { + palette.colors[i].r = *rawPalette++; + palette.colors[i].g = *rawPalette++; + palette.colors[i].b = *rawPalette++; + palette.colors[i].used = true; + } + + g_sci->_gfxPalette32->submit(palette); + } + + g_sci->_gfxFrameout->updateScreenItem(*_screenItem); + g_sci->getSciDebugger()->onFrame(); + g_sci->_gfxFrameout->frameOut(true); + } else { + Common::Rect drawRect(_drawRect); + 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; + + if (_pixelDouble) { + scale(2, _scaleBuffer, surface->pitch * 2, surface->getPixels(), surface->pitch, surface->format.bytesPerPixel, surface->w, surface->h); + g_system->copyRectToScreen(_scaleBuffer, surface->pitch * 2, _drawRect.left, _drawRect.top, _drawRect.width(), _drawRect.height()); + } else { + mulinc(drawRect, Ratio(screenWidth, scriptWidth), Ratio(screenHeight, scriptHeight)); + + g_system->copyRectToScreen(surface->getPixels(), surface->pitch, drawRect.left, drawRect.top, surface->w, surface->h); + } + } +} + +AVIPlayer::EventFlags AVIPlayer::playUntilEvent(EventFlags flags) { + _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(); + } + + 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; + } + } + + // TODO: Hot rectangles + if ((flags & kEventFlagHotRectangle) /* && event.type == SCI_EVENT_HOT_RECTANGLE */) { + warning("Hot rectangles not implemented in VMD player"); + stopFlag = kEventFlagHotRectangle; + break; + } + } + + return stopFlag; +} + +#pragma mark - #pragma mark VMDPlayer VMDPlayer::VMDPlayer(SegManager *segMan, EventManager *eventMan) : @@ -117,7 +503,7 @@ VMDPlayer::IOStatus VMDPlayer::close() { if (!_planeIsOwned && _screenItem != nullptr) { g_sci->_gfxFrameout->deleteScreenItem(*_screenItem); - g_sci->getEngineState()->_segMan->freeBitmap(_screenItem->_celInfo.bitmap); + _segMan->freeBitmap(_screenItem->_celInfo.bitmap); _screenItem = nullptr; } else if (_plane != nullptr) { g_sci->_gfxFrameout->deletePlane(*_plane); diff --git a/engines/sci/graphics/video32.h b/engines/sci/graphics/video32.h index 7033f7c647..b0deba148a 100644 --- a/engines/sci/graphics/video32.h +++ b/engines/sci/graphics/video32.h @@ -23,12 +23,208 @@ #ifndef SCI_GRAPHICS_VIDEO32_H #define SCI_GRAPHICS_VIDEO32_H -namespace Video { class AdvancedVMDDecoder; } +#include "common/rect.h" // for Rect +#include "common/scummsys.h" // for int16, uint8, int32 +#include "common/str.h" // for String +#include "sci/video/robot_decoder.h" // for RobotDecoder + +namespace Video { +class AdvancedVMDDecoder; +class AVIDecoder; +} namespace Sci { class Plane; class ScreenItem; class SegManager; +class SEQDecoder; +struct Palette; +#pragma mark SEQPlayer + +/** + * SEQPlayer is used to play SEQ animations. + * Used by DOS versions of GK1 and QFG4CD. + */ +class SEQPlayer { +public: + SEQPlayer(SegManager *segMan); + + /** + * 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); + +private: + SegManager *_segMan; + SEQDecoder *_decoder; + + /** + * The plane where the SEQ will be drawn. + */ + Plane *_plane; + + /** + * The screen item representing the SEQ surface. + */ + ScreenItem *_screenItem; + + /** + * The bitmap used to render video output. + */ + reg_t _bitmap; + + /** + * Renders a single frame of video. + */ + void renderFrame() const; +}; + +#pragma mark - +#pragma mark AVIPlayer + +/** + * AVIPlayer is used to play AVI videos. Used by + * Windows versions of GK1CD, KQ7, and QFG4CD. + */ +class AVIPlayer { +public: + enum IOStatus { + kIOSuccess = 0, + kIOFileNotFound = 2, + kIOSeekFailed = 12 + }; + + enum AVIStatus { + kAVINotOpen = 0, + kAVIOpen = 1, + kAVIPlaying = 2, + kAVIPaused = 3 + }; + + enum EventFlags { + kEventFlagNone = 0, + kEventFlagEnd = 1, + kEventFlagEscapeKey = 2, + kEventFlagMouseDown = 4, + kEventFlagHotRectangle = 8 + }; + + AVIPlayer(SegManager *segMan, EventManager *eventMan); + ~AVIPlayer(); + + /** + * Opens a stream to an AVI resource. + */ + IOStatus open(const Common::String &fileName); + + /** + * Initializes the AVI rendering parameters for the + * current AVI. This must be called after `open`. + */ + IOStatus init1x(const int16 x, const int16 y, const int16 width, const int16 height); + + /** + * Initializes the AVI rendering parameters for the + * current AVI, in pixel-doubling mode. This must + * be called after `open`. + */ + IOStatus init2x(const int16 x, const int16 y); + + /** + * Begins playback of the current AVI. + */ + IOStatus play(const int16 from, const int16 to, const int16 showStyle, const bool cue); + + /** + * Stops playback and closes the currently open AVI stream. + */ + IOStatus close(); + + /** + * Seeks the currently open AVI stream to the given frame. + */ + IOStatus cue(const uint16 frameNo); + + /** + * Returns the duration of the current video. + */ + 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; + + SegManager *_segMan; + EventManager *_eventMan; + Video::AVIDecoder *_decoder; + + /** + * Playback status of the player. + */ + AVIStatus _status; + + /** + * The plane where the AVI will be drawn. + */ + Plane *_plane; + + /** + * The screen item representing the AVI surface, + * in 8bpp mode. In 24bpp mode, video is drawn + * directly to the screen. + */ + ScreenItem *_screenItem; + + /** + * The bitmap used to render video output in + * 8bpp mode. + */ + reg_t _bitmap; + + /** + * The rectangle where the video will be drawn, + * in game script coordinates. + */ + Common::Rect _drawRect; + + /** + * The scale buffer for pixel-doubled videos + * drawn in 24bpp mode. + */ + void *_scaleBuffer; + + /** + * In SCI2.1, whether or not the video should + * be pixel doubled for playback. + */ + bool _pixelDouble; + + /** + * Performs common initialisation for both + * scaled and unscaled videos. + */ + void init(); + + /** + * Renders video without event input until the + * video is complete. + */ + void renderVideo() const; + + /** + * Renders a single frame of video. + */ + void renderFrame() const; +}; + +#pragma mark - #pragma mark VMDPlayer /** @@ -300,11 +496,17 @@ private: class Video32 { public: Video32(SegManager *segMan, EventManager *eventMan) : + _SEQPlayer(segMan), + _AVIPlayer(segMan, eventMan), _VMDPlayer(segMan, eventMan) {} + SEQPlayer &getSEQPlayer() { return _SEQPlayer; } + AVIPlayer &getAVIPlayer() { return _AVIPlayer; } VMDPlayer &getVMDPlayer() { return _VMDPlayer; } private: + SEQPlayer _SEQPlayer; + AVIPlayer _AVIPlayer; VMDPlayer _VMDPlayer; }; } // End of namespace Sci -- cgit v1.2.3