aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/graphics/video32.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci/graphics/video32.cpp')
-rw-r--r--engines/sci/graphics/video32.cpp510
1 files changed, 496 insertions, 14 deletions
diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp
index dd841f5b4c..8b1d4ef32b 100644
--- a/engines/sci/graphics/video32.cpp
+++ b/engines/sci/graphics/video32.cpp
@@ -20,20 +20,484 @@
*
*/
-#include "audio/mixer.h"
-#include "common/config-manager.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 "audio/mixer.h" // for Audio::Mixer::kSFXSoundType
+#include "common/config-manager.h" // for ConfMan
+#include "common/textconsole.h" // for warning, error
+#include "common/util.h" // for ARRAYSIZE
+#include "common/system.h" // for g_system
+#include "engine.h" // for Engine, g_engine
+#include "engines/util.h" // for initGraphics
+#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, ::kLowResX, ::kLo...
+#include "sci/graphics/cursor32.h" // for GfxCursor32
+#include "sci/graphics/frameout.h" // for GfxFrameout
+#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/sci.h" // for SciEngine, g_sci, getSciVersion
#include "sci/graphics/video32.h"
-#include "sci/sci.h"
-#include "video/coktel_decoder.h"
+#include "sci/video/seq_decoder.h" // for SEQDecoder
+#include "video/avi_decoder.h" // for AVIDecoder
+#include "video/coktel_decoder.h" // for AdvancedVMDDecoder
+namespace Graphics { struct Surface; }
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 (_status == kAVINotOpen) {
+ return kIOFileNotFound;
+ }
+
+ _pixelDouble = false;
+
+ if (!width || !height) {
+ width = _decoder->getWidth();
+ height = _decoder->getHeight();
+ } else if (getSciVersion() == SCI_VERSION_2_1_EARLY && g_sci->getGameId() == GID_KQ7) {
+ // KQ7 1.51 provides an explicit width and height when it wants scaling,
+ // though the width and height it provides are not scaled
+ _pixelDouble = true;
+ width *= 2;
+ height *= 2;
+ }
+
+ // 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.1mid 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) {
+ // 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;
+ }
+ }
+
+ init();
+
+ return kIOSuccess;
+}
+
+AVIPlayer::IOStatus AVIPlayer::init2x(const int16 x, const int16 y) {
+ if (_status == kAVINotOpen) {
+ return kIOFileNotFound;
+ }
+
+ _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;
+
+ bool useScreenDimensions = false;
+ if (g_sci->_gfxFrameout->_isHiRes && _decoder->getWidth() > 320) {
+ useScreenDimensions = true;
+ }
+
+ // KQ7 1.51 gives video position in screen coordinates, not game
+ // coordinates, because in SSCI they are passed to Video for Windows, which
+ // renders as an overlay on the game video. Because we put the video into a
+ // ScreenItem instead of rendering directly to the hardware surface, the
+ // coordinates need to be converted to game script coordinates
+ if (g_sci->getGameId() == GID_KQ7 && getSciVersion() == SCI_VERSION_2_1_EARLY) {
+ useScreenDimensions = !_pixelDouble;
+ // This y-translation is arbitrary, based on what roughly centers the
+ // videos in the game window
+ _drawRect.translate(-_drawRect.left / 2, -_drawRect.top * 2 / 3);
+ }
+
+ if (useScreenDimensions) {
+ 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 {
+ // Attempting to draw a palettized cursor into a 24bpp surface will
+ // cause memory corruption, so hide the cursor in this mode (SCI did not
+ // have a 24bpp mode but just directed VFW to display videos instead)
+ g_sci->_gfxCursor32->hide();
+
+ const Buffer &currentBuffer = 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 (_status == kAVINotOpen) {
+ return kIOFileNotFound;
+ }
+
+ if (from >= 0 && to > 0 && from <= to) {
+ _decoder->seekToFrame(from);
+ _decoder->setEndFrame(to);
+ }
+
+ if (!async) {
+ renderVideo();
+ } else if (getSciVersion() == SCI_VERSION_2_1_EARLY) {
+ playUntilEvent((EventFlags)(kEventFlagEnd | kEventFlagEscapeKey));
+ } else {
+ _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 &currentBuffer = g_sci->_gfxFrameout->getCurrentBuffer();
+ const Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8();
+ initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, isHiRes, &format);
+ g_sci->_gfxCursor32->unhide();
+ }
+
+ _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;
+ }
+
+ // KQ7 1.51 encodes videos with palette entry 0 as white, which makes
+ // the area around the video turn white too, since it is coded to use
+ // palette entry 0. This happens to work in the original game because
+ // the video is rendered by VfW, not in the engine itself. To fix this,
+ // we just modify the incoming pixel data from the video so if a pixel
+ // is using entry 0, we change it to use entry 255, which is guaranteed
+ // to always be white
+ if (getSciVersion() == SCI_VERSION_2_1_EARLY && g_sci->getGameId() == GID_KQ7) {
+ uint8 *target = bitmap.getPixels();
+ const uint8 *source = (const uint8 *)surface->getPixels();
+ const uint8 *end = (const uint8 *)surface->getPixels() + surface->w * surface->h;
+
+ while (source != end) {
+ uint8 value = *source++;
+ *target++ = value == 0 ? 255 : value;
+ }
+ } else {
+ 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;
+ }
+
+ // Prevent KQ7 1.51 from setting entry 0 to white
+ palette.colors[0].used = false;
+
+ g_sci->_gfxPalette32->submit(palette);
+ }
+
+ g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
+ g_sci->getSciDebugger()->onFrame();
+ g_sci->_gfxFrameout->frameOut(true);
+ } else {
+ assert(surface->format.bytesPerPixel == 4);
+
+ Common::Rect drawRect(_drawRect);
+
+ if (_pixelDouble) {
+ const uint32 *source = (const uint32 *)surface->getPixels();
+ uint32 *target = (uint32 *)_scaleBuffer;
+ // target pitch here is in uint32s, not bytes
+ const uint16 pitch = surface->pitch / 2;
+ for (int y = 0; y < surface->h; ++y) {
+ for (int x = 0; x < surface->w; ++x) {
+ const uint32 value = *source++;
+
+ target[0] = value;
+ target[1] = value;
+ target[pitch] = value;
+ target[pitch + 1] = value;
+ target += 2;
+ }
+ target += pitch;
+ }
+
+ g_system->copyRectToScreen(_scaleBuffer, surface->pitch * 2, _drawRect.left, _drawRect.top, _drawRect.width(), _drawRect.height());
+ } else {
+ 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;
+
+ 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,6 +581,7 @@ VMDPlayer::IOStatus VMDPlayer::close() {
if (!_planeIsOwned && _screenItem != nullptr) {
g_sci->_gfxFrameout->deleteScreenItem(*_screenItem);
+ _segMan->freeBitmap(_screenItem->_celInfo.bitmap);
_screenItem = nullptr;
} else if (_plane != nullptr) {
g_sci->_gfxFrameout->deletePlane(*_plane);
@@ -139,7 +604,7 @@ VMDPlayer::IOStatus VMDPlayer::close() {
}
if (!_showCursor) {
- g_sci->_gfxCursor->kernelShow();
+ g_sci->_gfxCursor32->unhide();
}
_lastYieldedFrameNo = 0;
@@ -148,6 +613,22 @@ VMDPlayer::IOStatus VMDPlayer::close() {
return kIOSuccess;
}
+VMDPlayer::VMDStatus VMDPlayer::getStatus() const {
+ if (!_isOpen) {
+ return kVMDNotOpen;
+ }
+ if (_decoder->isPaused()) {
+ return kVMDPaused;
+ }
+ if (_decoder->isPlaying()) {
+ return kVMDPlaying;
+ }
+ if (_decoder->endOfVideo()) {
+ return kVMDFinished;
+ }
+ return kVMDOpen;
+}
+
VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, const int16 lastFrameNo, const int16 yieldInterval) {
assert(lastFrameNo >= -1);
@@ -200,7 +681,7 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) {
_isInitialized = true;
if (!_showCursor) {
- g_sci->_gfxCursor->kernelHide();
+ g_sci->_gfxCursor32->hide();
}
Common::Rect vmdRect(_x,
@@ -231,14 +712,15 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) {
const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
- BitmapResource vmdBitmap(_segMan, vmdRect.width(), vmdRect.height(), 255, 0, 0, screenWidth, screenHeight, 0, false);
+ reg_t bitmapId;
+ SciBitmap &vmdBitmap = *_segMan->allocateBitmap(&bitmapId, vmdRect.width(), vmdRect.height(), 255, 0, 0, screenWidth, screenHeight, 0, false, false);
if (screenWidth != scriptWidth || screenHeight != scriptHeight) {
mulru(vmdRect, Ratio(scriptWidth, screenWidth), Ratio(scriptHeight, screenHeight), 1);
}
CelInfo32 vmdCelInfo;
- vmdCelInfo.bitmap = vmdBitmap.getObject();
+ vmdCelInfo.bitmap = bitmapId;
_decoder->setSurfaceMemory(vmdBitmap.getPixels(), vmdBitmap.getWidth(), vmdBitmap.getHeight(), 1);
if (_planeIsOwned) {