mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-16 14:50:17 +00:00
05b21ace68
Phantasmagoria 1's censorship mode is now supported Trac #11229
1211 lines
37 KiB
C++
1211 lines
37 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*
|
|
*/
|
|
|
|
#include "audio/mixer.h" // for Audio::Mixer::kSFXSoundType
|
|
#include "common/config-manager.h" // for ConfMan
|
|
#include "common/textconsole.h" // for warning, error
|
|
#ifndef USE_RGB_COLOR
|
|
#include "common/translation.h" // for _
|
|
#endif
|
|
#include "common/util.h" // for ARRAYSIZE
|
|
#include "common/system.h" // for g_system
|
|
#include "engine.h" // for Engine, g_engine
|
|
#include "graphics/colormasks.h" // for createPixelFormat
|
|
#include "graphics/palette.h" // for PaletteManager
|
|
#include "graphics/transparent_surface.h" // for TransparentSurface
|
|
#include "sci/console.h" // for Console
|
|
#include "sci/engine/features.h" // for GameFeatures
|
|
#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/resource.h" // for ResourceManager, ResourceId,...
|
|
#include "sci/sci.h" // for SciEngine, g_sci, getSciVersion
|
|
#include "sci/sound/audio32.h" // for Audio32
|
|
#include "sci/video/seq_decoder.h" // for SEQDecoder
|
|
#include "video/avi_decoder.h" // for AVIDecoder
|
|
#include "video/coktel_decoder.h" // for AdvancedVMDDecoder
|
|
#include "sci/graphics/video32.h"
|
|
|
|
namespace Graphics { struct Surface; }
|
|
|
|
namespace Sci {
|
|
|
|
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;
|
|
}
|
|
|
|
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
|
|
|
|
return false;
|
|
}
|
|
|
|
VideoPlayer::EventFlags VideoPlayer::playUntilEvent(const EventFlags flags, const uint32 maxSleepMs) {
|
|
// 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
|
|
_eventMan->flushEvents();
|
|
|
|
_decoder->start();
|
|
|
|
EventFlags stopFlag = kEventFlagNone;
|
|
for (;;) {
|
|
if (!_needsUpdate) {
|
|
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 _needsUpdate has been set but it's not time to render the next frame
|
|
// then the current frame is rendered again. This reduces the delay between
|
|
// a script adding or removing censorship blobs and the screen reflecting
|
|
// this upon resuming playback.
|
|
if (nextFrame) {
|
|
renderFrame(*nextFrame);
|
|
_currentFrame = nextFrame;
|
|
_needsUpdate = false;
|
|
} else if (_needsUpdate) {
|
|
if (_currentFrame) {
|
|
renderFrame(*_currentFrame);
|
|
}
|
|
_needsUpdate = false;
|
|
}
|
|
|
|
// Event checks must only happen *after* the decoder is updated (1) and
|
|
// frame rendered (2), otherwise (1) interval yields will get stuck
|
|
// forever on the current frame, and (2) other events will end up
|
|
// dropping the new frame entirely
|
|
stopFlag = checkForEvent(flags);
|
|
if (stopFlag != kEventFlagNone) {
|
|
break;
|
|
}
|
|
|
|
// Only call to update the screen after the event check, otherwise
|
|
// whatever the game scripts try to change when the player yields to
|
|
// them will not make it into the hardware buffer until the next tick
|
|
g_sci->_gfxFrameout->updateScreen();
|
|
}
|
|
|
|
return stopFlag;
|
|
}
|
|
|
|
VideoPlayer::EventFlags VideoPlayer::checkForEvent(const EventFlags flags) {
|
|
if (g_engine->shouldQuit() || _decoder->endOfVideo()) {
|
|
return kEventFlagEnd;
|
|
}
|
|
|
|
SciEvent event = _eventMan->getSciEvent(kSciEventMousePress | kSciEventPeek);
|
|
if ((flags & kEventFlagMouseDown) && event.type == kSciEventMousePress) {
|
|
return kEventFlagMouseDown;
|
|
}
|
|
|
|
event = _eventMan->getSciEvent(kSciEventKeyDown | kSciEventPeek);
|
|
if ((flags & kEventFlagEscapeKey) && event.type == kSciEventKeyDown) {
|
|
if (getSciVersion() < SCI_VERSION_3) {
|
|
while ((event = _eventMan->getSciEvent(kSciEventKeyDown)),
|
|
event.type != kSciEventNone) {
|
|
if (event.character == kSciKeyEsc) {
|
|
return kEventFlagEscapeKey;
|
|
}
|
|
}
|
|
} else if (event.character == kSciKeyEsc) {
|
|
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()) {
|
|
freeConvertedFrame = false;
|
|
convertedFrame = const_cast<Graphics::Surface *>(&nextFrame);
|
|
} else {
|
|
freeConvertedFrame = true;
|
|
convertedFrame = nextFrame.convertTo(g_system->getScreenFormat(), _decoder->getPalette());
|
|
}
|
|
assert(convertedFrame);
|
|
|
|
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, which should just be part of
|
|
// Graphics::Surface (or some free functions).
|
|
const Graphics::TransparentSurface tsUnscaledFrame(*unscaledFrame);
|
|
#ifdef USE_RGB_COLOR
|
|
if (_hqVideoMode) {
|
|
convertedFrame = tsUnscaledFrame.scaleT<Graphics::FILTER_BILINEAR>(_drawRect.width(), _drawRect.height());
|
|
} else {
|
|
#elif 1
|
|
{
|
|
#else
|
|
}
|
|
#endif
|
|
convertedFrame = tsUnscaledFrame.scaleT<Graphics::FILTER_NEAREST>(_drawRect.width(), _drawRect.height());
|
|
}
|
|
assert(convertedFrame);
|
|
if (freeConvertedFrame) {
|
|
unscaledFrame->free();
|
|
delete unscaledFrame;
|
|
}
|
|
freeConvertedFrame = true;
|
|
}
|
|
|
|
g_system->copyRectToScreen(convertedFrame->getPixels(), convertedFrame->pitch, _drawRect.left, _drawRect.top, _drawRect.width(), _drawRect.height());
|
|
g_sci->_gfxFrameout->updateScreen();
|
|
|
|
if (freeConvertedFrame) {
|
|
convertedFrame->free();
|
|
delete convertedFrame;
|
|
}
|
|
}
|
|
|
|
template <typename PixelType>
|
|
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);
|
|
}
|
|
}
|
|
|
|
void VideoPlayer::setDrawRect(const int16 x, const int16 y, const int16 width, const int16 height) {
|
|
_drawRect = Common::Rect(x, y, x + width, y + height);
|
|
if (_drawRect.right > g_system->getWidth() || _drawRect.bottom > g_system->getHeight()) {
|
|
warning("Draw rect (%d, %d, %d, %d) is out of bounds of the screen; clipping it", PRINT_RECT(_drawRect));
|
|
_drawRect.clip(g_system->getWidth(), g_system->getHeight());
|
|
}
|
|
}
|
|
|
|
#pragma mark SEQPlayer
|
|
|
|
SEQPlayer::SEQPlayer(EventManager *eventMan) :
|
|
VideoPlayer(eventMan) {}
|
|
|
|
void SEQPlayer::play(const Common::String &fileName, const int16 numTicks, const int16, const int16) {
|
|
|
|
_decoder.reset(new SEQDecoder(numTicks));
|
|
|
|
if (!VideoPlayer::open(fileName)) {
|
|
_decoder.reset();
|
|
return;
|
|
}
|
|
|
|
const int16 scriptWidth = g_sci->_gfxFrameout->getScriptWidth();
|
|
const int16 scriptHeight = g_sci->_gfxFrameout->getScriptHeight();
|
|
const int16 screenWidth = g_sci->_gfxFrameout->getScreenWidth();
|
|
const int16 screenHeight = g_sci->_gfxFrameout->getScreenHeight();
|
|
|
|
const int16 scaledWidth = (_decoder->getWidth() * Ratio(screenWidth, scriptWidth)).toInt();
|
|
const int16 scaledHeight = (_decoder->getHeight() * Ratio(screenHeight, scriptHeight)).toInt();
|
|
|
|
// Normally we would use the coordinates passed into the play function to
|
|
// position the video, but since we are scaling the video (which SSCI did
|
|
// not do), the coordinates are not correct. Since videos are always
|
|
// intended to play in the center of the screen, we just recalculate the
|
|
// origin here.
|
|
_drawRect.left = (screenWidth - scaledWidth) / 2;
|
|
_drawRect.top = (screenHeight - scaledHeight) / 2;
|
|
_drawRect.setWidth(scaledWidth);
|
|
_drawRect.setHeight(scaledHeight);
|
|
|
|
startHQVideo();
|
|
playUntilEvent(kEventFlagMouseDown | kEventFlagEscapeKey);
|
|
endHQVideo();
|
|
g_system->fillScreen(0);
|
|
_decoder.reset();
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark AVIPlayer
|
|
|
|
AVIPlayer::AVIPlayer(EventManager *eventMan) :
|
|
VideoPlayer(eventMan, new Video::AVIDecoder()),
|
|
_status(kAVINotOpen) {
|
|
_decoder->setSoundType(Audio::Mixer::kSFXSoundType);
|
|
}
|
|
|
|
AVIPlayer::IOStatus AVIPlayer::open(const Common::String &fileName) {
|
|
if (_status != kAVINotOpen) {
|
|
close();
|
|
}
|
|
|
|
if (!VideoPlayer::open(fileName)) {
|
|
return kIOFileNotFound;
|
|
}
|
|
|
|
_status = kAVIOpen;
|
|
return kIOSuccess;
|
|
}
|
|
|
|
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
|
|
// original resolution, or
|
|
// * kShowMovie(WinInit, x, y, w, h) to render the video at (x,y) with
|
|
// rescaling to the given width and height, or
|
|
// * kShowMovie(WinInitDouble, x, y) to render the video at (x,y) with
|
|
// rescaling to double the original resolution.
|
|
//
|
|
// Unfortunately, the values passed by game scripts are frequently wrong:
|
|
//
|
|
// * KQ7 passes origin coordinates that cause videos to be misaligned on the
|
|
// Y-axis;
|
|
// * GK1 passes width and height that change the aspect ratio of the videos,
|
|
// even though they were rendered with square pixels (and in the case of
|
|
// CREDITS.AVI, cause the video to be badly downscaled);
|
|
// * The GK2 demo does all of these things at the same time.
|
|
//
|
|
// Fortunately, whenever all of these games play an AVI, they are just
|
|
// trying to play a video at the center of the screen. So, we ignore the
|
|
// values that the game sends, and instead calculate the correct dimensions
|
|
// and origin based on the video data, only allowing games to specify
|
|
// whether or not the videos should be scaled up 2x.
|
|
|
|
if (_status == kAVINotOpen) {
|
|
return kIOFileNotFound;
|
|
}
|
|
|
|
g_sci->_gfxCursor32->hide();
|
|
|
|
int16 width = _decoder->getWidth();
|
|
int16 height = _decoder->getHeight();
|
|
if (doublePixels) {
|
|
width *= 2;
|
|
height *= 2;
|
|
}
|
|
|
|
const int16 screenWidth = g_sci->_gfxFrameout->getScreenWidth();
|
|
const int16 screenHeight = g_sci->_gfxFrameout->getScreenHeight();
|
|
|
|
// When scaling videos, they must not grow larger than the hardware screen
|
|
// or else the engine will crash. This is particularly important for the GK1
|
|
// CREDITS.AVI since the game sends extra width/height arguments, causing it
|
|
// to be treated as needing upscaling even though it does not.
|
|
width = MIN<int16>(width, screenWidth);
|
|
height = MIN<int16>(height, screenHeight);
|
|
|
|
_drawRect.left = (screenWidth - width) / 2;
|
|
_drawRect.top = (screenHeight - height) / 2;
|
|
_drawRect.setWidth(width);
|
|
_drawRect.setHeight(height);
|
|
|
|
if (!startHQVideo() && _decoder->getPixelFormat().bytesPerPixel != 1) {
|
|
const Common::List<Graphics::PixelFormat> outFormats = g_system->getSupportedFormats();
|
|
Graphics::PixelFormat inFormat = _decoder->getPixelFormat();
|
|
Graphics::PixelFormat bestFormat = outFormats.front();
|
|
Common::List<Graphics::PixelFormat>::const_iterator it;
|
|
for (it = outFormats.begin(); it != outFormats.end(); ++it) {
|
|
if (*it == inFormat) {
|
|
bestFormat = inFormat;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bestFormat.bytesPerPixel != 2 && bestFormat.bytesPerPixel != 4) {
|
|
error("Failed to find any valid output pixel format");
|
|
}
|
|
|
|
g_sci->_gfxFrameout->setPixelFormat(bestFormat);
|
|
}
|
|
|
|
return kIOSuccess;
|
|
}
|
|
|
|
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 || getSciVersion() == SCI_VERSION_2_1_EARLY) {
|
|
playUntilEvent(kEventFlagNone);
|
|
} else {
|
|
_status = kAVIPlaying;
|
|
}
|
|
|
|
return kIOSuccess;
|
|
}
|
|
|
|
AVIPlayer::EventFlags AVIPlayer::playUntilEvent(const EventFlags flags, const uint32 maxSleepMs) {
|
|
// 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() {
|
|
if (_status == kAVINotOpen) {
|
|
return kIOSuccess;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
g_system->fillScreen(0);
|
|
g_sci->_gfxCursor32->unhide();
|
|
_decoder->close();
|
|
_status = kAVINotOpen;
|
|
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();
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark VMDPlayer
|
|
|
|
VMDPlayer::VMDPlayer(EventManager *eventMan, SegManager *segMan) :
|
|
VideoPlayer(eventMan, new Video::AdvancedVMDDecoder(Audio::Mixer::kSFXSoundType)),
|
|
_segMan(segMan),
|
|
|
|
_isOpen(false),
|
|
_isInitialized(false),
|
|
_bundledVmd(nullptr),
|
|
_yieldFrame(0),
|
|
_yieldInterval(0),
|
|
_lastYieldedFrameNo(0),
|
|
|
|
_plane(nullptr),
|
|
_screenItem(nullptr),
|
|
_planeIsOwned(true),
|
|
_priority(0),
|
|
_doublePixels(false),
|
|
_stretchVertical(false),
|
|
_blackLines(false),
|
|
_leaveScreenBlack(false),
|
|
_leaveLastFrame(false),
|
|
_ignorePalettes(false),
|
|
|
|
_blackoutPlane(nullptr),
|
|
|
|
_startColor(0),
|
|
_endColor(255),
|
|
#ifdef SCI_VMD_BLACK_PALETTE
|
|
_blackPalette(false),
|
|
#endif
|
|
_boostPercent(100),
|
|
_boostStartColor(0),
|
|
_boostEndColor(255),
|
|
|
|
_showCursor(false) {}
|
|
|
|
VMDPlayer::~VMDPlayer() {
|
|
close();
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark VMDPlayer - Playback
|
|
|
|
VMDPlayer::IOStatus VMDPlayer::open(const Common::String &fileName, const OpenFlags flags) {
|
|
if (_isOpen) {
|
|
error("Attempted to play %s, but another VMD was loaded", fileName.c_str());
|
|
}
|
|
|
|
if (g_sci->_features->VMDOpenStopsAudio()) {
|
|
g_sci->_audio32->stop(kAllChannels);
|
|
}
|
|
|
|
Resource *bundledVmd = g_sci->getResMan()->findResource(ResourceId(kResourceTypeVMD, fileName.asUint64()), true);
|
|
|
|
if (bundledVmd != nullptr) {
|
|
Common::SeekableReadStream *stream = bundledVmd->makeStream();
|
|
if (_decoder->loadStream(stream)) {
|
|
_bundledVmd = bundledVmd;
|
|
_isOpen = true;
|
|
} else {
|
|
delete stream;
|
|
g_sci->getResMan()->unlockResource(bundledVmd);
|
|
}
|
|
} else if (_decoder->loadFile(fileName)) {
|
|
_isOpen = true;
|
|
}
|
|
|
|
if (_isOpen) {
|
|
if (flags & kOpenFlagMute) {
|
|
_decoder->setVolume(0);
|
|
}
|
|
return kIOSuccess;
|
|
}
|
|
|
|
return kIOError;
|
|
}
|
|
|
|
void VMDPlayer::init(int16 x, int16 y, const PlayFlags flags, const int16 boostPercent, const int16 boostStartColor, const int16 boostEndColor) {
|
|
const int16 screenWidth = g_sci->_gfxFrameout->getScreenWidth();
|
|
const int16 screenHeight = g_sci->_gfxFrameout->getScreenHeight();
|
|
const bool upscaleVideos = ConfMan.hasKey("enable_video_upscale") ? ConfMan.getBool("enable_video_upscale") : false;
|
|
|
|
_doublePixels = (flags & kPlayFlagDoublePixels) || upscaleVideos;
|
|
_stretchVertical = flags & kPlayFlagStretchVertical;
|
|
|
|
const int16 width = _decoder->getWidth() << (_doublePixels ? 1 : 0);
|
|
const int16 height = _decoder->getHeight() << (_doublePixels || _stretchVertical ? 1 : 0);
|
|
|
|
if (getSciVersion() < SCI_VERSION_3) {
|
|
x &= ~1;
|
|
}
|
|
|
|
if (upscaleVideos) {
|
|
x = (screenWidth - width) / 2;
|
|
y = (screenHeight - height) / 2;
|
|
}
|
|
|
|
_blackLines = ConfMan.getBool("enable_black_lined_video") && (flags & kPlayFlagBlackLines);
|
|
// If ScummVM has been configured to disable black lines on video playback,
|
|
// the boosts need to be ignored too or else the brightness of the video
|
|
// will be too high
|
|
_boostPercent = 100 + (_blackLines && (flags & kPlayFlagBoost) ? boostPercent : 0);
|
|
_boostStartColor = CLIP<int16>(boostStartColor, 0, 255);
|
|
_boostEndColor = CLIP<int16>(boostEndColor, 0, 255);
|
|
_leaveScreenBlack = flags & kPlayFlagLeaveScreenBlack;
|
|
_leaveLastFrame = flags & kPlayFlagLeaveLastFrame;
|
|
#ifdef SCI_VMD_BLACK_PALETTE
|
|
_blackPalette = flags & kPlayFlagBlackPalette;
|
|
#endif
|
|
|
|
setDrawRect(x, y, width, height);
|
|
}
|
|
|
|
VMDPlayer::IOStatus VMDPlayer::close() {
|
|
if (!_isOpen) {
|
|
return kIOSuccess;
|
|
}
|
|
|
|
if (_isInitialized) {
|
|
if (_isComposited) {
|
|
closeComposited();
|
|
} else {
|
|
closeOverlay();
|
|
}
|
|
|
|
if (_blackoutPlane != nullptr) {
|
|
g_sci->_gfxFrameout->deletePlane(*_blackoutPlane);
|
|
_blackoutPlane = nullptr;
|
|
}
|
|
|
|
if (!_leaveLastFrame && !_leaveScreenBlack) {
|
|
// This call *actually* deletes the blackout plane
|
|
g_sci->_gfxFrameout->frameOut(true);
|
|
}
|
|
|
|
if (!_showCursor) {
|
|
g_sci->_gfxCursor32->unhide();
|
|
}
|
|
}
|
|
|
|
_decoder->close();
|
|
|
|
if (_bundledVmd) {
|
|
g_sci->getResMan()->unlockResource(_bundledVmd);
|
|
_bundledVmd = nullptr;
|
|
}
|
|
|
|
_isOpen = false;
|
|
_isInitialized = false;
|
|
_ignorePalettes = false;
|
|
_lastYieldedFrameNo = 0;
|
|
_planeIsOwned = true;
|
|
_priority = 0;
|
|
_drawRect = Common::Rect();
|
|
_blobs.clear();
|
|
_needsUpdate = false;
|
|
_currentFrame = nullptr;
|
|
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);
|
|
|
|
const int32 maxFrameNo = _decoder->getFrameCount() - 1;
|
|
|
|
if ((flags & kEventFlagToFrame) && lastFrameNo) {
|
|
_yieldFrame = MIN<int32>(lastFrameNo, maxFrameNo);
|
|
} else {
|
|
_yieldFrame = maxFrameNo;
|
|
}
|
|
|
|
if (flags & kEventFlagYieldToVM) {
|
|
_yieldInterval = 3;
|
|
if (yieldInterval == -1 && !(flags & kEventFlagToFrame)) {
|
|
_yieldInterval = lastFrameNo;
|
|
} else if (yieldInterval != -1) {
|
|
_yieldInterval = MIN<int32>(yieldInterval, maxFrameNo);
|
|
}
|
|
} else {
|
|
_yieldInterval = maxFrameNo;
|
|
}
|
|
|
|
return playUntilEvent(flags);
|
|
}
|
|
|
|
VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags, const uint32) {
|
|
if (flags & kEventFlagReverse) {
|
|
// 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);
|
|
}
|
|
|
|
if (!_isInitialized) {
|
|
_isInitialized = true;
|
|
|
|
if (!_showCursor) {
|
|
g_sci->_gfxCursor32->hide();
|
|
}
|
|
|
|
if (!_blackoutRect.isEmpty() && _planeIsOwned) {
|
|
_blackoutPlane = new Plane(_blackoutRect);
|
|
g_sci->_gfxFrameout->addPlane(_blackoutPlane);
|
|
}
|
|
|
|
if (shouldUseCompositing()) {
|
|
_isComposited = true;
|
|
initComposited();
|
|
} else {
|
|
_isComposited = false;
|
|
initOverlay();
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
VMDPlayer::EventFlags VMDPlayer::checkForEvent(const EventFlags flags) {
|
|
const int currentFrameNo = _decoder->getCurFrame();
|
|
|
|
if (currentFrameNo >= _yieldFrame) {
|
|
return kEventFlagEnd;
|
|
}
|
|
|
|
if (_yieldInterval > 0 &&
|
|
currentFrameNo != _lastYieldedFrameNo &&
|
|
(currentFrameNo % _yieldInterval) == 0) {
|
|
|
|
_lastYieldedFrameNo = currentFrameNo;
|
|
return kEventFlagYieldToVM;
|
|
}
|
|
|
|
EventFlags stopFlag = VideoPlayer::checkForEvent(flags);
|
|
if (stopFlag != kEventFlagNone) {
|
|
return stopFlag;
|
|
}
|
|
|
|
const SciEvent event = _eventMan->getSciEvent(kSciEventHotRectangle | kSciEventPeek);
|
|
if ((flags & kEventFlagHotRectangle) && event.type == kSciEventHotRectangle) {
|
|
return kEventFlagHotRectangle;
|
|
}
|
|
|
|
return kEventFlagNone;
|
|
}
|
|
|
|
int16 VMDPlayer::addBlob(int16 blockSize, int16 top, int16 left, int16 bottom, int16 right) {
|
|
if (_blobs.size() >= kMaxBlobs) {
|
|
return -1;
|
|
}
|
|
|
|
int16 blobNumber = 0;
|
|
Common::List<Blob>::iterator prevBlobIterator = _blobs.begin();
|
|
for (; prevBlobIterator != _blobs.end(); ++prevBlobIterator, ++blobNumber) {
|
|
if (blobNumber < prevBlobIterator->blobNumber) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Blob blob = { blobNumber, blockSize, top, left, bottom, right };
|
|
_blobs.insert(prevBlobIterator, blob);
|
|
|
|
_needsUpdate = true;
|
|
return blobNumber;
|
|
}
|
|
|
|
void VMDPlayer::deleteBlobs() {
|
|
if (!_blobs.empty()) {
|
|
_blobs.clear();
|
|
_needsUpdate = true;
|
|
}
|
|
}
|
|
|
|
void VMDPlayer::deleteBlob(int16 blobNumber) {
|
|
for (Common::List<Blob>::iterator b = _blobs.begin(); b != _blobs.end(); ++b) {
|
|
if (b->blobNumber == blobNumber) {
|
|
_blobs.erase(b);
|
|
_needsUpdate = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void VMDPlayer::initOverlay() {
|
|
// Composited videos forced through the overlay renderer (due to HQ video
|
|
// mode) still need to occlude whatever is behind them in the renderer (as
|
|
// in composited mode) to prevent palette glitches caused by premature
|
|
// submission of occluded screen items (e.g. leaving the lava room sphere in
|
|
// the volcano in Lighthouse, the pic after the video finishes playing will
|
|
// be rendered with the wrong palette)
|
|
if (isNormallyComposited() && _planeIsOwned) {
|
|
_plane = new Plane(_drawRect, kPlanePicColored);
|
|
if (_priority) {
|
|
_plane->_priority = _priority;
|
|
}
|
|
g_sci->_gfxFrameout->addPlane(_plane);
|
|
}
|
|
|
|
// 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
|
|
// TODO: Allow interpolation for videos where the cursor is drawn, either by
|
|
// 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 (startHQVideo()) {
|
|
redrawGameScreen();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#ifdef USE_RGB_COLOR
|
|
void VMDPlayer::redrawGameScreen() const {
|
|
if (!_hqVideoMode) {
|
|
return;
|
|
}
|
|
|
|
g_sci->_gfxFrameout->redrawGameScreen(_drawRect);
|
|
}
|
|
#endif
|
|
|
|
void VMDPlayer::renderOverlay(const Graphics::Surface &nextFrame) const {
|
|
#ifdef USE_RGB_COLOR
|
|
if (_hqVideoMode) {
|
|
VideoPlayer::renderFrame(nextFrame);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
Graphics::Surface out = g_sci->_gfxFrameout->getCurrentBuffer().getSubArea(_drawRect);
|
|
renderLQToSurface<uint8>(out, nextFrame, _doublePixels, _blackLines);
|
|
g_sci->_gfxFrameout->directFrameOut(_drawRect);
|
|
}
|
|
|
|
void VMDPlayer::submitPalette(const uint8 rawPalette[256 * 3]) const {
|
|
if (_ignorePalettes) {
|
|
return;
|
|
}
|
|
|
|
Palette palette;
|
|
for (uint16 i = 0; i < _startColor; ++i) {
|
|
palette.colors[i].used = false;
|
|
}
|
|
for (uint16 i = _endColor + 1; i < ARRAYSIZE(palette.colors); ++i) {
|
|
palette.colors[i].used = false;
|
|
}
|
|
#if SCI_VMD_BLACK_PALETTE
|
|
if (_blackPalette) {
|
|
for (uint16 i = _startColor; i <= _endColor; ++i) {
|
|
palette.colors[i].r = palette.colors[i].g = palette.colors[i].b = 0;
|
|
palette.colors[i].used = true;
|
|
}
|
|
} else
|
|
#endif
|
|
fillPalette(rawPalette, palette);
|
|
|
|
if (_isComposited) {
|
|
SciBitmap *bitmap = _segMan->lookupBitmap(_bitmapId);
|
|
bitmap->setPalette(palette);
|
|
// SSCI calls updateScreenItem and frameOut here, but this is not
|
|
// 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();
|
|
g_sci->_gfxPalette32->updateHardware();
|
|
}
|
|
|
|
#if SCI_VMD_BLACK_PALETTE
|
|
if (_blackPalette) {
|
|
fillPalette(rawPalette, palette);
|
|
if (_isComposited) {
|
|
SciBitmap *bitmap = _segMan->lookupBitmap(_bitmapId);
|
|
bitmap->setPalette(palette);
|
|
}
|
|
g_sci->_gfxPalette32->submit(palette);
|
|
g_sci->_gfxPalette32->updateForFrame();
|
|
g_sci->_gfxPalette32->updateHardware();
|
|
}
|
|
#endif
|
|
|
|
#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() {
|
|
if (isNormallyComposited() && _planeIsOwned && _plane != nullptr) {
|
|
g_sci->_gfxFrameout->deletePlane(*_plane);
|
|
_plane = nullptr;
|
|
}
|
|
|
|
#ifdef USE_RGB_COLOR
|
|
if (_hqVideoMode) {
|
|
if (endHQVideo()) {
|
|
g_sci->_gfxFrameout->resetHardware();
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
g_sci->_gfxFrameout->frameOut(true, _drawRect);
|
|
}
|
|
|
|
void VMDPlayer::initComposited() {
|
|
ScaleInfo vmdScaleInfo;
|
|
|
|
if (_doublePixels) {
|
|
vmdScaleInfo.x *= 2;
|
|
vmdScaleInfo.y *= 2;
|
|
vmdScaleInfo.signal = kScaleSignalManual;
|
|
} else if (_stretchVertical) {
|
|
vmdScaleInfo.y *= 2;
|
|
vmdScaleInfo.signal = kScaleSignalManual;
|
|
}
|
|
|
|
const uint32 hunkPaletteSize = HunkPalette::calculateHunkPaletteSize(256, false);
|
|
const int16 screenWidth = g_sci->_gfxFrameout->getScreenWidth();
|
|
const int16 screenHeight = g_sci->_gfxFrameout->getScreenHeight();
|
|
|
|
SciBitmap &vmdBitmap = *_segMan->allocateBitmap(&_bitmapId, _drawRect.width(), _drawRect.height(), 255, 0, 0, screenWidth, screenHeight, hunkPaletteSize, false, false);
|
|
vmdBitmap.getBuffer().fillRect(Common::Rect(_drawRect.width(), _drawRect.height()), 0);
|
|
|
|
CelInfo32 vmdCelInfo;
|
|
vmdCelInfo.bitmap = _bitmapId;
|
|
|
|
Video::AdvancedVMDDecoder *decoder = dynamic_cast<Video::AdvancedVMDDecoder *>(_decoder.get());
|
|
assert(decoder);
|
|
decoder->setSurfaceMemory(vmdBitmap.getPixels(), vmdBitmap.getWidth(), vmdBitmap.getHeight(), 1);
|
|
|
|
if (_planeIsOwned) {
|
|
_plane = new Plane(_drawRect, kPlanePicColored);
|
|
if (_priority) {
|
|
_plane->_priority = _priority;
|
|
}
|
|
g_sci->_gfxFrameout->addPlane(_plane);
|
|
_screenItem = new ScreenItem(_plane->_object, vmdCelInfo, Common::Point(), vmdScaleInfo);
|
|
} else {
|
|
_screenItem = new ScreenItem(_plane->_object, vmdCelInfo, Common::Point(_drawRect.left, _drawRect.top), vmdScaleInfo);
|
|
if (_priority) {
|
|
_screenItem->_priority = _priority;
|
|
}
|
|
}
|
|
|
|
if (_blackLines) {
|
|
_screenItem->_drawBlackLines = true;
|
|
}
|
|
|
|
// In SSCI, there was code for positioning the screen item using insetRect
|
|
// here, but none of the game scripts seem to use this functionality.
|
|
|
|
g_sci->_gfxFrameout->addScreenItem(*_screenItem);
|
|
|
|
// Composited VMDs periodically yield to game scripts which will often call
|
|
// kFrameOut to make changes to other parts of the screen. Since VMDPlayer
|
|
// is responsible for throttling output during these times, GfxFrameout
|
|
// needs to stop throttling kFrameOut calls or else we will drop frames when
|
|
// kFrameOut sleeps right through the next frame
|
|
g_sci->_gfxFrameout->_throttleKernelFrameOut = false;
|
|
}
|
|
|
|
void VMDPlayer::renderComposited() const {
|
|
g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
|
|
g_sci->_gfxFrameout->frameOut(true);
|
|
}
|
|
|
|
void VMDPlayer::closeComposited() {
|
|
if (_bitmapId != NULL_REG) {
|
|
_segMan->freeBitmap(_bitmapId);
|
|
_bitmapId = NULL_REG;
|
|
}
|
|
|
|
if (!_planeIsOwned && _screenItem != nullptr) {
|
|
g_sci->_gfxFrameout->deleteScreenItem(*_screenItem);
|
|
_screenItem = nullptr;
|
|
} else if (_plane != nullptr) {
|
|
g_sci->_gfxFrameout->deletePlane(*_plane);
|
|
_plane = nullptr;
|
|
}
|
|
|
|
if (!_leaveLastFrame && _leaveScreenBlack) {
|
|
// This call *actually* deletes the plane/screen item
|
|
g_sci->_gfxFrameout->frameOut(true);
|
|
}
|
|
|
|
g_sci->_gfxFrameout->_throttleKernelFrameOut = true;
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark VMDPlayer - Rendering
|
|
|
|
void VMDPlayer::renderFrame(const Graphics::Surface &nextFrame) const {
|
|
if (_isComposited) {
|
|
renderComposited();
|
|
} else {
|
|
if (_blobs.empty()) {
|
|
renderOverlay(nextFrame);
|
|
} else {
|
|
Graphics::Surface censoredFrame;
|
|
censoredFrame.create(nextFrame.w, nextFrame.h, nextFrame.format);
|
|
censoredFrame.copyFrom(nextFrame);
|
|
drawBlobs(censoredFrame);
|
|
renderOverlay(censoredFrame);
|
|
censoredFrame.free();
|
|
}
|
|
}
|
|
}
|
|
|
|
void VMDPlayer::drawBlobs(Graphics::Surface& frame) const {
|
|
for (Common::List<Blob>::const_iterator blob = _blobs.begin(); blob != _blobs.end(); ++blob) {
|
|
for (int16 blockLeft = blob->left; blockLeft < blob->right; blockLeft += blob->blockSize) {
|
|
for (int16 blockTop = blob->top; blockTop < blob->bottom; blockTop += blob->blockSize) {
|
|
byte color = *(byte *)frame.getBasePtr(blockLeft, blockTop);
|
|
Common::Rect block(blockLeft, blockTop, blockLeft + blob->blockSize, blockTop + blob->blockSize);
|
|
frame.fillRect(block, color);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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++;
|
|
uint8 b = *vmdPalette++;
|
|
|
|
if (_boostPercent != 100 && i >= _boostStartColor && i <= _boostEndColor) {
|
|
r = CLIP(r * _boostPercent / 100, 0, 255);
|
|
g = CLIP(g * _boostPercent / 100, 0, 255);
|
|
b = CLIP(b * _boostPercent / 100, 0, 255);
|
|
}
|
|
|
|
outPalette.colors[i].r = r;
|
|
outPalette.colors[i].g = g;
|
|
outPalette.colors[i].b = b;
|
|
outPalette.colors[i].used = true;
|
|
}
|
|
}
|
|
|
|
void VMDPlayer::setPlane(const int16 priority, const reg_t planeId) {
|
|
_priority = priority;
|
|
if (planeId != NULL_REG) {
|
|
_plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeId);
|
|
assert(_plane != nullptr);
|
|
_planeIsOwned = false;
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark VMDPlayer - Palette
|
|
|
|
void VMDPlayer::restrictPalette(const uint8 startColor, const int16 endColor) {
|
|
_startColor = startColor;
|
|
// At least GK2 sends 256 as the end color, which is wrong, but works in
|
|
// SSCI as the storage size is 4 bytes and used values are clamped to 0-255
|
|
_endColor = MIN<int16>(255, endColor);
|
|
}
|
|
|
|
#pragma mark -
|
|
#pragma mark DuckPlayer
|
|
|
|
DuckPlayer::DuckPlayer(EventManager *eventMan, SegManager *segMan) :
|
|
VideoPlayer(eventMan, new Video::AVIDecoder()),
|
|
_plane(nullptr),
|
|
_status(kDuckClosed),
|
|
_volume(Audio::Mixer::kMaxChannelVolume),
|
|
_doFrameOut(false) {
|
|
_decoder->setSoundType(Audio::Mixer::kSFXSoundType);
|
|
}
|
|
|
|
void DuckPlayer::open(const GuiResourceId resourceId, const int displayMode, const int16 x, const int16 y) {
|
|
if (_status != kDuckClosed) {
|
|
error("Attempted to play %u.duk, but another video was loaded", resourceId);
|
|
}
|
|
|
|
const Common::String fileName = Common::String::format("%u.duk", resourceId);
|
|
|
|
if (!VideoPlayer::open(fileName)) {
|
|
return;
|
|
}
|
|
|
|
_decoder->setVolume(_volume);
|
|
|
|
_doublePixels = displayMode != 0;
|
|
_blackLines = ConfMan.getBool("enable_black_lined_video") &&
|
|
(displayMode == 1 || displayMode == 3);
|
|
|
|
// SSCI seems to incorrectly calculate the draw rect by scaling the origin
|
|
// in addition to the width/height for the BR point
|
|
setDrawRect(x, y,
|
|
(_decoder->getWidth() << (_doublePixels ? 1 : 0)),
|
|
(_decoder->getHeight() << (_doublePixels ? 1 : 0)));
|
|
|
|
g_sci->_gfxCursor32->hide();
|
|
|
|
if (_doFrameOut) {
|
|
_plane = new Plane(_drawRect, kPlanePicColored);
|
|
g_sci->_gfxFrameout->addPlane(_plane);
|
|
g_sci->_gfxFrameout->frameOut(true);
|
|
}
|
|
|
|
if (!startHQVideo() && _decoder->getPixelFormat().bytesPerPixel != 1) {
|
|
g_sci->_gfxFrameout->setPixelFormat(_decoder->getPixelFormat());
|
|
}
|
|
|
|
_status = kDuckOpen;
|
|
}
|
|
|
|
void DuckPlayer::play(const int lastFrameNo) {
|
|
// This status check does not exist in the original interpreter, but is
|
|
// necessary to avoid a crash if the engine cannot find or render the video
|
|
// for playback. Game scripts receive no feedback from the kernel regarding
|
|
// whether or not an attempt to open a Duck video actually succeeded, so
|
|
// they can only assume it always succeeds (and so always call to `play`
|
|
// even if they shouldn't).
|
|
if (_status == kDuckClosed) {
|
|
return;
|
|
}
|
|
|
|
if (_status != kDuckPlaying) {
|
|
_status = kDuckPlaying;
|
|
}
|
|
|
|
if (lastFrameNo != -1) {
|
|
_decoder->setEndFrame(lastFrameNo);
|
|
}
|
|
|
|
playUntilEvent(kEventFlagMouseDown | kEventFlagEscapeKey);
|
|
}
|
|
|
|
void DuckPlayer::close() {
|
|
if (_status == kDuckClosed) {
|
|
return;
|
|
}
|
|
|
|
_decoder->close();
|
|
|
|
endHQVideo();
|
|
|
|
g_sci->_gfxCursor32->unhide();
|
|
|
|
if (_doFrameOut) {
|
|
g_sci->_gfxFrameout->deletePlane(*_plane);
|
|
g_sci->_gfxFrameout->frameOut(true);
|
|
_plane = nullptr;
|
|
}
|
|
|
|
_drawRect = Common::Rect();
|
|
_status = kDuckClosed;
|
|
_volume = Audio::Mixer::kMaxChannelVolume;
|
|
_doFrameOut = false;
|
|
}
|
|
|
|
void DuckPlayer::renderFrame(const Graphics::Surface &nextFrame) const {
|
|
#ifdef USE_RGB_COLOR
|
|
if (_hqVideoMode) {
|
|
VideoPlayer::renderFrame(nextFrame);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
Graphics::Surface out;
|
|
out.create(_drawRect.width(), _drawRect.height(), nextFrame.format);
|
|
renderLQToSurface<uint16>(out, nextFrame, _doublePixels, _blackLines);
|
|
if (out.format != g_system->getScreenFormat()) {
|
|
out.convertToInPlace(g_system->getScreenFormat());
|
|
}
|
|
g_system->copyRectToScreen(out.getPixels(), out.pitch, _drawRect.left, _drawRect.top, out.w, out.h);
|
|
out.free();
|
|
}
|
|
|
|
} // End of namespace Sci
|