1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "common/algorithm.h"
24 #include "common/config-manager.h"
25 #include "common/events.h"
26 #include "common/keyboard.h"
27 #include "common/list.h"
28 #include "common/str.h"
29 #include "common/system.h"
30 #include "common/textconsole.h"
31 #include "engines/engine.h"
32 #include "engines/util.h"
33 #include "graphics/palette.h"
34 #include "graphics/surface.h"
35
36 #include "sci/sci.h"
37 #include "sci/console.h"
38 #include "sci/event.h"
39 #include "sci/engine/features.h"
40 #include "sci/engine/kernel.h"
41 #include "sci/engine/state.h"
42 #include "sci/engine/selector.h"
43 #include "sci/engine/vm.h"
44 #include "sci/graphics/cache.h"
45 #include "sci/graphics/compare.h"
46 #include "sci/graphics/cursor32.h"
47 #include "sci/graphics/scifont.h"
48 #include "sci/graphics/frameout.h"
49 #include "sci/graphics/helpers.h"
50 #include "sci/graphics/paint32.h"
51 #include "sci/graphics/palette32.h"
52 #include "sci/graphics/plane32.h"
53 #include "sci/graphics/remap32.h"
54 #include "sci/graphics/screen.h"
55 #include "sci/graphics/screen_item32.h"
56 #include "sci/graphics/text32.h"
57 #include "sci/graphics/frameout.h"
58 #include "sci/graphics/transitions32.h"
59 #include "sci/graphics/video32.h"
60
61 namespace Sci {
62
GfxFrameout(SegManager * segMan,GfxPalette32 * palette,GfxTransitions32 * transitions,GfxCursor32 * cursor)63 GfxFrameout::GfxFrameout(SegManager *segMan, GfxPalette32 *palette, GfxTransitions32 *transitions, GfxCursor32 *cursor) :
64 _isHiRes(detectHiRes()),
65 _palette(palette),
66 _cursor(cursor),
67 _segMan(segMan),
68 _transitions(transitions),
69 _throttleState(0),
70 _remapOccurred(false),
71 _overdrawThreshold(0),
72 _throttleKernelFrameOut(true),
73 _palMorphIsOn(false),
74 _lastScreenUpdateTick(0) {
75
76 if (g_sci->getGameId() == GID_PHANTASMAGORIA) {
77 _currentBuffer.create(630, 450, Graphics::PixelFormat::createFormatCLUT8());
78 } else if (_isHiRes) {
79 _currentBuffer.create(640, 480, Graphics::PixelFormat::createFormatCLUT8());
80 } else {
81 _currentBuffer.create(320, 200, Graphics::PixelFormat::createFormatCLUT8());
82 }
83 initGraphics(_currentBuffer.w, _currentBuffer.h);
84
85 switch (g_sci->getGameId()) {
86 case GID_HOYLE5:
87 case GID_LIGHTHOUSE:
88 case GID_LSL7:
89 case GID_PHANTASMAGORIA2:
90 case GID_TORIN:
91 case GID_RAMA:
92 _scriptWidth = 640;
93 _scriptHeight = 480;
94 break;
95 case GID_GK2:
96 case GID_PQSWAT:
97 if (!g_sci->isDemo()) {
98 _scriptWidth = 640;
99 _scriptHeight = 480;
100 break;
101 }
102 // fall through
103 default:
104 _scriptWidth = 320;
105 _scriptHeight = 200;
106 break;
107 }
108 }
109
~GfxFrameout()110 GfxFrameout::~GfxFrameout() {
111 clear();
112 CelObj::deinit();
113 _currentBuffer.free();
114 }
115
run()116 void GfxFrameout::run() {
117 CelObj::init();
118 Plane::init();
119 ScreenItem::init();
120 GfxText32::init();
121
122 // This plane is created in SCI::InitPlane in SSCI, and is a background fill
123 // plane to ensure "hidden" planes (planes with negative priority) are never
124 // drawn
125 Plane *initPlane = new Plane(Common::Rect(_scriptWidth, _scriptHeight));
126 initPlane->_priority = 0;
127 _planes.add(initPlane);
128 }
129
clear()130 void GfxFrameout::clear() {
131 _planes.clear();
132 _visiblePlanes.clear();
133 _showList.clear();
134 }
135
detectHiRes() const136 bool GfxFrameout::detectHiRes() const {
137 // QFG4 is always low resolution
138 if (g_sci->getGameId() == GID_QFG4) {
139 return false;
140 }
141
142 // PQ4 DOS floppy is low resolution only
143 if (g_sci->getGameId() == GID_PQ4 &&
144 g_sci->getPlatform() == Common::kPlatformDOS &&
145 !g_sci->isCD()) {
146 return false;
147 }
148
149 // GK1 DOS floppy is low resolution only, but GK1 Mac floppy is high
150 // resolution only
151 if (g_sci->getGameId() == GID_GK1 &&
152 !g_sci->isCD() &&
153 g_sci->getPlatform() != Common::kPlatformMacintosh) {
154
155 return false;
156 }
157
158 // All other games are either high resolution by default, or have a
159 // user-defined toggle
160 return ConfMan.getBool("enable_high_resolution_graphics");
161 }
162
163 #pragma mark -
164 #pragma mark Screen items
165
addScreenItem(ScreenItem & screenItem) const166 void GfxFrameout::addScreenItem(ScreenItem &screenItem) const {
167 Plane *plane = _planes.findByObject(screenItem._plane);
168 if (plane == nullptr) {
169 error("GfxFrameout::addScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(screenItem._plane), PRINT_REG(screenItem._object));
170 }
171 plane->_screenItemList.add(&screenItem);
172 }
173
updateScreenItem(ScreenItem & screenItem) const174 void GfxFrameout::updateScreenItem(ScreenItem &screenItem) const {
175 // TODO: In SCI3+ this will need to go through Plane
176 // Plane *plane = _planes.findByObject(screenItem._plane);
177 // if (plane == nullptr) {
178 // error("GfxFrameout::updateScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(screenItem._plane), PRINT_REG(screenItem._object));
179 // }
180
181 screenItem.update();
182 }
183
deleteScreenItem(ScreenItem & screenItem)184 void GfxFrameout::deleteScreenItem(ScreenItem &screenItem) {
185 Plane *plane = _planes.findByObject(screenItem._plane);
186 if (plane == nullptr) {
187 error("GfxFrameout::deleteScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(screenItem._plane), PRINT_REG(screenItem._object));
188 }
189 if (plane->_screenItemList.findByObject(screenItem._object) == nullptr) {
190 error("GfxFrameout::deleteScreenItem: Screen item %04x:%04x not found in plane %04x:%04x", PRINT_REG(screenItem._object), PRINT_REG(screenItem._plane));
191 }
192 deleteScreenItem(screenItem, *plane);
193 }
194
deleteScreenItem(ScreenItem & screenItem,Plane & plane)195 void GfxFrameout::deleteScreenItem(ScreenItem &screenItem, Plane &plane) {
196 if (screenItem._created == 0) {
197 screenItem._created = 0;
198 screenItem._updated = 0;
199 screenItem._deleted = getScreenCount();
200 } else {
201 plane._screenItemList.erase(&screenItem);
202 plane._screenItemList.pack();
203 }
204 }
205
deleteScreenItem(ScreenItem & screenItem,const reg_t planeObject)206 void GfxFrameout::deleteScreenItem(ScreenItem &screenItem, const reg_t planeObject) {
207 Plane *plane = _planes.findByObject(planeObject);
208 if (plane == nullptr) {
209 error("GfxFrameout::deleteScreenItem: Could not find plane %04x:%04x for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(screenItem._object));
210 }
211 deleteScreenItem(screenItem, *plane);
212 }
213
kernelAddScreenItem(const reg_t object)214 void GfxFrameout::kernelAddScreenItem(const reg_t object) {
215 const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
216
217 _segMan->getObject(object)->setInfoSelectorFlag(kInfoFlagViewInserted);
218
219 Plane *plane = _planes.findByObject(planeObject);
220 if (plane == nullptr) {
221 error("kAddScreenItem: Plane %04x:%04x not found for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(object));
222 }
223
224 ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
225 if (screenItem != nullptr) {
226 screenItem->update(object);
227 } else {
228 screenItem = new ScreenItem(object);
229 plane->_screenItemList.add(screenItem);
230 }
231 }
232
kernelUpdateScreenItem(const reg_t object)233 void GfxFrameout::kernelUpdateScreenItem(const reg_t object) {
234 const reg_t magnifierObject = readSelector(_segMan, object, SELECTOR(magnifier));
235 if (magnifierObject.isNull()) {
236 const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
237 Plane *plane = _planes.findByObject(planeObject);
238 if (plane == nullptr) {
239 // Script bug in PQ:SWAT, when skipping the Tactics Training
240 warning("kUpdateScreenItem: Plane %04x:%04x not found for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(object));
241 return;
242 }
243
244 ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
245 if (screenItem == nullptr) {
246 error("kUpdateScreenItem: Screen item %04x:%04x not found in plane %04x:%04x", PRINT_REG(object), PRINT_REG(planeObject));
247 }
248
249 screenItem->update(object);
250 } else {
251 error("Magnifier view is not known to be used by any game. Please submit a bug report with details about the game you were playing and what you were doing that triggered this error. Thanks!");
252 }
253 }
254
kernelDeleteScreenItem(const reg_t object)255 void GfxFrameout::kernelDeleteScreenItem(const reg_t object) {
256 _segMan->getObject(object)->clearInfoSelectorFlag(kInfoFlagViewInserted);
257
258 const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
259 Plane *plane = _planes.findByObject(planeObject);
260 if (plane == nullptr) {
261 return;
262 }
263
264 ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
265 if (screenItem == nullptr) {
266 return;
267 }
268
269 deleteScreenItem(*screenItem, *plane);
270 }
271
272 #pragma mark -
273 #pragma mark Planes
274
kernelAddPlane(const reg_t object)275 void GfxFrameout::kernelAddPlane(const reg_t object) {
276 Plane *plane = _planes.findByObject(object);
277 if (plane != nullptr) {
278 plane->update(object);
279 updatePlane(*plane);
280 } else {
281 plane = new Plane(object);
282 addPlane(plane);
283 }
284
285 // Detect the QFG4 import character dialog, disable the Change Directory
286 // button, and display a message box explaining how to import saved
287 // character files in ScummVM. SCI16 games are handled by kDrawControl.
288 if (g_sci->inQfGImportRoom()) {
289 // kAddPlane is called several times, this detects the second call
290 // which is for the import character dialog. If changeButton:value
291 // is non-zero then the dialog is initializing. If the button isn't
292 // disabled then we haven't displayed the message box yet. There
293 // are multiple changeButtons because the script clones the object.
294 SegManager *segMan = g_sci->getEngineState()->_segMan;
295 Common::Array<reg_t> changeDirButtons = _segMan->findObjectsByName("changeButton");
296 for (uint i = 0; i < changeDirButtons.size(); ++i) {
297 if (readSelectorValue(segMan, changeDirButtons[i], SELECTOR(value))) {
298 // disable Change Directory button by setting state to zero
299 if (readSelectorValue(segMan, changeDirButtons[i], SELECTOR(state))) {
300 writeSelectorValue(segMan, changeDirButtons[i], SELECTOR(state), 0);
301 g_sci->showQfgImportMessageBox();
302 break;
303 }
304 }
305 }
306 }
307 }
308
kernelUpdatePlane(const reg_t object)309 void GfxFrameout::kernelUpdatePlane(const reg_t object) {
310 Plane *plane = _planes.findByObject(object);
311 if (plane == nullptr) {
312 error("kUpdatePlane: Plane %04x:%04x not found", PRINT_REG(object));
313 }
314
315 plane->update(object);
316 updatePlane(*plane);
317 }
318
kernelDeletePlane(const reg_t object)319 void GfxFrameout::kernelDeletePlane(const reg_t object) {
320 Plane *plane = _planes.findByObject(object);
321 if (plane == nullptr) {
322 error("kDeletePlane: Plane %04x:%04x not found", PRINT_REG(object));
323 }
324
325 if (plane->_created) {
326 // SSCI calls some `AbortPlane` function that just ends up doing this
327 // anyway, so we skip the extra indirection
328 _planes.erase(plane);
329 } else {
330 plane->_created = 0;
331 plane->_deleted = g_sci->_gfxFrameout->getScreenCount();
332 }
333 }
334
deletePlane(Plane & planeToFind)335 void GfxFrameout::deletePlane(Plane &planeToFind) {
336 Plane *plane = _planes.findByObject(planeToFind._object);
337 if (plane == nullptr) {
338 error("deletePlane: Plane %04x:%04x not found", PRINT_REG(planeToFind._object));
339 }
340
341 if (plane->_created) {
342 _planes.erase(plane);
343 } else {
344 plane->_created = 0;
345 plane->_moved = 0;
346 plane->_deleted = getScreenCount();
347 }
348 }
349
deletePlanesForMacRestore()350 void GfxFrameout::deletePlanesForMacRestore() {
351 // SCI32 PC games delete planes and screen items from
352 // their Game:restore script before calling kRestore.
353 // In Mac this work was moved into the interpreter
354 // for some games, while others added it back to
355 // Game:restore or used their own scripts that took
356 // care of this in both PC and Mac versions.
357 if (!(g_sci->getGameId() == GID_GK1 ||
358 g_sci->getGameId() == GID_PQ4 ||
359 g_sci->getGameId() == GID_LSL6HIRES ||
360 g_sci->getGameId() == GID_KQ7)) {
361 return;
362 }
363
364 for (PlaneList::size_type i = 0; i < _planes.size(); ) {
365 Plane *plane = _planes[i];
366
367 // don't delete the default plane
368 if (plane->isDefaultPlane()) {
369 i++;
370 continue;
371 }
372
373 // delete all inserted screen items from the plane
374 for (ScreenItemList::size_type j = 0; j < plane->_screenItemList.size(); ++j) {
375 ScreenItem *screenItem = plane->_screenItemList[j];
376 if (screenItem != nullptr &&
377 !screenItem->_object.isNumber() &&
378 _segMan->getObject(screenItem->_object)->isInserted()) {
379
380 // delete the screen item
381 if (screenItem->_created) {
382 plane->_screenItemList.erase_at(j);
383 } else {
384 screenItem->_updated = 0;
385 screenItem->_deleted = getScreenCount();
386 }
387 }
388 }
389 plane->_screenItemList.pack();
390
391 // delete the plane
392 if (plane->_created) {
393 _planes.erase(plane);
394 } else {
395 plane->_moved = 0;
396 plane->_deleted = getScreenCount();
397 i++;
398 }
399 }
400 }
401
kernelMovePlaneItems(const reg_t object,const int16 deltaX,const int16 deltaY,const bool scrollPics)402 void GfxFrameout::kernelMovePlaneItems(const reg_t object, const int16 deltaX, const int16 deltaY, const bool scrollPics) {
403 Plane *plane = _planes.findByObject(object);
404 if (plane == nullptr) {
405 error("kMovePlaneItems: Plane %04x:%04x not found", PRINT_REG(object));
406 }
407
408 plane->scrollScreenItems(deltaX, deltaY, scrollPics);
409
410 for (ScreenItemList::iterator it = plane->_screenItemList.begin(); it != plane->_screenItemList.end(); ++it) {
411 ScreenItem &screenItem = **it;
412
413 // If object is a number, the screen item from the engine, not a script,
414 // and should be ignored
415 if (screenItem._object.isNumber()) {
416 continue;
417 }
418
419 if (deltaX != 0) {
420 writeSelectorValue(_segMan, screenItem._object, SELECTOR(x), readSelectorValue(_segMan, screenItem._object, SELECTOR(x)) + deltaX);
421 }
422
423 if (deltaY != 0) {
424 writeSelectorValue(_segMan, screenItem._object, SELECTOR(y), readSelectorValue(_segMan, screenItem._object, SELECTOR(y)) + deltaY);
425 }
426 }
427 }
428
kernelGetHighPlanePri()429 int16 GfxFrameout::kernelGetHighPlanePri() {
430 return _planes.getTopSciPlanePriority();
431 }
432
addPlane(Plane * plane)433 void GfxFrameout::addPlane(Plane *plane) {
434 // In SSCI, if a plane with the same object ID already existed, this call
435 // would cancel deletion and update an already-existing plane, but callers
436 // expect the passed plane object to become memory-managed by GfxFrameout,
437 // so doing what SSCI did would end up leaking the Plane objects
438 if (_planes.findByObject(plane->_object) != nullptr) {
439 error("Plane %04x:%04x already exists", PRINT_REG(plane->_object));
440 }
441
442 plane->clipScreenRect(Common::Rect(_currentBuffer.w, _currentBuffer.h));
443 _planes.add(plane);
444 }
445
updatePlane(Plane & plane)446 void GfxFrameout::updatePlane(Plane &plane) {
447 // This assertion comes from SSCI
448 assert(_planes.findByObject(plane._object) == &plane);
449
450 Plane *visiblePlane = _visiblePlanes.findByObject(plane._object);
451 plane.sync(visiblePlane, Common::Rect(_currentBuffer.w, _currentBuffer.h));
452 // updateScreenRect was called a second time here in SSCI, but it is already
453 // called at the end of the sync call (also in SSCI) so there is no reason
454 // to do it again
455
456 _planes.sort();
457 }
458
459 #pragma mark -
460 #pragma mark Pics
461
kernelAddPicAt(const reg_t planeObject,const GuiResourceId pictureId,const int16 x,const int16 y,const bool mirrorX,const bool deleteDuplicate)462 void GfxFrameout::kernelAddPicAt(const reg_t planeObject, const GuiResourceId pictureId, const int16 x, const int16 y, const bool mirrorX, const bool deleteDuplicate) {
463 Plane *plane = _planes.findByObject(planeObject);
464 if (plane == nullptr) {
465 error("kAddPicAt: Plane %04x:%04x not found", PRINT_REG(planeObject));
466 }
467 plane->addPic(pictureId, Common::Point(x, y), mirrorX, deleteDuplicate);
468 }
469
470 #pragma mark -
471 #pragma mark Rendering
472
frameOut(const bool shouldShowBits,const Common::Rect & eraseRect)473 void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &eraseRect) {
474 updateMousePositionForRendering();
475
476 RobotDecoder &robotPlayer = g_sci->_video32->getRobotPlayer();
477 const bool robotIsActive = robotPlayer.getStatus() != RobotDecoder::kRobotStatusUninitialized;
478
479 if (robotIsActive) {
480 robotPlayer.doRobot();
481 }
482
483 // SSCI allocated these as static arrays of 100 pointers to
484 // ScreenItemList / RectList
485 ScreenItemListList screenItemLists;
486 EraseListList eraseLists;
487
488 screenItemLists.resize(_planes.size());
489 eraseLists.resize(_planes.size());
490
491 if (g_sci->_gfxRemap32->getRemapCount() > 0 && _remapOccurred) {
492 remapMarkRedraw();
493 }
494
495 calcLists(screenItemLists, eraseLists, eraseRect);
496
497 for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
498 list->sort();
499 }
500
501 for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
502 for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
503 (*drawItem)->screenItem->getCelObj().submitPalette();
504 }
505 }
506
507 _remapOccurred = _palette->updateForFrame();
508
509 for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
510 drawEraseList(eraseLists[i], *_planes[i]);
511 drawScreenItemList(screenItemLists[i]);
512 }
513
514 if (robotIsActive) {
515 robotPlayer.frameAlmostVisible();
516 }
517
518 _palette->updateHardware();
519
520 if (shouldShowBits) {
521 showBits();
522 }
523
524 if (robotIsActive) {
525 robotPlayer.frameNowVisible();
526 }
527 }
528
palMorphFrameOut(const int8 * styleRanges,PlaneShowStyle * showStyle)529 void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, PlaneShowStyle *showStyle) {
530 updateMousePositionForRendering();
531
532 Palette sourcePalette(_palette->getNextPalette());
533 alterVmap(sourcePalette, sourcePalette, -1, styleRanges);
534
535 int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][kGlobalVarPreviousRoomNo].toSint16();
536
537 Common::Rect rect(_currentBuffer.w, _currentBuffer.h);
538 _showList.add(rect);
539 showBits();
540
541 // SSCI allocated these as static arrays of 100 pointers to
542 // ScreenItemList / RectList
543 ScreenItemListList screenItemLists;
544 EraseListList eraseLists;
545
546 screenItemLists.resize(_planes.size());
547 eraseLists.resize(_planes.size());
548
549 if (g_sci->_gfxRemap32->getRemapCount() > 0 && _remapOccurred) {
550 remapMarkRedraw();
551 }
552
553 calcLists(screenItemLists, eraseLists);
554 for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
555 list->sort();
556 }
557
558 for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
559 for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
560 (*drawItem)->screenItem->getCelObj().submitPalette();
561 }
562 }
563
564 _remapOccurred = _palette->updateForFrame();
565
566 for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
567 drawEraseList(eraseLists[i], *_planes[i]);
568 drawScreenItemList(screenItemLists[i]);
569 }
570
571 Palette nextPalette(_palette->getNextPalette());
572
573 if (prevRoom < 1000) {
574 for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
575 if (styleRanges[i] == -1 || styleRanges[i] == 0) {
576 sourcePalette.colors[i] = nextPalette.colors[i];
577 sourcePalette.colors[i].used = true;
578 }
579 }
580 } else {
581 for (int i = 0; i < ARRAYSIZE(sourcePalette.colors); ++i) {
582 if (styleRanges[i] == -1 || validZeroStyle(styleRanges[i], i)) {
583 sourcePalette.colors[i] = nextPalette.colors[i];
584 sourcePalette.colors[i].used = true;
585 }
586 }
587 }
588
589 _palette->submit(sourcePalette);
590 _palette->updateFFrame();
591 _palette->updateHardware();
592 alterVmap(nextPalette, sourcePalette, 1, _transitions->_styleRanges);
593
594 if (showStyle && showStyle->type != kShowStyleMorph) {
595 _transitions->processEffects(*showStyle);
596 } else {
597 showBits();
598 }
599
600 for (PlaneList::iterator plane = _planes.begin(); plane != _planes.end(); ++plane) {
601 (*plane)->_redrawAllCount = getScreenCount();
602 }
603
604 if (g_sci->_gfxRemap32->getRemapCount() > 0 && _remapOccurred) {
605 remapMarkRedraw();
606 }
607
608 calcLists(screenItemLists, eraseLists);
609 for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
610 list->sort();
611 }
612
613 for (ScreenItemListList::iterator list = screenItemLists.begin(); list != screenItemLists.end(); ++list) {
614 for (DrawList::iterator drawItem = list->begin(); drawItem != list->end(); ++drawItem) {
615 (*drawItem)->screenItem->getCelObj().submitPalette();
616 }
617 }
618
619 _remapOccurred = _palette->updateForFrame();
620
621 for (PlaneList::size_type i = 0; i < _planes.size(); ++i) {
622 drawEraseList(eraseLists[i], *_planes[i]);
623 drawScreenItemList(screenItemLists[i]);
624 }
625
626 _palette->submit(nextPalette);
627 _palette->updateFFrame();
628 _palette->updateHardware();
629 showBits();
630 }
631
directFrameOut(const Common::Rect & showRect)632 void GfxFrameout::directFrameOut(const Common::Rect &showRect) {
633 updateMousePositionForRendering();
634 _showList.add(showRect);
635 showBits();
636 }
637
638 #ifdef USE_RGB_COLOR
redrawGameScreen(const Common::Rect & skipRect) const639 void GfxFrameout::redrawGameScreen(const Common::Rect &skipRect) const {
640 Common::ScopedPtr<Graphics::Surface> game(_currentBuffer.convertTo(g_system->getScreenFormat(), _palette->getHardwarePalette()));
641 assert(game);
642
643 Common::Rect rects[4];
644 int splitCount = splitRects(Common::Rect(game->w, game->h), skipRect, rects);
645 if (splitCount != -1) {
646 while (splitCount--) {
647 const Common::Rect &drawRect = rects[splitCount];
648 g_system->copyRectToScreen(game->getBasePtr(drawRect.left, drawRect.top), game->pitch, drawRect.left, drawRect.top, drawRect.width(), drawRect.height());
649 }
650 }
651
652 game->free();
653 }
654
resetHardware()655 void GfxFrameout::resetHardware() {
656 updateMousePositionForRendering();
657 _showList.add(Common::Rect(_currentBuffer.w, _currentBuffer.h));
658 g_system->getPaletteManager()->setPalette(_palette->getHardwarePalette(), 0, 256);
659 showBits();
660 }
661 #endif
662
663 /**
664 * Determines the parts of `middleRect` that aren't overlapped by `showRect`,
665 * optimised for contiguous memory writes.
666 *
667 * `middleRect` is modified directly to extend into the upper and lower rects.
668 *
669 * @returns -1 if `middleRect` and `showRect` have no intersection, or the
670 * number of returned parts (in `outRects`) otherwise. (In particular, this
671 * returns 0 if `middleRect` is contained in `showRect`.)
672 */
splitRectsForRender(Common::Rect & middleRect,const Common::Rect & showRect,Common::Rect (& outRects)[2])673 int splitRectsForRender(Common::Rect &middleRect, const Common::Rect &showRect, Common::Rect(&outRects)[2]) {
674 if (!middleRect.intersects(showRect)) {
675 return -1;
676 }
677
678 const int16 minLeft = MIN(middleRect.left, showRect.left);
679 const int16 maxRight = MAX(middleRect.right, showRect.right);
680
681 int16 upperLeft, upperTop, upperRight, upperMaxTop;
682 if (middleRect.top < showRect.top) {
683 upperLeft = middleRect.left;
684 upperTop = middleRect.top;
685 upperRight = middleRect.right;
686 upperMaxTop = showRect.top;
687 }
688 else {
689 upperLeft = showRect.left;
690 upperTop = showRect.top;
691 upperRight = showRect.right;
692 upperMaxTop = middleRect.top;
693 }
694
695 int16 lowerLeft, lowerRight, lowerBottom, lowerMinBottom;
696 if (middleRect.bottom > showRect.bottom) {
697 lowerLeft = middleRect.left;
698 lowerRight = middleRect.right;
699 lowerBottom = middleRect.bottom;
700 lowerMinBottom = showRect.bottom;
701 } else {
702 lowerLeft = showRect.left;
703 lowerRight = showRect.right;
704 lowerBottom = showRect.bottom;
705 lowerMinBottom = middleRect.bottom;
706 }
707
708 int splitCount = 0;
709 middleRect.left = minLeft;
710 middleRect.top = upperMaxTop;
711 middleRect.right = maxRight;
712 middleRect.bottom = lowerMinBottom;
713
714 if (upperTop != upperMaxTop) {
715 Common::Rect &upperRect = outRects[0];
716 upperRect.left = upperLeft;
717 upperRect.top = upperTop;
718 upperRect.right = upperRight;
719 upperRect.bottom = upperMaxTop;
720
721 // Merge upper rect into middle rect if possible
722 if (upperRect.left == middleRect.left && upperRect.right == middleRect.right) {
723 middleRect.top = upperRect.top;
724 } else {
725 ++splitCount;
726 }
727 }
728
729 if (lowerBottom != lowerMinBottom) {
730 Common::Rect &lowerRect = outRects[splitCount];
731 lowerRect.left = lowerLeft;
732 lowerRect.top = lowerMinBottom;
733 lowerRect.right = lowerRight;
734 lowerRect.bottom = lowerBottom;
735
736 // Merge lower rect into middle rect if possible
737 if (lowerRect.left == middleRect.left && lowerRect.right == middleRect.right) {
738 middleRect.bottom = lowerRect.bottom;
739 } else {
740 ++splitCount;
741 }
742 }
743
744 assert(splitCount <= 2);
745 return splitCount;
746 }
747
748 // The third rectangle parameter is only ever passed by VMD code
calcLists(ScreenItemListList & drawLists,EraseListList & eraseLists,const Common::Rect & eraseRect)749 void GfxFrameout::calcLists(ScreenItemListList &drawLists, EraseListList &eraseLists, const Common::Rect &eraseRect) {
750 RectList eraseList;
751 Common::Rect outRects[4];
752 int deletedPlaneCount = 0;
753 bool addedToEraseList = false;
754 bool foundTransparentPlane = false;
755
756 if (!eraseRect.isEmpty()) {
757 addedToEraseList = true;
758 eraseList.add(eraseRect);
759 }
760
761 PlaneList::size_type planeCount = _planes.size();
762 for (PlaneList::size_type outerPlaneIndex = 0; outerPlaneIndex < planeCount; ++outerPlaneIndex) {
763 const Plane *outerPlane = _planes[outerPlaneIndex];
764 const Plane *visiblePlane = _visiblePlanes.findByObject(outerPlane->_object);
765
766 // SSCI only ever checks for kPlaneTypeTransparent here, even though
767 // kPlaneTypeTransparentPicture is also a transparent plane
768 if (outerPlane->_type == kPlaneTypeTransparent) {
769 foundTransparentPlane = true;
770 }
771
772 if (outerPlane->_deleted) {
773 if (visiblePlane != nullptr && !visiblePlane->_screenRect.isEmpty()) {
774 eraseList.add(visiblePlane->_screenRect);
775 addedToEraseList = true;
776 }
777 ++deletedPlaneCount;
778 } else if (visiblePlane != nullptr && outerPlane->_moved) {
779 // _moved will be decremented in the final loop through the planes,
780 // at the end of this function
781
782 {
783 const int splitCount = splitRects(visiblePlane->_screenRect, outerPlane->_screenRect, outRects);
784 if (splitCount) {
785 if (splitCount == -1 && !visiblePlane->_screenRect.isEmpty()) {
786 eraseList.add(visiblePlane->_screenRect);
787 } else {
788 for (int i = 0; i < splitCount; ++i) {
789 eraseList.add(outRects[i]);
790 }
791 }
792 addedToEraseList = true;
793 }
794 }
795
796 if (!outerPlane->_redrawAllCount) {
797 const int splitCount = splitRects(outerPlane->_screenRect, visiblePlane->_screenRect, outRects);
798 if (splitCount) {
799 for (int i = 0; i < splitCount; ++i) {
800 eraseList.add(outRects[i]);
801 }
802 addedToEraseList = true;
803 }
804 }
805 }
806
807 if (addedToEraseList) {
808 for (RectList::size_type rectIndex = 0; rectIndex < eraseList.size(); ++rectIndex) {
809 const Common::Rect &rect = *eraseList[rectIndex];
810 for (int innerPlaneIndex = planeCount - 1; innerPlaneIndex >= 0; --innerPlaneIndex) {
811 const Plane &innerPlane = *_planes[innerPlaneIndex];
812
813 if (
814 !innerPlane._deleted &&
815 innerPlane._type != kPlaneTypeTransparent &&
816 innerPlane._screenRect.intersects(rect)
817 ) {
818 if (!innerPlane._redrawAllCount) {
819 eraseLists[innerPlaneIndex].add(innerPlane._screenRect.findIntersectingRect(rect));
820 }
821
822 const int splitCount = splitRects(rect, innerPlane._screenRect, outRects);
823 for (int i = 0; i < splitCount; ++i) {
824 eraseList.add(outRects[i]);
825 }
826
827 eraseList.erase_at(rectIndex);
828 break;
829 }
830 }
831 }
832
833 eraseList.pack();
834 }
835 }
836
837 if (deletedPlaneCount) {
838 for (int planeIndex = planeCount - 1; planeIndex >= 0; --planeIndex) {
839 Plane *plane = _planes[planeIndex];
840
841 if (plane->_deleted) {
842 --plane->_deleted;
843 if (plane->_deleted <= 0) {
844 const int visiblePlaneIndex = _visiblePlanes.findIndexByObject(plane->_object);
845 if (visiblePlaneIndex != -1) {
846 _visiblePlanes.remove_at(visiblePlaneIndex);
847 }
848
849 _planes.remove_at(planeIndex);
850 eraseLists.remove_at(planeIndex);
851 drawLists.remove_at(planeIndex);
852 }
853
854 if (--deletedPlaneCount <= 0) {
855 break;
856 }
857 }
858 }
859 }
860
861 // Some planes may have been deleted, so re-retrieve count
862 planeCount = _planes.size();
863
864 for (PlaneList::size_type outerIndex = 0; outerIndex < planeCount; ++outerIndex) {
865 // "outer" just refers to the outer loop
866 Plane &outerPlane = *_planes[outerIndex];
867 if (outerPlane._priorityChanged) {
868 --outerPlane._priorityChanged;
869
870 const Plane *visibleOuterPlane = _visiblePlanes.findByObject(outerPlane._object);
871 if (visibleOuterPlane == nullptr) {
872 warning("calcLists could not find visible plane for %04x:%04x", PRINT_REG(outerPlane._object));
873 continue;
874 }
875
876 eraseList.add(outerPlane._screenRect.findIntersectingRect(visibleOuterPlane->_screenRect));
877
878 for (int innerIndex = (int)planeCount - 1; innerIndex >= 0; --innerIndex) {
879 // "inner" just refers to the inner loop
880 const Plane &innerPlane = *_planes[innerIndex];
881 const Plane *visibleInnerPlane = _visiblePlanes.findByObject(innerPlane._object);
882
883 const RectList::size_type rectCount = eraseList.size();
884 for (RectList::size_type rectIndex = 0; rectIndex < rectCount; ++rectIndex) {
885 const int splitCount = splitRects(*eraseList[rectIndex], innerPlane._screenRect, outRects);
886 if (splitCount == 0) {
887 if (visibleInnerPlane != nullptr) {
888 // same priority, or relative priority between inner/outer changed
889 if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane._priority - innerPlane._priority) <= 0) {
890 if (outerPlane._priority <= innerPlane._priority) {
891 eraseLists[innerIndex].add(*eraseList[rectIndex]);
892 } else {
893 eraseLists[outerIndex].add(*eraseList[rectIndex]);
894 }
895 }
896 }
897
898 eraseList.erase_at(rectIndex);
899 } else if (splitCount != -1) {
900 for (int i = 0; i < splitCount; ++i) {
901 eraseList.add(outRects[i]);
902 }
903
904 if (visibleInnerPlane != nullptr) {
905 // same priority, or relative priority between inner/outer changed
906 if ((visibleOuterPlane->_priority - visibleInnerPlane->_priority) * (outerPlane._priority - innerPlane._priority) <= 0) {
907 *eraseList[rectIndex] = outerPlane._screenRect.findIntersectingRect(innerPlane._screenRect);
908
909 if (outerPlane._priority <= innerPlane._priority) {
910 eraseLists[innerIndex].add(*eraseList[rectIndex]);
911 } else {
912 eraseLists[outerIndex].add(*eraseList[rectIndex]);
913 }
914 }
915 }
916 eraseList.erase_at(rectIndex);
917 }
918 }
919 eraseList.pack();
920 }
921 }
922 }
923
924 for (PlaneList::size_type planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
925 Plane &plane = *_planes[planeIndex];
926 Plane *visiblePlane = _visiblePlanes.findByObject(plane._object);
927
928 if (!plane._screenRect.isEmpty()) {
929 if (plane._redrawAllCount) {
930 plane.redrawAll(visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
931 } else {
932 if (visiblePlane == nullptr) {
933 error("Missing visible plane for source plane %04x:%04x", PRINT_REG(plane._object));
934 }
935
936 plane.calcLists(*visiblePlane, _planes, drawLists[planeIndex], eraseLists[planeIndex]);
937 }
938 } else {
939 plane.decrementScreenItemArrayCounts(visiblePlane, false);
940 }
941
942 if (plane._moved) {
943 // the work for handling moved/resized planes was already done
944 // earlier in the function, we are just cleaning up now
945 --plane._moved;
946 }
947
948 if (plane._created) {
949 _visiblePlanes.add(new Plane(plane));
950 --plane._created;
951 } else if (plane._updated) {
952 if (visiblePlane == nullptr) {
953 error("[GfxFrameout::calcLists]: Attempt to update nonexistent visible plane");
954 }
955
956 *visiblePlane = plane;
957 --plane._updated;
958 }
959 }
960
961 // SSCI really only looks for kPlaneTypeTransparent, not
962 // kPlaneTypeTransparentPicture
963 if (foundTransparentPlane) {
964 for (PlaneList::size_type planeIndex = 0; planeIndex < planeCount; ++planeIndex) {
965 for (PlaneList::size_type i = planeIndex + 1; i < planeCount; ++i) {
966 if (_planes[i]->_type == kPlaneTypeTransparent) {
967 _planes[i]->filterUpEraseRects(drawLists[i], eraseLists[planeIndex]);
968 }
969 }
970
971 if (_planes[planeIndex]->_type == kPlaneTypeTransparent) {
972 for (int i = (int)planeIndex - 1; i >= 0; --i) {
973 _planes[i]->filterDownEraseRects(drawLists[i], eraseLists[i], eraseLists[planeIndex]);
974 }
975
976 if (eraseLists[planeIndex].size() > 0) {
977 error("Transparent plane's erase list not absorbed");
978 }
979 }
980
981 for (PlaneList::size_type i = planeIndex + 1; i < planeCount; ++i) {
982 if (_planes[i]->_type == kPlaneTypeTransparent) {
983 _planes[i]->filterUpDrawRects(drawLists[i], drawLists[planeIndex]);
984 }
985 }
986 }
987 }
988 }
989
drawEraseList(const RectList & eraseList,const Plane & plane)990 void GfxFrameout::drawEraseList(const RectList &eraseList, const Plane &plane) {
991 if (plane._type != kPlaneTypeColored) {
992 return;
993 }
994
995 const RectList::size_type eraseListSize = eraseList.size();
996 for (RectList::size_type i = 0; i < eraseListSize; ++i) {
997 mergeToShowList(*eraseList[i], _showList, _overdrawThreshold);
998 _currentBuffer.fillRect(*eraseList[i], plane._back);
999 }
1000 }
1001
drawScreenItemList(const DrawList & screenItemList)1002 void GfxFrameout::drawScreenItemList(const DrawList &screenItemList) {
1003 const DrawList::size_type drawListSize = screenItemList.size();
1004 for (DrawList::size_type i = 0; i < drawListSize; ++i) {
1005 const DrawItem &drawItem = *screenItemList[i];
1006 mergeToShowList(drawItem.rect, _showList, _overdrawThreshold);
1007 const ScreenItem &screenItem = *drawItem.screenItem;
1008 CelObj &celObj = *screenItem._celObj;
1009 celObj.draw(_currentBuffer, screenItem, drawItem.rect, screenItem._mirrorX ^ celObj._mirrorX);
1010 }
1011 }
1012
mergeToShowList(const Common::Rect & drawRect,RectList & showList,const int overdrawThreshold)1013 void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showList, const int overdrawThreshold) {
1014 RectList mergeList;
1015 Common::Rect merged;
1016 mergeList.add(drawRect);
1017
1018 for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
1019 bool didMerge = false;
1020 const Common::Rect &r1 = *mergeList[i];
1021 if (!r1.isEmpty()) {
1022 for (RectList::size_type j = 0; j < showList.size(); ++j) {
1023 const Common::Rect &r2 = *showList[j];
1024 if (!r2.isEmpty()) {
1025 merged = r1;
1026 merged.extend(r2);
1027
1028 int difference = merged.width() * merged.height();
1029 difference -= r1.width() * r1.height();
1030 difference -= r2.width() * r2.height();
1031 if (r1.intersects(r2)) {
1032 const Common::Rect overlap = r1.findIntersectingRect(r2);
1033 difference += overlap.width() * overlap.height();
1034 }
1035
1036 if (difference <= overdrawThreshold) {
1037 mergeList.erase_at(i);
1038 showList.erase_at(j);
1039 mergeList.add(merged);
1040 didMerge = true;
1041 break;
1042 } else {
1043 Common::Rect outRects[2];
1044 int splitCount = splitRectsForRender(*mergeList[i], *showList[j], outRects);
1045 if (splitCount != -1) {
1046 mergeList.add(*mergeList[i]);
1047 mergeList.erase_at(i);
1048 showList.erase_at(j);
1049 didMerge = true;
1050 while (splitCount--) {
1051 mergeList.add(outRects[splitCount]);
1052 }
1053 break;
1054 }
1055 }
1056 }
1057 }
1058
1059 if (didMerge) {
1060 showList.pack();
1061 }
1062 }
1063 }
1064
1065 mergeList.pack();
1066 for (RectList::size_type i = 0; i < mergeList.size(); ++i) {
1067 showList.add(*mergeList[i]);
1068 }
1069 }
1070
showBits()1071 void GfxFrameout::showBits() {
1072 if (!_showList.size()) {
1073 updateScreen();
1074 return;
1075 }
1076
1077 for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {
1078 Common::Rect rounded(**rect);
1079 // SSCI uses BR-inclusive rects so has slightly different masking here
1080 // to ensure that the width of rects is always even
1081 rounded.left &= ~1;
1082 rounded.right = (rounded.right + 1) & ~1;
1083 _cursor->gonnaPaint(rounded);
1084 }
1085
1086 _cursor->paintStarting();
1087
1088 for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {
1089 Common::Rect rounded(**rect);
1090 // SSCI uses BR-inclusive rects so has slightly different masking here
1091 // to ensure that the width of rects is always even
1092 rounded.left &= ~1;
1093 rounded.right = (rounded.right + 1) & ~1;
1094
1095 byte *sourceBuffer = (byte *)_currentBuffer.getPixels() + rounded.top * _currentBuffer.w + rounded.left;
1096
1097 // Sometimes screen items (especially from SCI2.1early transitions, like
1098 // in the asteroids minigame in PQ4) generate zero-dimension show
1099 // rectangles. In SSCI, zero-dimension rectangles are OK (they just
1100 // result in no copy), but OSystem::copyRectToScreen will assert on
1101 // them, so we need to check for zero-dimensions rectangles and ignore
1102 // them explicitly
1103 if (rounded.width() == 0 || rounded.height() == 0) {
1104 continue;
1105 }
1106
1107 #ifdef USE_RGB_COLOR
1108 if (g_system->getScreenFormat() != _currentBuffer.format) {
1109 // This happens (at least) when playing a video in Shivers with
1110 // HQ video on & subtitles on
1111 Graphics::Surface *screenSurface = _currentBuffer.getSubArea(rounded).convertTo(g_system->getScreenFormat(), _palette->getHardwarePalette());
1112 assert(screenSurface);
1113 g_system->copyRectToScreen(screenSurface->getPixels(), screenSurface->pitch, rounded.left, rounded.top, screenSurface->w, screenSurface->h);
1114 screenSurface->free();
1115 delete screenSurface;
1116 } else {
1117 #else
1118 {
1119 #endif
1120 g_system->copyRectToScreen(sourceBuffer, _currentBuffer.w, rounded.left, rounded.top, rounded.width(), rounded.height());
1121 }
1122 }
1123
1124 _cursor->donePainting();
1125
1126 _showList.clear();
1127 updateScreen();
1128 }
1129
1130 void GfxFrameout::alterVmap(const Palette &palette1, const Palette &palette2, const int8 style, const int8 *const styleRanges) {
1131 uint8 clut[256];
1132
1133 for (int paletteIndex = 0; paletteIndex < ARRAYSIZE(palette1.colors); ++paletteIndex) {
1134 int outerR = palette1.colors[paletteIndex].r;
1135 int outerG = palette1.colors[paletteIndex].g;
1136 int outerB = palette1.colors[paletteIndex].b;
1137
1138 if (styleRanges[paletteIndex] == style) {
1139 int minDiff = 262140;
1140 int minDiffIndex = paletteIndex;
1141
1142 for (int i = 0; i < 236; ++i) {
1143 if (styleRanges[i] != style) {
1144 int r = palette1.colors[i].r;
1145 int g = palette1.colors[i].g;
1146 int b = palette1.colors[i].b;
1147 int diffSquared = (outerR - r) * (outerR - r) + (outerG - g) * (outerG - g) + (outerB - b) * (outerB - b);
1148 if (diffSquared < minDiff) {
1149 minDiff = diffSquared;
1150 minDiffIndex = i;
1151 }
1152 }
1153 }
1154
1155 clut[paletteIndex] = minDiffIndex;
1156 }
1157
1158 if (style == 1 && styleRanges[paletteIndex] == 0) {
1159 int minDiff = 262140;
1160 int minDiffIndex = paletteIndex;
1161
1162 for (int i = 0; i < 236; ++i) {
1163 int r = palette2.colors[i].r;
1164 int g = palette2.colors[i].g;
1165 int b = palette2.colors[i].b;
1166
1167 int diffSquared = (outerR - r) * (outerR - r) + (outerG - g) * (outerG - g) + (outerB - b) * (outerB - b);
1168 if (diffSquared < minDiff) {
1169 minDiff = diffSquared;
1170 minDiffIndex = i;
1171 }
1172 }
1173
1174 clut[paletteIndex] = minDiffIndex;
1175 }
1176 }
1177
1178 byte *pixels = (byte *)_currentBuffer.getPixels();
1179
1180 for (int pixelIndex = 0, numPixels = _currentBuffer.w * _currentBuffer.h; pixelIndex < numPixels; ++pixelIndex) {
1181 byte currentValue = pixels[pixelIndex];
1182 int8 styleRangeValue = styleRanges[currentValue];
1183 if (styleRangeValue == -1 && styleRangeValue == style) {
1184 currentValue = pixels[pixelIndex] = clut[currentValue];
1185 // In SSCI this assignment happens outside of the condition, but if
1186 // the branch is not followed the value is just going to be the same
1187 // as it was before, so we do it here instead
1188 styleRangeValue = styleRanges[currentValue];
1189 }
1190
1191 if (
1192 (styleRangeValue == 1 && styleRangeValue == style) ||
1193 (styleRangeValue == 0 && style == 1)
1194 ) {
1195 pixels[pixelIndex] = clut[currentValue];
1196 }
1197 }
1198 }
1199
1200 void GfxFrameout::updateScreen(const int delta) {
1201 // Using OSystem::getMillis instead of Sci::getTickCount here because these
1202 // values need to be monotonically increasing for the duration of the
1203 // GfxFrameout object or else the screen will stop updating
1204 const uint32 now = g_system->getMillis() * 60 / 1000;
1205 if (now <= _lastScreenUpdateTick + delta) {
1206 return;
1207 }
1208
1209 _lastScreenUpdateTick = now;
1210 g_system->updateScreen();
1211 g_sci->getSciDebugger()->onFrame();
1212 }
1213
1214 void GfxFrameout::kernelFrameOut(const bool shouldShowBits) {
1215 if (_transitions->hasShowStyles()) {
1216 _transitions->processShowStyles();
1217 } else if (_palMorphIsOn) {
1218 palMorphFrameOut(_transitions->_styleRanges, nullptr);
1219 _palMorphIsOn = false;
1220 } else {
1221 if (_transitions->hasScrolls()) {
1222 _transitions->processScrolls();
1223 }
1224
1225 frameOut(shouldShowBits);
1226 }
1227
1228 if (_throttleKernelFrameOut) {
1229 throttle();
1230 }
1231 }
1232
1233 void GfxFrameout::throttle() {
1234 uint8 throttleTime;
1235 if (_throttleState == 2) {
1236 throttleTime = 16;
1237 _throttleState = 0;
1238 } else {
1239 throttleTime = 17;
1240 ++_throttleState;
1241 }
1242
1243 g_sci->getEngineState()->speedThrottler(throttleTime);
1244 g_sci->getEngineState()->_throttleTrigger = true;
1245 }
1246
1247 void GfxFrameout::shakeScreen(int16 numShakes, const ShakeDirection direction) {
1248 while (numShakes--) {
1249 if (g_engine->shouldQuit()) {
1250 break;
1251 }
1252
1253 int shakeXOffset = 0;
1254 if (direction & kShakeHorizontal) {
1255 shakeXOffset = _isHiRes ? 8 : 4;
1256 }
1257
1258 int shakeYOffset = 0;
1259 if (direction & kShakeVertical) {
1260 shakeYOffset = _isHiRes ? 8 : 4;
1261 }
1262
1263 g_system->setShakePos(shakeXOffset, shakeYOffset);
1264
1265 updateScreen();
1266 g_sci->getEngineState()->sleep(3);
1267
1268 g_system->setShakePos(0, 0);
1269
1270 updateScreen();
1271 g_sci->getEngineState()->sleep(3);
1272 }
1273 }
1274
1275 #pragma mark -
1276 #pragma mark Mouse cursor
1277
1278 reg_t GfxFrameout::kernelIsOnMe(const reg_t object, const Common::Point &position, bool checkPixel) const {
1279 const reg_t planeObject = readSelector(_segMan, object, SELECTOR(plane));
1280 Plane *plane = _visiblePlanes.findByObject(planeObject);
1281 if (plane == nullptr) {
1282 return make_reg(0, 0);
1283 }
1284
1285 ScreenItem *screenItem = plane->_screenItemList.findByObject(object);
1286 if (screenItem == nullptr) {
1287 return make_reg(0, 0);
1288 }
1289
1290 // SSCI passed a copy of the ScreenItem into isOnMe as a hack around the
1291 // fact that the screen items in `_visiblePlanes` did not have their
1292 // `_celObj` pointers cleared when their CelInfo was updated by
1293 // `Plane::decrementScreenItemArrayCounts`. We handle this this more
1294 // intelligently by clearing `_celObj` in the copy assignment operator,
1295 // which is only ever called by `decrementScreenItemArrayCounts` anyway.
1296 return make_reg(0, isOnMe(*screenItem, *plane, position, checkPixel));
1297 }
1298
1299 bool GfxFrameout::isOnMe(const ScreenItem &screenItem, const Plane &plane, const Common::Point &position, const bool checkPixel) const {
1300
1301 Common::Point scaledPosition(position);
1302 mulru(scaledPosition, Ratio(_currentBuffer.w, _scriptWidth), Ratio(_currentBuffer.h, _scriptHeight));
1303 scaledPosition.x += plane._planeRect.left;
1304 scaledPosition.y += plane._planeRect.top;
1305
1306 if (!screenItem._screenRect.contains(scaledPosition)) {
1307 return false;
1308 }
1309
1310 if (checkPixel) {
1311 CelObj &celObj = screenItem.getCelObj();
1312
1313 bool mirrorX = screenItem._mirrorX ^ celObj._mirrorX;
1314
1315 scaledPosition.x -= screenItem._scaledPosition.x;
1316 scaledPosition.y -= screenItem._scaledPosition.y;
1317
1318 if (getSciVersion() < SCI_VERSION_2_1_LATE) {
1319 mulru(scaledPosition, Ratio(celObj._xResolution, _currentBuffer.w), Ratio(celObj._yResolution, _currentBuffer.h));
1320 }
1321
1322 if (screenItem._scale.signal != kScaleSignalNone && screenItem._scale.x && screenItem._scale.y) {
1323 scaledPosition.x = scaledPosition.x * 128 / screenItem._scale.x;
1324 scaledPosition.y = scaledPosition.y * 128 / screenItem._scale.y;
1325 }
1326
1327 // TODO/HACK: When clicking at the very bottom edge of a scaled cel, it
1328 // is possible that the calculated `scaledPosition` ends up one pixel
1329 // outside of the bounds of the cel. It is not known yet whether this is
1330 // a bug that also existed in SSCI (and so garbage memory would be read
1331 // there), or if there is actually an error in our scaling of
1332 // `ScreenItem::_screenRect` and/or `scaledPosition`. For now, just do
1333 // an extra bounds check and return so games don't crash when a user
1334 // clicks an unlucky point. Later, double-check the disassembly and
1335 // either confirm this is a suitable fix (because SSCI just read bad
1336 // memory) or fix the actual broken thing and remove this workaround.
1337 if (scaledPosition.x < 0 ||
1338 scaledPosition.y < 0 ||
1339 scaledPosition.x >= celObj._width ||
1340 scaledPosition.y >= celObj._height) {
1341
1342 return false;
1343 }
1344
1345 uint8 pixel = celObj.readPixel(scaledPosition.x, scaledPosition.y, mirrorX);
1346 return pixel != celObj._skipColor;
1347 }
1348
1349 return true;
1350 }
1351
1352 bool GfxFrameout::getNowSeenRect(const reg_t screenItemObject, Common::Rect &result) const {
1353 const reg_t planeObject = readSelector(_segMan, screenItemObject, SELECTOR(plane));
1354 const Plane *plane = _planes.findByObject(planeObject);
1355 if (plane == nullptr) {
1356 error("getNowSeenRect: Plane %04x:%04x not found for screen item %04x:%04x", PRINT_REG(planeObject), PRINT_REG(screenItemObject));
1357 }
1358
1359 const ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject);
1360 if (screenItem == nullptr) {
1361 // MGDX is assumed to use the older getNowSeenRect since it was released
1362 // before SQ6, but this has not been verified since it cannot be
1363 // disassembled at the moment (Phar Lap Windows-only release)
1364 // (See also kSetNowSeen32)
1365 if (getSciVersion() <= SCI_VERSION_2_1_EARLY ||
1366 g_sci->getGameId() == GID_SQ6 ||
1367 g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
1368
1369 error("getNowSeenRect: Unable to find screen item %04x:%04x", PRINT_REG(screenItemObject));
1370 }
1371
1372 warning("getNowSeenRect: Unable to find screen item %04x:%04x", PRINT_REG(screenItemObject));
1373 return false;
1374 }
1375
1376 result = screenItem->getNowSeenRect(*plane);
1377
1378 return true;
1379 }
1380
1381 bool GfxFrameout::kernelSetNowSeen(const reg_t screenItemObject) const {
1382 Common::Rect nsrect;
1383
1384 bool found = getNowSeenRect(screenItemObject, nsrect);
1385
1386 if (!found)
1387 return false;
1388
1389 if (g_sci->_features->usesAlternateSelectors()) {
1390 writeSelectorValue(_segMan, screenItemObject, SELECTOR(left), nsrect.left);
1391 writeSelectorValue(_segMan, screenItemObject, SELECTOR(top), nsrect.top);
1392 writeSelectorValue(_segMan, screenItemObject, SELECTOR(right), nsrect.right - 1);
1393 writeSelectorValue(_segMan, screenItemObject, SELECTOR(bottom), nsrect.bottom - 1);
1394 } else {
1395 writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsLeft), nsrect.left);
1396 writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsTop), nsrect.top);
1397 writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsRight), nsrect.right - 1);
1398 writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsBottom), nsrect.bottom - 1);
1399 }
1400 return true;
1401 }
1402
1403 int16 GfxFrameout::kernelObjectIntersect(const reg_t object1, const reg_t object2) const {
1404 Common::Rect nsrect1, nsrect2;
1405
1406 bool found1 = getNowSeenRect(object1, nsrect1);
1407 bool found2 = getNowSeenRect(object2, nsrect2);
1408
1409 // If both objects were not found, SSCI would probably return an
1410 // intersection area of 1 since SSCI's invalid/uninitialized rect has an
1411 // area of 1. We (mostly) ignore that corner case here.
1412 if (!found1 && !found2)
1413 warning("Both objects not found in kObjectIntersect");
1414
1415 // If one object was not found, SSCI would use its invalid/uninitialized
1416 // rect for it, which is at coordinates 0x89ABCDEF. This can't intersect
1417 // valid rects, so we return 0.
1418 if (!found1 || !found2)
1419 return 0;
1420
1421 const Common::Rect intersection = nsrect1.findIntersectingRect(nsrect2);
1422
1423 return intersection.width() * intersection.height();
1424 }
1425
1426 void GfxFrameout::remapMarkRedraw() {
1427 for (PlaneList::const_iterator it = _planes.begin(); it != _planes.end(); ++it) {
1428 Plane *p = *it;
1429 p->remapMarkRedraw();
1430 }
1431 }
1432
1433 #pragma mark -
1434 #pragma mark Debugging
1435
1436 Plane *GfxFrameout::getTopVisiblePlane() {
1437 for (PlaneList::const_iterator it = _visiblePlanes.begin(); it != _visiblePlanes.end(); ++it) {
1438 Plane *p = *it;
1439 if (p->_type == kPlaneTypePicture)
1440 return p;
1441 }
1442
1443 return nullptr;
1444 }
1445
1446 void GfxFrameout::printPlaneListInternal(Console *con, const PlaneList &planeList) const {
1447 for (PlaneList::const_iterator it = planeList.begin(); it != planeList.end(); ++it) {
1448 Plane *p = *it;
1449 p->printDebugInfo(con);
1450 }
1451 }
1452
1453 void GfxFrameout::printPlaneList(Console *con) const {
1454 printPlaneListInternal(con, _planes);
1455 }
1456
1457 void GfxFrameout::printVisiblePlaneList(Console *con) const {
1458 printPlaneListInternal(con, _visiblePlanes);
1459 }
1460
1461 void GfxFrameout::printPlaneItemListInternal(Console *con, const ScreenItemList &screenItemList) const {
1462 ScreenItemList::size_type i = 0;
1463 for (ScreenItemList::const_iterator sit = screenItemList.begin(); sit != screenItemList.end(); sit++) {
1464 ScreenItem *screenItem = *sit;
1465 con->debugPrintf("%2d: ", i++);
1466 screenItem->printDebugInfo(con);
1467 }
1468 }
1469
1470 void GfxFrameout::printPlaneItemList(Console *con, const reg_t planeObject) const {
1471 Plane *p = _planes.findByObject(planeObject);
1472
1473 if (p == nullptr) {
1474 con->debugPrintf("Plane does not exist");
1475 return;
1476 }
1477
1478 printPlaneItemListInternal(con, p->_screenItemList);
1479 }
1480
1481 void GfxFrameout::printVisiblePlaneItemList(Console *con, const reg_t planeObject) const {
1482 Plane *p = _visiblePlanes.findByObject(planeObject);
1483
1484 if (p == nullptr) {
1485 con->debugPrintf("Plane does not exist");
1486 return;
1487 }
1488
1489 printPlaneItemListInternal(con, p->_screenItemList);
1490 }
1491
1492 } // End of namespace Sci
1493