diff options
Diffstat (limited to 'engines/wintermute/base/gfx/osystem/base_render_osystem.cpp')
-rw-r--r-- | engines/wintermute/base/gfx/osystem/base_render_osystem.cpp | 604 |
1 files changed, 604 insertions, 0 deletions
diff --git a/engines/wintermute/base/gfx/osystem/base_render_osystem.cpp b/engines/wintermute/base/gfx/osystem/base_render_osystem.cpp new file mode 100644 index 0000000000..6d67253038 --- /dev/null +++ b/engines/wintermute/base/gfx/osystem/base_render_osystem.cpp @@ -0,0 +1,604 @@ +/* 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. + * + */ + +/* + * This file is based on WME Lite. + * http://dead-code.org/redir.php?target=wmelite + * Copyright (c) 2011 Jan Nedoma + */ + +#include "engines/wintermute/base/gfx/osystem/base_render_osystem.h" +#include "engines/wintermute/base/gfx/osystem/base_surface_osystem.h" +#include "engines/wintermute/base/base_surface_storage.h" +#include "engines/wintermute/base/gfx/base_image.h" +#include "engines/wintermute/math/math_util.h" +#include "engines/wintermute/base/base_game.h" +#include "engines/wintermute/base/base_sprite.h" +#include "common/system.h" +#include "engines/wintermute/graphics/transparent_surface.h" +#include "common/queue.h" +#include "common/config-manager.h" + +namespace Wintermute { + +RenderTicket::RenderTicket(BaseSurfaceOSystem *owner, const Graphics::Surface *surf, Common::Rect *srcRect, Common::Rect *dstRect, bool mirrorX, bool mirrorY, bool disableAlpha) : _owner(owner), + _srcRect(*srcRect), _dstRect(*dstRect), _drawNum(0), _isValid(true), _wantsDraw(true), _hasAlpha(!disableAlpha) { + _colorMod = 0; + _mirror = TransparentSurface::FLIP_NONE; + if (mirrorX) { + _mirror |= TransparentSurface::FLIP_V; + } + if (mirrorY) { + _mirror |= TransparentSurface::FLIP_H; + } + if (surf) { + _surface = new Graphics::Surface(); + _surface->create((uint16)srcRect->width(), (uint16)srcRect->height(), surf->format); + assert(_surface->format.bytesPerPixel == 4); + // Get a clipped copy of the surface + for (int i = 0; i < _surface->h; i++) { + memcpy(_surface->getBasePtr(0, i), surf->getBasePtr(srcRect->left, srcRect->top + i), srcRect->width() * _surface->format.bytesPerPixel); + } + // Then scale it if necessary + if (dstRect->width() != srcRect->width() || dstRect->height() != srcRect->height()) { + TransparentSurface src(*_surface, false); + Graphics::Surface *temp = src.scale(dstRect->width(), dstRect->height()); + _surface->free(); + delete _surface; + _surface = temp; + } + } else { + _surface = NULL; + } +} + +RenderTicket::~RenderTicket() { + if (_surface) { + _surface->free(); + delete _surface; + } +} + +bool RenderTicket::operator==(RenderTicket &t) { + if ((t._srcRect != _srcRect) || + (t._dstRect != _dstRect) || + (t._mirror != _mirror) || + (t._owner != _owner) || + (t._hasAlpha != _hasAlpha) || + (t._colorMod != _colorMod)) { + return false; + } + return true; +} + +BaseRenderer *makeOSystemRenderer(BaseGame *inGame) { + return new BaseRenderOSystem(inGame); +} + +////////////////////////////////////////////////////////////////////////// +BaseRenderOSystem::BaseRenderOSystem(BaseGame *inGame) : BaseRenderer(inGame) { + _renderSurface = new Graphics::Surface(); + _blankSurface = new Graphics::Surface(); + _drawNum = 1; + _needsFlip = true; + + _borderLeft = _borderRight = _borderTop = _borderBottom = 0; + _ratioX = _ratioY = 1.0f; + setAlphaMod(255); + setColorMod(255, 255, 255); + _dirtyRect = NULL; +} + +////////////////////////////////////////////////////////////////////////// +BaseRenderOSystem::~BaseRenderOSystem() { + _renderSurface->free(); + delete _renderSurface; + _blankSurface->free(); + delete _blankSurface; + TransparentSurface::destroyLookup(); +} + +////////////////////////////////////////////////////////////////////////// +bool BaseRenderOSystem::initRenderer(int width, int height, bool windowed) { + _width = width; + _height = height; + _renderRect.setWidth(_width); + _renderRect.setHeight(_height); + + _realWidth = width; + _realHeight = height; + + //TODO: Tiny resolution-displays might want to do some resolution-selection logic here + + //_realWidth = BaseEngine::instance().getRegistry()->readInt("Debug", "ForceResWidth", _width); + //_realHeight = BaseEngine::instance().getRegistry()->readInt("Debug", "ForceResHeight", _height); + + float origAspect = (float)_width / (float)_height; + float realAspect = (float)_realWidth / (float)_realHeight; + + float ratio; + if (origAspect < realAspect) { + // normal to wide + ratio = (float)_realHeight / (float)_height; + } else { + // wide to normal + ratio = (float)_realWidth / (float)_width; + } + + _borderLeft = (int)((_realWidth - (_width * ratio)) / 2); + _borderRight = (int)(_realWidth - (_width * ratio) - _borderLeft); + + _borderTop = (int)((_realHeight - (_height * ratio)) / 2); + _borderBottom = (int)(_realHeight - (_height * ratio) - _borderTop); + + + + _ratioX = (float)(_realWidth - _borderLeft - _borderRight) / (float)_width; + _ratioY = (float)(_realHeight - _borderTop - _borderBottom) / (float)_height; + + _windowed = !ConfMan.getBool("fullscreen"); + + Graphics::PixelFormat format(4, 8, 8, 8, 8, 16, 8, 0, 24); + g_system->beginGFXTransaction(); + g_system->initSize(_width, _height, &format); + OSystem::TransactionError gfxError = g_system->endGFXTransaction(); + + if (gfxError != OSystem::kTransactionSuccess) { + warning("Couldn't setup GFX-backend for %dx%dx%d", _width, _height, format.bytesPerPixel * 8); + return STATUS_FAILED; + } + + g_system->showMouse(false); + + _renderSurface->create(g_system->getWidth(), g_system->getHeight(), g_system->getScreenFormat()); + _blankSurface->create(g_system->getWidth(), g_system->getHeight(), g_system->getScreenFormat()); + _blankSurface->fillRect(Common::Rect(0, 0, _blankSurface->h, _blankSurface->w), _blankSurface->format.ARGBToColor(255, 0, 0, 0)); + _active = true; + + _clearColor = _renderSurface->format.ARGBToColor(255, 0, 0, 0); + + return STATUS_OK; +} + +void BaseRenderOSystem::setAlphaMod(byte alpha) { + byte r = RGBCOLGetR(_colorMod); + byte g = RGBCOLGetB(_colorMod); + byte b = RGBCOLGetB(_colorMod); + _colorMod = BS_ARGB(alpha, r, g, b); +} + +void BaseRenderOSystem::setColorMod(byte r, byte g, byte b) { + byte alpha = RGBCOLGetA(_colorMod); + _colorMod = BS_ARGB(alpha, r, g, b); +} + +bool BaseRenderOSystem::indicatorFlip() { + g_system->copyRectToScreen((byte *)_renderSurface->getBasePtr(_indicatorX, _indicatorY), _renderSurface->pitch, _indicatorX, _indicatorY, _indicatorWidthDrawn, _indicatorHeight); + g_system->updateScreen(); + return STATUS_OK; +} + +bool BaseRenderOSystem::flip() { + if (!_disableDirtyRects) { + drawTickets(); + } else { + // Clear the scale-buffered tickets that wasn't reused. + RenderQueueIterator it = _renderQueue.begin(); + while (it != _renderQueue.end()) { + if ((*it)->_wantsDraw == false) { + RenderTicket *ticket = *it; + it = _renderQueue.erase(it); + delete ticket; + } else { + (*it)->_wantsDraw = false; + ++it; + } + } + } + if (_needsFlip || _disableDirtyRects) { + if (_disableDirtyRects) { + g_system->copyRectToScreen((byte *)_renderSurface->pixels, _renderSurface->pitch, 0, 0, _renderSurface->w, _renderSurface->h); + } + // g_system->copyRectToScreen((byte *)_renderSurface->pixels, _renderSurface->pitch, _dirtyRect->left, _dirtyRect->top, _dirtyRect->width(), _dirtyRect->height()); + delete _dirtyRect; + _dirtyRect = NULL; + g_system->updateScreen(); + _needsFlip = false; + } + _drawNum = 1; + + return STATUS_OK; +} + +////////////////////////////////////////////////////////////////////////// +bool BaseRenderOSystem::fill(byte r, byte g, byte b, Common::Rect *rect) { + _clearColor = _renderSurface->format.ARGBToColor(0xFF, r, g, b); + if (!_disableDirtyRects) { + return STATUS_OK; + } + if (!rect) { +// TODO: This should speed things up, but for some reason it misses the size by quite a bit. +/* if (r == 0 && g == 0 && b == 0) { + // Simply memcpy from the buffered black-surface, way faster than Surface::fillRect. + memcpy(_renderSurface->pixels, _blankSurface->pixels, _renderSurface->pitch * _renderSurface->h); + return STATUS_OK; + }*/ + rect = &_renderRect; + } + // TODO: This doesn't work with dirty rects + _renderSurface->fillRect(*rect, _clearColor); + + return STATUS_OK; +} + +////////////////////////////////////////////////////////////////////////// +void BaseRenderOSystem::fade(uint16 alpha) { + byte dwAlpha = (byte)(255 - alpha); + return fadeToColor(0, 0, 0, dwAlpha); +} + + +////////////////////////////////////////////////////////////////////////// +void BaseRenderOSystem::fadeToColor(byte r, byte g, byte b, byte a, Common::Rect *rect) { + // This particular warning is rather messy, as this function is called a ton, + // thus we avoid printing it more than once. + + // TODO: Add fading with dirty rects. + if (!_disableDirtyRects) { + warning("BaseRenderOSystem::FadeToColor - Breaks when using dirty rects"); + } + + Common::Rect fillRect; + + if (rect) { + fillRect.left = rect->left; + fillRect.top = rect->top; + fillRect.setWidth(rect->width()); + fillRect.setHeight(rect->height()); + } else { + Rect32 rc; + _gameRef->getCurrentViewportRect(&rc); + fillRect.left = (int16)rc.left; + fillRect.top = (int16)rc.top; + fillRect.setWidth((int16)(rc.right - rc.left)); + fillRect.setHeight((int16)(rc.bottom - rc.top)); + } + modTargetRect(&fillRect); + + //TODO: This is only here until I'm sure about the final pixelformat + uint32 col = _renderSurface->format.ARGBToColor(a, r, g, b); + + setAlphaMod(255); + setColorMod(255, 255, 255); + Graphics::Surface surf; + surf.create((uint16)fillRect.width(), (uint16)fillRect.height(), _renderSurface->format); + Common::Rect sizeRect(fillRect); + sizeRect.translate(-fillRect.top, -fillRect.left); + surf.fillRect(fillRect, col); + drawSurface(NULL, &surf, &sizeRect, &fillRect, false, false); + surf.free(); + + //SDL_SetRenderDrawColor(_renderer, r, g, b, a); + //SDL_SetRenderDrawBlendMode(_renderer, SDL_BLENDMODE_BLEND); + //SDL_RenderFillRect(_renderer, &fillRect); +} + +Graphics::PixelFormat BaseRenderOSystem::getPixelFormat() const { + return _renderSurface->format; +} + +void BaseRenderOSystem::drawSurface(BaseSurfaceOSystem *owner, const Graphics::Surface *surf, Common::Rect *srcRect, Common::Rect *dstRect, bool mirrorX, bool mirrorY, bool disableAlpha) { + // Skip rects that are completely outside the screen: + if ((dstRect->left < 0 && dstRect->right < 0) || (dstRect->top < 0 && dstRect->bottom < 0)) { + return; + } + + if (owner) { // Fade-tickets are owner-less + RenderTicket compare(owner, NULL, srcRect, dstRect, mirrorX, mirrorY, disableAlpha); + compare._colorMod = _colorMod; + RenderQueueIterator it; + for (it = _renderQueue.begin(); it != _renderQueue.end(); ++it) { + if ((*it)->_owner == owner && *(*it) == compare && (*it)->_isValid) { + (*it)->_colorMod = _colorMod; + if (_disableDirtyRects) { + drawFromSurface(*it, NULL); + } else { + drawFromTicket(*it); + } + return; + } + } + } + RenderTicket *ticket = new RenderTicket(owner, surf, srcRect, dstRect, mirrorX, mirrorY, disableAlpha); + ticket->_colorMod = _colorMod; + if (!_disableDirtyRects) { + drawFromTicket(ticket); + } else { + ticket->_wantsDraw = true; + _renderQueue.push_back(ticket); + drawFromSurface(ticket, NULL); + } +} + +void BaseRenderOSystem::invalidateTicket(RenderTicket *renderTicket) { + addDirtyRect(renderTicket->_dstRect); + renderTicket->_isValid = false; +// renderTicket->_canDelete = true; // TODO: Maybe readd this, to avoid even more duplicates. +} + +void BaseRenderOSystem::invalidateTicketsFromSurface(BaseSurfaceOSystem *surf) { + RenderQueueIterator it; + for (it = _renderQueue.begin(); it != _renderQueue.end(); ++it) { + if ((*it)->_owner == surf) { + invalidateTicket(*it); + } + } +} + +void BaseRenderOSystem::drawFromTicket(RenderTicket *renderTicket) { + renderTicket->_wantsDraw = true; + // A new item always has _drawNum == 0 + if (renderTicket->_drawNum == 0) { + // In-order + if (_renderQueue.empty() || _drawNum > (_renderQueue.back())->_drawNum) { + renderTicket->_drawNum = _drawNum++; + _renderQueue.push_back(renderTicket); + addDirtyRect(renderTicket->_dstRect); + } else { + // Before something + Common::List<RenderTicket *>::iterator pos; + for (pos = _renderQueue.begin(); pos != _renderQueue.end(); pos++) { + if ((*pos)->_drawNum >= _drawNum) { + break; + } + } + _renderQueue.insert(pos, renderTicket); + Common::List<RenderTicket *>::iterator it; + renderTicket->_drawNum = _drawNum++; + // Increment the following tickets, so they still are in line + for (it = pos; it != _renderQueue.end(); ++it) { + (*it)->_drawNum++; + (*it)->_wantsDraw = false; + } + addDirtyRect(renderTicket->_dstRect); + } + } else { + // Was drawn last round, still in the same order + if (_drawNum == renderTicket->_drawNum) { + _drawNum++; + } else { + // Remove the ticket from the list + RenderQueueIterator it = _renderQueue.begin(); + while (it != _renderQueue.end()) { + if ((*it) == renderTicket) { + it = _renderQueue.erase(it); + break; + } else { + ++it; + } + } + if (it != _renderQueue.end()) { + // Decreement the following tickets. + for (; it != _renderQueue.end(); ++it) { + (*it)->_drawNum--; + } + } + // Is not in order, so readd it as if it was a new ticket + renderTicket->_drawNum = 0; + drawFromTicket(renderTicket); + } + } +} + +void BaseRenderOSystem::addDirtyRect(const Common::Rect &rect) { + if (!_dirtyRect) { + _dirtyRect = new Common::Rect(rect); + } else { + _dirtyRect->extend(rect); + } + _dirtyRect->clip(_renderRect); +} + +void BaseRenderOSystem::drawTickets() { + RenderQueueIterator it = _renderQueue.begin(); + // Clean out the old tickets + int decrement = 0; + while (it != _renderQueue.end()) { + if ((*it)->_wantsDraw == false || (*it)->_isValid == false) { + RenderTicket *ticket = *it; + addDirtyRect((*it)->_dstRect); + it = _renderQueue.erase(it); + delete ticket; + decrement++; + } else { + (*it)->_drawNum -= decrement; + ++it; + } + } + if (!_dirtyRect || _dirtyRect->width() == 0 || _dirtyRect->height() == 0) { + return; + } + // The color-mods are stored in the RenderTickets on add, since we set that state again during + // draw, we need to keep track of what it was prior to draw. + uint32 oldColorMod = _colorMod; + + // Apply the clear-color to the dirty rect. + _renderSurface->fillRect(*_dirtyRect, _clearColor); + _drawNum = 1; + for (it = _renderQueue.begin(); it != _renderQueue.end(); ++it) { + RenderTicket *ticket = *it; + assert(ticket->_drawNum == _drawNum++); + if (ticket->_isValid && ticket->_dstRect.intersects(*_dirtyRect)) { + // dstClip is the area we want redrawn. + Common::Rect dstClip(ticket->_dstRect); + // reduce it to the dirty rect + dstClip.clip(*_dirtyRect); + // we need to keep track of the position to redraw the dirty rect + Common::Rect pos(dstClip); + int16 offsetX = ticket->_dstRect.left; + int16 offsetY = ticket->_dstRect.top; + // convert from screen-coords to surface-coords. + dstClip.translate(-offsetX, -offsetY); + + _colorMod = ticket->_colorMod; + drawFromSurface(ticket->getSurface(), &ticket->_srcRect, &pos, &dstClip, ticket->_mirror); + _needsFlip = true; + } + // Some tickets want redraw but don't actually clip the dirty area (typically the ones that shouldnt become clear-color) + ticket->_wantsDraw = false; + } + g_system->copyRectToScreen((byte *)_renderSurface->getBasePtr(_dirtyRect->left, _dirtyRect->top), _renderSurface->pitch, _dirtyRect->left, _dirtyRect->top, _dirtyRect->width(), _dirtyRect->height()); + + // Revert the colorMod-state. + _colorMod = oldColorMod; +} + +// Replacement for SDL2's SDL_RenderCopy +void BaseRenderOSystem::drawFromSurface(RenderTicket *ticket, Common::Rect *clipRect) { + TransparentSurface src(*ticket->getSurface(), false); + bool doDelete = false; + if (!clipRect) { + doDelete = true; + clipRect = new Common::Rect(); + clipRect->setWidth(ticket->getSurface()->w); + clipRect->setHeight(ticket->getSurface()->h); + } + + src._enableAlphaBlit = ticket->_hasAlpha; + src.blit(*_renderSurface, ticket->_dstRect.left, ticket->_dstRect.top, ticket->_mirror, clipRect, _colorMod, clipRect->width(), clipRect->height()); + if (doDelete) { + delete clipRect; + } +} +void BaseRenderOSystem::drawFromSurface(const Graphics::Surface *surf, Common::Rect *srcRect, Common::Rect *dstRect, Common::Rect *clipRect, uint32 mirror) { + TransparentSurface src(*surf, false); + bool doDelete = false; + if (!clipRect) { + doDelete = true; + clipRect = new Common::Rect(); + clipRect->setWidth(surf->w); + clipRect->setHeight(surf->h); + } + + src.blit(*_renderSurface, dstRect->left, dstRect->top, mirror, clipRect, _colorMod, clipRect->width(), clipRect->height()); + if (doDelete) { + delete clipRect; + } +} + +////////////////////////////////////////////////////////////////////////// +bool BaseRenderOSystem::drawLine(int x1, int y1, int x2, int y2, uint32 color) { + + if (!_disableDirtyRects) { + warning("BaseRenderOSystem::DrawLine - doesn't work for dirty rects yet"); + } + + byte r = RGBCOLGetR(color); + byte g = RGBCOLGetG(color); + byte b = RGBCOLGetB(color); + byte a = RGBCOLGetA(color); + + //SDL_SetRenderDrawColor(_renderer, r, g, b, a); + //SDL_SetRenderDrawBlendMode(_renderer, SDL_BLENDMODE_BLEND); + + Point32 point1, point2; + point1.x = x1; + point1.y = y1; + pointToScreen(&point1); + + point2.x = x2; + point2.y = y2; + pointToScreen(&point2); + + // TODO: This thing is mostly here until I'm sure about the final color-format. + uint32 colorVal = _renderSurface->format.ARGBToColor(a, r, g, b); + _renderSurface->drawLine(point1.x, point1.y, point2.x, point2.y, colorVal); + //SDL_RenderDrawLine(_renderer, point1.x, point1.y, point2.x, point2.y); + return STATUS_OK; +} + +////////////////////////////////////////////////////////////////////////// +BaseImage *BaseRenderOSystem::takeScreenshot() { +// TODO: Clip by viewport. + BaseImage *screenshot = new BaseImage(); + screenshot->copyFrom(_renderSurface); + return screenshot; +} + +////////////////////////////////////////////////////////////////////////// +Common::String BaseRenderOSystem::getName() const { + return "ScummVM-OSystem-renderer"; +} + +////////////////////////////////////////////////////////////////////////// +bool BaseRenderOSystem::setViewport(int left, int top, int right, int bottom) { + Common::Rect rect; + // TODO: Hopefully this is the same logic that ScummVM uses. + rect.left = (int16)(left + _borderLeft); + rect.top = (int16)(top + _borderTop); + rect.right = (int16)((right - left) * _ratioX); + rect.bottom = (int16)((bottom - top) * _ratioY); + + _renderRect = rect; + return STATUS_OK; +} + +Rect32 BaseRenderOSystem::getViewPort() { + Rect32 ret; + ret.top = _renderRect.top; + ret.bottom = _renderRect.bottom; + ret.left = _renderRect.left; + ret.right = _renderRect.right; + return ret; +} + +////////////////////////////////////////////////////////////////////////// +void BaseRenderOSystem::modTargetRect(Common::Rect *rect) { + rect->left = (int16)MathUtil::round(rect->left * _ratioX + _borderLeft - _renderRect.left); + rect->top = (int16)MathUtil::round(rect->top * _ratioY + _borderTop - _renderRect.top); + rect->setWidth((int16)MathUtil::roundUp(rect->width() * _ratioX)); + rect->setHeight((int16)MathUtil::roundUp(rect->height() * _ratioY)); +} + +////////////////////////////////////////////////////////////////////////// +void BaseRenderOSystem::pointFromScreen(Point32 *point) { + point->x = (int16)(point->x / _ratioX - _borderLeft / _ratioX + _renderRect.left); + point->y = (int16)(point->y / _ratioY - _borderTop / _ratioY + _renderRect.top); +} + + +////////////////////////////////////////////////////////////////////////// +void BaseRenderOSystem::pointToScreen(Point32 *point) { + point->x = (int16)MathUtil::roundUp(point->x * _ratioX) + _borderLeft - _renderRect.left; + point->y = (int16)MathUtil::roundUp(point->y * _ratioY) + _borderTop - _renderRect.top; +} + +////////////////////////////////////////////////////////////////////////// +void BaseRenderOSystem::dumpData(const char *filename) { + warning("BaseRenderOSystem::DumpData(%s) - stubbed", filename); // TODO +} + +BaseSurface *BaseRenderOSystem::createSurface() { + return new BaseSurfaceOSystem(_gameRef); +} + +} // end of namespace Wintermute |