diff options
Diffstat (limited to 'engines/sci/graphics')
30 files changed, 4462 insertions, 1440 deletions
diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp index 693bc5f196..a3dcbbbab6 100644 --- a/engines/sci/graphics/celobj32.cpp +++ b/engines/sci/graphics/celobj32.cpp @@ -26,13 +26,12 @@ #include "sci/graphics/celobj32.h" #include "sci/graphics/frameout.h" #include "sci/graphics/palette32.h" -#include "sci/graphics/picture.h" -#include "sci/graphics/remap.h" +#include "sci/graphics/remap32.h" #include "sci/graphics/text32.h" -#include "sci/graphics/view.h" namespace Sci { #pragma mark CelScaler + CelScaler *CelObj::_scaler = nullptr; void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) { @@ -84,9 +83,11 @@ const CelScalerTable *CelScaler::getScalerTable(const Ratio &scaleX, const Ratio #pragma mark - #pragma mark CelObj +bool CelObj::_drawBlackLines = false; void CelObj::init() { CelObj::deinit(); + _drawBlackLines = false; _nextCacheId = 1; _scaler = new CelScaler(); _cache = new CelCache; @@ -120,6 +121,7 @@ struct SCALER_NoScale { const int16 _sourceY; SCALER_NoScale(const CelObj &celObj, const int16 maxWidth, const Common::Point &scaledPosition) : + _row(nullptr), _reader(celObj, FLIP ? celObj._width : maxWidth), _lastIndex(celObj._width - 1), _sourceX(scaledPosition.x), @@ -166,6 +168,7 @@ struct SCALER_Scale { static int16 _valuesY[1024]; SCALER_Scale(const CelObj &celObj, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio scaleX, const Ratio scaleY) : + _row(nullptr), #ifndef NDEBUG _maxX(targetRect.right - 1), #endif @@ -175,34 +178,61 @@ struct SCALER_Scale { // line of source data when scaling _reader(celObj, celObj._width) { // In order for scaling ratios to apply equally across objects that - // start at different positions on the screen, the pixels that are - // read from the source bitmap must all use the same pattern of - // division. In other words, cels must follow the same scaling pattern - // as if they were drawn starting at an even multiple of the scaling - // ratio, even if they were not. + // start at different positions on the screen (like the cels of a + // picture), the pixels that are read from the source bitmap must all + // use the same pattern of division. In other words, cels must follow + // a global scaling pattern as if they were always drawn starting at an + // even multiple of the scaling ratio, even if they are not. // // To get the correct source pixel when reading out through the scaler, // the engine creates a lookup table for each axis that translates // directly from target positions to the indexes of source pixels using // the global cadence for the given scaling ratio. + // + // Note, however, that not all games use the global scaling mode. + // + // SQ6 definitely uses the global scaling mode (an easy visual + // comparison is to leave Implants N' Stuff and then look at Roger); + // Torin definitely does not (scaling subtitle backgrounds will cause it + // to attempt a read out of bounds and crash). They are both SCI + // "2.1mid" games, so currently the common denominator looks to be that + // games which use global scaling are the ones that use low-resolution + // script coordinates too. const CelScalerTable *table = CelObj::_scaler->getScalerTable(scaleX, scaleY); - const int16 unscaledX = (scaledPosition.x / scaleX).toInt(); - if (FLIP) { - int lastIndex = celObj._width - 1; - for (int16 x = targetRect.left; x < targetRect.right; ++x) { - _valuesX[x] = lastIndex - (table->valuesX[x] - unscaledX); + if (g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth == kLowResX) { + const int16 unscaledX = (scaledPosition.x / scaleX).toInt(); + if (FLIP) { + int lastIndex = celObj._width - 1; + for (int16 x = targetRect.left; x < targetRect.right; ++x) { + _valuesX[x] = lastIndex - (table->valuesX[x] - unscaledX); + } + } else { + for (int16 x = targetRect.left; x < targetRect.right; ++x) { + _valuesX[x] = table->valuesX[x] - unscaledX; + } + } + + const int16 unscaledY = (scaledPosition.y / scaleY).toInt(); + for (int16 y = targetRect.top; y < targetRect.bottom; ++y) { + _valuesY[y] = table->valuesY[y] - unscaledY; } } else { - for (int16 x = targetRect.left; x < targetRect.right; ++x) { - _valuesX[x] = table->valuesX[x] - unscaledX; + if (FLIP) { + int lastIndex = celObj._width - 1; + for (int16 x = 0; x < targetRect.width(); ++x) { + _valuesX[targetRect.left + x] = lastIndex - table->valuesX[x]; + } + } else { + for (int16 x = 0; x < targetRect.width(); ++x) { + _valuesX[targetRect.left + x] = table->valuesX[x]; + } } - } - const int16 unscaledY = (scaledPosition.y / scaleY).toInt(); - for (int16 y = targetRect.top; y < targetRect.bottom; ++y) { - _valuesY[y] = table->valuesY[y] - unscaledY; + for (int16 y = 0; y < targetRect.height(); ++y) { + _valuesY[targetRect.top + y] = table->valuesY[y]; + } } } @@ -321,6 +351,10 @@ public: #pragma mark - #pragma mark CelObj - Remappers +/** + * Pixel mapper for a CelObj with transparent pixels and no + * remapping data. + */ struct MAPPER_NoMD { inline void draw(byte *target, const byte pixel, const uint8 skipColor) const { if (pixel != skipColor) { @@ -328,29 +362,54 @@ struct MAPPER_NoMD { } } }; + +/** + * Pixel mapper for a CelObj with no transparent pixels and + * no remapping data. + */ struct MAPPER_NoMDNoSkip { inline void draw(byte *target, const byte pixel, const uint8) const { *target = pixel; } }; +/** + * Pixel mapper for a CelObj with transparent pixels, + * remapping data, and remapping enabled. + */ struct MAPPER_Map { inline void draw(byte *target, const byte pixel, const uint8 skipColor) const { if (pixel != skipColor) { + // NOTE: For some reason, SSCI never checks if the source + // pixel is *above* the range of remaps. if (pixel < g_sci->_gfxRemap32->getStartColor()) { *target = pixel; - } else { - if (g_sci->_gfxRemap32->remapEnabled(pixel)) - *target = g_sci->_gfxRemap32->remapColor(pixel, *target); + } else if (g_sci->_gfxRemap32->remapEnabled(pixel)) { + *target = g_sci->_gfxRemap32->remapColor(pixel, *target); } } } }; +/** + * Pixel mapper for a CelObj with transparent pixels, + * remapping data, and remapping disabled. + */ +struct MAPPER_NoMap { + inline void draw(byte *target, const byte pixel, const uint8 skipColor) const { + // NOTE: For some reason, SSCI never checks if the source + // pixel is *above* the range of remaps. + if (pixel != skipColor && pixel < g_sci->_gfxRemap32->getStartColor()) { + *target = pixel; + } + } +}; + void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const { const Common::Point &scaledPosition = screenItem._scaledPosition; const Ratio &scaleX = screenItem._ratioX; const Ratio &scaleY = screenItem._ratioY; + _drawBlackLines = screenItem._drawBlackLines; if (_remap) { // NOTE: In the original code this check was `g_Remap_numActiveRemaps && _remap`, @@ -432,6 +491,8 @@ void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Re } } } + + _drawBlackLines = false; } void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, bool mirrorX) { @@ -509,46 +570,42 @@ uint8 CelObj::readPixel(uint16 x, const uint16 y, bool mirrorX) const { void CelObj::submitPalette() const { if (_hunkPaletteOffset) { - Palette palette; - - byte *res = getResPointer(); - // NOTE: In SCI engine this uses HunkPalette::Init. - // TODO: Use a better size value - g_sci->_gfxPalette32->createFromData(res + _hunkPaletteOffset, 999999, &palette); + HunkPalette palette(getResPointer() + _hunkPaletteOffset); g_sci->_gfxPalette32->submit(palette); } } #pragma mark - #pragma mark CelObj - Caching + int CelObj::_nextCacheId = 1; CelCache *CelObj::_cache = nullptr; int CelObj::searchCache(const CelInfo32 &celInfo, int *nextInsertIndex) const { + *nextInsertIndex = -1; int oldestId = _nextCacheId + 1; - int oldestIndex = -1; + int oldestIndex = 0; for (int i = 0, len = _cache->size(); i < len; ++i) { CelCacheEntry &entry = (*_cache)[i]; - if (entry.celObj != nullptr) { - if (entry.celObj->_info == celInfo) { - entry.id = ++_nextCacheId; - return i; + if (entry.celObj == nullptr) { + if (*nextInsertIndex == -1) { + *nextInsertIndex = i; } - - if (oldestId > entry.id) { - oldestId = entry.id; - oldestIndex = i; - } - } else if (oldestIndex == -1) { + } else if (entry.celObj->_info == celInfo) { + entry.id = ++_nextCacheId; + return i; + } else if (oldestId > entry.id) { + oldestId = entry.id; oldestIndex = i; } } - // NOTE: Unlike the original SCI engine code, the out-param - // here is only updated if there was not a cache hit. - *nextInsertIndex = oldestIndex; + if (*nextInsertIndex == -1) { + *nextInsertIndex = oldestIndex; + } + return -1; } @@ -570,7 +627,7 @@ void CelObj::putCopyInCache(const int cacheIndex) const { #pragma mark - #pragma mark CelObj - Drawing -template<typename MAPPER, typename SCALER> +template<typename MAPPER, typename SCALER, bool DRAW_BLACK_LINES> struct RENDERER { MAPPER &_mapper; SCALER &_scaler; @@ -588,6 +645,12 @@ struct RENDERER { const int16 targetWidth = targetRect.width(); const int16 targetHeight = targetRect.height(); for (int16 y = 0; y < targetHeight; ++y) { + if (DRAW_BLACK_LINES && (y % 2) == 0) { + memset(targetPixel, 0, targetWidth); + targetPixel += targetWidth + skipStride; + continue; + } + _scaler.setTarget(targetRect.left, targetRect.top + y); for (int16 x = 0; x < targetWidth; ++x) { @@ -604,7 +667,7 @@ void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common MAPPER mapper; SCALER scaler(*this, targetRect.left - scaledPosition.x + targetRect.width(), scaledPosition); - RENDERER<MAPPER, SCALER> renderer(mapper, scaler, _transparentColor); + RENDERER<MAPPER, SCALER, false> renderer(mapper, scaler, _transparentColor); renderer.draw(target, targetRect, scaledPosition); } @@ -613,42 +676,45 @@ void CelObj::render(Buffer &target, const Common::Rect &targetRect, const Common MAPPER mapper; SCALER scaler(*this, targetRect, scaledPosition, scaleX, scaleY); - RENDERER<MAPPER, SCALER> renderer(mapper, scaler, _transparentColor); - renderer.draw(target, targetRect, scaledPosition); -} - -void dummyFill(Buffer &target, const Common::Rect &targetRect) { - target.fillRect(targetRect, 250); + if (_drawBlackLines) { + RENDERER<MAPPER, SCALER, true> renderer(mapper, scaler, _transparentColor); + renderer.draw(target, targetRect, scaledPosition); + } else { + RENDERER<MAPPER, SCALER, false> renderer(mapper, scaler, _transparentColor); + renderer.draw(target, targetRect, scaledPosition); + } } void CelObj::drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - debug("drawHzFlip"); - dummyFill(target, targetRect); + render<MAPPER_NoMap, SCALER_NoScale<true, READER_Compressed> >(target, targetRect, scaledPosition); } void CelObj::drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - debug("drawNoFlip"); - dummyFill(target, targetRect); + render<MAPPER_NoMap, SCALER_NoScale<false, READER_Compressed> >(target, targetRect, scaledPosition); } void CelObj::drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - debug("drawUncompNoFlip"); - dummyFill(target, targetRect); + render<MAPPER_NoMap, SCALER_NoScale<false, READER_Uncompressed> >(target, targetRect, scaledPosition); } void CelObj::drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - debug("drawUncompHzFlip"); - dummyFill(target, targetRect); + render<MAPPER_NoMap, SCALER_NoScale<true, READER_Uncompressed> >(target, targetRect, scaledPosition); } void CelObj::scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - debug("scaleDraw"); - dummyFill(target, targetRect); + if (_drawMirrored) { + render<MAPPER_NoMap, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + } else { + render<MAPPER_NoMap, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + } } void CelObj::scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - debug("scaleDrawUncomp"); - dummyFill(target, targetRect); + if (_drawMirrored) { + render<MAPPER_NoMap, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + } else { + render<MAPPER_NoMap, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + } } void CelObj::drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { @@ -668,17 +734,19 @@ void CelObj::drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect, } void CelObj::scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - if (_drawMirrored) + if (_drawMirrored) { render<MAPPER_Map, SCALER_Scale<true, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY); - else + } else { render<MAPPER_Map, SCALER_Scale<false, READER_Compressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + } } void CelObj::scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - if (_drawMirrored) + if (_drawMirrored) { render<MAPPER_Map, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); - else + } else { render<MAPPER_Map, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + } } void CelObj::drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { @@ -713,14 +781,16 @@ void CelObj::scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &sca } void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { - if (_drawMirrored) + if (_drawMirrored) { render<MAPPER_NoMD, SCALER_Scale<true, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); - else + } else { render<MAPPER_NoMD, SCALER_Scale<false, READER_Uncompressed> >(target, targetRect, scaledPosition, scaleX, scaleY); + } } #pragma mark - #pragma mark CelObjView + CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) { _info.type = kCelTypeView; _info.resourceId = viewId; @@ -734,7 +804,11 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int int cacheIndex = searchCache(_info, &cacheInsertIndex); if (cacheIndex != -1) { CelCacheEntry &entry = (*_cache)[cacheIndex]; - *this = *dynamic_cast<CelObjView *>(entry.celObj); + const CelObjView *const cachedCelObj = dynamic_cast<CelObjView *>(entry.celObj); + if (cachedCelObj == nullptr) { + error("Expected a CelObjView in cache slot %d", cacheIndex); + } + *this = *cachedCelObj; entry.id = ++_nextCacheId; return; } @@ -759,8 +833,8 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int if (_scaledWidth == 0 || _scaledHeight == 0) { byte sizeFlag = data[5]; if (sizeFlag == 0) { - _scaledWidth = 320; - _scaledHeight = 200; + _scaledWidth = kLowResX; + _scaledHeight = kLowResY; } else if (sizeFlag == 1) { _scaledWidth = 640; _scaledHeight = 480; @@ -834,8 +908,12 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int bool CelObjView::analyzeUncompressedForRemap() const { byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24); for (int i = 0; i < _width * _height; ++i) { - byte pixel = pixels[i]; - if (pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && pixel != _transparentColor) { + const byte pixel = pixels[i]; + if ( + pixel >= g_sci->_gfxRemap32->getStartColor() && + pixel <= g_sci->_gfxRemap32->getEndColor() && + pixel != _transparentColor + ) { return true; } } @@ -847,8 +925,12 @@ bool CelObjView::analyzeForRemap() const { for (int y = 0; y < _height; y++) { const byte *curRow = reader.getRow(y); for (int x = 0; x < _width; x++) { - byte pixel = curRow[x]; - if (pixel >= g_sci->_gfxRemap32->getStartColor() && pixel <= g_sci->_gfxRemap32->getEndColor() && pixel != _transparentColor) { + const byte pixel = curRow[x]; + if ( + pixel >= g_sci->_gfxRemap32->getStartColor() && + pixel <= g_sci->_gfxRemap32->getEndColor() && + pixel != _transparentColor + ) { return true; } } @@ -866,11 +948,16 @@ CelObjView *CelObjView::duplicate() const { } byte *CelObjView::getResPointer() const { - return g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false)->data; + const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false); + if (resource == nullptr) { + error("Failed to load view %d from resource manager", _info.resourceId); + } + return resource->data; } #pragma mark - #pragma mark CelObjPic + CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { _info.type = kCelTypePic; _info.resourceId = picId; @@ -885,7 +972,11 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { int cacheIndex = searchCache(_info, &cacheInsertIndex); if (cacheIndex != -1) { CelCacheEntry &entry = (*_cache)[cacheIndex]; - *this = *dynamic_cast<CelObjPic *>(entry.celObj); + const CelObjPic *const cachedCelObj = dynamic_cast<CelObjPic *>(entry.celObj); + if (cachedCelObj == nullptr) { + error("Expected a CelObjPic in cache slot %d", cacheIndex); + } + *this = *cachedCelObj; entry.id = ++_nextCacheId; return; } @@ -928,8 +1019,8 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { _scaledWidth = sizeFlag1; _scaledHeight = sizeFlag2; } else if (sizeFlag1 == 0) { - _scaledWidth = 320; - _scaledHeight = 200; + _scaledWidth = kLowResX; + _scaledHeight = kLowResY; } else if (sizeFlag1 == 1) { _scaledWidth = 640; _scaledHeight = 480; @@ -979,11 +1070,16 @@ CelObjPic *CelObjPic::duplicate() const { } byte *CelObjPic::getResPointer() const { - return g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, _info.resourceId), false)->data; + const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, _info.resourceId), false); + if (resource == nullptr) { + error("Failed to load pic %d from resource manager", _info.resourceId); + } + return resource->data; } #pragma mark - #pragma mark CelObjMem + CelObjMem::CelObjMem(const reg_t bitmapObject) { _info.type = kCelTypeMem; _info.bitmap = bitmapObject; @@ -1013,6 +1109,7 @@ byte *CelObjMem::getResPointer() const { #pragma mark - #pragma mark CelObjColor + CelObjColor::CelObjColor(const uint8 color, const int16 width, const int16 height) { _info.type = kCelTypeColor; _info.color = color; diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h index 0bb4b03ae2..eb6ce3a3c9 100644 --- a/engines/sci/graphics/celobj32.h +++ b/engines/sci/graphics/celobj32.h @@ -31,6 +31,20 @@ namespace Sci { typedef Common::Rational Ratio; +// SCI32 has four different coordinate systems: +// 1. low resolution, 2. game/script resolution, +// 3. text/bitmap resolution, 4. screen resolution +// +// In CelObj, these values are used when there is +// no baked in resolution of cels. +// +// In ScreenItem, it is used when deciding which +// path to take to calculate dimensions. +enum { + kLowResX = 320, + kLowResY = 200 +}; + enum CelType { kCelTypeView = 0, kCelTypePic = 1, @@ -214,6 +228,18 @@ class ScreenItem; class CelObj { protected: /** + * When true, every second line of the cel will be + * rendered as a black line. + * + * @see ScreenItem::_drawBlackLines + * @note Using a static member because otherwise this + * would otherwise need to be copied down through + * several calls. (SSCI did similar, using a global + * variable.) + */ + static bool _drawBlackLines; + + /** * When true, this cel will be horizontally mirrored * when it is drawn. This is an internal flag that is * set by draw methods based on the combination of the @@ -474,7 +500,7 @@ private: bool analyzeForRemap() const; public: - CelObjView(GuiResourceId viewId, int16 loopNo, int16 celNo); + CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo); virtual ~CelObjView() override {}; using CelObj::draw; @@ -525,7 +551,7 @@ public: */ int16 _priority; - CelObjPic(GuiResourceId pictureId, int16 celNo); + CelObjPic(const GuiResourceId pictureId, const int16 celNo); virtual ~CelObjPic() override {}; using CelObj::draw; @@ -546,7 +572,7 @@ public: */ class CelObjMem : public CelObj { public: - CelObjMem(reg_t bitmap); + CelObjMem(const reg_t bitmap); virtual ~CelObjMem() override {}; virtual CelObjMem *duplicate() const override; @@ -562,7 +588,7 @@ public: */ class CelObjColor : public CelObj { public: - CelObjColor(uint8 color, int16 width, int16 height); + CelObjColor(const uint8 color, const int16 width, const int16 height); virtual ~CelObjColor() override {}; using CelObj::draw; diff --git a/engines/sci/graphics/compare.cpp b/engines/sci/graphics/compare.cpp index 729eeeaf81..130416ff60 100644 --- a/engines/sci/graphics/compare.cpp +++ b/engines/sci/graphics/compare.cpp @@ -162,12 +162,20 @@ reg_t GfxCompare::kernelCantBeHere32(const reg_t curObject, const reg_t listRefe // rects before operating on them, but this call leverages SCI16 engine // code that operates on inclusive rects, so the rect's bottom-right // point is not modified like in other SCI32 kernel calls - Common::Rect checkRect( - readSelectorValue(_segMan, curObject, SELECTOR(brLeft)), - readSelectorValue(_segMan, curObject, SELECTOR(brTop)), - readSelectorValue(_segMan, curObject, SELECTOR(brRight)), - readSelectorValue(_segMan, curObject, SELECTOR(brBottom)) - ); + Common::Rect checkRect; + + // At least LSL6 hires passes invalid rectangles which trigger the + // isValidRect assertion in the Rect constructor; this is avoided by + // assigning the properties after construction and then testing the + // rect for validity ourselves here. SSCI does not care about whether + // or not the rects are valid + checkRect.left = readSelectorValue(_segMan, curObject, SELECTOR(brLeft)); + checkRect.top = readSelectorValue(_segMan, curObject, SELECTOR(brTop)); + checkRect.right = readSelectorValue(_segMan, curObject, SELECTOR(brRight)); + checkRect.bottom = readSelectorValue(_segMan, curObject, SELECTOR(brBottom)); + if (!checkRect.isValidRect()) { + return make_reg(0, 0); + } uint16 result = 0; uint16 signal = readSelectorValue(_segMan, curObject, SELECTOR(signal)); diff --git a/engines/sci/graphics/controls32.cpp b/engines/sci/graphics/controls32.cpp index a877d8c276..f4e9d26df4 100644 --- a/engines/sci/graphics/controls32.cpp +++ b/engines/sci/graphics/controls32.cpp @@ -21,7 +21,8 @@ */ #include "common/system.h" - +#include "common/translation.h" +#include "gui/message.h" #include "sci/sci.h" #include "sci/console.h" #include "sci/event.h" @@ -41,7 +42,31 @@ GfxControls32::GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *tex _gfxCache(cache), _gfxText32(text), _overwriteMode(false), - _nextCursorFlashTick(0) {} + _nextCursorFlashTick(0), + // SSCI used a memory handle for a ScrollWindow object + // as ID. We use a simple numeric handle instead. + _nextScrollWindowId(10000) {} + +GfxControls32::~GfxControls32() { + ScrollWindowMap::iterator it; + for (it = _scrollWindows.begin(); it != _scrollWindows.end(); ++it) + delete it->_value; +} + +#pragma mark - +#pragma mark Garbage collection + +Common::Array<reg_t> GfxControls32::listObjectReferences() { + Common::Array<reg_t> ret; + ScrollWindowMap::const_iterator it; + for (it = _scrollWindows.begin(); it != _scrollWindows.end(); ++it) + ret.push_back(it->_value->getBitmap()); + + return ret; +} + +#pragma mark - +#pragma mark Text input control reg_t GfxControls32::kernelEditText(const reg_t controlObject) { SegManager *segMan = _segMan; @@ -90,7 +115,10 @@ reg_t GfxControls32::kernelEditText(const reg_t controlObject) { reg_t planeObj = readSelector(_segMan, controlObject, SELECTOR(plane)); Plane *sourcePlane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj); if (sourcePlane == nullptr) { - error("Could not find plane %04x:%04x", PRINT_REG(planeObj)); + sourcePlane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj); + if (sourcePlane == nullptr) { + error("Could not find plane %04x:%04x", PRINT_REG(planeObj)); + } } editorPlaneRect.translate(sourcePlane->_gameRect.left, sourcePlane->_gameRect.top); @@ -104,10 +132,7 @@ reg_t GfxControls32::kernelEditText(const reg_t controlObject) { bool dimmed = readSelectorValue(_segMan, controlObject, SELECTOR(dimmed)); editor.bitmap = _gfxText32->createFontBitmap(width, height, editor.textRect, editor.text, editor.foreColor, editor.backColor, editor.skipColor, editor.fontId, alignment, editor.borderColor, dimmed, true); } else { - Common::String title = _segMan->getString(titleObject); - int16 titleBackColor = readSelectorValue(_segMan, controlObject, SELECTOR(titleBack)); - int16 titleForeColor = readSelectorValue(_segMan, controlObject, SELECTOR(titleFore)); - editor.bitmap = _gfxText32->createTitledBitmap(width, height, editor.textRect, editor.text, editor.foreColor, editor.backColor, editor.skipColor, editor.fontId, alignment, editor.borderColor, title, titleForeColor, titleBackColor, titleFontId, true); + error("Titled bitmaps are not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!"); } } @@ -169,7 +194,8 @@ reg_t GfxControls32::kernelEditText(const reg_t controlObject) { // Consume the event now that we know it is not one of the // defocusing events above - eventManager->getSciEvent(SCI_EVENT_ANY); + if (event.type != SCI_EVENT_NONE) + eventManager->getSciEvent(SCI_EVENT_ANY); // NOTE: In the original engine, the font and bitmap were // reset here on each iteration through the loop, but it @@ -287,8 +313,7 @@ reg_t GfxControls32::kernelEditText(const reg_t controlObject) { g_sci->_gfxFrameout->frameOut(true); g_sci->getSciDebugger()->onFrame(); - g_sci->getEngineState()->speedThrottler(16); - g_sci->getEngineState()->_throttleTrigger = true; + g_sci->_gfxFrameout->throttle(); } g_sci->_gfxFrameout->deletePlane(*plane); @@ -353,4 +378,470 @@ void GfxControls32::flashCursor(TextEditor &editor) { _nextCursorFlashTick = g_sci->getTickCount() + 30; } } + +#pragma mark - +#pragma mark Scrollable window control + +ScrollWindow::ScrollWindow(SegManager *segMan, const Common::Rect &gameRect, const Common::Point &position, const reg_t plane, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries) : + _gfxText32(segMan, g_sci->_gfxCache), + _maxNumEntries(maxNumEntries), + _firstVisibleChar(0), + _topVisibleLine(0), + _lastVisibleChar(0), + _bottomVisibleLine(0), + _numLines(0), + _numVisibleLines(0), + _plane(plane), + _foreColor(defaultForeColor), + _backColor(defaultBackColor), + _borderColor(defaultBorderColor), + _fontId(defaultFontId), + _alignment(defaultAlignment), + _visible(false), + _position(position), + _screenItem(nullptr), + _nextEntryId(1) { + + _entries.reserve(maxNumEntries); + + _gfxText32.setFont(_fontId); + _pointSize = _gfxText32._font->getHeight(); + + const uint16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const uint16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + + Common::Rect bitmapRect(gameRect); + mulinc(bitmapRect, Ratio(_gfxText32._scaledWidth, scriptWidth), Ratio(_gfxText32._scaledHeight, scriptHeight)); + + _textRect.left = 2; + _textRect.top = 2; + _textRect.right = bitmapRect.width() - 2; + _textRect.bottom = bitmapRect.height() - 2; + + uint8 skipColor = 0; + while (skipColor == _foreColor || skipColor == _backColor) { + skipColor++; + } + + assert(bitmapRect.width() > 0 && bitmapRect.height() > 0); + _bitmap = _gfxText32.createFontBitmap(bitmapRect.width(), bitmapRect.height(), _textRect, "", _foreColor, _backColor, skipColor, _fontId, _alignment, _borderColor, false, false); + + debugC(1, kDebugLevelGraphics, "New ScrollWindow: textRect size: %d x %d, bitmap: %04x:%04x", _textRect.width(), _textRect.height(), PRINT_REG(_bitmap)); +} + +ScrollWindow::~ScrollWindow() { + // _gfxText32._bitmap will get GCed once ScrollWindow is gone. + // _screenItem will be deleted by GfxFrameout +} + +Ratio ScrollWindow::where() const { + return Ratio(_topVisibleLine, MAX(_numLines, 1)); +} + +void ScrollWindow::show() { + if (_visible) { + return; + } + + if (_screenItem == nullptr) { + CelInfo32 celInfo; + celInfo.type = kCelTypeMem; + celInfo.bitmap = _bitmap; + + _screenItem = new ScreenItem(_plane, celInfo, _position, ScaleInfo()); + } + + Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(_plane); + plane->_screenItemList.add(_screenItem); + + _visible = true; +} + +void ScrollWindow::hide() { + if (!_visible) { + return; + } + + g_sci->_gfxFrameout->deleteScreenItem(*_screenItem, _plane); + _screenItem = nullptr; + g_sci->_gfxFrameout->frameOut(true); + + _visible = false; +} + +reg_t ScrollWindow::add(const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo) { + if (_entries.size() == _maxNumEntries) { + ScrollWindowEntry removedEntry = _entries.remove_at(0); + _text.erase(0, removedEntry.text.size()); + // `_firstVisibleChar` will be reset shortly if + // `scrollTo` is true, so there is no reason to + // update it + if (!scrollTo) { + _firstVisibleChar -= removedEntry.text.size(); + } + } + + _entries.push_back(ScrollWindowEntry()); + ScrollWindowEntry &entry = _entries.back(); + + // NOTE: In SSCI the line ID was a memory handle for the + // string of this line. We use a numeric ID instead. + entry.id = make_reg(0, _nextEntryId++); + + if (_nextEntryId > _maxNumEntries) { + _nextEntryId = 1; + } + + // NOTE: In SSCI this was updated after _text was + // updated, which meant there was an extra unnecessary + // subtraction operation (subtracting `entry.text` size) + if (scrollTo) { + _firstVisibleChar = _text.size(); + } + + fillEntry(entry, text, fontId, foreColor, alignment); + _text += entry.text; + + computeLineIndices(); + update(true); + + return entry.id; +} + +void ScrollWindow::fillEntry(ScrollWindowEntry &entry, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment) { + entry.alignment = alignment; + entry.foreColor = foreColor; + entry.fontId = fontId; + + Common::String formattedText; + + // NB: There are inconsistencies here. + // If there is a multi-line entry with non-default properties, and it + // is only partially displayed, it may not be displayed right, since the + // property directives are only added to the first line. + // (Verified by trying this in SSCI SQ6 with a custom ScrollWindowAdd call.) + // + // The converse is also a potential issue (but unverified), where lines + // with properties -1 can inherit properties from the previously rendered + // line instead of the defaults. + + // NOTE: SSCI added "|s<lineIndex>|" here, but |s| is + // not a valid control code, so it just always ended up + // getting skipped + if (entry.fontId != -1) { + formattedText += Common::String::format("|f%d|", entry.fontId); + } + if (entry.foreColor != -1) { + formattedText += Common::String::format("|c%d|", entry.foreColor); + } + if (entry.alignment != -1) { + formattedText += Common::String::format("|a%d|", entry.alignment); + } + formattedText += text; + entry.text = formattedText; +} + +reg_t ScrollWindow::modify(const reg_t id, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo) { + + EntriesList::iterator it = _entries.begin(); + uint firstCharLocation = 0; + for ( ; it != _entries.end(); ++it) { + if (it->id == id) { + break; + } + firstCharLocation += it->text.size(); + } + + if (it == _entries.end()) { + return make_reg(0, 0); + } + + ScrollWindowEntry &entry = *it; + + uint oldTextLength = entry.text.size(); + + fillEntry(entry, text, fontId, foreColor, alignment); + _text.replace(firstCharLocation, oldTextLength, entry.text); + + if (scrollTo) { + _firstVisibleChar = firstCharLocation; + } + + computeLineIndices(); + update(true); + + return entry.id; +} + +void ScrollWindow::upArrow() { + if (_topVisibleLine == 0) { + return; + } + + _topVisibleLine--; + _bottomVisibleLine--; + + if (_bottomVisibleLine - _topVisibleLine + 1 < _numVisibleLines) { + _bottomVisibleLine = _numLines - 1; + } + + _firstVisibleChar = _startsOfLines[_topVisibleLine]; + _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1; + + _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1); + + Common::String lineText(_text.c_str() + _startsOfLines[_topVisibleLine], _text.c_str() + _startsOfLines[_topVisibleLine + 1] - 1); + + debugC(3, kDebugLevelGraphics, "ScrollWindow::upArrow: top: %d, bottom: %d, num: %d, numvis: %d, lineText: %s", _topVisibleLine, _bottomVisibleLine, _numLines, _numVisibleLines, lineText.c_str()); + + _gfxText32.scrollLine(lineText, _numVisibleLines, _foreColor, _alignment, _fontId, kScrollUp); + + if (_visible) { + assert(_screenItem); + + _screenItem->update(); + g_sci->_gfxFrameout->frameOut(true); + } +} + +void ScrollWindow::downArrow() { + if (_topVisibleLine + 1 >= _numLines) { + return; + } + + _topVisibleLine++; + _bottomVisibleLine++; + + if (_bottomVisibleLine + 1 >= _numLines) { + _bottomVisibleLine = _numLines - 1; + } + + _firstVisibleChar = _startsOfLines[_topVisibleLine]; + _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1; + + _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1); + + Common::String lineText; + if (_bottomVisibleLine - _topVisibleLine + 1 == _numVisibleLines) { + lineText = Common::String(_text.c_str() + _startsOfLines[_bottomVisibleLine], _text.c_str() + _startsOfLines[_bottomVisibleLine + 1] - 1); + } else { + // scroll in empty string + } + + debugC(3, kDebugLevelGraphics, "ScrollWindow::downArrow: top: %d, bottom: %d, num: %d, numvis: %d, lineText: %s", _topVisibleLine, _bottomVisibleLine, _numLines, _numVisibleLines, lineText.c_str()); + + + _gfxText32.scrollLine(lineText, _numVisibleLines, _foreColor, _alignment, _fontId, kScrollDown); + + if (_visible) { + assert(_screenItem); + + _screenItem->update(); + g_sci->_gfxFrameout->frameOut(true); + } +} + +void ScrollWindow::go(const Ratio location) { + const int line = (location * _numLines).toInt(); + if (line < 0 || line > _numLines) { + error("Index is Out of Range in ScrollWindow"); + } + + _firstVisibleChar = _startsOfLines[line]; + update(true); + + // HACK: + // It usually isn't possible to set _topVisibleLine >= _numLines, and so + // update() doesn't. However, in this case we should set _topVisibleLine + // past the end. This is clearly visible in Phantasmagoria when dragging + // the slider in the About dialog to the very end. The slider ends up lower + // than where it can be moved by scrolling down with the arrows. + if (location.isOne()) { + _topVisibleLine = _numLines; + } +} + +void ScrollWindow::home() { + if (_firstVisibleChar == 0) { + return; + } + + _firstVisibleChar = 0; + update(true); +} + +void ScrollWindow::end() { + if (_bottomVisibleLine + 1 >= _numLines) { + return; + } + + int line = _numLines - _numVisibleLines; + if (line < 0) { + line = 0; + } + _firstVisibleChar = _startsOfLines[line]; + update(true); +} + +void ScrollWindow::pageUp() { + if (_topVisibleLine == 0) { + return; + } + + _topVisibleLine -= _numVisibleLines; + if (_topVisibleLine < 0) { + _topVisibleLine = 0; + } + + _firstVisibleChar = _startsOfLines[_topVisibleLine]; + update(true); +} + +void ScrollWindow::pageDown() { + if (_topVisibleLine + 1 >= _numLines) { + return; + } + + _topVisibleLine += _numVisibleLines; + if (_topVisibleLine + 1 >= _numLines) { + _topVisibleLine = _numLines - 1; + } + + _firstVisibleChar = _startsOfLines[_topVisibleLine]; + update(true); +} + +void ScrollWindow::computeLineIndices() { + _gfxText32.setFont(_fontId); + // NOTE: Unlike SSCI, foreColor and alignment are not + // set since these properties do not affect the width of + // lines + + if (_gfxText32._font->getHeight() != _pointSize) { + error("Illegal font size font = %d pointSize = %d, should be %d.", _fontId, _gfxText32._font->getHeight(), _pointSize); + } + + Common::Rect lineRect(0, 0, _textRect.width(), _pointSize + 3); + + _startsOfLines.clear(); + + // NOTE: The original engine had a 1000-line limit; we + // do not enforce any limit + for (uint charIndex = 0; charIndex < _text.size(); ) { + _startsOfLines.push_back(charIndex); + charIndex += _gfxText32.getTextCount(_text, charIndex, lineRect, false); + } + + _numLines = _startsOfLines.size(); + + _startsOfLines.push_back(_text.size()); + + _lastVisibleChar = _gfxText32.getTextCount(_text, 0, _fontId, _textRect, false) - 1; + + _bottomVisibleLine = 0; + while ( + _bottomVisibleLine < _numLines - 1 && + _startsOfLines[_bottomVisibleLine + 1] < _lastVisibleChar + ) { + ++_bottomVisibleLine; + } + + _numVisibleLines = _bottomVisibleLine + 1; +} + +void ScrollWindow::update(const bool doFrameOut) { + _topVisibleLine = 0; + while ( + _topVisibleLine < _numLines - 1 && + _firstVisibleChar >= _startsOfLines[_topVisibleLine + 1] + ) { + ++_topVisibleLine; + } + + _bottomVisibleLine = _topVisibleLine + _numVisibleLines - 1; + if (_bottomVisibleLine >= _numLines) { + _bottomVisibleLine = _numLines - 1; + } + + _firstVisibleChar = _startsOfLines[_topVisibleLine]; + + if (_bottomVisibleLine >= 0) { + _lastVisibleChar = _startsOfLines[_bottomVisibleLine + 1] - 1; + } else { + _lastVisibleChar = -1; + } + + _visibleText = Common::String(_text.c_str() + _firstVisibleChar, _text.c_str() + _lastVisibleChar + 1); + + _gfxText32.erase(_textRect, false); + _gfxText32.drawTextBox(_visibleText); + + if (_visible) { + assert(_screenItem); + + _screenItem->update(); + if (doFrameOut) { + g_sci->_gfxFrameout->frameOut(true); + } + } +} + +reg_t GfxControls32::makeScrollWindow(const Common::Rect &gameRect, const Common::Point &position, const reg_t planeObj, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries) { + + ScrollWindow *scrollWindow = new ScrollWindow(_segMan, gameRect, position, planeObj, defaultForeColor, defaultBackColor, defaultFontId, defaultAlignment, defaultBorderColor, maxNumEntries); + + const uint16 id = _nextScrollWindowId++; + _scrollWindows[id] = scrollWindow; + return make_reg(0, id); +} + +ScrollWindow *GfxControls32::getScrollWindow(const reg_t id) { + ScrollWindowMap::iterator it; + it = _scrollWindows.find(id.toUint16()); + if (it == _scrollWindows.end()) + error("Invalid ScrollWindow ID"); + + return it->_value; +} + +void GfxControls32::destroyScrollWindow(const reg_t id) { + ScrollWindow *scrollWindow = getScrollWindow(id); + scrollWindow->hide(); + _scrollWindows.erase(id.getOffset()); + delete scrollWindow; +} + +#pragma mark - +#pragma mark Message box + +int16 GfxControls32::showMessageBox(const Common::String &message, const char *const okLabel, const char *const altLabel, const int16 okValue, const int16 altValue) { + GUI::MessageDialog dialog(message, okLabel, altLabel); + return (dialog.runModal() == GUI::kMessageOK) ? okValue : altValue; +} + +reg_t GfxControls32::kernelMessageBox(const Common::String &message, const Common::String &title, const uint16 style) { + if (g_engine) { + g_engine->pauseEngine(true); + } + + int16 result; + + switch (style & 0xF) { + case kMessageBoxOK: + result = showMessageBox(message, _("OK"), NULL, 1, 1); + break; + case kMessageBoxYesNo: + result = showMessageBox(message, _("Yes"), _("No"), 6, 7); + break; + default: + error("Unsupported MessageBox style 0x%x", style & 0xF); + } + + if (g_engine) { + g_engine->pauseEngine(false); + } + + return make_reg(0, result); +} + } // End of namespace Sci diff --git a/engines/sci/graphics/controls32.h b/engines/sci/graphics/controls32.h index 1bb7679ddd..460b0b5625 100644 --- a/engines/sci/graphics/controls32.h +++ b/engines/sci/graphics/controls32.h @@ -23,12 +23,19 @@ #ifndef SCI_GRAPHICS_CONTROLS32_H #define SCI_GRAPHICS_CONTROLS32_H +#include "sci/graphics/text32.h" + namespace Sci { class GfxCache; class GfxScreen; class GfxText32; +enum MessageBoxStyle { + kMessageBoxOK = 0x0, + kMessageBoxYesNo = 0x4 +}; + struct TextEditor { /** * The bitmap where the editor is rendered. @@ -100,24 +107,403 @@ struct TextEditor { }; /** + * A single block of text written to a ScrollWindow. + */ +struct ScrollWindowEntry { + /** + * ID of the line. In SSCI this was actually a memory + * handle for the string of this line. We use a simple + * numeric ID instead. + */ + reg_t id; + + /** + * The alignment to use when rendering this line of + * text. If -1, the default alignment from the + * corresponding ScrollWindow will be used. + */ + TextAlign alignment; + + /** + * The color to use to render this line of text. If -1, + * the default foreground color from the corresponding + * ScrollWindow will be used. + */ + int16 foreColor; + + /** + * The font to use to render this line of text. If -1, + * the default font from the corresponding ScrollWindow + * will be used. + */ + GuiResourceId fontId; + + /** + * The text. + */ + Common::String text; +}; + +class ScreenItem; + +/** + * A scrollable text window. + */ +class ScrollWindow { +public: + ScrollWindow(SegManager *segMan, const Common::Rect &gameRect, const Common::Point &position, const reg_t planeObj, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries); + ~ScrollWindow(); + + /** + * Adds a new text entry to the window. If `fontId`, + * `foreColor`, or `alignment` are `-1`, the + * ScrollWindow's default values will be used. + */ + reg_t add(const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo); + + /** + * Modifies an existing text entry with the given ID. If + * `fontId`, `foreColor`, or `alignment` are `-1`, the + * ScrollWindow's default values will be used. + */ + reg_t modify(const reg_t id, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment, const bool scrollTo); + + /** + * Shows the ScrollWindow if it is not already visible. + */ + void show(); + + /** + * Hides the ScrollWindow if it is currently visible. + */ + void hide(); + + /** + * Gets the number of lines that the content of a + * ScrollWindow is scrolled upward, as a ratio of the + * total number of lines of content. + */ + Ratio where() const; + + /** + * Scrolls the window to a specific location. + */ + void go(const Ratio location); + + /** + * Scrolls the window to the top. + */ + void home(); + + /** + * Scrolls the window to the bottom. + */ + void end(); + + /** + * Scrolls the window up one line. + */ + void upArrow(); + + /** + * Scrolls the window down one line. + */ + void downArrow(); + + /** + * Scrolls the window up by one page. + */ + void pageUp(); + + /** + * Scrolls the window down by one page. + */ + void pageDown(); + + /** + * Gets a reference to the in-memory bitmap that + * is used to render the text in the ScrollWindow. + */ + const reg_t getBitmap() const { return _bitmap; } + +private: + typedef Common::Array<ScrollWindowEntry> EntriesList; + + /** + * A convenience function that fills a + * ScrollWindowEntry's properties. + */ + void fillEntry(ScrollWindowEntry &entry, const Common::String &text, const GuiResourceId fontId, const int16 foreColor, const TextAlign alignment); + + /** + * Rescans the entire text of the ScrollWindow when an + * entry is added or modified, calculating the character + * offsets of all line endings, the total number of + * lines of text, the height of the viewport (in lines + * of text), the last character visible in the viewport + * (assuming the viewport is scrolled to the top), and + * the line index of the bottommost visible line + * (assuming the viewport is scrolled to the top). + */ + void computeLineIndices(); + + /** + * Calculates which text is visible within the + * ScrollWindow's viewport and renders the text to the + * internal bitmap. + * + * If `doFrameOut` is true, the screen will be refreshed + * immediately instead of waiting for the next call to + * `kFrameOut`. + */ + void update(const bool doFrameOut); + + /** + * The text renderer. + */ + GfxText32 _gfxText32; + + /** + * The individual text entries added to the + * ScrollWindow. + */ + EntriesList _entries; + + /** + * The maximum number of entries allowed. Once this + * limit is reached, the oldest entry will be removed + * when a new entry is added. + */ + uint _maxNumEntries; + + /** + * A mapping from a line index to the line's character + * offset in `_text`. + */ + Common::Array<int> _startsOfLines; + + /** + * All text added to the window. + */ + Common::String _text; + + /** + * Text that is within the viewport of the ScrollWindow. + */ + Common::String _visibleText; + + /** + * The offset of the first visible character in `_text`. + */ + int _firstVisibleChar; + + /** + * The index of the line that is at the top of the + * viewport. + */ + int _topVisibleLine; + + /** + * The index of the last visible character in `_text`, + * or -1 if there is no text. + */ + int _lastVisibleChar; + + /** + * The index of the line that is at the bottom of the + * viewport, or -1 if there is no text. + */ + int _bottomVisibleLine; + + /** + * The total number of lines in the backbuffer. This + * number may be higher than the total number of entries + * if an entry contains newlines. + */ + int _numLines; + + /** + * The number of lines that are currently visible in the + * text area of the window. + */ + int _numVisibleLines; + + /** + * The plane in which the ScrollWindow should be + * rendered. + */ + reg_t _plane; + + /** + * The default text color. + */ + uint8 _foreColor; + + /** + * The default background color of the text bitmap. + */ + uint8 _backColor; + + /** + * The default border color of the text bitmap. If -1, + * the viewport will have no border. + */ + int16 _borderColor; + + /** + * The default font used for rendering text into the + * ScrollWindow. + */ + GuiResourceId _fontId; + + /** + * The default text alignment used for rendering text + * into the ScrollWindow. + */ + TextAlign _alignment; + + /** + * The visibility of the ScrollWindow. + */ + bool _visible; + + /** + * The dimensions of the text box inside the font + * bitmap, in text-system coordinates. + */ + Common::Rect _textRect; + + /** + * The top-left corner of the ScrollWindow's screen + * item, in game script coordinates, relative to the + * parent plane. + */ + Common::Point _position; + + /** + * The height of the default font in screen pixels. All + * fonts rendered into the ScrollWindow must have this + * same height. + */ + uint8 _pointSize; + + /** + * The bitmap used to render text. + */ + reg_t _bitmap; + + /** + * A monotonically increasing ID used to identify + * text entries added to the ScrollWindow. + */ + uint16 _nextEntryId; + + /** + * The ScrollWindow's screen item. + */ + ScreenItem *_screenItem; +}; + +/** * Controls class, handles drawing of controls in SCI32 (SCI2, SCI2.1, SCI3) games */ class GfxControls32 { public: GfxControls32(SegManager *segMan, GfxCache *cache, GfxText32 *text); - - reg_t kernelEditText(const reg_t controlObject); + ~GfxControls32(); private: SegManager *_segMan; GfxCache *_gfxCache; GfxText32 *_gfxText32; +#pragma mark - +#pragma mark Garbage collection +public: + Common::Array<reg_t> listObjectReferences(); + +#pragma mark - +#pragma mark Text input control +public: + reg_t kernelEditText(const reg_t controlObject); + +private: + /** + * If true, typing will overwrite text that already + * exists at the text cursor's current position. + */ bool _overwriteMode; + + /** + * The tick at which the text cursor should be toggled + * by `flashCursor`. + */ uint32 _nextCursorFlashTick; + + /** + * Draws the text cursor for the given editor. + */ void drawCursor(TextEditor &editor); + + /** + * Erases the text cursor for the given editor. + */ void eraseCursor(TextEditor &editor); + + /** + * Toggles the text cursor for the given editor to be + * either drawn or erased. + */ void flashCursor(TextEditor &editor); + +#pragma mark - +#pragma mark Scrollable window control +public: + /** + * Creates a new scrollable window and returns the ID + * for the new window, which is used by game scripts to + * interact with scrollable windows. + */ + reg_t makeScrollWindow(const Common::Rect &gameRect, const Common::Point &position, const reg_t plane, const uint8 defaultForeColor, const uint8 defaultBackColor, const GuiResourceId defaultFontId, const TextAlign defaultAlignment, const int16 defaultBorderColor, const uint16 maxNumEntries); + + /** + * Gets a registered ScrollWindow instance by ID. + */ + ScrollWindow *getScrollWindow(const reg_t id); + + /** + * Destroys the scroll window with the given ID. + */ + void destroyScrollWindow(const reg_t id); + +private: + typedef Common::HashMap<uint16, ScrollWindow *> ScrollWindowMap; + + /** + * Monotonically increasing ID used to identify + * ScrollWindow instances. + */ + uint16 _nextScrollWindowId; + + /** + * A lookup table for registered ScrollWindow instances. + */ + ScrollWindowMap _scrollWindows; + +#pragma mark - +#pragma mark Message box +public: + /** + * Displays an OS-level message dialog. + */ + reg_t kernelMessageBox(const Common::String &message, const Common::String &title, const uint16 style); + +private: + /** + * Convenience function for creating and showing a + * message box. + */ + int16 showMessageBox(const Common::String &message, const char *const okLabel, const char *const altLabel, const int16 okValue, const int16 altValue); }; } // End of namespace Sci diff --git a/engines/sci/graphics/cursor.h b/engines/sci/graphics/cursor.h index 8d125c45b3..5125469cfe 100644 --- a/engines/sci/graphics/cursor.h +++ b/engines/sci/graphics/cursor.h @@ -25,6 +25,8 @@ #include "common/array.h" #include "common/hashmap.h" +#include "sci/sci.h" +#include "sci/graphics/helpers.h" namespace Sci { diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp index 6454a1eb32..b5b320637f 100644 --- a/engines/sci/graphics/frameout.cpp +++ b/engines/sci/graphics/frameout.cpp @@ -42,15 +42,13 @@ #include "sci/graphics/coordadjuster.h" #include "sci/graphics/compare.h" #include "sci/graphics/font.h" -#include "sci/graphics/view.h" #include "sci/graphics/screen.h" #include "sci/graphics/paint32.h" #include "sci/graphics/palette32.h" -#include "sci/graphics/picture.h" -#include "sci/graphics/remap.h" -#include "sci/graphics/text32.h" #include "sci/graphics/plane32.h" +#include "sci/graphics/remap32.h" #include "sci/graphics/screen_item32.h" +#include "sci/graphics/text32.h" #include "sci/graphics/frameout.h" #include "sci/video/robot_decoder.h" @@ -69,15 +67,16 @@ static int16 unknownCDefaults[2][16] = { /* SCI2.1mid+ */ { 0, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 0, 0, 7, 7, 0 } }; -GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32) : +GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette) : _isHiRes(false), - _cache(cache), _palette(palette), _resMan(resMan), _screen(screen), _segMan(segMan), - _paint32(paint32), + _benchmarkingFinished(false), + _throttleFrameOut(true), _showStyles(nullptr), + _throttleState(0), // TODO: Stop using _gfxScreen _currentBuffer(screen->getDisplayWidth(), screen->getDisplayHeight(), nullptr), _remapOccurred(false), @@ -116,6 +115,7 @@ GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAd } switch (g_sci->getGameId()) { + case GID_HOYLE5: case GID_GK2: case GID_LIGHTHOUSE: case GID_LSL7: @@ -255,9 +255,87 @@ void GfxFrameout::syncWithScripts(bool addElements) { } #pragma mark - +#pragma mark Benchmarking + +bool GfxFrameout::checkForFred(const reg_t object) { + const int16 viewId = readSelectorValue(_segMan, object, SELECTOR(view)); + const SciGameId gameId = g_sci->getGameId(); + + if (gameId == GID_QFG4 && viewId == 9999) { + return true; + } + + if (gameId != GID_QFG4 && viewId == -556) { + return true; + } + + if (Common::String(_segMan->getObjectName(object)) == "fred") { + return true; + } + + return false; +} + +#pragma mark - #pragma mark Screen items +void GfxFrameout::addScreenItem(ScreenItem &screenItem) const { + Plane *plane = _planes.findByObject(screenItem._plane); + if (plane == nullptr) { + error("GfxFrameout::addScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(screenItem._plane), PRINT_REG(screenItem._object)); + } + plane->_screenItemList.add(&screenItem); +} + +void GfxFrameout::updateScreenItem(ScreenItem &screenItem) const { + // TODO: In SCI3+ this will need to go through Plane +// Plane *plane = _planes.findByObject(screenItem._plane); +// if (plane == nullptr) { +// error("GfxFrameout::updateScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(screenItem._plane), PRINT_REG(screenItem._object)); +// } + + screenItem.update(); +} + +void GfxFrameout::deleteScreenItem(ScreenItem &screenItem) { + Plane *plane = _planes.findByObject(screenItem._plane); + if (plane == nullptr) { + error("GfxFrameout::deleteScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(screenItem._plane), PRINT_REG(screenItem._object)); + } + if (plane->_screenItemList.findByObject(screenItem._object) == nullptr) { + error("GfxFrameout::deleteScreenItem: Screen item %04x:%04x not found in plane %04x:%04x", PRINT_REG(screenItem._object), PRINT_REG(screenItem._plane)); + } + deleteScreenItem(screenItem, *plane); +} + +void GfxFrameout::deleteScreenItem(ScreenItem &screenItem, Plane &plane) { + if (screenItem._created == 0) { + screenItem._created = 0; + screenItem._updated = 0; + screenItem._deleted = getScreenCount(); + } else { + plane._screenItemList.erase(&screenItem); + plane._screenItemList.pack(); + } +} + +void GfxFrameout::deleteScreenItem(ScreenItem &screenItem, const reg_t planeObject) { + Plane *plane = _planes.findByObject(planeObject); + if (plane == nullptr) { + error("GfxFrameout::deleteScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(screenItem._object)); + } + deleteScreenItem(screenItem, *plane); +} + void GfxFrameout::kernelAddScreenItem(const reg_t object) { + // The "fred" object is used to test graphics performance; + // it is impacted by framerate throttling, so disable the + // throttling when this item is on the screen for the + // performance check to pass. + if (!_benchmarkingFinished && _throttleFrameOut && checkForFred(object)) { + _throttleFrameOut = false; + } + const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane)); _segMan->getObject(object)->setInfoSelectorFlag(kInfoFlagViewInserted); @@ -297,6 +375,15 @@ void GfxFrameout::kernelUpdateScreenItem(const reg_t object) { } void GfxFrameout::kernelDeleteScreenItem(const reg_t object) { + // The "fred" object is used to test graphics performance; + // it is impacted by framerate throttling, so disable the + // throttling when this item is on the screen for the + // performance check to pass. + if (!_benchmarkingFinished && checkForFred(object)) { + _benchmarkingFinished = true; + _throttleFrameOut = true; + } + _segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewInserted); const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane)); @@ -310,14 +397,7 @@ void GfxFrameout::kernelDeleteScreenItem(const reg_t object) { return; } - if (screenItem->_created == 0) { - screenItem->_created = 0; - screenItem->_updated = 0; - screenItem->_deleted = getScreenCount(); - } else { - plane->_screenItemList.erase(screenItem); - plane->_screenItemList.pack(); - } + deleteScreenItem(*screenItem, *plane); } #pragma mark - @@ -446,7 +526,7 @@ void GfxFrameout::kernelAddPicAt(const reg_t planeObject, const GuiResourceId pi #pragma mark - #pragma mark Rendering -void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect) { +void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &eraseRect) { // TODO: Robot // if (_robot != nullptr) { // _robot.doRobot(); @@ -464,7 +544,7 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect) remapMarkRedraw(); } - calcLists(screenItemLists, eraseLists, rect); + calcLists(screenItemLists, eraseLists, eraseRect); for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { list->sort(); @@ -493,7 +573,7 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect) // _robot->frameAlmostVisible(); // } - _palette->updateHardware(); + _palette->updateHardware(!shouldShowBits); if (shouldShowBits) { showBits(); @@ -507,130 +587,221 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect) // } } -// Determine the parts of 'r' that aren't overlapped by 'other'. -// Returns -1 if r and other have no intersection. -// Returns number of returned parts (in outRects) otherwise. -// (In particular, this returns 0 if r is contained in other.) +/** + * Determines the parts of `r` that aren't overlapped by `other`. + * Returns -1 if `r` and `other` have no intersection. + * Returns number of returned parts (in `outRects`) otherwise. + * (In particular, this returns 0 if `r` is contained in `other`.) + */ int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]) { if (!r.intersects(other)) { return -1; } - int count = 0; + int splitCount = 0; if (r.top < other.top) { - Common::Rect &t = outRects[count++]; + Common::Rect &t = outRects[splitCount++]; t = r; t.bottom = other.top; r.top = other.top; } if (r.bottom > other.bottom) { - Common::Rect &t = outRects[count++]; + Common::Rect &t = outRects[splitCount++]; t = r; t.top = other.bottom; r.bottom = other.bottom; } if (r.left < other.left) { - Common::Rect &t = outRects[count++]; + Common::Rect &t = outRects[splitCount++]; t = r; t.right = other.left; r.left = other.left; } if (r.right > other.right) { - Common::Rect &t = outRects[count++]; + Common::Rect &t = outRects[splitCount++]; t = r; t.left = other.right; } - return count; + return splitCount; } -void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect) { - RectList rectlist; - Common::Rect outRects[4]; +/** + * Determines the parts of `middleRect` that aren't overlapped + * by `showRect`, optimised for contiguous memory writes. + * Returns -1 if `middleRect` and `showRect` have no intersection. + * Returns number of returned parts (in `outRects`) otherwise. + * (In particular, this returns 0 if `middleRect` is contained + * in `other`.) + * + * `middleRect` is modified directly to extend into the upper + * and lower rects. + */ +int splitRectsForRender(Common::Rect &middleRect, const Common::Rect &showRect, Common::Rect(&outRects)[2]) { + if (!middleRect.intersects(showRect)) { + return -1; + } + + const int16 minLeft = MIN(middleRect.left, showRect.left); + const int16 maxRight = MAX(middleRect.right, showRect.right); + + int16 upperLeft, upperTop, upperRight, upperMaxTop; + if (middleRect.top < showRect.top) { + upperLeft = middleRect.left; + upperTop = middleRect.top; + upperRight = middleRect.right; + upperMaxTop = showRect.top; + } + else { + upperLeft = showRect.left; + upperTop = showRect.top; + upperRight = showRect.right; + upperMaxTop = middleRect.top; + } + + int16 lowerLeft, lowerRight, lowerBottom, lowerMinBottom; + if (middleRect.bottom > showRect.bottom) { + lowerLeft = middleRect.left; + lowerRight = middleRect.right; + lowerBottom = middleRect.bottom; + lowerMinBottom = showRect.bottom; + } else { + lowerLeft = showRect.left; + lowerRight = showRect.right; + lowerBottom = showRect.bottom; + lowerMinBottom = middleRect.bottom; + } + + int splitCount = 0; + middleRect.left = minLeft; + middleRect.top = upperMaxTop; + middleRect.right = maxRight; + middleRect.bottom = lowerMinBottom; + + if (upperTop != upperMaxTop) { + Common::Rect &upperRect = outRects[0]; + upperRect.left = upperLeft; + upperRect.top = upperTop; + upperRect.right = upperRight; + upperRect.bottom = upperMaxTop; + + // Merge upper rect into middle rect if possible + if (upperRect.left == middleRect.left && upperRect.right == middleRect.right) { + middleRect.top = upperRect.top; + } else { + ++splitCount; + } + } + if (lowerBottom != lowerMinBottom) { + Common::Rect &lowerRect = outRects[splitCount]; + lowerRect.left = lowerLeft; + lowerRect.top = lowerMinBottom; + lowerRect.right = lowerRight; + lowerRect.bottom = lowerBottom; + + // Merge lower rect into middle rect if possible + if (lowerRect.left == middleRect.left && lowerRect.right == middleRect.right) { + middleRect.bottom = lowerRect.bottom; + } else { + ++splitCount; + } + } + + assert(splitCount <= 2); + return splitCount; +} + +// NOTE: The third rectangle parameter is only ever given a non-empty rect +// by VMD code, via `frameOut` +void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &eraseRect) { + RectList eraseList; + Common::Rect outRects[4]; int deletedPlaneCount = 0; - bool addedToRectList = false; - int planeCount = _planes.size(); + bool addedToEraseList = false; bool foundTransparentPlane = false; - if (!calcRect.isEmpty()) { - addedToRectList = true; - rectlist.add(calcRect); + if (!eraseRect.isEmpty()) { + addedToEraseList = true; + eraseList.add(eraseRect); } - for (int outerPlaneIndex = 0; outerPlaneIndex < planeCount; ++outerPlaneIndex) { - Plane *outerPlane = _planes[outerPlaneIndex]; + PlaneList::size_type planeCount = _planes.size(); + for (PlaneList::size_type outerPlaneIndex = 0; outerPlaneIndex < planeCount; ++outerPlaneIndex) { + const Plane *outerPlane = _planes[outerPlaneIndex]; + const Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object); + // NOTE: SSCI only ever checks for kPlaneTypeTransparent here, even + // though kPlaneTypeTransparentPicture is also a transparent plane if (outerPlane->_type == kPlaneTypeTransparent) { foundTransparentPlane = true; } - Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object); - if (outerPlane->_deleted) { - if (visiblePlane != nullptr) { - if (!visiblePlane->_screenRect.isEmpty()) { - addedToRectList = true; - rectlist.add(visiblePlane->_screenRect); - } + if (visiblePlane != nullptr && !visiblePlane->_screenRect.isEmpty()) { + eraseList.add(visiblePlane->_screenRect); + addedToEraseList = true; } ++deletedPlaneCount; - } else if (visiblePlane != nullptr) { - if (outerPlane->_updated) { - --outerPlane->_updated; - - int splitcount = splitRects(visiblePlane->_screenRect, outerPlane->_screenRect, outRects); - if (splitcount) { - if (splitcount == -1) { - if (!visiblePlane->_screenRect.isEmpty()) { - rectlist.add(visiblePlane->_screenRect); - } + } else if (visiblePlane != nullptr && outerPlane->_moved) { + // _moved will be decremented in the final loop through the planes, + // at the end of this function + + { + const int splitCount = splitRects(visiblePlane->_screenRect, outerPlane->_screenRect, outRects); + if (splitCount) { + if (splitCount == -1 && !visiblePlane->_screenRect.isEmpty()) { + eraseList.add(visiblePlane->_screenRect); } else { - for (int i = 0; i < splitcount; ++i) { - rectlist.add(outRects[i]); + for (int i = 0; i < splitCount; ++i) { + eraseList.add(outRects[i]); } } - - addedToRectList = true; + addedToEraseList = true; } + } - if (!outerPlane->_redrawAllCount) { - int splitCount = splitRects(outerPlane->_screenRect, visiblePlane->_screenRect, outRects); - if (splitCount) { - for (int i = 0; i < splitCount; ++i) { - rectlist.add(outRects[i]); - } - addedToRectList = true; + if (!outerPlane->_redrawAllCount) { + const int splitCount = splitRects(outerPlane->_screenRect, visiblePlane->_screenRect, outRects); + if (splitCount) { + for (int i = 0; i < splitCount; ++i) { + eraseList.add(outRects[i]); } + addedToEraseList = true; } } } - if (addedToRectList) { - for (RectList::iterator rect = rectlist.begin(); rect != rectlist.end(); ++rect) { - for (int innerPlaneIndex = _planes.size() - 1; innerPlaneIndex >= 0; --innerPlaneIndex) { - Plane *innerPlane = _planes[innerPlaneIndex]; - - if (!innerPlane->_deleted && innerPlane->_type != kPlaneTypeTransparent && innerPlane->_screenRect.intersects(**rect)) { - if (innerPlane->_redrawAllCount == 0) { - eraseLists[innerPlaneIndex].add(innerPlane->_screenRect.findIntersectingRect(**rect)); + if (addedToEraseList) { + for (RectList::size_type rectIndex = 0; rectIndex < eraseList.size(); ++rectIndex) { + const Common::Rect &rect = *eraseList[rectIndex]; + for (int innerPlaneIndex = planeCount - 1; innerPlaneIndex >= 0; --innerPlaneIndex) { + const Plane &innerPlane = *_planes[innerPlaneIndex]; + + if ( + !innerPlane._deleted && + innerPlane._type != kPlaneTypeTransparent && + innerPlane._screenRect.intersects(rect) + ) { + if (!innerPlane._redrawAllCount) { + eraseLists[innerPlaneIndex].add(innerPlane._screenRect.findIntersectingRect(rect)); } - int splitCount = splitRects(**rect, innerPlane->_screenRect, outRects); + const int splitCount = splitRects(rect, innerPlane._screenRect, outRects); for (int i = 0; i < splitCount; ++i) { - rectlist.add(outRects[i]); + eraseList.add(outRects[i]); } - rectlist.erase(rect); + eraseList.erase_at(rectIndex); break; } } } - rectlist.pack(); + eraseList.pack(); } } @@ -642,9 +813,9 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL if (plane->_deleted) { --plane->_deleted; if (plane->_deleted <= 0) { - PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object)); - if (visiblePlaneIt != _visiblePlanes.end()) { - _visiblePlanes.erase(visiblePlaneIt); + const int visiblePlaneIndex = _visiblePlanes.findIndexByObject(plane->_object); + if (visiblePlaneIndex != -1) { + _visiblePlanes.remove_at(visiblePlaneIndex); } _planes.remove_at(planeIndex); @@ -659,103 +830,114 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL } } + // Some planes may have been deleted, so re-retrieve count planeCount = _planes.size(); - for (int outerIndex = 0; outerIndex < planeCount; ++outerIndex) { - // "outer" just refers to the outer loop - Plane *outerPlane = _planes[outerIndex]; - if (outerPlane->_priorityChanged) { - --outerPlane->_priorityChanged; - Plane *visibleOuterPlane = _visiblePlanes.findByObject(outerPlane->_object); + for (PlaneList::size_type outerIndex = 0; outerIndex < planeCount; ++outerIndex) { + // "outer" just refers to the outer loop + Plane &outerPlane = *_planes[outerIndex]; + if (outerPlane._priorityChanged) { + --outerPlane._priorityChanged; + + const Plane *visibleOuterPlane = _visiblePlanes.findByObject(outerPlane._object); + if (visibleOuterPlane == nullptr) { + warning("calcLists could not find visible plane for %04x:%04x", PRINT_REG(outerPlane._object)); + continue; + } - rectlist.add(outerPlane->_screenRect.findIntersectingRect(visibleOuterPlane->_screenRect)); + eraseList.add(outerPlane._screenRect.findIntersectingRect(visibleOuterPlane->_screenRect)); - for (int innerIndex = planeCount - 1; innerIndex >= 0; --innerIndex) { + for (int innerIndex = (int)planeCount - 1; innerIndex >= 0; --innerIndex) { // "inner" just refers to the inner loop - Plane *innerPlane = _planes[innerIndex]; - Plane *visibleInnerPlane = _visiblePlanes.findByObject(innerPlane->_object); - - int rectCount = rectlist.size(); - for (int rectIndex = 0; rectIndex < rectCount; ++rectIndex) { - int splitCount = splitRects(*rectlist[rectIndex], _planes[innerIndex]->_screenRect, outRects); + const Plane &innerPlane = *_planes[innerIndex]; + const Plane *visibleInnerPlane = _visiblePlanes.findByObject(innerPlane._object); + const RectList::size_type rectCount = eraseList.size(); + for (RectList::size_type rectIndex = 0; rectIndex < rectCount; ++rectIndex) { + const int splitCount = splitRects(*eraseList[rectIndex], innerPlane._screenRect, outRects); if (splitCount == 0) { if (visibleInnerPlane != nullptr) { // same priority, or relative priority between inner/outer changed - if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) { - if (outerPlane->_priority <= innerPlane->_priority) { - eraseLists[innerIndex].add(*rectlist[rectIndex]); + if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane._priority - innerPlane._priority) <= 0) { + if (outerPlane._priority <= innerPlane._priority) { + eraseLists[innerIndex].add(*eraseList[rectIndex]); } else { - eraseLists[outerIndex].add(*rectlist[rectIndex]); + eraseLists[outerIndex].add(*eraseList[rectIndex]); } } } - rectlist.erase_at(rectIndex); + eraseList.erase_at(rectIndex); } else if (splitCount != -1) { for (int i = 0; i < splitCount; ++i) { - rectlist.add(outRects[i]); + eraseList.add(outRects[i]); } if (visibleInnerPlane != nullptr) { // same priority, or relative priority between inner/outer changed - if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane->_priority - innerPlane->_priority) <= 0) { - *rectlist[rectIndex] = outerPlane->_screenRect.findIntersectingRect(innerPlane->_screenRect); - if (outerPlane->_priority <= innerPlane->_priority) { - eraseLists[innerIndex].add(*rectlist[rectIndex]); - } - else { - eraseLists[outerIndex].add(*rectlist[rectIndex]); + if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane._priority - innerPlane._priority) <= 0) { + *eraseList[rectIndex] = outerPlane._screenRect.findIntersectingRect(innerPlane._screenRect); + + if (outerPlane._priority <= innerPlane._priority) { + eraseLists[innerIndex].add(*eraseList[rectIndex]); + } else { + eraseLists[outerIndex].add(*eraseList[rectIndex]); } } } - rectlist.erase_at(rectIndex); + eraseList.erase_at(rectIndex); } } - rectlist.pack(); + eraseList.pack(); } } } - for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) { - Plane *plane = _planes[planeIndex]; - Plane *visiblePlane = nullptr; + for (PlaneList::size_type planeIndex = 0; planeIndex < planeCount; ++planeIndex) { + Plane &plane = *_planes[planeIndex]; + Plane *visiblePlane = _visiblePlanes.findByObject(plane._object); - PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject<Plane *>(plane->_object)); - if (visiblePlaneIt != _visiblePlanes.end()) { - visiblePlane = *visiblePlaneIt; - } + if (!plane._screenRect.isEmpty()) { + if (plane._redrawAllCount) { + plane.redrawAll(visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]); + } else { + if (visiblePlane == nullptr) { + error("Missing visible plane for source plane %04x:%04x", PRINT_REG(plane._object)); + } - if (plane->_redrawAllCount) { - plane->redrawAll(visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]); - } else { - if (visiblePlane == nullptr) { - error("Missing visible plane for source plane %04x:%04x", PRINT_REG(plane->_object)); + plane.calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]); } + } else { + plane.decrementScreenItemArrayCounts(visiblePlane, false); + } - plane->calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]); + if (plane._moved) { + // the work for handling moved/resized planes was already done + // earlier in the function, we are just cleaning up now + --plane._moved; } - if (plane->_created) { - _visiblePlanes.add(new Plane(*plane)); - --plane->_created; - } else if (plane->_moved) { - assert(visiblePlaneIt != _visiblePlanes.end()); - **visiblePlaneIt = *plane; - --plane->_moved; + if (plane._created) { + _visiblePlanes.add(new Plane(plane)); + --plane._created; + } else if (plane._updated) { + *visiblePlane = plane; + --plane._updated; } } + // NOTE: SSCI only looks for kPlaneTypeTransparent, not + // kPlaneTypeTransparentPicture if (foundTransparentPlane) { - for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) { - for (int i = planeIndex + 1; i < planeCount; ++i) { + for (PlaneList::size_type planeIndex = 0; planeIndex < planeCount; ++planeIndex) { + for (PlaneList::size_type i = planeIndex + 1; i < planeCount; ++i) { if (_planes[i]->_type == kPlaneTypeTransparent) { _planes[i]->filterUpEraseRects(drawLists[i], eraseLists[planeIndex]); } } if (_planes[planeIndex]->_type == kPlaneTypeTransparent) { - for (int i = planeIndex - 1; i >= 0; --i) { + for (int i = (int)planeIndex - 1; i >= 0; --i) { _planes[i]->filterDownEraseRects(drawLists[i], eraseLists[i], eraseLists[planeIndex]); } @@ -764,7 +946,7 @@ void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseL } } - for (int i = planeIndex + 1; i < planeCount; ++i) { + for (PlaneList::size_type i = planeIndex + 1; i < planeCount; ++i) { if (_planes[i]->_type == kPlaneTypeTransparent) { _planes[i]->filterUpDrawRects(drawLists[i], drawLists[planeIndex]); } @@ -778,17 +960,19 @@ void GfxFrameout::drawEraseList(const RectList &eraseList, const Plane &plane) { return; } - for (RectList::const_iterator it = eraseList.begin(); it != eraseList.end(); ++it) { - mergeToShowList(**it, _showList, _overdrawThreshold); - _currentBuffer.fillRect(**it, plane._back); + const RectList::size_type eraseListSize = eraseList.size(); + for (RectList::size_type i = 0; i < eraseListSize; ++i) { + mergeToShowList(*eraseList[i], _showList, _overdrawThreshold); + _currentBuffer.fillRect(*eraseList[i], plane._back); } } void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) { - for (DrawList::const_iterator it = screenItemList.begin(); it != screenItemList.end(); ++it) { - DrawItem &drawItem = **it; + const DrawList::size_type drawListSize = screenItemList.size(); + for (DrawList::size_type i = 0; i < drawListSize; ++i) { + const DrawItem &drawItem = *screenItemList[i]; mergeToShowList(drawItem.rect, _showList, _overdrawThreshold); - ScreenItem &screenItem = *drawItem.screenItem; + const ScreenItem &screenItem = *drawItem.screenItem; // TODO: Remove // debug("Drawing item %04x:%04x to %d %d %d %d", PRINT_REG(screenItem._object), PRINT_RECT(drawItem.rect)); CelObj &celObj = *screenItem._celObj; @@ -797,36 +981,65 @@ void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) { } void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold) { - Common::Rect merged(drawRect); - - bool didDelete = true; - RectList::size_type count = showList.size(); - while (didDelete && count) { - didDelete = false; - - for (RectList::size_type i = 0; i < count; ++i) { - Common::Rect existing = *showList[i]; - Common::Rect candidate; - candidate.left = MIN(merged.left, existing.left); - candidate.top = MIN(merged.top, existing.top); - candidate.right = MAX(merged.right, existing.right); - candidate.bottom = MAX(merged.bottom, existing.bottom); - - if (candidate.height() * candidate.width() - merged.width() * merged.height() - existing.width() * existing.height() <= overdrawThreshold) { - merged = candidate; - showList.erase_at(i); - didDelete = true; + RectList mergeList; + Common::Rect merged; + mergeList.add(drawRect); + + for (RectList::size_type i = 0; i < mergeList.size(); ++i) { + bool didMerge = false; + const Common::Rect &r1 = *mergeList[i]; + if (!r1.isEmpty()) { + for (RectList::size_type j = 0; j < showList.size(); ++j) { + const Common::Rect &r2 = *showList[j]; + if (!r2.isEmpty()) { + merged = r1; + merged.extend(r2); + + int difference = merged.width() * merged.height(); + difference -= r1.width() * r1.height(); + difference -= r2.width() * r2.height(); + if (r1.intersects(r2)) { + const Common::Rect overlap = r1.findIntersectingRect(r2); + difference += overlap.width() * overlap.height(); + } + + if (difference <= overdrawThreshold) { + mergeList.erase_at(i); + showList.erase_at(j); + mergeList.add(merged); + didMerge = true; + break; + } else { + Common::Rect outRects[2]; + int splitCount = splitRectsForRender(*mergeList[i], *showList[j], outRects); + if (splitCount != -1) { + mergeList.add(*mergeList[i]); + mergeList.erase_at(i); + showList.erase_at(j); + didMerge = true; + while (splitCount--) { + mergeList.add(outRects[splitCount]); + } + break; + } + } + } } - } - count = showList.pack(); + if (didMerge) { + showList.pack(); + } + } } - showList.add(merged); + mergeList.pack(); + for (RectList::size_type i = 0; i < mergeList.size(); ++i) { + showList.add(*mergeList[i]); + } } void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle) { - Palette sourcePalette(*_palette->getNextPalette()); + Palette sourcePalette(_palette->getNextPalette()); alterVmap(sourcePalette, sourcePalette, -1, styleRanges); int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16(); @@ -835,8 +1048,6 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry _showList.add(rect); showBits(); - Common::Rect calcRect(0, 0); - // NOTE: The original engine allocated these as static arrays of 100 // pointers to ScreenItemList / RectList ScreenItemListList screenItemLists; @@ -849,7 +1060,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry remapMarkRedraw(); } - calcLists(screenItemLists, eraseLists, calcRect); + calcLists(screenItemLists, eraseLists); for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { list->sort(); } @@ -868,7 +1079,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry drawScreenItemList(screenItemLists[i]); } - Palette nextPalette(*_palette->getNextPalette()); + Palette nextPalette(_palette->getNextPalette()); if (prevRoom < 1000) { for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) { @@ -879,6 +1090,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry } } else { for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) { + // TODO: Limiting range 72 to 103 is NOT present in every game if (styleRanges[i] == -1 || (styleRanges[i] == 0 && i > 71 && i < 104)) { sourcePalette.colors[i] = nextPalette.colors[i]; sourcePalette.colors[i].used = true; @@ -909,7 +1121,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry remapMarkRedraw(); } - calcLists(screenItemLists, eraseLists, calcRect); + calcLists(screenItemLists, eraseLists); for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { list->sort(); } @@ -932,16 +1144,12 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry _palette->submit(nextPalette); _palette->updateFFrame(); - _palette->updateHardware(); + _palette->updateHardware(false); showBits(); _frameNowVisible = true; } -// TODO: What does the bit masking for the show rects do, -// and does it cause an off-by-one error in rect calculations -// since SOL_Rect is BR inclusive and Common::Rect is BR -// exclusive? void GfxFrameout::showBits() { for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) { Common::Rect rounded(**rect); @@ -987,7 +1195,7 @@ void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, co if (styleRanges[paletteIndex] == style) { int minDiff = 262140; - int minDiffIndex; + int minDiffIndex = paletteIndex; for (int i = 0; i < 236; ++i) { if (styleRanges[i] != style) { @@ -1007,7 +1215,7 @@ void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, co if (style == 1 && styleRanges[paletteIndex] == 0) { int minDiff = 262140; - int minDiffIndex; + int minDiffIndex = paletteIndex; for (int i = 0; i < 236; ++i) { int r = palette2.colors[i].r; @@ -1308,6 +1516,10 @@ void GfxFrameout::processShowStyles() { } } + if (g_engine->shouldQuit()) { + return; + } + if (doFrameOut) { frameOut(true); @@ -1315,9 +1527,7 @@ void GfxFrameout::processShowStyles() { // fast, but the throttle value is arbitrary. Someone on // real hardware probably needs to test what the actual // speed of these transitions should be - EngineState *state = g_sci->getEngineState(); - state->speedThrottler(33); - state->_throttleTrigger = true; + throttle(); } } while(continueProcessing && doFrameOut); } @@ -1391,6 +1601,24 @@ void GfxFrameout::kernelFrameOut(const bool shouldShowBits) { frameOut(shouldShowBits); } + + throttle(); +} + +void GfxFrameout::throttle() { + if (_throttleFrameOut) { + uint8 throttleTime; + if (_throttleState == 2) { + throttleTime = 16; + _throttleState = 0; + } else { + throttleTime = 17; + ++_throttleState; + } + + g_sci->getEngineState()->speedThrottler(throttleTime); + g_sci->getEngineState()->_throttleTrigger = true; + } } #pragma mark - diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h index 8ed95a00de..99658ede6a 100644 --- a/engines/sci/graphics/frameout.h +++ b/engines/sci/graphics/frameout.h @@ -149,10 +149,7 @@ struct ShowStyleEntry { typedef Common::Array<DrawList> ScreenItemListList; typedef Common::Array<RectList> EraseListList; -class GfxCache; class GfxCoordAdjuster32; -class GfxPaint32; -class GfxPalette; class GfxScreen; /** @@ -162,16 +159,14 @@ class GfxScreen; class GfxFrameout { private: bool _isHiRes; - GfxCache *_cache; GfxCoordAdjuster32 *_coordAdjuster; GfxPalette32 *_palette; ResourceManager *_resMan; GfxScreen *_screen; SegManager *_segMan; - GfxPaint32 *_paint32; public: - GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32); + GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette); ~GfxFrameout(); void clear(); @@ -179,12 +174,58 @@ public: void run(); #pragma mark - +#pragma mark Benchmarking +private: + /** + * Optimization to avoid the more expensive object name + * comparision on every call to kAddScreenItem and + * kRemoveScreenItem. + */ + bool _benchmarkingFinished; + + /** + * Whether or not calls to kFrameOut should be framerate + * limited to 60fps. + */ + bool _throttleFrameOut; + + /** + * Determines whether or not a screen item is the "Fred" + * object. + */ + bool checkForFred(const reg_t object); + +#pragma mark - #pragma mark Screen items private: - void deleteScreenItem(ScreenItem *screenItem, const reg_t plane); void remapMarkRedraw(); public: + /** + * Adds a screen item. + */ + void addScreenItem(ScreenItem &screenItem) const; + + /** + * Updates a screen item. + */ + void updateScreenItem(ScreenItem &screenItem) const; + + /** + * Deletes a screen item. + */ + void deleteScreenItem(ScreenItem &screenItem); + + /** + * Deletes a screen item from the given plane. + */ + void deleteScreenItem(ScreenItem &screenItem, Plane &plane); + + /** + * Deletes a screen item from the given plane. + */ + void deleteScreenItem(ScreenItem &screenItem, const reg_t plane); + void kernelAddScreenItem(const reg_t object); void kernelUpdateScreenItem(const reg_t object); void kernelDeleteScreenItem(const reg_t object); @@ -278,6 +319,12 @@ public: #pragma mark Rendering private: /** + * State tracker to provide more accurate 60fps + * video throttling. + */ + uint8 _throttleState; + + /** * TODO: Documentation */ int8 _styleRanges[256]; @@ -292,7 +339,9 @@ private: Buffer _currentBuffer; /** - * TODO: Documentation + * When true, a change to the remap zone in the palette + * has occurred and screen items with remap data need to + * be redrawn. */ bool _remapOccurred; @@ -350,8 +399,10 @@ private: * over the entire screen for rendering the next frame. * The draw and erase lists in `drawLists` and * `eraseLists` each represent one plane on the screen. + * The optional `eraseRect` argument allows a specific + * area of the screen to be erased. */ - void calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect); + void calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &eraseRect = Common::Rect()); /** * Erases the areas in the given erase list from the @@ -395,18 +446,25 @@ public: */ bool _palMorphIsOn; - inline Buffer &getCurrentBuffer() { + inline const Buffer &getCurrentBuffer() const { return _currentBuffer; } void kernelFrameOut(const bool showBits); /** + * Throttles the engine as necessary to maintain + * 60fps output. + */ + void throttle(); + + /** * Updates the internal screen buffer for the next * frame. If `shouldShowBits` is true, also sends the - * buffer to hardware. + * buffer to hardware. If `eraseRect` is non-empty, + * it is added to the erase list for this frame. */ - void frameOut(const bool shouldShowBits, const Common::Rect &rect = Common::Rect()); + void frameOut(const bool shouldShowBits, const Common::Rect &eraseRect = Common::Rect()); /** * Modifies the raw pixel data for the next frame with diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h index 19dddd74b8..3fcc83c5e2 100644 --- a/engines/sci/graphics/helpers.h +++ b/engines/sci/graphics/helpers.h @@ -191,6 +191,12 @@ struct Buffer : public Graphics::Surface { uint16 scriptWidth; uint16 scriptHeight; + Buffer() : + screenWidth(0), + screenHeight(0), + scriptWidth(320), + scriptHeight(200) {} + Buffer(const uint16 width, const uint16 height, uint8 *const pix) : screenWidth(width), screenHeight(height), @@ -231,7 +237,7 @@ struct Color { return used == other.used && r == other.r && g == other.g && b == other.b; } inline bool operator!=(const Color &other) const { - return !(*this == other); + return !operator==(other); } #endif }; diff --git a/engines/sci/graphics/paint.cpp b/engines/sci/graphics/paint.cpp deleted file mode 100644 index 482b81aff1..0000000000 --- a/engines/sci/graphics/paint.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* 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 "graphics/primitives.h" - -#include "sci/sci.h" -#include "sci/engine/state.h" -#include "sci/engine/selector.h" -#include "sci/graphics/paint.h" - -namespace Sci { - -GfxPaint::GfxPaint() { -} - -GfxPaint::~GfxPaint() { -} - -void GfxPaint::kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo) { -} - -void GfxPaint::kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control) { -} - -} // End of namespace Sci diff --git a/engines/sci/graphics/paint.h b/engines/sci/graphics/paint.h deleted file mode 100644 index b2277131d5..0000000000 --- a/engines/sci/graphics/paint.h +++ /dev/null @@ -1,39 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#ifndef SCI_GRAPHICS_PAINT_H -#define SCI_GRAPHICS_PAINT_H - -namespace Sci { - -class GfxPaint { -public: - GfxPaint(); - virtual ~GfxPaint(); - - virtual void kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo); - virtual void kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control); -}; - -} // End of namespace Sci - -#endif diff --git a/engines/sci/graphics/paint16.h b/engines/sci/graphics/paint16.h index 955cfdec8f..317388b2df 100644 --- a/engines/sci/graphics/paint16.h +++ b/engines/sci/graphics/paint16.h @@ -23,8 +23,6 @@ #ifndef SCI_GRAPHICS_PAINT16_H #define SCI_GRAPHICS_PAINT16_H -#include "sci/graphics/paint.h" - namespace Sci { class GfxPorts; @@ -36,7 +34,7 @@ class GfxView; /** * Paint16 class, handles painting/drawing for SCI16 (SCI0-SCI1.1) games */ -class GfxPaint16 : public GfxPaint { +class GfxPaint16 { public: GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio); ~GfxPaint16(); diff --git a/engines/sci/graphics/paint32.cpp b/engines/sci/graphics/paint32.cpp index a210a469f1..7773b9f4e1 100644 --- a/engines/sci/graphics/paint32.cpp +++ b/engines/sci/graphics/paint32.cpp @@ -20,49 +20,162 @@ * */ -#include "sci/sci.h" -#include "sci/engine/state.h" -#include "sci/engine/selector.h" -#include "sci/graphics/coordadjuster.h" -#include "sci/graphics/cache.h" +#include "graphics/primitives.h" +#include "sci/engine/seg_manager.h" #include "sci/graphics/paint32.h" -#include "sci/graphics/font.h" -#include "sci/graphics/picture.h" -#include "sci/graphics/view.h" -#include "sci/graphics/screen.h" -#include "sci/graphics/palette.h" +#include "sci/graphics/text32.h" namespace Sci { -GfxPaint32::GfxPaint32(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette) - : _resMan(resMan), _coordAdjuster(coordAdjuster), _screen(screen), _palette(palette) { +GfxPaint32::GfxPaint32(SegManager *segMan) : + _segMan(segMan) {} + +reg_t GfxPaint32::kernelAddLine(const reg_t planeObject, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness) { + Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObject); + if (plane == nullptr) { + error("kAddLine: Plane %04x:%04x not found", PRINT_REG(planeObject)); + } + + Common::Rect gameRect; + BitmapResource bitmap = makeLineBitmap(startPoint, endPoint, priority, color, style, pattern, thickness, gameRect); + + CelInfo32 celInfo; + celInfo.type = kCelTypeMem; + celInfo.bitmap = bitmap.getObject(); + // SSCI stores the line color on `celInfo`, even though + // this is not a `kCelTypeColor`, as a hack so that + // `kUpdateLine` can get the originally used color + celInfo.color = color; + + ScreenItem *screenItem = new ScreenItem(planeObject, celInfo, Common::Rect(startPoint.x, startPoint.y, startPoint.x + bitmap.getWidth(), startPoint.y + bitmap.getHeight())); + screenItem->_priority = priority; + screenItem->_fixedPriority = true; + + plane->_screenItemList.add(screenItem); + + return screenItem->_object; +} + +void GfxPaint32::kernelUpdateLine(ScreenItem *screenItem, Plane *plane, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness) { + + Common::Rect gameRect; + BitmapResource bitmap = makeLineBitmap(startPoint, endPoint, priority, color, style, pattern, thickness, gameRect); + + _segMan->freeHunkEntry(screenItem->_celInfo.bitmap); + screenItem->_celInfo.bitmap = bitmap.getObject(); + screenItem->_celInfo.color = color; + screenItem->_position = startPoint; + screenItem->_priority = priority; + screenItem->update(); } -GfxPaint32::~GfxPaint32() { +void GfxPaint32::kernelDeleteLine(const reg_t screenItemObject, const reg_t planeObject) { + Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObject); + if (plane == nullptr) { + return; + } + + ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject); + if (screenItem == nullptr) { + return; + } + + _segMan->freeHunkEntry(screenItem->_celInfo.bitmap); + g_sci->_gfxFrameout->deleteScreenItem(*screenItem, *plane); } -void GfxPaint32::fillRect(Common::Rect rect, byte color) { - int16 y, x; - Common::Rect clipRect = rect; +void GfxPaint32::plotter(int x, int y, int color, void *data) { + LineProperties &properties = *static_cast<LineProperties *>(data); + byte *pixels = properties.bitmap->getPixels(); + + const uint32 index = properties.bitmap->getWidth() * y + x; + + if (index < properties.bitmap->getDataSize()) { + if (properties.solid) { + pixels[index] = (uint8)color; + return; + } + + if (properties.horizontal && x != properties.lastAddress) { + properties.lastAddress = x; + ++properties.patternIndex; + } else if (!properties.horizontal && y != properties.lastAddress) { + properties.lastAddress = y; + ++properties.patternIndex; + } - clipRect.clip(_screen->getWidth(), _screen->getHeight()); + if (properties.pattern[properties.patternIndex]) { + pixels[index] = (uint8)color; + } - for (y = clipRect.top; y < clipRect.bottom; y++) { - for (x = clipRect.left; x < clipRect.right; x++) { - _screen->putPixel(x, y, GFX_SCREEN_MASK_VISUAL, color, 0, 0); + if (properties.patternIndex == ARRAYSIZE(properties.pattern)) { + properties.patternIndex = 0; } + } else { + warning("GfxPaint32::plotter: Attempted to write out of bounds (%u >= %u)", index, properties.bitmap->getDataSize()); } } -void GfxPaint32::kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo) { - GfxPicture *picture = new GfxPicture(_resMan, _coordAdjuster, 0, _screen, _palette, pictureId, false); +BitmapResource GfxPaint32::makeLineBitmap(const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, uint16 pattern, uint8 thickness, Common::Rect &outRect) { + const uint8 skipColor = color != kDefaultSkipColor ? kDefaultSkipColor : 0; - picture->draw(animationNr, mirroredFlag, addToFlag, EGApaletteNo); - delete picture; -} + // Thickness is expected to be 2n+1 + thickness = ((MAX((uint8)1, thickness) - 1) | 1); + const uint8 halfThickness = thickness >> 1; + + outRect.left = (startPoint.x < endPoint.x ? startPoint.x : endPoint.x) - halfThickness; + outRect.top = (startPoint.y < endPoint.y ? startPoint.y : endPoint.y) - halfThickness; + outRect.right = (startPoint.x > endPoint.x ? startPoint.x : endPoint.x) + halfThickness + 1; + outRect.bottom = (startPoint.y > endPoint.y ? startPoint.y : endPoint.y) + halfThickness + 1; + + BitmapResource bitmap(_segMan, outRect.width(), outRect.height(), skipColor, 0, 0, g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth, g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight, 0, false); + + byte *pixels = bitmap.getPixels(); + memset(pixels, skipColor, bitmap.getWidth() * bitmap.getHeight()); + + LineProperties properties; + properties.bitmap = &bitmap; + + switch (style) { + case kLineStyleSolid: + pattern = 0xFFFF; + properties.solid = true; + break; + case kLineStyleDashed: + pattern = 0xFF00; + properties.solid = false; + break; + case kLineStylePattern: + properties.solid = pattern == 0xFFFF; + break; + } + + const Common::Rect drawRect( + startPoint.x - outRect.left, + startPoint.y - outRect.top, + endPoint.x - outRect.left, + endPoint.y - outRect.top + ); -void GfxPaint32::kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control) { - _screen->drawLine(startPoint.x, startPoint.y, endPoint.x, endPoint.y, color, priority, control); + if (!properties.solid) { + for (int i = 0; i < ARRAYSIZE(properties.pattern); ++i) { + properties.pattern[i] = (pattern & 0x8000); + pattern <<= 1; + } + + properties.patternIndex = 0; + properties.horizontal = ABS(drawRect.right - drawRect.left) > ABS(drawRect.bottom - drawRect.top); + properties.lastAddress = properties.horizontal ? drawRect.left : drawRect.top; + } + + if (thickness <= 1) { + Graphics::drawLine(drawRect.left, drawRect.top, drawRect.right, drawRect.bottom, color, plotter, &properties); + } else { + Graphics::drawThickLine2(drawRect.left, drawRect.top, drawRect.right, drawRect.bottom, thickness, color, plotter, &properties); + } + + return bitmap; } + } // End of namespace Sci diff --git a/engines/sci/graphics/paint32.h b/engines/sci/graphics/paint32.h index e7a3ec256d..6d5a957fcd 100644 --- a/engines/sci/graphics/paint32.h +++ b/engines/sci/graphics/paint32.h @@ -23,30 +23,48 @@ #ifndef SCI_GRAPHICS_PAINT32_H #define SCI_GRAPHICS_PAINT32_H -#include "sci/graphics/paint.h" - namespace Sci { +class BitmapResource; +class Plane; +class ScreenItem; +class SegManager; -class GfxPorts; +enum LineStyle { + kLineStyleSolid, + kLineStyleDashed, + kLineStylePattern +}; /** * Paint32 class, handles painting/drawing for SCI32 (SCI2+) games */ -class GfxPaint32 : public GfxPaint { +class GfxPaint32 { public: - GfxPaint32(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette); - ~GfxPaint32(); + GfxPaint32(SegManager *segMan); - void fillRect(Common::Rect rect, byte color); +private: + SegManager *_segMan; - void kernelDrawPicture(GuiResourceId pictureId, int16 animationNr, bool animationBlackoutFlag, bool mirroredFlag, bool addToFlag, int16 EGApaletteNo); - void kernelGraphDrawLine(Common::Point startPoint, Common::Point endPoint, int16 color, int16 priority, int16 control); +#pragma mark - +#pragma mark Line drawing +public: + reg_t kernelAddLine(const reg_t planeObject, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness); + void kernelUpdateLine(ScreenItem *screenItem, Plane *plane, const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness); + void kernelDeleteLine(const reg_t screenItemObject, const reg_t planeObject); private: - ResourceManager *_resMan; - GfxCoordAdjuster *_coordAdjuster; - GfxScreen *_screen; - GfxPalette *_palette; + typedef struct { + BitmapResource *bitmap; + bool pattern[16]; + uint8 patternIndex; + bool solid; + bool horizontal; + int lastAddress; + } LineProperties; + + static void plotter(int x, int y, int color, void *data); + + BitmapResource makeLineBitmap(const Common::Point &startPoint, const Common::Point &endPoint, const int16 priority, const uint8 color, const LineStyle style, const uint16 pattern, const uint8 thickness, Common::Rect &outRect); }; } // End of namespace Sci diff --git a/engines/sci/graphics/palette.h b/engines/sci/graphics/palette.h index 7335dc59d0..2178de8a91 100644 --- a/engines/sci/graphics/palette.h +++ b/engines/sci/graphics/palette.h @@ -51,7 +51,7 @@ public: bool setAmiga(); void modifyAmigaPalette(byte *data); void setEGA(); - virtual void set(Palette *sciPal, bool force, bool forceRealMerge = false); + void set(Palette *sciPal, bool force, bool forceRealMerge = false); bool insert(Palette *newPalette, Palette *destPalette); bool merge(Palette *pFrom, bool force, bool forceRealMerge); uint16 matchColor(byte r, byte g, byte b); @@ -63,11 +63,11 @@ public: void drewPicture(GuiResourceId pictureId); - virtual bool kernelSetFromResource(GuiResourceId resourceId, bool force); + bool kernelSetFromResource(GuiResourceId resourceId, bool force); void kernelSetFlag(uint16 fromColor, uint16 toColor, uint16 flag); void kernelUnsetFlag(uint16 fromColor, uint16 toColor, uint16 flag); void kernelSetIntensity(uint16 fromColor, uint16 toColor, uint16 intensity, bool setPalette); - virtual int16 kernelFindColor(uint16 r, uint16 g, uint16 b); + int16 kernelFindColor(uint16 r, uint16 g, uint16 b); bool kernelAnimate(byte fromColor, byte toColor, int speed); void kernelAnimateSet(); reg_t kernelSave(); @@ -81,7 +81,7 @@ public: int16 kernelPalVaryGetCurrentStep(); int16 kernelPalVaryChangeTarget(GuiResourceId resourceId); void kernelPalVaryChangeTicks(uint16 ticks); - virtual void kernelPalVaryPause(bool pause); + void kernelPalVaryPause(bool pause); void kernelPalVaryDeinit(); void palVaryUpdate(); void palVaryPrepareForTransition(); @@ -89,7 +89,7 @@ public: Palette _sysPalette; - virtual void saveLoadWithSerializer(Common::Serializer &s); + void saveLoadWithSerializer(Common::Serializer &s); void palVarySaveLoadPalette(Common::Serializer &s, Palette *palette); byte findMacIconBarColor(byte r, byte g, byte b); diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp index 6844011675..2a98c237b0 100644 --- a/engines/sci/graphics/palette32.cpp +++ b/engines/sci/graphics/palette32.cpp @@ -27,19 +27,109 @@ #include "sci/sci.h" #include "sci/event.h" #include "sci/resource.h" +#include "sci/util.h" #include "sci/graphics/palette32.h" -#include "sci/graphics/remap.h" +#include "sci/graphics/remap32.h" #include "sci/graphics/screen.h" namespace Sci { -GfxPalette32::GfxPalette32(ResourceManager *resMan, GfxScreen *screen) - : GfxPalette(resMan, screen), +#pragma mark HunkPalette + +HunkPalette::HunkPalette(byte *rawPalette) : + _version(0), + // NOTE: The header size in palettes is garbage. In at least KQ7 2.00b and + // Phant1, the 999.pal sets this value to 0. In most other palettes it is + // set to 14, but the *actual* size of the header structure used in SSCI is + // 13, which is reflected by `kHunkPaletteHeaderSize`. + // _headerSize(rawPalette[0]), + _numPalettes(rawPalette[10]), + _data(nullptr) { + assert(_numPalettes == 0 || _numPalettes == 1); + if (_numPalettes) { + _data = rawPalette; + _version = getEntryHeader().version; + } +} + +void HunkPalette::setVersion(const uint32 version) { + if (_numPalettes != _data[10]) { + error("Invalid HunkPalette"); + } + + if (_numPalettes) { + const EntryHeader header = getEntryHeader(); + if (header.version != _version) { + error("Invalid HunkPalette"); + } + + WRITE_SCI11ENDIAN_UINT32(getPalPointer() + kEntryVersionOffset, version); + _version = version; + } +} + +const HunkPalette::EntryHeader HunkPalette::getEntryHeader() const { + const byte *const data = getPalPointer(); + + EntryHeader header; + header.startColor = data[10]; + header.numColors = READ_SCI11ENDIAN_UINT16(data + 14); + header.used = data[16]; + header.sharedUsed = data[17]; + header.version = READ_SCI11ENDIAN_UINT32(data + kEntryVersionOffset); + + return header; +} + +const Palette HunkPalette::toPalette() const { + Palette outPalette; + + for (int16 i = 0; i < ARRAYSIZE(outPalette.colors); ++i) { + outPalette.colors[i].used = false; + outPalette.colors[i].r = 0; + outPalette.colors[i].g = 0; + outPalette.colors[i].b = 0; + } + + if (_numPalettes) { + const EntryHeader header = getEntryHeader(); + byte *data = getPalPointer() + kEntryHeaderSize; + + int16 end = header.startColor + header.numColors; + assert(end <= 256); + + if (header.sharedUsed) { + for (int16 i = header.startColor; i < end; ++i) { + outPalette.colors[i].used = header.used; + outPalette.colors[i].r = *data++; + outPalette.colors[i].g = *data++; + outPalette.colors[i].b = *data++; + } + } else { + for (int16 i = header.startColor; i < end; ++i) { + outPalette.colors[i].used = *data++; + outPalette.colors[i].r = *data++; + outPalette.colors[i].g = *data++; + outPalette.colors[i].b = *data++; + } + } + } + + return outPalette; +} + + +#pragma mark - +#pragma mark GfxPalette32 + +GfxPalette32::GfxPalette32(ResourceManager *resMan) + : _resMan(resMan), // Palette versioning _version(1), - _versionUpdated(false), - _sourcePalette(_sysPalette), - _nextPalette(_sysPalette), + _needsUpdate(false), + _currentPalette(), + _sourcePalette(), + _nextPalette(), // Clut _clutTable(nullptr), // Palette varying @@ -55,106 +145,91 @@ GfxPalette32::GfxPalette32(ResourceManager *resMan, GfxScreen *screen) // Palette cycling _cyclers(), _cycleMap() { - _varyPercent = _varyTargetPercent; - for (int i = 0, len = ARRAYSIZE(_fadeTable); i < len; ++i) { - _fadeTable[i] = 100; - } - // NOTE: In SCI engine, the palette manager constructor loads - // the default palette, but in ScummVM this initialisation - // is performed by SciEngine::run; see r49523 for details + _varyPercent = _varyTargetPercent; + for (int i = 0, len = ARRAYSIZE(_fadeTable); i < len; ++i) { + _fadeTable[i] = 100; + } + + loadPalette(999); } GfxPalette32::~GfxPalette32() { +#ifdef ENABLE_SCI3_GAMES unloadClut(); +#endif varyOff(); cycleAllOff(); } inline void mergePaletteInternal(Palette *const to, const Palette *const from) { - for (int i = 0, len = ARRAYSIZE(to->colors); i < len; ++i) { + // The last color is always white, so it is not copied. + // (Some palettes try to set the last color, which causes + // churning in the palettes when they are merged) + for (int i = 0, len = ARRAYSIZE(to->colors) - 1; i < len; ++i) { if (from->colors[i].used) { to->colors[i] = from->colors[i]; } } } -const Palette *GfxPalette32::getNextPalette() const { - return &_nextPalette; +void GfxPalette32::submit(const Palette &palette) { + const Palette oldSourcePalette(_sourcePalette); + mergePaletteInternal(&_sourcePalette, &palette); + + if (!_needsUpdate && _sourcePalette != oldSourcePalette) { + ++_version; + _needsUpdate = true; + } } -void GfxPalette32::submit(Palette &palette) { - // TODO: The resource manager in SCI32 retains raw data of palettes from - // the ResourceManager (ResourceMgr) through SegManager (MemoryMgr), and - // the version number for submitted palettes is set in the raw palette - // data in memory as an int at an offset - // `rawData + *rawData[0x0a] * 2 + 31`. However, ScummVM does not retain - // resource data like this, so this versioning code, while accurate to - // the original engine, does not do much. - // (Hopefully this was an optimisation mechanism in SCI engine and not a - // clever thing to keep the same palette submitted many times from - // overwriting other palette entries.) - if (palette.timestamp == _version) { +void GfxPalette32::submit(HunkPalette &hunkPalette) { + if (hunkPalette.getVersion() == _version) { return; } - Palette oldSourcePalette(_sourcePalette); + const Palette oldSourcePalette(_sourcePalette); + const Palette palette = hunkPalette.toPalette(); mergePaletteInternal(&_sourcePalette, &palette); - if (!_versionUpdated && _sourcePalette != oldSourcePalette) { + if (!_needsUpdate && oldSourcePalette != _sourcePalette) { ++_version; - _versionUpdated = true; + _needsUpdate = true; } - // Technically this information is supposed to be persisted through a - // HunkPalette object; right now it would just be lost once the temporary - // palette was destroyed. - palette.timestamp = _version; + hunkPalette.setVersion(_version); } -bool GfxPalette32::kernelSetFromResource(GuiResourceId resourceId, bool force) { - // TODO: In SCI32, palettes that come from resources come in as - // HunkPalette objects, not SOLPalette objects. The HunkPalettes - // have some extra persistence stuff associated with them, such that - // when they are passed to GfxPalette32::submit, they would get the - // version number of GfxPalette32 assigned to them. - Palette palette; +bool GfxPalette32::loadPalette(const GuiResourceId resourceId) { + Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false); - if (createPaletteFromResourceInternal(resourceId, &palette)) { - submit(palette); - return true; + if (!palResource) { + return false; } - return false; + HunkPalette palette(palResource->data); + submit(palette); + return true; } -// In SCI32 engine this method is SOLPalette::Match(Rgb24 *) -// and is called as PaletteMgr.Current().Match(color) -int16 GfxPalette32::kernelFindColor(uint16 r, uint16 g, uint16 b) { - // SQ6 SCI32 engine takes the 16-bit r, g, b arguments from the - // VM and puts them into al, ah, dl. For compatibility, make sure - // to discard any high bits here too - r = r & 0xFF; - g = g & 0xFF; - b = b & 0xFF; +int16 GfxPalette32::matchColor(const uint8 r, const uint8 g, const uint8 b) { int16 bestIndex = 0; int bestDifference = 0xFFFFF; int difference; - // SQ6 DOS really does check only the first 236 entries - for (int i = 0, channelDifference; i < 236; ++i) { - difference = _sysPalette.colors[i].r - r; + for (int i = 0, channelDifference; i < g_sci->_gfxRemap32->getStartColor(); ++i) { + difference = _currentPalette.colors[i].r - r; difference *= difference; if (bestDifference <= difference) { continue; } - channelDifference = _sysPalette.colors[i].g - g; + channelDifference = _currentPalette.colors[i].g - g; difference += channelDifference * channelDifference; if (bestDifference <= difference) { continue; } - channelDifference = _sysPalette.colors[i].b - b; + channelDifference = _currentPalette.colors[i].b - b; difference += channelDifference * channelDifference; if (bestDifference <= difference) { continue; @@ -166,99 +241,56 @@ int16 GfxPalette32::kernelFindColor(uint16 r, uint16 g, uint16 b) { return bestIndex; } -// TODO: set is overridden for the time being to send palettes coming from -// various draw methods like GfxPicture::drawSci32Vga and GfxView::draw to -// _nextPalette instead of _sysPalette. In the SCI32 engine, CelObj palettes -// (which are stored as Hunk palettes) are submitted by GraphicsMgr::FrameOut -// to PaletteMgr::Submit by way of calls to CelObj::SubmitPalette. -// GfxPalette::set is very similar to GfxPalette32::submit, except that SCI32 -// does not do any fancy best-fit merging and so does not accept arguments -// like `force` and `forceRealMerge`. -void GfxPalette32::set(Palette *newPalette, bool force, bool forceRealMerge) { - submit(*newPalette); -} - -// In SCI32 engine this method is SOLPalette::Match(Rgb24 *, int, int *, int *) -// and is used by Remap -// TODO: Anything that calls GfxPalette::matchColor(int, int, int) is going to -// match using an algorithm from SCI16 engine right now. This needs to be -// corrected in the future so either nothing calls -// GfxPalette::matchColor(int, int, int), or it is fixed to match the other -// SCI32 algorithms. -int16 GfxPalette32::matchColor(const byte r, const byte g, const byte b, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable) { - int16 bestIndex = -1; - int bestDifference = 0xFFFFF; - int difference = defaultDifference; - - // SQ6 DOS really does check only the first 236 entries - for (int i = 0, channelDifference; i < 236; ++i) { - if (matchTable[i] == 0) { - continue; - } - - difference = _sysPalette.colors[i].r - r; - difference *= difference; - if (bestDifference <= difference) { - continue; - } - channelDifference = _sysPalette.colors[i].g - g; - difference += channelDifference * channelDifference; - if (bestDifference <= difference) { - continue; - } - channelDifference = _sysPalette.colors[i].b - b; - difference += channelDifference * channelDifference; - if (bestDifference <= difference) { - continue; - } - bestDifference = difference; - bestIndex = i; - } - - // NOTE: This value is only valid if the last index to - // perform a difference calculation was the best index - lastCalculatedDifference = difference; - return bestIndex; -} - bool GfxPalette32::updateForFrame() { applyAll(); - _versionUpdated = false; - return g_sci->_gfxRemap32->remapAllTables(_nextPalette != _sysPalette); + _needsUpdate = false; + return g_sci->_gfxRemap32->remapAllTables(_nextPalette != _currentPalette); } void GfxPalette32::updateFFrame() { for (int i = 0; i < ARRAYSIZE(_nextPalette.colors); ++i) { _nextPalette.colors[i] = _sourcePalette.colors[i]; } - _versionUpdated = false; - g_sci->_gfxRemap32->remapAllTables(_nextPalette != _sysPalette); + _needsUpdate = false; + g_sci->_gfxRemap32->remapAllTables(_nextPalette != _currentPalette); } -void GfxPalette32::updateHardware() { - if (_sysPalette == _nextPalette) { +void GfxPalette32::updateHardware(const bool updateScreen) { + if (_currentPalette == _nextPalette) { return; } byte bpal[3 * 256]; - for (int i = 0; i < ARRAYSIZE(_sysPalette.colors); ++i) { - _sysPalette.colors[i] = _nextPalette.colors[i]; + for (int i = 0; i < ARRAYSIZE(_currentPalette.colors) - 1; ++i) { + _currentPalette.colors[i] = _nextPalette.colors[i]; // NOTE: If the brightness option in the user configuration file is set, // SCI engine adjusts palette brightnesses here by mapping RGB values to values // in some hard-coded brightness tables. There is no reason on modern hardware // to implement this, unless it is discovered that some game uses a non-standard // brightness setting by default - if (_sysPalette.colors[i].used) { - bpal[i * 3 ] = _sysPalette.colors[i].r; - bpal[i * 3 + 1] = _sysPalette.colors[i].g; - bpal[i * 3 + 2] = _sysPalette.colors[i].b; - } + + // All color entries MUST be copied, not just "used" entries, otherwise + // uninitialised memory from bpal makes its way into the system palette. + // This would not normally be a problem, except that games sometimes use + // unused palette entries. e.g. Phant1 title screen references palette + // entries outside its own palette, so will render garbage colors where + // the game expects them to be black + bpal[i * 3 ] = _currentPalette.colors[i].r; + bpal[i * 3 + 1] = _currentPalette.colors[i].g; + bpal[i * 3 + 2] = _currentPalette.colors[i].b; } + // The last color must always be white + bpal[255 * 3 ] = 255; + bpal[255 * 3 + 1] = 255; + bpal[255 * 3 + 2] = 255; + g_system->getPaletteManager()->setPalette(bpal, 0, 256); - g_sci->getEventManager()->updateScreen(); + if (updateScreen) { + g_sci->getEventManager()->updateScreen(); + } } void GfxPalette32::applyAll() { @@ -270,6 +302,7 @@ void GfxPalette32::applyAll() { #pragma mark - #pragma mark Colour look-up +#ifdef ENABLE_SCI3_GAMES bool GfxPalette32::loadClut(uint16 clutId) { // loadClut() will load a color lookup table from a clu file and set // the palette found in the file. This is to be used with Phantasmagoria 2. @@ -319,27 +352,20 @@ void GfxPalette32::unloadClut() { delete[] _clutTable; _clutTable = nullptr; } +#endif #pragma mark - #pragma mark Varying -inline bool GfxPalette32::createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const { - Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, paletteId), false); +inline Palette GfxPalette32::getPaletteFromResourceInternal(const GuiResourceId resourceId) const { + Resource *palResource = _resMan->findResource(ResourceId(kResourceTypePalette, resourceId), false); if (!palResource) { - return false; + error("Could not load vary palette %d", resourceId); } - createFromData(palResource->data, palResource->size, out); - return true; -} - -inline Palette GfxPalette32::getPaletteFromResourceInternal(const GuiResourceId paletteId) const { - Palette palette; - if (!createPaletteFromResourceInternal(paletteId, &palette)) { - error("Could not load vary target %d", paletteId); - } - return palette; + HunkPalette rawPalette(palResource->data); + return rawPalette.toPalette(); } inline void GfxPalette32::setVaryTimeInternal(const int16 percent, const int time) { @@ -363,8 +389,6 @@ inline void GfxPalette32::setVaryTimeInternal(const int16 percent, const int tim } } -// TODO: This gets called *a lot* in at least the first scene -// of SQ6. Optimisation would not be the worst idea in the world. void GfxPalette32::kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int time, const int16 fromColor, const int16 toColor) { Palette palette = getPaletteFromResourceInternal(paletteId); setVary(&palette, percent, time, fromColor, toColor); @@ -632,18 +656,15 @@ void GfxPalette32::setCycle(const uint8 fromColor, const uint8 toColor, const in // SCI engine overrides the first oldest cycler that it finds where // “oldest” is determined by the difference between the tick and now if (cycler == nullptr) { - int maxUpdateDelta = -1; - // Optimization: Unlike actual SCI (SQ6) engine, we call - // getTickCount only once and store it, instead of calling it - // twice on each iteration through the loop const uint32 now = g_sci->getTickCount(); + uint32 minUpdateDelta = 0xFFFFFFFF; for (cyclerIndex = 0; cyclerIndex < numCyclers; ++cyclerIndex) { PalCycler *candidate = _cyclers[cyclerIndex]; - const int32 updateDelta = now - candidate->lastUpdateTick; - if (updateDelta >= maxUpdateDelta) { - maxUpdateDelta = updateDelta; + const uint32 updateDelta = now - candidate->lastUpdateTick; + if (updateDelta < minUpdateDelta) { + minUpdateDelta = updateDelta; cycler = candidate; } } diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h index a5450776dc..dc2158022f 100644 --- a/engines/sci/graphics/palette32.h +++ b/engines/sci/graphics/palette32.h @@ -26,6 +26,118 @@ #include "sci/graphics/palette.h" namespace Sci { + +/** + * HunkPalette represents a raw palette resource + * read from disk. + */ +class HunkPalette { +public: + HunkPalette(byte *rawPalette); + + /** + * Gets the version of the palette. + */ + uint32 getVersion() const { return _version; } + + /** + * Sets the version of the palette. + */ + void setVersion(const uint32 version); + + /** + * Converts the hunk palette to a standard + * palette. + */ + const Palette toPalette() const; + +private: + enum { + /** + * The size of the HunkPalette header. + */ + kHunkPaletteHeaderSize = 13, + + /** + * The size of a palette entry header. + */ + kEntryHeaderSize = 22, + + /** + * The offset of the hunk palette version + * within the palette entry header. + */ + kEntryVersionOffset = 18 + }; + + /** + * The header for a palette inside the + * HunkPalette. + */ + struct EntryHeader { + /** + * The start color. + */ + uint8 startColor; + + /** + * The number of palette colors in this + * entry. + */ + uint16 numColors; + + /** + * The default `used` flag. + */ + bool used; + + /** + * Whether or not all palette entries + * share the same `used` value in + * `defaultFlag`. + */ + bool sharedUsed; + + /** + * The palette version. + */ + uint32 version; + }; + + /** + * The version number from the last time this + * palette was submitted to GfxPalette32. + */ + uint32 _version; + + /** + * The number of palettes stored in the hunk + * palette. In SCI32 games this is always 1. + */ + uint8 _numPalettes; + + /** + * The raw palette data for this hunk palette. + */ + byte *_data; + + /** + * Returns a struct that describes the palette + * held by this HunkPalette. The entry header + * is reconstructed on every call from the raw + * palette data. + */ + const EntryHeader getEntryHeader() const; + + /** + * Returns a pointer to the palette data within + * the hunk palette. + */ + byte *getPalPointer() const { + return _data + kHunkPaletteHeaderSize + (2 * _numPalettes); + } +}; + enum PalCyclerDirection { PalCycleBackward = 0, PalCycleForward = 1 @@ -72,28 +184,29 @@ struct PalCycler { uint16 numTimesPaused; }; -class GfxPalette32 : public GfxPalette { +class GfxPalette32 { public: - GfxPalette32(ResourceManager *resMan, GfxScreen *screen); + GfxPalette32(ResourceManager *resMan); ~GfxPalette32(); private: - // NOTE: currentPalette in SCI engine is called _sysPalette - // here. + ResourceManager *_resMan; /** * The palette revision version. Increments once per game - * loop that changes the source palette. TODO: Possibly - * other areas also change _version, double-check once it - * is all implemented. + * loop that changes the source palette. */ uint32 _version; /** - * Whether or not the palette manager version was updated - * during this loop. + * Whether or not the hardware palette needs updating. + */ + bool _needsUpdate; + + /** + * The currently displayed palette. */ - bool _versionUpdated; + Palette _currentPalette; /** * The unmodified source palette loaded by kPalette. Additional @@ -104,7 +217,7 @@ private: /** * The palette to be used when the hardware is next updated. - * On update, _nextPalette is transferred to _sysPalette. + * On update, _nextPalette is transferred to _currentPalette. */ Palette _nextPalette; @@ -112,28 +225,37 @@ private: Palette getPaletteFromResourceInternal(const GuiResourceId paletteId) const; public: - virtual void saveLoadWithSerializer(Common::Serializer &s) override; - const Palette *getNextPalette() const; + void saveLoadWithSerializer(Common::Serializer &s); + inline const Palette &getNextPalette() const { return _nextPalette; }; + inline const Palette &getCurrentPalette() const { return _currentPalette; }; - bool kernelSetFromResource(GuiResourceId resourceId, bool force) override; - int16 kernelFindColor(uint16 r, uint16 g, uint16 b) override; - void set(Palette *newPalette, bool force, bool forceRealMerge = false) override; - int16 matchColor(const byte matchRed, const byte matchGreen, const byte matchBlue, const int defaultDifference, int &lastCalculatedDifference, const bool *const matchTable); + /** + * Loads a palette into GfxPalette32 with the given resource + * ID. + */ + bool loadPalette(const GuiResourceId resourceId); + + /** + * Finds the nearest color in the current palette matching the + * given RGB value. + */ + int16 matchColor(const uint8 r, const uint8 g, const uint8 b); /** * Submits a palette to display. Entries marked as “used” in the * submitted palette are merged into the existing entries of * _sourcePalette. */ - void submit(Palette &palette); + void submit(const Palette &palette); + void submit(HunkPalette &palette); bool updateForFrame(); void updateFFrame(); - void updateHardware(); + void updateHardware(const bool updateScreen = true); void applyAll(); #pragma mark - -#pragma mark color look-up +#pragma mark Color look-up private: /** * An optional lookup table used to remap RGB565 colors to a palette @@ -213,7 +335,7 @@ public: void kernelPalVarySetTarget(const GuiResourceId paletteId); void kernelPalVarySetStart(const GuiResourceId paletteId); void kernelPalVaryMergeStart(const GuiResourceId paletteId); - virtual void kernelPalVaryPause(bool pause) override; + void kernelPalVaryPause(bool pause); void setVary(const Palette *const targetPalette, const int16 percent, const int time, const int16 fromColor, const int16 toColor); void setVaryPercent(const int16 percent, const int time, const int16 fromColor, const int16 fromColorAlternate); @@ -240,6 +362,11 @@ private: * According to SCI engine code, when two cyclers overlap, * a fatal error has occurred and the engine will display * an error and then exit. + * + * The cycle map is also by the color remapping system to + * avoid attempting to remap to palette entries that are + * cycling (so won't be the expected color once the cycler + * runs again). */ bool _cycleMap[256]; inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear); @@ -257,7 +384,7 @@ public: void cycleAllOff(); void applyAllCycles(); void applyCycles(); - const bool *getCyclemap() { return _cycleMap; } + inline const bool *getCycleMap() const { return _cycleMap; } #pragma mark - #pragma mark Fading diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp index d05e4f79e1..6b05b2d98a 100644 --- a/engines/sci/graphics/plane32.cpp +++ b/engines/sci/graphics/plane32.cpp @@ -27,7 +27,7 @@ #include "sci/graphics/frameout.h" #include "sci/graphics/lists32.h" #include "sci/graphics/plane32.h" -#include "sci/graphics/remap.h" +#include "sci/graphics/remap32.h" #include "sci/graphics/screen.h" #include "sci/graphics/screen_item32.h" @@ -45,10 +45,9 @@ void DrawList::add(ScreenItem *screenItem, const Common::Rect &rect) { uint16 Plane::_nextObjectId = 20000; Plane::Plane(const Common::Rect &gameRect, PlanePictureCodes pictureId) : -_width(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth), -_height(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight), _pictureId(pictureId), _mirrored(false), +_type(kPlaneTypeColored), _back(0), _priorityChanged(0), _object(make_reg(0, _nextObjectId++)), @@ -65,8 +64,7 @@ _gameRect(gameRect) { } Plane::Plane(reg_t object) : -_width(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth), -_height(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight), +_type(kPlaneTypeColored), _priorityChanged(false), _object(object), _redrawAllCount(g_sci->_gfxFrameout->getScreenCount()), @@ -97,8 +95,7 @@ _moved(0) { Plane::Plane(const Plane &other) : _pictureId(other._pictureId), _mirrored(other._mirrored), -_field_34(other._field_34), _field_38(other._field_38), -_field_3C(other._field_3C), _field_40(other._field_40), +_type(other._type), _back(other._back), _object(other._object), _priority(other._priority), @@ -116,11 +113,7 @@ void Plane::operator=(const Plane &other) { _mirrored = other._mirrored; _priority = other._priority; _back = other._back; - _width = other._width; - _field_34 = other._field_34; - _height = other._height; _screenRect = other._screenRect; - _field_3C = other._field_3C; _priorityChanged = other._priorityChanged; } @@ -173,17 +166,21 @@ void Plane::printDebugInfo(Console *con) const { void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX) { uint16 celCount = 1000; + bool transparent = true; for (uint16 celNo = 0; celNo < celCount; ++celNo) { CelObjPic *celObj = new CelObjPic(pictureId, celNo); if (celCount == 1000) { celCount = celObj->_celCount; } + if (!celObj->_transparent) { + transparent = false; + } ScreenItem *screenItem = new ScreenItem(_object, celObj->_info); screenItem->_pictureId = pictureId; screenItem->_mirrorX = mirrorX; screenItem->_priority = celObj->_priority; - screenItem->_fixPriority = true; + screenItem->_fixedPriority = true; if (position != nullptr) { screenItem->_position = *position + celObj->_relativePosition; } else { @@ -194,6 +191,7 @@ void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *p delete screenItem->_celObj; screenItem->_celObj = celObj; } + _type = transparent ? kPlaneTypeTransparentPicture : kPlaneTypePicture; } void Plane::addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX) { @@ -206,7 +204,7 @@ void Plane::addPic(const GuiResourceId pictureId, const Common::Point &position, void Plane::changePic() { _pictureChanged = false; - if (_type != kPlaneTypePicture) { + if (_type != kPlaneTypePicture && _type != kPlaneTypeTransparentPicture) { return; } @@ -249,17 +247,23 @@ void Plane::deleteAllPics() { #pragma mark - #pragma mark Plane - Rendering +extern int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]); + void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const { - int index = planeList.findIndexByObject(_object); + const int nextPlaneIndex = planeList.findIndexByObject(_object) + 1; + const PlaneList::size_type planeCount = planeList.size(); for (DrawList::size_type i = 0; i < drawList.size(); ++i) { - for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) { - if (planeList[j]->_type != kPlaneTypeTransparent) { - Common::Rect ptr[4]; - int count = splitRects(drawList[i]->rect, planeList[j]->_screenRect, ptr); - if (count != -1) { - for (int k = count - 1; k >= 0; --k) { - drawList.add(drawList[i]->screenItem, ptr[k]); + for (PlaneList::size_type j = nextPlaneIndex; j < planeCount; ++j) { + if ( + planeList[j]->_type != kPlaneTypeTransparent && + planeList[j]->_type != kPlaneTypeTransparentPicture + ) { + Common::Rect outRects[4]; + int splitCount = splitRects(drawList[i]->rect, planeList[j]->_screenRect, outRects); + if (splitCount != -1) { + while (splitCount--) { + drawList.add(drawList[i]->screenItem, outRects[splitCount]); } drawList.erase_at(i); @@ -272,17 +276,20 @@ void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList } void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const { - int index = planeList.findIndexByObject(_object); + const int nextPlaneIndex = planeList.findIndexByObject(_object) + 1; + const PlaneList::size_type planeCount = planeList.size(); for (RectList::size_type i = 0; i < eraseList.size(); ++i) { - for (PlaneList::size_type j = index + 1; j < planeList.size(); ++j) { - if (planeList[j]->_type != kPlaneTypeTransparent) { - Common::Rect ptr[4]; - - int count = splitRects(*eraseList[i], planeList[j]->_screenRect, ptr); - if (count != -1) { - for (int k = count - 1; k >= 0; --k) { - eraseList.add(ptr[k]); + for (PlaneList::size_type j = nextPlaneIndex; j < planeCount; ++j) { + if ( + planeList[j]->_type != kPlaneTypeTransparent && + planeList[j]->_type != kPlaneTypeTransparentPicture + ) { + Common::Rect outRects[4]; + int splitCount = splitRects(*eraseList[i], planeList[j]->_screenRect, outRects); + if (splitCount != -1) { + while (splitCount--) { + eraseList.add(outRects[splitCount]); } eraseList.erase_at(i); @@ -295,94 +302,109 @@ void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeLi } void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) { - ScreenItemList::size_type planeItemCount = _screenItemList.size(); - ScreenItemList::size_type visiblePlaneItemCount = visiblePlane._screenItemList.size(); + const ScreenItemList::size_type screenItemCount = _screenItemList.size(); + const ScreenItemList::size_type visiblePlaneItemCount = visiblePlane._screenItemList.size(); + + for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) { + // Items can be added to ScreenItemList and we don't want to process + // those new items, but the list also can grow smaller, so we need + // to check that we are still within the upper bound of the list and + // quit if we aren't any more + if (i >= _screenItemList.size()) { + break; + } + + ScreenItem *item = _screenItemList[i]; + if (item == nullptr) { + continue; + } - for (ScreenItemList::size_type i = 0; i < planeItemCount; ++i) { - ScreenItem *vitem = nullptr; // NOTE: The original engine used an array without bounds checking // so could just get the visible screen item directly; we need to // verify that the index is actually within the valid range for // the visible plane before accessing the item to avoid a range // error. + const ScreenItem *visibleItem = nullptr; if (i < visiblePlaneItemCount) { - vitem = visiblePlane._screenItemList[i]; + visibleItem = visiblePlane._screenItemList[i]; } - ScreenItem *item = _screenItemList[i]; - if (i < _screenItemList.size() && item != nullptr) { - if (item->_deleted) { - // add item's rect to erase list - if ( - i < visiblePlane._screenItemList.size() && - vitem != nullptr && - !vitem->_screenRect.isEmpty() - ) { - if (g_sci->_gfxRemap32->getRemapCount()) { - mergeToRectList(vitem->_screenRect, eraseList); - } else { - eraseList.add(vitem->_screenRect); - } - } - } else if (item->_created) { - // add item to draw list - item->calcRects(*this); - - if(!item->_screenRect.isEmpty()) { - if (g_sci->_gfxRemap32->getRemapCount()) { - drawList.add(item, item->_screenRect); - mergeToRectList(item->_screenRect, eraseList); - } else { - drawList.add(item, item->_screenRect); - } - } - } else if (item->_updated) { - // add old rect to erase list, new item to draw list - item->calcRects(*this); + // Keep erase rects for this screen item from drawing outside + // of its owner plane + Common::Rect visibleItemScreenRect; + if (visibleItem != nullptr) { + visibleItemScreenRect = visibleItem->_screenRect; + visibleItemScreenRect.clip(_screenRect); + } + + if (item->_deleted) { + // Add item's rect to erase list + if ( + visibleItem != nullptr && + !visibleItemScreenRect.isEmpty() + ) { if (g_sci->_gfxRemap32->getRemapCount()) { - // if item and vitem don't overlap, ... - if (item->_screenRect.isEmpty() || - i >= visiblePlaneItemCount || - vitem == nullptr || - vitem->_screenRect.isEmpty() || - !vitem->_screenRect.intersects(item->_screenRect) - ) { - // add item to draw list, and old rect to erase list - if (!item->_screenRect.isEmpty()) { - drawList.add(item, item->_screenRect); - mergeToRectList(item->_screenRect, eraseList); - } - if ( - i < visiblePlaneItemCount && - vitem != nullptr && - !vitem->_screenRect.isEmpty() - ) { - mergeToRectList(vitem->_screenRect, eraseList); - } - } else { - // otherwise, add bounding box of old+new to erase list, - // and item to draw list + mergeToRectList(visibleItemScreenRect, eraseList); + } else { + eraseList.add(visibleItemScreenRect); + } + } + } - // TODO: This was changed from disasm, verify please! - Common::Rect extendedScreenRect = vitem->_screenRect; - extendedScreenRect.extend(item->_screenRect); + if (!item->_created && !item->_updated) { + continue; + } - drawList.add(item, item->_screenRect); - mergeToRectList(extendedScreenRect, eraseList); - } + item->calcRects(*this); + const Common::Rect itemScreenRect(item->_screenRect); + + if (item->_created) { + // Add item to draw list + if(!itemScreenRect.isEmpty()) { + if (g_sci->_gfxRemap32->getRemapCount()) { + drawList.add(item, itemScreenRect); + mergeToRectList(itemScreenRect, eraseList); } else { - // if no active remaps, just add item to draw list and old rect - // to erase list - if (!item->_screenRect.isEmpty()) { - drawList.add(item, item->_screenRect); + drawList.add(item, itemScreenRect); + } + } + } else { + // Add old rect to erase list, new item to draw list + + if (g_sci->_gfxRemap32->getRemapCount()) { + // If item and visibleItem don't overlap... + if (itemScreenRect.isEmpty() || + visibleItem == nullptr || + visibleItemScreenRect.isEmpty() || + !visibleItemScreenRect.intersects(itemScreenRect) + ) { + // ...add item to draw list, and old rect to erase list... + if (!itemScreenRect.isEmpty()) { + drawList.add(item, itemScreenRect); + mergeToRectList(itemScreenRect, eraseList); } - if ( - i < visiblePlaneItemCount && - vitem != nullptr && - !vitem->_screenRect.isEmpty() - ) { - eraseList.add(vitem->_screenRect); + if (visibleItem != nullptr && !visibleItemScreenRect.isEmpty()) { + mergeToRectList(visibleItemScreenRect, eraseList); } + } else { + // ...otherwise, add bounding box of old+new to erase list, + // and item to draw list + Common::Rect extendedScreenRect = visibleItemScreenRect; + extendedScreenRect.extend(itemScreenRect); + + drawList.add(item, itemScreenRect); + mergeToRectList(extendedScreenRect, eraseList); + } + } else { + // If no active remaps, just add item to draw list and old rect + // to erase list + + // TODO: SCI3 update rects for VMD? + if (!itemScreenRect.isEmpty()) { + drawList.add(item, itemScreenRect); + } + if (visibleItem != nullptr && !visibleItemScreenRect.isEmpty()) { + eraseList.add(visibleItemScreenRect); } } } @@ -395,40 +417,44 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList // We store the current size of the drawlist, as we want to loop // over the currently inserted entries later. DrawList::size_type drawListSizePrimary = drawList.size(); + const RectList::size_type eraseListCount = eraseList.size(); - if (/* TODO: dword_C6288 */ false) { // "high resolution pictures"???? + // TODO: Figure out which games need which rendering method + if (/* TODO: dword_C6288 */ false) { // "high resolution pictures" _screenItemList.sort(); - bool encounteredPic = false; - bool v81 = false; + bool pictureDrawn = false; + bool screenItemDrawn = false; - for (RectList::size_type i = 0; i < eraseList.size(); ++i) { - const Common::Rect *rect = eraseList[i]; + for (RectList::size_type i = 0; i < eraseListCount; ++i) { + const Common::Rect &rect = *eraseList[i]; - for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { + for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { ScreenItem *item = _screenItemList[j]; - if (j < _screenItemList.size() && item != nullptr) { - if (rect->intersects(item->_screenRect)) { - const Common::Rect intersection = rect->findIntersectingRect(item->_screenRect); - if (!item->_deleted) { - if (encounteredPic) { - if (item->_celInfo.type == kCelTypePic) { - if (v81 || item->_celInfo.celNo == 0) { - drawList.add(item, intersection); - } - } else { - if (!item->_updated && !item->_created) { - drawList.add(item, intersection); - } - v81 = true; + if (item == nullptr) { + continue; + } + + if (rect.intersects(item->_screenRect)) { + const Common::Rect intersection = rect.findIntersectingRect(item->_screenRect); + if (!item->_deleted) { + if (pictureDrawn) { + if (item->_celInfo.type == kCelTypePic) { + if (screenItemDrawn || item->_celInfo.celNo == 0) { + mergeToDrawList(j, intersection, drawList); } } else { if (!item->_updated && !item->_created) { - drawList.add(item, intersection); - } - if (item->_celInfo.type == kCelTypePic) { - encounteredPic = true; + mergeToDrawList(j, intersection, drawList); } + screenItemDrawn = true; + } + } else { + if (!item->_updated && !item->_created) { + mergeToDrawList(j, intersection, drawList); + } + if (item->_celInfo.type == kCelTypePic) { + pictureDrawn = true; } } } @@ -438,22 +464,23 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList _screenItemList.unsort(); } else { - // add all items overlapping the erase list to the draw list - for (RectList::size_type i = 0; i < eraseList.size(); ++i) { - for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { + // Add all items overlapping the erase list to the draw list + for (RectList::size_type i = 0; i < eraseListCount; ++i) { + const Common::Rect &rect = *eraseList[i]; + for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { ScreenItem *item = _screenItemList[j]; if ( item != nullptr && !item->_created && !item->_updated && !item->_deleted && - eraseList[i]->intersects(item->_screenRect) + rect.intersects(item->_screenRect) ) { - drawList.add(item, eraseList[i]->findIntersectingRect(item->_screenRect)); + drawList.add(item, rect.findIntersectingRect(item->_screenRect)); } } } } - if (g_sci->_gfxRemap32->getRemapCount() == 0) { // no remaps active? + if (g_sci->_gfxRemap32->getRemapCount() == 0) { // Add all items that overlap with items in the drawlist and have higher // priority. @@ -461,23 +488,28 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList // those that were added because of the erase list in the previous loop, // or those to be added in this loop. for (DrawList::size_type i = 0; i < drawListSizePrimary; ++i) { - DrawItem *dli = drawList[i]; + const DrawItem *drawListEntry = nullptr; + if (i < drawList.size()) { + drawListEntry = drawList[i]; + } - for (ScreenItemList::size_type j = 0; j < planeItemCount; ++j) { - ScreenItem *sli = _screenItemList[j]; + for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { + ScreenItem *newItem = nullptr; + if (j < _screenItemList.size()) { + newItem = _screenItemList[j]; + } if ( - i < drawList.size() && dli != nullptr && - j < _screenItemList.size() && sli != nullptr && - !sli->_created && !sli->_updated && !sli->_deleted + drawListEntry != nullptr && newItem != nullptr && + !newItem->_created && !newItem->_updated && !newItem->_deleted ) { - ScreenItem *item = dli->screenItem; + const ScreenItem *drawnItem = drawListEntry->screenItem; if ( - (sli->_priority > item->_priority || (sli->_priority == item->_priority && sli->_object > item->_object)) && - dli->rect.intersects(sli->_screenRect) + (newItem->_priority > drawnItem->_priority || (newItem->_priority == drawnItem->_priority && newItem->_object > drawnItem->_object)) && + drawListEntry->rect.intersects(newItem->_screenRect) ) { - drawList.add(sli, dli->rect.findIntersectingRect(sli->_screenRect)); + mergeToDrawList(j, drawListEntry->rect.findIntersectingRect(newItem->_screenRect), drawList); } } } @@ -485,14 +517,11 @@ void Plane::calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList } decrementScreenItemArrayCounts(&visiblePlane, false); - _screenItemList.pack(); - visiblePlane._screenItemList.pack(); } void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate) { - // The size of the screenItemList may change, so it is - // critical to re-check the size on each iteration - for (ScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) { + const ScreenItemList::size_type screenItemCount = _screenItemList.size(); + for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) { ScreenItem *item = _screenItemList[i]; if (item != nullptr) { @@ -505,7 +534,7 @@ void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool force visiblePlane->_screenItemList.findByObject(item->_object) != nullptr ) ) { - *visiblePlane->_screenItemList[i] = *_screenItemList[i]; + *visiblePlane->_screenItemList[i] = *item; } if (item->_updated) { @@ -524,175 +553,182 @@ void Plane::decrementScreenItemArrayCounts(Plane *visiblePlane, const bool force if (item->_deleted) { item->_deleted--; if (!item->_deleted) { - visiblePlane->_screenItemList.erase_at(i); + if (visiblePlane != nullptr && visiblePlane->_screenItemList.findByObject(item->_object) != nullptr) { + visiblePlane->_screenItemList.erase_at(i); + } _screenItemList.erase_at(i); } } } } + + _screenItemList.pack(); + if (visiblePlane != nullptr) { + visiblePlane->_screenItemList.pack(); + } } -void Plane::filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const { - if (_type == kPlaneTypeTransparent) { - for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) { - const Common::Rect *r = transparentEraseList[i]; - for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { - ScreenItem *item = _screenItemList[j]; - if (item != nullptr) { - if (r->intersects(item->_screenRect)) { - mergeToDrawList(j, *r, drawList); - } +void Plane::filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &higherEraseList) const { + const RectList::size_type higherEraseCount = higherEraseList.size(); + + if (_type == kPlaneTypeTransparent || _type == kPlaneTypeTransparentPicture) { + for (RectList::size_type i = 0; i < higherEraseCount; ++i) { + const Common::Rect &r = *higherEraseList[i]; + const ScreenItemList::size_type screenItemCount = _screenItemList.size(); + for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { + const ScreenItem *item = _screenItemList[j]; + if (item != nullptr && r.intersects(item->_screenRect)) { + mergeToDrawList(j, r, drawList); } } } } else { - for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) { - Common::Rect *r = transparentEraseList[i]; - if (r->intersects(_screenRect)) { - r->clip(_screenRect); - mergeToRectList(*r, eraseList); - - for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { - ScreenItem *item = _screenItemList[j]; - - if (item != nullptr) { - if (r->intersects(item->_screenRect)) { - mergeToDrawList(j, *r, drawList); - } + for (RectList::size_type i = 0; i < higherEraseCount; ++i) { + Common::Rect r = *higherEraseList[i]; + if (r.intersects(_screenRect)) { + r.clip(_screenRect); + mergeToRectList(r, eraseList); + + const ScreenItemList::size_type screenItemCount = _screenItemList.size(); + for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { + const ScreenItem *item = _screenItemList[j]; + if (item != nullptr && r.intersects(item->_screenRect)) { + mergeToDrawList(j, r, drawList); } } - Common::Rect ptr[4]; - const Common::Rect *r2 = transparentEraseList[i]; - int count = splitRects(*r2, *r, ptr); - for (int k = count - 1; k >= 0; --k) { - transparentEraseList.add(ptr[k]); + Common::Rect outRects[4]; + const Common::Rect &r2 = *higherEraseList[i]; + int splitCount = splitRects(r2, r, outRects); + if (splitCount > 0) { + while (splitCount--) { + higherEraseList.add(outRects[splitCount]); + } } - transparentEraseList.erase_at(i); + higherEraseList.erase_at(i); } } - transparentEraseList.pack(); + higherEraseList.pack(); } } -void Plane::filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const { - for (DrawList::size_type i = 0; i < drawList.size(); ++i) { - const Common::Rect &r = drawList[i]->rect; - - for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { - ScreenItem *item = _screenItemList[j]; - if (item != nullptr) { - if (r.intersects(item->_screenRect)) { - mergeToDrawList(j, r, transparentDrawList); - } +void Plane::filterUpDrawRects(DrawList &drawList, const DrawList &lowerDrawList) const { + const DrawList::size_type lowerDrawCount = lowerDrawList.size(); + for (DrawList::size_type i = 0; i < lowerDrawCount; ++i) { + const Common::Rect &r = lowerDrawList[i]->rect; + const ScreenItemList::size_type screenItemCount = _screenItemList.size(); + for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { + const ScreenItem *item = _screenItemList[j]; + if (item != nullptr && r.intersects(item->_screenRect)) { + mergeToDrawList(j, r, drawList); } } } } -void Plane::filterUpEraseRects(DrawList &drawList, RectList &eraseList) const { - for (RectList::size_type i = 0; i < eraseList.size(); ++i) { - const Common::Rect &r = *eraseList[i]; - for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { - ScreenItem *item = _screenItemList[j]; - - if (item != nullptr) { - if (r.intersects(item->_screenRect)) { - mergeToDrawList(j, r, drawList); - } +void Plane::filterUpEraseRects(DrawList &drawList, const RectList &lowerEraseList) const { + const RectList::size_type lowerEraseCount = lowerEraseList.size(); + for (RectList::size_type i = 0; i < lowerEraseCount; ++i) { + const Common::Rect &r = *lowerEraseList[i]; + const ScreenItemList::size_type screenItemCount = _screenItemList.size(); + for (ScreenItemList::size_type j = 0; j < screenItemCount; ++j) { + const ScreenItem *item = _screenItemList[j]; + if (item != nullptr && r.intersects(item->_screenRect)) { + mergeToDrawList(j, r, drawList); } } } } void Plane::mergeToDrawList(const ScreenItemList::size_type index, const Common::Rect &rect, DrawList &drawList) const { - RectList rects; - - ScreenItem *item = _screenItemList[index]; - Common::Rect r = item->_screenRect; + RectList mergeList; + ScreenItem &item = *_screenItemList[index]; + Common::Rect r = item._screenRect; r.clip(rect); - rects.add(r); + mergeList.add(r); - for (RectList::size_type i = 0; i < rects.size(); ++i) { - r = *rects[i]; + for (RectList::size_type i = 0; i < mergeList.size(); ++i) { + r = *mergeList[i]; - for (DrawList::size_type j = 0; j < drawList.size(); ++j) { - const DrawItem *drawitem = drawList[j]; - if (item->_object == drawitem->screenItem->_object) { - if (drawitem->rect.contains(r)) { - rects.erase_at(i); + const DrawList::size_type drawCount = drawList.size(); + for (DrawList::size_type j = 0; j < drawCount; ++j) { + const DrawItem &drawItem = *drawList[j]; + if (item._object == drawItem.screenItem->_object) { + if (drawItem.rect.contains(r)) { + mergeList.erase_at(i); break; } Common::Rect outRects[4]; - const int count = splitRects(r, drawitem->rect, outRects); - if (count != -1) { - for (int k = count - 1; k >= 0; --k) { - rects.add(outRects[k]); + int splitCount = splitRects(r, drawItem.rect, outRects); + if (splitCount != -1) { + while (splitCount--) { + mergeList.add(outRects[splitCount]); } - rects.erase_at(i); + mergeList.erase_at(i); // proceed to the next rect - r = *rects[++i]; + r = *mergeList[++i]; } } } } - rects.pack(); + mergeList.pack(); - for (RectList::size_type i = 0; i < rects.size(); ++i) { - drawList.add(item, *rects[i]); + for (RectList::size_type i = 0; i < mergeList.size(); ++i) { + drawList.add(&item, *mergeList[i]); } } -void Plane::mergeToRectList(const Common::Rect &rect, RectList &rectList) const { - RectList temp; - temp.add(rect); +void Plane::mergeToRectList(const Common::Rect &rect, RectList &eraseList) const { + RectList mergeList; + Common::Rect r; + mergeList.add(rect); - for (RectList::size_type i = 0; i < temp.size(); ++i) { - Common::Rect r = *temp[i]; + for (RectList::size_type i = 0; i < mergeList.size(); ++i) { + r = *mergeList[i]; - for (RectList::size_type j = 0; j < rectList.size(); ++j) { - const Common::Rect *innerRect = rectList[j]; - if (innerRect->contains(r)) { - temp.erase_at(i); + const RectList::size_type eraseCount = eraseList.size(); + for (RectList::size_type j = 0; j < eraseCount; ++j) { + const Common::Rect &eraseRect = *eraseList[j]; + if (eraseRect.contains(r)) { + mergeList.erase_at(i); break; } - Common::Rect out[4]; - const int count = splitRects(r, *innerRect, out); - if (count != -1) { - for (int k = count - 1; k >= 0; --k) { - temp.add(out[k]); + Common::Rect outRects[4]; + int splitCount = splitRects(r, eraseRect, outRects); + if (splitCount != -1) { + while (splitCount--) { + mergeList.add(outRects[splitCount]); } - temp.erase_at(i); + mergeList.erase_at(i); // proceed to the next rect - r = *temp[++i]; + r = *mergeList[++i]; } } } - temp.pack(); + mergeList.pack(); - for (RectList::size_type i = 0; i < temp.size(); ++i) { - rectList.add(*temp[i]); + for (RectList::size_type i = 0; i < mergeList.size(); ++i) { + eraseList.add(*mergeList[i]); } } void Plane::redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList) { - for (ScreenItemList::const_iterator screenItemPtr = _screenItemList.begin(); screenItemPtr != _screenItemList.end(); ++screenItemPtr) { - if (*screenItemPtr != nullptr) { - ScreenItem &screenItem = **screenItemPtr; - if (!screenItem._deleted) { - screenItem.calcRects(*this); - if (!screenItem._screenRect.isEmpty()) { - drawList.add(&screenItem, screenItem._screenRect); - } + const ScreenItemList::size_type screenItemCount = _screenItemList.size(); + for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) { + ScreenItem *screenItem = _screenItemList[i]; + if (screenItem != nullptr && !screenItem->_deleted) { + screenItem->calcRects(*this); + if (!screenItem->_screenRect.isEmpty()) { + mergeToDrawList(i, screenItem->_screenRect, drawList); } } } @@ -706,21 +742,27 @@ void Plane::redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList breakDrawListByPlanes(drawList, planeList); --_redrawAllCount; decrementScreenItemArrayCounts(visiblePlane, true); - _screenItemList.pack(); - if (visiblePlane != nullptr) { - visiblePlane->_screenItemList.pack(); - } } void Plane::setType() { - if (_pictureId == kPlanePicOpaque) { - _type = kPlaneTypeOpaque; - } else if (_pictureId == kPlanePicTransparent) { - _type = kPlaneTypeTransparent; - } else if (_pictureId == kPlanePicColored) { + switch (_pictureId) { + case kPlanePicColored: _type = kPlaneTypeColored; - } else { - _type = kPlaneTypePicture; + break; + case kPlanePicTransparent: + _type = kPlaneTypeTransparent; + break; + case kPlanePicOpaque: + _type = kPlaneTypeOpaque; + break; + case kPlanePicTransparentPicture: + _type = kPlaneTypeTransparentPicture; + break; + default: + if (_type != kPlaneTypeTransparentPicture) { + _type = kPlaneTypePicture; + } + break; } } @@ -741,10 +783,12 @@ void Plane::sync(const Plane *other, const Common::Rect &screenRect) { _planeRect.right > other->_planeRect.right || _planeRect.bottom > other->_planeRect.bottom ) { + // the plane moved or got larger _redrawAllCount = g_sci->_gfxFrameout->getScreenCount(); - _updated = g_sci->_gfxFrameout->getScreenCount(); + _moved = g_sci->_gfxFrameout->getScreenCount(); } else if (_planeRect != other->_planeRect) { - _updated = g_sci->_gfxFrameout->getScreenCount(); + // the plane got smaller + _moved = g_sci->_gfxFrameout->getScreenCount(); } if (_priority != other->_priority) { @@ -765,12 +809,10 @@ void Plane::sync(const Plane *other, const Common::Rect &screenRect) { _deleted = 0; if (_created == 0) { - _moved = g_sci->_gfxFrameout->getScreenCount(); + _updated = g_sci->_gfxFrameout->getScreenCount(); } convertGameRectToPlaneRect(); - _width = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; - _height = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; _screenRect = _planeRect; // NOTE: screenRect originally was retrieved through globals // instead of being passed into the function @@ -813,18 +855,22 @@ void Plane::scrollScreenItems(const int16 deltaX, const int16 deltaY, const bool } void Plane::remapMarkRedraw() { - for (ScreenItemList::const_iterator screenItemPtr = _screenItemList.begin(); screenItemPtr != _screenItemList.end(); ++screenItemPtr) { - if (*screenItemPtr != nullptr) { - ScreenItem &screenItem = **screenItemPtr; - if (screenItem.getCelObj()._remap && !screenItem._deleted && !screenItem._created) { - screenItem._updated = g_sci->_gfxFrameout->getScreenCount(); - } + ScreenItemList::size_type screenItemCount = _screenItemList.size(); + for (ScreenItemList::size_type i = 0; i < screenItemCount; ++i) { + ScreenItem *screenItem = _screenItemList[i]; + if ( + screenItem != nullptr && + !screenItem->_deleted && !screenItem->_created && + screenItem->getCelObj()._remap + ) { + screenItem->_updated = g_sci->_gfxFrameout->getScreenCount(); } } } #pragma mark - #pragma mark PlaneList + void PlaneList::add(Plane *plane) { for (iterator it = begin(); it != end(); ++it) { if ((*it)->_priority > plane->_priority) { diff --git a/engines/sci/graphics/plane32.h b/engines/sci/graphics/plane32.h index 770a6fa445..3981a2b319 100644 --- a/engines/sci/graphics/plane32.h +++ b/engines/sci/graphics/plane32.h @@ -32,19 +32,21 @@ namespace Sci { enum PlaneType { - kPlaneTypeColored = 0, - kPlaneTypePicture = 1, - kPlaneTypeTransparent = 2, - kPlaneTypeOpaque = 3 + kPlaneTypeColored = 0, + kPlaneTypePicture = 1, + kPlaneTypeTransparent = 2, + kPlaneTypeOpaque = 3, + kPlaneTypeTransparentPicture = 4 }; enum PlanePictureCodes { - // NOTE: Any value at or below 65532 means the plane + // NOTE: Any value at or below 65531 means the plane // is a kPlaneTypePicture. - kPlanePic = 65532, - kPlanePicOpaque = 65533, - kPlanePicTransparent = 65534, - kPlanePicColored = 65535 + kPlanePic = 65531, + kPlanePicTransparentPicture = 65532, + kPlanePicOpaque = 65533, + kPlanePicTransparent = 65534, + kPlanePicColored = 65535 }; #pragma mark - @@ -62,7 +64,14 @@ public: #pragma mark DrawList struct DrawItem { + /** + * The screen item to draw. + */ ScreenItem *screenItem; + + /** + * The target rectangle of the draw operation. + */ Common::Rect rect; inline bool operator<(const DrawItem &other) const { @@ -101,16 +110,6 @@ private: static uint16 _nextObjectId; /** - * The dimensions of the plane, in game script - * coordinates. - * TODO: These are never used and are always - * scriptWidth x scriptHeight in SCI engine? The actual - * dimensions of the plane are always in - * gameRect/planeRect. - */ - int16 _width, _height; - - /** * For planes that are used to render picture data, the * resource ID of the picture to be displayed. This * value may also be one of the special @@ -135,10 +134,6 @@ private: */ bool _pictureChanged; - // TODO: Are these ever actually used? - int _field_34, _field_38; // probably a point or ratio - int _field_3C, _field_40; // probably a point or ratio - /** * Converts the dimensions of the game rect used by * scripts to the dimensions of the plane rect used to @@ -155,6 +150,11 @@ private: public: /** + * The type of the plane. + */ + PlaneType _type; + + /** * The color to use when erasing the plane. Only * applies to planes of type kPlaneTypeColored. */ @@ -191,8 +191,6 @@ public: */ int _redrawAllCount; - PlaneType _type; - /** * Flags indicating the state of the plane. * - `created` is set when the plane is first created, @@ -203,16 +201,15 @@ public: * not match * - `deleted` is set when the plane is deleted by a * kernel call - * - `moved` is set when the plane is synchronised from - * another plane and is not already in the "created" - * state + * - `moved` is set when the plane has been moved or + * resized */ int _created, _updated, _deleted, _moved; /** * The vanishing point for the plane. Used when - * calculating the correct scaling of the plane's screen - * items according to their position. + * automatically calculating the correct scaling of the + * plane's screen items according to their position. */ Common::Point _vanishingPoint; @@ -275,7 +272,11 @@ public: * given screen rect. */ inline void clipScreenRect(const Common::Rect &screenRect) { - if (_screenRect.intersects(screenRect)) { + // LSL6 hires creates planes with invalid rects; SSCI does not + // care about this, but `Common::Rect::clip` does, so we need to + // check whether or not the rect is actually valid before clipping + // and only clip valid rects + if (_screenRect.isValidRect() && _screenRect.intersects(screenRect)) { _screenRect.clip(screenRect); } else { _screenRect.left = 0; @@ -368,42 +369,33 @@ public: private: /** * Splits all rects in the given draw list at the edges - * of all non-transparent planes above the current - * plane. + * of all higher-priority, non-transparent, intersecting + * planes. */ void breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const; /** - * Splits all rects in the given erase list rects at the - * edges of all non-transparent planes above the current - * plane. + * Splits all rects in the given erase list at the + * edges of higher-priority, non-transparent, + * intersecting planes. */ void breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const; /** - * Synchronises changes to screen items from the current - * plane to the visible plane and deletes screen items - * from the current plane that have been marked as - * deleted. If `forceUpdate` is true, all screen items - * on the visible plane will be updated, even if they - * are not marked as having changed. - */ - void decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate); - - /** - * Merges the screen item from this plane at the given - * index into the given draw list, clipped to the given - * rect. TODO: Finish documenting + * Adds the screen item at `index` into `drawList`, + * ensuring it is only drawn within the bounds of + * `rect`. If an existing draw list entry exists + * for this screen item, it will be modified. + * Otherwise, a new entry will be added. */ void mergeToDrawList(const DrawList::size_type index, const Common::Rect &rect, DrawList &drawList) const; /** - * Adds the given rect into the given rect list, - * merging it with other rects already inside the list, - * if possible, to avoid overdraw. TODO: Finish - * documenting + * Merges `rect` with an existing rect in `eraseList`, + * if possible. Otherwise, adds the rect as a new entry + * to `eraseList`. */ - void mergeToRectList(const Common::Rect &rect, RectList &rectList) const; + void mergeToRectList(const Common::Rect &rect, RectList &eraseList) const; public: /** @@ -416,19 +408,73 @@ public: void calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList); /** - * TODO: Documentation + * Synchronises changes to screen items from the current + * plane to the visible plane and deletes screen items + * from the current plane that have been marked as + * deleted. If `forceUpdate` is true, all screen items + * on the visible plane will be updated, even if they + * are not marked as having changed. + */ + void decrementScreenItemArrayCounts(Plane *visiblePlane, const bool forceUpdate); + + /** + * This method is called from the highest priority plane + * to the lowest priority plane. + * + * Adds screen items from this plane to the draw list + * that must be redrawn because they intersect entries + * in the `higherEraseList`. + * + * If this plane is opaque, all intersecting erase rects + * in `lowerEraseList` are removed, as they would be + * completely overwritten by the contents of this plane. + * + * If this plane is transparent, erase rects from the + * `lowerEraseList` are added to the erase list for this + * plane, so that lower planes. + * + * @param drawList The draw list for this plane. + * @param eraseList The erase list for this plane. + * @param higherEraseList The erase list for a plane + * above this plane. */ - void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const; + void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &higherEraseList) const; /** - * TODO: Documentation + * This method is called from the lowest priority plane + * to the highest priority plane. + * + * Adds screen items from this plane to the draw list + * that must be drawn because the lower plane is being + * redrawn and potentially transparent screen items + * from this plane would draw over the lower priority + * plane's screen items. + * + * This method applies only to transparent planes. + * + * @param drawList The draw list for this plane. + * @param eraseList The erase list for a plane below + * this plane. */ - void filterUpEraseRects(DrawList &drawList, RectList &eraseList) const; + void filterUpEraseRects(DrawList &drawList, const RectList &lowerEraseList) const; /** - * TODO: Documentation + * This method is called from the lowest priority plane + * to the highest priority plane. + * + * Adds screen items from this plane to the draw list + * that must be drawn because the lower plane is being + * redrawn and potentially transparent screen items + * from this plane would draw over the lower priority + * plane's screen items. + * + * This method applies only to transparent planes. + * + * @param drawList The draw list for this plane. + * @param lowerDrawList The draw list for a plane below + * this plane. */ - void filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const; + void filterUpDrawRects(DrawList &drawList, const DrawList &lowerDrawList) const; /** * Updates all of the plane's non-deleted screen items diff --git a/engines/sci/graphics/remap.cpp b/engines/sci/graphics/remap.cpp index e331eaf971..2abf03ea29 100644 --- a/engines/sci/graphics/remap.cpp +++ b/engines/sci/graphics/remap.cpp @@ -21,31 +21,23 @@ */ #include "sci/sci.h" -#include "sci/resource.h" #include "sci/graphics/palette.h" -#include "sci/graphics/palette32.h" #include "sci/graphics/remap.h" #include "sci/graphics/screen.h" namespace Sci { -#pragma mark - -#pragma mark SCI16 remapping (QFG4 demo) - GfxRemap::GfxRemap(GfxPalette *palette) : _palette(palette) { _remapOn = false; resetRemapping(); } -GfxRemap::~GfxRemap() { -} - byte GfxRemap::remapColor(byte remappedColor, byte screenColor) { assert(_remapOn); - if (_remappingType[remappedColor] == kRemappingByRange) + if (_remappingType[remappedColor] == kRemapByRange) return _remappingByRange[screenColor]; - else if (_remappingType[remappedColor] == kRemappingByPercent) + else if (_remappingType[remappedColor] == kRemapByPercent) return _remappingByPercent[screenColor]; else error("remapColor(): Color %d isn't remapped", remappedColor); @@ -58,7 +50,7 @@ void GfxRemap::resetRemapping() { _remappingPercentToSet = 0; for (int i = 0; i < 256; i++) { - _remappingType[i] = kRemappingNone; + _remappingType[i] = kRemapNone; _remappingByPercent[i] = i; _remappingByRange[i] = i; } @@ -80,7 +72,7 @@ void GfxRemap::setRemappingPercent(byte color, byte percent) { _remappingByPercent[i] = _palette->kernelFindColor(r, g, b); } - _remappingType[color] = kRemappingByPercent; + _remappingType[color] = kRemapByPercent; } void GfxRemap::setRemappingRange(byte color, byte from, byte to, byte base) { @@ -90,7 +82,7 @@ void GfxRemap::setRemappingRange(byte color, byte from, byte to, byte base) { _remappingByRange[i] = i + base; } - _remappingType[color] = kRemappingByRange; + _remappingType[color] = kRemapByRange; } void GfxRemap::updateRemapping() { @@ -104,283 +96,4 @@ void GfxRemap::updateRemapping() { } } } - -#pragma mark - -#pragma mark SCI32 remapping - -#ifdef ENABLE_SCI32 - -GfxRemap32::GfxRemap32(GfxPalette32 *palette) : _palette(palette) { - for (int i = 0; i < REMAP_COLOR_COUNT; i++) - _remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemappingNone); - _noMapStart = _noMapCount = 0; - _update = false; - _remapCount = 0; - - // The remap range was 245 - 254 in SCI2, but was changed to 235 - 244 in SCI21 middle - _remapEndColor = (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) ? 244 : 254; -} - -void GfxRemap32::remapOff(byte color) { - if (!color) { - for (int i = 0; i < REMAP_COLOR_COUNT; i++) - _remaps[i] = RemapParams(0, 0, 0, 0, 100, kRemappingNone); - - _remapCount = 0; - } else { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - const byte index = _remapEndColor - color; - _remaps[index] = RemapParams(0, 0, 0, 0, 100, kRemappingNone); - _remapCount--; - } - - _update = true; -} - -void GfxRemap32::setRemappingRange(byte color, byte from, byte to, byte base) { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - _remaps[_remapEndColor - color] = RemapParams(from, to, base, 0, 100, kRemappingByRange); - initColorArrays(_remapEndColor - color); - _remapCount++; - _update = true; -} - -void GfxRemap32::setRemappingPercent(byte color, byte percent) { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, 0, percent, kRemappingByPercent); - initColorArrays(_remapEndColor - color); - _remapCount++; - _update = true; -} - -void GfxRemap32::setRemappingToGray(byte color, byte gray) { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, 100, kRemappingToGray); - initColorArrays(_remapEndColor - color); - _remapCount++; - _update = true; -} - -void GfxRemap32::setRemappingToPercentGray(byte color, byte gray, byte percent) { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - _remaps[_remapEndColor - color] = RemapParams(0, 0, 0, gray, percent, kRemappingToPercentGray); - initColorArrays(_remapEndColor - color); - _remapCount++; - _update = true; -} - -void GfxRemap32::setNoMatchRange(byte from, byte count) { - _noMapStart = from; - _noMapCount = count; -} - -bool GfxRemap32::remapEnabled(byte color) const { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - const byte index = _remapEndColor - color; - return (_remaps[index].type != kRemappingNone); -} - -byte GfxRemap32::remapColor(byte color, byte target) { - assert(_remapEndColor - color >= 0 && _remapEndColor - color < REMAP_COLOR_COUNT); - const byte index = _remapEndColor - color; - if (_remaps[index].type != kRemappingNone) - return _remaps[index].remap[target]; - else - return target; -} - -void GfxRemap32::initColorArrays(byte index) { - Palette *curPalette = &_palette->_sysPalette; - RemapParams *curRemap = &_remaps[index]; - - memcpy(curRemap->curColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color)); - memcpy(curRemap->targetColor, curPalette->colors, NON_REMAPPED_COLOR_COUNT * sizeof(Color)); -} - -bool GfxRemap32::updateRemap(byte index, bool palChanged) { - int result; - RemapParams *curRemap = &_remaps[index]; - const Palette *curPalette = &_palette->_sysPalette; - const Palette *nextPalette = _palette->getNextPalette(); - bool changed = false; - - if (!_update && !palChanged) - return false; - - Common::fill(_targetChanged, _targetChanged + NON_REMAPPED_COLOR_COUNT, false); - - switch (curRemap->type) { - case kRemappingNone: - return false; - case kRemappingByRange: - for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) { - if (curRemap->from <= i && i <= curRemap->to) - result = i + curRemap->base; - else - result = i; - - if (curRemap->remap[i] != result) { - changed = true; - curRemap->remap[i] = result; - } - - curRemap->colorChanged[i] = true; - } - return changed; - case kRemappingByPercent: - for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { - // NOTE: This method uses nextPalette instead of curPalette - Color color = nextPalette->colors[i]; - - if (curRemap->curColor[i] != color) { - curRemap->colorChanged[i] = true; - curRemap->curColor[i] = color; - } - - if (curRemap->percent != curRemap->oldPercent || curRemap->colorChanged[i]) { - byte red = CLIP<byte>(color.r * curRemap->percent / 100, 0, 255); - byte green = CLIP<byte>(color.g * curRemap->percent / 100, 0, 255); - byte blue = CLIP<byte>(color.b * curRemap->percent / 100, 0, 255); - byte used = curRemap->targetColor[i].used; - - Color newColor = { used, red, green, blue }; - if (curRemap->targetColor[i] != newColor) { - _targetChanged[i] = true; - curRemap->targetColor[i] = newColor; - } - } - } - - changed = applyRemap(index); - Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false); - curRemap->oldPercent = curRemap->percent; - return changed; - case kRemappingToGray: - for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { - Color color = curPalette->colors[i]; - - if (curRemap->curColor[i] != color) { - curRemap->colorChanged[i] = true; - curRemap->curColor[i] = color; - } - - if (curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i]) { - byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8; - byte red = CLIP<byte>(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255); - byte green = CLIP<byte>(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255); - byte blue = CLIP<byte>(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255); - byte used = curRemap->targetColor[i].used; - - Color newColor = { used, red, green, blue }; - if (curRemap->targetColor[i] != newColor) { - _targetChanged[i] = true; - curRemap->targetColor[i] = newColor; - } - } - } - - changed = applyRemap(index); - Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false); - curRemap->oldGray = curRemap->gray; - return changed; - case kRemappingToPercentGray: - for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { - Color color = curPalette->colors[i]; - - if (curRemap->curColor[i] != color) { - curRemap->colorChanged[i] = true; - curRemap->curColor[i] = color; - } - - if (curRemap->percent != curRemap->oldPercent || curRemap->gray != curRemap->oldGray || curRemap->colorChanged[i]) { - byte lumosity = ((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8; - lumosity = lumosity * curRemap->percent / 100; - byte red = CLIP<byte>(color.r - ((color.r - lumosity) * curRemap->gray / 100), 0, 255); - byte green = CLIP<byte>(color.g - ((color.g - lumosity) * curRemap->gray / 100), 0, 255); - byte blue = CLIP<byte>(color.b - ((color.b - lumosity) * curRemap->gray / 100), 0, 255); - byte used = curRemap->targetColor[i].used; - - Color newColor = { used, red, green, blue }; - if (curRemap->targetColor[i] != newColor) { - _targetChanged[i] = true; - curRemap->targetColor[i] = newColor; - } - } - } - - changed = applyRemap(index); - Common::fill(curRemap->colorChanged, curRemap->colorChanged + NON_REMAPPED_COLOR_COUNT, false); - curRemap->oldPercent = curRemap->percent; - curRemap->oldGray = curRemap->gray; - return changed; - default: - return false; - } -} - -static int colorDistance(Color a, Color b) { - int rDiff = (a.r - b.r) * (a.r - b.r); - int gDiff = (a.g - b.g) * (a.g - b.g); - int bDiff = (a.b - b.b) * (a.b - b.b); - return rDiff + gDiff + bDiff; -} - -bool GfxRemap32::applyRemap(byte index) { - RemapParams *curRemap = &_remaps[index]; - const bool *cycleMap = _palette->getCyclemap(); - bool unmappedColors[NON_REMAPPED_COLOR_COUNT]; - Color newColors[NON_REMAPPED_COLOR_COUNT]; - bool changed = false; - - Common::fill(unmappedColors, unmappedColors + NON_REMAPPED_COLOR_COUNT, false); - if (_noMapCount) - Common::fill(unmappedColors + _noMapStart, unmappedColors + _noMapStart + _noMapCount, true); - - for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) { - if (cycleMap[i]) - unmappedColors[i] = true; - } - - int curColor = 0; - for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { - if (curRemap->colorChanged[i] && !unmappedColors[i]) - newColors[curColor++] = curRemap->curColor[i]; - } - - for (int i = 1; i < NON_REMAPPED_COLOR_COUNT; i++) { - Color targetColor = curRemap->targetColor[i]; - bool colorChanged = curRemap->colorChanged[curRemap->remap[i]]; - - if (!_targetChanged[i] && !colorChanged) - continue; - - if (_targetChanged[i] && colorChanged) - if (curRemap->distance[i] < 100 && colorDistance(targetColor, curRemap->curColor[curRemap->remap[i]]) <= curRemap->distance[i]) - continue; - - int diff = 0; - int16 result = _palette->matchColor(targetColor.r, targetColor.g, targetColor.b, curRemap->distance[i], diff, unmappedColors); - if (result != -1 && curRemap->remap[i] != result) { - changed = true; - curRemap->remap[i] = result; - curRemap->distance[i] = diff; - } - } - - return changed; -} - -bool GfxRemap32::remapAllTables(bool palChanged) { - bool changed = false; - - for (int i = 0; i < REMAP_COLOR_COUNT; i++) { - changed |= updateRemap(i, palChanged); - } - - _update = false; - return changed; -} - -#endif - } // End of namespace Sci diff --git a/engines/sci/graphics/remap.h b/engines/sci/graphics/remap.h index d012568f7f..98177f6d19 100644 --- a/engines/sci/graphics/remap.h +++ b/engines/sci/graphics/remap.h @@ -24,42 +24,36 @@ #define SCI_GRAPHICS_REMAP_H #include "common/array.h" -#include "sci/graphics/helpers.h" +#include "common/serializer.h" namespace Sci { class GfxScreen; -enum ColorRemappingType { - kRemappingNone = 0, - kRemappingByRange = 1, - kRemappingByPercent = 2, - kRemappingToGray = 3, - kRemappingToPercentGray = 4 -}; - -#define REMAP_COLOR_COUNT 9 -#define NON_REMAPPED_COLOR_COUNT 236 - /** - * Remap class, handles color remapping + * This class handles color remapping for the QFG4 demo. */ class GfxRemap { +private: + enum ColorRemappingType { + kRemapNone = 0, + kRemapByRange = 1, + kRemapByPercent = 2 + }; + public: GfxRemap(GfxPalette *_palette); - ~GfxRemap(); void resetRemapping(); void setRemappingPercent(byte color, byte percent); void setRemappingRange(byte color, byte from, byte to, byte base); bool isRemapped(byte color) const { - return _remapOn && (_remappingType[color] != kRemappingNone); + return _remapOn && (_remappingType[color] != kRemapNone); } byte remapColor(byte remappedColor, byte screenColor); void updateRemapping(); private: - GfxScreen *_screen; GfxPalette *_palette; bool _remapOn; @@ -68,87 +62,6 @@ private: byte _remappingByRange[256]; uint16 _remappingPercentToSet; }; - -#ifdef ENABLE_SCI32 - -struct RemapParams { - byte from; - byte to; - byte base; - byte gray; - byte oldGray; - byte percent; - byte oldPercent; - ColorRemappingType type; - Color curColor[256]; - Color targetColor[256]; - byte distance[256]; - byte remap[256]; - bool colorChanged[256]; - - RemapParams() { - from = to = base = gray = oldGray = percent = oldPercent = 0; - type = kRemappingNone; - - // curColor and targetColor are initialized in GfxRemap32::initColorArrays - memset(curColor, 0, 256 * sizeof(Color)); - memset(targetColor, 0, 256 * sizeof(Color)); - memset(distance, 0, 256); - for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) - remap[i] = i; - Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true); - } - - RemapParams(byte from_, byte to_, byte base_, byte gray_, byte percent_, ColorRemappingType type_) { - from = from_; - to = to_; - base = base_; - gray = oldGray = gray_; - percent = oldPercent = percent_; - type = type_; - - // curColor and targetColor are initialized in GfxRemap32::initColorArrays - memset(curColor, 0, 256 * sizeof(Color)); - memset(targetColor, 0, 256 * sizeof(Color)); - memset(distance, 0, 256); - for (int i = 0; i < NON_REMAPPED_COLOR_COUNT; i++) - remap[i] = i; - Common::fill(colorChanged, colorChanged + ARRAYSIZE(colorChanged), true); - } -}; - -class GfxRemap32 { -public: - GfxRemap32(GfxPalette32 *palette); - ~GfxRemap32() {} - - void remapOff(byte color); - void setRemappingRange(byte color, byte from, byte to, byte base); - void setRemappingPercent(byte color, byte percent); - void setRemappingToGray(byte color, byte gray); - void setRemappingToPercentGray(byte color, byte gray, byte percent); - void setNoMatchRange(byte from, byte count); - bool remapEnabled(byte color) const; - byte remapColor(byte color, byte target); - bool remapAllTables(bool palChanged); - int getRemapCount() const { return _remapCount; } - int getStartColor() const { return _remapEndColor - REMAP_COLOR_COUNT + 1; } - int getEndColor() const { return _remapEndColor; } -private: - GfxPalette32 *_palette; - RemapParams _remaps[REMAP_COLOR_COUNT]; - bool _update; - byte _noMapStart, _noMapCount; - bool _targetChanged[NON_REMAPPED_COLOR_COUNT]; - byte _remapEndColor; - int _remapCount; - - void initColorArrays(byte index); - bool applyRemap(byte index); - bool updateRemap(byte index, bool palChanged); -}; -#endif - } // End of namespace Sci #endif diff --git a/engines/sci/graphics/remap32.cpp b/engines/sci/graphics/remap32.cpp new file mode 100644 index 0000000000..d5a2362f14 --- /dev/null +++ b/engines/sci/graphics/remap32.cpp @@ -0,0 +1,468 @@ +/* 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 "sci/sci.h" +#include "sci/graphics/palette32.h" +#include "sci/graphics/remap32.h" + +namespace Sci { + +#pragma mark SingleRemap + +void SingleRemap::reset() { + _lastPercent = 100; + _lastGray = 0; + + const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); + const Palette ¤tPalette = g_sci->_gfxPalette32->getCurrentPalette(); + for (uint i = 0; i < remapStartColor; ++i) { + const Color &color = currentPalette.colors[i]; + _remapColors[i] = i; + _originalColors[i] = color; + _originalColorsChanged[i] = true; + _idealColors[i] = color; + _idealColorsChanged[i] = false; + _matchDistances[i] = 0; + } +} + +bool SingleRemap::update() { + switch (_type) { + case kRemapNone: + break; + case kRemapByRange: + return updateRange(); + case kRemapByPercent: + return updateBrightness(); + case kRemapToGray: + return updateSaturation(); + case kRemapToPercentGray: + return updateSaturationAndBrightness(); + default: + error("Illegal remap type %d", _type); + } + + return false; +} + +bool SingleRemap::updateRange() { + const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); + bool updated = false; + + for (uint i = 0; i < remapStartColor; ++i) { + uint8 targetColor; + if (_from <= i && i <= _to) { + targetColor = i + _delta; + } else { + targetColor = i; + } + + if (_remapColors[i] != targetColor) { + updated = true; + _remapColors[i] = targetColor; + } + + _originalColorsChanged[i] = true; + } + + return updated; +} + +bool SingleRemap::updateBrightness() { + const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); + const Palette &nextPalette = g_sci->_gfxPalette32->getNextPalette(); + for (uint i = 1; i < remapStartColor; ++i) { + Color color(nextPalette.colors[i]); + + if (_originalColors[i] != color) { + _originalColorsChanged[i] = true; + _originalColors[i] = color; + } + + if (_percent != _lastPercent || _originalColorsChanged[i]) { + // NOTE: SSCI checked if percent was over 100 and only + // then clipped values, but we always unconditionally + // ensure the result is in the correct range + color.r = MIN(255, (uint16)color.r * _percent / 100); + color.g = MIN(255, (uint16)color.g * _percent / 100); + color.b = MIN(255, (uint16)color.b * _percent / 100); + + if (_idealColors[i] != color) { + _idealColorsChanged[i] = true; + _idealColors[i] = color; + } + } + } + + const bool updated = apply(); + Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false); + Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false); + _lastPercent = _percent; + return updated; +} + +bool SingleRemap::updateSaturation() { + const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); + const Palette ¤tPalette = g_sci->_gfxPalette32->getCurrentPalette(); + for (uint i = 1; i < remapStartColor; ++i) { + Color color(currentPalette.colors[i]); + if (_originalColors[i] != color) { + _originalColorsChanged[i] = true; + _originalColors[i] = color; + } + + if (_gray != _lastGray || _originalColorsChanged[i]) { + const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100; + + color.r = MIN(255, color.r - ((color.r - luminosity) * _gray / 100)); + color.g = MIN(255, color.g - ((color.g - luminosity) * _gray / 100)); + color.b = MIN(255, color.b - ((color.b - luminosity) * _gray / 100)); + + if (_idealColors[i] != color) { + _idealColorsChanged[i] = true; + _idealColors[i] = color; + } + } + } + + const bool updated = apply(); + Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false); + Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false); + _lastGray = _gray; + return updated; +} + +bool SingleRemap::updateSaturationAndBrightness() { + const uint8 remapStartColor = g_sci->_gfxRemap32->getStartColor(); + const Palette ¤tPalette = g_sci->_gfxPalette32->getCurrentPalette(); + for (uint i = 1; i < remapStartColor; i++) { + Color color(currentPalette.colors[i]); + if (_originalColors[i] != color) { + _originalColorsChanged[i] = true; + _originalColors[i] = color; + } + + if (_percent != _lastPercent || _gray != _lastGray || _originalColorsChanged[i]) { + const int luminosity = (((color.r * 77) + (color.g * 151) + (color.b * 28)) >> 8) * _percent / 100; + + color.r = MIN(255, color.r - ((color.r - luminosity) * _gray) / 100); + color.g = MIN(255, color.g - ((color.g - luminosity) * _gray) / 100); + color.b = MIN(255, color.b - ((color.b - luminosity) * _gray) / 100); + + if (_idealColors[i] != color) { + _idealColorsChanged[i] = true; + _idealColors[i] = color; + } + } + } + + const bool updated = apply(); + Common::fill(_originalColorsChanged, _originalColorsChanged + remapStartColor, false); + Common::fill(_idealColorsChanged, _idealColorsChanged + remapStartColor, false); + _lastPercent = _percent; + _lastGray = _gray; + return updated; +} + +bool SingleRemap::apply() { + const GfxRemap32 *const gfxRemap32 = g_sci->_gfxRemap32; + const uint8 remapStartColor = gfxRemap32->getStartColor(); + + // Blocked colors are not allowed to be used as target + // colors for the remap + bool blockedColors[236]; + Common::fill(blockedColors, blockedColors + remapStartColor, false); + + const bool *const paletteCycleMap = g_sci->_gfxPalette32->getCycleMap(); + + const int16 blockedRangeCount = gfxRemap32->getBlockedRangeCount(); + if (blockedRangeCount) { + const uint8 blockedRangeStart = gfxRemap32->getBlockedRangeStart(); + Common::fill(blockedColors + blockedRangeStart, blockedColors + blockedRangeStart + blockedRangeCount, true); + } + + for (uint i = 0; i < remapStartColor; ++i) { + if (paletteCycleMap[i]) { + blockedColors[i] = true; + } + } + + // NOTE: SSCI did a loop over colors here to create a + // new array of updated, unblocked colors, but then + // never used it + + bool updated = false; + for (uint i = 1; i < remapStartColor; ++i) { + int distance; + + if (!_idealColorsChanged[i] && !_originalColorsChanged[_remapColors[i]]) { + continue; + } + + if ( + _idealColorsChanged[i] && + _originalColorsChanged[_remapColors[i]] && + _matchDistances[i] < 100 && + colorDistance(_idealColors[i], _originalColors[_remapColors[i]]) <= _matchDistances[i] + ) { + continue; + } + + const int16 bestColor = matchColor(_idealColors[i], _matchDistances[i], distance, blockedColors); + + if (bestColor != -1 && _remapColors[i] != bestColor) { + updated = true; + _remapColors[i] = bestColor; + _matchDistances[i] = distance; + } + } + + return updated; +} + +int SingleRemap::colorDistance(const Color &a, const Color &b) const { + int channelDistance = a.r - b.r; + int distance = channelDistance * channelDistance; + channelDistance = a.g - b.g; + distance += channelDistance * channelDistance; + channelDistance = a.b - b.b; + distance += channelDistance * channelDistance; + return distance; +} + +int16 SingleRemap::matchColor(const Color &color, const int minimumDistance, int &outDistance, const bool *const blockedIndexes) const { + int16 bestIndex = -1; + int bestDistance = 0xFFFFF; + int distance = minimumDistance; + const Palette &nextPalette = g_sci->_gfxPalette32->getNextPalette(); + + for (uint i = 0, channelDistance; i < g_sci->_gfxRemap32->getStartColor(); ++i) { + if (blockedIndexes[i]) { + continue; + } + + distance = nextPalette.colors[i].r - color.r; + distance *= distance; + if (bestDistance <= distance) { + continue; + } + channelDistance = nextPalette.colors[i].g - color.g; + distance += channelDistance * channelDistance; + if (bestDistance <= distance) { + continue; + } + channelDistance = nextPalette.colors[i].b - color.b; + distance += channelDistance * channelDistance; + if (bestDistance <= distance) { + continue; + } + bestDistance = distance; + bestIndex = i; + } + + // This value is only valid if the last index to + // perform a distance calculation was the best index + outDistance = distance; + return bestIndex; +} + +#pragma mark - +#pragma mark GfxRemap32 + +GfxRemap32::GfxRemap32() : + _needsUpdate(false), + _blockedRangeStart(0), + _blockedRangeCount(0), + _remapStartColor(236), + _numActiveRemaps(0) { + // The `_remapStartColor` seems to always be 236 in SSCI, + // but if it is ever changed then the various C-style + // member arrays hard-coded to 236 need to be changed to + // match the highest possible value of `_remapStartColor` + assert(_remapStartColor == 236); + + if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE || g_sci->getGameId() == GID_KQ7) { + _remaps.resize(9); + } else { + _remaps.resize(19); + } + + _remapEndColor = _remapStartColor + _remaps.size() - 1; +} + +void GfxRemap32::remapOff(const uint8 color) { + if (color == 0) { + remapAllOff(); + return; + } + + // NOTE: SSCI simply ignored invalid input values, but + // we at least give a warning so games can be investigated + // for script bugs + if (color < _remapStartColor || color > _remapEndColor) { + warning("GfxRemap32::remapOff: %d out of remap range", color); + return; + } + + const uint8 index = _remapEndColor - color; + SingleRemap &singleRemap = _remaps[index]; + singleRemap._type = kRemapNone; + --_numActiveRemaps; + _needsUpdate = true; +} + +void GfxRemap32::remapAllOff() { + for (uint i = 0, len = _remaps.size(); i < len; ++i) { + _remaps[i]._type = kRemapNone; + } + + _numActiveRemaps = 0; + _needsUpdate = true; +} + +void GfxRemap32::remapByRange(const uint8 color, const int16 from, const int16 to, const int16 delta) { + // NOTE: SSCI simply ignored invalid input values, but + // we at least give a warning so games can be investigated + // for script bugs + if (color < _remapStartColor || color > _remapEndColor) { + warning("GfxRemap32::remapByRange: %d out of remap range", color); + return; + } + + if (from < 0) { + warning("GfxRemap32::remapByRange: attempt to remap negative color %d", from); + return; + } + + if (to >= _remapStartColor) { + warning("GfxRemap32::remapByRange: attempt to remap into the remap zone at %d", to); + return; + } + + const uint8 index = _remapEndColor - color; + SingleRemap &singleRemap = _remaps[index]; + + if (singleRemap._type == kRemapNone) { + ++_numActiveRemaps; + singleRemap.reset(); + } + + singleRemap._from = from; + singleRemap._to = to; + singleRemap._delta = delta; + singleRemap._type = kRemapByRange; + _needsUpdate = true; +} + +void GfxRemap32::remapByPercent(const uint8 color, const int16 percent) { + // NOTE: SSCI simply ignored invalid input values, but + // we at least give a warning so games can be investigated + // for script bugs + if (color < _remapStartColor || color > _remapEndColor) { + warning("GfxRemap32::remapByPercent: %d out of remap range", color); + return; + } + + const uint8 index = _remapEndColor - color; + SingleRemap &singleRemap = _remaps[index]; + + if (singleRemap._type == kRemapNone) { + ++_numActiveRemaps; + singleRemap.reset(); + } + + singleRemap._percent = percent; + singleRemap._type = kRemapByPercent; + _needsUpdate = true; +} + +void GfxRemap32::remapToGray(const uint8 color, const int8 gray) { + // NOTE: SSCI simply ignored invalid input values, but + // we at least give a warning so games can be investigated + // for script bugs + if (color < _remapStartColor || color > _remapEndColor) { + warning("GfxRemap32::remapToGray: %d out of remap range", color); + return; + } + + if (gray < 0 || gray > 100) { + error("RemapToGray percent out of range; gray = %d", gray); + } + + const uint8 index = _remapEndColor - color; + SingleRemap &singleRemap = _remaps[index]; + + if (singleRemap._type == kRemapNone) { + ++_numActiveRemaps; + singleRemap.reset(); + } + + singleRemap._gray = gray; + singleRemap._type = kRemapToGray; + _needsUpdate = true; +} + +void GfxRemap32::remapToPercentGray(const uint8 color, const int16 gray, const int16 percent) { + // NOTE: SSCI simply ignored invalid input values, but + // we at least give a warning so games can be investigated + // for script bugs + if (color < _remapStartColor || color > _remapEndColor) { + warning("GfxRemap32::remapToPercentGray: %d out of remap range", color); + return; + } + + const uint8 index = _remapEndColor - color; + SingleRemap &singleRemap = _remaps[index]; + + if (singleRemap._type == kRemapNone) { + ++_numActiveRemaps; + singleRemap.reset(); + } + + singleRemap._percent = percent; + singleRemap._gray = gray; + singleRemap._type = kRemapToPercentGray; + _needsUpdate = true; +} + +void GfxRemap32::blockRange(const uint8 from, const int16 count) { + _blockedRangeStart = from; + _blockedRangeCount = count; +} + +bool GfxRemap32::remapAllTables(const bool paletteUpdated) { + if (!_needsUpdate && !paletteUpdated) { + return false; + } + + bool updated = false; + + for (SingleRemapsList::iterator it = _remaps.begin(); it != _remaps.end(); ++it) { + if (it->_type != kRemapNone) { + updated |= it->update(); + } + } + + _needsUpdate = false; + return updated; +} +} // End of namespace Sci diff --git a/engines/sci/graphics/remap32.h b/engines/sci/graphics/remap32.h new file mode 100644 index 0000000000..1b9628c7be --- /dev/null +++ b/engines/sci/graphics/remap32.h @@ -0,0 +1,405 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCI_GRAPHICS_REMAP32_H +#define SCI_GRAPHICS_REMAP32_H + +#include "common/algorithm.h" +#include "common/array.h" +#include "common/scummsys.h" +#include "sci/graphics/helpers.h" + +namespace Sci { +class GfxPalette32; + +enum RemapType { + kRemapNone = 0, + kRemapByRange = 1, + kRemapByPercent = 2, + kRemapToGray = 3, + kRemapToPercentGray = 4 +}; + +#pragma mark - +#pragma mark SingleRemap + +/** + * SingleRemap objects each manage one remapping operation. + */ +class SingleRemap { +public: + SingleRemap() : _type(kRemapNone) {} + + /** + * The type of remap. + */ + RemapType _type; + + /** + * The first color that should be shifted by a range + * remap. + */ + uint8 _from; + + /** + * The last color that should be shifted a range remap. + */ + uint8 _to; + + /** + * The direction and amount that the colors should be + * shifted in a range remap. + */ + int16 _delta; + + /** + * The difference in brightness that should be + * applied by a brightness (percent) remap. + * + * This value may be be greater than 100, in + * which case the color will be oversaturated. + */ + int16 _percent; + + /** + * The amount of desaturation that should be + * applied by a saturation (gray) remap, where + * 0 is full saturation and 100 is full + * desaturation. + */ + uint8 _gray; + + /** + * The final array used by CelObj renderers to composite + * remapped pixels to the screen buffer. + * + * Here is how it works: + * + * The source bitmap being rendered will have pixels + * within the remap range (236-245 or 236-254), and the + * target buffer will have colors in the non-remapped + * range (0-235). + * + * To arrive at the correct color, first the source + * pixel is used to look up the correct SingleRemap for + * that pixel. Then, the final composited color is + * looked up in this array using the target's pixel + * color. In other words, + * `target = _remaps[remapEndColor - source].remapColors[target]`. + */ + uint8 _remapColors[236]; + + /** + * Resets this SingleRemap's color information to + * default values. + */ + void reset(); + + /** + * Recalculates and reapplies remap colors to the + * `_remapColors` array. + */ + bool update(); + +private: + /** + * The previous brightness value. Used to + * determine whether or not targetColors needs + * to be updated. + */ + int16 _lastPercent; + + /** + * The previous saturation value. Used to + * determine whether or not targetColors needs + * to be updated. + */ + uint8 _lastGray; + + /** + * The colors from the current GfxPalette32 palette + * before this SingleRemap is applied. + */ + Color _originalColors[236]; + + /** + * Map of colors that changed in `_originalColors` + * when this SingleRemap was updated. This map is + * transient and gets reset to `false` after the + * SingleRemap finishes updating. + */ + bool _originalColorsChanged[236]; + + /** + * The ideal target RGB color values for each generated + * remap color. + */ + Color _idealColors[236]; + + /** + * Map of colors that changed in `_idealColors` when + * this SingleRemap was updated. This map is transient + * and gets reset to `false` after the SingleRemap + * finishes applying. + */ + bool _idealColorsChanged[236]; + + /** + * When applying a SingleRemap, finding an appropriate + * color in the palette is the responsibility of a + * distance function. Once a match is found, the + * distance of that match is stored here so that the + * next time the SingleRemap is applied, it can check + * the distance from the previous application and avoid + * triggering an expensive redraw of the entire screen + * if the new palette value only changed slightly. + */ + int _matchDistances[236]; + + /** + * Computes the final target values for a range remap + * and applies them directly to the `_remaps` map. + * + * @note Was ByRange in SSCI. + */ + bool updateRange(); + + /** + * Computes the intermediate target values for a + * brightness remap and applies them indirectly via + * the `apply` method. + * + * @note Was ByPercent in SSCI. + */ + bool updateBrightness(); + + /** + * Computes the intermediate target values for a + * saturation remap and applies them indirectly via + * the `apply` method. + * + * @note Was ToGray in SSCI. + */ + bool updateSaturation(); + + /** + * Computes the intermediate target values for a + * saturation + brightness bitmap and applies them + * indirectly via the `apply` method. + * + * @note Was ToPercentGray in SSCI. + */ + bool updateSaturationAndBrightness(); + + /** + * Computes and applies the final values to the + * `_remaps` map. + * + * @note In SSCI, a boolean array of changed values + * was passed into this method, but this was done by + * creating arrays on the stack in the caller. Instead + * of doing this, we simply add another member property + * `_idealColorsChanged` and use that instead. + */ + bool apply(); + + /** + * Calculates the square distance of two colors. + * + * @note In SSCI this method is Rgb24::Dist, but it is + * only used by SingleRemap. + */ + int colorDistance(const Color &a, const Color &b) const; + + /** + * Finds the closest index in the next palette matching + * the given RGB color. Returns -1 if no match can be + * found that is closer than `minimumDistance`. + * + * @note In SSCI, this method is SOLPalette::Match, but + * this particular signature is only used by + * SingleRemap. + */ + int16 matchColor(const Color &color, const int minimumDistance, int &outDistance, const bool *const blockedIndexes) const; +}; + +#pragma mark - +#pragma mark GfxRemap32 + +/** + * This class provides color remapping support for SCI32 + * games. + */ +class GfxRemap32 : public Common::Serializable { +public: + GfxRemap32(); + + void saveLoadWithSerializer(Common::Serializer &s); + + inline uint8 getRemapCount() const { return _numActiveRemaps; } + inline uint8 getStartColor() const { return _remapStartColor; } + inline uint8 getEndColor() const { return _remapEndColor; } + inline uint8 getBlockedRangeStart() const { return _blockedRangeStart; } + inline int16 getBlockedRangeCount() const { return _blockedRangeCount; } + + /** + * Turns off remapping of the given color. If `color` is + * 0, all remaps are turned off. + */ + void remapOff(const uint8 color); + + /** + * Turns off all color remaps. + */ + void remapAllOff(); + + /** + * Configures a SingleRemap for the remap color `color`. + * The SingleRemap will shift palette colors between + * `from` and `to` (inclusive) by `delta` palette + * entries when the remap is applied. + */ + void remapByRange(const uint8 color, const int16 from, const int16 to, const int16 delta); + + /** + * Configures a SingleRemap for the remap color `color` + * to modify the brightness of remapped colors by + * `percent`. + */ + void remapByPercent(const uint8 color, const int16 percent); + + /** + * Configures a SingleRemap for the remap color `color` + * to modify the saturation of remapped colors by + * `gray`. + */ + void remapToGray(const uint8 color, const int8 gray); + + /** + * Configures a SingleRemap for the remap color `color` + * to modify the brightness of remapped colors by + * `percent`, and saturation of remapped colors by + * `gray`. + */ + void remapToPercentGray(const uint8 color, const int16 gray, const int16 percent); + + /** + * Prevents GfxRemap32 from using the given range of + * palette entries as potential remap targets. + * + * @NOTE Was DontMapToRange in SSCI. + */ + void blockRange(const uint8 from, const int16 count); + + /** + * Determines whether or not the given color has an + * active remapper. If it does not, it is treated as a + * skip color and the pixel is not drawn. + * + * @note SSCI uses a boolean array to decide whether a + * a pixel is remapped, but it is possible to get the + * same information from `_remaps`, as this function + * does. + * Presumably, the separate array was created for + * performance reasons, since this is called a lot in + * the most critical section of the renderer. + */ + inline bool remapEnabled(uint8 color) const { + const uint8 index = _remapEndColor - color; + // At least KQ7 DOS uses remap colors that are outside the valid remap + // range; in these cases, just treat those pixels as skip pixels (which + // is how they would be treated in SSCI) + if (index >= _remaps.size()) { + return false; + } + return (_remaps[index]._type != kRemapNone); + } + + /** + * Calculates the correct color for a target by looking + * up the target color in the SingleRemap that controls + * the given sourceColor. If there is no remap for the + * given color, it will be treated as a skip color. + */ + inline uint8 remapColor(const uint8 sourceColor, const uint8 targetColor) const { + const uint8 index = _remapEndColor - sourceColor; + assert(index < _remaps.size()); + const SingleRemap &singleRemap = _remaps[index]; + assert(singleRemap._type != kRemapNone); + return singleRemap._remapColors[targetColor]; + } + + /** + * Updates all active remaps in response to a palette + * change or a remap settings change. + * + * `paletteChanged` is true if the next palette in + * GfxPalette32 has been previously modified by other + * palette operations. + */ + bool remapAllTables(const bool paletteUpdated); + +private: + typedef Common::Array<SingleRemap> SingleRemapsList; + + /** + * The first index of the remap area in the system + * palette. + */ + const uint8 _remapStartColor; + + /** + * The last index of the remap area in the system + * palette. + */ + uint8 _remapEndColor; + + /** + * The number of currently active remaps. + */ + uint8 _numActiveRemaps; + + /** + * The list of SingleRemaps. + */ + SingleRemapsList _remaps; + + /** + * If true, indicates that one or more SingleRemaps were + * reconfigured and all remaps need to be recalculated. + */ + bool _needsUpdate; + + /** + * The first color that is blocked from being used as a + * remap target color. + */ + uint8 _blockedRangeStart; + + /** + * The size of the range of blocked colors. If zero, + * all colors are potential targets for remapping. + */ + int16 _blockedRangeCount; +}; +} // End of namespace Sci +#endif diff --git a/engines/sci/graphics/screen_item32.cpp b/engines/sci/graphics/screen_item32.cpp index c3fdbb6845..7383dc222e 100644 --- a/engines/sci/graphics/screen_item32.cpp +++ b/engines/sci/graphics/screen_item32.cpp @@ -42,7 +42,8 @@ _pictureId(-1), _created(g_sci->_gfxFrameout->getScreenCount()), _updated(0), _deleted(0), -_mirrorX(false) { +_mirrorX(false), +_drawBlackLines(false) { SegManager *segMan = g_sci->getEngineState()->_segMan; setFromObject(segMan, object, true, true); @@ -55,14 +56,15 @@ _useInsetRect(false), _z(0), _celInfo(celInfo), _celObj(nullptr), -_fixPriority(false), +_fixedPriority(false), _position(0, 0), _object(make_reg(0, _nextObjectId++)), _pictureId(-1), _created(g_sci->_gfxFrameout->getScreenCount()), _updated(0), _deleted(0), -_mirrorX(false) {} +_mirrorX(false), +_drawBlackLines(false) {} ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect) : _plane(plane), @@ -70,14 +72,15 @@ _useInsetRect(false), _z(0), _celInfo(celInfo), _celObj(nullptr), -_fixPriority(false), +_fixedPriority(false), _position(rect.left, rect.top), _object(make_reg(0, _nextObjectId++)), _pictureId(-1), _created(g_sci->_gfxFrameout->getScreenCount()), _updated(0), _deleted(0), -_mirrorX(false) { +_mirrorX(false), +_drawBlackLines(false) { if (celInfo.type == kCelTypeColor) { _insetRect = rect; } @@ -90,14 +93,15 @@ _useInsetRect(false), _z(0), _celInfo(celInfo), _celObj(nullptr), -_fixPriority(false), +_fixedPriority(false), _position(position), _object(make_reg(0, _nextObjectId++)), _pictureId(-1), _created(g_sci->_gfxFrameout->getScreenCount()), _updated(0), _deleted(0), -_mirrorX(false) {} +_mirrorX(false), +_drawBlackLines(false) {} ScreenItem::ScreenItem(const ScreenItem &other) : _plane(other._plane), @@ -108,7 +112,8 @@ _celObj(nullptr), _object(other._object), _mirrorX(other._mirrorX), _scaledPosition(other._scaledPosition), -_screenRect(other._screenRect) { +_screenRect(other._screenRect), +_drawBlackLines(other._drawBlackLines) { if (other._useInsetRect) { _insetRect = other._insetRect; } @@ -134,6 +139,7 @@ void ScreenItem::operator=(const ScreenItem &other) { } _scale = other._scale; _scaledPosition = other._scaledPosition; + _drawBlackLines = other._drawBlackLines; } ScreenItem::~ScreenItem() { @@ -209,10 +215,10 @@ void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const boo } if (readSelectorValue(segMan, object, SELECTOR(fixPriority))) { - _fixPriority = true; + _fixedPriority = true; _priority = readSelectorValue(segMan, object, SELECTOR(priority)); } else { - _fixPriority = false; + _fixedPriority = false; writeSelectorValue(segMan, object, SELECTOR(priority), _position.y); } @@ -273,7 +279,9 @@ void ScreenItem::calcRects(const Plane &plane) { // Cel may use a coordinate system that is not the same size as the // script coordinate system (usually this means high-resolution // pictures with low-resolution scripts) - if (celObj._scaledWidth != scriptWidth || celObj._scaledHeight != scriptHeight) { + if (celObj._scaledWidth != kLowResX || celObj._scaledHeight != kLowResY) { + // high resolution coordinates + if (_useInsetRect) { const Ratio scriptToCelX(celObj._scaledWidth, scriptWidth); const Ratio scriptToCelY(celObj._scaledHeight, scriptHeight); @@ -294,7 +302,30 @@ void ScreenItem::calcRects(const Plane &plane) { } if (!scaleX.isOne() || !scaleY.isOne()) { - mulinc(_screenItemRect, scaleX, scaleY); + // Different games use a different cel scaling mode, but the + // difference isn't consistent across SCI versions; instead, + // it seems to be related to an update that happened during + // SCI2.1mid where games started using hi-resolution game + // scripts + if (scriptWidth == kLowResX) { + mulinc(_screenItemRect, scaleX, scaleY); + } else { + _screenItemRect.left = (_screenItemRect.left * scaleX).toInt(); + _screenItemRect.top = (_screenItemRect.top * scaleY).toInt(); + + if (scaleX.getNumerator() > scaleX.getDenominator()) { + _screenItemRect.right = (_screenItemRect.right * scaleX).toInt(); + } else { + _screenItemRect.right = ((_screenItemRect.right - 1) * scaleX).toInt() + 1; + } + + if (scaleY.getNumerator() > scaleY.getDenominator()) { + _screenItemRect.bottom = (_screenItemRect.bottom * scaleY).toInt(); + } else { + _screenItemRect.bottom = ((_screenItemRect.bottom - 1) * scaleY).toInt() + 1; + } + } + displaceX = (displaceX * scaleX).toInt(); displaceY = (displaceY * scaleY).toInt(); } @@ -326,6 +357,9 @@ void ScreenItem::calcRects(const Plane &plane) { mulinc(temp, celToScreenX, Ratio()); CelObjPic *celObjPic = dynamic_cast<CelObjPic *>(_celObj); + if (celObjPic == nullptr) { + error("Expected a CelObjPic"); + } temp.translate((celObjPic->_relativePosition.x * scriptToScreenX).toInt() - displaceX, 0); // TODO: This is weird. @@ -342,6 +376,8 @@ void ScreenItem::calcRects(const Plane &plane) { _ratioX = scaleX * celToScreenX; _ratioY = scaleY * celToScreenY; } else { + // low resolution coordinates + int displaceX = celObj._displace.x; if (_mirrorX != celObj._mirrorX && _celInfo.type != kCelTypePic) { displaceX = celObj._width - celObj._displace.x - 1; @@ -369,6 +405,9 @@ void ScreenItem::calcRects(const Plane &plane) { } CelObjPic *celObjPic = dynamic_cast<CelObjPic *>(_celObj); + if (celObjPic == nullptr) { + error("Expected a CelObjPic"); + } temp.translate(celObjPic->_relativePosition.x - (displaceX * scaleX).toInt(), celObjPic->_relativePosition.y - (celObj._displace.y * scaleY).toInt()); // TODO: This is weird. @@ -402,7 +441,7 @@ void ScreenItem::calcRects(const Plane &plane) { _screenRect.top = 0; } - if (!_fixPriority) { + if (!_fixedPriority) { _priority = _z + _position.y; } } else { @@ -509,8 +548,25 @@ void ScreenItem::update(const reg_t object) { _deleted = 0; } -// TODO: This code is quite similar to calcRects, so try to deduplicate -// if possible +void ScreenItem::update() { + Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(_plane); + if (plane == nullptr) { + error("ScreenItem::update: Invalid plane %04x:%04x", PRINT_REG(_plane)); + } + + if (plane->_screenItemList.findByObject(_object) == nullptr) { + error("ScreenItem::update: %04x:%04x not in plane %04x:%04x", PRINT_REG(_object), PRINT_REG(_plane)); + } + + if (!_created) { + _updated = g_sci->_gfxFrameout->getScreenCount(); + } + _deleted = 0; + + delete _celObj; + _celObj = nullptr; +} + Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const { CelObj &celObj = getCelObj(); @@ -518,10 +574,7 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const { Common::Rect nsRect; if (_useInsetRect) { - // TODO: This is weird. Checking to see if the inset rect is - // fully inside the bounds of the celObjRect, and then - // clipping to the celObjRect, is pretty useless. - if (_insetRect.right > 0 && _insetRect.bottom > 0 && _insetRect.left < celObj._width && _insetRect.top < celObj._height) { + if (_insetRect.intersects(celObjRect)) { nsRect = _insetRect; nsRect.clip(celObjRect); } else { @@ -557,16 +610,15 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const { displaceX = celObj._width - displaceX - 1; } - if (celObj._scaledWidth != scriptWidth || celObj._scaledHeight != scriptHeight) { + if (celObj._scaledWidth != kLowResX || celObj._scaledHeight != kLowResY) { + // high resolution coordinates + if (_useInsetRect) { Ratio scriptToCelX(celObj._scaledWidth, scriptWidth); Ratio scriptToCelY(celObj._scaledHeight, scriptHeight); mulru(nsRect, scriptToCelX, scriptToCelY, 0); - // TODO: This is weird. Checking to see if the inset rect is - // fully inside the bounds of the celObjRect, and then - // clipping to the celObjRect, is pretty useless. - if (nsRect.right > 0 && nsRect.bottom > 0 && nsRect.left < celObj._width && nsRect.top < celObj._height) { + if (nsRect.intersects(celObjRect)) { nsRect.clip(celObjRect); } else { nsRect = Common::Rect(); @@ -574,12 +626,34 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const { } if (!scaleX.isOne() || !scaleY.isOne()) { - mulinc(nsRect, scaleX, scaleY); - // TODO: This was in the original code, baked into the - // multiplication though it is not immediately clear - // why this is the only one that reduces the BR corner - nsRect.right -= 1; - nsRect.bottom -= 1; + // Different games use a different cel scaling mode, but the + // difference isn't consistent across SCI versions; instead, + // it seems to be related to an update that happened during + // SCI2.1mid where games started using hi-resolution game + // scripts + if (scriptWidth == kLowResX) { + mulinc(nsRect, scaleX, scaleY); + // TODO: This was in the original code, baked into the + // multiplication though it is not immediately clear + // why this is the only one that reduces the BR corner + nsRect.right -= 1; + nsRect.bottom -= 1; + } else { + nsRect.left = (nsRect.left * scaleX).toInt(); + nsRect.top = (nsRect.top * scaleY).toInt(); + + if (scaleX.getNumerator() > scaleX.getDenominator()) { + nsRect.right = (nsRect.right * scaleX).toInt(); + } else { + nsRect.right = ((nsRect.right - 1) * scaleX).toInt() + 1; + } + + if (scaleY.getNumerator() > scaleY.getDenominator()) { + nsRect.bottom = (nsRect.bottom * scaleY).toInt(); + } else { + nsRect.bottom = ((nsRect.bottom - 1) * scaleY).toInt() + 1; + } + } } Ratio celToScriptX(scriptWidth, celObj._scaledWidth); @@ -591,6 +665,8 @@ Common::Rect ScreenItem::getNowSeenRect(const Plane &plane) const { mulinc(nsRect, celToScriptX, celToScriptY); nsRect.translate(_position.x - displaceX, _position.y - displaceY); } else { + // low resolution coordinates + if (!scaleX.isOne() || !scaleY.isOne()) { mulinc(nsRect, scaleX, scaleY); // TODO: This was in the original code, baked into the @@ -624,23 +700,43 @@ ScreenItem *ScreenItemList::findByObject(const reg_t object) const { return *screenItemIt; } void ScreenItemList::sort() { - // TODO: SCI engine used _unsorted as an array of indexes into the - // list itself and then performed the same swap operations on the - // _unsorted array as the _storage array during sorting, but the - // only reason to do this would be if some of the pointers in the - // list were replaced so the pointer values themselves couldn’t - // simply be recorded and then restored later. It is not yet - // verified whether this simplification of the sort/unsort is - // safe. + if (size() < 2) { + return; + } + for (size_type i = 0; i < size(); ++i) { - _unsorted[i] = (*this)[i]; + _unsorted[i] = i; } - Common::sort(begin(), end(), sortHelper); + for (size_type i = size() - 1; i > 0; --i) { + bool swap = false; + + for (size_type j = 0; j < i; ++j) { + value_type &a = operator[](j); + value_type &b = operator[](j + 1); + + if (a == nullptr || *a > *b) { + SWAP(a, b); + SWAP(_unsorted[j], _unsorted[j + 1]); + swap = true; + } + } + + if (!swap) { + break; + } + } } void ScreenItemList::unsort() { + if (size() < 2) { + return; + } + for (size_type i = 0; i < size(); ++i) { - (*this)[i] = _unsorted[i]; + while (_unsorted[i] != i) { + SWAP(operator[](_unsorted[i]), operator[](i)); + SWAP(_unsorted[_unsorted[i]], _unsorted[i]); + } } } diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h index 977d80ebad..3d9d5ef3d7 100644 --- a/engines/sci/graphics/screen_item32.h +++ b/engines/sci/graphics/screen_item32.h @@ -64,12 +64,12 @@ private: */ static uint16 _nextObjectId; +public: /** * The parent plane of this screen item. */ reg_t _plane; -public: /** * Scaling data used to calculate the final screen * dimensions of the screen item as well as the scaling @@ -88,15 +88,23 @@ private: Common::Rect _screenItemRect; /** - * TODO: Document + * If true, the `_insetRect` rectangle will be used + * when calculating the dimensions of the screen item + * instead of the cel's intrinsic width and height. + * + * In other words, using an inset rect means that + * the cel is cropped to the dimensions given in + * `_insetRect`. */ bool _useInsetRect; /** - * TODO: Documentation - * The insetRect is also used to describe the fill - * rectangle of a screen item that is drawn using - * CelObjColor. + * The cropping rectangle used when `_useInsetRect` + * is true. + * + * `_insetRect` is also used to describe the fill + * rectangle of a screen item with a CelObjColor + * cel. */ Common::Rect _insetRect; @@ -132,7 +140,7 @@ public: * in place. Otherwise, the priority of the screen item * is calculated from its y-position + z-index. */ - bool _fixPriority; + bool _fixedPriority; /** * The rendering priority of the screen item, relative @@ -172,7 +180,7 @@ public: * plane is a pic type and its picture resource ID has * changed */ - int _created, _updated, _deleted; // ? + int _created, _updated, _deleted; /** * For screen items that represent picture cels, this @@ -206,6 +214,14 @@ public: Common::Rect _screenRect; /** + * Whether or not the screen item should be drawn + * with black lines drawn every second line. This is + * used when pixel doubling videos to improve apparent + * sharpness at the cost of your eyesight. + */ + bool _drawBlackLines; + + /** * Initialises static Plane members. */ static void init(); @@ -236,6 +252,24 @@ public: return false; } + inline bool operator>(const ScreenItem &other) const { + if (_priority > other._priority) { + return true; + } + + if (_priority == other._priority) { + if (_position.y + _z > other._position.y + other._z) { + return true; + } + + if (_position.y + _z == other._position.y + other._z) { + return _object > other._object; + } + } + + return false; + } + /** * Calculates the dimensions and scaling parameters for * the screen item, using the given plane as the parent @@ -261,6 +295,12 @@ public: void update(const reg_t object); /** + * Updates the properties of the screen item for one not belonging + * to a VM object. Originally GraphicsMgr::UpdateScreenItem. + */ + void update(); + + /** * Gets the "now seen" rect for the screen item, which * represents the current size and position of the * screen item on the screen in script coordinates. @@ -273,12 +313,10 @@ public: typedef StablePointerArray<ScreenItem, 250> ScreenItemListBase; class ScreenItemList : public ScreenItemListBase { - static bool inline sortHelper(const ScreenItem *a, const ScreenItem *b) { - return *a < *b; - } -public: - ScreenItem *_unsorted[250]; +private: + size_type _unsorted[250]; +public: ScreenItem *findByObject(const reg_t object) const; void sort(); void unsort(); diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp index 99ffc6e328..277e6e93d0 100644 --- a/engines/sci/graphics/text32.cpp +++ b/engines/sci/graphics/text32.cpp @@ -39,29 +39,28 @@ namespace Sci { int16 GfxText32::_defaultFontId = 0; +int16 GfxText32::_scaledWidth = 0; +int16 GfxText32::_scaledHeight = 0; GfxText32::GfxText32(SegManager *segMan, GfxCache *fonts) : _segMan(segMan), _cache(fonts), - _scaledWidth(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth), - _scaledHeight(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight), // Not a typo, the original engine did not initialise height, only width _width(0), _text(""), - _field_20(0), - _field_2C(2), - _field_30(0), - _field_34(0), - _field_38(0), - _field_3C(0), _bitmap(NULL_REG) { _fontId = _defaultFontId; _font = _cache->getFont(_defaultFontId); + + if (_scaledWidth == 0) { + // initialize the statics + _scaledWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + _scaledHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + } } reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect &rect, const Common::String &text, const uint8 foreColor, const uint8 backColor, const uint8 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, const bool dimmed, const bool doScaling) { - _field_22 = 0; _borderColor = borderColor; _text = text; _textRect = rect; @@ -111,7 +110,6 @@ reg_t GfxText32::createFontBitmap(int16 width, int16 height, const Common::Rect } reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &rect, const Common::String &text, const int16 foreColor, const int16 backColor, const GuiResourceId fontId, const int16 skipColor, const int16 borderColor, const bool dimmed) { - _field_22 = 0; _borderColor = borderColor; _text = text; _textRect = rect; @@ -123,7 +121,6 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect & int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; - int borderSize = 1; mulinc(_textRect, Ratio(_scaledWidth, scriptWidth), Ratio(_scaledHeight, scriptHeight)); CelObjView view(celInfo.resourceId, celInfo.loopNo, celInfo.celNo); @@ -140,7 +137,6 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect & BitmapResource bitmap(_segMan, _width, _height, _skipColor, 0, 0, _scaledWidth, _scaledHeight, 0, false); _bitmap = bitmap.getObject(); - Buffer buffer(_width, _height, bitmap.getPixels()); // NOTE: The engine filled the bitmap pixels with 11 here, which is silly // because then it just erased the bitmap using the skip color. So we don't @@ -150,7 +146,7 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect & erase(bitmapRect, false); _backColor = backColor; - view.draw(buffer, bitmapRect, Common::Point(0, 0), false, Ratio(_scaledWidth, view._scaledWidth), Ratio(_scaledHeight, view._scaledHeight)); + view.draw(bitmap.getBuffer(), bitmapRect, Common::Point(0, 0), false, Ratio(_scaledWidth, view._scaledWidth), Ratio(_scaledHeight, view._scaledHeight)); if (_backColor != skipColor && _foreColor != skipColor) { erase(_textRect, false); @@ -161,7 +157,7 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect & error("TODO: Implement transparent text"); } else { if (borderColor != -1) { - drawFrame(bitmapRect, borderSize, _borderColor, false); + drawFrame(bitmapRect, 1, _borderColor, false); } drawTextBox(); @@ -171,11 +167,6 @@ reg_t GfxText32::createFontBitmap(const CelInfo32 &celInfo, const Common::Rect & return _bitmap; } -reg_t GfxText32::createTitledBitmap(const int16 width, const int16 height, const Common::Rect &textRect, const Common::String &text, const int16 foreColor, const int16 backColor, const int16 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, Common::String &title, const int16 titleForeColor, const int16 titleBackColor, const GuiResourceId titleFontId, const bool doScaling) { - warning("TODO: createTitledBitmap incomplete !"); - return createFontBitmap(width, height, textRect, text, foreColor, backColor, skipColor, fontId, alignment, borderColor, false, doScaling); -} - void GfxText32::setFont(const GuiResourceId fontId) { // NOTE: In SCI engine this calls FontMgr::BuildFontTable and then a font // table is built on the FontMgr directly; instead, because we already have @@ -244,8 +235,11 @@ void GfxText32::drawTextBox() { int16 textRectWidth = _textRect.width(); _drawPosition.y = _textRect.top; uint charIndex = 0; - if (getLongest(&charIndex, textRectWidth) == 0) { - error("DrawTextBox GetLongest=0"); + + if (g_sci->getGameId() == GID_SQ6 || g_sci->getGameId() == GID_MOTHERGOOSEHIRES) { + if (getLongest(&charIndex, textRectWidth) == 0) { + error("DrawTextBox GetLongest=0"); + } } charIndex = 0; @@ -324,6 +318,10 @@ void GfxText32::drawText(const uint index, uint length) { ++text; --length; } + if (length > 0) { + ++text; + --length; + } } else { drawChar(currentChar); } @@ -511,7 +509,7 @@ int16 GfxText32::getTextWidth(const uint index, uint length) const { --length; fontId = fontId * 10 + currentChar - '0'; - } while (length > 0 && currentChar >= '0' && currentChar <= '9'); + } while (length > 0 && *text >= '0' && *text <= '9'); if (length > 0) { font = _cache->getFont(fontId); @@ -519,7 +517,11 @@ int16 GfxText32::getTextWidth(const uint index, uint length) const { } // Forward through any more unknown control character data - while (length > 0 && currentChar != '|') { + while (length > 0 && *text != '|') { + ++text; + --length; + } + if (length > 0) { ++text; --length; } @@ -527,8 +529,10 @@ int16 GfxText32::getTextWidth(const uint index, uint length) const { width += font->getCharWidth(currentChar); } - currentChar = *text++; - --length; + if (length > 0) { + currentChar = *text++; + --length; + } } return width; @@ -558,12 +562,7 @@ Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth, if (maxWidth >= 0) { if (maxWidth == 0) { - // TODO: This was hardcoded to 192, but guessing - // that it was originally 60% of the scriptWidth - // before the compiler took over. - // Verify this by looking at a game that uses a - // scriptWidth other than 320, like LSL7 - maxWidth = _scaledWidth * (scriptWidth * 0.6) / scriptWidth; + maxWidth = _scaledWidth * 3 / 5; } result.right = maxWidth; @@ -591,11 +590,16 @@ Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth, } } else { result.right = getTextWidth(0, 10000); - // NOTE: In the original engine code, the bottom was not decremented - // by 1, which means that the rect was actually a pixel taller than - // the height of the font. This was not the case in the other branch, - // which decremented the bottom by 1 at the end of the loop. - result.bottom = _font->getHeight() + 1; + + if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) { + result.bottom = 0; + } else { + // NOTE: In the original engine code, the bottom was not decremented + // by 1, which means that the rect was actually a pixel taller than + // the height of the font. This was not the case in the other branch, + // which decremented the bottom by 1 at the end of the loop. + result.bottom = _font->getHeight() + 1; + } } if (doScaling) { @@ -611,14 +615,8 @@ Common::Rect GfxText32::getTextSize(const Common::String &text, int16 maxWidth, void GfxText32::erase(const Common::Rect &rect, const bool doScaling) { Common::Rect targetRect = doScaling ? scaleRect(rect) : rect; - byte *bitmap = _segMan->getHunkPointer(_bitmap); - byte *pixels = bitmap + READ_SCI11ENDIAN_UINT32(bitmap + 28); - - // NOTE: There is an extra optimisation within the SCI code to - // do a single memset if the scaledRect is the same size as - // the bitmap, not implemented here. - Buffer buffer(_width, _height, pixels); - buffer.fillRect(targetRect, _backColor); + BitmapResource bitmap(_bitmap); + bitmap.getBuffer().fillRect(targetRect, _backColor); } int16 GfxText32::getStringWidth(const Common::String &text) { @@ -653,5 +651,69 @@ int16 GfxText32::getTextCount(const Common::String &text, const uint index, cons return getTextCount(text, index, textRect, doScaling); } +void GfxText32::scrollLine(const Common::String &lineText, int numLines, uint8 color, TextAlign align, GuiResourceId fontId, ScrollDirection dir) { + BitmapResource bmr(_bitmap); + byte *pixels = bmr.getPixels(); + + int h = _font->getHeight(); + + if (dir == kScrollUp) { + // Scroll existing text down + for (int i = 0; i < (numLines - 1) * h; ++i) { + int y = _textRect.top + numLines * h - i - 1; + memcpy(pixels + y * _width + _textRect.left, + pixels + (y - h) * _width + _textRect.left, + _textRect.width()); + } + } else { + // Scroll existing text up + for (int i = 0; i < (numLines - 1) * h; ++i) { + int y = _textRect.top + i; + memcpy(pixels + y * _width + _textRect.left, + pixels + (y + h) * _width + _textRect.left, + _textRect.width()); + } + } + + Common::Rect lineRect = _textRect; + + if (dir == kScrollUp) { + lineRect.bottom = lineRect.top + h; + } else { + // It is unclear to me what the purpose of this bottom++ is. + // It does not seem to be the usual inc/exc issue. + lineRect.top += (numLines - 1) * h; + lineRect.bottom++; + } + + erase(lineRect, false); + + _drawPosition.x = _textRect.left; + _drawPosition.y = _textRect.top; + if (dir == kScrollDown) { + _drawPosition.y += (numLines - 1) * h; + } + + _foreColor = color; + _alignment = align; + //int fc = _foreColor; + + setFont(fontId); + + _text = lineText; + int16 textWidth = getTextWidth(0, lineText.size()); + + if (_alignment == kTextAlignCenter) { + _drawPosition.x += (_textRect.width() - textWidth) / 2; + } else if (_alignment == kTextAlignRight) { + _drawPosition.x += _textRect.width() - textWidth; + } + + //_foreColor = fc; + //setFont(fontId); + + drawText(0, lineText.size()); +} + } // End of namespace Sci diff --git a/engines/sci/graphics/text32.h b/engines/sci/graphics/text32.h index 5768ea0c59..f57433f42d 100644 --- a/engines/sci/graphics/text32.h +++ b/engines/sci/graphics/text32.h @@ -23,21 +23,33 @@ #ifndef SCI_GRAPHICS_TEXT32_H #define SCI_GRAPHICS_TEXT32_H +#include "sci/engine/state.h" #include "sci/graphics/celobj32.h" #include "sci/graphics/frameout.h" +#include "sci/graphics/helpers.h" namespace Sci { enum TextAlign { - kTextAlignLeft = 0, - kTextAlignCenter = 1, - kTextAlignRight = 2 + kTextAlignDefault = -1, + kTextAlignLeft = 0, + kTextAlignCenter = 1, + kTextAlignRight = 2 +}; + +enum ScrollDirection { + kScrollUp, + kScrollDown }; enum BitmapFlags { kBitmapRemap = 2 }; +enum { + kDefaultSkipColor = 250 +}; + #define BITMAP_PROPERTY(size, property, offset)\ inline uint##size get##property() const {\ return READ_SCI11ENDIAN_UINT##size(_bitmap + (offset));\ @@ -53,6 +65,7 @@ inline void set##property(uint##size value) {\ class BitmapResource { byte *_bitmap; reg_t _object; + Buffer _buffer; /** * Gets the size of the bitmap header for the current @@ -96,6 +109,8 @@ public: if (_bitmap == nullptr || getUncompressedDataOffset() != getBitmapHeaderSize()) { error("Invalid Text bitmap %04x:%04x", PRINT_REG(bitmap)); } + + _buffer = Buffer(getWidth(), getHeight(), getPixels()); } /** @@ -103,7 +118,6 @@ public: * segment manager. */ inline BitmapResource(SegManager *segMan, const int16 width, const int16 height, const uint8 skipColor, const int16 displaceX, const int16 displaceY, const int16 scaledWidth, const int16 scaledHeight, const uint32 hunkPaletteOffset, const bool remap) { - _object = segMan->allocateHunkEntry("Bitmap()", getBitmapSize(width, height)); _bitmap = segMan->getHunkPointer(_object); @@ -124,12 +138,18 @@ public: setControlOffset(0); setScaledWidth(scaledWidth); setScaledHeight(scaledHeight); + + _buffer = Buffer(getWidth(), getHeight(), getPixels()); } - reg_t getObject() const { + inline reg_t getObject() const { return _object; } + inline Buffer &getBuffer() { + return _buffer; + } + BITMAP_PROPERTY(16, Width, 0); BITMAP_PROPERTY(16, Height, 2); @@ -173,7 +193,7 @@ public: return READ_SCI11ENDIAN_UINT32(_bitmap + 20); } - void setHunkPaletteOffset(uint32 hunkPaletteOffset) { + inline void setHunkPaletteOffset(uint32 hunkPaletteOffset) { if (hunkPaletteOffset) { hunkPaletteOffset += getBitmapHeaderSize(); } @@ -304,17 +324,6 @@ private: */ TextAlign _alignment; - int16 _field_20; - - /** - * TODO: Document - */ - int16 _field_22; - - int _field_2C, _field_30, _field_34, _field_38; - - int16 _field_3C; - /** * The position of the text draw cursor. */ @@ -362,15 +371,15 @@ public: /** * The size of the x-dimension of the coordinate system - * used by the text renderer. + * used by the text renderer. Static since it was global in SSCI. */ - int16 _scaledWidth; + static int16 _scaledWidth; /** * The size of the y-dimension of the coordinate system - * used by the text renderer. + * used by the text renderer. Static since it was global in SSCI. */ - int16 _scaledHeight; + static int16 _scaledHeight; /** * The currently active font resource used to write text @@ -392,11 +401,6 @@ public: */ reg_t createFontBitmap(const CelInfo32 &celInfo, const Common::Rect &rect, const Common::String &text, const int16 foreColor, const int16 backColor, const GuiResourceId fontId, const int16 skipColor, const int16 borderColor, const bool dimmed); - /** - * Creates a font bitmap with a title. - */ - reg_t createTitledBitmap(const int16 width, const int16 height, const Common::Rect &textRect, const Common::String &text, const int16 foreColor, const int16 backColor, const int16 skipColor, const GuiResourceId fontId, const TextAlign alignment, const int16 borderColor, Common::String &title, const int16 titleForeColor, const int16 titleBackColor, const GuiResourceId titleFontId, const bool doScaling); - inline int scaleUpWidth(int value) const { const int scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; return (value * scriptWidth + _scaledWidth - 1) / _scaledWidth; @@ -472,6 +476,13 @@ public: * `textRect` using the given font. */ int16 getTextCount(const Common::String &text, const uint index, const GuiResourceId fontId, const Common::Rect &textRect, const bool doScaling); + + /** + * Scroll up/down one line. `numLines` is the number of the lines in the + * textarea, and `textLine` contains the text to draw as the newly + * visible line. Originally FontMgr::DrawOneLine and FontMgr::UpOneLine. + */ + void scrollLine(const Common::String &textLine, int numLines, uint8 color, TextAlign align, GuiResourceId fontId, ScrollDirection dir); }; } // End of namespace Sci diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp new file mode 100644 index 0000000000..dd841f5b4c --- /dev/null +++ b/engines/sci/graphics/video32.cpp @@ -0,0 +1,415 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "audio/mixer.h" +#include "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 "sci/graphics/video32.h" +#include "sci/sci.h" +#include "video/coktel_decoder.h" + +namespace Sci { + +#pragma mark VMDPlayer + +VMDPlayer::VMDPlayer(SegManager *segMan, EventManager *eventMan) : + _segMan(segMan), + _eventMan(eventMan), + _decoder(new Video::AdvancedVMDDecoder(Audio::Mixer::kSFXSoundType)), + + _isOpen(false), + _isInitialized(false), + _yieldInterval(0), + _lastYieldedFrameNo(0), + + _plane(nullptr), + _screenItem(nullptr), + _planeIsOwned(true), + _priority(0), + _doublePixels(false), + _stretchVertical(false), + _blackLines(false), + _leaveScreenBlack(false), + _leaveLastFrame(false), + + _blackoutPlane(nullptr), + + _startColor(0), + _endColor(255), + _blackPalette(false), + + _boostPercent(100), + _boostStartColor(0), + _boostEndColor(255), + + _showCursor(false) {} + +VMDPlayer::~VMDPlayer() { + close(); + delete _decoder; +} + +#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 (_decoder->loadFile(fileName)) { + if (flags & kOpenFlagMute) { + _decoder->setVolume(0); + } + _isOpen = true; + return kIOSuccess; + } else { + return kIOError; + } +} + +void VMDPlayer::init(const int16 x, const int16 y, const PlayFlags flags, const int16 boostPercent, const int16 boostStartColor, const int16 boostEndColor) { + _x = getSciVersion() >= SCI_VERSION_3 ? x : (x & ~1); + _y = y; + _doublePixels = flags & kPlayFlagDoublePixels; + _blackLines = ConfMan.getBool("enable_black_lined_video") && (flags & kPlayFlagBlackLines); + _boostPercent = 100 + (flags & kPlayFlagBoost ? boostPercent : 0); + _boostStartColor = CLIP<int16>(boostStartColor, 0, 255); + _boostEndColor = CLIP<int16>(boostEndColor, 0, 255); + _leaveScreenBlack = flags & kPlayFlagLeaveScreenBlack; + _leaveLastFrame = flags & kPlayFlagLeaveLastFrame; + _blackPalette = flags & kPlayFlagBlackPalette; + _stretchVertical = flags & kPlayFlagStretchVertical; +} + +VMDPlayer::IOStatus VMDPlayer::close() { + if (!_isOpen) { + return kIOSuccess; + } + + _decoder->close(); + _isOpen = false; + _isInitialized = false; + + 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); + } + + if (_blackoutPlane != nullptr) { + g_sci->_gfxFrameout->deletePlane(*_blackoutPlane); + _blackoutPlane = nullptr; + } + + if (!_leaveLastFrame && !_leaveScreenBlack) { + // This call *actually* deletes the blackout plane + g_sci->_gfxFrameout->frameOut(true); + } + + if (!_showCursor) { + g_sci->_gfxCursor->kernelShow(); + } + + _lastYieldedFrameNo = 0; + _planeIsOwned = true; + _priority = 0; + return kIOSuccess; +} + +VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, const int16 lastFrameNo, const int16 yieldInterval) { + assert(lastFrameNo >= -1); + + const int32 maxFrameNo = (int32)(_decoder->getFrameCount() - 1); + + if ((flags & kEventFlagToFrame) && lastFrameNo > 0) { + _decoder->setEndFrame(MIN((int32)lastFrameNo, maxFrameNo)); + } else { + _decoder->setEndFrame(maxFrameNo); + } + + if (flags & kEventFlagYieldToVM) { + _yieldInterval = 3; + if (yieldInterval == -1 && !(flags & kEventFlagToFrame)) { + _yieldInterval = lastFrameNo; + } else if (yieldInterval != -1) { + _yieldInterval = MIN((int32)yieldInterval, maxFrameNo); + } + } else { + _yieldInterval = maxFrameNo; + } + + return playUntilEvent(flags); +} + +VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) { + // Flushing all the keyboard and mouse events out of the event manager to + // avoid letting any events queued from before the video started from + // accidentally activating an event callback + for (;;) { + const SciEvent event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_MOUSE_PRESS | SCI_EVENT_MOUSE_RELEASE | SCI_EVENT_QUIT); + if (event.type == SCI_EVENT_NONE) { + break; + } else if (event.type == SCI_EVENT_QUIT) { + return kEventFlagEnd; + } + } + + _decoder->pauseVideo(false); + + if (flags & kEventFlagReverse) { + // NOTE: This flag may not work properly since SSCI does not care + // if a video has audio, but the VMD decoder does. + const bool success = _decoder->setReverse(true); + assert(success); + _decoder->setVolume(0); + } + + if (!_isInitialized) { + _isInitialized = true; + + if (!_showCursor) { + g_sci->_gfxCursor->kernelHide(); + } + + Common::Rect vmdRect(_x, + _y, + _x + _decoder->getWidth(), + _y + _decoder->getHeight()); + ScaleInfo vmdScaleInfo; + + if (!_blackoutRect.isEmpty() && _planeIsOwned) { + _blackoutPlane = new Plane(_blackoutRect); + g_sci->_gfxFrameout->addPlane(*_blackoutPlane); + } + + if (_doublePixels) { + vmdScaleInfo.x = 256; + vmdScaleInfo.y = 256; + vmdScaleInfo.signal = kScaleSignalDoScaling32; + vmdRect.right += vmdRect.width(); + vmdRect.bottom += vmdRect.height(); + } else if (_stretchVertical) { + vmdScaleInfo.y = 256; + vmdScaleInfo.signal = kScaleSignalDoScaling32; + vmdRect.bottom += vmdRect.height(); + } + + 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; + + BitmapResource vmdBitmap(_segMan, vmdRect.width(), vmdRect.height(), 255, 0, 0, screenWidth, screenHeight, 0, false); + + if (screenWidth != scriptWidth || screenHeight != scriptHeight) { + mulru(vmdRect, Ratio(scriptWidth, screenWidth), Ratio(scriptHeight, screenHeight), 1); + } + + CelInfo32 vmdCelInfo; + vmdCelInfo.bitmap = vmdBitmap.getObject(); + _decoder->setSurfaceMemory(vmdBitmap.getPixels(), vmdBitmap.getWidth(), vmdBitmap.getHeight(), 1); + + if (_planeIsOwned) { + _x = 0; + _y = 0; + _plane = new Plane(vmdRect, kPlanePicColored); + if (_priority) { + _plane->_priority = _priority; + } + g_sci->_gfxFrameout->addPlane(*_plane); + _screenItem = new ScreenItem(_plane->_object, vmdCelInfo, Common::Point(), vmdScaleInfo); + } else { + _screenItem = new ScreenItem(_plane->_object, vmdCelInfo, Common::Point(_x, _y), vmdScaleInfo); + if (_priority) { + _screenItem->_priority = _priority; + } + } + + if (_blackLines) { + _screenItem->_drawBlackLines = true; + } + + // NOTE: There was code for positioning the screen item using insetRect + // here, but none of the game scripts seem to use this functionality. + + g_sci->_gfxFrameout->addScreenItem(*_screenItem); + + _decoder->start(); + } + + EventFlags stopFlag = kEventFlagNone; + while (!g_engine->shouldQuit()) { + if (_decoder->endOfVideo()) { + stopFlag = kEventFlagEnd; + break; + } + + g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame()); + g_sci->getEngineState()->_throttleTrigger = true; + if (_decoder->needsUpdate()) { + renderFrame(); + } + + const int currentFrameNo = _decoder->getCurFrame(); + + if (_yieldInterval > 0 && + currentFrameNo != _lastYieldedFrameNo && + (currentFrameNo % _yieldInterval) == 0 + ) { + _lastYieldedFrameNo = currentFrameNo; + stopFlag = kEventFlagYieldToVM; + break; + } + + SciEvent event = _eventMan->getSciEvent(SCI_EVENT_MOUSE_PRESS | SCI_EVENT_PEEK); + if ((flags & kEventFlagMouseDown) && event.type == SCI_EVENT_MOUSE_PRESS) { + stopFlag = kEventFlagMouseDown; + break; + } + + event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK); + if ((flags & kEventFlagEscapeKey) && event.type == SCI_EVENT_KEYBOARD) { + bool stop = false; + if (getSciVersion() < SCI_VERSION_3) { + while ((event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD)), + event.type != SCI_EVENT_NONE) { + if (event.character == SCI_KEY_ESC) { + stop = true; + break; + } + } + } else { + stop = (event.character == SCI_KEY_ESC); + } + + 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; + } + } + + _decoder->pauseVideo(true); + return stopFlag; +} + +#pragma mark - +#pragma mark VMDPlayer - Rendering + +void VMDPlayer::renderFrame() const { + // This writes directly to the CelObjMem we already created, + // so no need to take its return value + _decoder->decodeNextFrame(); + + // NOTE: Normally this would write a hunk palette at the end of the + // video bitmap that CelObjMem would read out and submit, but instead + // we are just submitting it directly here because the decoder exposes + // this information a little bit differently than the one in SSCI + const bool dirtyPalette = _decoder->hasDirtyPalette(); + if (dirtyPalette) { + Palette palette; + palette.timestamp = g_sci->getTickCount(); + for (uint16 i = 0; i < _startColor; ++i) { + palette.colors[i].used = false; + } + for (uint16 i = _endColor; i < 256; ++i) { + palette.colors[i].used = false; + } + 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 { + fillPalette(palette); + } + + g_sci->_gfxPalette32->submit(palette); + g_sci->_gfxFrameout->updateScreenItem(*_screenItem); + g_sci->_gfxFrameout->frameOut(true); + + if (_blackPalette) { + fillPalette(palette); + g_sci->_gfxPalette32->submit(palette); + g_sci->_gfxPalette32->updateForFrame(); + g_sci->_gfxPalette32->updateHardware(); + } + } else { + g_sci->_gfxFrameout->updateScreenItem(*_screenItem); + g_sci->getSciDebugger()->onFrame(); + g_sci->_gfxFrameout->frameOut(true); + g_sci->_gfxFrameout->throttle(); + } +} + +void VMDPlayer::fillPalette(Palette &palette) const { + const byte *vmdPalette = _decoder->getPalette() + _startColor * 3; + for (uint16 i = _startColor; i <= _endColor; ++i) { + int16 r = *vmdPalette++; + int16 g = *vmdPalette++; + int16 b = *vmdPalette++; + + if (_boostPercent != 100 && i >= _boostStartColor && i <= _boostEndColor) { + r = CLIP<int16>(r * _boostPercent / 100, 0, 255); + g = CLIP<int16>(g * _boostPercent / 100, 0, 255); + b = CLIP<int16>(b * _boostPercent / 100, 0, 255); + } + + palette.colors[i].r = r; + palette.colors[i].g = g; + palette.colors[i].b = b; + palette.colors[i].used = true; + } +} + +#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 the original engine as the storage size is 4 bytes + // and used values are clamped to 0-255 + _endColor = MIN((int16)255, endColor); +} + +} // End of namespace Sci diff --git a/engines/sci/graphics/video32.h b/engines/sci/graphics/video32.h new file mode 100644 index 0000000000..7033f7c647 --- /dev/null +++ b/engines/sci/graphics/video32.h @@ -0,0 +1,312 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCI_GRAPHICS_VIDEO32_H +#define SCI_GRAPHICS_VIDEO32_H + +namespace Video { class AdvancedVMDDecoder; } +namespace Sci { +class Plane; +class ScreenItem; +class SegManager; + +#pragma mark VMDPlayer + +/** + * VMDPlayer is used to play VMD videos. + */ +class VMDPlayer { +public: + enum OpenFlags { + kOpenFlagNone = 0, + kOpenFlagMute = 1 + }; + + enum IOStatus { + kIOSuccess = 0, + kIOError = 0xFFFF + }; + + enum PlayFlags { + kPlayFlagNone = 0, + kPlayFlagDoublePixels = 1, + kPlayFlagBlackLines = 4, + kPlayFlagBoost = 0x10, + kPlayFlagLeaveScreenBlack = 0x20, + kPlayFlagLeaveLastFrame = 0x40, + kPlayFlagBlackPalette = 0x80, + kPlayFlagStretchVertical = 0x100 + }; + + enum EventFlags { + kEventFlagNone = 0, + kEventFlagEnd = 1, + kEventFlagEscapeKey = 2, + kEventFlagMouseDown = 4, + kEventFlagHotRectangle = 8, + kEventFlagToFrame = 0x10, + kEventFlagYieldToVM = 0x20, + kEventFlagReverse = 0x80 + }; + + VMDPlayer(SegManager *segMan, EventManager *eventMan); + ~VMDPlayer(); + +private: + SegManager *_segMan; + EventManager *_eventMan; + Video::AdvancedVMDDecoder *_decoder; + +#pragma mark - +#pragma mark VMDPlayer - Playback +public: + /** + * Opens a stream to a VMD resource. + */ + IOStatus open(const Common::String &fileName, const OpenFlags flags); + + /** + * Initializes the VMD rendering parameters for the + * current VMD. This must be called after `open`. + */ + void init(const int16 x, const int16 y, const PlayFlags flags, const int16 boostPercent, const int16 boostStartColor, const int16 boostEndColor); + + /** + * Stops playback and closes the currently open VMD stream. + */ + IOStatus close(); + + // NOTE: Was WaitForEvent in SSCI + EventFlags kernelPlayUntilEvent(const EventFlags flags, const int16 lastFrameNo, const int16 yieldInterval); + +private: + /** + * Whether or not a VMD stream has been opened with + * `open`. + */ + bool _isOpen; + + /** + * Whether or not a VMD player has been initialised + * with `init`. + */ + bool _isInitialized; + + /** + * For VMDs played with the `kEventFlagYieldToVM` flag, + * the number of frames that should be rendered until + * yielding back to the SCI VM. + */ + int32 _yieldInterval; + + /** + * For VMDs played with the `kEventFlagYieldToVM` flag, + * the last frame when control of the main thread was + * yielded back to the SCI VM. + */ + int _lastYieldedFrameNo; + + /** + * Plays the VMD until an event occurs (e.g. user + * presses escape, clicks, etc.). + */ + EventFlags playUntilEvent(const EventFlags flags); + +#pragma mark - +#pragma mark VMDPlayer - Rendering +private: + /** + * The location of the VMD plane, in game script + * coordinates. + */ + int16 _x, _y; + + /** + * The plane where the VMD will be drawn. + */ + Plane *_plane; + + /** + * The screen item representing the VMD surface. + */ + ScreenItem *_screenItem; + + // TODO: planeIsOwned and priority are used in SCI3+ only + + /** + * If true, the plane for this VMD was set + * externally and is not owned by this VMDPlayer. + */ + bool _planeIsOwned; + + /** + * The screen priority of the video. + * @see ScreenItem::_priority + */ + int _priority; + + /** + * Whether or not the video should be pixel doubled. + */ + bool _doublePixels; + + /** + * Whether or not the video should be pixel doubled + * vertically only. + */ + bool _stretchVertical; + + /** + * Whether or not black lines should be rendered + * across the video. + */ + bool _blackLines; + + /** + * Whether or not the playback area of the VMD + * should be left black at the end of playback. + */ + bool _leaveScreenBlack; + + /** + * Whether or not the area of the VMD should be left + * displaying the final frame of the video. + */ + bool _leaveLastFrame; + + /** + * Renders a frame of video to the output bitmap. + */ + void renderFrame() const; + + /** + * Fills the given palette with RGB values from + * the VMD palette, applying brightness boost if + * it is enabled. + */ + void fillPalette(Palette &palette) const; + +#pragma mark - +#pragma mark VMDPlayer - Blackout +public: + /** + * Sets the area of the screen that should be blacked out + * during VMD playback. + */ + void setBlackoutArea(const Common::Rect &rect) { _blackoutRect = rect; } + +private: + /** + * The dimensions of the blackout plane. + */ + Common::Rect _blackoutRect; + + /** + * An optional plane that will be used to black out + * areas of the screen outside of the VMD surface. + */ + Plane *_blackoutPlane; + +#pragma mark - +#pragma mark VMDPlayer - Palette +public: + /** + * Restricts use of the system palette by VMD playback to + * the given range of palette indexes. + */ + void restrictPalette(const uint8 startColor, const int16 endColor); + +private: + /** + * The first color in the system palette that the VMD + * can write to. + */ + uint8 _startColor; + + /** + * The last color in the system palette that the VMD + * can write to. + */ + uint8 _endColor; + + /** + * If true, video frames are rendered after a blank + * palette is submitted to the palette manager, + * which is then restored after the video pixels + * have already been rendered. + */ + bool _blackPalette; + +#pragma mark - +#pragma mark VMDPlayer - Brightness boost +private: + /** + * The amount of brightness boost for the video. + * Values above 100 increase brightness; values below + * 100 reduce it. + */ + int16 _boostPercent; + + /** + * The first color in the palette that should be + * brightness boosted. + */ + uint8 _boostStartColor; + + /** + * The last color in the palette that should be + * brightness boosted. + */ + uint8 _boostEndColor; + +#pragma mark - +#pragma mark VMDPlayer - Mouse cursor +public: + /** + * Sets whether or not the mouse cursor should be drawn. + * This does not have any effect during playback, but can + * be used to prevent the mouse cursor from being shown + * again after the video has finished. + */ + void setShowCursor(const bool shouldShow) { _showCursor = shouldShow; } + +private: + /** + * Whether or not the mouse cursor should be shown + * during playback. + */ + bool _showCursor; +}; + +class Video32 { +public: + Video32(SegManager *segMan, EventManager *eventMan) : + _VMDPlayer(segMan, eventMan) {} + + VMDPlayer &getVMDPlayer() { return _VMDPlayer; } + +private: + VMDPlayer _VMDPlayer; +}; +} // End of namespace Sci + +#endif diff --git a/engines/sci/graphics/view.h b/engines/sci/graphics/view.h index 91590208c1..96b48c0477 100644 --- a/engines/sci/graphics/view.h +++ b/engines/sci/graphics/view.h @@ -55,6 +55,7 @@ struct LoopInfo { class GfxScreen; class GfxPalette; +class Resource; /** * View class, handles loading of view resources and drawing contained cels to screen |