/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * */ #include "common/system.h" #include "toltecs/toltecs.h" #include "toltecs/render.h" #include "toltecs/resource.h" namespace Toltecs { Common::Rect makeRect(int16 x, int16 y, int16 width, int16 height) { Common::Rect rect; rect.left = x; rect.top = y; rect.setWidth(width); rect.setHeight(height); return rect; } RenderQueue::RenderQueue(ToltecsEngine *vm) : _vm(vm) { _currQueue = new RenderQueueArray(); _prevQueue = new RenderQueueArray(); _updateUta = new MicroTileArray(640, 400); } RenderQueue::~RenderQueue() { delete _currQueue; delete _prevQueue; delete _updateUta; } void RenderQueue::addSprite(SpriteDrawItem &sprite) { RenderQueueItem item; item.type = kSprite; item.flags = kRefresh; item.rect = makeRect(sprite.x - _vm->_cameraX, sprite.y - _vm->_cameraY, sprite.width, sprite.height); item.priority = sprite.priority; item.sprite = sprite; item.sprite.x -= _vm->_cameraX; item.sprite.y -= _vm->_cameraY; // Add sprite sorted by priority RenderQueueArray::iterator iter = _currQueue->begin(); while (iter != _currQueue->end() && (*iter).priority <= item.priority) { iter++; } _currQueue->insert(iter, item); } void RenderQueue::addText(int16 x, int16 y, byte color, uint fontResIndex, byte *text, int len) { // TODO: Font caching? Font font(_vm->_res->load(fontResIndex)->data); RenderQueueItem item; item.type = kText; item.flags = kRefresh; item.rect = makeRect(x, y, font.getTextWidth(text), font.getHeight()); item.priority = 1000; item.text.color = color; item.text.fontResIndex = fontResIndex; item.text.text = text; item.text.len = len; _currQueue->push_back(item); } void RenderQueue::addMask(SegmapMaskRect &mask) { RenderQueueItem item; item.type = kMask; item.flags = kRefresh; item.rect = makeRect(mask.x - _vm->_cameraX, mask.y - _vm->_cameraY, mask.width, mask.height); item.priority = mask.priority; item.mask = mask; // Only add the mask if a sprite intersects its rect if (rectIntersectsItem(item.rect)) { RenderQueueArray::iterator iter = _currQueue->begin(); while (iter != _currQueue->end() && (*iter).priority <= item.priority) { iter++; } _currQueue->insert(iter, item); } } void RenderQueue::update() { bool doFullRefresh = _vm->_screen->_fullRefresh; _updateUta->clear(); if (!doFullRefresh) { for (RenderQueueArray::iterator iter = _currQueue->begin(); iter != _currQueue->end(); iter++) { RenderQueueItem *item = &(*iter); RenderQueueItem *prevItem = findItemInQueue(_prevQueue, *item); if (prevItem) { if (hasItemChanged(*prevItem, *item)) { item->flags = kRefresh; addDirtyRect(prevItem->rect); } else { item->flags = kUnchanged; } } else { item->flags = kRefresh; } } for (RenderQueueArray::iterator iter = _prevQueue->begin(); iter != _prevQueue->end(); iter++) { RenderQueueItem *prevItem = &(*iter); RenderQueueItem *item = findItemInQueue(_currQueue, *prevItem); if (!item) { prevItem->flags = kRemoved; addDirtyRect(prevItem->rect); } } restoreDirtyBackground(); for (RenderQueueArray::iterator iter = _currQueue->begin(); iter != _currQueue->end(); iter++) { RenderQueueItem *item = &(*iter); if (item->flags != kUnchanged) invalidateItemsByRect(item->rect, item); } } else { byte *destp = _vm->_screen->_frontScreen; byte *srcp = _vm->_screen->_backScreen + _vm->_cameraX + _vm->_cameraY * _vm->_sceneWidth; int16 w = MIN(640, _vm->_sceneWidth); int16 h = MIN(400, _vm->_cameraHeight); while (h--) { memcpy(destp, srcp, w); destp += 640; srcp += _vm->_sceneWidth; } _vm->_screen->_fullRefresh = false; } for (RenderQueueArray::iterator iter = _currQueue->begin(); iter != _currQueue->end(); iter++) { const RenderQueueItem *item = &(*iter); if (item->flags == kRefresh || doFullRefresh) { switch (item->type) { case kSprite: _vm->_screen->drawSprite(item->sprite); break; case kText: _vm->_screen->drawString(item->rect.left, item->rect.top, item->text.color, item->text.fontResIndex, item->text.text, item->text.len, NULL, true); break; case kMask: _vm->_screen->drawSurface(item->rect.left, item->rect.top, item->mask.surface); break; default: break; } if (!doFullRefresh) addDirtyRect(item->rect); } } if (doFullRefresh) { clear(); _vm->_system->copyRectToScreen((const byte *)_vm->_screen->_frontScreen, 640, 0, 0, 640, _vm->_cameraHeight); } else { updateDirtyRects(); } SWAP(_currQueue, _prevQueue); _currQueue->clear(); } void RenderQueue::clear() { _prevQueue->clear(); _currQueue->clear(); } bool RenderQueue::rectIntersectsItem(const Common::Rect &rect) { for (RenderQueueArray::iterator iter = _currQueue->begin(); iter != _currQueue->end(); iter++) { const RenderQueueItem *item = &(*iter); if (rect.intersects(item->rect)) return true; } return false; } RenderQueueItem *RenderQueue::findItemInQueue(RenderQueueArray *queue, const RenderQueueItem &item) { /* This checks if the given item also exists in the previously drawn frame. The state of the item (position, color etc) is handled elsewhere. */ for (RenderQueueArray::iterator iter = queue->begin(); iter != queue->end(); iter++) { RenderQueueItem *prevItem = &(*iter); if (prevItem->type == item.type) { switch (item.type) { case kSprite: if (prevItem->sprite.resIndex == item.sprite.resIndex && prevItem->sprite.frameNum == item.sprite.frameNum) return prevItem; break; case kText: if (prevItem->text.text == item.text.text && prevItem->text.len == item.text.len) return prevItem; break; case kMask: if (prevItem->mask.surface == item.mask.surface) return prevItem; break; } } } return NULL; // Not found } bool RenderQueue::hasItemChanged(const RenderQueueItem &item1, const RenderQueueItem &item2) { // TODO: Clean up. if (item1.type != item1.type) return true; if (item1.rect.left != item2.rect.left || item1.rect.top != item2.rect.top || item1.rect.right != item2.rect.right || item1.rect.bottom != item2.rect.bottom) return true; if (item1.type == kText && item1.text.color != item2.text.color) return true; return false; } void RenderQueue::invalidateItemsByRect(const Common::Rect &rect, const RenderQueueItem *item) { for (RenderQueueArray::iterator iter = _currQueue->begin(); iter != _currQueue->end(); iter++) { RenderQueueItem *subItem = &(*iter); if (item != subItem && subItem->flags == kUnchanged && rect.intersects(subItem->rect)) { subItem->flags = kRefresh; invalidateItemsByRect(subItem->rect, subItem); } } } void RenderQueue::addDirtyRect(const Common::Rect &rect) { _updateUta->addRect(rect); } void RenderQueue::restoreDirtyBackground() { int n_rects = 0; Common::Rect *rects = _updateUta->getRectangles(&n_rects, 0, 0, 639, _vm->_cameraHeight - 1); for (int i = 0; i < n_rects; i++) { byte *destp = _vm->_screen->_frontScreen + rects[i].left + rects[i].top * 640; byte *srcp = _vm->_screen->_backScreen + (_vm->_cameraX + rects[i].left) + (_vm->_cameraY + rects[i].top) * _vm->_sceneWidth; int16 w = rects[i].width(); int16 h = rects[i].height(); while (h--) { memcpy(destp, srcp, w); destp += 640; srcp += _vm->_sceneWidth; } invalidateItemsByRect(rects[i], NULL); } delete[] rects; } void RenderQueue::updateDirtyRects() { int n_rects = 0; Common::Rect *rects = _updateUta->getRectangles(&n_rects, 0, 0, 639, _vm->_cameraHeight - 1); for (int i = 0; i < n_rects; i++) { _vm->_system->copyRectToScreen((const byte *)_vm->_screen->_frontScreen + rects[i].left + rects[i].top * 640, 640, rects[i].left, rects[i].top, rects[i].width(), rects[i].height()); } delete[] rects; } } // End of namespace Toltecs