diff options
Diffstat (limited to 'engines/sword2/render.cpp')
-rw-r--r-- | engines/sword2/render.cpp | 584 |
1 files changed, 584 insertions, 0 deletions
diff --git a/engines/sword2/render.cpp b/engines/sword2/render.cpp new file mode 100644 index 0000000000..da60ecd6d9 --- /dev/null +++ b/engines/sword2/render.cpp @@ -0,0 +1,584 @@ +/* Copyright (C) 1994-1998 Revolution Software Ltd. + * Copyright (C) 2003-2006 The ScummVM project + * + * 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. + * + * $URL$ + * $Id$ + */ + +#include "common/stdafx.h" +#include "common/system.h" + +#include "graphics/primitives.h" + +#include "sword2/sword2.h" +#include "sword2/defs.h" +#include "sword2/build_display.h" + +#ifdef BACKEND_8BIT +#include "sword2/animation.h" +#endif + +namespace Sword2 { + +#define MILLISECSPERCYCLE 83 +#define RENDERAVERAGETOTAL 4 + +void Screen::updateRect(Common::Rect *r) { + _vm->_system->copyRectToScreen(_buffer + r->top * _screenWide + r->left, + _screenWide, r->left, r->top, r->right - r->left, + r->bottom - r->top); +} + +void Screen::blitBlockSurface(BlockSurface *s, Common::Rect *r, Common::Rect *clipRect) { + if (!r->intersects(*clipRect)) + return; + + byte *src = s->data; + + if (r->top < clipRect->top) { + src -= BLOCKWIDTH * (r->top - clipRect->top); + r->top = clipRect->top; + } + if (r->left < clipRect->left) { + src -= (r->left - clipRect->left); + r->left = clipRect->left; + } + if (r->bottom > clipRect->bottom) + r->bottom = clipRect->bottom; + if (r->right > clipRect->right) + r->right = clipRect->right; + + byte *dst = _buffer + r->top * _screenWide + r->left; + int i; + + if (s->transparent) { + for (i = 0; i < r->bottom - r->top; i++) { + for (int j = 0; j < r->right - r->left; j++) { + if (src[j]) + dst[j] = src[j]; + } + src += BLOCKWIDTH; + dst += _screenWide; + } + } else { + for (i = 0; i < r->bottom - r->top; i++) { + memcpy(dst, src, r->right - r->left); + src += BLOCKWIDTH; + dst += _screenWide; + } + } +} + +// There are two different separate functions for scaling the image - one fast +// and one good. Or at least that's the theory. I'm sure there are better ways +// to scale an image than this. The latter is used at the highest graphics +// quality setting. Note that the "good" scaler takes an extra parameter, a +// pointer to the area of the screen where the sprite will be drawn. +// +// This code isn't quite like the original DrawSprite(), but should be close +// enough. + +void Screen::scaleImageFast(byte *dst, uint16 dstPitch, uint16 dstWidth, uint16 dstHeight, byte *src, uint16 srcPitch, uint16 srcWidth, uint16 srcHeight) { + int x, y; + + for (x = 0; x < dstWidth; x++) + _xScale[x] = (x * srcWidth) / dstWidth; + + for (y = 0; y < dstHeight; y++) + _yScale[y] = (y * srcHeight) / dstHeight; + + for (y = 0; y < dstHeight; y++) { + for (x = 0; x < dstWidth; x++) { + dst[x] = src[_yScale[y] * srcPitch + _xScale[x]]; + } + dst += dstPitch; + } +} + +void Screen::scaleImageGood(byte *dst, uint16 dstPitch, uint16 dstWidth, uint16 dstHeight, byte *src, uint16 srcPitch, uint16 srcWidth, uint16 srcHeight, byte *backbuf) { + for (int y = 0; y < dstHeight; y++) { + for (int x = 0; x < dstWidth; x++) { + uint8 c1, c2, c3, c4; + + uint32 xPos = (x * srcWidth) / dstWidth; + uint32 yPos = (y * srcHeight) / dstHeight; + uint32 xFrac = dstWidth - (x * srcWidth) % dstWidth; + uint32 yFrac = dstHeight - (y * srcHeight) % dstHeight; + + byte *srcPtr = src + yPos * srcPitch + xPos; + byte *backPtr = backbuf + y * _screenWide + x; + + bool transparent = true; + + if (*srcPtr) { + c1 = *srcPtr; + transparent = false; + } else + c1 = *backPtr; + + if (x < dstWidth - 1) { + if (*(srcPtr + 1)) { + c2 = *(srcPtr + 1); + transparent = false; + } else + c2 = *(backPtr + 1); + } else + c2 = c1; + + if (y < dstHeight - 1) { + if (*(srcPtr + srcPitch)) { + c3 = *(srcPtr + srcPitch); + transparent = false; + } else + c3 = *(backPtr + _screenWide); + } else + c3 = c1; + + if (x < dstWidth - 1 && y < dstHeight - 1) { + if (*(srcPtr + srcPitch + 1)) { + c4 = *(srcPtr + srcPitch + 1); + transparent = false; + } else + c4 = *(backPtr + _screenWide + 1); + } else + c4 = c3; + + if (!transparent) { + uint32 r1 = _palette[c1 * 4 + 0]; + uint32 g1 = _palette[c1 * 4 + 1]; + uint32 b1 = _palette[c1 * 4 + 2]; + + uint32 r2 = _palette[c2 * 4 + 0]; + uint32 g2 = _palette[c2 * 4 + 1]; + uint32 b2 = _palette[c2 * 4 + 2]; + + uint32 r3 = _palette[c3 * 4 + 0]; + uint32 g3 = _palette[c3 * 4 + 1]; + uint32 b3 = _palette[c3 * 4 + 2]; + + uint32 r4 = _palette[c4 * 4 + 0]; + uint32 g4 = _palette[c4 * 4 + 1]; + uint32 b4 = _palette[c4 * 4 + 2]; + + uint32 r5 = (r1 * xFrac + r2 * (dstWidth - xFrac)) / dstWidth; + uint32 g5 = (g1 * xFrac + g2 * (dstWidth - xFrac)) / dstWidth; + uint32 b5 = (b1 * xFrac + b2 * (dstWidth - xFrac)) / dstWidth; + + uint32 r6 = (r3 * xFrac + r4 * (dstWidth - xFrac)) / dstWidth; + uint32 g6 = (g3 * xFrac + g4 * (dstWidth - xFrac)) / dstWidth; + uint32 b6 = (b3 * xFrac + b4 * (dstWidth - xFrac)) / dstWidth; + + uint32 r = (r5 * yFrac + r6 * (dstHeight - yFrac)) / dstHeight; + uint32 g = (g5 * yFrac + g6 * (dstHeight - yFrac)) / dstHeight; + uint32 b = (b5 * yFrac + b6 * (dstHeight - yFrac)) / dstHeight; + + dst[y * dstWidth + x] = quickMatch(r, g, b); + } else + dst[y * dstWidth + x] = 0; + } + } +} + +/** + * Plots a point relative to the top left corner of the screen. This is only + * used for debugging. + * @param x x-coordinate of the point + * @param y y-coordinate of the point + * @param colour colour of the point + */ + +void Screen::plotPoint(int x, int y, uint8 colour) { + byte *buf = _buffer + MENUDEEP * RENDERWIDE; + + x -= _scrollX; + y -= _scrollY; + + if (x >= 0 && x < RENDERWIDE && y >= 0 && y < RENDERDEEP) { + buf[y * RENDERWIDE + x] = colour; + markAsDirty(x, y + MENUDEEP, x, y + MENUDEEP); + } +} + +static void plot(int x, int y, int colour, void *data) { + Screen *screen = (Screen *)data; + screen->plotPoint(x, y, (uint8) colour); +} + +/** + * Draws a line from one point to another. This is only used for debugging. + * @param x0 x-coordinate of the start point + * @param y0 y-coordinate of the start point + * @param x1 x-coordinate of the end point + * @param y1 y-coordinate of the end point + * @param colour colour of the line + */ + +void Screen::drawLine(int x0, int y0, int x1, int y1, uint8 colour) { + Graphics::drawLine(x0, y0, x1, y1, colour, &plot, this); +} + +/** + * This function tells the driver the size of the background screen for the + * current location. + * @param w width of the current location + * @param h height of the current location + */ + +void Screen::setLocationMetrics(uint16 w, uint16 h) { + _locationWide = w; + _locationDeep = h; + setNeedFullRedraw(); +} + +/** + * Draws a parallax layer at the current position determined by the scroll. A + * parallax can be either foreground, background or the main screen. + */ + +void Screen::renderParallax(byte *ptr, int16 l) { + Parallax p; + int16 x, y; + Common::Rect r; + + p.read(ptr); + + if (_locationWide == _screenWide) + x = 0; + else + x = ((int32)((p.w - _screenWide) * _scrollX) / (int32)(_locationWide - _screenWide)); + + if (_locationDeep == _screenDeep - MENUDEEP * 2) + y = 0; + else + y = ((int32)((p.h - (_screenDeep - MENUDEEP * 2)) * _scrollY) / (int32)(_locationDeep - (_screenDeep - MENUDEEP * 2))); + + Common::Rect clipRect; + + // Leave enough space for the top and bottom menues + + clipRect.left = 0; + clipRect.right = _screenWide; + clipRect.top = MENUDEEP; + clipRect.bottom = _screenDeep - MENUDEEP; + + for (int j = 0; j < _yBlocks[l]; j++) { + for (int i = 0; i < _xBlocks[l]; i++) { + if (_blockSurfaces[l][i + j * _xBlocks[l]]) { + r.left = i * BLOCKWIDTH - x; + r.right = r.left + BLOCKWIDTH; + r.top = j * BLOCKHEIGHT - y + MENUDEEP; + r.bottom = r.top + BLOCKHEIGHT; + blitBlockSurface(_blockSurfaces[l][i + j * _xBlocks[l]], &r, &clipRect); + } + } + } + + _parallaxScrollX = _scrollX - x; + _parallaxScrollY = _scrollY - y; +} + +// Uncomment this when benchmarking the drawing routines. +#define LIMIT_FRAME_RATE + +/** + * Initialises the timers before the render loop is entered. + */ + +void Screen::initialiseRenderCycle() { + _initialTime = _vm->_system->getMillis(); + _totalTime = _initialTime + MILLISECSPERCYCLE; +} + +/** + * This function should be called when the game engine is ready to start the + * render cycle. + */ + +void Screen::startRenderCycle() { + _scrollXOld = _scrollX; + _scrollYOld = _scrollY; + + _startTime = _vm->_system->getMillis(); + + if (_startTime + _renderAverageTime >= _totalTime) { + _scrollX = _scrollXTarget; + _scrollY = _scrollYTarget; + _renderTooSlow = true; + } else { + _scrollX = (int16)(_scrollXOld + ((_scrollXTarget - _scrollXOld) * (_startTime - _initialTime + _renderAverageTime)) / (_totalTime - _initialTime)); + _scrollY = (int16)(_scrollYOld + ((_scrollYTarget - _scrollYOld) * (_startTime - _initialTime + _renderAverageTime)) / (_totalTime - _initialTime)); + _renderTooSlow = false; + } + + if (_scrollXOld != _scrollX || _scrollYOld != _scrollY) + setNeedFullRedraw(); + + _framesPerGameCycle = 0; +} + +/** + * This function should be called at the end of the render cycle. + * @return true if the render cycle is to be terminated, + * or false if it should continue + */ + +bool Screen::endRenderCycle() { + static int32 renderTimeLog[4] = { 60, 60, 60, 60 }; + static int32 renderCountIndex = 0; + int32 time; + + time = _vm->_system->getMillis(); + renderTimeLog[renderCountIndex] = time - _startTime; + _startTime = time; + _renderAverageTime = (renderTimeLog[0] + renderTimeLog[1] + renderTimeLog[2] + renderTimeLog[3]) >> 2; + + _framesPerGameCycle++; + + if (++renderCountIndex == RENDERAVERAGETOTAL) + renderCountIndex = 0; + + if (_renderTooSlow) { + initialiseRenderCycle(); + return true; + } + + if (_startTime + _renderAverageTime >= _totalTime) { + _totalTime += MILLISECSPERCYCLE; + _initialTime = time; + return true; + } + +#ifdef LIMIT_FRAME_RATE + if (_scrollXTarget == _scrollX && _scrollYTarget == _scrollY) { + // If we have already reached the scroll target sleep for the + // rest of the render cycle. + _vm->sleepUntil(_totalTime); + _initialTime = _vm->_system->getMillis(); + _totalTime += MILLISECSPERCYCLE; + return true; + } +#endif + + // This is an attempt to ensure that we always reach the scroll target. + // Otherwise the game frequently tries to pump out new interpolation + // frames without ever getting anywhere. + + if (ABS(_scrollX - _scrollXTarget) <= 1 && ABS(_scrollY - _scrollYTarget) <= 1) { + _scrollX = _scrollXTarget; + _scrollY = _scrollYTarget; + } else { + _scrollX = (int16)(_scrollXOld + ((_scrollXTarget - _scrollXOld) * (_startTime - _initialTime + _renderAverageTime)) / (_totalTime - _initialTime)); + _scrollY = (int16)(_scrollYOld + ((_scrollYTarget - _scrollYOld) * (_startTime - _initialTime + _renderAverageTime)) / (_totalTime - _initialTime)); + } + + if (_scrollX != _scrollXOld || _scrollY != _scrollYOld) + setNeedFullRedraw(); + +#ifdef LIMIT_FRAME_RATE + // Give the other threads some breathing space. This apparently helps + // against bug #875683, though I was never able to reproduce it for + // myself. + _vm->_system->delayMillis(10); +#endif + + return false; +} + +/** + * Reset scrolling stuff. This function is called from initBackground() + */ + +void Screen::resetRenderEngine() { + _parallaxScrollX = 0; + _parallaxScrollY = 0; + _scrollX = 0; + _scrollY = 0; +} + +/** + * This function should be called five times with either the parallax layer + * or a NULL pointer in order of background parallax to foreground parallax. + */ + +int32 Screen::initialiseBackgroundLayer(byte *parallax) { + Parallax p; + uint16 i, j, k; + byte *data; + byte *dst; + + debug(2, "initialiseBackgroundLayer"); + + assert(_layer < MAXLAYERS); + + if (!parallax) { + _layer++; + return RD_OK; + } + + p.read(parallax); + + _xBlocks[_layer] = (p.w + BLOCKWIDTH - 1) / BLOCKWIDTH; + _yBlocks[_layer] = (p.h + BLOCKHEIGHT - 1) / BLOCKHEIGHT; + + _blockSurfaces[_layer] = (BlockSurface **)calloc(_xBlocks[_layer] * _yBlocks[_layer], sizeof(BlockSurface *)); + if (!_blockSurfaces[_layer]) + return RDERR_OUTOFMEMORY; + + // Decode the parallax layer into a large chunk of memory + + byte *memchunk = (byte *)calloc(_xBlocks[_layer] * _yBlocks[_layer], BLOCKWIDTH * BLOCKHEIGHT); + if (!memchunk) + return RDERR_OUTOFMEMORY; + + for (i = 0; i < p.h; i++) { + uint32 p_offset = READ_LE_UINT32(parallax + Parallax::size() + 4 * i); + + if (!p_offset) + continue; + + byte *pLine = parallax + p_offset; + uint16 packets = READ_LE_UINT16(pLine); + uint16 offset = READ_LE_UINT16(pLine + 2); + + data = pLine + 4; + dst = memchunk + i * p.w + offset; + + if (!packets) { + memcpy(dst, data, p.w); + continue; + } + + bool zeros = false; + + for (j = 0; j < packets; j++) { + if (zeros) { + dst += *data; + offset += *data; + data++; + zeros = false; + } else if (!*data) { + data++; + zeros = true; + } else { + uint16 count = *data++; + memcpy(dst, data, count); + data += count; + dst += count; + offset += count; + zeros = true; + } + } + } + + // The large memory chunk is now divided into a number of smaller + // surfaces. For most parallax layers, we'll end up using less memory + // this way, and it will be faster to draw since completely transparent + // surfaces are discarded. + + for (i = 0; i < _xBlocks[_layer] * _yBlocks[_layer]; i++) { + bool block_has_data = false; + bool block_is_transparent = false; + + int x = BLOCKWIDTH * (i % _xBlocks[_layer]); + int y = BLOCKHEIGHT * (i / _xBlocks[_layer]); + + data = memchunk + p.w * y + x; + + for (j = 0; j < BLOCKHEIGHT; j++) { + for (k = 0; k < BLOCKWIDTH; k++) { + if (x + k < p.w && y + j < p.h) { + if (data[j * p.w + k]) + block_has_data = true; + else + block_is_transparent = true; + } + } + } + + // Only assign a surface to the block if it contains data. + + if (block_has_data) { + _blockSurfaces[_layer][i] = (BlockSurface *)malloc(sizeof(BlockSurface)); + + // Copy the data into the surfaces. + dst = _blockSurfaces[_layer][i]->data; + for (j = 0; j < BLOCKHEIGHT; j++) { + memcpy(dst, data, BLOCKWIDTH); + data += p.w; + dst += BLOCKWIDTH; + } + + _blockSurfaces[_layer][i]->transparent = block_is_transparent; + + } else + _blockSurfaces[_layer][i] = NULL; + } + + free(memchunk); + _layer++; + + return RD_OK; +} + +/** + * Should be called once after leaving the room to free up memory. + */ + +void Screen::closeBackgroundLayer() { + debug(2, "CloseBackgroundLayer"); + + for (int i = 0; i < MAXLAYERS; i++) { + if (_blockSurfaces[i]) { + for (int j = 0; j < _xBlocks[i] * _yBlocks[i]; j++) + if (_blockSurfaces[i][j]) + free(_blockSurfaces[i][j]); + free(_blockSurfaces[i]); + _blockSurfaces[i] = NULL; + } + } + + _layer = 0; +} + +#ifdef BACKEND_8BIT +void Screen::plotYUV(byte *lut, int width, int height, byte *const *dat) { + byte *buf = _buffer + ((480 - height) / 2) * RENDERWIDE + (640 - width) / 2; + + int x, y; + + int ypos = 0; + int cpos = 0; + int linepos = 0; + + for (y = 0; y < height; y += 2) { + for (x = 0; x < width; x += 2) { + int i = ((((dat[2][cpos] + ROUNDADD) >> SHIFT) * (BITDEPTH + 1)) + ((dat[1][cpos] + ROUNDADD) >> SHIFT)) * (BITDEPTH + 1); + cpos++; + + buf[linepos ] = lut[i + ((dat[0][ ypos ] + ROUNDADD) >> SHIFT)]; + buf[RENDERWIDE + linepos++] = lut[i + ((dat[0][width + ypos++] + ROUNDADD) >> SHIFT)]; + buf[linepos ] = lut[i + ((dat[0][ ypos ] + ROUNDADD) >> SHIFT)]; + buf[RENDERWIDE + linepos++] = lut[i + ((dat[0][width + ypos++] + ROUNDADD) >> SHIFT)]; + } + linepos += (2 * RENDERWIDE - width); + ypos += width; + } +} +#endif + + +} // End of namespace Sword2 |