From 75ccabc325d56876dd34d4a55e2034ee66d33d0b Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Mon, 18 Jan 2016 00:12:47 -0600 Subject: SCI: Implement accurate renderer architecture for SCI32 --- engines/sci/console.cpp | 18 + engines/sci/console.h | 1 + engines/sci/engine/kgraphics32.cpp | 186 ++- engines/sci/engine/object.h | 36 +- engines/sci/engine/selector.cpp | 11 +- engines/sci/engine/selector.h | 2 + engines/sci/engine/vm_types.cpp | 14 + engines/sci/engine/vm_types.h | 13 + engines/sci/graphics/celobj32.cpp | 988 ++++++++++++++ engines/sci/graphics/celobj32.h | 577 ++++++++ engines/sci/graphics/frameout.cpp | 2355 ++++++++++++++++++++++---------- engines/sci/graphics/frameout.h | 554 ++++++-- engines/sci/graphics/helpers.h | 81 ++ engines/sci/graphics/lists32.h | 192 +++ engines/sci/graphics/palette32.cpp | 27 +- engines/sci/graphics/palette32.h | 383 +++--- engines/sci/graphics/picture.cpp | 6 +- engines/sci/graphics/picture.h | 3 + engines/sci/graphics/plane32.cpp | 841 ++++++++++++ engines/sci/graphics/plane32.h | 465 +++++++ engines/sci/graphics/screen.h | 5 + engines/sci/graphics/screen_item32.cpp | 534 ++++++++ engines/sci/graphics/screen_item32.h | 280 ++++ engines/sci/module.mk | 3 + engines/sci/sci.cpp | 1 + 25 files changed, 6426 insertions(+), 1150 deletions(-) create mode 100644 engines/sci/graphics/celobj32.cpp create mode 100644 engines/sci/graphics/celobj32.h create mode 100644 engines/sci/graphics/lists32.h create mode 100644 engines/sci/graphics/plane32.cpp create mode 100644 engines/sci/graphics/plane32.h create mode 100644 engines/sci/graphics/screen_item32.cpp create mode 100644 engines/sci/graphics/screen_item32.h (limited to 'engines/sci') diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp index 438c725324..bea67e6535 100644 --- a/engines/sci/console.cpp +++ b/engines/sci/console.cpp @@ -137,6 +137,8 @@ Console::Console(SciEngine *engine) : GUI::Debugger(), registerCmd("wl", WRAP_METHOD(Console, cmdWindowList)); // alias registerCmd("plane_list", WRAP_METHOD(Console, cmdPlaneList)); registerCmd("pl", WRAP_METHOD(Console, cmdPlaneList)); // alias + registerCmd("visible_plane_list", WRAP_METHOD(Console, cmdVisiblePlaneList)); + registerCmd("vpl", WRAP_METHOD(Console, cmdVisiblePlaneList)); // alias registerCmd("plane_items", WRAP_METHOD(Console, cmdPlaneItemList)); registerCmd("pi", WRAP_METHOD(Console, cmdPlaneItemList)); // alias registerCmd("saved_bits", WRAP_METHOD(Console, cmdSavedBits)); @@ -380,6 +382,7 @@ bool Console::cmdHelp(int argc, const char **argv) { debugPrintf(" animate_list / al - Shows the current list of objects in kAnimate's draw list (SCI0 - SCI1.1)\n"); debugPrintf(" window_list / wl - Shows a list of all the windows (ports) in the draw list (SCI0 - SCI1.1)\n"); debugPrintf(" plane_list / pl - Shows a list of all the planes in the draw list (SCI2+)\n"); + debugPrintf(" visible_plane_list / vpl - Shows a list of all the planes in the visible draw list (SCI2+)\n"); debugPrintf(" plane_items / pi - Shows a list of all items for a plane (SCI2+)\n"); debugPrintf(" saved_bits - List saved bits on the hunk\n"); debugPrintf(" show_saved_bits - Display saved bits\n"); @@ -1766,6 +1769,21 @@ bool Console::cmdPlaneList(int argc, const char **argv) { return true; } +bool Console::cmdVisiblePlaneList(int argc, const char **argv) { +#ifdef ENABLE_SCI32 + if (_engine->_gfxFrameout) { + debugPrintf("Visible plane list:\n"); + _engine->_gfxFrameout->printVisiblePlaneList(this); + } else { + debugPrintf("This SCI version does not have a list of planes\n"); + } +#else + debugPrintf("SCI32 isn't included in this compiled executable\n"); +#endif + return true; +} + + bool Console::cmdPlaneItemList(int argc, const char **argv) { if (argc != 2) { debugPrintf("Shows the list of items for a plane\n"); diff --git a/engines/sci/console.h b/engines/sci/console.h index 8b10912fbe..7c4de02182 100644 --- a/engines/sci/console.h +++ b/engines/sci/console.h @@ -96,6 +96,7 @@ private: bool cmdAnimateList(int argc, const char **argv); bool cmdWindowList(int argc, const char **argv); bool cmdPlaneList(int argc, const char **argv); + bool cmdVisiblePlaneList(int argc, const char **argv); bool cmdPlaneItemList(int argc, const char **argv); bool cmdSavedBits(int argc, const char **argv); bool cmdShowSavedBits(int argc, const char **argv); diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp index 8d41393a9e..93e8b90ecc 100644 --- a/engines/sci/engine/kgraphics32.cpp +++ b/engines/sci/engine/kgraphics32.cpp @@ -80,21 +80,18 @@ reg_t kCantBeHere32(EngineState *s, int argc, reg_t *argv) { } reg_t kAddScreenItem(EngineState *s, int argc, reg_t *argv) { - if (g_sci->_gfxFrameout->findScreenItem(argv[0]) == NULL) - g_sci->_gfxFrameout->kernelAddScreenItem(argv[0]); - else - g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]); - return s->r_acc; + g_sci->_gfxFrameout->kernelAddScreenItem(argv[0]); + return NULL_REG; } reg_t kUpdateScreenItem(EngineState *s, int argc, reg_t *argv) { g_sci->_gfxFrameout->kernelUpdateScreenItem(argv[0]); - return s->r_acc; + return NULL_REG; } reg_t kDeleteScreenItem(EngineState *s, int argc, reg_t *argv) { g_sci->_gfxFrameout->kernelDeleteScreenItem(argv[0]); - return s->r_acc; + return NULL_REG; } reg_t kAddPlane(EngineState *s, int argc, reg_t *argv) { @@ -115,11 +112,12 @@ reg_t kUpdatePlane(EngineState *s, int argc, reg_t *argv) { reg_t kAddPicAt(EngineState *s, int argc, reg_t *argv) { reg_t planeObj = argv[0]; GuiResourceId pictureId = argv[1].toUint16(); - int16 pictureX = argv[2].toSint16(); - int16 pictureY = argv[3].toSint16(); + int16 x = argv[2].toSint16(); + int16 y = argv[3].toSint16(); + bool mirrorX = argc > 4 ? argv[4].toSint16() : false; - g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, pictureX, pictureY); - return s->r_acc; + g_sci->_gfxFrameout->kernelAddPicAt(planeObj, pictureId, x, y, mirrorX); + return NULL_REG; } reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) { @@ -127,43 +125,13 @@ reg_t kGetHighPlanePri(EngineState *s, int argc, reg_t *argv) { } reg_t kFrameOut(EngineState *s, int argc, reg_t *argv) { -/* TODO: Transcribed from SCI engine disassembly. - GraphicsMgr &graphicsMgr = g_sci->_graphicsMgr; - if (graphicsMgr.palMorphNeeded) { - graphicsMgr.PalMorphFrameOut(&g_PalStyleRanges, false); - } - else { - // TODO: Not sure if this is a pointer or not yet. - if (g_ScrollState != nullptr) { - kFrameOutDoScroll(); - } - - bool showBits = true; - if (argc == 1) { - showBits = (bool) argv[0].toUint16(); - } - - rect SOL_Rect = { .left = 0, .top = 0, .right = UINT32_MAX, .bottom = UINT32_MAX }; - graphicsMgr.FrameOut(showBits, &rect); - } -*/ - g_sci->_gfxFrameout->kernelFrameout(); + bool showBits = argc > 0 ? argv[0].toUint16() : true; + g_sci->_gfxFrameout->kernelFrameout(showBits); return NULL_REG; } reg_t kSetPalStyleRange(EngineState *s, int argc, reg_t *argv) { -/* TODO: Transcribed from SCI engine disassembly. - uint16 start = argv[0].toUint16(); - uint16 end = argv[1].toUint16(); - if (end <= start) { - uint16 index = start; - while (index <= end) { - g_PalStyleRanges[index] = 0; - } - } -*/ - - kStub(s, argc, argv); + g_sci->_gfxFrameout->kernelSetPalStyleRange(argv[0].toUint16(), argv[1].toUint16()); return NULL_REG; } @@ -266,72 +234,59 @@ reg_t kWinHelp(EngineState *s, int argc, reg_t *argv) { } /** - * Used for scene transitions, replacing (but reusing parts of) the old - * transition code. + * Causes an immediate plane transition with an optional transition + * effect */ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) { - // Can be called with 7 or 8 parameters - // The style defines which transition to perform. Related to the transition - // tables inside graphics/transitions.cpp - uint16 showStyle = argv[0].toUint16(); // 0 - 15 - reg_t planeObj = argv[1]; // the affected plane - Common::String planeObjName = s->_segMan->getObjectName(planeObj); - uint16 seconds = argv[2].toUint16(); // seconds that the transition lasts - uint16 backColor = argv[3].toUint16(); // target back color(?). When fading out, it's 0x0000. When fading in, it's 0xffff - int16 priority = argv[4].toSint16(); // always 0xc8 (200) when fading in/out - uint16 animate = argv[5].toUint16(); // boolean, animate or not while the transition lasts - uint16 refFrame = argv[6].toUint16(); // refFrame, always 0 when fading in/out + ShowStyleType type = (ShowStyleType)argv[0].toUint16(); + reg_t planeObj = argv[1]; + int16 seconds = argv[2].toSint16(); + // NOTE: This value seems to indicate whether the transition is an + // “exit” transition (0) or an “enter” transition (-1) for fade + // transitions. For other types of transitions, it indicates a palette + // index value to use when filling the screen. + int16 back = argv[3].toSint16(); + int16 priority = argv[4].toSint16(); + int16 animate = argv[5].toSint16(); + // TODO: Rename to frameOutNow? + int16 refFrame = argv[6].toSint16(); + int16 blackScreen; + reg_t pFadeArray; int16 divisions; - // If the game has the pFadeArray selector, another parameter is used here, - // before the optional last parameter - bool hasFadeArray = g_sci->getKernel()->findSelector("pFadeArray") > 0; - if (hasFadeArray) { - // argv[7] - divisions = (argc >= 9) ? argv[8].toSint16() : -1; // divisions (transition steps?) - } else { - divisions = (argc >= 8) ? argv[7].toSint16() : -1; // divisions (transition steps?) + // SCI 2–2.1early + if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) { + blackScreen = 0; + pFadeArray = NULL_REG; + divisions = argc > 7 ? argv[7].toSint16() : -1; } - - if (showStyle > 15) { - warning("kSetShowStyle: Illegal style %d for plane %04x:%04x", showStyle, PRINT_REG(planeObj)); - return s->r_acc; + // SCI 2.1mid–2.1late + else if (getSciVersion() < SCI_VERSION_3) { + blackScreen = 0; + pFadeArray = argc > 7 ? argv[7] : NULL_REG; + divisions = argc > 8 ? argv[8].toSint16() : -1; + } + // SCI 3 + else { + blackScreen = argv[7].toSint16(); + pFadeArray = argc > 8 ? argv[8] : NULL_REG; + divisions = argc > 9 ? argv[9].toSint16() : -1; } - // GK1 calls fadeout (13) / fadein (14) with the following parameters: - // seconds: 1 - // backColor: 0 / -1 - // fade: 200 - // animate: 0 - // refFrame: 0 - // divisions: 0 / 20 - - // TODO: Check if the plane is in the list of planes to draw - - Common::String effectName = "unknown"; +// TODO: Reuse later for SCI2 and SCI3 implementation and then discard +// warning("kSetShowStyle: effect %d, plane: %04x:%04x (%s), sec: %d, " +// "dir: %d, prio: %d, animate: %d, ref frame: %d, black screen: %d, " +// "pFadeArray: %04x:%04x (%s), divisions: %d", +// type, PRINT_REG(planeObj), s->_segMan->getObjectName(planeObj), seconds, +// back, priority, animate, refFrame, blackScreen, +// PRINT_REG(pFadeArray), s->_segMan->getObjectName(pFadeArray), divisions); - switch (showStyle) { - case 0: // no transition / show - effectName = "show"; - break; - case 13: // fade out - effectName = "fade out"; - // TODO - break; - case 14: // fade in - effectName = "fade in"; - // TODO - break; - default: - // TODO - break; - } + // NOTE: The order of planeObj and showStyle are reversed + // because this is how SCI3 called the corresponding method + // on the KernelMgr + g_sci->_gfxFrameout->kernelSetShowStyle(argc, planeObj, type, seconds, back, priority, animate, refFrame, pFadeArray, divisions, blackScreen); - warning("kSetShowStyle: effect %d (%s) - plane: %04x:%04x (%s), sec: %d, " - "back: %d, prio: %d, animate: %d, ref frame: %d, divisions: %d", - showStyle, effectName.c_str(), PRINT_REG(planeObj), planeObjName.c_str(), - seconds, backColor, priority, animate, refFrame, divisions); - return s->r_acc; + return NULL_REG; } reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) { @@ -359,6 +314,8 @@ reg_t kCelInfo(EngineState *s, int argc, reg_t *argv) { } reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) { + return kStub(s, argc, argv); +#if 0 // Used by SQ6 and LSL6 hires for the text area in the bottom of the // screen. The relevant scripts also exist in Phantasmagoria 1, but they're // unused. This is always called by scripts 64906 (ScrollerWindow) and @@ -464,6 +421,7 @@ reg_t kScrollWindow(EngineState *s, int argc, reg_t *argv) { } return s->r_acc; +#endif } reg_t kSetFontRes(EngineState *s, int argc, reg_t *argv) { @@ -497,6 +455,8 @@ reg_t kFont(EngineState *s, int argc, reg_t *argv) { // TODO: Eventually, all of the kBitmap operations should be put // in a separate class +// NOTE: This size is correct only for SCI2.1mid; the size for +// SCI2/2.1early is 36 #define BITMAP_HEADER_SIZE 46 reg_t kBitmap(EngineState *s, int argc, reg_t *argv) { @@ -673,6 +633,8 @@ reg_t kEditText(EngineState *s, int argc, reg_t *argv) { } reg_t kAddLine(EngineState *s, int argc, reg_t *argv) { + return kStub(s, argc, argv); +#if 0 reg_t plane = argv[0]; Common::Point startPoint(argv[1].toUint16(), argv[2].toUint16()); Common::Point endPoint(argv[3].toUint16(), argv[4].toUint16()); @@ -681,10 +643,15 @@ reg_t kAddLine(EngineState *s, int argc, reg_t *argv) { byte priority = (byte)argv[7].toUint16(); byte control = (byte)argv[8].toUint16(); // argv[9] is unknown (usually a small number, 1 or 2). Thickness, perhaps? - return g_sci->_gfxFrameout->addPlaneLine(plane, startPoint, endPoint, color, priority, control); +// return g_sci->_gfxFrameout->addPlaneLine(plane, startPoint, endPoint, color, priority, control); + return s->r_acc; +#endif } reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv) { + return kStub(s, argc, argv); + +#if 0 reg_t hunkId = argv[0]; reg_t plane = argv[1]; Common::Point startPoint(argv[2].toUint16(), argv[3].toUint16()); @@ -694,14 +661,18 @@ reg_t kUpdateLine(EngineState *s, int argc, reg_t *argv) { byte priority = (byte)argv[8].toUint16(); byte control = (byte)argv[9].toUint16(); // argv[10] is unknown (usually a small number, 1 or 2). Thickness, perhaps? - g_sci->_gfxFrameout->updatePlaneLine(plane, hunkId, startPoint, endPoint, color, priority, control); +// g_sci->_gfxFrameout->updatePlaneLine(plane, hunkId, startPoint, endPoint, color, priority, control); return s->r_acc; +#endif } reg_t kDeleteLine(EngineState *s, int argc, reg_t *argv) { + return kStub(s, argc, argv); +#if 0 reg_t hunkId = argv[0]; reg_t plane = argv[1]; - g_sci->_gfxFrameout->deletePlaneLine(plane, hunkId); +// g_sci->_gfxFrameout->deletePlaneLine(plane, hunkId); return s->r_acc; +#endif } reg_t kSetScroll(EngineState *s, int argc, reg_t *argv) { @@ -730,12 +701,7 @@ reg_t kSetScroll(EngineState *s, int argc, reg_t *argv) { // Used by SQ6, script 900, the datacorder reprogramming puzzle (from room 270) reg_t kMorphOn(EngineState *s, int argc, reg_t *argv) { - // TODO: g_sci->_gfxManager->palMorphIsOn = true - // This function sets the palMorphIsOn flag which causes kFrameOut to use - // an alternative FrameOut function (GraphicsMgr::PalMorphFrameOut instead - // of GraphicsMgr::FrameOut). At the end of the frame, kFrameOut sets the - // palMorphIsOn flag back to false. - kStub(s, argc, argv); + g_sci->_gfxFrameout->_palMorphIsOn = true; return NULL_REG; } diff --git a/engines/sci/engine/object.h b/engines/sci/engine/object.h index 0ae7ed2cab..cc9f5ebb52 100644 --- a/engines/sci/engine/object.h +++ b/engines/sci/engine/object.h @@ -41,8 +41,21 @@ enum { }; enum infoSelectorFlags { - kInfoFlagClone = 0x0001, - kInfoFlagClass = 0x8000 + kInfoFlagClone = 0x0001, +#ifdef ENABLE_SCI32 + /** + * When set, indicates to game scripts that a screen + * item can be updated. + */ + kInfoFlagViewVisible = 0x0008, // TODO: "dirty" ? + + /** + * When set, the object has an associated screen item in + * the rendering tree. + */ + kInfoFlagViewInserted = 0x0010, +#endif + kInfoFlagClass = 0x8000 }; enum ObjectOffsets { @@ -120,7 +133,24 @@ public: _infoSelectorSci3 = info; } - // No setter for the -info- selector +#ifdef ENABLE_SCI32 + void setInfoSelectorFlag(infoSelectorFlags flag) { + if (getSciVersion() < SCI_VERSION_3) { + _variables[_offset + 2] |= flag; + } else { + _infoSelectorSci3 |= flag; + } + } + + // NOTE: In real engine, -info- is treated as byte size + void clearInfoSelectorFlag(infoSelectorFlags flag) { + if (getSciVersion() < SCI_VERSION_3) { + _variables[_offset + 2] &= ~flag; + } else { + _infoSelectorSci3 &= ~flag; + } + } +#endif reg_t getNameSelector() const { if (getSciVersion() < SCI_VERSION_3) diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp index 910f1f885f..05a9e6c980 100644 --- a/engines/sci/engine/selector.cpp +++ b/engines/sci/engine/selector.cpp @@ -187,6 +187,7 @@ void Kernel::mapSelectors() { FIND_SELECTOR(inLeft); FIND_SELECTOR(inBottom); FIND_SELECTOR(inRight); + FIND_SELECTOR(magnifier); #endif } @@ -211,8 +212,16 @@ void writeSelector(SegManager *segMan, reg_t object, Selector selectorId, reg_t if (lookupSelector(segMan, object, selectorId, &address, NULL) != kSelectorVariable) error("Selector '%s' of object at %04x:%04x could not be" " written to", g_sci->getKernel()->getSelectorName(selectorId).c_str(), PRINT_REG(object)); - else + else { *address.getPointer(segMan) = value; +#ifdef ENABLE_SCI32 + // TODO: Make this correct for all SCI versions + // Selectors 26 through 44 are selectors for View script objects + if (getSciVersion() >= SCI_VERSION_2 && selectorId >= 26 && selectorId <= 44) { + segMan->getObject(object)->setInfoSelectorFlag(kInfoFlagViewVisible); + } +#endif + } } void invokeSelector(EngineState *s, reg_t object, int selectorId, diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h index b3dd393708..a8b195f245 100644 --- a/engines/sci/engine/selector.h +++ b/engines/sci/engine/selector.h @@ -153,6 +153,8 @@ struct SelectorCache { Selector useInsetRect; Selector inTop, inLeft, inBottom, inRight; + + Selector magnifier; #endif }; diff --git a/engines/sci/engine/vm_types.cpp b/engines/sci/engine/vm_types.cpp index cf008c45e1..53a5a5c507 100644 --- a/engines/sci/engine/vm_types.cpp +++ b/engines/sci/engine/vm_types.cpp @@ -210,6 +210,20 @@ reg_t reg_t::operator^(const reg_t right) const { return lookForWorkaround(right, "bitwise XOR"); } +#ifdef ENABLE_SCI32 +reg_t reg_t::operator&(int16 right) const { + return *this & make_reg(0, right); +} + +reg_t reg_t::operator|(int16 right) const { + return *this | make_reg(0, right); +} + +reg_t reg_t::operator^(int16 right) const { + return *this ^ make_reg(0, right); +} +#endif + int reg_t::cmp(const reg_t right, bool treatAsUnsigned) const { if (getSegment() == right.getSegment()) { // can compare things in the same segment if (treatAsUnsigned || !isNumber()) diff --git a/engines/sci/engine/vm_types.h b/engines/sci/engine/vm_types.h index af78bd0b84..a646478a8e 100644 --- a/engines/sci/engine/vm_types.h +++ b/engines/sci/engine/vm_types.h @@ -136,6 +136,19 @@ struct reg_t { reg_t operator|(const reg_t right) const; reg_t operator^(const reg_t right) const; +#ifdef ENABLE_SCI32 + reg_t operator&(int16 right) const; + reg_t operator|(int16 right) const; + reg_t operator^(int16 right) const; + + void operator&=(const reg_t &right) { *this = *this & right; } + void operator|=(const reg_t &right) { *this = *this | right; } + void operator^=(const reg_t &right) { *this = *this ^ right; } + void operator&=(int16 right) { *this = *this & right; } + void operator|=(int16 right) { *this = *this | right; } + void operator^=(int16 right) { *this = *this ^ right; } +#endif + private: /** * Compares two reg_t's. diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp new file mode 100644 index 0000000000..4b8d9af384 --- /dev/null +++ b/engines/sci/graphics/celobj32.cpp @@ -0,0 +1,988 @@ +/* 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/resource.h" +#include "sci/engine/seg_manager.h" +#include "sci/engine/state.h" +#include "sci/graphics/celobj32.h" +#include "sci/graphics/frameout.h" +#include "sci/graphics/palette32.h" +#include "sci/graphics/picture.h" +#include "sci/graphics/view.h" + +namespace Sci { +#pragma mark CelScaler +CelScaler *CelObj::_scaler = nullptr; + +void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) { + const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + + for (int i = 0; i < ARRAYSIZE(_scaleTables); ++i) { + if (_scaleTables[i].scaleX == scaleX && _scaleTables[i].scaleY == scaleY) { + _activeIndex = i; + return; + } + } + + int i = 1 - _activeIndex; + _activeIndex = i; + CelScalerTable &table = _scaleTables[i]; + + if (table.scaleX != scaleX) { + assert(screenWidth <= ARRAYSIZE(table.valuesX)); + buildLookupTable(table.valuesX, scaleX, screenWidth); + } + + if (table.scaleY != scaleY) { + assert(screenHeight <= ARRAYSIZE(table.valuesY)); + buildLookupTable(table.valuesY, scaleY, screenHeight); + } +} + +void CelScaler::buildLookupTable(int *table, const Ratio &ratio, const int size) { + int value = 0; + int remainder = 0; + int num = ratio.getNumerator(); + for (int i = 0; i < size; ++i) { + *table++ = value; + remainder += ratio.getDenominator(); + if (remainder >= num) { + value += remainder / num; + remainder %= num; + } + } +} + +const CelScalerTable *CelScaler::getScalerTable(const Ratio &scaleX, const Ratio &scaleY) { + activateScaleTables(scaleX, scaleY); + return &_scaleTables[_activeIndex]; +} + +#pragma mark - +#pragma mark CelObj + +void CelObj::init() { + _nextCacheId = 1; + delete _scaler; + _scaler = new CelScaler(); + delete _cache; + _cache = new CelCache; + _cache->resize(100); +} + +void CelObj::deinit() { + delete _scaler; + _scaler = nullptr; + delete _cache; + _cache = nullptr; +} + +void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const { + const Buffer &priorityMap = g_sci->_gfxFrameout->getPriorityMap(); + const Common::Point &scaledPosition = screenItem._scaledPosition; + const Ratio &scaleX = screenItem._ratioX; + const Ratio &scaleY = screenItem._ratioY; + + if (_remap) { + if (g_sci->_gfxFrameout->_hasRemappedScreenItem) { + const uint8 priority = MAX((uint8)0, MIN((uint8)255, (uint8)screenItem._priority)); + + // NOTE: In the original engine code, there was a second branch for + // _remap here that would then call the following functions if _remap was false: + // + // drawHzFlip(Buffer &, Buffer &, Common::Rect &, Common::Point &, uint8) + // drawNoFlip(Buffer &, Buffer &, Common::Rect &, Common::Point &, uint8) + // drawUncompHzFlip(Buffer &, Buffer &, Common::Rect &, Common::Point &, uint8) + // drawUncompNoFlip(Buffer &, Buffer &, Common::Rect &, Common::Point &, uint8) + // scaleDraw(Buffer &, Buffer &, Ratio &, Ratio &, Common::Rect &, Common::Point &, uint8) + // scaleDrawUncomp(Buffer &, Buffer &, Ratio &, Ratio &, Common::Rect &, Common::Point &, uint8) + // + // However, obviously, _remap cannot be false here. This dead code branch existed in + // at least SCI2/GK1 and SCI2.1/SQ6. + + if (scaleX.isOne() && scaleY.isOne()) { + if (_compressionType == kCelCompressionNone) { + if (_drawMirrored) { + drawUncompHzFlipMap(target, priorityMap, targetRect, scaledPosition, priority); + } else { + drawUncompNoFlipMap(target, priorityMap, targetRect, scaledPosition, priority); + } + } else { + if (_drawMirrored) { + drawHzFlipMap(target, priorityMap, targetRect, scaledPosition, priority); + } else { + drawNoFlipMap(target, priorityMap, targetRect, scaledPosition, priority); + } + } + } else { + if (_compressionType == kCelCompressionNone) { + scaleDrawUncompMap(target, priorityMap, scaleX, scaleY, targetRect, scaledPosition, priority); + } else { + scaleDrawMap(target, priorityMap, scaleX, scaleY, targetRect, scaledPosition, priority); + } + } + } else { + // NOTE: In the original code this check was `g_Remap_numActiveRemaps && _remap`, + // but since we are already in a `_remap` branch, there is no reason to check it + // again + if (/* TODO: g_Remap_numActiveRemaps */ false) { + if (scaleX.isOne() && scaleY.isOne()) { + if (_compressionType == kCelCompressionNone) { + if (_drawMirrored) { + drawUncompHzFlipMap(target, targetRect, scaledPosition); + } else { + drawUncompNoFlipMap(target, targetRect, scaledPosition); + } + } else { + if (_drawMirrored) { + drawHzFlipMap(target, targetRect, scaledPosition); + } else { + drawNoFlipMap(target, targetRect, scaledPosition); + } + } + } else { + if (_compressionType == kCelCompressionNone) { + scaleDrawUncompMap(target, scaleX, scaleY, targetRect, scaledPosition); + } else { + scaleDrawMap(target, scaleX, scaleY, targetRect, scaledPosition); + } + } + } else { + if (scaleX.isOne() && scaleY.isOne()) { + if (_compressionType == kCelCompressionNone) { + if (_drawMirrored) { + drawUncompHzFlip(target, targetRect, scaledPosition); + } else { + drawUncompNoFlip(target, targetRect, scaledPosition); + } + } else { + if (_drawMirrored) { + drawHzFlip(target, targetRect, scaledPosition); + } else { + drawNoFlip(target, targetRect, scaledPosition); + } + } + } else { + if (_compressionType == kCelCompressionNone) { + scaleDrawUncomp(target, scaleX, scaleY, targetRect, scaledPosition); + } else { + scaleDraw(target, scaleX, scaleY, targetRect, scaledPosition); + } + } + } + } + } else { + if (g_sci->_gfxFrameout->_hasRemappedScreenItem) { + const uint8 priority = MAX((uint8)0, MIN((uint8)255, (uint8)screenItem._priority)); + if (scaleX.isOne() && scaleY.isOne()) { + if (_compressionType == kCelCompressionNone) { + if (_drawMirrored) { + drawUncompHzFlipNoMD(target, priorityMap, targetRect, scaledPosition, priority); + } else { + drawUncompNoFlipNoMD(target, priorityMap, targetRect, scaledPosition, priority); + } + } else { + if (_drawMirrored) { + drawHzFlipNoMD(target, priorityMap, targetRect, scaledPosition, priority); + } else { + drawNoFlipNoMD(target, priorityMap, targetRect, scaledPosition, priority); + } + } + } else { + if (_compressionType == kCelCompressionNone) { + scaleDrawUncompNoMD(target, priorityMap, scaleX, scaleY, targetRect, scaledPosition, priority); + } else { + scaleDrawNoMD(target, priorityMap, scaleX, scaleY, targetRect, scaledPosition, priority); + } + } + } else { + if (scaleX.isOne() && scaleY.isOne()) { + if (_compressionType == kCelCompressionNone) { + if (_transparent) { + if (_drawMirrored) { + drawUncompHzFlipNoMD(target, targetRect, scaledPosition); + } else { + drawUncompNoFlipNoMD(target, targetRect, scaledPosition); + } + } else { + if (_drawMirrored) { + drawUncompHzFlipNoMDNoSkip(target, targetRect, scaledPosition); + } else { + drawUncompNoFlipNoMDNoSkip(target, targetRect, scaledPosition); + } + } + } else { + if (_drawMirrored) { + drawHzFlipNoMD(target, targetRect, scaledPosition); + } else { + drawNoFlipNoMD(target, targetRect, scaledPosition); + } + } + } else { + if (_compressionType == kCelCompressionNone) { + scaleDrawUncompNoMD(target, scaleX, scaleY, targetRect, scaledPosition); + } else { + scaleDrawNoMD(target, scaleX, scaleY, targetRect, scaledPosition); + } + } + } + } +} + +void CelObj::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, bool mirrorX) { + _drawMirrored = mirrorX; + draw(target, screenItem, targetRect); +} + +void CelObj::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) { + _drawMirrored = mirrorX; + Ratio square; + drawTo(target, targetRect, scaledPosition, square, square); +} + +void CelObj::drawTo(Buffer &target, Common::Rect const &targetRect, Common::Point const &scaledPosition, Ratio const &scaleX, Ratio const &scaleY) const { + if (_remap) { + if (scaleX.isOne() && scaleY.isOne()) { + if (_compressionType == kCelCompressionNone) { + if (_drawMirrored) { + drawUncompHzFlipMap(target, targetRect, scaledPosition); + } else { + drawUncompNoFlipMap(target, targetRect, scaledPosition); + } + } else { + if (_drawMirrored) { + drawHzFlipMap(target, targetRect, scaledPosition); + } else { + drawNoFlipMap(target, targetRect, scaledPosition); + } + } + } else { + if (_compressionType == kCelCompressionNone) { + scaleDrawUncompMap(target, scaleX, scaleY, targetRect, scaledPosition); + } else { + scaleDrawMap(target, scaleX, scaleY, targetRect, scaledPosition); + } + } + } else { + if (scaleX.isOne() && scaleY.isOne()) { + if (_compressionType == kCelCompressionNone) { + if (_drawMirrored) { + drawUncompHzFlipNoMD(target, targetRect, scaledPosition); + } else { + drawUncompNoFlipNoMD(target, targetRect, scaledPosition); + } + } else { + if (_drawMirrored) { + drawHzFlipNoMD(target, targetRect, scaledPosition); + } else { + drawNoFlipNoMD(target, targetRect, scaledPosition); + } + } + } else { + if (_compressionType == kCelCompressionNone) { + scaleDrawUncompNoMD(target, scaleX, scaleY, targetRect, scaledPosition); + } else { + scaleDrawNoMD(target, scaleX, scaleY, targetRect, scaledPosition); + } + } + } +} + +uint8 CelObj::readPixel(uint16 x, const uint16 y, bool mirrorX) const { + byte *resource = getResPointer(); + byte *celHeader = resource + _celHeaderOffset; + + if (mirrorX) { + x = _width - x - 1; + } + + if (_compressionType == kCelCompressionNone) { + byte *pixels = resource + READ_SCI11ENDIAN_UINT32(celHeader + 24); + return pixels[y * _width + x]; + } else { + byte buffer[1024]; + + uint32 dataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 24); + uint32 uncompressedDataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 28); + uint32 controlOffset = READ_SCI11ENDIAN_UINT32(celHeader + 32); + + // compressed data segment for row + byte *row = resource + dataOffset + READ_SCI11ENDIAN_UINT32(resource + controlOffset + y * 4); + + // uncompressed data segment for row + byte *literal = resource + uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(resource + controlOffset + _height * 4 + y * 4); + + uint8 length; + byte controlByte; + for (uint i = 0; i <= x; i += length) { + controlByte = *row++; + length = controlByte; + + // Run-length encoded + if (controlByte & 0x80) { + length &= 0x3F; + assert(i + length < sizeof(buffer)); + + // Fill with skip color + if (controlByte & 0x40) { + memset(buffer + i, _transparentColor, length); + // Next value is fill colour + } else { + memset(buffer + i, *literal, length); + ++literal; + } + // Uncompressed + } else { + assert(i + length < sizeof(buffer)); + memcpy(buffer + i, literal, length); + literal += length; + } + } + + return buffer[x]; + } +} + +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); + 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 { + int oldestId = _nextCacheId + 1; + int oldestIndex = -1; + + 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 (oldestId > entry.id) { + oldestId = entry.id; + oldestIndex = i; + } + } else if (oldestIndex == -1) { + 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; + return -1; +} + +void CelObj::putCopyInCache(const int cacheIndex) const { + if (cacheIndex == -1) { + error("Invalid cache index"); + } + + CelCacheEntry &entry = (*_cache)[cacheIndex]; + + if (entry.celObj != nullptr) { + delete entry.celObj; + } + + entry.celObj = duplicate(); + entry.id = ++_nextCacheId; +} + +#pragma mark - +#pragma mark CelObj - Drawing +void dummyFill(Buffer &target, const Common::Rect &targetRect) { + target.fillRect(targetRect, 250); +} + +void CelObj::drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("drawHzFlip"); + dummyFill(target, targetRect); +} +void CelObj::drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("drawNoFlip"); + dummyFill(target, targetRect); +} +void CelObj::drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("drawUncompNoFlip"); + dummyFill(target, targetRect); +} +void CelObj::drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("drawUncompHzFlip"); + dummyFill(target, targetRect); +} +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); +} +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); +} +void CelObj::drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("drawHzFlipMap"); + dummyFill(target, targetRect); +} +void CelObj::drawNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("drawNoFlipMap"); + dummyFill(target, targetRect); +} +void CelObj::drawUncompNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("drawUncompNoFlipMap"); + dummyFill(target, targetRect); +} +void CelObj::drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("drawUncompHzFlipMap"); + dummyFill(target, targetRect); +} +void CelObj::scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("scaleDrawMap"); + dummyFill(target, targetRect); +} +void CelObj::scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("scaleDrawUncompMap"); + dummyFill(target, targetRect); +} +void CelObj::drawHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("drawHzFlipNoMD"); + dummyFill(target, targetRect); +} +void CelObj::drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + const int sourceX = targetRect.left - scaledPosition.x; + const int sourceY = targetRect.top - scaledPosition.y; + + byte *targetPixel = (byte *)target.getPixels() + (targetRect.top * target.screenWidth) + targetRect.left; + + const int stride = target.screenWidth - targetRect.width(); + + byte *resource = getResPointer(); + byte *celHeader = resource + _celHeaderOffset; + + byte buffer[1024]; + + uint32 dataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 24); + uint32 uncompressedDataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 28); + uint32 controlOffset = READ_SCI11ENDIAN_UINT32(celHeader + 32); + + for (int y = sourceY; y < sourceY + targetRect.height(); ++y) { + // compressed data segment for row + byte *row = resource + dataOffset + READ_SCI11ENDIAN_UINT32(resource + controlOffset + y * 4); + + // uncompressed data segment for row + byte *literal = resource + uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(resource + controlOffset + _height * 4 + y * 4); + + uint8 length; + byte controlByte; + for (int i = 0; i <= targetRect.width(); i += length) { + controlByte = *row++; + length = controlByte; + + // Run-length encoded + if (controlByte & 0x80) { + length &= 0x3F; + assert(i + length < (int)sizeof(buffer)); + + // Fill with skip color + if (controlByte & 0x40) { + memset(buffer + i, _transparentColor, length); + // Next value is fill colour + } else { + memset(buffer + i, *literal, length); + ++literal; + } + // Uncompressed + } else { + assert(i + length < (int)sizeof(buffer)); + memcpy(buffer + i, literal, length); + literal += length; + } + } + + for (int x = 0; x < targetRect.width(); ++x) { + byte pixel = buffer[sourceX + x]; + + if (pixel != _transparentColor) { + *targetPixel = pixel; + } + + ++targetPixel; + } + + targetPixel += stride; + } +} +void CelObj::drawUncompNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + const int sourceX = targetRect.left - scaledPosition.x; + const int sourceY = targetRect.top - scaledPosition.y; + + const int sourceStride = _width - targetRect.width(); + const int targetStride = target.screenWidth - targetRect.width(); + const int dataOffset = READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24); + + byte *sourcePixel = getResPointer() + dataOffset + (sourceY * _width) + sourceX; + byte *targetPixel = (byte *)target.getPixels() + targetRect.top * target.screenWidth + targetRect.left; + + for (int y = 0; y < targetRect.height(); ++y) { + for (int x = 0; x < targetRect.width(); ++x) { + byte pixel = *sourcePixel++; + + if (pixel != _transparentColor) { + *targetPixel = pixel; + } + + ++targetPixel; + } + + sourcePixel += sourceStride; + targetPixel += targetStride; + } +} +void CelObj::drawUncompNoFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + const int sourceX = targetRect.left - scaledPosition.x; + const int sourceY = targetRect.top - scaledPosition.y; + const int dataOffset = READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24); + + byte *sourcePixel = getResPointer() + dataOffset + (sourceY * _width) + sourceX; + + target.copyRectToSurface(sourcePixel, _width, targetRect.left, targetRect.top, targetRect.width(), targetRect.height()); + +} +void CelObj::drawUncompHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("drawUncompHzFlipNoMD"); + dummyFill(target, targetRect); +} +void CelObj::drawUncompHzFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("drawUncompHzFlipNoMDNoSkip"); + dummyFill(target, targetRect); +} +void CelObj::scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("scaleDrawNoMD %d/%d, %d/%d", scaleX.getNumerator(), scaleX.getDenominator(), scaleY.getNumerator(), scaleY.getDenominator()); + dummyFill(target, targetRect); +} + +void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const { + debug("scaleDrawUncompNoMD %d/%d, %d/%d", scaleX.getNumerator(), scaleX.getDenominator(), scaleY.getNumerator(), scaleY.getDenominator()); + if (targetRect.isEmpty()) { + return; + } + + const CelScalerTable *table = _scaler->getScalerTable(scaleX, scaleY); + + int pixelX[1024]; + int pixelY[1024]; + + bool use2xOptimisedDrawRoutine = false /* TODO: scaleX.getDenominator() * 2 == scaleX.getNumerator() */; + + int16 sourceX = (scaledPosition.x * scaleX.getInverse()).toInt(); + int16 sourceY = (scaledPosition.y * scaleY.getInverse()).toInt(); + + if (_drawMirrored) { + for (int x = targetRect.left; x < targetRect.right; ++x) { + pixelX[x] = _width - 1 - (table->valuesX[x] - sourceX); + } + } else { + for (int x = targetRect.left; x < targetRect.right; ++x) { + pixelX[x] = table->valuesX[x] - sourceX; + } + } + + for (int y = targetRect.top; y < targetRect.bottom; ++y) { + pixelY[y] = table->valuesY[y] - sourceY; + } + + byte *sourcePixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24); + + for (int y = targetRect.top; y < targetRect.bottom; ++y) { + byte *targetPixel = target.getAddress(targetRect.left, y); + byte *sourcePixel = sourcePixels + pixelY[y] * _width; + const int *sourcePixelIndex = pixelX + targetRect.left; + + if (/* TODO */ use2xOptimisedDrawRoutine) { + // WriteUncompScaleLine2(); + } else { + // start WriteUncompScaleLine + for (int x = targetRect.left; x < targetRect.right; ++x) { + byte value = sourcePixel[*sourcePixelIndex++]; + if (value != _transparentColor) { + *targetPixel = value; + } + ++targetPixel; + } + // end WriteUncompScaleLine + } + } +} + +// TODO: These functions may all be vestigial. +void CelObj::drawHzFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {} +void CelObj::drawNoFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {} +void CelObj::drawUncompNoFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {} +void CelObj::drawUncompHzFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {} +void CelObj::scaleDrawMap(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {} +void CelObj::scaleDrawUncompMap(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {} +void CelObj::drawHzFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {} +void CelObj::drawNoFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {} +void CelObj::drawUncompNoFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {} +void CelObj::drawUncompHzFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {} +void CelObj::scaleDrawNoMD(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {} +void CelObj::scaleDrawUncompNoMD(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const {} + +#pragma mark - +#pragma mark CelObjView +CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) { + _info.type = kCelTypeView; + _info.resourceId = viewId; + _info.loopNo = loopNo; + _info.celNo = celNo; + _mirrorX = false; + _compressionType = kCelCompressionInvalid; + _transparent = true; + + int cacheInsertIndex; + int cacheIndex = searchCache(_info, &cacheInsertIndex); + if (cacheIndex != -1) { + CelCacheEntry &entry = (*_cache)[cacheIndex]; + *this = *dynamic_cast(entry.celObj); + entry.id = ++_nextCacheId; + return; + } + + // TODO: The next code should be moved to a common file that + // generates view resource metadata for both SCI16 and SCI32 + // implementations + + Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false); + + // NOTE: SCI2.1/SQ6 just silently returns here. + if (!resource) { + warning("View resource %d not loaded", viewId); + return; + } + + byte *data = resource->data; + + _scaledWidth = READ_SCI11ENDIAN_UINT16(data + 14); + _scaledHeight = READ_SCI11ENDIAN_UINT16(data + 16); + + if (_scaledWidth == 0 || _scaledHeight == 0) { + byte sizeFlag = data[5]; + if (sizeFlag == 0) { + _scaledWidth = 320; + _scaledHeight = 200; + } else if (sizeFlag == 1) { + _scaledWidth = 640; + _scaledHeight = 480; + } else if (sizeFlag == 2) { + _scaledWidth = 640; + _scaledHeight = 400; + } + } + + uint16 loopCount = data[2]; + if (_info.loopNo >= loopCount) { + _info.loopNo = loopCount - 1; + } + + // NOTE: This is the actual check, in the actual location, + // from SCI engine. + if (loopNo < 0) { + error("Loop is less than 0!"); + } + + const uint16 viewHeaderSize = READ_SCI11ENDIAN_UINT16(data); + const uint8 loopHeaderSize = data[12]; + const uint8 viewHeaderFieldSize = 2; + + byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * _info.loopNo); + + if ((int8)loopHeader[0] != -1) { + if (loopHeader[1] == 1) { + _mirrorX = true; + } + + loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * (int8)loopHeader[0]); + } + + uint8 celCount = loopHeader[2]; + if (_info.celNo >= celCount) { + _info.celNo = celCount - 1; + } + + _hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 8); + _celHeaderOffset = READ_SCI11ENDIAN_UINT32(loopHeader + 12) + (data[13] * _info.celNo); + + byte *celHeader = data + _celHeaderOffset; + + _width = READ_SCI11ENDIAN_UINT16(celHeader); + _height = READ_SCI11ENDIAN_UINT16(celHeader + 2); + _displace.x = _width / 2 - (int16)READ_SCI11ENDIAN_UINT16(celHeader + 4); + _displace.y = _height - (int16)READ_SCI11ENDIAN_UINT16(celHeader + 6) - 1; + _transparentColor = celHeader[8]; + _compressionType = (CelCompressionType)celHeader[9]; + + if (_compressionType != kCelCompressionNone && _compressionType != kCelCompressionRLE) { + error("Compression type not supported - V: %d L: %d C: %d", _info.resourceId, _info.loopNo, _info.celNo); + } + + if (celHeader[10] & 128) { + // NOTE: This is correct according to SCI2.1/SQ6/DOS; + // the engine re-reads the byte value as a word value + uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10); + _transparent = flags & 1 ? true : false; + _remap = flags & 2 ? true : false; + } else if (_compressionType == kCelCompressionNone) { + _remap = analyzeUncompressedForRemap(); + } else { + _remap = analyzeForRemap(); + } + + putCopyInCache(cacheInsertIndex); +} + +bool CelObjView::analyzeUncompressedForRemap() const { + byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24); + for (int i = 0; i < _width * _height; ++i) { + uint8 pixel = pixels[i]; + if (/* TODO: pixel >= Remap::minRemapColor && pixel <= Remap::maxRemapColor */ false && pixel != _transparentColor) { + return true; + } + } + return false; +} + +bool CelObjView::analyzeForRemap() const { + // TODO: Implement decompression and analysis + return false; +} + +void CelObjView::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX, const Ratio &scaleX, const Ratio &scaleY) { + _drawMirrored = mirrorX; + drawTo(target, targetRect, scaledPosition, scaleX, scaleY); +} + +CelObjView *CelObjView::duplicate() const { + return new CelObjView(*this); +} + +byte *CelObjView::getResPointer() const { + return g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false)->data; +} + +#pragma mark - +#pragma mark CelObjPic +CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { + _info.type = kCelTypePic; + _info.resourceId = picId; + _info.loopNo = 0; + _info.celNo = celNo; + _mirrorX = false; + _compressionType = kCelCompressionInvalid; + _transparent = true; + _remap = false; + + int cacheInsertIndex; + int cacheIndex = searchCache(_info, &cacheInsertIndex); + if (cacheIndex != -1) { + CelCacheEntry &entry = (*_cache)[cacheIndex]; + *this = *dynamic_cast(entry.celObj); + entry.id = ++_nextCacheId; + return; + } + + Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, picId), false); + + // NOTE: SCI2.1/SQ6 just silently returns here. + if (!resource) { + warning("Pic resource %d not loaded", picId); + return; + } + + byte *data = resource->data; + + _celCount = data[2]; + + if (_info.celNo >= _celCount) { + error("Cel number %d greater than cel count %d", _info.celNo, _celCount); + } + + _celHeaderOffset = READ_SCI11ENDIAN_UINT16(data) + (READ_SCI11ENDIAN_UINT16(data + 4) * _info.celNo); + _hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 6); + + byte *celHeader = data + _celHeaderOffset; + + _width = READ_SCI11ENDIAN_UINT16(celHeader); + _height = READ_SCI11ENDIAN_UINT16(celHeader + 2); + _displace.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 4); + _displace.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 6); + _transparentColor = celHeader[8]; + _compressionType = (CelCompressionType)celHeader[9]; + _priority = READ_SCI11ENDIAN_UINT16(celHeader + 36); + _relativePosition.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 38); + _relativePosition.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 40); + + uint16 sizeFlag1 = READ_SCI11ENDIAN_UINT16(data + 10); + uint16 sizeFlag2 = READ_SCI11ENDIAN_UINT16(data + 12); + + if (sizeFlag2) { + _scaledWidth = sizeFlag1; + _scaledHeight = sizeFlag2; + } else if (sizeFlag1 == 0) { + _scaledWidth = 320; + _scaledHeight = 200; + } else if (sizeFlag1 == 1) { + _scaledWidth = 640; + _scaledHeight = 480; + } else if (sizeFlag1 == 2) { + _scaledWidth = 640; + _scaledHeight = 400; + } + + if (celHeader[10] & 128) { + // NOTE: This is correct according to SCI2.1/SQ6/DOS; + // the engine re-reads the byte value as a word value + uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10); + _transparent = flags & 1 ? true : false; + _remap = flags & 2 ? true : false; + } else { + _transparent = _compressionType != kCelCompressionNone ? true : analyzeUncompressedForSkip(); + + if (_compressionType != kCelCompressionNone && _compressionType != kCelCompressionRLE) { + error("Compression type not supported - P: %d C: %d", picId, celNo); + } + } + + putCopyInCache(cacheInsertIndex); +} + +bool CelObjPic::analyzeUncompressedForSkip() const { + byte *resource = getResPointer(); + byte *pixels = resource + READ_SCI11ENDIAN_UINT32(resource + _celHeaderOffset + 24); + for (int i = 0; i < _width * _height; ++i) { + uint8 pixel = pixels[i]; + if (pixel == _transparentColor) { + return true; + } + } + + return false; +} + +void CelObjPic::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) { + Ratio square; + _drawMirrored = mirrorX; + drawTo(target, targetRect, scaledPosition, square, square); +} + +CelObjPic *CelObjPic::duplicate() const { + return new CelObjPic(*this); +} + +byte *CelObjPic::getResPointer() const { + return g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, _info.resourceId), false)->data; +} + +#pragma mark - +#pragma mark CelObjMem +CelObjMem::CelObjMem(const reg_t bitmap) { + _info.type = kCelTypeMem; + _info.bitmap = bitmap; + _mirrorX = false; + _compressionType = kCelCompressionNone; + _celHeaderOffset = 0; + _transparent = true; + + byte *bitmapData = g_sci->getEngineState()->_segMan->getHunkPointer(bitmap); + if (bitmapData == nullptr || READ_SCI11ENDIAN_UINT32(bitmapData + 28) != 46) { + error("Invalid Text bitmap %04x:%04x", PRINT_REG(bitmap)); + } + + _width = READ_SCI11ENDIAN_UINT16(bitmapData); + _height = READ_SCI11ENDIAN_UINT16(bitmapData + 2); + _displace.x = READ_SCI11ENDIAN_UINT16(bitmapData + 4); + _displace.y = READ_SCI11ENDIAN_UINT16(bitmapData + 6); + _transparentColor = bitmapData[8]; + _scaledWidth = READ_SCI11ENDIAN_UINT16(bitmapData + 36); + _scaledHeight = READ_SCI11ENDIAN_UINT16(bitmapData + 38); + _hunkPaletteOffset = READ_SCI11ENDIAN_UINT16(bitmapData + 20); + _remap = (READ_SCI11ENDIAN_UINT16(bitmapData + 10) & 2) ? true : false; +} + +CelObjMem *CelObjMem::duplicate() const { + return new CelObjMem(*this); +} + +byte *CelObjMem::getResPointer() const { + return g_sci->getEngineState()->_segMan->getHunkPointer(_info.bitmap); +} + +#pragma mark - +#pragma mark CelObjColor +CelObjColor::CelObjColor(const uint8 color, const int16 width, const int16 height) { + _info.type = kCelTypeColor; + _info.color = color; + _displace.x = 0; + _displace.y = 0; + _scaledWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + _scaledHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + _hunkPaletteOffset = 0; + _mirrorX = false; + _remap = false; + _width = width; + _height = height; +} + +void CelObjColor::draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, const bool mirrorX) { + // TODO: The original engine sets this flag but why? One cannot + // draw a solid colour mirrored. + _drawMirrored = mirrorX; + draw(target, targetRect); +} +void CelObjColor::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX) { + error("Unsupported method"); +} +void CelObjColor::draw(Buffer &target, const Common::Rect &targetRect) const { + target.fillRect(targetRect, _info.color); +} + +CelObjColor *CelObjColor::duplicate() const { + return new CelObjColor(*this); +} + +byte *CelObjColor::getResPointer() const { + error("Unsupported method"); +} +} diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h new file mode 100644 index 0000000000..8bda86ec7e --- /dev/null +++ b/engines/sci/graphics/celobj32.h @@ -0,0 +1,577 @@ +/* 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_CELOBJ32_H +#define SCI_GRAPHICS_CELOBJ32_H + +#include "common/rational.h" +#include "common/rect.h" +#include "sci/resource.h" +#include "sci/engine/vm_types.h" + +namespace Sci { +typedef Common::Rational Ratio; + +enum CelType { + kCelTypeView = 0, + kCelTypePic = 1, + kCelTypeMem = 2, + kCelTypeColor = 3 +}; + +enum CelCompressionType { + kCelCompressionNone = 0, + kCelCompressionRLE = 138, + kCelCompressionInvalid = 1000 +}; + +/** + * A CelInfo32 object describes the basic properties of a + * cel object. + */ +struct CelInfo32 { + /** + * The type of the cel object. + */ + CelType type; + + /** + * For cel objects that draw from resources, the ID of + * the resource to load. + */ + GuiResourceId resourceId; + + /** + * For CelObjView, the loop number to draw from the + * view resource. + */ + int16 loopNo; + + /** + * For CelObjView and CelObjPic, the cel number to draw + * from the view or pic resource. + */ + int16 celNo; + + /** + * For CelObjMem, a segment register pointing to a heap + * resource containing headered bitmap data. + */ + reg_t bitmap; + + /** + * For CelObjColor, the fill colour. + */ + uint8 color; + + // NOTE: In at least SCI2.1/SQ6, color is left + // uninitialised. + CelInfo32() : + type(kCelTypeMem), + resourceId(0), + loopNo(0), + celNo(0), + bitmap(NULL_REG) {} + + // NOTE: This is the equivalence criteria used by + // CelObj::searchCache in at least SCI2.1/SQ6. Notably, + // it does not check the color field. + inline bool operator==(const CelInfo32 &other) { + return ( + type == other.type && + resourceId == other.resourceId && + loopNo == other.loopNo && + celNo == other.celNo && + bitmap == other.bitmap + ); + } +}; + +class CelObj; +struct CelCacheEntry { + /** + * A monotonically increasing cache ID used to identify + * the least recently used item in the cache for + * replacement. + */ + int id; + CelObj *celObj; + CelCacheEntry() : id(0), celObj(nullptr) {} +}; + +typedef Common::Array CelCache; + +#pragma mark - +#pragma mark CelScaler + +struct CelScalerTable { + /** + * A lookup table of indexes that should be used to find + * the correct column to read from the source bitmap + * when drawing a scaled version of the source bitmap. + */ + int valuesX[1024]; + + /** + * The ratio used to generate the x-values. + */ + Ratio scaleX; + + /** + * A lookup table of indexes that should be used to find + * the correct row to read from a source bitmap when + * drawing a scaled version of the source bitmap. + */ + int valuesY[1024]; + + /** + * The ratio used to generate the y-values. + */ + Ratio scaleY; +}; + +class CelScaler { + /** + * Cached scale tables. + */ + CelScalerTable _scaleTables[2]; + + /** + * The index of the most recently used scale table. + */ + int _activeIndex; + + /** + * Activates a scale table for the given X and Y ratios. + * If there is no table that matches the given ratios, + * the least most recently used table will be replaced + * and activated. + */ + void activateScaleTables(const Ratio &scaleX, const Ratio &scaleY); + + /** + * Builds a pixel lookup table in `table` for the given + * ratio. The table will be filled up to the specified + * size, which should be large enough to draw across the + * entire target buffer. + */ + void buildLookupTable(int *table, const Ratio &ratio, const int size); + +public: + CelScaler() : + _scaleTables(), + _activeIndex(0) { + CelScalerTable &table = _scaleTables[_activeIndex]; + table.scaleX = Ratio(); + table.scaleY = Ratio(); + for (int i = 0; i < ARRAYSIZE(table.valuesX); ++i) { + table.valuesX[i] = i; + table.valuesY[i] = i; + } + } + + /** + * Retrieves scaler tables for the given X and Y ratios. + */ + const CelScalerTable *getScalerTable(const Ratio &scaleX, const Ratio &scaleY); +}; + +#pragma mark - +#pragma mark CelObj + +class ScreenItem; +/** + * A cel object is the lowest-level rendering primitive in + * the SCI engine and draws itself directly to a target + * pixel buffer. + */ +class CelObj { +private: + static CelScaler *_scaler; + +protected: + /** + * 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 + * cel's `_mirrorX` property and the owner screen item's + * `_mirrorX` property. + */ + bool _drawMirrored; + +public: + /** + * The basic identifying information for this cel. This + * information effectively acts as a composite key for + * a cel object, and any cel object can be recreated + * from this data alone. + */ + CelInfo32 _info; + + /** + * The offset to the cel header for this cel within the + * raw resource data. + */ + uint32 _celHeaderOffset; + + /** + * The offset to the embedded palette for this cel + * within the raw resource data. + */ + uint32 _hunkPaletteOffset; + + /** + * The natural dimensions of the cel. + */ + uint16 _width, _height; + + /** + * TODO: Documentation + */ + Common::Point _displace; + + /** + * The dimensions of the original coordinate system for + * the cel. Used to scale cels from their native size + * to the correct size on screen. + * + * @note This is set to scriptWidth/Height for + * CelObjColor. For other cel objects, the value comes + * from the raw resource data. For text bitmaps, this is + * the width/height of the coordinate system used to + * generate the text, which also defaults to + * scriptWidth/Height but seems to typically be changed + * to more closely match the native screen resolution. + */ + uint16 _scaledWidth, _scaledHeight; + + /** + * The skip (transparent) colour for the cel. When + * compositing, any pixels matching this colour will not + * be copied to the buffer. + */ + uint8 _transparentColor; + + /** + * Whether or not this cel has any transparent regions. + * This is used for optimised drawing of non-transparent + * cels. + */ + bool _transparent; // TODO: probably "skip"? + + /** + * The compression type for the pixel data for this cel. + */ + CelCompressionType _compressionType; + + /** + * Whether or not this cel should be palette-remapped? + */ + bool _remap; + + /** + * If true, the cel contains pre-mirrored picture data. + * This value comes directly from the resource data and + * is XORed with the `_mirrorX` property of the owner + * screen item when rendering. + */ + bool _mirrorX; + + /** + * Initialises static CelObj members. + */ + static void init(); + + /** + * Frees static CelObj members. + */ + static void deinit(); + + virtual ~CelObj() {}; + + /** + * Draws the cel to the target buffer using the priority + * and positioning information from the given screen + * item. The mirroring of the cel will be unchanged from + * any previous call to draw. + */ + void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect) const; + + /** + * Draws the cel to the target buffer using the priority + * and positioning information from the given screen + * item and the given mirror flag. + * + * @note In SCI engine, this function was a virtual + * function, but CelObjView, CelObjPic, and CelObjMem + * all used the same function and the compiler + * deduplicated the copies; we deduplicate the source by + * putting the implementation on CelObj instead of + * copying it to 3/4 of the subclasses. + */ + virtual void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, const bool mirrorX); + + /** + * Draws the cel to the target buffer using the + * positioning and mirroring information from the + * provided arguments. + * + * @note In SCI engine, this function was a virtual + * function, but CelObjView, CelObjPic, and CelObjMem + * all used the same function and the compiler + * deduplicated the copies; we deduplicate the source by + * putting the implementation on CelObj instead of + * copying it to 3/4 of the subclasses. + */ + virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX); + + /** + * Draws the cel to the target buffer using the given + * position and scaling parameters. The mirroring of the + * cel will be unchanged from any previous call to draw. + */ + void drawTo(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio &scaleX, const Ratio &scaleY) const; + + /** + * Creates a copy of this cel on the free store and + * returns a pointer to the new object. The new cel will + * point to a shared copy of bitmap/resource data. + */ + virtual CelObj *duplicate() const = 0; + + /** + * Retrieves a pointer to the raw resource data for this + * cel. This method cannot be used with a CelObjColor. + */ + virtual byte *getResPointer() const = 0; + + /** + * Reads the pixel at the given coordinates. This method + * is valid only for CelObjView and CelObjPic. + */ + inline uint8 readPixel(uint16 x, uint16 y, bool mirrorX) const; + + /** + * Submits the palette from this cel to the palette + * manager for integration into the master screen + * palette. + */ + void submitPalette() const; + +#pragma mark - +#pragma mark CelObj - Drawing +private: + void drawHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawUncompNoFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawUncompHzFlip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void scaleDraw(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void scaleDrawUncomp(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawUncompNoFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawUncompHzFlipMap(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void scaleDrawMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void scaleDrawUncompMap(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawHzFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const; + void drawNoFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const; + void drawUncompNoFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const; + void drawUncompHzFlipMap(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const; + void scaleDrawMap(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const; + void scaleDrawUncompMap(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const; + void drawHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawUncompNoFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawUncompNoFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawUncompHzFlipNoMD(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawUncompHzFlipNoMDNoSkip(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void scaleDrawNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition) const; + void drawHzFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const; + void drawNoFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const; + void drawUncompNoFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const; + void drawUncompHzFlipNoMD(Buffer &target, const Buffer &priorityMap, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const; + void scaleDrawNoMD(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const; + void scaleDrawUncompNoMD(Buffer &target, const Buffer &priorityMap, const Ratio &scaleX, const Ratio &scaleY, const Common::Rect &targetRect, const Common::Point &scaledPosition, const uint8 priority) const; + +#pragma mark - +#pragma mark CelObj - Caching +protected: + /** + * A monotonically increasing cache ID used to identify + * the least recently used item in the cache for + * replacement. + */ + static int _nextCacheId; + + /** + * A cache of cel objects used to avoid reinitialisation + * overhead for cels with the same CelInfo32. + */ + // NOTE: At least SQ6 uses a fixed cache size of 100. + static CelCache *_cache; + + /** + * Searches the cel cache for a CelObj matching the + * provided CelInfo32. If not found, -1 is returned. + * nextInsertIndex will receive the index of the oldest + * item in the cache, which can be used to replace + * the oldest item with a newer item. + */ + int searchCache(const CelInfo32 &celInfo, int *nextInsertIndex) const; + + /** + * Puts a copy of this CelObj into the cache at the + * given cache index. + */ + void putCopyInCache(int index) const; +}; + +#pragma mark - +#pragma mark CelObjView + +/** + * A CelObjView is the drawing primitive for a View type + * resource. Each CelObjView corresponds to a single cel + * within a single loop of a view. + */ +class CelObjView : public CelObj { +private: + /** + * Analyses resources without baked-in remap flags + * to determine whether or not they should be remapped. + */ + bool analyzeUncompressedForRemap() const; + + /** + * Analyses compressed resources without baked-in remap + * flags to determine whether or not they should be + * remapped. + */ + bool analyzeForRemap() const; + +public: + CelObjView(GuiResourceId viewId, int16 loopNo, int16 celNo); + virtual ~CelObjView() override {}; + + using CelObj::draw; + + /** + * Draws the cel to the target buffer using the + * positioning, mirroring, and scaling information from + * the provided arguments. + */ + void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX, const Ratio &scaleX, const Ratio &scaleY); + + virtual CelObjView *duplicate() const override; + virtual byte *getResPointer() const override; +}; + +#pragma mark - +#pragma mark CelObjPic + +/** + * A CelObjPic is the drawing primitive for a Picture type + * resource. Each CelObjPic corresponds to a single cel + * within a picture. + */ +class CelObjPic : public CelObj { +private: + /** + * Analyses uncompressed resources without baked-in skip + * flags to determine whether or not they can use fast + * blitting. + */ + bool analyzeUncompressedForSkip() const; + +public: + /** + * The number of cels in the original picture resource. + */ + uint8 _celCount; + + /** + * The position of this cel relative to the top-left + * corner of the picture. + */ + Common::Point _relativePosition; + + /** + * The z-buffer priority for this cel. Higher prorities + * are drawn on top of lower priorities. + */ + int16 _priority; + + CelObjPic(GuiResourceId pictureId, int16 celNo); + virtual ~CelObjPic() override {}; + + using CelObj::draw; + virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) override; + + virtual CelObjPic *duplicate() const override; + virtual byte *getResPointer() const override; +}; + +#pragma mark - +#pragma mark CelObjMem + +/** + * A CelObjMem is the drawing primitive for arbitrary + * bitmaps generated in memory. Generated bitmaps in SCI32 + * include text & vector drawings and per-pixel screen + * transitions like dissolves. + */ +class CelObjMem : public CelObj { +public: + CelObjMem(reg_t bitmap); + virtual ~CelObjMem() override {}; + + virtual CelObjMem *duplicate() const override; + virtual byte *getResPointer() const override; +}; + +#pragma mark - +#pragma mark CelObjColor + +/** + * A CelObjColor is the drawing primitive for fast, + * low-memory, flat colour fills. + */ +class CelObjColor : public CelObj { +public: + CelObjColor(uint8 color, int16 width, int16 height); + virtual ~CelObjColor() override {}; + + using CelObj::draw; + /** + * Block fills the target buffer with the cel colour. + */ + void draw(Buffer &target, const Common::Rect &targetRect) const; + virtual void draw(Buffer &target, const ScreenItem &screenItem, const Common::Rect &targetRect, bool mirrorX) override; + virtual void draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, bool mirrorX) override; + + virtual CelObjColor *duplicate() const override; + virtual byte *getResPointer() const override; +}; +} + +#endif diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp index cb81fe8d61..d748cc05de 100644 --- a/engines/sci/graphics/frameout.cpp +++ b/engines/sci/graphics/frameout.cpp @@ -47,962 +47,1857 @@ #include "sci/graphics/palette32.h" #include "sci/graphics/picture.h" #include "sci/graphics/text32.h" +#include "sci/graphics/plane32.h" +#include "sci/graphics/screen_item32.h" #include "sci/graphics/frameout.h" #include "sci/video/robot_decoder.h" namespace Sci { -// TODO/FIXME: This is all guesswork +// TODO/FIXME: This is partially guesswork -enum SciSpeciaPlanelPictureCodes { - kPlaneTranslucent = 0xfffe, // -2 - kPlanePlainColored = 0xffff // -1 +static int dissolveSequences[2][20] = { + /* SCI2.1early- */ { 3, 6, 12, 20, 48, 96, 184, 272, 576, 1280, 3232, 6912, 13568, 24576, 46080 }, + /* SCI2.1mid+ */ { 0, 0, 3, 6, 12, 20, 48, 96, 184, 272, 576, 1280, 3232, 6912, 13568, 24576, 46080, 73728, 132096, 466944 } +}; +static int16 divisionsDefaults[2][16] = { + /* SCI2.1early- */ { 1, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 40, 40, 101, 101 }, + /* SCI2.1mid+ */ { 1, 20, 20, 20, 20, 10, 10, 10, 10, 20, 20, 6, 10, 101, 101, 2 } +}; +static int16 unknownCDefaults[2][16] = { + /* SCI2.1early- */ { 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 0, 0, 0, 0 }, + /* 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) - : _segMan(segMan), _resMan(resMan), _cache(cache), _screen(screen), _palette(palette), _paint32(paint32), _isHiRes(false) { - - _coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster; - _curScrollText = -1; - _showScrollText = false; - _maxScrollTexts = 0; +GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxCache *cache, GfxScreen *screen, GfxPalette32 *palette, GfxPaint32 *paint32) : + _segMan(segMan), + _resMan(resMan), + _cache(cache), + _screen(screen), + _palette(palette), + _paint32(paint32), + _isHiRes(false), + _palMorphIsOn(false), + _showStyles(nullptr), + _remapOccurred(false), + _frameNowVisible(false), + // TODO: Stop using _gfxScreen + _currentBuffer(screen->getWidth(), screen->getHeight(), nullptr), + _priorityMap(screen->getWidth(), screen->getHeight(), nullptr), + _screenRect(screen->getWidth(), screen->getHeight()), + _overdrawThreshold(0) { + + _currentBuffer.setPixels(calloc(1, screen->getWidth() * screen->getHeight())); + + for (int i = 0; i < 236; i += 2) { + _styleRanges[i] = 0; + _styleRanges[i + 1] = -1; + } + for (int i = 236; i < ARRAYSIZE(_styleRanges); ++i) { + _styleRanges[i] = 0; + } // TODO: Make hires detection work uniformly across all SCI engine // versions (this flag is normally passed by SCI::MakeGraphicsMgr - // to the GraphicsMgr constructor depending upon video configuration) + // to the GraphicsMgr constructor depending upon video configuration, + // so should be handled upstream based on game configuration instead + // of here) if (getSciVersion() >= SCI_VERSION_2_1_EARLY && _resMan->detectHires()) { _isHiRes = true; } + + if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) { + _dissolveSequenceSeeds = dissolveSequences[0]; + _defaultDivisions = divisionsDefaults[0]; + _defaultUnknownC = unknownCDefaults[0]; + } else { + _dissolveSequenceSeeds = dissolveSequences[1]; + _defaultDivisions = divisionsDefaults[1]; + _defaultUnknownC = unknownCDefaults[1]; + } + + // TODO: Nothing in the renderer really uses this. Currently, + // the cursor renderer does, and kLocalToGlobal/kGlobalToLocal + // do, but in the real engine (1) the cursor is handled in + // frameOut, and (2) functions do a very simple lookup of the + // plane and arithmetic with the plane's gameRect. In + // principle, CoordAdjuster could be reused for + // convertGameRectToPlaneRect, but it is not super clear yet + // what the benefit would be to do that. + _coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster; + + // TODO: Script resolution is hard-coded per game; + // also this must be set or else the engine will crash + _coordAdjuster->setScriptsResolution(_currentBuffer.scriptWidth, _currentBuffer.scriptHeight); } GfxFrameout::~GfxFrameout() { + CelObj::deinit(); clear(); } +void GfxFrameout::run() { + CelObj::init(); + Plane::init(); + ScreenItem::init(); + + // NOTE: This happens in SCI::InitPlane in the actual engine, + // and is a background fill plane to ensure hidden planes + // (planes with a priority of -1) are never drawn + Plane *initPlane = new Plane(Common::Rect(_currentBuffer.scriptWidth - 1, _currentBuffer.scriptHeight - 1)); + initPlane->_priority = 0; + _planes.add(initPlane); +} + void GfxFrameout::clear() { - deletePlaneItems(NULL_REG); _planes.clear(); - deletePlanePictures(NULL_REG); - clearScrollTexts(); -} - -void GfxFrameout::clearScrollTexts() { - _scrollTexts.clear(); - _curScrollText = -1; -} - -void GfxFrameout::addScrollTextEntry(Common::String &text, reg_t kWindow, uint16 x, uint16 y, bool replace) { - //reg_t bitmapHandle = g_sci->_gfxText32->createScrollTextBitmap(text, kWindow); - // HACK: We set the container dimensions manually - reg_t bitmapHandle = g_sci->_gfxText32->createScrollTextBitmap(text, kWindow, 480, 70); - ScrollTextEntry textEntry; - textEntry.bitmapHandle = bitmapHandle; - textEntry.kWindow = kWindow; - textEntry.x = x; - textEntry.y = y; - if (!replace || _scrollTexts.size() == 0) { - if (_scrollTexts.size() > _maxScrollTexts) { - _scrollTexts.remove_at(0); - _curScrollText--; - } - _scrollTexts.push_back(textEntry); - _curScrollText++; + _showList.clear(); +} + +#pragma mark - +#pragma mark Screen items + +void GfxFrameout::kernelAddScreenItem(const reg_t object) { + const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane)); + +// TODO: Remove +// debug("Adding screen item %04x:%04x to plane %04x:%04x", PRINT_REG(object), PRINT_REG(planeObject)); + + _segMan->getObject(object)->setInfoSelectorFlag(kInfoFlagViewInserted); + + Plane *plane = _planes.findByObject(planeObject); + if (plane == nullptr) { + error("Invalid plane selector passed to kAddScreenItem"); + } + + ScreenItem *screenItem = plane->_screenItemList.findByObject(object); + if (screenItem != nullptr) { + screenItem->update(object); } else { - _scrollTexts.pop_back(); - _scrollTexts.push_back(textEntry); + screenItem = new ScreenItem(object); + plane->_screenItemList.add(screenItem); } } -void GfxFrameout::showCurrentScrollText() { - if (!_showScrollText || _curScrollText < 0) - return; +void GfxFrameout::kernelUpdateScreenItem(const reg_t object) { + const reg_t magnifierObject = readSelector(_segMan, object, SELECTOR(magnifier)); + if (magnifierObject.isNull()) { + const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane)); + Plane *plane = _planes.findByObject(planeObject); + if (plane == nullptr) { + error("Invalid plane selector passed to kUpdateScreenItem"); + } - uint16 size = (uint16)_scrollTexts.size(); - if (size > 0) { - assert(_curScrollText < size); - ScrollTextEntry textEntry = _scrollTexts[_curScrollText]; - g_sci->_gfxText32->drawScrollTextBitmap(textEntry.kWindow, textEntry.bitmapHandle, textEntry.x, textEntry.y); - } -} - -extern void showScummVMDialog(const Common::String &message); - -void GfxFrameout::kernelAddPlane(reg_t object) { - PlaneEntry newPlane; - - if (_planes.empty()) { - // There has to be another way for sierra sci to do this or maybe script resolution is compiled into - // interpreter (TODO) - uint16 scriptWidth = readSelectorValue(_segMan, object, SELECTOR(resX)); - uint16 scriptHeight = readSelectorValue(_segMan, object, SELECTOR(resY)); - - // Phantasmagoria 2 doesn't specify a script width/height - if (g_sci->getGameId() == GID_PHANTASMAGORIA2) { - scriptWidth = 640; - scriptHeight = 480; - } - - assert(scriptWidth > 0 && scriptHeight > 0); - _coordAdjuster->setScriptsResolution(scriptWidth, scriptHeight); - } - - // Import of QfG character files dialog is shown in QFG4. - // Display additional popup information before letting user use it. - // For the SCI0-SCI1.1 version of this, check kDrawControl(). - if (g_sci->inQfGImportRoom() && !strcmp(_segMan->getObjectName(object), "DSPlane")) { - showScummVMDialog("Characters saved inside ScummVM are shown " - "automatically. Character files saved in the original " - "interpreter need to be put inside ScummVM's saved games " - "directory and a prefix needs to be added depending on which " - "game it was saved in: 'qfg1-' for Quest for Glory 1, 'qfg2-' " - "for Quest for Glory 2, 'qfg3-' for Quest for Glory 3. " - "Example: 'qfg2-thief.sav'."); - } - - newPlane.object = object; - newPlane.priority = readSelectorValue(_segMan, object, SELECTOR(priority)); - newPlane.lastPriority = -1; // hidden - newPlane.planeOffsetX = 0; - newPlane.planeOffsetY = 0; - newPlane.pictureId = kPlanePlainColored; - newPlane.planePictureMirrored = false; - newPlane.planeBack = 0; - _planes.push_back(newPlane); - - kernelUpdatePlane(object); -} - -void GfxFrameout::kernelUpdatePlane(reg_t object) { - for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) { - if (it->object == object) { - // Read some information - it->priority = readSelectorValue(_segMan, object, SELECTOR(priority)); - GuiResourceId lastPictureId = it->pictureId; - it->pictureId = readSelectorValue(_segMan, object, SELECTOR(picture)); - if (lastPictureId != it->pictureId) { - // picture got changed, load new picture - deletePlanePictures(object); - // Draw the plane's picture if it's not a translucent/plane colored frame - if ((it->pictureId != kPlanePlainColored) && (it->pictureId != kPlaneTranslucent)) { - // SQ6 gives us a bad picture number for the control menu - if (_resMan->testResource(ResourceId(kResourceTypePic, it->pictureId))) - addPlanePicture(object, it->pictureId, 0); - } - } - it->planeRect.top = readSelectorValue(_segMan, object, SELECTOR(top)); - it->planeRect.left = readSelectorValue(_segMan, object, SELECTOR(left)); - it->planeRect.bottom = readSelectorValue(_segMan, object, SELECTOR(bottom)); - it->planeRect.right = readSelectorValue(_segMan, object, SELECTOR(right)); + ScreenItem *screenItem = plane->_screenItemList.findByObject(object); + if (screenItem == nullptr) { + error("Invalid screen item passed to kUpdateScreenItem"); + } - _coordAdjuster->fromScriptToDisplay(it->planeRect.top, it->planeRect.left); - _coordAdjuster->fromScriptToDisplay(it->planeRect.bottom, it->planeRect.right); + screenItem->update(object); + } else { + warning("TODO: Magnifier view not implemented yet!"); + } +} - // We get negative left in kq7 in scrolling rooms - if (it->planeRect.left < 0) { - it->planeOffsetX = -it->planeRect.left; - it->planeRect.left = 0; - } else { - it->planeOffsetX = 0; - } +void GfxFrameout::kernelDeleteScreenItem(const reg_t object) { + _segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewInserted); - if (it->planeRect.top < 0) { - it->planeOffsetY = -it->planeRect.top; - it->planeRect.top = 0; - } else { - it->planeOffsetY = 0; - } + const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane)); + Plane *plane = _planes.findByObject(planeObject); + if (plane == nullptr) { + // TODO: Remove +// warning("Invalid plane selector %04x:%04x passed to kDeleteScreenItem (real engine ignores this)", PRINT_REG(object)); + return; + } - // We get bad plane-bottom in sq6 - if (it->planeRect.right > _screen->getWidth()) - it->planeRect.right = _screen->getWidth(); - if (it->planeRect.bottom > _screen->getHeight()) - it->planeRect.bottom = _screen->getHeight(); +// TODO: Remove +// debug("Deleting screen item %04x:%04x from plane %04x:%04x", PRINT_REG(object), PRINT_REG(planeObject)); - it->planeClipRect = Common::Rect(it->planeRect.width(), it->planeRect.height()); - it->upscaledPlaneRect = it->planeRect; - it->upscaledPlaneClipRect = it->planeClipRect; - if (_screen->getUpscaledHires()) { - _screen->adjustToUpscaledCoordinates(it->upscaledPlaneRect.top, it->upscaledPlaneRect.left); - _screen->adjustToUpscaledCoordinates(it->upscaledPlaneRect.bottom, it->upscaledPlaneRect.right); - _screen->adjustToUpscaledCoordinates(it->upscaledPlaneClipRect.top, it->upscaledPlaneClipRect.left); - _screen->adjustToUpscaledCoordinates(it->upscaledPlaneClipRect.bottom, it->upscaledPlaneClipRect.right); - } + ScreenItem *screenItem = plane->_screenItemList.findByObject(object); + if (screenItem == nullptr) { +// TODO: Remove +// warning("Invalid screen item %04x:%04x passed to kDeleteScreenItem (real engine ignores this)", PRINT_REG(object)); + return; + } - it->planePictureMirrored = readSelectorValue(_segMan, object, SELECTOR(mirrored)); - it->planeBack = readSelectorValue(_segMan, object, SELECTOR(back)); + if (screenItem->_created == 0) { + screenItem->_created = 0; + screenItem->_updated = 0; + screenItem->_deleted = getScreenCount(); + } else { + plane->_screenItemList.erase(screenItem); + plane->_screenItemList.pack(); + } +} - sortPlanes(); +#pragma mark - +#pragma mark Planes - // Update the items in the plane - for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) { - reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane)); - if (object == itemPlane) { - kernelUpdateScreenItem((*listIterator)->object); - } - } +void GfxFrameout::kernelAddPlane(const reg_t object) { + Plane *plane = _planes.findByObject(object); + if (plane != nullptr) { + plane->update(object); + updatePlane(*plane); + } else { + plane = new Plane(object); + addPlane(*plane); + } +} - return; - } +void GfxFrameout::kernelUpdatePlane(const reg_t object) { + Plane *plane = _planes.findByObject(object); + if (plane == nullptr) { + error("Invalid plane selector passed to kUpdatePlane"); } - error("kUpdatePlane called on plane that wasn't added before"); + + plane->update(object); + updatePlane(*plane); } -void GfxFrameout::kernelDeletePlane(reg_t object) { - deletePlaneItems(object); - deletePlanePictures(object); +void GfxFrameout::kernelDeletePlane(const reg_t object) { + Plane *plane = _planes.findByObject(object); + if (plane == nullptr) { + error("Invalid plane selector passed to kDeletePlane"); + } - for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) { - if (it->object == object) { - _planes.erase(it); - Common::Rect planeRect; - planeRect.top = readSelectorValue(_segMan, object, SELECTOR(top)); - planeRect.left = readSelectorValue(_segMan, object, SELECTOR(left)); - planeRect.bottom = readSelectorValue(_segMan, object, SELECTOR(bottom)); - planeRect.right = readSelectorValue(_segMan, object, SELECTOR(right)); + if (plane->_created) { + // NOTE: The original engine calls some `AbortPlane` function that + // just ends up doing this anyway so we skip the extra indirection + _planes.erase(plane); + } else { + // TODO: Remove +// debug("Deleting plane %04x:%04x", PRINT_REG(object)); + plane->_created = 0; + plane->_deleted = g_sci->_gfxFrameout->getScreenCount(); + } +} - _coordAdjuster->fromScriptToDisplay(planeRect.top, planeRect.left); - _coordAdjuster->fromScriptToDisplay(planeRect.bottom, planeRect.right); +int16 GfxFrameout::kernelGetHighPlanePri() { + return _planes.getTopSciPlanePriority(); +} - // Blackout removed plane rect - _paint32->fillRect(planeRect, 0); - return; +void GfxFrameout::addPlane(Plane &plane) { + if (_planes.findByObject(plane._object) == nullptr) { + plane.clipScreenRect(_screenRect); + _planes.add(&plane); + } else { + plane._deleted = 0; + if (plane._created == 0) { + plane._moved = g_sci->_gfxFrameout->getScreenCount(); } + _planes.sort(); } } -void GfxFrameout::addPlanePicture(reg_t object, GuiResourceId pictureId, uint16 startX, uint16 startY) { - if (pictureId == kPlanePlainColored || pictureId == kPlaneTranslucent) // sanity check - return; +void GfxFrameout::updatePlane(Plane &plane) { + // NOTE: This assertion comes from SCI engine code. + assert(_planes.findByObject(plane._object) == &plane); - PlanePictureEntry newPicture; - newPicture.object = object; - newPicture.pictureId = pictureId; - newPicture.picture = new GfxPicture(_resMan, _coordAdjuster, 0, _screen, _palette, pictureId, false); - newPicture.startX = startX; - newPicture.startY = startY; - newPicture.pictureCels = 0; - _planePictures.push_back(newPicture); + Plane *visiblePlane = _visiblePlanes.findByObject(plane._object); + plane.sync(visiblePlane, _screenRect); + // NOTE: updateScreenRect was originally called a second time here, + // but it is already called at the end of the Plane::Update call + // in the original engine anyway. + + _planes.sort(); } -void GfxFrameout::deletePlanePictures(reg_t object) { - PlanePictureList::iterator it = _planePictures.begin(); +#pragma mark - +#pragma mark Pics - while (it != _planePictures.end()) { - if (it->object == object || object.isNull()) { - delete it->pictureCels; - delete it->picture; - it = _planePictures.erase(it); - } else { - ++it; - } +void GfxFrameout::kernelAddPicAt(const reg_t planeObject, const GuiResourceId pictureId, const int16 x, const int16 y, const bool mirrorX) { + Plane *plane = _planes.findByObject(planeObject); + if (plane == nullptr) { + error("Invalid plane selector passed to kAddPicAt"); } + plane->addPic(pictureId, Common::Point(x, y), mirrorX); } -// Provides the same functionality as kGraph(DrawLine) -reg_t GfxFrameout::addPlaneLine(reg_t object, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control) { - for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) { - if (it->object == object) { - PlaneLineEntry line; - line.hunkId = _segMan->allocateHunkEntry("PlaneLine()", 1); // we basically use this for a unique ID - line.startPoint = startPoint; - line.endPoint = endPoint; - line.color = color; - line.priority = priority; - line.control = control; - it->lines.push_back(line); - return line.hunkId; +#pragma mark - +#pragma mark Rendering + +void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &rect) { +// TODO: Robot +// if (_robot != nullptr) { +// _robot.doRobot(); +// } + + // NOTE: The original engine allocated these as static arrays of 100 + // pointers to ScreenItemList / RectList + ScreenItemListList screenItemLists; + EraseListList eraseLists; + + screenItemLists.resize(_planes.size()); + eraseLists.resize(_planes.size()); + + // _numActiveRemaps was a global in SCI engine + if (/* TODO Remap::_numActiveRemaps > 0 */ false && _remapOccurred) { + // remapMarkRedraw(); + } + + calcLists(screenItemLists, eraseLists, rect); + + for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { + list->sort(); + } + + for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { + for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) { + (*drawItem)->screenItem->getCelObj().submitPalette(); } } - return NULL_REG; + _remapOccurred = _palette->updateForFrame(); + + // NOTE: SCI engine set this to false on each loop through the + // planelist iterator below. Since that is a waste, we only set + // it once. + _frameNowVisible = false; + + for (PlaneList::size_type i = 0; i < _planes.size(); ++i) { + drawEraseList(eraseLists[i], *_planes[i]); + drawScreenItemList(screenItemLists[i]); + } + +// TODO: Robot +// if (_robot != nullptr) { +// _robot->frameAlmostVisible(); +// } + + _palette->updateHardware(); + + if (shouldShowBits) { + showBits(); + } + + _frameNowVisible = true; + +// TODO: Robot +// if (_robot != nullptr) { +// robot->frameNowVisible(); +// } } -void GfxFrameout::updatePlaneLine(reg_t object, reg_t hunkId, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control) { - // Check if we're asked to update a line that was never added - if (hunkId.isNull()) - return; +int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]) { + if (!r.intersects(other)) { + return -1; + } + + int count = 0; + if (r.top < other.top) { + Common::Rect &t = outRects[count++]; + t = r; + t.bottom = other.top; + r.top = other.top; + } + + if (r.bottom > other.bottom) { + Common::Rect &t = outRects[count++]; + t = r; + t.top = other.bottom; + r.bottom = other.bottom; + } + + if (r.left < other.left) { + Common::Rect &t = outRects[count++]; + t = r; + t.right = other.left; + r.left = other.left; + } + + if (r.right > other.right) { + Common::Rect &t = outRects[count++]; + t = r; + t.left = other.right; + } + + return count; +} + +void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect) { + RectList rectlist; + Common::Rect outRects[4]; + + int deletedPlaneCount = 0; + bool addedToRectList = false; + int planeCount = _planes.size(); + bool foundTransparentPlane = false; + + if (!calcRect.isEmpty()) { + addedToRectList = true; + rectlist.add(calcRect); + } + + for (int outerPlaneIndex = 0; outerPlaneIndex < planeCount; ++outerPlaneIndex) { + Plane *outerPlane = _planes[outerPlaneIndex]; + + if (outerPlane->_type == kPlaneTypeTransparent) { + foundTransparentPlane = true; + } - for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) { - if (it->object == object) { - for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) { - if (it2->hunkId == hunkId) { - it2->startPoint = startPoint; - it2->endPoint = endPoint; - it2->color = color; - it2->priority = priority; - it2->control = control; - return; + Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object); + + if (outerPlane->_deleted) { + if (visiblePlane != nullptr) { + if (!visiblePlane->_screenRect.isEmpty()) { + addedToRectList = true; + rectlist.add(visiblePlane->_screenRect); + } + } + ++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 { + for (int i = 0; i < splitcount; ++i) { + rectlist.add(outRects[i]); + } + } + + addedToRectList = 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; + } } } } - } -} -void GfxFrameout::deletePlaneLine(reg_t object, reg_t hunkId) { - // Check if we're asked to delete a line that was never added (happens during the intro of LSL6) - if (hunkId.isNull()) - return; + 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)); + } + + int splitCount = splitRects(**rect, innerPlane->_screenRect, outRects); + for (int i = 0; i < splitCount; ++i) { + rectlist.add(outRects[i]); + } - for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); ++it) { - if (it->object == object) { - for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) { - if (it2->hunkId == hunkId) { - _segMan->freeHunkEntry(hunkId); - it2 = it->lines.erase(it2); - return; + rectlist.erase(rect); + break; + } } } + + rectlist.pack(); } } -} -// Adapted from GfxAnimate::applyGlobalScaling() -void GfxFrameout::applyGlobalScaling(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 celHeight) { - // Global scaling uses global var 2 and some other stuff to calculate scaleX/scaleY - int16 maxScale = readSelectorValue(_segMan, itemEntry->object, SELECTOR(maxScale)); - int16 maxCelHeight = (maxScale * celHeight) >> 7; - reg_t globalVar2 = g_sci->getEngineState()->variables[VAR_GLOBAL][2]; // current room object - int16 vanishingY = readSelectorValue(_segMan, globalVar2, SELECTOR(vanishingY)); + // clean up deleted planes + if (deletedPlaneCount) { + for (int planeIndex = planeCount - 1; planeIndex >= 0; --planeIndex) { + Plane *plane = _planes[planeIndex]; + + if (plane->_deleted) { + --plane->_deleted; + if (plane->_deleted <= 0) { + PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject(plane->_object)); + if (visiblePlaneIt != _visiblePlanes.end()) { + _visiblePlanes.erase(visiblePlaneIt); + } - int16 fixedPortY = planeRect.bottom - vanishingY; - int16 fixedEntryY = itemEntry->y - vanishingY; - if (!fixedEntryY) - fixedEntryY = 1; + _planes.remove_at(planeIndex); + eraseLists.remove_at(planeIndex); + drawLists.remove_at(planeIndex); + } - if ((celHeight == 0) || (fixedPortY == 0)) - error("global scaling panic"); + if (--deletedPlaneCount <= 0) { + break; + } + } + } + } - itemEntry->scaleY = (maxCelHeight * fixedEntryY) / fixedPortY; - itemEntry->scaleY = (itemEntry->scaleY * maxScale) / celHeight; + 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); + + rectlist.add(outerPlane->_screenRect.findIntersectingRect(visibleOuterPlane->_screenRect)); + + for (int innerIndex = 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); + + 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]); + } else { + eraseLists[outerIndex].add(*rectlist[rectIndex]); + } + } + } + + rectlist.erase_at(rectIndex); + } else if (splitCount != -1) { + for (int i = 0; i < splitCount; ++i) { + rectlist.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]); + } + } + } + rectlist.erase_at(rectIndex); + } + } + rectlist.pack(); + } + } + } - // Make sure that the calculated value is sane - if (itemEntry->scaleY < 1 /*|| itemEntry->scaleY > 128*/) - itemEntry->scaleY = 128; + for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) { + Plane *plane = _planes[planeIndex]; + Plane *visiblePlane = nullptr; - itemEntry->scaleX = itemEntry->scaleY; + PlaneList::iterator visiblePlaneIt = Common::find_if(_visiblePlanes.begin(), _visiblePlanes.end(), FindByObject(plane->_object)); + if (visiblePlaneIt != _visiblePlanes.end()) { + visiblePlane = *visiblePlaneIt; + } - // and set objects scale selectors - //writeSelectorValue(_segMan, itemEntry->object, SELECTOR(scaleX), itemEntry->scaleX); - //writeSelectorValue(_segMan, itemEntry->object, SELECTOR(scaleY), itemEntry->scaleY); -} + 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)); + } -void GfxFrameout::kernelAddScreenItem(reg_t object) { - // Ignore invalid items - if (!_segMan->isObject(object)) { - warning("kernelAddScreenItem: Attempt to add an invalid object (%04x:%04x)", PRINT_REG(object)); - return; + plane->calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]); + } + + if (plane->_created) { + _visiblePlanes.add(new Plane(*plane)); + --plane->_created; + } else if (plane->_moved) { + assert(visiblePlaneIt != _visiblePlanes.end()); + **visiblePlaneIt = *plane; + --plane->_moved; + } } - FrameoutEntry *itemEntry = new FrameoutEntry(); - memset(itemEntry, 0, sizeof(FrameoutEntry)); - itemEntry->object = object; - itemEntry->givenOrderNr = _screenItems.size(); - itemEntry->visible = true; - _screenItems.push_back(itemEntry); + if (foundTransparentPlane) { + for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) { + for (int 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) { + _planes[i]->filterDownEraseRects(drawLists[i], eraseLists[i], eraseLists[planeIndex]); + } + + if (eraseLists[planeIndex].size() > 0) { + error("Transparent plane's erase list not absorbed"); + } + } - kernelUpdateScreenItem(object); + for (int i = planeIndex + 1; i < planeCount; ++i) { + if (_planes[i]->_type == kPlaneTypeTransparent) { + _planes[i]->filterUpDrawRects(drawLists[i], drawLists[planeIndex]); + } + } + } + } } -void GfxFrameout::kernelUpdateScreenItem(reg_t object) { - // Ignore invalid items - if (!_segMan->isObject(object)) { - warning("kernelUpdateScreenItem: Attempt to update an invalid object (%04x:%04x)", PRINT_REG(object)); +void GfxFrameout::drawEraseList(const RectList &eraseList, const Plane &plane) { + if (plane._type != kPlaneTypeColored) { return; } - FrameoutEntry *itemEntry = findScreenItem(object); - if (!itemEntry) { - warning("kernelUpdateScreenItem: invalid object %04x:%04x", PRINT_REG(object)); - return; + for (RectList::const_iterator it = eraseList.begin(); it != eraseList.end(); ++it) { + mergeToShowList(**it, _showList, _overdrawThreshold); + _currentBuffer.fillRect(**it, plane._back); + } +} + +void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) { + _hasRemappedScreenItem = false; + if (/* TODO: g_Remap_UnknownCounter2 */ false && !_priorityMap.isNull()) { + for (DrawList::const_iterator it = screenItemList.begin(); it != screenItemList.end(); ++it) { + if ((*it)->screenItem->getCelObj()._remap) { + _hasRemappedScreenItem = true; + break; + } + } } - itemEntry->viewId = readSelectorValue(_segMan, object, SELECTOR(view)); - itemEntry->loopNo = readSelectorValue(_segMan, object, SELECTOR(loop)); - itemEntry->celNo = readSelectorValue(_segMan, object, SELECTOR(cel)); - itemEntry->x = readSelectorValue(_segMan, object, SELECTOR(x)); - itemEntry->y = readSelectorValue(_segMan, object, SELECTOR(y)); - itemEntry->z = readSelectorValue(_segMan, object, SELECTOR(z)); - itemEntry->priority = readSelectorValue(_segMan, object, SELECTOR(priority)); - if (readSelectorValue(_segMan, object, SELECTOR(fixPriority)) == 0) - itemEntry->priority = itemEntry->y; + for (DrawList::const_iterator it = screenItemList.begin(); it != screenItemList.end(); ++it) { + DrawItem &drawItem = **it; + mergeToShowList(drawItem.rect, _showList, _overdrawThreshold); + ScreenItem &screenItem = *drawItem.screenItem; + // TODO: Remove +// debug("Drawing item %04x:%04x to %d %d %d %d", PRINT_REG(screenItem._object), drawItem.rect.left, drawItem.rect.top, drawItem.rect.right, drawItem.rect.bottom); + CelObj &celObj = *screenItem._celObj; + celObj.draw(_currentBuffer, screenItem, drawItem.rect, screenItem._mirrorX ^ celObj._mirrorX); + } +} - itemEntry->signal = readSelectorValue(_segMan, object, SELECTOR(signal)); - itemEntry->scaleSignal = readSelectorValue(_segMan, object, SELECTOR(scaleSignal)); +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; + } + } - if (itemEntry->scaleSignal & kScaleSignalDoScaling32) { - itemEntry->scaleX = readSelectorValue(_segMan, object, SELECTOR(scaleX)); - itemEntry->scaleY = readSelectorValue(_segMan, object, SELECTOR(scaleY)); - } else { - itemEntry->scaleX = 128; - itemEntry->scaleY = 128; + count = showList.pack(); } - itemEntry->visible = true; - // Check if the entry can be hidden - if (lookupSelector(_segMan, object, SELECTOR(visible), NULL, NULL) != kSelectorNone) - itemEntry->visible = readSelectorValue(_segMan, object, SELECTOR(visible)); + showList.add(merged); } -void GfxFrameout::kernelDeleteScreenItem(reg_t object) { - FrameoutEntry *itemEntry = findScreenItem(object); - // If the item could not be found, it may already have been deleted - if (!itemEntry) - return; +void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle) { + Palette sourcePalette(*_palette->getNextPalette()); + alterVmap(sourcePalette, sourcePalette, -1, styleRanges); - _screenItems.remove(itemEntry); - delete itemEntry; -} + // TODO: unsure if this is what this variable actually + // represents, but it is the correct variable number + int16 lastRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16(); -void GfxFrameout::deletePlaneItems(reg_t planeObject) { - FrameoutList::iterator listIterator = _screenItems.begin(); + Common::Rect rect(_screen->getDisplayWidth(), _screen->getDisplayHeight()); + _showList.add(rect); + showBits(); - while (listIterator != _screenItems.end()) { - bool objectMatches = false; - if (!planeObject.isNull()) { - reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane)); - objectMatches = (planeObject == itemPlane); - } else { - objectMatches = true; + Common::Rect calcRect(0, 0); + + // NOTE: The original engine allocated these as static arrays of 100 + // pointers to ScreenItemList / RectList + ScreenItemListList screenItemLists; + EraseListList eraseLists; + + screenItemLists.resize(_planes.size()); + eraseLists.resize(_planes.size()); + + // TODO: Remap + // _numActiveRemaps was a global in SCI engine + // if (Remap::_numActiveRemaps > 0 && _remapOccurred) { + // _screen->remapMarkRedraw(); + // } + + calcLists(screenItemLists, eraseLists, calcRect); + for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { + list->sort(); + } + + for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { + for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) { + (*drawItem)->screenItem->getCelObj().submitPalette(); } + } - if (objectMatches) { - FrameoutEntry *itemEntry = *listIterator; - listIterator = _screenItems.erase(listIterator); - delete itemEntry; - } else { - ++listIterator; + _remapOccurred = _palette->updateForFrame(); + _frameNowVisible = false; + + for (PlaneList::size_type i = 0; i < _planes.size(); ++i) { + drawEraseList(eraseLists[i], *_planes[i]); + drawScreenItemList(screenItemLists[i]); + } + + Palette nextPalette(*_palette->getNextPalette()); + + if (lastRoom < 1000) { + for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) { + if (styleRanges[i] == -1 || styleRanges[i] == 0) { + sourcePalette.colors[i] = nextPalette.colors[i]; + sourcePalette.colors[i].used = true; + } + } + } else { + for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) { + if (styleRanges[i] == -1 || (styleRanges[i] == 0 && i > 71 && i < 104)) { + sourcePalette.colors[i] = nextPalette.colors[i]; + sourcePalette.colors[i].used = true; + } } } -} -FrameoutEntry *GfxFrameout::findScreenItem(reg_t object) { - for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) { - FrameoutEntry *itemEntry = *listIterator; - if (itemEntry->object == object) - return itemEntry; + _palette->submit(sourcePalette); + _palette->updateFFrame(); + _palette->updateHardware(); + alterVmap(nextPalette, sourcePalette, 1, _styleRanges); + + if (showStyle->type > 0 && showStyle->type < 15) { +// TODO: SCI2.1mid transition effects +// processEffects(); + warning("Transition not implemented!"); + } else { + showBits(); } - return NULL; -} + _frameNowVisible = true; -int16 GfxFrameout::kernelGetHighPlanePri() { - sortPlanes(); - return readSelectorValue(g_sci->getEngineState()->_segMan, _planes.back().object, SELECTOR(priority)); -} + for (PlaneList::iterator plane = _planes.begin(); plane != _planes.end(); ++plane) { +// TODO: +// plane->updateRedrawAllCount(); + } -void GfxFrameout::kernelAddPicAt(reg_t planeObj, GuiResourceId pictureId, int16 pictureX, int16 pictureY) { - addPlanePicture(planeObj, pictureId, pictureX, pictureY); -} + // TODO: Remap + // _numActiveRemaps was a global in SCI engine + // if (Remap::_numActiveRemaps > 0 && _remapOccurred) { + // _screen->remapMarkRedraw(); + // } -bool sortHelper(const FrameoutEntry* entry1, const FrameoutEntry* entry2) { - if (entry1->priority == entry2->priority) { - if (entry1->y == entry2->y) - return (entry1->givenOrderNr < entry2->givenOrderNr); - return (entry1->y < entry2->y); + calcLists(screenItemLists, eraseLists, calcRect); + for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { + list->sort(); } - return (entry1->priority < entry2->priority); -} -bool planeSortHelper(const PlaneEntry &entry1, const PlaneEntry &entry2) { - if (entry1.priority < 0) - return true; + for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) { + for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) { + (*drawItem)->screenItem->getCelObj().submitPalette(); + } + } + + _remapOccurred = _palette->updateForFrame(); + // NOTE: During this second loop, `_frameNowVisible = false` is + // inside the next loop in SCI2.1mid + _frameNowVisible = false; + + for (PlaneList::size_type i = 0; i < _planes.size(); ++i) { + drawEraseList(eraseLists[i], *_planes[i]); + drawScreenItemList(screenItemLists[i]); + } - if (entry2.priority < 0) - return false; + _palette->submit(nextPalette); + _palette->updateFFrame(); + _palette->updateHardware(); + showBits(); - return entry1.priority < entry2.priority; + _frameNowVisible = true; } -void GfxFrameout::sortPlanes() { - // First, remove any invalid planes - for (PlaneList::iterator it = _planes.begin(); it != _planes.end();) { - if (!_segMan->isObject(it->object)) - it = _planes.erase(it); - else - it++; +// 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); + // NOTE: SCI engine used BR-inclusive rects so used slightly + // different masking here to ensure that the width of rects + // was always even. + rounded.left &= ~1; + rounded.right = (rounded.right + 1) & ~1; + + // TODO: + // _cursor->GonnaPaint(rounded); } - // Sort the rest of them - Common::sort(_planes.begin(), _planes.end(), planeSortHelper); + // TODO: + // _cursor->PaintStarting(); + + for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) { + Common::Rect rounded(**rect); + // NOTE: SCI engine used BR-inclusive rects so used slightly + // different masking here to ensure that the width of rects + // was always even. + rounded.left &= ~1; + rounded.right = (rounded.right + 1) & ~1; + + byte *sourceBuffer = (byte *)_currentBuffer.getPixels() + rounded.top * _currentBuffer.screenWidth + rounded.left; + + g_system->copyRectToScreen(sourceBuffer, _currentBuffer.screenWidth, rounded.left, rounded.top, rounded.width(), rounded.height()); + } + + // TODO: + // _cursor->DonePainting(); + + _showList.clear(); } -void GfxFrameout::showVideo() { - bool skipVideo = false; - RobotDecoder *videoDecoder = g_sci->_robotDecoder; - uint16 x = videoDecoder->getPos().x; - uint16 y = videoDecoder->getPos().y; - uint16 screenWidth = _screen->getWidth(); - uint16 screenHeight = _screen->getHeight(); - uint16 outputWidth; - uint16 outputHeight; +void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, const int8 style, const int8 *const styleRanges) { + uint8 clut[256]; + + for (int paletteIndex = 0; paletteIndex < ARRAYSIZE(palette1.colors); ++paletteIndex) { + int outerR = palette1.colors[paletteIndex].r; + int outerG = palette1.colors[paletteIndex].g; + int outerB = palette1.colors[paletteIndex].b; + + if (styleRanges[paletteIndex] == style) { + int minDiff = 262140; + int minDiffIndex; + + for (int i = 0; i < 236; ++i) { + if (styleRanges[i] != style) { + int r = palette1.colors[i].r; + int g = palette1.colors[i].g; + int b = palette1.colors[i].b; + int diffSquared = (outerR - r) * (outerR - r) + (outerG - g) * (outerG - g) + (outerB - b) * (outerB - b); + if (diffSquared < minDiff) { + minDiff = diffSquared; + minDiffIndex = i; + } + } + } - if (videoDecoder->hasDirtyPalette()) - g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256); + clut[paletteIndex] = minDiffIndex; + } - while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) { - if (videoDecoder->needsUpdate()) { - const Graphics::Surface *frame = videoDecoder->decodeNextFrame(); - if (frame) { - // We need to clip here - // At least Phantasmagoria shows a 640x390 video on a 630x450 screen during the intro - outputWidth = frame->w > screenWidth ? screenWidth : frame->w; - outputHeight = frame->h > screenHeight ? screenHeight : frame->h; - g_system->copyRectToScreen(frame->getPixels(), frame->pitch, x, y, outputWidth, outputHeight); + if (style == 1 && styleRanges[paletteIndex] == 0) { + int minDiff = 262140; + int minDiffIndex; - if (videoDecoder->hasDirtyPalette()) - g_system->getPaletteManager()->setPalette(videoDecoder->getPalette(), 0, 256); + for (int i = 0; i < 236; ++i) { + int r = palette2.colors[i].r; + int g = palette2.colors[i].g; + int b = palette2.colors[i].b; - g_system->updateScreen(); + int diffSquared = (outerR - r) * (outerR - r) + (outerG - g) * (outerG - g) + (outerB - b) * (outerB - b); + if (diffSquared < minDiff) { + minDiff = diffSquared; + minDiffIndex = i; + } } + + clut[paletteIndex] = minDiffIndex; } + } - Common::Event event; - while (g_system->getEventManager()->pollEvent(event)) { - if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE) || event.type == Common::EVENT_LBUTTONUP) - skipVideo = true; + // NOTE: This is currBuffer->ptr in SCI engine + byte *pixels = (byte *)_currentBuffer.getPixels(); + + // TODO: Guessing that display width/height is the correct + // equivalent to screen width/height in SCI engine + for (int pixelIndex = 0, numPixels = _currentBuffer.screenWidth * _currentBuffer.screenHeight; pixelIndex < numPixels; ++pixelIndex) { + byte currentValue = pixels[pixelIndex]; + int8 styleRangeValue = styleRanges[currentValue]; + if (styleRangeValue == -1 && styleRangeValue == style) { + currentValue = pixels[pixelIndex] = clut[currentValue]; + styleRangeValue = styleRanges[clut[currentValue]]; } - g_system->delayMillis(10); + if ( + (styleRangeValue == 1 && styleRangeValue == style) || + (styleRangeValue == 0 && style == 1) + ) { + pixels[pixelIndex] = clut[currentValue]; + } } } -void GfxFrameout::createPlaneItemList(reg_t planeObject, FrameoutList &itemList) { - // Copy screen items of the current frame to the list of items to be drawn - for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) { - reg_t itemPlane = readSelector(_segMan, (*listIterator)->object, SELECTOR(plane)); - if (planeObject == itemPlane) { - kernelUpdateScreenItem((*listIterator)->object); // TODO: Why is this necessary? - itemList.push_back(*listIterator); +void GfxFrameout::kernelSetPalStyleRange(const uint8 fromColor, const uint8 toColor) { + if (toColor > fromColor) { + return; + } + + for (int i = fromColor; i < toColor; ++i) { + _styleRanges[i] = 0; + } +} + +inline ShowStyleEntry * GfxFrameout::findShowStyleForPlane(const reg_t planeObj) const { + ShowStyleEntry *entry = _showStyles; + while (entry != nullptr) { + if (entry->plane == planeObj) { + break; } + entry = entry->next; } - for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) { - if (pictureIt->object == planeObject) { - GfxPicture *planePicture = pictureIt->picture; - // Allocate memory for picture cels - pictureIt->pictureCels = new FrameoutEntry[planePicture->getSci32celCount()]; + return entry; +} + +inline ShowStyleEntry *GfxFrameout::deleteShowStyleInternal(ShowStyleEntry *const showStyle) { + ShowStyleEntry *lastEntry = nullptr; - // Add following cels to the itemlist - FrameoutEntry *picEntry = pictureIt->pictureCels; - int planePictureCels = planePicture->getSci32celCount(); - for (int pictureCelNr = 0; pictureCelNr < planePictureCels; pictureCelNr++) { - picEntry->celNo = pictureCelNr; - picEntry->object = NULL_REG; - picEntry->picture = planePicture; - picEntry->y = planePicture->getSci32celY(pictureCelNr); - picEntry->x = planePicture->getSci32celX(pictureCelNr); - picEntry->picStartX = pictureIt->startX; - picEntry->picStartY = pictureIt->startY; - picEntry->visible = true; + for (ShowStyleEntry *testEntry = _showStyles; testEntry != nullptr; testEntry = testEntry->next) { + if (testEntry == showStyle) { + break; + } + lastEntry = testEntry; + } - picEntry->priority = planePicture->getSci32celPriority(pictureCelNr); + if (lastEntry == nullptr) { + _showStyles = showStyle->next; + lastEntry = _showStyles; + } else { + lastEntry->next = showStyle->next; + } - itemList.push_back(picEntry); - picEntry++; + // NOTE: Differences from SCI2/2.1early engine: + // 1. Memory of ShowStyle-owned objects was freed before ShowStyle was + // removed from the linked list, but since this operation is position + // independent, it has been moved after removal from the list for + // consistency with SCI2.1mid+ + // 2. In SCI2, `screenItems` was a pointer to an array of pointers, so + // extra deletes were performed here; we use an owned container object + // instead, which is automatically freed when ShowStyle is freed +#if 0 + if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) { + uint8 type = showStyle->type; + + if (type >= 1 && type <= 10) { + ScreenItemList &styleItems = showStyle->screenItems; + for (ScreenItemList::iterator it = styleItems.begin(); it != styleItems.end(); ++it) { + if (active) { + // TODO: _screen->deleteScreenItem(showStyle->plane, *it->id); + _screenItems.remove(*it); + } + delete *it; + } + } else if (type == 11 || type == 12) { + if (!showStyle->bitmapMemId.isNull()) { + _segMan->freeHunkEntry(showStyle->bitmapMemId); + } + if (showStyle->bitmapScreenItem != nullptr) { + // TODO: _screen->deleteScreenItem(showStyle->plane, showStyle->bitmapScreenItem->id); + _screenItems.remove(showStyle->bitmapScreenItem); + delete showStyle->bitmapScreenItem; } } + } else { +#endif + delete[] showStyle->fadeColorRanges; +#if 0 } +#endif + + delete showStyle; - // Now sort our itemlist - Common::sort(itemList.begin(), itemList.end(), sortHelper); + // TODO: Verify that this is the correct entry to return + // for the loop in processShowStyles to work correctly + return lastEntry; } -bool GfxFrameout::isPictureOutOfView(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 planeOffsetX, int16 planeOffsetY) { - // Out of view horizontally (sanity checks) - int16 pictureCelStartX = itemEntry->picStartX + itemEntry->x; - int16 pictureCelEndX = pictureCelStartX + itemEntry->picture->getSci32celWidth(itemEntry->celNo); - int16 planeStartX = planeOffsetX; - int16 planeEndX = planeStartX + planeRect.width(); - if (pictureCelEndX < planeStartX) - return true; - if (pictureCelStartX > planeEndX) - return true; +// TODO: 10-argument version is only in SCI3; argc checks are currently wrong for this version +// and need to be fixed in future +// TODO: SQ6 does not use 'priority' (exists since SCI2) or 'blackScreen' (exists since SCI3); +// check to see if other versions use or if they are just always ignored +void GfxFrameout::kernelSetShowStyle(const uint16 argc, const reg_t &planeObj, const ShowStyleType type, const int16 seconds, const int16 back, const int16 priority, const int16 animate, const int16 frameOutNow, const reg_t &pFadeArray, const int16 divisions, const int16 blackScreen) { + + bool hasDivisions = false; + bool hasFadeArray = false; + if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) { + hasDivisions = argc > 7; + hasFadeArray = false; + } else if (getSciVersion() < SCI_VERSION_3) { + hasDivisions = argc > 8; + hasFadeArray = argc > 7; + } else { + hasDivisions = argc > 9; + hasFadeArray = argc > 8; + } - // Out of view vertically (sanity checks) - int16 pictureCelStartY = itemEntry->picStartY + itemEntry->y; - int16 pictureCelEndY = pictureCelStartY + itemEntry->picture->getSci32celHeight(itemEntry->celNo); - int16 planeStartY = planeOffsetY; - int16 planeEndY = planeStartY + planeRect.height(); - if (pictureCelEndY < planeStartY) - return true; - if (pictureCelStartY > planeEndY) - return true; + bool isFadeUp; + int16 color; + if (back != -1) { + isFadeUp = false; + color = back; + } else { + isFadeUp = true; + color = 0; + } - return false; -} + if ((getSciVersion() < SCI_VERSION_2_1_MIDDLE && type == 15) || type > 15) { + error("Illegal show style %d for plane %04x:%04x", type, PRINT_REG(planeObj)); + } + + Plane *plane = _planes.findByObject(planeObj); + if (plane == nullptr) { + error("Plane %04x:%04x is not present in active planes list", PRINT_REG(planeObj)); + } + + // TODO: This is Plane.gameRect in SCI engine, not planeRect. Engine uses + // Plane::ConvGameRectToPlaneRect to convert from gameRect to planeRect. + // Also this never gets used by SQ6 so it is not clear what it does yet + // Common::Rect gameRect = plane.planeRect; + + bool createNewEntry = true; + ShowStyleEntry *entry = findShowStyleForPlane(planeObj); + if (entry != nullptr) { + bool useExisting = true; + + if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) { + useExisting = plane->_planeRect.width() == entry->width && plane->_planeRect.height() == entry->height; + } + + if (useExisting) { + useExisting = entry->divisions == (hasDivisions ? divisions : _defaultDivisions[type]) && entry->unknownC == _defaultUnknownC[type]; + } -void GfxFrameout::drawPicture(FrameoutEntry *itemEntry, int16 planeOffsetX, int16 planeOffsetY, bool planePictureMirrored) { - int16 pictureOffsetX = planeOffsetX; - int16 pictureX = itemEntry->x; - if ((planeOffsetX) || (itemEntry->picStartX)) { - if (planeOffsetX <= itemEntry->picStartX) { - pictureX += itemEntry->picStartX - planeOffsetX; - pictureOffsetX = 0; + if (useExisting) { + createNewEntry = false; + isFadeUp = true; + entry->currentStep = 0; } else { - pictureOffsetX = planeOffsetX - itemEntry->picStartX; + isFadeUp = true; + color = entry->color; + deleteShowStyleInternal(entry/*, true*/); + entry = nullptr; } } - int16 pictureOffsetY = planeOffsetY; - int16 pictureY = itemEntry->y; - if ((planeOffsetY) || (itemEntry->picStartY)) { - if (planeOffsetY <= itemEntry->picStartY) { - pictureY += itemEntry->picStartY - planeOffsetY; - pictureOffsetY = 0; - } else { - pictureOffsetY = planeOffsetY - itemEntry->picStartY; + if (type > 0) { + if (createNewEntry) { + entry = new ShowStyleEntry; + // NOTE: SCI2.1 engine tests if allocation returned a null pointer + // but then only avoids setting currentStep if this is so. Since + // this is a nonsensical approach, we do not do that here + entry->currentStep = 0; + entry->unknownC = _defaultUnknownC[type]; + entry->processed = false; + entry->divisions = hasDivisions ? divisions : _defaultDivisions[type]; + entry->plane = planeObj; + +#if 0 + if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) { + entry->bitmapMemId = NULL_REG; + entry->screenItems.empty(); + entry->width = plane->_planeRect.width(); + entry->height = plane->_planeRect.height(); + } else { +#endif + entry->fadeColorRanges = nullptr; + if (hasFadeArray) { + // NOTE: SCI2.1mid engine does no check to verify that an array is + // successfully retrieved, and SegMan will cause a fatal error + // if we try to use a memory segment that is not an array + SciArray *table = _segMan->lookupArray(pFadeArray); + + uint32 rangeCount = table->getSize(); + entry->fadeColorRangesCount = rangeCount; + + // NOTE: SCI engine code always allocates memory even if the range + // table has no entries, but this does not really make sense, so + // we avoid the allocation call in this case + if (rangeCount > 0) { + entry->fadeColorRanges = new uint16[rangeCount]; + for (size_t i = 0; i < rangeCount; ++i) { + entry->fadeColorRanges[i] = table->getValue(i).toUint16(); + } + } + } else { + entry->fadeColorRangesCount = 0; + } +#if 0 + } +#endif + } + + // NOTE: The original engine had no nullptr check and would just crash + // if it got to here + if (entry == nullptr) { + error("Cannot edit non-existing ShowStyle entry"); + } + + entry->fadeUp = isFadeUp; + entry->color = color; + entry->nextTick = g_sci->getTickCount(); + entry->type = type; + entry->animate = animate; + entry->delay = (seconds * 60 + entry->divisions - 1) / entry->divisions; + + if (entry->delay == 0) { +#if 0 + if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE && entry->fadeColorRanges != nullptr) { +#endif + if (entry->fadeColorRanges != nullptr) { + delete[] entry->fadeColorRanges; + } + delete entry; + error("ShowStyle has no duration"); + } + + if (frameOutNow) { + Common::Rect frameOutRect(0, 0); + frameOut(false, frameOutRect); + } + + if (createNewEntry) { + // TODO: Implement SCI3, which may or may not actually have + // the same transitions as SCI2/SCI2.1early, but implemented + // differently +#if 0 + if (getSciVersion() < SCI_VERSION_2_1_MIDDLE) { + switch (entry->type) { + case kShowStyleHShutterIn: + case kShowStyleHShutterOut: + prepareShowStyleWipe(entry, priority, 2, true); + break; + + case kShowStyleVShutterIn: + case kShowStyleVShutterOut: + prepareShowStyleWipe(entry, priority, 2, false); + break; + + case kShowStyleHWipe1: + case kShowStyleHWipe2: + prepareShowStyleWipe(entry, priority, 1, true); + break; + + case kShowStyleVWipe1: + case kShowStyleVWipe2: + prepareShowStyleWipe(entry, priority, 1, false); + break; + + case kShowStyleIrisIn: + case kShowStyleIrisOut: + prepareShowStyleIris(entry, priority); + break; + + case kShowStyle11: + case kShowStyle12: + prepareShowStylePixels(entry, priority, plane->planeRect); + break; + + default: + break; + } + } +#endif + + entry->next = _showStyles; + _showStyles = entry; } } +} - itemEntry->picture->drawSci32Vga(itemEntry->celNo, pictureX, itemEntry->y, pictureOffsetX, pictureOffsetY, planePictureMirrored); - // warning("picture cel %d %d", itemEntry->celNo, itemEntry->priority); +#if 0 +void addFrameoutEntryInternal(ShowStyleEntry *const showStyle, const int16 priority, const CelInfo32 &celInfo, const Common::Rect &rect) { + ScreenItem *screenItem = new ScreenItem; + screenItem->plane = showStyle->plane; + screenItem->celInfo = celInfo; + screenItem->celRect = rect; + screenItem->isInList = true; + screenItem->priority = priority; + screenItem->visible = true; + showStyle->screenItems.push_back(screenItem); } -/* TODO: This is the proper implementation of GraphicsMgr::FrameOut transcribed from SQ6 SCI engine disassembly. -static DrawList* g_drawLists[100]; -static RectList* g_rectLists[100]; -void GfxFrameout::FrameOut(bool shouldShowBits, SOL_Rect *rect) { - if (robot) { - robot.doRobot(); +void GfxFrameout::prepareShowStyleWipe(ShowStyleEntry *const showStyle, const int16 priority, const int16 edgeCount, const bool horizontal) { + assert(edgeCount == 1 || edgeCount == 2); + + const int numScreenItems = showStyle->divisions * edgeCount; + const int extra = edgeCount > 1 ? 1 : 0; + + showStyle->edgeCount = edgeCount; + showStyle->screenItems.reserve(numScreenItems); + + CelInfo32 celInfo; + celInfo.bitmap = NULL_REG; + celInfo.type = kCelObjTypeView; + celInfo.color = showStyle->color; + + for (int i = 0; i < numScreenItems; ++i) { + Common::Rect rect; + + if (horizontal) { + rect.top = 0; + rect.bottom = showStyle->height - 1; + rect.left = (showStyle->width * i) / (showStyle->divisions * edgeCount); + rect.right = ((i + 1) * (showStyle->width + extra)) / (showStyle->divisions * edgeCount) - 1; + } else { + rect.left = 0; + rect.right = showStyle->width - 1; + rect.top = (showStyle->height * i) / (showStyle->divisions * edgeCount); + rect.bottom = ((i + 1) * (showStyle->height + extra)) / (showStyle->divisions * edgeCount) - 1; + } + + addFrameoutEntryInternal(showStyle, priority, celInfo, rect); + + if (edgeCount == 2) { + if (horizontal) { + int temp = rect.left; + rect.left = showStyle->width - rect.right - 1; + rect.right = showStyle->width - temp - 1; + } else { + int temp = rect.top; + rect.top = showStyle->height - rect.bottom - 1; + rect.bottom = showStyle->height - temp - 1; + } + + addFrameoutEntryInternal(showStyle, priority, celInfo, rect); + } } +} - auto planeCount = screen.planeList.planeCount; - if (planeCount > 0) { - for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) { - Plane plane = *screen.planeList[planeIndex]; +void GfxFrameout::prepareShowStyleIris(ShowStyleEntry *const showStyle, const int16 priority) { + const int edgeCount = 4; + const int numScreenItems = showStyle->divisions * edgeCount; + + showStyle->edgeCount = edgeCount; + showStyle->screenItems.reserve(numScreenItems); + + CelInfo32 celInfo; + celInfo.bitmap = NULL_REG; + celInfo.type = kCelObjTypeView; + celInfo.color = showStyle->color; + + for (int i = 0; i < numScreenItems; ++i) { + Common::Rect rect; + + rect.right = showStyle->width - ((showStyle->width * i) / (showStyle->divisions * 2)) - 1; + rect.left = (showStyle->width * i) / (showStyle->divisions * 2); + rect.top = (showStyle->height * i) / (showStyle->divisions * 2); + rect.bottom = ((i + 1) * (showStyle->height + 1)) / (showStyle->divisions * 2) - 1; + + addFrameoutEntryInternal(showStyle, priority, celInfo, rect); + + { + int temp = rect.top; + rect.top = showStyle->height - rect.bottom - 1; + rect.bottom = showStyle->height - temp - 1; + } + + addFrameoutEntryInternal(showStyle, priority, celInfo, rect); + + rect.top = ((i + 1) * (showStyle->height + 1)) / (showStyle->divisions * 2); + rect.right = ((i + 1) * (showStyle->width + 1)) / (showStyle->divisions * 2) - 1; + rect.bottom = ((i + 1) * (showStyle->height + 1)) / (showStyle->divisions * 2) - 1; - DrawList* drawList = new DrawList(); - g_drawLists[planeIndex] = drawList; - RectList* rectList = new RectList(); - g_rectLists[planeIndex] = rectList; + addFrameoutEntryInternal(showStyle, priority, celInfo, rect); + + { + int temp = rect.left; + rect.left = showStyle->width - rect.right - 1; + rect.right = showStyle->width - temp - 1; } + + addFrameoutEntryInternal(showStyle, priority, celInfo, rect); } - - if (g_Remap_numActiveRemaps > 0 && remapNeeded) { - screen.RemapMarkRedraw(); +} + +void GfxFrameout::prepareShowStylePixels(ShowStyleEntry *const showStyle, const int16 priority, const Common::Rect planeGameRect) { + const int bitmapSize = showStyle->width * showStyle->height; + + // TODO: Verify that memory type 0x200 (what GK1 engine uses) + // is Hunk type + reg_t bitmapMemId = _segMan->allocateHunkEntry("ShowStylePixels()", bitmapSize + sizeof(GfxBitmapHeader)); + showStyle->bitmapMemId = bitmapMemId; + + // TODO: SCI2 GK1 uses a Bitmap constructor function to + // do this work + byte *bitmap = _segMan->getHunkPointer(bitmapMemId); + GfxBitmapHeader *header = (GfxBitmapHeader *)bitmap; + byte *bitmapData = bitmap + sizeof(GfxBitmapHeader); + + // TODO: These are defaults from the Bitmap constructor in + // GK1, not specific values set by this function. + // TODO: This probably should not even be using a struct at + // all since this information is machine endian dependent + // and will be reversed for Mac versions or when running + // ScummVM on big-endian systems. GK1 used packed structs + // everywhere so this probably worked better there too. + header->field_18 = 36; + header->field_1c = 36; + memset(header, 0, sizeof(GfxBitmapHeader)); + + header->width = showStyle->width; + header->height = showStyle->height; + header->field_8 = 250; + header->size = bitmapSize; + + // TODO: Scaled dimensions in bitmap headers was not added + // until SCI2.1mid. It is not clear what the right thing to + // do here is. + if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) { + header->scaledWidth = _currentBuffer.scriptWidth; + header->scaledHeight = _currentBuffer.scriptHeight; } - - CalcLists(&g_drawLists, &g_rectLists, rect); - // SCI engine stores reference *after* CalcLists - planeCount = screen.planeList.planeCount; - if (planeCount > 0) { - for (int drawListIndex = 0; drawListIndex < planeCount; ++i) { - DrawList* drawList = g_drawLists[drawListIndex]; - drawList->Sort(); - } + Common::Rect copyRect; + // TODO: planeGameRect is supposedly in script coordinates, + // which are usually 320x200. If bitsSaveDisplayScreen is + // in native resolution then seemingly this function will + // not work properly since we will be not copy enough bits, + // or from the correct location. + copyRect.left = planeGameRect.left; + copyRect.top = planeGameRect.top; + copyRect.right = planeGameRect.left + showStyle->width; + copyRect.bottom = planeGameRect.top + showStyle->height; + _screen->bitsSaveDisplayScreen(copyRect, bitmapData); + + CelInfo32 celInfo; + celInfo.bitmap = bitmapMemId; + celInfo.type = kCelObjTypeMem; + + ScreenItem *screenItem = new ScreenItem; + + screenItem->position.x = 0; + screenItem->position.y = 0; + + showStyle->bitmapScreenItem = screenItem; + screenItem->priority = priority; + + // TODO: Have not seen/identified this particular flag yet in + // SCI2.1mid (SQ6) engine; maybe (1) a duplicate of `created`, + // or (2) does not exist any more, or (3) one of the other + // still-unidentified fields. Probably need to look at the + // GK1 source for its use in drawing algorithms to determine + // if/how this correlates to ScreenItem members in the + // SCI2.1mid engine. +// screenItem->isInList = true; + + Plane *plane = _planes.findByObject(showStyle.plane); + plane->_screenItemList.add(screenItem); +} +#endif + +// NOTE: Different version of SCI engine support different show styles +// SCI2 implements 0, 1/3/5/7/9, 2/4/6/8/10, 11, 12, 13, 14 +// SCI2.1 implements 0, 1/2/3/4/5/6/7/8/9/10/11/12/15, 13, 14 +// SCI3 implements 0, 1/3/5/7/9, 2/4/6/8/10, 11, 12/15, 13, 14 +// TODO: Sierra code needs to be replaced with code that uses the +// computed entry->delay property instead of just counting divisors, +// as the latter is machine-speed-dependent and leads to wrong +// transition speeds +void GfxFrameout::processShowStyles() { + uint32 now = g_sci->getTickCount(); + + bool continueProcessing; + + // TODO: Change to bool? Engine uses inc to set the value to true, + // but there does not seem to be any reason to actually count how + // many times it was set + int doFrameOut; + do { + continueProcessing = false; + doFrameOut = 0; + ShowStyleEntry *showStyle = _showStyles; + while (showStyle != nullptr) { + bool retval = false; + + if (!showStyle->animate) { + ++doFrameOut; + } + + if (showStyle->nextTick < now || !showStyle->animate) { + // TODO: Different versions of SCI use different processors! + // This is the SQ6/KQ7/SCI2.1mid table. + switch (showStyle->type) { + case kShowStyleNone: { + retval = processShowStyleNone(showStyle); + break; + } + case kShowStyleHShutterOut: + case kShowStyleVShutterOut: + case kShowStyleWipeLeft: + case kShowStyleWipeUp: + case kShowStyleIrisOut: +#if 0 + if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) { +#endif + retval = processShowStyleMorph(showStyle); +#if 0 + } else { + retval = processShowStyleWipe(-1, showStyle); + } +#endif + break; + case kShowStyleHShutterIn: + case kShowStyleVShutterIn: + case kShowStyleWipeRight: + case kShowStyleWipeDown: + case kShowStyleIrisIn: +#if 0 + if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) { +#endif + retval = processShowStyleMorph(showStyle); +#if 0 + } else { + retval = processShowStyleWipe(1, showStyle); + } +#endif + break; + case kShowStyle11: +#if 0 + if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) { +#endif + retval = processShowStyleMorph(showStyle); +#if 0 + } else { + retval = processShowStyle11(showStyle); + } +#endif + break; + case kShowStyle12: + case kShowStyleUnknown: { +#if 0 + if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) { +#endif + retval = processShowStyleMorph(showStyle); +#if 0 + } else { + retval = processShowStyle12(showStyle); + } +#endif + break; + } + case kShowStyleFadeOut: { + retval = processShowStyleFade(-1, showStyle); + break; + } + case kShowStyleFadeIn: { + retval = processShowStyleFade(1, showStyle); + break; + } + } + } - for (int drawListIndex = 0; drawListIndex < planeCount; ++i) { - DrawList* drawList = g_drawLists[drawListIndex]; - if (drawList == nullptr || drawList->count == 0) { - continue; + if (!retval) { + continueProcessing = true; } - for (int screenItemIndex = 0, screenItemCount = drawList->count; screenItemIndex < screenItemCount; ++screenItemIndex) { - ScreenItem* screenItem = drawList->items[screenItemIndex]; - screenItem->GetCelObj()->SubmitPalette(); + if (retval && showStyle->processed) { + showStyle = deleteShowStyleInternal(showStyle); + } else { + showStyle = showStyle->next; } } + + if (doFrameOut) { + Common::Rect frameOutRect(0, 0); + frameOut(true, frameOutRect); + + // TODO: It seems like transitions without the “animate” + // flag are too fast in in SCI2–2.1early, but the throttle + // value is arbitrary. Someone on real hardware probably + // needs to test what the actual speed of transitions + // should be + //state->speedThrottler(30); + //state->_throttleTrigger = true; + } + } while(continueProcessing && doFrameOut); +} + +bool GfxFrameout::processShowStyleNone(ShowStyleEntry *const showStyle) { + if (showStyle->fadeUp) { + _palette->setFade(100, 0, 255); + } else { + _palette->setFade(0, 0, 255); } - // UpdateForFrame is where all palette mutations occur (cycles, varies, etc.) - bool remapNeeded = GPalette().UpdateForFrame(); - if (planeCount > 0) { - frameNowVisible = false; + showStyle->processed = true; + return true; +} - for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) { - Plane* plane = screen.planeList[planeIndex]; +bool GfxFrameout::processShowStyleMorph(ShowStyleEntry *const showStyle) { + palMorphFrameOut(_styleRanges, showStyle); + showStyle->processed = true; + return true; +} - DrawEraseList(g_rectLists[planeIndex], plane); - DrawScreenItemsList(g_drawLists[planeIndex]); +// TODO: Normalise use of 'entry' vs 'showStyle' +bool GfxFrameout::processShowStyleFade(const int direction, ShowStyleEntry *const showStyle) { + bool unchanged = true; + if (showStyle->currentStep < showStyle->divisions) { + int percent; + if (direction <= 0) { + percent = showStyle->divisions - showStyle->currentStep - 1; + } else { + percent = showStyle->currentStep; } - } - if (robot) { - robot.FrameAlmostVisible(); - } + percent *= 100; + percent /= showStyle->divisions - 1; - GPalette().UpdateHardware(); + if (showStyle->fadeColorRangesCount > 0) { + for (int i = 0, len = showStyle->fadeColorRangesCount; i < len; i += 2) { + _palette->setFade(percent, showStyle->fadeColorRanges[i], showStyle->fadeColorRanges[i + 1]); + } + } else { + _palette->setFade(percent, 0, 255); + } - if (shouldShowBits) { - ShowBits(); + ++showStyle->currentStep; + showStyle->nextTick += showStyle->delay; + unchanged = false; } - frameNowVisible = true; + if (showStyle->currentStep >= showStyle->divisions && unchanged) { + if (direction > 0) { + showStyle->processed = true; + } - if (robot) { - robot.FrameNowVisible(); + return true; } - if (planeCount > 0) { - for (int planeIndex = 0; planeIndex < planeCount; ++planeIndex) { - if (g_rectLists[planeIndex] != nullptr) { - delete g_rectLists[planeIndex]; + return false; +} + +// TODO: Rect sizes are wrong, rects in SCI are inclusive of bottom/right but +// in ScummVM are exclusive so extra ±1 operations here are wrong +#if 0 +bool GfxFrameout::processShowStyleWipe(const ShowStyleEntry *const style) { + const int16 divisions = style->divisions; + Common::Rect rect(divisions, divisions); + + const Plane *const plane = _visibleScreen->planeList->findByObject(style->plane); + + const int16 planeLeft = plane->field_4C.left; + const int16 planeTop = plane->field_4C.top; + const int16 planeRight = plane->field_4C.right; + const int16 planeBottom = plane->field_4C.bottom; + const int16 planeWidth = planeRight - planeLeft + 1; + const int16 planeHeight = planeBottom - planeTop + 1; + + const int16 divisionWidth = planeWidth / divisions - 1; + int16 shutterDivisionWidth = planeWidth / (2 * divisions); + if (shutterDivisionWidth >= 0) { + const int16 heightPerDivision = planeHeight / divisions; + int16 shutterMiddleX = divisions * shutterDivisionWidth; + for (int16 x = divisionWidth - shutterDivisionWidth; x <= divisionWidth; ++x) { + int16 divisionTop = 0; + for (int16 y = 0; y < heightPerDivision; ++y, divisionTop += divisions) { + rect.top = planeTop + divisionTop; + rect.bottom = rect.top + divisions - 1; + rect.left = planeLeft + shutterMiddleX; + rect.right = rect.left + divisions - 1; + // _screen->rectList.clear(); + // _screen->rectList.add(rect); + // showBits(); + } + // number of divisions does not divide evenly into plane height, + // draw the remainder + if (planeHeight % divisions) { + rect.top = planeTop + divisionTop; + rect.bottom = rect.top + planeHeight % divisions - 1; + rect.left = planeLeft + shutterMiddleX; + rect.right = rect.left + divisions - 1; + // _screen->rectList.clear(); + // _screen->rectList.add(rect); + // showBits(); } - if (g_drawLists[planeIndex] != nullptr) { - delete g_drawLists[planeIndex]; + + divisionTop = 0; + for (int16 y = 0; y < heightPerDivision; ++y, divisionTop += divisions) { + rect.top = planeTop + divisionTop; + rect.bottom = rect.top + divisions - 1; + rect.left = planeLeft + divisions * x; + rect.right = rect.left + divisions - 1; + // _screen->rectList.clear(); + // _screen->rectList.add(rect); + // showBits(); + } + if (planeHeight % divisions) { + rect.top = planeTop + divisionTop; + rect.bottom = rect.top + planeHeight % divisions - 1; + rect.left = planeLeft + divisions * x; + rect.right = rect.left + divisions - 1; + // _screen->rectList.clear(); + // _screen->rectList.add(rect); + // showBits(); } + + shutterMiddleX -= divisions; + --shutterDivisionWidth; } } -} -void GfxFrameout::CalcLists(DrawList **drawLists, RectList **rectLists, SOL_Rect *rect) { - screen.CalcLists(&visibleScreen, drawLists, rectLists, rect); -} -*/ -void GfxFrameout::kernelFrameout() { - if (g_sci->_robotDecoder->isVideoLoaded()) { - showVideo(); - return; + + if (planeWidth % divisions) { + const int16 roundedPlaneWidth = divisions * divisionWidth; + int16 divisionTop = 0; + for (int16 y = 0; y < planeHeight / divisions; ++y, divisionTop += divisions) { + rect.top = planeTop + divisionTop; + rect.bottom = rect.top + divisions - 1; + rect.left = planeLeft + roundedPlaneWidth; + rect.right = rect.left + planeWidth % divisions + divisions - 1; + // _screen->rectList.clear(); + // _screen->rectList.add(rect); + // showBits(); + } + if (planeHeight % divisions) { + rect.top = planeTop + divisionTop; + rect.bottom = rect.top + planeHeight % divisions - 1; + rect.left = planeLeft + roundedPlaneWidth; + rect.right = rect.left + planeWidth % divisions + divisions - 1; + // _screen->rectList.clear(); + // _screen->rectList.add(rect); + // showBits(); + } } - _palette->updateForFrame(); + rect.right = planeRight; + rect.left = planeLeft; + rect.top = planeTop; + rect.bottom = planeBottom; + // _screen->rectList.clear(); + // _screen->rectList.add(rect); + // showBits(); +} +#endif +#if 0 +bool GfxFrameout::processShowStyleWipe(const int direction, ShowStyleEntry *const showStyle) { + if (showStyle->currentStep < showStyle->divisions) { + int index; + if (direction <= 0) { + index = showStyle->divisions - showStyle->currentStep - 1; + } else { + index = showStyle->currentStep; + } - // TODO: Tons of drawing stuff should be here, see commented out implementation above + index *= showStyle->edgeCount; - _palette->updateHardware(); + if (showStyle->edgeCount > 0) { + for (int i = 0; i < showStyle->edgeCount; ++i) { + if (showStyle->fadeUp) { + ScreenItem *screenItem = showStyle->screenItems[index + i]; + if (screenItem != nullptr) { + // TODO: _screen->deleteScreenItem(screenItem); + _screenItems.remove(screenItem); - for (PlaneList::iterator it = _planes.begin(); it != _planes.end(); it++) { - reg_t planeObject = it->object; + delete screenItem; + showStyle->screenItems[index + i] = nullptr; + } + } else { + ScreenItem *screenItem = showStyle->screenItems[index + i]; + // TODO: _screen->addScreenItem(screenItem); + _screenItems.push_back(screenItem); + } + } - // Draw any plane lines, if they exist - // These are drawn on invisible planes as well. (e.g. "invisiblePlane" in LSL6 hires) - // FIXME: Lines aren't always drawn (e.g. when the narrator speaks in LSL6 hires). - // Perhaps something is painted over them? - for (PlaneLineList::iterator it2 = it->lines.begin(); it2 != it->lines.end(); ++it2) { - Common::Point startPoint = it2->startPoint; - Common::Point endPoint = it2->endPoint; - _coordAdjuster->kernelLocalToGlobal(startPoint.x, startPoint.y, it->object); - _coordAdjuster->kernelLocalToGlobal(endPoint.x, endPoint.y, it->object); - _screen->drawLine(startPoint, endPoint, it2->color, it2->priority, it2->control); + ++showStyle->currentStep; + showStyle->nextTick += showStyle->delay; } + } - int16 planeLastPriority = it->lastPriority; - - // Update priority here, sq6 sets it w/o UpdatePlane - int16 planePriority = it->priority = readSelectorValue(_segMan, planeObject, SELECTOR(priority)); - - it->lastPriority = planePriority; - if (planePriority < 0) { // Plane currently not meant to be shown - // If plane was shown before, delete plane rect - if (planePriority != planeLastPriority) - _paint32->fillRect(it->planeRect, 0); - continue; + if (showStyle->currentStep >= showStyle->divisions) { + if (showStyle->fadeUp) { + showStyle->processed = true; } - // There is a race condition lurking in SQ6, which causes the game to hang in the intro, when teleporting to Polysorbate LX. - // Since I first wrote the patch, the race has stopped occurring for me though. - // I'll leave this for investigation later, when someone can reproduce. - //if (it->pictureId == kPlanePlainColored) // FIXME: This is what SSCI does, and fixes the intro of LSL7, but breaks the dialogs in GK1 (adds black boxes) - if (it->pictureId == kPlanePlainColored && (it->planeBack || g_sci->getGameId() != GID_GK1)) - _paint32->fillRect(it->planeRect, it->planeBack); + return true; + } + + return false; +} - _coordAdjuster->pictureSetDisplayArea(it->planeRect); - // Invoking drewPicture() with an invalid picture ID in SCI32 results in - // invalidating the palVary palette when a palVary effect is active. This - // is quite obvious in QFG4, where the day time palette is incorrectly - // shown when exiting the caves, and the correct night time palette - // flashes briefly each time that kPalVaryInit is called. - if (it->pictureId != 0xFFFF) - _palette->drewPicture(it->pictureId); +void fillRect(byte *data, const Common::Rect &rect, const int16 color, const int16 stride) { - FrameoutList itemList; +} - createPlaneItemList(planeObject, itemList); +bool GfxFrameout::processShowStyle11(ShowStyleEntry *const showStyle) { + int divisions = showStyle->divisions * showStyle->divisions; - for (FrameoutList::iterator listIterator = itemList.begin(); listIterator != itemList.end(); listIterator++) { - FrameoutEntry *itemEntry = *listIterator; + byte *bitmapData = _segMan->getHunkPointer(showStyle->bitmapMemId) + sizeof(GfxBitmapHeader); - if (!itemEntry->visible) - continue; + int ebx; - if (itemEntry->object.isNull()) { - // Picture cel data - _coordAdjuster->fromScriptToDisplay(itemEntry->y, itemEntry->x); - _coordAdjuster->fromScriptToDisplay(itemEntry->picStartY, itemEntry->picStartX); + if (showStyle->currentStep == 0) { + int ctr = 0; + int bloot = divisions; + do { + bloot >>= 1; + ++ctr; + } while (bloot != 1); - if (!isPictureOutOfView(itemEntry, it->planeRect, it->planeOffsetX, it->planeOffsetY)) - drawPicture(itemEntry, it->planeOffsetX, it->planeOffsetY, it->planePictureMirrored); + showStyle->dissolveSeed = _dissolveSequenceSeeds[ctr]; + ebx = 800; + showStyle->unknown3A = 800; + showStyle->dissolveInitial = 800; + } else { + int ebx = showStyle->unknown3A; + do { + int eax = ebx >> 1; + if (ebx & 1) { + ebx = showStyle->dissolveSeed ^ eax; } else { - GfxView *view = (itemEntry->viewId != 0xFFFF) ? _cache->getView(itemEntry->viewId) : NULL; - int16 dummyX = 0; - - if (view && view->isSci2Hires()) { - view->adjustToUpscaledCoordinates(itemEntry->y, itemEntry->x); - view->adjustToUpscaledCoordinates(itemEntry->z, dummyX); - } else if (getSciVersion() >= SCI_VERSION_2_1_EARLY) { - _coordAdjuster->fromScriptToDisplay(itemEntry->y, itemEntry->x); - _coordAdjuster->fromScriptToDisplay(itemEntry->z, dummyX); - } + ebx = eax; + } + } while (ebx >= divisions); - // Adjust according to current scroll position - itemEntry->x -= it->planeOffsetX; - itemEntry->y -= it->planeOffsetY; - - uint16 useInsetRect = readSelectorValue(_segMan, itemEntry->object, SELECTOR(useInsetRect)); - if (useInsetRect) { - itemEntry->celRect.top = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inTop)); - itemEntry->celRect.left = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inLeft)); - itemEntry->celRect.bottom = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inBottom)); - itemEntry->celRect.right = readSelectorValue(_segMan, itemEntry->object, SELECTOR(inRight)); - if (view && view->isSci2Hires()) { - view->adjustToUpscaledCoordinates(itemEntry->celRect.top, itemEntry->celRect.left); - view->adjustToUpscaledCoordinates(itemEntry->celRect.bottom, itemEntry->celRect.right); - } - itemEntry->celRect.translate(itemEntry->x, itemEntry->y); - // TODO: maybe we should clip the cels rect with this, i'm not sure - // the only currently known usage is game menu of gk1 - } else if (view) { - // Process global scaling, if needed. - // TODO: Seems like SCI32 always processes global scaling for scaled objects - // TODO: We can only process symmetrical scaling for now (i.e. same value for scaleX/scaleY) - if ((itemEntry->scaleSignal & kScaleSignalDoScaling32) && - !(itemEntry->scaleSignal & kScaleSignalDisableGlobalScaling32) && - (itemEntry->scaleX == itemEntry->scaleY) && - itemEntry->scaleX != 128) - applyGlobalScaling(itemEntry, it->planeRect, view->getHeight(itemEntry->loopNo, itemEntry->celNo)); - - if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128)) - view->getCelRect(itemEntry->loopNo, itemEntry->celNo, - itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->celRect); - else - view->getCelScaledRect(itemEntry->loopNo, itemEntry->celNo, - itemEntry->x, itemEntry->y, itemEntry->z, itemEntry->scaleX, - itemEntry->scaleY, itemEntry->celRect); - - Common::Rect nsRect = itemEntry->celRect; - // Translate back to actual coordinate within scrollable plane - nsRect.translate(it->planeOffsetX, it->planeOffsetY); - - if (g_sci->getGameId() == GID_PHANTASMAGORIA2) { - // HACK: Some (?) objects in Phantasmagoria 2 have no NS rect. Skip them for now. - // TODO: Remove once we figure out how Phantasmagoria 2 draws objects on screen. - if (lookupSelector(_segMan, itemEntry->object, SELECTOR(nsLeft), NULL, NULL) != kSelectorVariable) - continue; - } + if (ebx == showStyle->dissolveInitial) { + ebx = 0; + } + } - if (view && view->isSci2Hires()) { - view->adjustBackUpscaledCoordinates(nsRect.top, nsRect.left); - view->adjustBackUpscaledCoordinates(nsRect.bottom, nsRect.right); - g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect); - } else if (getSciVersion() >= SCI_VERSION_2_1_EARLY && _isHiRes) { - _coordAdjuster->fromDisplayToScript(nsRect.top, nsRect.left); - _coordAdjuster->fromDisplayToScript(nsRect.bottom, nsRect.right); - g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect); - } + Common::Rect rect; - // TODO: For some reason, the top left nsRect coordinates get - // swapped in the GK1 inventory screen, investigate why. - // This is also needed for GK1 rooms 710 and 720 (catacombs, inner and - // outer circle), for handling the tiles and talking to Wolfgang. - // HACK: Fix the coordinates by explicitly setting them here for GK1. - // Also check bug #6729, for another case where this is needed. - if (g_sci->getGameId() == GID_GK1) - g_sci->_gfxCompare->setNSRect(itemEntry->object, nsRect); - } + rect.left = (showStyle->width + showStyle->divisions - 1) / showStyle->divisions; + rect.top = (showStyle->height + showStyle->divisions - 1) / showStyle->divisions; - // Don't attempt to draw sprites that are outside the visible - // screen area. An example is the random people walking in - // Jackson Square in GK1. - if (itemEntry->celRect.bottom < 0 || itemEntry->celRect.top >= _screen->getDisplayHeight() || - itemEntry->celRect.right < 0 || itemEntry->celRect.left >= _screen->getDisplayWidth()) - continue; + if (showStyle->currentStep <= showStyle->divisions) { + int ebp = 0; + do { + int ecx = ebx % showStyle->divisions; - Common::Rect clipRect, translatedClipRect; - clipRect = itemEntry->celRect; + Common::Rect drawRect; + drawRect.left = rect.left * ecx; + drawRect.right = drawRect.left + rect.left - 1; + drawRect.top = rect.top * ebx; + drawRect.bottom = rect.top * ebx + rect.top - 1; - if (view && view->isSci2Hires()) { - clipRect.clip(it->upscaledPlaneClipRect); - translatedClipRect = clipRect; - translatedClipRect.translate(it->upscaledPlaneRect.left, it->upscaledPlaneRect.top); - } else { - // QFG4 passes invalid rectangles when a battle is starting - if (!clipRect.isValidRect()) - continue; - clipRect.clip(it->planeClipRect); - translatedClipRect = clipRect; - translatedClipRect.translate(it->planeRect.left, it->planeRect.top); + bool doit; + if (drawRect.right >= 0 && drawRect.bottom >= 0 && drawRect.left <= rect.right && drawRect.top <= rect.bottom) { + doit = true; + } else { + doit = false; + } + + if (doit) { + if (drawRect.left < 0) { + drawRect.left = 0; } - if (view) { - if (!clipRect.isEmpty()) { - if ((itemEntry->scaleX == 128) && (itemEntry->scaleY == 128)) - view->draw(itemEntry->celRect, clipRect, translatedClipRect, - itemEntry->loopNo, itemEntry->celNo, 255, 0, view->isSci2Hires()); - else - view->drawScaled(itemEntry->celRect, clipRect, translatedClipRect, - itemEntry->loopNo, itemEntry->celNo, 255, itemEntry->scaleX, itemEntry->scaleY); - } + if (drawRect.top < 0) { + drawRect.top = 0; } - // Draw text, if it exists - if (lookupSelector(_segMan, itemEntry->object, SELECTOR(text), NULL, NULL) == kSelectorVariable) { - g_sci->_gfxText32->drawTextBitmap(itemEntry->x, itemEntry->y, it->planeRect, itemEntry->object); + if (drawRect.right > rect.right) { + drawRect.right = rect.right; } + + if (drawRect.bottom > rect.bottom) { + drawRect.bottom = rect.bottom; + } + } else { + drawRect.right = 0; + drawRect.bottom = 0; + drawRect.left = 0; + drawRect.top = 0; } - } - for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) { - if (pictureIt->object == planeObject) { - delete[] pictureIt->pictureCels; - pictureIt->pictureCels = 0; + fillRect(bitmapData, drawRect, showStyle->color, showStyle->width); + + int eax = ebx; + do { + eax >>= 1; + if (ebx & 1) { + ebx = showStyle->dissolveSeed; + ebx ^= eax; + } else { + ebx = eax; + } + } while (ebx >= divisions); + + if (showStyle->currentStep != showStyle->divisions) { + ebp++; + } else { + drawRect.left = 0; + drawRect.top = 0; + drawRect.right = showStyle->width - 1; + drawRect.bottom = showStyle->height - 1; + fillRect(bitmapData, drawRect, showStyle->color, showStyle->width); } - } + + } while (ebp <= showStyle->divisions); + + showStyle->unknown3A = ebx; + ++showStyle->currentStep; + showStyle->nextTick += showStyle->delay; + // _screen->updateScreenItem(showStyle->bitmapScreenItem); } - showCurrentScrollText(); + if (showStyle->currentStep >= showStyle->divisions) { + if (showStyle->fadeUp) { + showStyle->processed = true; + } - _screen->copyToScreen(); + return true; + } - g_sci->getEngineState()->_throttleTrigger = true; + return false; } -void GfxFrameout::printPlaneList(Console *con) { - for (PlaneList::const_iterator it = _planes.begin(); it != _planes.end(); ++it) { - PlaneEntry p = *it; - Common::String curPlaneName = _segMan->getObjectName(p.object); - Common::Rect r = p.upscaledPlaneRect; - Common::Rect cr = p.upscaledPlaneClipRect; +bool GfxFrameout::processShowStyle12(ShowStyleEntry *const showStyle) { + return true; +} +#endif + +void GfxFrameout::kernelFrameout(const bool shouldShowBits) { + if (_showStyles != nullptr) { + processShowStyles(); + } else if (_palMorphIsOn) { + palMorphFrameOut(_styleRanges, nullptr); + _palMorphIsOn = false; + } else { +// TODO: Window scroll +// if (g_ScrollWindow) { +// doScroll(); +// } - con->debugPrintf("%04x:%04x (%s): prio %d, lastprio %d, offsetX %d, offsetY %d, pic %d, mirror %d, back %d\n", - PRINT_REG(p.object), curPlaneName.c_str(), - (int16)p.priority, (int16)p.lastPriority, - p.planeOffsetX, p.planeOffsetY, p.pictureId, - p.planePictureMirrored, p.planeBack); - con->debugPrintf(" rect: (%d, %d, %d, %d), clip rect: (%d, %d, %d, %d)\n", - r.left, r.top, r.right, r.bottom, - cr.left, cr.top, cr.right, cr.bottom); + Common::Rect frameOutRect(0, 0); + frameOut(shouldShowBits, frameOutRect); + } +} - if (p.pictureId != 0xffff && p.pictureId != 0xfffe) { - con->debugPrintf("Pictures:\n"); +#pragma mark - +#pragma mark Debugging - for (PlanePictureList::iterator pictureIt = _planePictures.begin(); pictureIt != _planePictures.end(); pictureIt++) { - if (pictureIt->object == p.object) { - con->debugPrintf(" Picture %d: x %d, y %d\n", pictureIt->pictureId, pictureIt->startX, pictureIt->startY); - } - } - } +void GfxFrameout::printPlaneListInternal(Console *con, const PlaneList &planeList) const { + for (PlaneList::const_iterator it = planeList.begin(); it != planeList.end(); ++it) { + Plane *p = *it; + p->printDebugInfo(con); } } -void GfxFrameout::printPlaneItemList(Console *con, reg_t planeObject) { - for (FrameoutList::iterator listIterator = _screenItems.begin(); listIterator != _screenItems.end(); listIterator++) { - FrameoutEntry *e = *listIterator; - reg_t itemPlane = readSelector(_segMan, e->object, SELECTOR(plane)); +void GfxFrameout::printPlaneList(Console *con) const { + printPlaneListInternal(con, _planes); +} + +void GfxFrameout::printVisiblePlaneList(Console *con) const { + printPlaneListInternal(con, _visiblePlanes); +} - if (planeObject == itemPlane) { - Common::String curItemName = _segMan->getObjectName(e->object); - Common::Rect icr = e->celRect; - GuiResourceId picId = e->picture ? e->picture->getResourceId() : 0; +void GfxFrameout::printPlaneItemList(Console *con, const reg_t planeObject) const { + Plane *p = _planes.findByObject(planeObject); - con->debugPrintf("%d: %04x:%04x (%s), view %d, loop %d, cel %d, x %d, y %d, z %d, " - "signal %d, scale signal %d, scaleX %d, scaleY %d, rect (%d, %d, %d, %d), " - "pic %d, picX %d, picY %d, visible %d\n", - e->givenOrderNr, PRINT_REG(e->object), curItemName.c_str(), - e->viewId, e->loopNo, e->celNo, e->x, e->y, e->z, - e->signal, e->scaleSignal, e->scaleX, e->scaleY, - icr.left, icr.top, icr.right, icr.bottom, - picId, e->picStartX, e->picStartY, e->visible); - } + if (p == nullptr) { + con->debugPrintf("Plane does not exist"); + return; + } + + ScreenItemList::size_type i = 0; + for (ScreenItemList::iterator sit = p->_screenItemList.begin(); sit != p->_screenItemList.end(); sit++) { + ScreenItem *screenItem = *sit; + con->debugPrintf("%2d: ", i++); + screenItem->printDebugInfo(con); } } diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h index d1a706e8de..5323a2ad04 100644 --- a/engines/sci/graphics/frameout.h +++ b/engines/sci/graphics/frameout.h @@ -23,86 +23,190 @@ #ifndef SCI_GRAPHICS_FRAMEOUT_H #define SCI_GRAPHICS_FRAMEOUT_H -namespace Sci { +#include "sci/graphics/plane32.h" +#include "sci/graphics/screen_item32.h" -class GfxPicture; +namespace Sci { +// TODO: Don't do this this way +int splitRects(Common::Rect r, const Common::Rect &other, Common::Rect(&outRects)[4]); -struct PlaneLineEntry { - reg_t hunkId; - Common::Point startPoint; - Common::Point endPoint; - byte color; - byte priority; - byte control; +// TODO: Verify display styles and adjust names appropriately for +// types 1 through 12 & 15 (others are correct) +// Names should be: +// * VShutterIn, VShutterOut +// * HShutterIn, HShutterOut +// * WipeLeft, WipeRight, WipeDown, WipeUp +// * PixelDissolve +// * ShutDown and Kill? (and Plain and Fade?) +enum ShowStyleType /* : uint8 */ { + kShowStyleNone = 0, + kShowStyleHShutterOut = 1, + kShowStyleHShutterIn = 2, + kShowStyleVShutterOut = 3, + kShowStyleVShutterIn = 4, + kShowStyleWipeLeft = 5, + kShowStyleWipeRight = 6, + kShowStyleWipeUp = 7, + kShowStyleWipeDown = 8, + kShowStyleIrisOut = 9, + kShowStyleIrisIn = 10, + kShowStyle11 = 11, + kShowStyle12 = 12, + kShowStyleFadeOut = 13, + kShowStyleFadeIn = 14, + // TODO: Only in SCI3 + kShowStyleUnknown = 15 }; -typedef Common::List PlaneLineList; - -struct PlaneEntry { - reg_t object; - int16 priority; - int16 lastPriority; - int16 planeOffsetX; - int16 planeOffsetY; - GuiResourceId pictureId; - Common::Rect planeRect; - Common::Rect planeClipRect; - Common::Rect upscaledPlaneRect; - Common::Rect upscaledPlaneClipRect; - bool planePictureMirrored; - byte planeBack; - PlaneLineList lines; -}; +/** + * Show styles represent transitions applied to draw planes. + * One show style per plane can be active at a time. + */ +struct ShowStyleEntry { + /** + * The ID of the plane this show style belongs to. + * In SCI2.1mid (at least SQ6), per-plane transitions + * were removed and a single plane ID is used. + */ + reg_t plane; -typedef Common::List PlaneList; - -struct FrameoutEntry { - uint16 givenOrderNr; - reg_t object; - GuiResourceId viewId; - int16 loopNo; - int16 celNo; - int16 x, y, z; - int16 priority; - uint16 signal; - uint16 scaleSignal; - int16 scaleX; - int16 scaleY; - Common::Rect celRect; - GfxPicture *picture; - int16 picStartX; - int16 picStartY; - bool visible; -}; + /** + * The type of the transition. + */ + ShowStyleType type; -typedef Common::List FrameoutList; + // TODO: This name is probably incorrect + bool fadeUp; -struct PlanePictureEntry { - reg_t object; - int16 startX; - int16 startY; - GuiResourceId pictureId; - GfxPicture *picture; - FrameoutEntry *pictureCels; // temporary -}; + /** + * The number of steps for the show style. + */ + int16 divisions; -typedef Common::List PlanePictureList; + // NOTE: This property exists from SCI2 through at least + // SCI2.1mid but is never used in the actual processing + // of the styles? + int unknownC; -struct ScrollTextEntry { - reg_t bitmapHandle; - reg_t kWindow; - uint16 x; - uint16 y; -}; + /** + * The colour used by transitions that draw CelObjColor + * screen items. -1 for transitions that do not draw + * screen items. + */ + int16 color; + + // TODO: Probably uint32 + // TODO: This field probably should be used in order to + // provide time-accurate processing of show styles. In the + // actual SCI engine (at least 2–2.1mid) it appears that + // style transitions are drawn “as fast as possible”, one + // step per loop, even though this delay field exists + int delay; + + // TODO: Probably bool, but never seems to be true? + int animate; + + /** + * The wall time at which the next step of the animation + * should execute. + */ + uint32 nextTick; + + /** + * During playback of the show style, the current step + * (out of divisions). + */ + int currentStep; + + /** + * The next show style. + */ + ShowStyleEntry *next; + + /** + * Whether or not this style has finished running and + * is ready for disposal. + */ + bool processed; + + // + // Engine-specific properties for SCI2 through 2.1early + // + + // TODO: Could union this stuff to save literally + // several bytes of memory. + + /** + * The width of the plane. Used to determine the correct + * size of screen items for wipes. + */ + int width; + + /** + * The height of the plane. Used to determine the correct + * size of screen items for wipes. + */ + int height; + + /** + * The number of edges that a transition operates on. + * Slide wipe: 1 edge + * Reveal wipe: 2 edges + * Iris wipe: 4 edges + */ + // TODO: I have no idea why SCI engine stores this instead + // of a screenItems count + int edgeCount; + + /** + * Used by transition types 1 through 10. + * One screen item per division per edge. + */ + ScreenItemList screenItems; + + /** + * Used by transition types 11 and 12. A copy of the + * visible frame buffer. + */ + // TODO: This is a reg_t in SCI engine; not sure if + // we can avoid allocation through SegMan or not. + reg_t bitmapMemId; + + /** + * Used by transition types 11 and 12. A screen item + * used to display the associated bitmap data. + */ + ScreenItem *bitmapScreenItem; -typedef Common::Array ScrollTextList; + /** + * A number used to pick pixels to dissolve by types + * 11 and 12. + */ + int dissolveSeed; + int unknown3A; + // max? + int dissolveInitial; -enum ViewScaleSignals32 { - kScaleSignalDoScaling32 = 0x0001, // enables scaling when drawing that cel (involves scaleX and scaleY) - kScaleSignalUnk1 = 0x0002, // unknown - kScaleSignalDisableGlobalScaling32 = 0x0004 + // + // Engine specific properties for SCI2.1mid through SCI3 + // + + /** + * The number of entries in the fadeColorRanges array. + */ + uint8 fadeColorRangesCount; + + /** + * A pointer to an dynamically sized array of palette + * indexes, in the order [ fromColor, toColor, ... ]. + * Only colors within this range are transitioned. + */ + uint16 *fadeColorRanges; }; +typedef Common::Array ScreenItemListList; +typedef Common::Array EraseListList; + class GfxCache; class GfxCoordAdjuster32; class GfxPaint32; @@ -114,69 +218,285 @@ class GfxScreen; * Roughly equivalent to GraphicsMgr in the actual SCI engine. */ 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(); - void kernelAddPlane(reg_t object); - void kernelUpdatePlane(reg_t object); - void kernelDeletePlane(reg_t object); - void applyGlobalScaling(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 celHeight); - void kernelAddScreenItem(reg_t object); - void kernelUpdateScreenItem(reg_t object); - void kernelDeleteScreenItem(reg_t object); - void deletePlaneItems(reg_t planeObject); - FrameoutEntry *findScreenItem(reg_t object); - int16 kernelGetHighPlanePri(); - void kernelAddPicAt(reg_t planeObj, GuiResourceId pictureId, int16 pictureX, int16 pictureY); - void kernelFrameout(); - - void addPlanePicture(reg_t object, GuiResourceId pictureId, uint16 startX, uint16 startY = 0); - void deletePlanePictures(reg_t object); - reg_t addPlaneLine(reg_t object, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control); - void updatePlaneLine(reg_t object, reg_t hunkId, Common::Point startPoint, Common::Point endPoint, byte color, byte priority, byte control); - void deletePlaneLine(reg_t object, reg_t hunkId); void clear(); + void run(); - // Scroll text functions - void addScrollTextEntry(Common::String &text, reg_t kWindow, uint16 x, uint16 y, bool replace); - void showCurrentScrollText(); - void initScrollText(uint16 maxItems) { _maxScrollTexts = maxItems; } - void clearScrollTexts(); - void firstScrollText() { if (_scrollTexts.size() > 0) _curScrollText = 0; } - void lastScrollText() { if (_scrollTexts.size() > 0) _curScrollText = _scrollTexts.size() - 1; } - void prevScrollText() { if (_curScrollText > 0) _curScrollText--; } - void nextScrollText() { if (_curScrollText + 1 < (uint16)_scrollTexts.size()) _curScrollText++; } - void toggleScrollText(bool show) { _showScrollText = show; } +#pragma mark - +#pragma mark Screen items +private: + void deleteScreenItem(ScreenItem *screenItem, const reg_t plane); - void printPlaneList(Console *con); - void printPlaneItemList(Console *con, reg_t planeObject); +public: + void kernelAddScreenItem(const reg_t object); + void kernelUpdateScreenItem(const reg_t object); + void kernelDeleteScreenItem(const reg_t object); +#pragma mark - +#pragma mark Planes private: - bool _isHiRes; + /** + * The list of planes (i.e. layers) that have been added + * to the screen. + * + * @note This field is on `GraphicsMgr.screen` in SCI + * engine. + */ + PlaneList _planes; - void showVideo(); - void createPlaneItemList(reg_t planeObject, FrameoutList &itemList); - bool isPictureOutOfView(FrameoutEntry *itemEntry, Common::Rect planeRect, int16 planeOffsetX, int16 planeOffsetY); - void drawPicture(FrameoutEntry *itemEntry, int16 planeOffsetX, int16 planeOffsetY, bool planePictureMirrored); + /** + * Creates and adds a new plane to the plane list, or + * cancels deletion and updates an already-existing + * plane if a plane matching the given plane VM object + * already exists within the current plane list. + * + * @note This method is on Screen in SCI engine, but it + * is only ever called on `GraphicsMgr.screen`. + */ + void addPlane(Plane &plane); - SegManager *_segMan; - ResourceManager *_resMan; - GfxCoordAdjuster32 *_coordAdjuster; - GfxCache *_cache; - GfxPalette32 *_palette; - GfxScreen *_screen; - GfxPaint32 *_paint32; + /** + * Updates an existing plane with properties from the + * given VM object. + */ + void updatePlane(Plane &plane); - FrameoutList _screenItems; - PlaneList _planes; - PlanePictureList _planePictures; - ScrollTextList _scrollTexts; - int16 _curScrollText; - bool _showScrollText; - uint16 _maxScrollTexts; +public: + const PlaneList &getPlanes() const { + return _planes; + } + void kernelAddPlane(const reg_t object); + void kernelUpdatePlane(const reg_t object); + void kernelDeletePlane(const reg_t object); + int16 kernelGetHighPlanePri(); + +#pragma mark - +#pragma mark Pics +public: + void kernelAddPicAt(const reg_t planeObject, const GuiResourceId pictureId, const int16 pictureX, const int16 pictureY, const bool mirrorX); + +#pragma mark - + + // TODO: Remap-related? + void kernelSetPalStyleRange(const uint8 fromColor, const uint8 toColor); + +#pragma mark - +#pragma mark Transitions +private: + int *_dissolveSequenceSeeds; + int16 *_defaultDivisions; + int16 *_defaultUnknownC; + + /** + * TODO: Documentation + */ + ShowStyleEntry *_showStyles; + + inline ShowStyleEntry *findShowStyleForPlane(const reg_t planeObj) const; + inline ShowStyleEntry *deleteShowStyleInternal(ShowStyleEntry *const showStyle); + void processShowStyles(); + bool processShowStyleNone(ShowStyleEntry *showStyle); + bool processShowStyleMorph(ShowStyleEntry *showStyle); + bool processShowStyleFade(const int direction, ShowStyleEntry *showStyle); +#if 0 + bool processShowStyleWipe(const int direction, ShowStyleEntry *const showStyle); +#endif + +public: + // NOTE: This signature is taken from SCI3 Phantasmagoria 2 + // and is valid for all implementations of SCI32 + void kernelSetShowStyle(const uint16 argc, const reg_t &planeObj, const ShowStyleType type, const int16 seconds, const int16 direction, const int16 priority, const int16 animate, const int16 frameOutNow, const reg_t &pFadeArray, const int16 divisions, const int16 blackScreen); + +#pragma mark - +#pragma mark Rendering +private: + /** + * TODO: Documentation + */ + int8 _styleRanges[256]; + + /** + * The internal display pixel buffer. During frameOut, + * this buffer is drawn into according to the draw and + * erase rects calculated by `calcLists`, then drawn out + * to the hardware surface according to the `_showList` + * rects (which are also calculated by `calcLists`). + */ + Buffer _currentBuffer; + + // TODO: In SCI2.1/SQ6, priority map pixels are not allocated + // by default. In SCI2/GK1, pixels are allocated, but not used + // anywhere except within CelObj::Draw in seemingly the same + // way they are used in SCI2.1/SQ6: that is, never read, only + // written. + Buffer _priorityMap; + + /** + * TODO: Documentation + */ + bool _remapOccurred; - void sortPlanes(); + /** + * Whether or not the data in the current buffer is what + * is visible to the user. During rendering updates, + * this flag is set to false. + */ + bool _frameNowVisible; + + /** + * TODO: Document + * TODO: Depending upon if the engine ever modifies this + * rect, it may be stupid to store it separately instead + * of just getting width/height from GfxScreen. + * + * @note This field is on `GraphicsMgr.screen` in SCI + * engine. + */ + Common::Rect _screenRect; + + /** + * A list of rectangles, in display coordinates, that + * represent portions of the internal screen buffer that + * should be drawn to the hardware display surface. + * + * @note This field is on `GraphicsMgr.screen` in SCI + * engine. + */ + RectList _showList; + + /** + * The amount of extra overdraw that is acceptable when + * merging two show list rectangles together into a + * single larger rectangle. + * + * @note This field is on `GraphicsMgr.screen` in SCI + * engine. + */ + int _overdrawThreshold; + + /** + * A list of planes that are currently drawn to the + * hardware display surface. Used to calculate + * differences in plane properties between the last + * frame and current frame. + * + * @note This field is on `GraphicsMgr.visibleScreen` in + * SCI engine. + */ + PlaneList _visiblePlanes; + + /** + * Calculates the location and dimensions of dirty rects + * 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. + */ + void calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &calcRect); + + /** + * Erases the areas in the given erase list from the + * visible screen buffer by filling them with the color + * from the corresponding plane. This is an optimisation + * for coloured-type planes only; other plane types have + * to be redrawn from pixel data. + */ + void drawEraseList(const RectList &eraseList, const Plane &plane); + + /** + * Draws all screen items from the given draw list to + * the visible screen buffer. + */ + void drawScreenItemList(const DrawList &screenItemList); + + /** + * Updates the internal screen buffer for the next + * frame. If `shouldShowBits` is true, also sends the + * buffer to hardware. + */ + void frameOut(const bool shouldShowBits, const Common::Rect &rect); + + /** + * Adds a new rectangle to the list of regions to write + * out to the hardware. The provided rect may be merged + * into an existing rectangle to reduce the number of + * blit operations. + */ + void mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold); + + /** + * TODO: Documentation + */ + void palMorphFrameOut(const int8 *styleRanges, const ShowStyleEntry *showStyle); + + /** + * Writes the internal frame buffer out to hardware and + * clears the show list. + */ + void showBits(); + +public: + /** + * TODO: Document + * This is used by CelObj::Draw. + */ + bool _hasRemappedScreenItem; + + /** + * Whether palMorphFrameOut should be used instead of + * frameOut for rendering. Used by kMorphOn to + * explicitly enable palMorphFrameOut for one frame. + */ + bool _palMorphIsOn; + + inline Buffer &getCurrentBuffer() { + return _currentBuffer; + } + + void kernelFrameout(const bool showBits); + + /** + * Modifies the raw pixel data for the next frame with + * new palette indexes based on matched style ranges. + */ + void alterVmap(const Palette &palette1, const Palette &palette2, const int8 style, const int8 *const styleRanges); + + // TODO: SCI2 engine never uses priority map? + inline Buffer &getPriorityMap() { + return _priorityMap; + } + + // NOTE: This function is used within ScreenItem subsystem and assigned + // to various booleanish fields that seem to represent the state of the + // screen item (created, updated, deleted). In GK1/DOS, Phant1/m68k, + // SQ6/DOS, SQ6/Win, and Phant2/Win, this function simply returns 1. If + // you know of any game/environment where this function returns some + // value other than 1, or if you used to work at Sierra and can explain + // why this is a thing (and if anyone needs to care about it), please + // open a ticket!! + inline int getScreenCount() const { + return 1; + }; + +#pragma mark - +#pragma mark Debugging +public: + void printPlaneList(Console *con) const; + void printVisiblePlaneList(Console *con) const; + void printPlaneListInternal(Console *con, const PlaneList &planeList) const; + void printPlaneItemList(Console *con, const reg_t planeObject) const; }; } // End of namespace Sci diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h index e5b9f2aaed..c48ad4c8bf 100644 --- a/engines/sci/graphics/helpers.h +++ b/engines/sci/graphics/helpers.h @@ -26,6 +26,11 @@ #include "common/endian.h" // for READ_LE_UINT16 #include "common/rect.h" #include "common/serializer.h" +#ifdef ENABLE_SCI32 +#include "common/rational.h" +#include "graphics/pixelformat.h" +#include "graphics/surface.h" +#endif #include "sci/engine/vm_types.h" namespace Sci { @@ -45,6 +50,9 @@ typedef int16 TextAlignment; #define PORTS_FIRSTWINDOWID 2 #define PORTS_FIRSTSCRIPTWINDOWID 3 +#ifdef ENABLE_SCI32 +#define PRINT_RECT(x) (x).left,(x).top,(x).right,(x).bottom +#endif struct Port { uint16 id; @@ -118,6 +126,79 @@ struct Window : public Port, public Common::Serializable { } }; +#ifdef ENABLE_SCI32 +/** + * Multiplies a number by a rational number, rounding up to + * the nearest whole number. + */ +inline int mulru(const int value, const Common::Rational &ratio, const int extra = 0) { + int num = (value + extra) * ratio.getNumerator(); + int result = num / ratio.getDenominator(); + if (num > ratio.getDenominator() && num % ratio.getDenominator()) { + ++result; + } + return result - extra; +} + +/** + * Multiplies a point by two rational numbers for X and Y, + * rounding up to the nearest whole number. Modifies the + * point directly. + */ +inline void mulru(Common::Point &point, const Common::Rational &ratioX, const Common::Rational &ratioY) { + point.x = mulru(point.x, ratioX); + point.y = mulru(point.y, ratioY); +} + +/** + * Multiplies a point by two rational numbers for X and Y, + * rounding up to the nearest whole number. Modifies the + * rect directly. + */ +inline void mulru(Common::Rect &rect, const Common::Rational &ratioX, const Common::Rational &ratioY, const int brExtra = 0) { + rect.left = mulru(rect.left, ratioX); + rect.top = mulru(rect.top, ratioY); + rect.right = mulru(rect.right, ratioX, brExtra); + rect.bottom = mulru(rect.bottom, ratioY, brExtra); +} + +struct Buffer : public Graphics::Surface { + uint16 screenWidth; + uint16 screenHeight; + uint16 scriptWidth; + uint16 scriptHeight; + + Buffer(const uint16 width, const uint16 height, uint8 *const pix) : + screenWidth(width), + screenHeight(height), + // TODO: These values are not correct for all games. Script + // dimensions were hard-coded per game in the original + // interpreter. Search all games for their internal script + // dimensions and set appropriately. (This code does not + // appear to exist at all in SCI3, which uses 640x480.) + scriptWidth(320), + scriptHeight(200) { + init(width, height, width, pix, Graphics::PixelFormat::createFormatCLUT8()); + } + + void clear(const uint8 value) { + memset(pixels, value, w * h); + } + + inline uint8 *getAddress(const uint16 x, const uint16 y) { + return (uint8 *)getBasePtr(x, y); + } + + inline uint8 *getAddressSimRes(const uint16 x, const uint16 y) { + return (uint8*)pixels + (y * w * screenHeight / scriptHeight) + (x * screenWidth / scriptWidth); + } + + bool isNull() { + return pixels == nullptr; + } +}; +#endif + struct Color { byte used; byte r, g, b; diff --git a/engines/sci/graphics/lists32.h b/engines/sci/graphics/lists32.h new file mode 100644 index 0000000000..b12cd55897 --- /dev/null +++ b/engines/sci/graphics/lists32.h @@ -0,0 +1,192 @@ +/* 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_LISTS32_H +#define SCI_GRAPHICS_LISTS32_H + +#include "common/array.h" + +namespace Sci { + +/** + * StablePointerArray holds pointers in a fixed-size array + * that maintains position of erased items until `pack` is + * called. It is used by DrawList, RectList, and + * ScreenItemList. StablePointerArray takes ownership of + * all pointers that are passed to it and deletes them when + * calling `erase` or when destroying the + * StablePointerArray. + */ +template +class StablePointerArray { + uint _size; + T *_items[N]; + +public: + typedef T **iterator; + typedef T *const *const_iterator; + typedef T *value_type; + typedef uint size_type; + + StablePointerArray() : _size(0), _items() {} + StablePointerArray(const StablePointerArray &other) : _size(other._size) { + for (size_type i = 0; i < _size; ++i) { + if (other._items[i] == nullptr) { + _items[i] = nullptr; + } else { + _items[i] = new T(*other._items[i]); + } + } + } + ~StablePointerArray() { + for (size_type i = 0; i < _size; ++i) { + delete _items[i]; + } + } + + void operator=(const StablePointerArray &other) { + clear(); + _size = other._size; + for (size_type i = 0; i < _size; ++i) { + if (other._items[i] == nullptr) { + _items[i] = nullptr; + } else { + _items[i] = new T(*other._items[i]); + } + } + } + + T *const &operator[](size_type index) const { + assert(index >= 0 && index < _size); + return _items[index]; + } + + T *&operator[](size_type index) { + assert(index >= 0 && index < _size); + return _items[index]; + } + + /** + * Adds a new pointer to the array. + */ + void add(T *item) { + assert(_size < N); + _items[_size++] = item; + } + + iterator begin() { + return _items; + } + + const_iterator begin() const { + return _items; + } + + void clear() { + for (size_type i = 0; i < _size; ++i) { + delete _items[i]; + _items[i] = nullptr; + } + + _size = 0; + } + + iterator end() { + return _items + _size; + } + + const_iterator end() const { + return _items + _size; + } + + /** + * Erases the object pointed to by the given iterator. + */ + void erase(T *item) { + for (iterator it = begin(); it != end(); ++it) { + if (*it == item) { + delete *it; + *it = nullptr; + break; + } + } + } + + /** + * Erases the object pointed to by the given iterator. + */ + void erase(iterator &it) { + assert(it >= _items && it < _items + _size); + delete *it; + *it = nullptr; + } + + /** + * Erases the object pointed to at the given index. + */ + void erase_at(size_type index) { + assert(index >= 0 && index < _size); + + delete _items[index]; + _items[index] = nullptr; + } + + /** + * Removes freed pointers from the pointer list. + */ + size_type pack() { + iterator freePtr = begin(); + size_type newSize = 0; + + for (iterator it = begin(), last = end(); it != last; ++it) { + if (*it != nullptr) { + *freePtr = *it; + ++freePtr; + ++newSize; + } + } + + _size = newSize; + return newSize; + } + + /** + * The number of populated slots in the array. The size + * of the array will only go down once `pack` is called. + */ + size_type size() const { + return _size; + } +}; + +template +class FindByObject { + const reg_t &_object; +public: + FindByObject(const reg_t &object) : _object(object) {} + bool operator()(const T entry) const { + return entry->_object == _object; + } +}; + +} +#endif diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp index e61ac5dac6..40e4021e72 100644 --- a/engines/sci/graphics/palette32.cpp +++ b/engines/sci/graphics/palette32.cpp @@ -47,11 +47,10 @@ GfxPalette32::GfxPalette32(ResourceManager *resMan, GfxScreen *screen) _version(1), _versionUpdated(false) { _varyPercent = _varyTargetPercent; memset(_fadeTable, 100, sizeof(_fadeTable)); - // 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 - } +} GfxPalette32::~GfxPalette32() { unloadClut(); @@ -67,6 +66,10 @@ inline void mergePaletteInternal(Palette *const to, const Palette *const from) { } } +const Palette *GfxPalette32::getNextPalette() const { + return &_nextPalette; +} + void GfxPalette32::submit(Palette &palette) { // TODO: The resource manager in SCI32 retains raw data of palettes from // the ResourceManager (ResourceMgr) through SegManager (MemoryMgr), and @@ -206,10 +209,20 @@ int16 GfxPalette32::matchColor(const byte r, const byte g, const byte b, const i return bestIndex; } -void GfxPalette32::updateForFrame() { +bool GfxPalette32::updateForFrame() { applyAll(); _versionUpdated = false; // TODO: Implement remapping + // return g_sci->_gfxFrameout->remapAllTables(_nextPalette != _sysPalette); + return false; +} + +void GfxPalette32::updateFFrame() { + for (int i = 0; i < ARRAYSIZE(_nextPalette.colors); ++i) { + _nextPalette.colors[i] = _sourcePalette.colors[i]; + } + _versionUpdated = false; + // TODO: Implement remapping // g_sci->_gfxFrameout->remapAllTables(_nextPalette != _sysPalette); } @@ -410,7 +423,7 @@ void GfxPalette32::setVaryPercent(const int16 percent, const int time, const int } int16 GfxPalette32::getVaryPercent() const { - return abs(_varyPercent); + return ABS(_varyPercent); } void GfxPalette32::varyOff() { @@ -773,6 +786,12 @@ void GfxPalette32::applyCycles() { // Palette fading // +// NOTE: There are some game scripts (like SQ6 Sierra logo and main menu) that call +// setFade with numColorsToFade set to 256, but other parts of the engine like +// processShowStyleNone use 255 instead of 256. It is not clear if this is because +// the last palette entry is intentionally left unmodified, or if this is a bug +// in the engine. It certainly seems confused because all other places that accept +// colour ranges typically receive values in the range of 0–255. void GfxPalette32::setFade(uint8 percent, uint8 fromColor, uint16 numColorsToFade) { if (fromColor > numColorsToFade) { return; diff --git a/engines/sci/graphics/palette32.h b/engines/sci/graphics/palette32.h index 8744687e4c..9110a8a6f4 100644 --- a/engines/sci/graphics/palette32.h +++ b/engines/sci/graphics/palette32.h @@ -25,6 +25,7 @@ #include "sci/graphics/palette.h" +namespace Sci { enum PalCyclerDirection { PalCycleBackward = 0, PalCycleForward = 1 @@ -71,188 +72,206 @@ struct PalCycler { uint16 numTimesPaused; }; -namespace Sci { - class GfxPalette32 : public GfxPalette { - public: - GfxPalette32(ResourceManager *resMan, GfxScreen *screen); - ~GfxPalette32(); - - protected: - /** - * 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. - */ - uint32 _version; - - /** - * Whether or not the palette manager version was updated - * during this loop. - */ - bool _versionUpdated; - - /** - * The unmodified source palette loaded by kPalette. Additional - * palette entries may be mixed into the source palette by - * CelObj objects, which contain their own palettes. - */ - Palette _sourcePalette; - - /** - * The palette to be used when the hardware is next updated. - * On update, _nextPalette is transferred to _sysPalette. - */ - Palette _nextPalette; - - // SQ6 defines 10 cyclers - PalCycler *_cyclers[10]; - - /** - * The cycle map is used to detect overlapping cyclers. - * According to SCI engine code, when two cyclers overlap, - * a fatal error has occurred and the engine will display - * an error and then exit. - */ - bool _cycleMap[256]; - inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear); - inline void setCycleMap(uint16 fromColor, uint16 numColorsToClear); - inline PalCycler *getCycler(uint16 fromColor); - - /** - * The fade table records the expected intensity level of each pixel - * in the palette that will be displayed on the next frame. - */ - byte _fadeTable[256]; - - /** - * An optional lookup table used to remap RGB565 colors to a palette - * index. Used by Phantasmagoria 2 in 8-bit color environments. - */ - byte *_clutTable; - - /** - * An optional palette used to describe the source colors used - * in a palette vary operation. If this palette is not specified, - * sourcePalette is used instead. - */ - Palette *_varyStartPalette; - - /** - * An optional palette used to describe the target colors used - * in a palette vary operation. - */ - Palette *_varyTargetPalette; - - /** - * The minimum palette index that has been varied from the - * source palette. 0–255 - */ - uint8 _varyFromColor; - - /** - * The maximum palette index that is has been varied from the - * source palette. 0-255 - */ - uint8 _varyToColor; - - /** - * The tick at the last time the palette vary was updated. - */ - uint32 _varyLastTick; - - /** - * TODO: Document - * The velocity of change in percent? - */ - int _varyTime; - - /** - * TODO: Better documentation - * The direction of change, -1, 0, or 1. - */ - int16 _varyDirection; - - /** - * The amount, in percent, that the vary color is currently - * blended into the source color. - */ - int16 _varyPercent; - - /** - * The target amount that a vary color will be blended into - * the source color. - */ - int16 _varyTargetPercent; - - /** - * The number of time palette varying has been paused. - */ - uint16 _varyNumTimesPaused; - - /** - * 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); - - public: - virtual void saveLoadWithSerializer(Common::Serializer &s) override; - - 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); - - void updateForFrame(); - void updateHardware(); - void applyAll(); - - bool loadClut(uint16 clutId); - byte matchClutColor(uint16 color); - void unloadClut(); - - void kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int time, const int16 fromColor, const int16 toColor); - void kernelPalVaryMergeTarget(const GuiResourceId paletteId); - void kernelPalVarySetTarget(const GuiResourceId paletteId); - void kernelPalVarySetStart(const GuiResourceId paletteId); - void kernelPalVaryMergeStart(const GuiResourceId paletteId); - virtual void kernelPalVaryPause(bool pause) override; - - 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); - int16 getVaryPercent() const; - void varyOff(); - void mergeTarget(const Palette *const palette); - void varyPause(); - void varyOn(); - void setVaryTime(const int time); - void setTarget(const Palette *const palette); - void setStart(const Palette *const palette); - void mergeStart(const Palette *const palette); - private: - bool createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const; - Palette getPaletteFromResourceInternal(const GuiResourceId paletteId) const; - void setVaryTimeInternal(const int16 percent, const int time); - public: - void applyVary(); - - void setCycle(const uint8 fromColor, const uint8 toColor, const int16 direction, const int16 delay); - void doCycle(const uint8 fromColor, const int16 speed); - void cycleOn(const uint8 fromColor); - void cyclePause(const uint8 fromColor); - void cycleAllOn(); - void cycleAllPause(); - void cycleOff(const uint8 fromColor); - void cycleAllOff(); - void applyAllCycles(); - void applyCycles(); - - void setFade(const uint8 percent, const uint8 fromColor, const uint16 toColor); - void fadeOff(); - void applyFade(); - }; +class GfxPalette32 : public GfxPalette { +public: + GfxPalette32(ResourceManager *resMan, GfxScreen *screen); + ~GfxPalette32(); + +private: + // NOTE: currentPalette in SCI engine is called _sysPalette + // here. + + /** + * 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. + */ + uint32 _version; + + /** + * Whether or not the palette manager version was updated + * during this loop. + */ + bool _versionUpdated; + + /** + * The unmodified source palette loaded by kPalette. Additional + * palette entries may be mixed into the source palette by + * CelObj objects, which contain their own palettes. + */ + Palette _sourcePalette; + + /** + * The palette to be used when the hardware is next updated. + * On update, _nextPalette is transferred to _sysPalette. + */ + Palette _nextPalette; + + bool createPaletteFromResourceInternal(const GuiResourceId paletteId, Palette *const out) const; + Palette getPaletteFromResourceInternal(const GuiResourceId paletteId) const; + +public: + virtual void saveLoadWithSerializer(Common::Serializer &s) override; + const Palette *getNextPalette() const; + + 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); + + /** + * 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); + + bool updateForFrame(); + void updateFFrame(); + void updateHardware(); + void applyAll(); + +#pragma mark - +#pragma mark Colour look-up +private: + /** + * An optional lookup table used to remap RGB565 colors to a palette + * index. Used by Phantasmagoria 2 in 8-bit color environments. + */ + byte *_clutTable; + +public: + bool loadClut(uint16 clutId); + byte matchClutColor(uint16 color); + void unloadClut(); + +#pragma mark - +#pragma mark Varying +private: + /** + * An optional palette used to describe the source colors used + * in a palette vary operation. If this palette is not specified, + * sourcePalette is used instead. + */ + Palette *_varyStartPalette; + + /** + * An optional palette used to describe the target colors used + * in a palette vary operation. + */ + Palette *_varyTargetPalette; + + /** + * The minimum palette index that has been varied from the + * source palette. 0–255 + */ + uint8 _varyFromColor; + + /** + * The maximum palette index that is has been varied from the + * source palette. 0-255 + */ + uint8 _varyToColor; + + /** + * The tick at the last time the palette vary was updated. + */ + uint32 _varyLastTick; + + /** + * The amount of time to elapse, in ticks, between each cycle + * of a palette vary animation. + */ + int _varyTime; + + /** + * The direction of change: -1, 0, or 1. + */ + int16 _varyDirection; + + /** + * The amount, in percent, that the vary color is currently + * blended into the source color. + */ + int16 _varyPercent; + + /** + * The target amount that a vary color will be blended into + * the source color. + */ + int16 _varyTargetPercent; + + /** + * The number of time palette varying has been paused. + */ + uint16 _varyNumTimesPaused; + +public: + void kernelPalVarySet(const GuiResourceId paletteId, const int16 percent, const int time, const int16 fromColor, const int16 toColor); + void kernelPalVaryMergeTarget(const GuiResourceId paletteId); + void kernelPalVarySetTarget(const GuiResourceId paletteId); + void kernelPalVarySetStart(const GuiResourceId paletteId); + void kernelPalVaryMergeStart(const GuiResourceId paletteId); + virtual void kernelPalVaryPause(bool pause) override; + + 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); + int16 getVaryPercent() const; + void varyOff(); + void mergeTarget(const Palette *const palette); + void varyPause(); + void varyOn(); + void setVaryTime(const int time); + void setTarget(const Palette *const palette); + void setStart(const Palette *const palette); + void mergeStart(const Palette *const palette); + void setVaryTimeInternal(const int16 percent, const int time); + void applyVary(); + +#pragma mark - +#pragma mark Cycling +private: + // SQ6 defines 10 cyclers + PalCycler *_cyclers[10]; + + /** + * The cycle map is used to detect overlapping cyclers. + * According to SCI engine code, when two cyclers overlap, + * a fatal error has occurred and the engine will display + * an error and then exit. + */ + bool _cycleMap[256]; + inline void clearCycleMap(uint16 fromColor, uint16 numColorsToClear); + inline void setCycleMap(uint16 fromColor, uint16 numColorsToClear); + inline PalCycler *getCycler(uint16 fromColor); + +public: + void setCycle(const uint8 fromColor, const uint8 toColor, const int16 direction, const int16 delay); + void doCycle(const uint8 fromColor, const int16 speed); + void cycleOn(const uint8 fromColor); + void cyclePause(const uint8 fromColor); + void cycleAllOn(); + void cycleAllPause(); + void cycleOff(const uint8 fromColor); + void cycleAllOff(); + void applyAllCycles(); + void applyCycles(); + +#pragma mark - +#pragma mark Fading +private: + /** + * The fade table records the expected intensity level of each pixel + * in the palette that will be displayed on the next frame. + */ + byte _fadeTable[256]; + +public: + void setFade(const uint8 percent, const uint8 fromColor, const uint16 toColor); + void fadeOff(); + void applyFade(); +}; } #endif diff --git a/engines/sci/graphics/picture.cpp b/engines/sci/graphics/picture.cpp index d7ef84dc1e..2eab391afd 100644 --- a/engines/sci/graphics/picture.cpp +++ b/engines/sci/graphics/picture.cpp @@ -209,12 +209,12 @@ void GfxPicture::drawSci32Vga(int16 celNo, int16 drawX, int16 drawY, int16 pictu } // Header - // [headerSize:WORD] [celCount:BYTE] [Unknown:BYTE] [Unknown:WORD] [paletteOffset:DWORD] [Unknown:DWORD] + // 0[headerSize:WORD] 2[celCount:BYTE] 3[Unknown:BYTE] 4[celHeaderSize:WORD] 6[paletteOffset:DWORD] 10[Unknown:WORD] 12[Unknown:WORD] // cel-header follow afterwards, each is 42 bytes // Cel-Header - // [width:WORD] [height:WORD] [displaceX:WORD] [displaceY:WORD] [clearColor:BYTE] [compressed:BYTE] + // 0[width:WORD] 2[height:WORD] 4[displaceX:WORD] 6[displaceY:WORD] 8[clearColor:BYTE] 9[compressed:BYTE] // offset 10-23 is unknown - // [rleOffset:DWORD] [literalOffset:DWORD] [Unknown:WORD] [Unknown:WORD] [priority:WORD] [relativeXpos:WORD] [relativeYpos:WORD] + // 24[rleOffset:DWORD] 28[literalOffset:DWORD] 32[Unknown:WORD] 34[Unknown:WORD] 36[priority:WORD] 38[relativeXpos:WORD] 40[relativeYpos:WORD] cel_headerPos += 42 * celNo; diff --git a/engines/sci/graphics/picture.h b/engines/sci/graphics/picture.h index 2404f99b41..942fa0f107 100644 --- a/engines/sci/graphics/picture.h +++ b/engines/sci/graphics/picture.h @@ -38,6 +38,9 @@ enum { class GfxPorts; class GfxScreen; class GfxPalette; +class GfxCoordAdjuster; +class ResourceManager; +class Resource; /** * Picture class, handles loading and displaying of picture resources diff --git a/engines/sci/graphics/plane32.cpp b/engines/sci/graphics/plane32.cpp new file mode 100644 index 0000000000..548e7e9d17 --- /dev/null +++ b/engines/sci/graphics/plane32.cpp @@ -0,0 +1,841 @@ +/* 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/console.h" +#include "sci/engine/kernel.h" +#include "sci/engine/selector.h" +#include "sci/engine/state.h" +#include "sci/graphics/frameout.h" +#include "sci/graphics/lists32.h" +#include "sci/graphics/plane32.h" +#include "sci/graphics/screen.h" +#include "sci/graphics/screen_item32.h" + +namespace Sci { +#pragma mark DrawList +void DrawList::add(ScreenItem *screenItem, const Common::Rect &rect) { + DrawItem *drawItem = new DrawItem; + drawItem->screenItem = screenItem; + drawItem->rect = rect; + DrawListBase::add(drawItem); +} + +#pragma mark - +#pragma mark Plane +uint16 Plane::_nextObjectId = 20000; + +Plane::Plane(const Common::Rect &gameRect) : +_gameRect(gameRect), +_object(make_reg(0, _nextObjectId++)), +_back(0), +_pictureId(kPlanePicColored), +_mirrored(false), +_width(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth), +_height(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight), +_deleted(0), +_updated(0), +_priorityChanged(0), +_created(g_sci->_gfxFrameout->getScreenCount()), +_redrawAllCount(g_sci->_gfxFrameout->getScreenCount()) { + convertGameRectToPlaneRect(); + _priority = MAX(10000, g_sci->_gfxFrameout->getPlanes().getTopPlanePriority() + 1); + setType(); + _screenRect = _planeRect; +} + +Plane::Plane(reg_t object) : +_object(object), +_width(g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth), +_height(g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight), +_created(g_sci->_gfxFrameout->getScreenCount()), +_redrawAllCount(g_sci->_gfxFrameout->getScreenCount()), +_deleted(0), +_updated(0), +_priorityChanged(false), +_moved(0) { + SegManager *segMan = g_sci->getEngineState()->_segMan; + _vanishingPoint.x = readSelectorValue(segMan, object, SELECTOR(vanishingX)); + _vanishingPoint.y = readSelectorValue(segMan, object, SELECTOR(vanishingY)); + + _gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft)); + _gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop)); + _gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)); + _gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)); + convertGameRectToPlaneRect(); + + _back = readSelectorValue(segMan, object, SELECTOR(back)); + _priority = readSelectorValue(segMan, object, SELECTOR(priority)); + _pictureId = readSelectorValue(segMan, object, SELECTOR(picture)); + setType(); + + _mirrored = readSelectorValue(segMan, object, SELECTOR(mirrored)); + _screenRect = _planeRect; + changePic(); +} + +Plane::Plane(const Plane &other) : +_object(other._object), +_priority(other._priority), +_pictureId(other._pictureId), +_mirrored(other._mirrored), +_back(other._back), +_field_34(other._field_34), _field_38(other._field_38), +_field_3C(other._field_3C), _field_40(other._field_40), +_planeRect(other._planeRect), +_gameRect(other._gameRect), +_screenRect(other._screenRect), +_screenItemList(other._screenItemList) {} + +void Plane::operator=(const Plane &other) { + _gameRect = other._gameRect; + _planeRect = other._planeRect; + _vanishingPoint = other._vanishingPoint; + _pictureId = other._pictureId; + _type = other._type; + _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; +} + +void Plane::init() { + _nextObjectId = 20000; +} + +void Plane::convertGameRectToPlaneRect() { + 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; + + const Ratio ratioX = Ratio(screenWidth, scriptWidth); + const Ratio ratioY = Ratio(screenHeight, scriptHeight); + + _planeRect = _gameRect; + mulru(_planeRect, ratioX, ratioY, 1); +} + +void Plane::printDebugInfo(Console *con) const { + Common::String name; + + if (_object.isNumber()) { + name = "-scummvm-"; + } else { + name = g_sci->getEngineState()->_segMan->getObjectName(_object); + } + + con->debugPrintf("%04x:%04x (%s): type %d, prio %d, pic %d, mirror %d, back %d\n", + PRINT_REG(_object), + name.c_str(), + _type, + _priority, + _pictureId, + _mirrored, + _back + ); + con->debugPrintf(" game rect: (%d, %d, %d, %d), plane rect: (%d, %d, %d, %d)\n screen rect: (%d, %d, %d, %d)\n", + PRINT_RECT(_gameRect), + PRINT_RECT(_planeRect), + PRINT_RECT(_screenRect) + ); + con->debugPrintf(" # screen items: %d\n", _screenItemList.size()); +} + +#pragma mark - +#pragma mark Plane - Pic + +void Plane::addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX) { + + uint16 celCount = 1000; + for (uint16 celNo = 0; celNo < celCount; ++celNo) { + CelObjPic *celObj = new CelObjPic(pictureId, celNo); + if (celCount == 1000) { + celCount = celObj->_celCount; + } + + ScreenItem *screenItem = new ScreenItem(_object, celObj->_info); + screenItem->_pictureId = pictureId; + screenItem->_mirrorX = mirrorX; + screenItem->_priority = celObj->_priority; + screenItem->_fixPriority = true; + if (position != nullptr) { + screenItem->_position = *position; + } else { + screenItem->_position = celObj->_relativePosition; + } + _screenItemList.add(screenItem); + + delete screenItem->_celObj; + screenItem->_celObj = celObj; + } +} + +void Plane::addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX) { + deletePic(pictureId); + addPicInternal(pictureId, &position, mirrorX); + // NOTE: In SCI engine this method returned the pictureId of the + // plane, but this return value was never used +} + +void Plane::changePic() { + _pictureChanged = false; + + if (_type != kPlaneTypePicture) { + return; + } + + addPicInternal(_pictureId, nullptr, _mirrored); +} + +void Plane::deletePic(const GuiResourceId pictureId) { + for (ScreenItemList::iterator it = _screenItemList.begin(); it != _screenItemList.end(); ++it) { + ScreenItem *screenItem = *it; + if (screenItem->_pictureId == pictureId) { + screenItem->_created = 0; + screenItem->_updated = 0; + screenItem->_deleted = g_sci->_gfxFrameout->getScreenCount(); + } + } +} + +void Plane::deletePic(const GuiResourceId oldPictureId, const GuiResourceId newPictureId) { + deletePic(oldPictureId); + _pictureId = newPictureId; +} + +void Plane::deleteAllPics() { + for (ScreenItemList::iterator it = _screenItemList.begin(); it != _screenItemList.end(); ++it) { + ScreenItem *screenItem = *it; + if (screenItem != nullptr && screenItem->_celInfo.type == kCelTypePic) { + if (screenItem->_created == 0) { + screenItem->_created = 0; + screenItem->_updated = 0; + screenItem->_deleted = g_sci->_gfxFrameout->getScreenCount(); + } else { + _screenItemList.erase(it); + } + } + } + + _screenItemList.pack(); +} + +#pragma mark - +#pragma mark Plane - Rendering + +void Plane::breakDrawListByPlanes(DrawList &drawList, const PlaneList &planeList) const { + int index = planeList.findIndexByObject(_object); + + 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]); + } + + drawList.erase_at(i); + break; + } + } + } + } + drawList.pack(); +} + +void Plane::breakEraseListByPlanes(RectList &eraseList, const PlaneList &planeList) const { + int index = planeList.findIndexByObject(_object); + + 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]); + } + + eraseList.erase_at(i); + break; + } + } + } + } + eraseList.pack(); +} + +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(); + + for (PlaneList::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. + if (i < visiblePlaneItemCount) { + vitem = 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) { + if (!vitem->_screenRect.isEmpty()) { + if (/* TODO: g_Remap_numActiveRemaps */ false) { // active remaps? + mergeToRectList(vitem->_screenRect, eraseList); + } else { + eraseList.add(vitem->_screenRect); + } + } + } + } else if (item->_created) { + // add item to draw list + item->getCelObj(); + item->calcRects(*this); + + if(!item->_screenRect.isEmpty()) { + if (/* TODO: g_Remap_numActiveRemaps */ false) { // active remaps? + 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->getCelObj(); + item->calcRects(*this); + if (/* TODO: g_Remap_numActiveRemaps */ false) { // active remaps + // 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 + + // TODO: This was changed from disasm, verify please! + Common::Rect extendedScreenItem = vitem->_screenRect; + extendedScreenItem.extend(item->_screenRect); + drawList.add(item, item->_screenRect); + mergeToRectList(extendedScreenItem, 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); + } + if (i < visiblePlaneItemCount && vitem != nullptr && !vitem->_screenRect.isEmpty()) { + eraseList.add(vitem->_screenRect); + } + } + } + } + } + + breakEraseListByPlanes(eraseList, planeList); + breakDrawListByPlanes(drawList, planeList); + + if (/* TODO: dword_C6288 */ false) { // "high resolution pictures"???? + _screenItemList.sort(); + bool encounteredPic = false; + bool v81 = false; + + for (RectList::size_type i = 0; i < eraseList.size(); ++i) { + Common::Rect *rect = eraseList[i]; + + for (ScreenItemList::size_type j = 0; j < _screenItemList.size(); ++j) { + ScreenItem *item = _screenItemList[j]; + + if (j < _screenItemList.size() && item != nullptr) { + if (rect->intersects(item->_screenRect)) { + 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; + } + } else { + if (!item->_updated && !item->_created) { + drawList.add(item, intersection); + } + if (item->_celInfo.type == kCelTypePic) { + encounteredPic = true; + } + } + } + } + } + } + } + + _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) { + ScreenItem *item = _screenItemList[j]; + if (j < _screenItemList.size() && item != nullptr && !item->_updated && !item->_deleted && !item->_created && eraseList[i]->intersects(item->_screenRect)) { + drawList.add(item, eraseList[i]->findIntersectingRect(item->_screenRect)); + } + } + } + } + if (/* TODO: g_Remap_numActiveRemaps */ false) { // no remaps active? + // Add all items that overlap with items in the drawlist and have higher + // priority + for (DrawList::size_type i = 0; i < drawList.size(); ++i) { + DrawItem *dli = drawList[i]; + + for (PlaneList::size_type j = 0; j < planeItemCount; ++j) { + ScreenItem *sli = _screenItemList[j]; + + if (i < drawList.size() && dli) { + if (j < _screenItemList.size() && sli) { + if (!sli->_updated && !sli->_deleted && !sli->_created) { + ScreenItem *item = dli->screenItem; + if (sli->_priority > item->_priority || (sli->_priority == item->_priority && sli->_object > item->_object)) { + if (dli->rect.intersects(sli->_screenRect)) { + drawList.add(sli, dli->rect.findIntersectingRect(sli->_screenRect)); + } + } + } + } + } + } + } + } + + 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) { + ScreenItem *item = _screenItemList[i]; + + if (item != nullptr) { + // update item in visiblePlane if item is updated + if ( + item->_updated || + ( + forceUpdate && + visiblePlane != nullptr && + Common::find(visiblePlane->_screenItemList.begin(), visiblePlane->_screenItemList.end(), item) != visiblePlane->_screenItemList.end() + ) + ) { + *visiblePlane->_screenItemList[i] = *_screenItemList[i]; + } + + if (item->_updated) { + item->_updated--; + } + + // create new item in visiblePlane if item was added + if (item->_created) { + item->_created--; + if (visiblePlane != nullptr) { + ScreenItem *n = new ScreenItem(*item); + visiblePlane->_screenItemList.add(n); + } + } + + // delete item from both planes if it was deleted + if (item->_deleted) { + item->_deleted--; + if (!item->_deleted) { + visiblePlane->_screenItemList.erase_at(i); + _screenItemList.erase_at(i); + } + } + } + } +} + +void Plane::filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const { + if (_type == kPlaneTypeTransparent) { + for (RectList::size_type i = 0; i < transparentEraseList.size(); ++i) { + 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); + } + } + } + } + } 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); + } + } + } + + Common::Rect ptr[4]; + Common::Rect *r2 = transparentEraseList[i]; + int count = splitRects(*r2, *r, ptr); + for (int k = count - 1; k >= 0; --k) { + transparentEraseList.add(ptr[k]); + } + transparentEraseList.erase_at(i); + } + } + + transparentEraseList.pack(); + } +} + +void Plane::filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const { + for (DrawList::size_type i = 0; i < drawList.size(); ++i) { + 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::filterUpEraseRects(DrawList &drawList, RectList &eraseList) const { + for (RectList::size_type i = 0; i < eraseList.size(); ++i) { + 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::mergeToDrawList(const DrawList::size_type index, const Common::Rect &rect, DrawList &drawList) const { + RectList rects; + + Common::Rect r = _screenItemList[index]->_screenRect; + r.clip(rect); + + rects.add(r); + ScreenItem *item = _screenItemList[index]; + + for (RectList::size_type i = 0; i < rects.size(); ++i) { + r = *rects[i]; + + for (DrawList::size_type j = 0; j < drawList.size(); ++j) { + DrawItem *drawitem = drawList[j]; + if (item->_object == drawitem->screenItem->_object) { + if (drawitem->rect.contains(r)) { + rects.erase_at(i); + break; + } + + Common::Rect outRects[4]; + int count = splitRects(r, drawitem->rect, outRects); + if (count != -1) { + for (int k = count - 1; k >= 0; --k) { + rects.add(outRects[k]); + } + + rects.erase_at(i); + + // proceed to the next rect + r = *rects[++i]; + } + } + } + } + + rects.pack(); + + for (RectList::size_type i = 0; i < rects.size(); ++i) { + drawList.add(item, *rects[i]); + } +} + +void Plane::mergeToRectList(const Common::Rect &rect, RectList &rectList) const { + RectList temp; + temp.add(rect); + + for (RectList::size_type i = 0; i < temp.size(); ++i) { + Common::Rect *outerRect = temp[i]; + for (RectList::size_type j = 0; j < rectList.size(); ++j) { + Common::Rect *innerRect = rectList[i]; + if (innerRect->intersects(*outerRect)) { + Common::Rect out[4]; + int count = splitRects(*outerRect, *innerRect, out); + for (int k = count - 1; k >= 0; --k) { + temp.add(out[k]); + } + temp.erase_at(i); + } else { + temp.erase_at(i); + } + } + } + + temp.pack(); + + for (RectList::size_type i = 0; i < temp.size(); ++i) { + rectList.add(*temp[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.getCelObj(); + screenItem.calcRects(*this); + if (!screenItem._screenRect.isEmpty()) { + drawList.add(&screenItem, screenItem._screenRect); + } + } + } + } + + eraseList.clear(); + + if (!_screenRect.isEmpty() && _type != kPlaneTypePicture && _type != kPlaneTypeOpaque) { + eraseList.add(_screenRect); + } + breakEraseListByPlanes(eraseList, planeList); + 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) { + _type = kPlaneTypeColored; + } else { + _type = kPlaneTypePicture; + } +} + +void Plane::sync(const Plane *other, const Common::Rect &screenRect) { + if (other == nullptr) { + if (_pictureChanged) { + deleteAllPics(); + setType(); + changePic(); + _redrawAllCount = g_sci->_gfxFrameout->getScreenCount(); + } else { + setType(); + } + } else { + if ( + _planeRect.top != other->_planeRect.top || + _planeRect.left != other->_planeRect.left || + _planeRect.right > other->_planeRect.right || + _planeRect.bottom > other->_planeRect.bottom + ) { + _redrawAllCount = g_sci->_gfxFrameout->getScreenCount(); + _updated = g_sci->_gfxFrameout->getScreenCount(); + } else if (_planeRect != other->_planeRect) { + _updated = g_sci->_gfxFrameout->getScreenCount(); + } + + if (_priority != other->_priority) { + _priorityChanged = g_sci->_gfxFrameout->getScreenCount(); + } + + if (_pictureId != other->_pictureId || _mirrored != other->_mirrored || _pictureChanged) { + deleteAllPics(); + setType(); + changePic(); + _redrawAllCount = g_sci->_gfxFrameout->getScreenCount(); + } + + if (_back != other->_back) { + _redrawAllCount = g_sci->_gfxFrameout->getScreenCount(); + } + } + + _deleted = 0; + if (_created == 0) { + _moved = 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 + clipScreenRect(screenRect); +} + +void Plane::update(const reg_t object) { + SegManager *segMan = g_sci->getEngineState()->_segMan; + _vanishingPoint.x = readSelectorValue(segMan, object, SELECTOR(vanishingX)); + _vanishingPoint.y = readSelectorValue(segMan, object, SELECTOR(vanishingY)); + _gameRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft)); + _gameRect.top = readSelectorValue(segMan, object, SELECTOR(inTop)); + _gameRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)); + _gameRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)); + convertGameRectToPlaneRect(); + + _priority = readSelectorValue(segMan, object, SELECTOR(priority)); + GuiResourceId pictureId = readSelectorValue(segMan, object, SELECTOR(picture)); + if (_pictureId != pictureId) { + _pictureId = pictureId; + _pictureChanged = true; + } + + _mirrored = readSelectorValue(segMan, object, SELECTOR(mirrored)); + _back = readSelectorValue(segMan, object, SELECTOR(back)); +} + +#pragma mark - +#pragma mark PlaneList +void PlaneList::clear() { + for (iterator it = begin(); it != end(); ++it) { + delete *it; + } + + PlaneListBase::clear(); +} + +void PlaneList::erase(Plane *plane) { + for (iterator it = begin(); it != end(); ++it) { + if (*it == plane) { + erase(it); + break; + } + } +} + +int PlaneList::findIndexByObject(const reg_t object) const { + for (size_type i = 0; i < size(); ++i) { + if ((*this)[i] != nullptr && (*this)[i]->_object == object) { + return i; + } + } + + return -1; +} + +Plane *PlaneList::findByObject(const reg_t object) const { + const_iterator planeIt = Common::find_if(begin(), end(), FindByObject(object)); + + if (planeIt == end()) { + return nullptr; + } + + return *planeIt; +} + +int16 PlaneList::getTopPlanePriority() const { + if (size() > 0) { + return (*this)[size() - 1]->_priority; + } + + return 0; +} + +int16 PlaneList::getTopSciPlanePriority() const { + int16 priority = 0; + + for (const_iterator it = begin(); it != end(); ++it) { + if ((*it)->_priority >= 10000) { + break; + } + + priority = (*it)->_priority; + } + + return priority; +} + +void PlaneList::add(Plane *plane) { + for (iterator it = begin(); it != end(); ++it) { + if ((*it)->_priority < plane->_priority) { + insert(it, plane); + return; + } + } + + push_back(plane); +} + +} diff --git a/engines/sci/graphics/plane32.h b/engines/sci/graphics/plane32.h new file mode 100644 index 0000000000..a68700a031 --- /dev/null +++ b/engines/sci/graphics/plane32.h @@ -0,0 +1,465 @@ +/* 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_PLANE32_H +#define SCI_GRAPHICS_PLANE32_H + +#include "common/array.h" +#include "common/rect.h" +#include "sci/engine/vm_types.h" +#include "sci/graphics/helpers.h" +#include "sci/graphics/lists32.h" +#include "sci/graphics/screen_item32.h" + +namespace Sci { +enum PlaneType { + kPlaneTypeColored = 0, + kPlaneTypePicture = 1, + kPlaneTypeTransparent = 2, + kPlaneTypeOpaque = 3 +}; + +enum PlanePictureCodes { + // NOTE: Any value at or below 65532 means the plane + // is a kPlaneTypePicture. + kPlanePic = 65532, + kPlanePicOpaque = 65533, + kPlanePicTransparent = 65534, + kPlanePicColored = 65535 +}; + +#pragma mark - +#pragma mark RectList + +typedef StablePointerArray RectListBase; +class RectList : public RectListBase { +public: + void add(const Common::Rect &rect) { + RectListBase::add(new Common::Rect(rect)); + } +}; + +#pragma mark - +#pragma mark DrawList + +struct DrawItem { + ScreenItem *screenItem; + Common::Rect rect; + + inline bool operator<(const DrawItem &other) const { + return *screenItem < *other.screenItem; + } +}; + +typedef StablePointerArray DrawListBase; +class DrawList : public DrawListBase { +private: + inline static bool sortHelper(const DrawItem *a, const DrawItem *b) { + return *a < *b; + } +public: + void add(ScreenItem *screenItem, const Common::Rect &rect); + inline void sort() { + pack(); + Common::sort(begin(), end(), sortHelper); + } +}; + +class PlaneList; + +#pragma mark - +#pragma mark Plane + +/** + * A plane is a grouped layer of screen items. + */ +class Plane { +private: + /** + * A serial used for planes that are generated inside + * the graphics engine, rather than the interpreter. + */ + 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 + * PlanePictureCodes, in which case the plane becomes a + * non-picture plane. + */ + GuiResourceId _pictureId; + + /** + * Whether or not the contents of picture planes should + * be drawn horizontally mirrored. Only applies to + * planes of type kPlaneTypePicture. + */ + bool _mirrored; + + /** + * Whether the picture ID for this plane has changed. + * This flag is set when the plane is created or updated + * from a VM object, and is cleared when the plane is + * synchronised to another plane (which calls + * changePic). + */ + 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 + * render content to the screen. Coordinates with + * remainders are rounded up to the next whole pixel. + */ + void convertGameRectToPlaneRect(); + + /** + * Sets the type of the plane according to its assigned + * picture resource ID. + */ + void setType(); + +public: + /** + * The colour to use when erasing the plane. Only + * applies to planes of type kPlaneTypeColored. + */ + byte _back; + + /** + * Whether the priority of this plane has changed. + * This flag is set when the plane is updated from + * another plane and cleared when draw list calculation + * occurs. + */ + int _priorityChanged; // ? + + /** + * A handle to the VM object corresponding to this + * plane. Some planes are generated purely within the + * graphics engine and have a numeric object value. + */ + reg_t _object; + + /** + * The rendering priority of the plane. Higher + * priorities are drawn above lower priorities. + */ + int16 _priority; + + /** + * TODO: Document + */ + int _redrawAllCount; + + PlaneType _type; + + /** + * Flags indicating the state of the plane. + * - `created` is set when the plane is first created, + * either from a VM object or from within the engine + * itself + * - `updated` is set when the plane is updated from + * another plane and the two planes' `planeRect`s do + * 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 + */ + 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. + */ + Common::Point _vanishingPoint; + + /** + * The position & dimensions of the plane in screen + * coordinates. This rect is not clipped to the screen, + * so may include coordinates that are offscreen. + */ + Common::Rect _planeRect; + + /** + * The position & dimensions of the plane in game script + * coordinates. + */ + Common::Rect _gameRect; + + /** + * The position & dimensions of the plane in screen + * coordinates. This rect is clipped to the screen. + */ + Common::Rect _screenRect; + + /** + * The list of screen items grouped within this plane. + */ + ScreenItemList _screenItemList; + +public: + /** + * Initialises static Plane members. + */ + static void init(); + + Plane(const Common::Rect &gameRect); + Plane(const reg_t object); + Plane(const Plane &other); + void operator=(const Plane &other); + inline bool operator<(const Plane &other) const { + // TODO: In SCI engine, _object is actually a uint16 and can either + // contain a MemID (a handle to MemoryMgr, similar to reg_t) or + // a serial (Plane::_nextObjectId). These numbers can be compared + // directly in the real engine and the lowest MemID wins, but in + // ScummVM reg_t pointers are not comparable so we have to use a + // different strategy when two planes generated by scripts conflict. + // For now we just don't check if the priority is below 0, since + // that priority is used to represent hidden planes and is guaranteed + // to generate conflicts with script-generated planes. If there are + // other future conflicts with script-generated planes then we need + // to come up with a solution that works, similar to + // reg_t::pointerComparisonWithInteger used by SCI16. + return _priority < other._priority || (_priority == other._priority && _priority > -1 && _object < other._object); + } + + /** + * Clips the screen rect of this plane to fit within the + * given screen rect. + */ + inline void clipScreenRect(const Common::Rect &screenRect) { + if (_screenRect.intersects(screenRect)) { + _screenRect.clip(screenRect); + } else { + _screenRect.left = 0; + _screenRect.top = 0; + _screenRect.right = 0; + _screenRect.bottom = 0; + } + } + + void printDebugInfo(Console *con) const; + + /** + * Compares the properties of the current plane against + * the properties of the `other` plane (which is the + * corresponding plane from the visible plane list) to + * discover which properties have been changed on this + * plane by a call to `update(reg_t)`. + * + * @note This method was originally called UpdatePlane + * in SCI engine. + */ + void sync(const Plane *other, const Common::Rect &screenRect); + + /** + * Updates the plane to match the state of the plane + * object from the virtual machine. + * + * @note This method was originally called UpdatePlane + * in SCI engine. + */ + void update(const reg_t object); + +#pragma mark - +#pragma mark Plane - Pic +private: + /** + * Adds all cels from the specified picture resource to + * the plane as screen items. If a position is provided, + * the screen items will be given that position; + * otherwise, the default relative positions for each + * cel will be taken from the picture resource data. + */ + inline void addPicInternal(const GuiResourceId pictureId, const Common::Point *position, const bool mirrorX); + + /** + * If the plane is a picture plane, re-adds all cels + * from its picture resource to the plane. + */ + void changePic(); + + /** + * Marks all screen items to be deleted that are within + * this plane and match the given picture ID. + */ + void deletePic(const GuiResourceId pictureId); + + /** + * Marks all screen items to be deleted that are within + * this plane and match the given picture ID, then sets + * the picture ID of the plane to the new picture ID + * without adding any screen items. + */ + void deletePic(const GuiResourceId oldPictureId, const GuiResourceId newPictureId); + + /** + * Marks all screen items to be deleted that are within + * this plane and are picture cels. + */ + void deleteAllPics(); + +public: + /** + * Marks all existing screen items matching the current + * picture to be deleted, then adds all cels from the + * new picture resource to the plane at the given + * position. + */ + void addPic(const GuiResourceId pictureId, const Common::Point &position, const bool mirrorX); + +#pragma mark - +#pragma mark Plane - Rendering +private: + /** + * Splits all rects in the given draw list at the edges + * of all non-transparent planes above the current + * plane. + */ + 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. + */ + 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 + */ + 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 + */ + void mergeToRectList(const Common::Rect &rect, RectList &rectList) const; + +public: + /** + * Calculates the location and dimensions of dirty rects + * of the screen items in this plane and adds them to + * the given draw and erase lists, and synchronises this + * plane's list of screen items to the given visible + * plane. + */ + void calcLists(Plane &visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList); + + /** + * TODO: Documentation + */ + void filterDownEraseRects(DrawList &drawList, RectList &eraseList, RectList &transparentEraseList) const; + + /** + * TODO: Documentation + */ + void filterUpEraseRects(DrawList &drawList, RectList &eraseList) const; + + /** + * TODO: Documentation + */ + void filterUpDrawRects(DrawList &transparentDrawList, const DrawList &drawList) const; + + /** + * Updates all of the plane's non-deleted screen items + * and adds them to the given draw and erase lists. + */ + void redrawAll(Plane *visiblePlane, const PlaneList &planeList, DrawList &drawList, RectList &eraseList); +}; + +#pragma mark - +#pragma mark PlaneList + +typedef Common::Array PlaneListBase; +class PlaneList : public PlaneListBase { +private: + inline static bool sortHelper(const Plane *a, const Plane *b) { + return *a < *b; + } + + using PlaneListBase::push_back; + +public: + // A method for finding the index of a plane inside a + // PlaneList is used because entries in the main plane + // list and visible plane list of GfxFrameout are + // synchronised by index + int findIndexByObject(const reg_t object) const; + Plane *findByObject(const reg_t object) const; + + /** + * Gets the priority of the top plane in the plane list. + */ + int16 getTopPlanePriority() const; + + /** + * Gets the priority of the top plane in the plane list + * created by a game script. + */ + int16 getTopSciPlanePriority() const; + + void add(Plane *plane); + void clear(); + using PlaneListBase::erase; + void erase(Plane *plane); + inline void sort() { + Common::sort(begin(), end(), sortHelper); + } +}; + +} + +#endif diff --git a/engines/sci/graphics/screen.h b/engines/sci/graphics/screen.h index 49c63d4681..65416252f6 100644 --- a/engines/sci/graphics/screen.h +++ b/engines/sci/graphics/screen.h @@ -76,6 +76,11 @@ public: byte getColorWhite() { return _colorWhite; } byte getColorDefaultVectorData() { return _colorDefaultVectorData; } +#ifdef ENABLE_SCI32 + byte *getDisplayScreen() { return _displayScreen; } + byte *getPriorityScreen() { return _priorityScreen; } +#endif + void clearForRestoreGame(); void copyToScreen(); void copyFromScreen(byte *buffer); diff --git a/engines/sci/graphics/screen_item32.cpp b/engines/sci/graphics/screen_item32.cpp new file mode 100644 index 0000000000..300912f110 --- /dev/null +++ b/engines/sci/graphics/screen_item32.cpp @@ -0,0 +1,534 @@ +/* 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/console.h" +#include "sci/resource.h" +#include "sci/engine/kernel.h" +#include "sci/engine/selector.h" +#include "sci/engine/state.h" +#include "sci/graphics/celobj32.h" +#include "sci/graphics/frameout.h" +#include "sci/graphics/screen_item32.h" +#include "sci/graphics/view.h" + +namespace Sci { +#pragma mark ScreenItem + +uint16 ScreenItem::_nextObjectId = 20000; + +ScreenItem::ScreenItem(const reg_t object) : +_mirrorX(false), +_pictureId(-1), +_celObj(nullptr), +_object(object), +_deleted(0), +_updated(0), +_created(g_sci->_gfxFrameout->getScreenCount()) { + SegManager *segMan = g_sci->getEngineState()->_segMan; + + setFromObject(segMan, object, true, true); + _plane = readSelector(segMan, object, SELECTOR(plane)); +} + +ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo) : +_position(0, 0), +_z(0), +_object(make_reg(0, _nextObjectId++)), +_celInfo(celInfo), +_plane(plane), +_celObj(nullptr), +_useInsetRect(false), +_fixPriority(false), +_mirrorX(false), +_pictureId(-1), +_updated(0), +_deleted(0), +_created(g_sci->_gfxFrameout->getScreenCount()) {} + +ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect) : +_position(rect.left, rect.top), +_z(0), +_object(make_reg(0, _nextObjectId++)), +_celInfo(celInfo), +_plane(plane), +_celObj(nullptr), +_useInsetRect(false), +_fixPriority(false), +_mirrorX(false), +_pictureId(-1), +_updated(0), +_deleted(0), +_created(g_sci->_gfxFrameout->getScreenCount()) { + if (celInfo.type == kCelTypeColor) { + _insetRect = rect; + } +} + +ScreenItem::ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect, const ScaleInfo &scaleInfo) : +_position(rect.left, rect.top), +_z(0), +_object(make_reg(0, _nextObjectId++)), +_celInfo(celInfo), +_plane(plane), +_celObj(nullptr), +_useInsetRect(false), +_fixPriority(false), +_mirrorX(false), +_pictureId(-1), +_updated(0), +_deleted(0), +_created(g_sci->_gfxFrameout->getScreenCount()), +_scale(scaleInfo) {} + +ScreenItem::ScreenItem(const ScreenItem &other) : +_object(other._object), +_plane(other._plane), +_celInfo(other._celInfo), +_celObj(nullptr), +_screenRect(other._screenRect), +_mirrorX(other._mirrorX), +_useInsetRect(other._useInsetRect), +_scale(other._scale), +_scaledPosition(other._scaledPosition) { + if (other._useInsetRect) { + _insetRect = other._insetRect; + } +} + +void ScreenItem::operator=(const ScreenItem &other) { + _celInfo = other._celInfo; + _screenRect = other._screenRect; + _mirrorX = other._mirrorX; + _useInsetRect = other._useInsetRect; + if (other._useInsetRect) { + _insetRect = other._insetRect; + } + _scale = other._scale; + _scaledPosition = other._scaledPosition; +} + +void ScreenItem::init() { + _nextObjectId = 20000; +} + +void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const bool updateCel, const bool updateBitmap) { + _position.x = readSelectorValue(segMan, object, SELECTOR(x)); + _position.y = readSelectorValue(segMan, object, SELECTOR(y)); + _scale.x = readSelectorValue(segMan, object, SELECTOR(scaleX)); + _scale.y = readSelectorValue(segMan, object, SELECTOR(scaleY)); + _scale.max = readSelectorValue(segMan, object, SELECTOR(maxScale)); + _scale.signal = (ScaleSignals32)(readSelectorValue(segMan, object, SELECTOR(scaleSignal)) & 3); + + if (updateCel) { + _celInfo.resourceId = (GuiResourceId)readSelectorValue(segMan, object, SELECTOR(view)); + _celInfo.loopNo = readSelectorValue(segMan, object, SELECTOR(loop)); + _celInfo.celNo = readSelectorValue(segMan, object, SELECTOR(cel)); + + if (_celInfo.resourceId <= kPlanePic) { + // TODO: Enhance GfxView or ResourceManager to allow + // metadata for resources to be retrieved once, from a + // single location + Resource *view = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _celInfo.resourceId), false); + if (!view) { + error("Failed to load resource %d", _celInfo.resourceId); + } + + // NOTE: +2 because the header size field itself is excluded from + // the header size in the data + const uint16 headerSize = READ_SCI11ENDIAN_UINT16(view->data) + 2; + const uint8 loopCount = view->data[2]; + const uint8 loopSize = view->data[12]; + + if (_celInfo.loopNo >= loopCount) { + const int maxLoopNo = loopCount - 1; + _celInfo.loopNo = maxLoopNo; + writeSelectorValue(segMan, object, SELECTOR(loop), maxLoopNo); + } + + byte *loopData = view->data + headerSize + (_celInfo.loopNo * loopSize); + const int8 seekEntry = loopData[0]; + if (seekEntry != -1) { + loopData = view->data + headerSize + (seekEntry * loopSize); + } + const uint8 celCount = loopData[2]; + if (_celInfo.celNo >= celCount) { + const int maxCelNo = celCount - 1; + _celInfo.celNo = maxCelNo; + writeSelectorValue(segMan, object, SELECTOR(cel), maxCelNo); + } + } + } + + if (updateBitmap) { + const reg_t bitmap = readSelector(segMan, object, SELECTOR(bitmap)); + if (!bitmap.isNull()) { + _celInfo.bitmap = bitmap; + _celInfo.type = kCelTypeMem; + } else { + _celInfo.bitmap = NULL_REG; + _celInfo.type = kCelTypeView; + } + } + + if (updateCel || updateBitmap) { + delete _celObj; + _celObj = nullptr; + } + + if (readSelectorValue(segMan, object, SELECTOR(fixPriority))) { + _fixPriority = true; + _priority = readSelectorValue(segMan, object, SELECTOR(priority)); + } else { + _fixPriority = false; + writeSelectorValue(segMan, object, SELECTOR(priority), _position.y); + } + + _z = readSelectorValue(segMan, object, SELECTOR(z)); + _position.y -= _z; + + if (readSelectorValue(segMan, object, SELECTOR(useInsetRect))) { + _useInsetRect = true; + _insetRect.left = readSelectorValue(segMan, object, SELECTOR(inLeft)); + _insetRect.top = readSelectorValue(segMan, object, SELECTOR(inTop)); + _insetRect.right = readSelectorValue(segMan, object, SELECTOR(inRight)); + _insetRect.bottom = readSelectorValue(segMan, object, SELECTOR(inBottom)); + } else { + _useInsetRect = false; + } + + // TODO: SCI2.1/SQ6 engine clears this flag any time ScreenItem::Update(MemID) + // or ScreenItem::ScreenItem(MemID) are called, but doing this breaks + // view cycling because the flag isn't being set again later. There are over + // 100 places in the engine code where this flag is set, so it is probably + // a matter of figuring out what all of those calls are that re-set it. For + // now, since these are the *only* calls that clear this flag, we can just + // leave it set all the time. + // segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewVisible); +} + +void ScreenItem::calcRects(const Plane &plane) { + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + + Common::Rect celRect(_celObj->_width, _celObj->_height); + if (_useInsetRect) { + if (_insetRect.intersects(celRect)) { + _insetRect.clip(celRect); + } else { + _insetRect = Common::Rect(); + } + } else { + _insetRect = celRect; + } + + Ratio newRatioX; + Ratio newRatioY; + + if (_scale.signal & kScaleSignalDoScaling32) { + if (_scale.signal & kScaleSignalUseVanishingPoint) { + int num = _scale.max * (_position.y - plane._vanishingPoint.y) / (scriptWidth - plane._vanishingPoint.y); + newRatioX = Ratio(num, 128); + newRatioY = Ratio(num, 128); + } else { + newRatioX = Ratio(_scale.x, 128); + newRatioY = Ratio(_scale.y, 128); + } + } + + if (newRatioX.getNumerator() && newRatioY.getNumerator()) { + _screenItemRect = _insetRect; + + if (_celObj->_scaledWidth != scriptWidth || _celObj->_scaledHeight != scriptHeight) { + if (_useInsetRect) { + Ratio celScriptXRatio(_celObj->_scaledWidth, scriptWidth); + Ratio celScriptYRatio(_celObj->_scaledHeight, scriptHeight); + mulru(_screenItemRect, celScriptXRatio, celScriptYRatio); + + if (_screenItemRect.intersects(celRect)) { + _screenItemRect.clip(celRect); + } else { + _screenItemRect = Common::Rect(); + } + } + + int displaceX = _celObj->_displace.x; + int displaceY = _celObj->_displace.y; + + if (_mirrorX != _celObj->_mirrorX && _celInfo.type != kCelTypePic) { + displaceX = _celObj->_width - _celObj->_displace.x - 1; + } + + if (!newRatioX.isOne() || !newRatioY.isOne()) { + mulru(_screenItemRect, newRatioX, newRatioY); + displaceX = (displaceX * newRatioX).toInt(); + displaceY = (displaceY * newRatioY).toInt(); + } + + Ratio celXRatio(screenWidth, _celObj->_scaledWidth); + Ratio celYRatio(screenHeight, _celObj->_scaledHeight); + + displaceX = (displaceX * celXRatio).toInt(); + displaceY = (displaceY * celYRatio).toInt(); + + mulru(_screenItemRect, celXRatio, celYRatio); + + if (/* TODO: dword_C6288 */ false && _celInfo.type == kCelTypePic) { + _scaledPosition.x = _position.x; + _scaledPosition.y = _position.y; + } else { + _scaledPosition.x = (_position.x * screenWidth / scriptWidth) - displaceX; + _scaledPosition.y = (_position.y * screenHeight / scriptHeight) - displaceY; + } + + _screenItemRect.translate(_scaledPosition.x, _scaledPosition.y); + + if (_mirrorX != _celObj->_mirrorX && _celInfo.type == kCelTypePic) { + Common::Rect temp(_insetRect); + + if (!newRatioX.isOne()) { + mulru(temp, newRatioX, Ratio()); + } + + mulru(temp, celXRatio, Ratio()); + + CelObjPic *celObjPic = dynamic_cast(_celObj); + + temp.translate(celObjPic->_relativePosition.x * screenWidth / scriptWidth - displaceX, 0); + + // TODO: This is weird, and probably wrong calculation of widths + // due to BR-inclusion + int deltaX = plane._planeRect.right - plane._planeRect.left + 1 - temp.right - 1 - temp.left; + + _scaledPosition.x += deltaX; + _screenItemRect.translate(deltaX, 0); + } + + _scaledPosition.x += plane._planeRect.left; + _scaledPosition.y += plane._planeRect.top; + _screenItemRect.translate(plane._planeRect.left, plane._planeRect.top); + + _ratioX = newRatioX * Ratio(screenWidth, _celObj->_scaledWidth); + _ratioY = newRatioY * Ratio(screenHeight, _celObj->_scaledHeight); + } else { + int displaceX = _celObj->_displace.x; + if (_mirrorX != _celObj->_mirrorX && _celInfo.type != kCelTypePic) { + displaceX = _celObj->_width - _celObj->_displace.x - 1; + } + + if (!newRatioX.isOne() || !newRatioY.isOne()) { + mulru(_screenItemRect, newRatioX, newRatioY); + // 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 + _screenItemRect.right -= 1; + _screenItemRect.bottom -= 1; + } + + _scaledPosition.x = _position.x - (displaceX * newRatioX).toInt(); + _scaledPosition.y = _position.y - (_celObj->_displace.y * newRatioY).toInt(); + _screenItemRect.translate(_scaledPosition.x, _scaledPosition.y); + + if (_mirrorX != _celObj->_mirrorX && _celInfo.type == kCelTypePic) { + Common::Rect temp(_insetRect); + + if (!newRatioX.isOne()) { + mulru(temp, newRatioX, Ratio()); + temp.right -= 1; + } + + CelObjPic *celObjPic = dynamic_cast(_celObj); + temp.translate(celObjPic->_relativePosition.x - (displaceX * newRatioX).toInt(), celObjPic->_relativePosition.y - (_celObj->_displace.y * newRatioY).toInt()); + + // TODO: This is weird, and probably wrong calculation of widths + // due to BR-inclusion + int deltaX = plane._gameRect.right - plane._gameRect.left + 1 - temp.right - 1 - temp.left; + + _scaledPosition.x += deltaX; + _screenItemRect.translate(deltaX, 0); + } + + _scaledPosition.x += plane._gameRect.left; + _scaledPosition.y += plane._gameRect.top; + _screenItemRect.translate(plane._gameRect.left, plane._gameRect.top); + + if (screenWidth != _celObj->_scaledWidth || _celObj->_scaledHeight != screenHeight) { + Ratio celXRatio(screenWidth, _celObj->_scaledWidth); + Ratio celYRatio(screenHeight, _celObj->_scaledHeight); + mulru(_scaledPosition, celXRatio, celYRatio); + mulru(_screenItemRect, celXRatio, celYRatio, 1); + } + + _ratioX = newRatioX * Ratio(screenWidth, _celObj->_scaledWidth); + _ratioY = newRatioY * Ratio(screenHeight, _celObj->_scaledHeight); + } + + _screenRect = _screenItemRect; + + if (_screenRect.intersects(plane._screenRect)) { + _screenRect.clip(plane._screenRect); + } else { + _screenRect.right = 0; + _screenRect.bottom = 0; + _screenRect.left = 0; + _screenRect.top = 0; + } + + if (!_fixPriority) { + _priority = _z + _position.y; + } + } else { + _screenRect.left = 0; + _screenRect.top = 0; + _screenRect.right = 0; + _screenRect.bottom = 0; + } +} + +CelObj &ScreenItem::getCelObj() { + if (_celObj == nullptr) { + switch (_celInfo.type) { + case kCelTypeView: + _celObj = new CelObjView(_celInfo.resourceId, _celInfo.loopNo, _celInfo.celNo); + break; + case kCelTypePic: + error("Internal error, pic screen item with no cel."); + break; + case kCelTypeMem: + _celObj = new CelObjMem(_celInfo.bitmap); + break; + case kCelTypeColor: + _celObj = new CelObjColor(_celInfo.color, _insetRect.width(), _insetRect.height()); + break; + } + } + + return *_celObj; +} + +void ScreenItem::printDebugInfo(Console *con) const { + con->debugPrintf("prio %d, x %d, y %d, z: %d, scaledX: %d, scaledY: %d flags: %d\n", + _priority, + _position.x, + _position.y, + _z, + _scaledPosition.x, + _scaledPosition.y, + _created | (_updated << 1) | (_deleted << 2) + ); + con->debugPrintf(" screen rect (%d, %d, %d, %d)\n", PRINT_RECT(_screenRect)); + if (_useInsetRect) { + con->debugPrintf(" inset rect: (%d, %d, %d, %d)\n", PRINT_RECT(_insetRect)); + } + + Common::String celType; + switch (_celInfo.type) { + case kCelTypePic: + celType = "pic"; + break; + case kCelTypeView: + celType = "view"; + break; + case kCelTypeColor: + celType = "color"; + break; + case kCelTypeMem: + celType = "mem"; + break; + } + + con->debugPrintf(" type: %s, res %d, loop %d, cel %d, bitmap %04x:%04x, color: %d\n", + celType.c_str(), + _celInfo.resourceId, + _celInfo.loopNo, + _celInfo.celNo, + PRINT_REG(_celInfo.bitmap), + _celInfo.color + ); + if (_celObj != nullptr) { + con->debugPrintf(" width %d, height %d, scaledWidth %d, scaledHeight %d\n", + _celObj->_width, + _celObj->_height, + _celObj->_scaledWidth, + _celObj->_scaledHeight + ); + } +} + +void ScreenItem::update(const reg_t object) { + SegManager *segMan = g_sci->getEngineState()->_segMan; + + const GuiResourceId view = readSelectorValue(segMan, object, SELECTOR(view)); + const int16 loopNo = readSelectorValue(segMan, object, SELECTOR(loop)); + const int16 celNo = readSelectorValue(segMan, object, SELECTOR(cel)); + + const bool updateCel = ( + _celInfo.resourceId != view || + _celInfo.loopNo != loopNo || + _celInfo.celNo != celNo + ); + + const bool updateBitmap = !readSelector(segMan, object, SELECTOR(bitmap)).isNull(); + + setFromObject(segMan, object, updateCel, updateBitmap); + + if (!_created) { + _updated = g_sci->_gfxFrameout->getScreenCount(); + } + + _deleted = 0; +} + +#pragma mark - +#pragma mark ScreenItemList +ScreenItem *ScreenItemList::findByObject(const reg_t object) const { + const_iterator screenItemIt = Common::find_if(begin(), end(), FindByObject(object)); + + if (screenItemIt == end()) { + return nullptr; + } + + 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. + for (size_type i = 0; i < size(); ++i) { + _unsorted[i] = (*this)[i]; + } + + Common::sort(begin(), end(), sortHelper); +} +void ScreenItemList::unsort() { + for (size_type i = 0; i < size(); ++i) { + (*this)[i] = _unsorted[i]; + } +} + +} diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h new file mode 100644 index 0000000000..d3968efeef --- /dev/null +++ b/engines/sci/graphics/screen_item32.h @@ -0,0 +1,280 @@ +/* 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_SCREEN_ITEM32_H +#define SCI_GRAPHICS_SCREEN_ITEM32_H + +#include "common/rect.h" +#include "sci/graphics/celobj32.h" +#include "sci/graphics/lists32.h" + +namespace Sci { + +enum ScaleSignals32 { + kScaleSignalNone = 0, + kScaleSignalDoScaling32 = 1, // enables scaling when drawing that cel (involves scaleX and scaleY) + kScaleSignalUseVanishingPoint = 2, + // TODO: Is this actually a thing? I have not seen it and + // the original engine masks &3 where it uses scale signals. + kScaleSignalDisableGlobalScaling32 = 4 +}; + +struct ScaleInfo { + int x, y, max; + ScaleSignals32 signal; + ScaleInfo() : x(128), y(128), max(100), signal(kScaleSignalNone) {} +}; + +class CelObj; +class Plane; +class SegManager; + +#pragma mark - +#pragma mark ScreenItem + +/** + * A ScreenItem is the engine-side representation of a + * game script View. + */ +class ScreenItem { +private: + /** + * A serial used for screen items that are generated + * inside the graphics engine, rather than the + * interpreter. + */ + static uint16 _nextObjectId; + + /** + * The parent plane of this screen item. + */ + reg_t _plane; + + /** + * Scaling data used to calculate the final screen + * dimensions of the screen item as well as the scaling + * ratios used when drawing the item to screen. + */ + ScaleInfo _scale; + + /** + * The position & dimensions of the screen item in + * screen coordinates. This rect includes the offset + * of the parent plane, but is not clipped to the + * screen, so may include coordinates that are + * offscreen. + */ + Common::Rect _screenItemRect; + + /** + * TODO: Document + */ + bool _useInsetRect; + + /** + * TODO: Documentation + * The insetRect is also used to describe the fill + * rectangle of a screen item that is drawn using + * CelObjColor. + */ + Common::Rect _insetRect; + + /** + * The z-index of the screen item in pseudo-3D space. + * Higher values are drawn on top of lower values. + */ + int _z; + + /** + * Sets the common properties of a screen item that must + * be set both during creation and update of a screen + * item. + */ + void setFromObject(SegManager *segMan, const reg_t object, const bool updateCel, const bool updateBitmap); + +public: + /** + * A descriptor for the cel object represented by the + * screen item. + */ + CelInfo32 _celInfo; + + /** + * The cel object used to actually render the screen + * item. This member is populated by calling + * `getCelObj`. + */ + CelObj *_celObj; + + /** + * If set, the priority for this screen item is fixed + * in place. Otherwise, the priority of the screen item + * is calculated from its y-position + z-index. + */ + bool _fixPriority; + + /** + * The rendering priority of the screen item, relative + * only to the other screen items within the same plane. + * Higher priorities are drawn above lower priorities. + */ + int16 _priority; + + /** + * The top-left corner of the screen item, in game + * script coordinates, relative to the parent plane. + */ + Common::Point _position; + + /** + * The associated View script object that was + * used to create the ScreenItem, or a numeric + * value in the case of a ScreenItem that was + * generated outside of the VM. + */ + reg_t _object; + + /** + * For screen items representing picture resources, + * the resource ID of the picture. + */ + GuiResourceId _pictureId; + + /** + * Flags indicating the state of the screen item. + * - `created` is set when the screen item is first + * created, either from a VM object or from within the + * engine itself + * - `updated` is set when `created` is not already set + * and the screen item is updated from a VM object + * - `deleted` is set by the parent plane, if the parent + * plane is a pic type and its picture resource ID has + * changed + */ + int _created, _updated, _deleted; // ? + + /** + * For screen items that represent picture cels, this + * value is set to match the `_mirrorX` property of the + * parent plane and indicates that the cel should be + * drawn horizontally mirrored. For final drawing, it is + * XORed with the `_mirrorX` property of the cel object. + * The cel object's `_mirrorX` property comes from the + * resource data itself. + */ + bool _mirrorX; + + /** + * The scaling ratios to use when drawing this screen + * item. These values are calculated according to the + * scale info whenever the screen item is updated. + */ + Ratio _ratioX, _ratioY; + + /** + * The top-left corner of the screen item, in screen + * coordinates. + */ + Common::Point _scaledPosition; + + /** + * The position & dimensions of the screen item in + * screen coordinates. This rect includes the offset of + * the parent plane and is clipped to the screen. + */ + Common::Rect _screenRect; + + /** + * Initialises static Plane members. + */ + static void init(); + + ScreenItem(const reg_t screenItem); + ScreenItem(const reg_t plane, const CelInfo32 &celInfo); + ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect); + ScreenItem(const reg_t plane, const CelInfo32 &celInfo, const Common::Rect &rect, const ScaleInfo &scaleInfo); + ScreenItem(const ScreenItem &other); + void operator=(const ScreenItem &); + + 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 false; + // TODO: Failure in SQ6 room 220 +// return _object < other._object; + } + } + + return false; + } + + /** + * Calculates the dimensions and scaling parameters for + * the screen item, using the given plane as the parent + * plane for screen rect positioning. + * + * @note This method was called Update in SCI engine. + */ + void calcRects(const Plane &plane); + + /** + * Retrieves the corresponding cel object for this + * screen item. If a cel object does not already exist, + * one will be created and assigned. + */ + CelObj &getCelObj(); + + void printDebugInfo(Console *con) const; + + /** + * Updates the properties of the screen item from a + * VM object. + */ + void update(const reg_t object); +}; + +#pragma mark - +#pragma mark ScreenItemList + +typedef StablePointerArray ScreenItemListBase; +class ScreenItemList : public ScreenItemListBase { + static bool inline sortHelper(const ScreenItem *a, const ScreenItem *b) { + return *a < *b; + } +public: + ScreenItem *_unsorted[250]; + + ScreenItem *findByObject(const reg_t object) const; + void sort(); + void unsort(); +}; +} + +#endif diff --git a/engines/sci/module.mk b/engines/sci/module.mk index 08e5ea84d8..0b5587483b 100644 --- a/engines/sci/module.mk +++ b/engines/sci/module.mk @@ -81,10 +81,13 @@ MODULE_OBJS := \ ifdef ENABLE_SCI32 MODULE_OBJS += \ engine/kgraphics32.o \ + graphics/celobj32.o \ graphics/controls32.o \ graphics/frameout.o \ graphics/paint32.o \ + graphics/plane32.o \ graphics/palette32.o \ + graphics/screen_item32.o\ graphics/text32.o \ video/robot_decoder.o endif diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp index 93cafadc52..9ef28b214b 100644 --- a/engines/sci/sci.cpp +++ b/engines/sci/sci.cpp @@ -694,6 +694,7 @@ void SciEngine::initGraphics() { _gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxText32); _robotDecoder = new RobotDecoder(getPlatform() == Common::kPlatformMacintosh); _gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxCache, _gfxScreen, _gfxPalette32, _gfxPaint32); + _gfxFrameout->run(); } else { #endif // SCI0-SCI1.1 graphic objects creation -- cgit v1.2.3