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