scummvm/engines/tinsel/graphics.cpp

552 lines
14 KiB
C++

/* 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.
*
* $URL$
* $Id$
*
* Low level graphics interface.
*/
#include "tinsel/graphics.h"
#include "tinsel/handle.h" // LockMem()
#include "tinsel/object.h"
#include "tinsel/palette.h"
#include "tinsel/scene.h"
#include "tinsel/tinsel.h"
namespace Tinsel {
//----------------- LOCAL DEFINES --------------------
// Defines used in graphic drawing
#define CHARPTR_OFFSET 16
#define CHAR_WIDTH 4
#define CHAR_HEIGHT 4
extern uint8 transPalette[MAX_COLOURS];
//----------------- SUPPORT FUNCTIONS ---------------------
/**
* Straight rendering with transparency support
*/
static void WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) {
// Set up the offset between destination blocks
int rightClip = applyClipping ? pObj->rightClip : 0;
Common::Rect boxBounds;
if (applyClipping) {
// Adjust the height down to skip any bottom clipping
pObj->height -= pObj->botClip;
// Make adjustment for the top clipping row
srcP += sizeof(uint16) * ((pObj->width + 3) >> 2) * (pObj->topClip >> 2);
pObj->height -= pObj->topClip;
pObj->topClip %= 4;
}
// Vertical loop
while (pObj->height > 0) {
// Get the start of the next line output
uint8 *tempDest = destP;
// Get the line width, and figure out which row range within the 4 row high blocks
// will be displayed if clipping is to be taken into account
int width = pObj->width;
if (!applyClipping) {
// No clipping, so so set box bounding area for drawing full 4x4 pixel blocks
boxBounds.top = 0;
boxBounds.bottom = 3;
boxBounds.left = 0;
} else {
// Handle any possible clipping at the top of the char block.
// We already handled topClip partially at the beginning of this function.
// Hence the only non-zero values it can assume at this point are 1,2,3,
// and that only during the very first iteration (i.e. when the top char
// block is drawn only partially). In particular, we set topClip to zero,
// as all following blocks are not to be top clipped.
boxBounds.top = pObj->topClip;
pObj->topClip = 0;
boxBounds.bottom = MIN(boxBounds.top + pObj->height - 1, 3);
// Handle any possible clipping at the start of the line
boxBounds.left = pObj->leftClip;
if (boxBounds.left >= 4) {
srcP += sizeof(uint16) * (boxBounds.left >> 2);
width -= boxBounds.left & 0xfffc;
boxBounds.left %= 4;
}
width -= boxBounds.left;
}
// Horizontal loop
while (width > rightClip) {
boxBounds.right = MIN(boxBounds.left + width - rightClip - 1, 3);
assert(boxBounds.bottom >= boxBounds.top);
assert(boxBounds.right >= boxBounds.left);
int16 indexVal = READ_LE_UINT16(srcP);
srcP += sizeof(uint16);
if (indexVal >= 0) {
// Draw a 4x4 block based on the opcode as in index into the block list
const uint8 *p = (uint8 *)pObj->charBase + (indexVal << 4);
p += boxBounds.top * sizeof(uint32);
for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp, p += sizeof(uint32)) {
Common::copy(p + boxBounds.left, p + boxBounds.right + 1, tempDest + (SCREEN_WIDTH * (yp - boxBounds.top)));
}
} else {
// Draw a 4x4 block with transparency support
indexVal &= 0x7fff;
// If index is zero, then skip drawing the block completely
if (indexVal > 0) {
// Use the index along with the object's translation offset
const uint8 *p = (uint8 *)pObj->charBase + ((pObj->transOffset + indexVal) << 4);
// Loop through each pixel - only draw a pixel if it's non-zero
p += boxBounds.top * sizeof(uint32);
for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp) {
p += boxBounds.left;
for (int xp = boxBounds.left; xp <= boxBounds.right; ++xp, ++p) {
if (*p)
*(tempDest + SCREEN_WIDTH * (yp - boxBounds.top) + (xp - boxBounds.left)) = *p;
}
p += 3 - boxBounds.right;
}
}
}
tempDest += boxBounds.right - boxBounds.left + 1;
width -= 3 - boxBounds.left + 1;
// None of the remaining horizontal blocks should be left clipped
boxBounds.left = 0;
}
// If there is any width remaining, there must be a right edge clipping
if (width >= 0)
srcP += sizeof(uint16) * ((width + 3) >> 2);
// Move to next line line
pObj->height -= boxBounds.bottom - boxBounds.top + 1;
destP += (boxBounds.bottom - boxBounds.top + 1) * SCREEN_WIDTH;
}
}
/**
* Tinsel 2 Straight rendering with transparency support
*/
static void t2WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping, bool horizFlipped) {
// Setup for correct clipping of object edges
int yClip = applyClipping ? pObj->topClip : 0;
if (applyClipping)
pObj->height -= pObj->botClip;
int numBytes;
int clipAmount;
for (int y = 0; y < pObj->height; ++y) {
// Get the position to start writing out from
uint8 *tempP = !horizFlipped ? destP :
destP + (pObj->width - pObj->leftClip - pObj->rightClip) - 1;
int leftClip = applyClipping ? pObj->leftClip : 0;
int rightClip = applyClipping ? pObj->rightClip : 0;
if (horizFlipped)
SWAP(leftClip, rightClip);
int x = 0;
while (x < pObj->width) {
// Get the next opcode
numBytes = *srcP++;
if (numBytes & 0x80) {
// Run length following
numBytes &= 0x7f;
clipAmount = MIN(numBytes, leftClip);
leftClip -= clipAmount;
x+= clipAmount;
int runLength = numBytes - clipAmount;
uint8 colour = *srcP++;
if ((yClip == 0) && (runLength > 0) && (colour != 0)) {
runLength = MIN(runLength, pObj->width - rightClip - x);
if (runLength > 0) {
// Non-transparent run length
colour += pObj->constant;
if (horizFlipped)
Common::set_to(tempP - runLength + 1, tempP + 1, colour);
else
Common::set_to(tempP, tempP + runLength, colour);
}
}
if (horizFlipped)
tempP -= runLength;
else
tempP += runLength;
x += numBytes - clipAmount;
} else {
// Dump a length of pixels
clipAmount = MIN(numBytes, leftClip);
leftClip -= clipAmount;
srcP += clipAmount;
int runLength = numBytes - clipAmount;
x += numBytes - runLength;
for (int xp = 0; xp < runLength; ++xp) {
if ((yClip > 0) || (x >= (pObj->width - rightClip)))
++srcP;
else if (horizFlipped)
*tempP-- = pObj->constant + *srcP++;
else
*tempP++ = pObj->constant + *srcP++;
++x;
}
}
}
assert(x == pObj->width);
if (yClip > 0)
--yClip;
else
destP += SCREEN_WIDTH;
}
}
/**
* Fill the destination area with a constant colour
*/
static void WrtConst(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) {
if (applyClipping) {
pObj->height -= pObj->topClip + pObj->botClip;
pObj->width -= pObj->leftClip + pObj->rightClip;
if (pObj->width <= 0)
return;
}
// Loop through any remaining lines
while (pObj->height > 0) {
Common::set_to(destP, destP + pObj->width, pObj->constant);
--pObj->height;
destP += SCREEN_WIDTH;
}
}
/**
* Translates the destination surface within the object's bounds using the transparency
* lookup table from transpal.cpp (the contents of which have been moved into palette.cpp)
*/
static void WrtTrans(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) {
if (applyClipping) {
pObj->height -= pObj->topClip + pObj->botClip;
pObj->width -= pObj->leftClip + pObj->rightClip;
if (pObj->width <= 0)
return;
}
// Set up the offset between destination lines
int lineOffset = SCREEN_WIDTH - pObj->width;
// Loop through any remaining lines
while (pObj->height > 0) {
for (int i = 0; i < pObj->width; ++i, ++destP)
*destP = transPalette[*destP];
--pObj->height;
destP += lineOffset;
}
}
/**
* Copies an uncompressed block of data straight to the screen
*/
static void WrtAll(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) {
int objWidth = pObj->width;
if (applyClipping) {
srcP += (pObj->topClip * pObj->width) + pObj->leftClip;
pObj->height -= pObj->topClip + pObj->botClip;
pObj->width -= pObj->leftClip + pObj->rightClip;
if (pObj->width <= 0)
return;
}
for (int y = 0; y < pObj->height; ++y) {
Common::copy(srcP, srcP + pObj->width, destP);
srcP += objWidth;
destP += SCREEN_WIDTH;
}
}
/**
* Renders a packed data stream with a variable sized palette
*/
static void PackedWrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP,
bool applyClipping, bool horizFlipped, int packingType) {
uint8 numColours = 0;
uint8 *colourTable = NULL;
int topClip = 0;
int xOffset = 0;
int numBytes, colour;
int v;
if (applyClipping) {
pObj->height -= pObj->botClip;
topClip = pObj->topClip;
}
if (packingType == 3) {
// Variable colours
numColours = *srcP++;
colourTable = srcP;
srcP += numColours;
}
for (int y = 0; y < pObj->height; ++y) {
// Get the position to start writing out from
uint8 *tempP = !horizFlipped ? destP :
destP + (pObj->width - pObj->leftClip - pObj->rightClip) - 1;
int leftClip = applyClipping ? pObj->leftClip : 0;
int rightClip = applyClipping ? pObj->rightClip : 0;
if (horizFlipped)
SWAP(leftClip, rightClip);
bool eolFlag = false;
// Get offset for first pixels in next line
xOffset = *srcP++;
int x = 0;
while (x < pObj->width) {
// Get next run size and colour to use
for (;;) {
if (xOffset > 0) {
x += xOffset;
// Reduce offset amount by any remaining left clipping
v = MIN(xOffset, leftClip);
xOffset -= v;
leftClip -= v;
if (horizFlipped) tempP -= xOffset; else tempP += xOffset;
xOffset = 0;
}
v = *srcP++;
numBytes = v & 0xf; // No. bytes 1-15
if (packingType == 3)
colour = colourTable[v >> 4];
else
colour = pObj->baseCol + (v >> 4);
if (numBytes != 0)
break;
numBytes = *srcP++;
if (numBytes >= 16)
break;
xOffset = numBytes + v;
if (xOffset == 0) {
// End of line encountered
eolFlag = true;
break;
}
}
if (eolFlag)
break;
// Apply clipping on byte sequence
v = MIN(numBytes, leftClip);
leftClip -= v;
numBytes -= v;
x += v;
while (numBytes-- > 0) {
if ((topClip == 0) && (x < (pObj->width - rightClip))) {
*tempP = colour;
if (horizFlipped) --tempP; else ++tempP;
}
++x;
}
}
assert(x <= pObj->width);
if (!eolFlag) {
// Assert that the next bytes signal a line end
assert((*srcP++ & 0xf) == 0);
assert(*srcP++ == 0);
}
if (topClip > 0)
--topClip;
else
destP += SCREEN_WIDTH;
}
}
//----------------- MAIN FUNCTIONS ---------------------
/**
* Clears both the screen surface buffer and screen to the specified value
*/
void ClearScreen() {
void *pDest = _vm->screen().getBasePtr(0, 0);
memset(pDest, 0, SCREEN_WIDTH * SCREEN_HEIGHT);
g_system->clearScreen();
g_system->updateScreen();
}
/**
* Updates the screen surface within the following rectangle
*/
void UpdateScreenRect(const Common::Rect &pClip) {
int yOffset = (g_system->getHeight() - SCREEN_HEIGHT) / 2;
byte *pSrc = (byte *)_vm->screen().getBasePtr(pClip.left, pClip.top);
g_system->copyRectToScreen(pSrc, _vm->screen().pitch, pClip.left, pClip.top + yOffset,
pClip.width(), pClip.height());
g_system->updateScreen();
}
/**
* Draws the specified object onto the screen surface buffer
*/
void DrawObject(DRAWOBJECT *pObj) {
uint8 *srcPtr = NULL;
uint8 *destPtr;
if ((pObj->width <= 0) || (pObj->height <= 0))
// Empty image, so return immediately
return;
// If writing constant data, don't bother locking the data pointer and reading src details
if ((pObj->flags & DMA_CONST) == 0) {
if (TinselV2) {
srcPtr = (byte *)LockMem(pObj->hBits);
pObj->charBase = NULL;
pObj->transOffset = 0;
} else {
byte *p = (byte *)LockMem(pObj->hBits & HANDLEMASK);
srcPtr = p + (pObj->hBits & OFFSETMASK);
pObj->charBase = (char *)p + READ_LE_UINT32(p + 0x10);
pObj->transOffset = READ_LE_UINT32(p + 0x14);
}
}
// Get destination starting point
destPtr = (byte *)_vm->screen().getBasePtr(pObj->xPos, pObj->yPos);
// Handle various draw types
uint8 typeId = pObj->flags & 0xff;
if (TinselV2) {
// Tinsel v2 decoders
// Initial switch statement for the different bit packing types
int packType = pObj->flags >> 14;
if (packType == 0) {
// No colour packing
switch (typeId) {
case 0x01:
case 0x11:
case 0x41:
case 0x51:
case 0x81:
case 0xC1:
t2WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40, (typeId & 0x10) != 0);
break;
case 0x02:
case 0x42:
// This renderer called 'RlWrtAll', but is the same as t2WrtNonZero
t2WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40, false);
break;
case 0x04:
case 0x44:
// WrtConst with/without clipping
WrtConst(pObj, destPtr, typeId == 0x44);
break;
case 0x08:
case 0x48:
WrtAll(pObj, srcPtr, destPtr, typeId >= 0x40);
break;
case 0x84:
case 0xC4:
// WrtTrans with/without clipping
WrtTrans(pObj, destPtr, typeId == 0xC4);
break;
default:
error("Unknown drawing type %d", typeId);
}
} else {
// 1 = 16 from 240
// 2 = 16 from 224
// 3 = variable colour
if (packType == 1) pObj->baseCol = 0xF0;
else if (packType == 2) pObj->baseCol = 0xE0;
PackedWrtNonZero(pObj, srcPtr, destPtr, (pObj->flags & DMA_CLIP) != 0,
(pObj->flags & DMA_FLIPH), packType);
}
} else {
// Tinsel v1 decoders
switch (typeId) {
case 0x01:
case 0x08:
case 0x41:
case 0x48:
WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40);
break;
case 0x04:
case 0x44:
// WrtConst with/without clipping
WrtConst(pObj,destPtr, typeId == 0x44);
break;
case 0x84:
case 0xC4:
// WrtTrans with/without clipping
WrtTrans(pObj, destPtr, typeId == 0xC4);
break;
default:
// NoOp
error("Unknown drawing type %d", typeId);
break;
}
}
}
} // End of namespace Tinsel