/* 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 3 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, see . * */ #include "common/debug.h" #include "common/file.h" #include "common/scummsys.h" #include "common/stream.h" #include "common/system.h" #include "engines/util.h" #include "graphics/blit.h" #include "image/tga.h" #include "zvision/detection.h" #include "zvision/zvision.h" #include "zvision/file/lzss_read_stream.h" #include "zvision/graphics/render_manager.h" #include "zvision/scripting/script_manager.h" #include "zvision/text/text.h" namespace ZVision { RenderManager::RenderManager(ZVision *engine, const ScreenLayout layout, const Graphics::PixelFormat pixelFormat, bool doubleFPS, bool widescreen) : _engine(engine), _system(engine->_system), _layout(layout), _screenArea(layout.screenArea), _workingArea(layout.workingArea), _workingAreaCenter(Common::Point(_workingArea.width() / 2, _workingArea.height() / 2)), _textArea(layout.textArea), _menuArea(layout.menuArea), _pixelFormat(pixelFormat), _backgroundWidth(0), _backgroundHeight(0), _backgroundOffset(0), _renderTable(engine, layout.workingArea.width(), layout.workingArea.height(), pixelFormat), _doubleFPS(doubleFPS), _widescreen(widescreen), _frameLimiter(engine->_system, doubleFPS ? 60 : 30) { debugC(1, kDebugGraphics, "creating render manager"); // Define graphics modes & screen subarea geometry Graphics::ModeList modes; _textOffset = _layout.workingArea.origin() - _layout.textArea.origin(); modes.push_back(Graphics::Mode(_screenArea.width(), _screenArea.height())); #if defined(USE_MPEG2) && defined(USE_A52) if (_engine->getGameId() == GID_GRANDINQUISITOR && (_engine->getFeatures() & ADGF_DVD)) { if (_widescreen) modes.push_back(Graphics::Mode(_HDscreenAreaWide.width(), _HDscreenAreaWide.height())); else modes.push_back(Graphics::Mode(_HDscreenArea.width(), _HDscreenArea.height())); } #endif initGraphicsModes(modes); // Create backbuffers _backgroundSurface.create(_workingArea.width(), _workingArea.height(), _pixelFormat); _effectSurface.create(_workingArea.width(), _workingArea.height(), _pixelFormat); _warpedSceneSurface.create(_workingArea.width(), _workingArea.height(), _pixelFormat); _menuSurface.create(_menuArea.width(), _menuArea.height(), _pixelFormat); _textSurface.create(_textArea.width(), _textArea.height(), _pixelFormat); debugC(1, kDebugGraphics, "render manager created"); initialize(false); } RenderManager::~RenderManager() { _currentBackgroundImage.free(); _backgroundSurface.free(); _workingManagedSurface.free(); _effectSurface.free(); _warpedSceneSurface.free(); _menuSurface.free(); _menuManagedSurface.free(); _textSurface.free(); _textManagedSurface.free(); _screen.free(); } void RenderManager::initialize(bool hiRes) { debugC(1, kDebugGraphics, "Initializing render manager"); _hiRes = hiRes; _screenArea = _layout.screenArea; _workingArea = _layout.workingArea; _textArea = _layout.textArea; _menuArea = _layout.menuArea; if (_widescreen) { _workingArea.moveTo(0, 0); _screenArea = _workingArea; _menuArea.moveTo(_workingArea.origin()); _menuLetterbox.moveTo(_menuArea.origin()); _textArea.moveTo(_workingArea.left, _workingArea.bottom - _textArea.height()); _textLetterbox.moveTo(_textArea.origin()); } // Screen #if defined(USE_MPEG2) && defined(USE_A52) if (_hiRes) { debugC(1, kDebugGraphics, "Switching to high resolution"); upscaleRect(_screenArea); upscaleRect(_workingArea); upscaleRect(_textArea); } else debugC(1, kDebugGraphics, "Switching to standard resolution"); #endif _screen.create(_screenArea.width(), _screenArea.height(), _engine->_screenPixelFormat); _screen.setTransparentColor((uint32)-1); _screen.clear(); debugC(1, kDebugGraphics, "_workingAreaCenter = %d,%d", _workingAreaCenter.x, _workingAreaCenter.y); // Managed screen subsurfaces _workingManagedSurface.create(_screen, _workingArea); _menuManagedSurface.create(_screen, _menuArea); _textManagedSurface.create(_screen, _textArea); debugC(2, kDebugGraphics, "screen area: %d,%d,%d,%d", _screenArea.left, _screenArea.top, _screenArea.bottom, _screenArea.right); debugC(2, kDebugGraphics, "working area: %d,%d,%d,%d", _workingArea.left, _workingArea.top, _workingArea.bottom, _workingArea.right); debugC(2, kDebugGraphics, "text area: %d,%d,%d,%d", _textArea.left, _textArea.top, _textArea.bottom, _textArea.right); // Menu & text area dirty rectangles _menuOverlay = _menuArea.findIntersectingRect(_workingArea); if (!_menuOverlay.isEmpty() && _menuArea.left >= _workingArea.left && _menuArea.right <= _workingArea.right) _menuLetterbox = Common::Rect(_menuArea.left, _menuArea.top, _menuArea.right, _workingArea.top); else _menuLetterbox = _menuArea; _textOverlay = _textArea.findIntersectingRect(_workingArea); if (!_textOverlay.isEmpty() && _textArea.left >= _workingArea.left && _textArea.right <= _workingArea.right) _textLetterbox = Common::Rect(_textArea.left, _workingArea.bottom, _textArea.right, _textArea.bottom); else _textLetterbox = _textArea; debugC(2, kDebugGraphics, "text overlay area: %d,%d,%d,%d", _textOverlay.left, _textOverlay.top, _textOverlay.bottom, _textOverlay.right); debugC(2, kDebugGraphics, "menu overlay area: %d,%d,%d,%d", _menuOverlay.left, _menuOverlay.top, _menuOverlay.bottom, _menuOverlay.right); debugC(2, kDebugGraphics, "Clearing backbuffers"); // Clear backbuffer surfaces clearMenuSurface(true); clearTextSurface(true); debugC(2, kDebugGraphics, "Backbuffers cleared"); // Set hardware/window resolution debugC(1, kDebugGraphics, "_screen.w = %d, _screen.h = %d", _screen.w, _screen.h); initGraphics(_screen.w, _screen.h, &_engine->_screenPixelFormat); _frameLimiter.initialize(); debugC(1, kDebugGraphics, "Render manager initialized"); } bool RenderManager::renderSceneToScreen(bool immediate, bool overlayOnly, bool preStream) { debugC(5, kDebugGraphics, "\nrenderSceneToScreen%s%s%s", immediate ? ", immediate" : "", overlayOnly ? ", overlay only" : "", preStream ? ", pre-stream" : ""); uint32 startTime = _system->getMillis(); if (!overlayOnly) { Graphics::Surface *inputSurface = &_backgroundSurface; Common::Rect outWndDirtyRect; // Apply graphical effects to temporary effects buffer and/or directly to current background image, as appropriate if (!_effects.empty()) { debugC(6, kDebugGraphics, "Rendering effects"); bool copied = false; const Common::Rect windowRect(_workingArea.width(), _workingArea.height()); for (EffectsList::iterator it = _effects.begin(); it != _effects.end(); it++) { Common::Rect rect = (*it)->getRegion(); Common::Rect screenSpaceLocation = rect; if ((*it)->isPort()) screenSpaceLocation = transformBackgroundSpaceRectToScreenSpace(screenSpaceLocation); if (windowRect.intersects(screenSpaceLocation)) { if (!copied) { copied = true; _effectSurface.copyFrom(_backgroundSurface); inputSurface = &_effectSurface; } const Graphics::Surface *post; if ((*it)->isPort()) post = (*it)->draw(_currentBackgroundImage.getSubArea(rect)); else post = (*it)->draw(_effectSurface.getSubArea(rect)); Common::Rect empty; blitSurfaceToSurface(*post, empty, _effectSurface, screenSpaceLocation.left, screenSpaceLocation.top); debugC(1, kDebugGraphics, "windowRect %d,%d,%d,%d, screenSpaceLocation %d,%d,%d,%d", windowRect.left, windowRect.top, windowRect.bottom, windowRect.right, screenSpaceLocation.left, screenSpaceLocation.top, screenSpaceLocation.bottom, screenSpaceLocation.right); screenSpaceLocation.clip(windowRect); if (_backgroundSurfaceDirtyRect.isEmpty()) _backgroundSurfaceDirtyRect = screenSpaceLocation; else _backgroundSurfaceDirtyRect.extend(screenSpaceLocation); } } debugC(5, kDebugGraphics, "\tCumulative render time this frame: %d ms", _system->getMillis() - startTime); } // Apply panorama/tilt warp to background image switch (_renderTable.getRenderState()) { case RenderTable::PANORAMA: case RenderTable::TILT: debugC(5, kDebugGraphics, "Rendering panorama"); if (!_backgroundSurfaceDirtyRect.isEmpty()) { _renderTable.mutateImage(&_warpedSceneSurface, inputSurface, _engine->getScriptManager()->getStateValue(StateKey_HighQuality)); _outputSurface = &_warpedSceneSurface; outWndDirtyRect = Common::Rect(_workingArea.width(), _workingArea.height()); } break; default: _outputSurface = inputSurface; outWndDirtyRect = _backgroundSurfaceDirtyRect; break; } debugC(5, kDebugGraphics, "\tCumulative render time this frame: %d ms", _system->getMillis() - startTime); debugC(5, kDebugGraphics, "Rendering working area"); _workingManagedSurface.simpleBlitFrom(*_outputSurface); // TODO - use member functions of managed surface to eliminate manual juggling of dirty rectangles, above. debugC(5, kDebugGraphics, "\tCumulative render time this frame: %d ms", _system->getMillis() - startTime); } if (preStream && !_hiRes) { debugC(5, kDebugGraphics, "Pre-rendering text area for video stream"); _workingManagedSurface.simpleBlitFrom(*_outputSurface, _textOverlay, _textOverlay.origin()); // Prevents subtitle visual corruption when streaming videos that don't fully overlap them, e.g. Nemesis sarcophagi return false; } else { debugC(5, kDebugGraphics, "Rendering menu"); _menuManagedSurface.transBlitFrom(_menuSurface, (uint32)-1); debugC(5, kDebugGraphics, "\tCumulative render time this frame: %d ms", _system->getMillis() - startTime); debugC(5, kDebugGraphics, "Rendering text"); _textManagedSurface.transBlitFrom(_textSurface, (uint32)-1); debugC(5, kDebugGraphics, "\tCumulative render time this frame: %d ms", _system->getMillis() - startTime); if (immediate) { _frameLimiter.startFrame(); debugC(5, kDebugGraphics, "Updating screen, immediate"); _screen.update(); debugC(5, kDebugGraphics, "\tCumulative render time this frame: %d ms", _system->getMillis() - startTime); debugC(10, kDebugGraphics, "~renderSceneToScreen, immediate"); return true; } else if (_engine->canRender()) { _frameLimiter.delayBeforeSwap(); _frameLimiter.startFrame(); debugC(5, kDebugGraphics, "Updating screen, frame limited"); _screen.update(); debugC(5, kDebugGraphics, "\tCumulative render time this frame: %d ms", _system->getMillis() - startTime); debugC(10, kDebugGraphics, "~renderSceneToScreen, frame limited"); return true; } else { debugC(2, kDebugGraphics, "Skipping screen update; engine forbids rendering at this time."); return false; } } } Graphics::ManagedSurface &RenderManager::getVidSurface(Common::Rect dstRect) { dstRect.translate(_workingArea.left, _workingArea.top); // Convert to screen coordinates _vidManagedSurface.create(_screen, dstRect); debugC(1, kDebugGraphics, "Obtaining managed video surface at %d,%d,%d,%d", dstRect.left, dstRect.top, dstRect.right, dstRect.bottom); return _vidManagedSurface; } void RenderManager::renderImageToBackground(const Common::Path &fileName, int16 destX, int16 destY) { Graphics::Surface surface; readImageToSurface(fileName, surface); blitSurfaceToBkg(surface, destX, destY); surface.free(); } void RenderManager::renderImageToBackground(const Common::Path &fileName, int16 destX, int16 destY, uint32 colorkey) { Graphics::Surface surface; readImageToSurface(fileName, surface); blitSurfaceToBkg(surface, destX, destY, colorkey); surface.free(); } void RenderManager::renderImageToBackground(const Common::Path &fileName, int16 destX, int16 destY, int16 keyX, int16 keyY) { Graphics::Surface surface; readImageToSurface(fileName, surface); uint16 keycolor = *(uint16 *)surface.getBasePtr(keyX, keyY); blitSurfaceToBkg(surface, destX, destY, keycolor); surface.free(); } void RenderManager::readImageToSurface(const Common::Path &fileName, Graphics::Surface &destination) { bool isTransposed = _renderTable.getRenderState() == RenderTable::PANORAMA; readImageToSurface(fileName, destination, isTransposed); } void RenderManager::readImageToSurface(const Common::Path &fileName, Graphics::Surface &destination, bool transposed) { Common::File file; if (!file.open(fileName)) { warning("Could not open file %s", fileName.toString().c_str()); return; } // Read the magic number // Some files are true TGA, while others are TGZ uint32 fileType = file.readUint32BE(); int imageWidth; int imageHeight; Image::TGADecoder tga; uint16 *buffer; // All Z-Vision images are in RGB 555 destination.format = _engine->_resourcePixelFormat; bool isTGZ; // Check for TGZ files if (fileType == MKTAG('T', 'G', 'Z', '\0')) { isTGZ = true; // TGZ files have a header and then Bitmap data that is compressed with LZSS uint32 decompressedSize = file.readSint32LE() / 2; imageWidth = file.readSint32LE(); imageHeight = file.readSint32LE(); LzssReadStream lzssStream(&file); buffer = (uint16 *)(new uint16[decompressedSize]); lzssStream.read(buffer, 2 * decompressedSize); #ifndef SCUMM_LITTLE_ENDIAN for (uint32 i = 0; i < decompressedSize; ++i) buffer[i] = FROM_LE_16(buffer[i]); #endif } else { isTGZ = false; // Reset the cursor file.seek(0); // Decode if (!tga.loadStream(file)) { warning("Error while reading TGA image"); return; } Graphics::Surface tgaSurface = *(tga.getSurface()); imageWidth = tgaSurface.w; imageHeight = tgaSurface.h; buffer = (uint16 *)tgaSurface.getPixels(); } // Flip the width and height if transposed if (transposed) { SWAP(imageWidth, imageHeight); } // If the destination internal buffer is the same size as what we're copying into it, // there is no need to free() and re-create if (imageWidth != destination.w || imageHeight != destination.h) { destination.create(imageWidth, imageHeight, _engine->_resourcePixelFormat); } // If transposed, 'un-transpose' the data while copying it to the destination // Otherwise, just do a simple copy if (transposed) { uint16 *dest = (uint16 *)destination.getPixels(); for (int y = 0; y < imageHeight; ++y) { uint32 columnIndex = y * imageWidth; for (int x = 0; x < imageWidth; ++x) { dest[columnIndex + x] = buffer[x * imageHeight + y]; } } } else { memcpy(destination.getPixels(), buffer, imageWidth * imageHeight * destination.format.bytesPerPixel); } // Cleanup if (isTGZ) { delete[] buffer; } else { tga.destroy(); } } const Common::Point RenderManager::screenSpaceToImageSpace(const Common::Point &point) { debugC(9, kDebugGraphics, "screenSpaceToImageSpace()"); if (_workingArea.contains(point)) { // Convert from screen space to working image space, i.e. panoramic background image or static image Common::Point newPoint(point - _workingArea.origin()); switch (_renderTable.getRenderState()) { case RenderTable::PANORAMA: newPoint = _renderTable.convertWarpedCoordToFlatCoord(newPoint); newPoint += (Common::Point(_backgroundOffset - _workingAreaCenter.x, 0)); break; case RenderTable::TILT: newPoint = _renderTable.convertWarpedCoordToFlatCoord(newPoint); newPoint += (Common::Point(0, _backgroundOffset - _workingAreaCenter.y)); break; default: break; } if (_backgroundWidth) newPoint.x %= _backgroundWidth; if (_backgroundHeight) newPoint.y %= _backgroundHeight; if (newPoint.x < 0) newPoint.x += _backgroundWidth; if (newPoint.y < 0) newPoint.y += _backgroundHeight; debugC(9, kDebugGraphics, "~screenSpaceToImageSpace()"); return newPoint; } else { debugC(9, kDebugGraphics, "~screenSpaceToImageSpace()"); return Common::Point(0, 0); } } RenderTable *RenderManager::getRenderTable() { return &_renderTable; } void RenderManager::setBackgroundImage(const Common::Path &fileName) { readImageToSurface(fileName, _currentBackgroundImage); _backgroundWidth = _currentBackgroundImage.w; _backgroundHeight = _currentBackgroundImage.h; _backgroundDirtyRect = Common::Rect(_backgroundWidth, _backgroundHeight); } void RenderManager::setBackgroundPosition(int offset) { switch (_renderTable.getRenderState()) { case RenderTable::PANORAMA: case RenderTable::TILT: if (_backgroundOffset != offset) _backgroundDirtyRect = Common::Rect(_backgroundWidth, _backgroundHeight); break; default: break; } _backgroundOffset = offset; _engine->getScriptManager()->setStateValue(StateKey_ViewPos, offset); } uint32 RenderManager::getCurrentBackgroundOffset() { switch (_renderTable.getRenderState()) { case RenderTable::PANORAMA: case RenderTable::TILT: return _backgroundOffset; default: return 0; } } Graphics::Surface *RenderManager::tranposeSurface(const Graphics::Surface *surface) { Graphics::Surface *tranposedSurface = new Graphics::Surface(); tranposedSurface->create(surface->h, surface->w, surface->format); const uint16 *source = (const uint16 *)surface->getPixels(); uint16 *dest = (uint16 *)tranposedSurface->getPixels(); for (int y = 0; y < tranposedSurface->h; ++y) { int columnIndex = y * tranposedSurface->w; for (int x = 0; x < tranposedSurface->w; ++x) { dest[columnIndex + x] = source[x * surface->w + y]; } } return tranposedSurface; } void RenderManager::scaleBuffer(const void *src, void *dst, uint32 srcWidth, uint32 srcHeight, byte bytesPerPixel, uint32 dstWidth, uint32 dstHeight) { assert(bytesPerPixel == 1 || bytesPerPixel == 2); const float xscale = (float)srcWidth / (float)dstWidth; const float yscale = (float)srcHeight / (float)dstHeight; if (bytesPerPixel == 1) { const byte *srcPtr = (const byte *)src; byte *dstPtr = (byte *)dst; for (uint32 y = 0; y < dstHeight; ++y) { for (uint32 x = 0; x < dstWidth; ++x) { *dstPtr = srcPtr[(int)(x * xscale) + (int)(y * yscale) * srcWidth]; dstPtr++; } } } else if (bytesPerPixel == 2) { const uint16 *srcPtr = (const uint16 *)src; uint16 *dstPtr = (uint16 *)dst; for (uint32 y = 0; y < dstHeight; ++y) { for (uint32 x = 0; x < dstWidth; ++x) { *dstPtr = srcPtr[(int)(x * xscale) + (int)(y * yscale) * srcWidth]; dstPtr++; } } } } void RenderManager::blitSurfaceToSurface(const Graphics::Surface &src, Common::Rect srcRect, Graphics::Surface &dst, int _x, int _y) { debugC(9, kDebugGraphics, "blitSurfaceToSurface"); Common::Point dstPos = Common::Point(_x, _y); // Default to using whole source surface if (srcRect.isEmpty()) srcRect = Common::Rect(src.w, src.h); // Clip source rectangle to within bounds of source buffer srcRect.clip(src.w, src.h); // Generate destination rectangle Common::Rect dstRect = Common::Rect(dst.w, dst.h); // Translate destination rectangle to its position relative to source rectangle dstRect.translate(srcRect.left - _x, srcRect.top - _y); // clip source rectangle to within bounds of offset destination rectangle srcRect.clip(dstRect); // Abort if nothing to blit if (!srcRect.isEmpty()) { // Convert pixel format of source to match destination Graphics::Surface *srcAdapted = src.convertTo(dst.format); // Get pointer for source buffer blit rectangle origin const byte *srcBuffer = (const byte *)srcAdapted->getBasePtr(srcRect.left, srcRect.top); // Default to blitting into origin of target surface if negative valued if (dstPos.x < 0) dstPos.x = 0; if (dstPos.y < 0) dstPos.y = 0; // If _x & _y lie within destination surface if (dstPos.x < dst.w && dstPos.y < dst.h) { // Get pointer for destination buffer blit rectangle origin byte *dstBuffer = (byte *)dst.getBasePtr(dstPos.x, dstPos.y); Graphics::copyBlit(dstBuffer, srcBuffer, dst.pitch, srcAdapted->pitch, srcRect.width(), srcRect.height(), srcAdapted->format.bytesPerPixel); } srcAdapted->free(); delete srcAdapted; } } void RenderManager::blitSurfaceToSurface(const Graphics::Surface &src, Common::Rect srcRect , Graphics::Surface &dst, int _x, int _y, uint32 colorkey) { debugC(9, kDebugGraphics, "blitSurfaceToSurface"); if (srcRect.isEmpty()) srcRect = Common::Rect(src.w, src.h); srcRect.clip(src.w, src.h); Common::Rect dstRect = Common::Rect(-_x + srcRect.left , -_y + srcRect.top, -_x + srcRect.left + dst.w, -_y + srcRect.top + dst.h); srcRect.clip(dstRect); // Abort if nothing to blit if (srcRect.isEmpty() || !srcRect.isValidRect()) return; Graphics::Surface *srcAdapted = src.convertTo(dst.format); uint32 keycolor = colorkey & ((1 << (src.format.bytesPerPixel << 3)) - 1); // Copy srcRect from src surface to dst surface const byte *srcBuffer = (const byte *)srcAdapted->getBasePtr(srcRect.left, srcRect.top); int xx = _x; int yy = _y; if (xx < 0) xx = 0; if (yy < 0) yy = 0; if (_x < dst.w && _y < dst.h) { byte *dstBuffer = (byte *)dst.getBasePtr(xx, yy); Graphics::keyBlit(dstBuffer, srcBuffer, dst.pitch, srcAdapted->pitch, srcRect.width(), srcRect.height(), srcAdapted->format.bytesPerPixel, keycolor); } srcAdapted->free(); delete srcAdapted; } void RenderManager::blitSurfaceToBkg(const Graphics::Surface &src, int x, int y, int32 colorkey) { if (colorkey >= 0) blitSurfaceToSurface(src, _currentBackgroundImage, x, y, colorkey); else blitSurfaceToSurface(src, _currentBackgroundImage, x, y); Common::Rect dirty(src.w, src.h); dirty.translate(x, y); if (_backgroundDirtyRect.isEmpty()) _backgroundDirtyRect = dirty; else _backgroundDirtyRect.extend(dirty); } void RenderManager::blitSurfaceToBkgScaled(const Graphics::Surface &src, const Common::Rect &dstRect, int32 colorkey) { if (src.w == dstRect.width() && src.h == dstRect.height()) { blitSurfaceToBkg(src, dstRect.left, dstRect.top, colorkey); } else { Graphics::Surface *tmp = new Graphics::Surface; tmp->create(dstRect.width(), dstRect.height(), src.format); scaleBuffer(src.getPixels(), tmp->getPixels(), src.w, src.h, src.format.bytesPerPixel, dstRect.width(), dstRect.height()); blitSurfaceToBkg(*tmp, dstRect.left, dstRect.top, colorkey); tmp->free(); delete tmp; } } void RenderManager::blitSurfaceToMenu(const Graphics::Surface &src, int16 x, int16 y, int32 colorkey) { blitSurfaceToSurface(src, _menuSurface, x, y, colorkey); Common::Rect dirty(src.w, src.h); dirty.moveTo(x, y); if (_menuSurfaceDirtyRect.isEmpty()) _menuSurfaceDirtyRect = dirty; else _menuSurfaceDirtyRect.extend(dirty); } void RenderManager::clearMenuSurface(bool force, int32 colorkey) { if (force) _menuSurfaceDirtyRect = Common::Rect(_menuArea.width(), _menuArea.height()); if (!_menuSurfaceDirtyRect.isEmpty()) { // Convert to local menuArea coordinates Common::Rect letterbox = _menuLetterbox; Common::Rect overlay = _menuOverlay; letterbox.translate(-_menuArea.left, -_menuArea.top); overlay.translate(-_menuArea.left, -_menuArea.top); Common::Rect _menuLetterboxDirty = _menuSurfaceDirtyRect.findIntersectingRect(letterbox); if (!_menuLetterboxDirty.isEmpty()) _menuSurface.fillRect(_menuLetterboxDirty, 0); Common::Rect _menuOverlayDirty = _menuSurfaceDirtyRect.findIntersectingRect(overlay); if (!_menuOverlayDirty.isEmpty()) { _menuSurface.fillRect(_menuOverlayDirty, colorkey); // TODO - mark working window dirty here so that it will redraw & blank overlaid residue on next frame } _menuSurfaceDirtyRect = Common::Rect(0, 0); } } void RenderManager::blitSurfaceToText(const Graphics::Surface &src, int16 x, int16 y, int32 colorkey) { blitSurfaceToSurface(src, _textSurface, x, y, colorkey); Common::Rect dirty(src.w, src.h); dirty.moveTo(x, y); if (_textSurfaceDirtyRect.isEmpty()) _textSurfaceDirtyRect = dirty; else _textSurfaceDirtyRect.extend(dirty); } void RenderManager::clearTextSurface(bool force, int32 colorkey) { if (force) _textSurfaceDirtyRect = Common::Rect(_textArea.width(), _textArea.height()); if (!_textSurfaceDirtyRect.isEmpty()) { // Convert to local textArea coordinates Common::Rect letterbox = _textLetterbox; Common::Rect overlay = _textOverlay; letterbox.translate(-_textArea.left, -_textArea.top); overlay.translate(-_textArea.left, -_textArea.top); Common::Rect _textLetterboxDirty = _textSurfaceDirtyRect.findIntersectingRect(letterbox); if (!_textLetterboxDirty.isEmpty()) _textSurface.fillRect(_textLetterboxDirty, 0); Common::Rect _textOverlayDirty = _textSurfaceDirtyRect.findIntersectingRect(overlay); if (!_textOverlayDirty.isEmpty()) { _textSurface.fillRect(_textOverlayDirty, colorkey); // TODO - mark working window dirty here so that it will redraw & blank overlaid residue on next frame } _textSurfaceDirtyRect = Common::Rect(0, 0); } } Graphics::Surface *RenderManager::getBkgRect(Common::Rect &rect) { debugC(11, kDebugGraphics, "getBkgRect()"); Common::Rect dst = rect; dst.clip(_backgroundWidth, _backgroundHeight); if (dst.isEmpty() || !dst.isValidRect()) return NULL; Graphics::Surface *srf = new Graphics::Surface; srf->create(dst.width(), dst.height(), _currentBackgroundImage.format); srf->copyRectToSurface(_currentBackgroundImage, 0, 0, Common::Rect(dst)); return srf; } Graphics::Surface *RenderManager::loadImage(const Common::Path &file) { Graphics::Surface *tmp = new Graphics::Surface; readImageToSurface(file, *tmp); return tmp; } Graphics::Surface *RenderManager::loadImage(const Common::Path &file, bool transposed) { Graphics::Surface *tmp = new Graphics::Surface; readImageToSurface(file, *tmp, transposed); return tmp; } void RenderManager::prepareBackground() { debugC(5, kDebugGraphics, "prepareBackground()"); _backgroundDirtyRect.clip(_backgroundWidth, _backgroundHeight); switch (_renderTable.getRenderState()) { case RenderTable::PANORAMA: { // Calculate the visible portion of the background Common::Rect viewPort(_workingArea.width(), _workingArea.height()); viewPort.translate(-(_workingAreaCenter.x - _backgroundOffset), 0); Common::Rect drawRect = _backgroundDirtyRect; drawRect.clip(viewPort); // Render the visible portion if (!drawRect.isEmpty()) { blitSurfaceToSurface(_currentBackgroundImage, drawRect, _backgroundSurface, _workingAreaCenter.x - _backgroundOffset + drawRect.left, drawRect.top); } // Mark the dirty portion of the surface _backgroundSurfaceDirtyRect = _backgroundDirtyRect; _backgroundSurfaceDirtyRect.translate(_workingAreaCenter.x - _backgroundOffset, 0); // Panorama mode allows the user to spin in circles. Therefore, we need to render // the portion of the image that wrapped to the other side of the screen if (_backgroundOffset < _workingAreaCenter.x) { viewPort.moveTo(-(_workingAreaCenter.x - (_backgroundOffset + _backgroundWidth)), 0); drawRect = _backgroundDirtyRect; drawRect.clip(viewPort); if (!drawRect.isEmpty()) blitSurfaceToSurface(_currentBackgroundImage, drawRect, _backgroundSurface, _workingAreaCenter.x - (_backgroundOffset + _backgroundWidth) + drawRect.left, drawRect.top); Common::Rect tmp = _backgroundDirtyRect; tmp.translate(_workingAreaCenter.x - (_backgroundOffset + _backgroundWidth), 0); if (!tmp.isEmpty()) _backgroundSurfaceDirtyRect.extend(tmp); } else if (_backgroundWidth - _backgroundOffset < _workingAreaCenter.x) { viewPort.moveTo(-(_workingAreaCenter.x + _backgroundWidth - _backgroundOffset), 0); drawRect = _backgroundDirtyRect; drawRect.clip(viewPort); if (!drawRect.isEmpty()) blitSurfaceToSurface(_currentBackgroundImage, drawRect, _backgroundSurface, _workingAreaCenter.x + _backgroundWidth - _backgroundOffset + drawRect.left, drawRect.top); Common::Rect tmp = _backgroundDirtyRect; tmp.translate(_workingAreaCenter.x + _backgroundWidth - _backgroundOffset, 0); if (!tmp.isEmpty()) _backgroundSurfaceDirtyRect.extend(tmp); } break; } case RenderTable::TILT: { // Tilt doesn't allow wrapping, so we just do a simple clip Common::Rect viewPort(_workingArea.width(), _workingArea.height()); viewPort.translate(0, -(_workingAreaCenter.y - _backgroundOffset)); Common::Rect drawRect = _backgroundDirtyRect; drawRect.clip(viewPort); if (!drawRect.isEmpty()) blitSurfaceToSurface(_currentBackgroundImage, drawRect, _backgroundSurface, drawRect.left, _workingAreaCenter.y - _backgroundOffset + drawRect.top); // Mark the dirty portion of the surface _backgroundSurfaceDirtyRect = _backgroundDirtyRect; _backgroundSurfaceDirtyRect.translate(0, _workingAreaCenter.y - _backgroundOffset); break; } default: { if (!_backgroundDirtyRect.isEmpty()) blitSurfaceToSurface(_currentBackgroundImage, _backgroundDirtyRect, _backgroundSurface, _backgroundDirtyRect.left, _backgroundDirtyRect.top); _backgroundSurfaceDirtyRect = _backgroundDirtyRect; break; } } // Clear the dirty rect since everything is clean now _backgroundDirtyRect = Common::Rect(); _backgroundSurfaceDirtyRect.clip(_workingArea.width(), _workingArea.height()); debugC(11, kDebugGraphics, "~prepareBackground()"); } Common::Point RenderManager::getBkgSize() { return Common::Point(_backgroundWidth, _backgroundHeight); } void RenderManager::addEffect(GraphicsEffect *_effect) { _effects.push_back(_effect); } void RenderManager::deleteEffect(uint32 ID) { for (EffectsList::iterator it = _effects.begin(); it != _effects.end(); it++) { if ((*it)->getKey() == ID) { delete *it; it = _effects.erase(it); } } } Common::Rect RenderManager::transformBackgroundSpaceRectToScreenSpace(const Common::Rect &src) { debugC(10, kDebugGraphics, "transformBackgroundSpaceRectToScreenSpace"); Common::Rect tmp = src; switch (_renderTable.getRenderState()) { case RenderTable::PANORAMA: { if (_backgroundOffset < _workingAreaCenter.x) { Common::Rect rScreen(_workingAreaCenter.x + _backgroundOffset, _workingArea.height()); Common::Rect lScreen(_workingArea.width() - rScreen.width(), _workingArea.height()); lScreen.translate(_backgroundWidth - lScreen.width(), 0); lScreen.clip(src); rScreen.clip(src); if (rScreen.width() < lScreen.width()) tmp.translate(_workingAreaCenter.x - _backgroundOffset - _backgroundWidth, 0); else tmp.translate(_workingAreaCenter.x - _backgroundOffset, 0); } else if (_backgroundWidth - _backgroundOffset < _workingAreaCenter.x) { Common::Rect rScreen(_workingAreaCenter.x - (_backgroundWidth - _backgroundOffset), _workingArea.height()); Common::Rect lScreen(_workingArea.width() - rScreen.width(), _workingArea.height()); lScreen.translate(_backgroundWidth - lScreen.width(), 0); lScreen.clip(src); rScreen.clip(src); if (lScreen.width() < rScreen.width()) tmp.translate(_workingAreaCenter.x + (_backgroundWidth - _backgroundOffset), 0); else tmp.translate(_workingAreaCenter.x - _backgroundOffset, 0); } else tmp.translate(_workingAreaCenter.x - _backgroundOffset, 0); break; } case RenderTable::TILT: tmp.translate(0, (_workingAreaCenter.y - _backgroundOffset)); break; default: break; } return tmp; } EffectMap *RenderManager::makeEffectMap(const Common::Point &xy, int16 depth, const Common::Rect &rect, int8 *_minComp, int8 *_maxComp) { Common::Rect bkgRect(_backgroundWidth, _backgroundHeight); if (!bkgRect.contains(xy)) return NULL; if (!bkgRect.intersects(rect)) return NULL; uint16 color = *(uint16 *)_currentBackgroundImage.getBasePtr(xy.x, xy.y); uint8 stC1, stC2, stC3; _currentBackgroundImage.format.colorToRGB(color, stC1, stC2, stC3); EffectMap *newMap = new EffectMap; EffectMapUnit unit; unit.count = 0; unit.inEffect = false; int16 w = rect.width(); int16 h = rect.height(); bool first = true; uint8 minComp = MIN(MIN(stC1, stC2), stC3); uint8 maxComp = MAX(MAX(stC1, stC2), stC3); uint8 depth8 = depth << 3; for (int16 j = 0; j < h; j++) { uint16 *pix = (uint16 *)_currentBackgroundImage.getBasePtr(rect.left, rect.top + j); for (int16 i = 0; i < w; i++) { uint16 curClr = pix[i]; uint8 cC1, cC2, cC3; _currentBackgroundImage.format.colorToRGB(curClr, cC1, cC2, cC3); bool use = false; if (curClr == color) use = true; else if (curClr > color) { if ((cC1 - stC1 < depth8) && (cC2 - stC2 < depth8) && (cC3 - stC3 < depth8)) use = true; } else { /* if (curClr < color) */ if ((stC1 - cC1 < depth8) && (stC2 - cC2 < depth8) && (stC3 - cC3 < depth8)) use = true; } if (first) { unit.inEffect = use; first = false; } if (use) { uint8 cMinComp = MIN(MIN(cC1, cC2), cC3); uint8 cMaxComp = MAX(MAX(cC1, cC2), cC3); if (cMinComp < minComp) minComp = cMinComp; if (cMaxComp > maxComp) maxComp = cMaxComp; } if (unit.inEffect == use) unit.count++; else { newMap->push_back(unit); unit.count = 1; unit.inEffect = use; } } } newMap->push_back(unit); if (_minComp) { if (minComp - depth8 < 0) *_minComp = -(minComp >> 3); else *_minComp = -depth; } if (_maxComp) { if ((int16)maxComp + (int16)depth8 > 255) *_maxComp = (255 - maxComp) >> 3; else *_maxComp = depth; } return newMap; } EffectMap *RenderManager::makeEffectMap(const Graphics::Surface &surf, uint16 transp) { EffectMapUnit unit; unit.count = 0; unit.inEffect = false; int16 w = surf.w; int16 h = surf.h; EffectMap *newMap = new EffectMap; bool first = true; for (int16 j = 0; j < h; j++) { const uint16 *pix = (const uint16 *)surf.getBasePtr(0, j); for (int16 i = 0; i < w; i++) { bool use = false; if (pix[i] != transp) use = true; if (first) { unit.inEffect = use; first = false; } if (unit.inEffect == use) unit.count++; else { newMap->push_back(unit); unit.count = 1; unit.inEffect = use; } } } newMap->push_back(unit); return newMap; } void RenderManager::markDirty() { _backgroundDirtyRect = Common::Rect(_backgroundWidth, _backgroundHeight); } /* //Only needed by ActionDissolve, which is disabled unless and until a better dissolve effect can be implemented. Gameplay is not significantly effected. void RenderManager::bkgFill(uint8 r, uint8 g, uint8 b) { _currentBackgroundImage.fillRect(Common::Rect(_currentBackgroundImage.w, _currentBackgroundImage.h), _currentBackgroundImage.format.RGBToColor(r, g, b)); markDirty(); } */ void RenderManager::updateRotation() { int16 velocity = _engine->getMouseVelocity() + _engine->getKeyboardVelocity(); ScriptManager *scriptManager = _engine->getScriptManager(); if (_doubleFPS || !_frameLimiter.isEnabled()) // Assuming 60fps when in Vsync mode. velocity /= 2; if (velocity) { switch (_renderTable.getRenderState()) { case RenderTable::PANORAMA: { int16 startPosition = scriptManager->getStateValue(StateKey_ViewPos); int16 newPosition = startPosition + (_renderTable.getPanoramaReverse() ? -velocity : velocity); int16 zeroPoint = _renderTable.getPanoramaZeroPoint(); if (startPosition >= zeroPoint && newPosition < zeroPoint) scriptManager->setStateValue(StateKey_Rounds, scriptManager->getStateValue(StateKey_Rounds) - 1); if (startPosition <= zeroPoint && newPosition > zeroPoint) scriptManager->setStateValue(StateKey_Rounds, scriptManager->getStateValue(StateKey_Rounds) + 1); int16 screenWidth = getBkgSize().x; if (screenWidth) newPosition %= screenWidth; if (newPosition < 0) newPosition += screenWidth; setBackgroundPosition(newPosition); break; } case RenderTable::TILT: { int16 startPosition = scriptManager->getStateValue(StateKey_ViewPos); int16 newPosition = startPosition + velocity; int16 screenHeight = getBkgSize().y; int16 tiltGap = (int16)_renderTable.getTiltGap(); if (newPosition >= (screenHeight - tiltGap)) newPosition = screenHeight - tiltGap; if (newPosition <= tiltGap) newPosition = tiltGap; setBackgroundPosition(newPosition); break; } default: break; } } } void RenderManager::checkBorders() { switch (_renderTable.getRenderState()) { case RenderTable::PANORAMA: { int16 startPosition = _engine->getScriptManager()->getStateValue(StateKey_ViewPos); int16 newPosition = startPosition; int16 screenWidth = getBkgSize().x; if (screenWidth) newPosition %= screenWidth; if (newPosition < 0) newPosition += screenWidth; if (startPosition != newPosition) setBackgroundPosition(newPosition); break; } case RenderTable::TILT: { int16 startPosition = _engine->getScriptManager()->getStateValue(StateKey_ViewPos); int16 newPosition = startPosition; int16 screenHeight = getBkgSize().y; int16 tiltGap = (int16)_renderTable.getTiltGap(); if (newPosition >= (screenHeight - tiltGap)) newPosition = screenHeight - tiltGap; if (newPosition <= tiltGap) newPosition = tiltGap; if (startPosition != newPosition) setBackgroundPosition(newPosition); break; } default: break; } } void RenderManager::rotateTo(int16 _toPos, int16 _time) { if (_renderTable.getRenderState() != RenderTable::PANORAMA) return; debugC(1, kDebugGraphics, "Rotating panorama to %d", _toPos); if (_time == 0) _time = 1; int32 maxX = getBkgSize().x; int32 curX = getCurrentBackgroundOffset(); int32 dx = 0; if (curX == _toPos) return; if (curX > _toPos) { if (curX - _toPos > maxX / 2) dx = (_toPos + (maxX - curX)) / _time; else dx = -(curX - _toPos) / _time; } else { if (_toPos - curX > maxX / 2) dx = -((maxX - _toPos) + curX) / _time; else dx = (_toPos - curX) / _time; } _engine->stopClock(); for (int16 i = 0; i <= _time; i++) { if (i == _time) curX = _toPos; else curX += dx; if (curX < 0) curX = maxX - curX; else if (curX >= maxX) curX %= maxX; setBackgroundPosition(curX); prepareBackground(); renderSceneToScreen(); _system->delayMillis(500 / _time); } _engine->startClock(); } void RenderManager::upscaleRect(Common::Rect &rect) { Common::Rect HDscreen = _widescreen ? _HDscreenAreaWide : _HDscreenArea; Common::Rect SDscreen = _widescreen ? _layout.workingArea : _layout.screenArea; SDscreen.moveTo(0, 0); rect.top = rect.top * HDscreen.height() / SDscreen.height(); rect.left = rect.left * HDscreen.width() / SDscreen.width(); rect.bottom = rect.bottom * HDscreen.height() / SDscreen.height(); rect.right = rect.right * HDscreen.width() / SDscreen.width(); } } // End of namespace ZVision