mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-04 18:06:26 +00:00
1269 lines
35 KiB
C++
1269 lines
35 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.
|
|
*
|
|
*/
|
|
|
|
|
|
#ifdef ENABLE_EOB
|
|
|
|
#include "common/system.h"
|
|
#include "kyra/resource/resource.h"
|
|
#include "kyra/graphics/screen_eob.h"
|
|
#include "kyra/graphics/screen_eob_segacd.h"
|
|
|
|
namespace Kyra {
|
|
|
|
void Screen_EoB::sega_initGraphics() {
|
|
_segaRenderer = new SegaRenderer(this);
|
|
_segaRenderer->setResolution(320, 224);
|
|
_segaRenderer->setPlaneTableLocation(SegaRenderer::kPlaneA, 0xC000);
|
|
_segaRenderer->setPlaneTableLocation(SegaRenderer::kPlaneB, 0xE000);
|
|
_segaRenderer->setPlaneTableLocation(SegaRenderer::kWindowPlane, 0xF000);
|
|
_segaRenderer->setupPlaneAB(512, 512);
|
|
_segaRenderer->setupWindowPlane(0, 0, SegaRenderer::kWinToLeft, SegaRenderer::kWinToTop);
|
|
_segaRenderer->setHScrollTableLocation(0xD800);
|
|
_segaRenderer->setSpriteTableLocation(0xDC00);
|
|
_segaAnimator = new SegaAnimator(_segaRenderer);
|
|
}
|
|
|
|
void Screen_EoB::sega_selectPalette(int srcPalID, int dstPalID, bool set) {
|
|
if (srcPalID < -1 || srcPalID > 59 || dstPalID < 0 || dstPalID > 3)
|
|
return;
|
|
|
|
const uint16 *src = &_segaCurPalette[dstPalID << 4];
|
|
uint8 rgbColors[48];
|
|
uint8 *dst = rgbColors;
|
|
|
|
if (srcPalID >= 31 && srcPalID <= 38) {
|
|
src = &_segaCustomPalettes[(srcPalID - 31) << 4];
|
|
} else if (srcPalID >= 0) {
|
|
int temp = 0;
|
|
const uint16 *palettes = _vm->staticres()->loadRawDataBe16(kEoB1PalettesSega, temp);
|
|
if (!palettes)
|
|
return;
|
|
src = &palettes[srcPalID << 4];
|
|
}
|
|
|
|
// R: bits 1, 2, 3 G: bits 5, 6, 7 B: bits 9, 10, 11
|
|
for (int i = 0; i < 16; ++i) {
|
|
uint16 in = *src++;
|
|
_segaCurPalette[dstPalID << 4 | i] = in;
|
|
#if 0
|
|
static const uint8 col[8] = { 0, 52, 87, 116, 144, 172, 206, 255 };
|
|
*dst++ = col[CLIP<int>(((in & 0x00F) >> 1) + _palFaders[dstPalID]._brCur, 0, 7)];
|
|
*dst++ = col[CLIP<int>(((in & 0x0F0) >> 5) + _palFaders[dstPalID]._brCur, 0, 7)];
|
|
*dst++ = col[CLIP<int>(((in & 0xF00) >> 9) + _palFaders[dstPalID]._brCur, 0, 7)];
|
|
#else
|
|
*dst++ = CLIP<int>(((in & 0x00F) >> 1) + _palFaders[dstPalID]._brCur, 0, 7) * 255 / 7;
|
|
*dst++ = CLIP<int>(((in & 0x0F0) >> 5) + _palFaders[dstPalID]._brCur, 0, 7) * 255 / 7;
|
|
*dst++ = CLIP<int>(((in & 0xF00) >> 9) + _palFaders[dstPalID]._brCur, 0, 7) * 255 / 7;
|
|
#endif
|
|
}
|
|
|
|
getPalette(0).copy(rgbColors, 0, 16, dstPalID << 4);
|
|
|
|
if (_specialColorReplace) {
|
|
const uint8 swapColors[6] = { 0x08, 0x09, 0x0C, 0x0D, 0x0E, 0x0F };
|
|
for (int i = 0; i < 6; ++i)
|
|
getPalette(0).copy(getPalette(0), 0x10 | swapColors[i], 1, swapColors[i]);
|
|
}
|
|
|
|
if (set)
|
|
setScreenPalette(getPalette(0));
|
|
}
|
|
|
|
void Screen_EoB::sega_loadCustomPaletteData(Common::ReadStream *in) {
|
|
uint16 *dst = _segaCustomPalettes;
|
|
for (int i = 0; i < 8; ++i) {
|
|
*dst++ = 0;
|
|
in->readUint16BE();
|
|
if (in->eos())
|
|
break;
|
|
for (int ii = 1; ii < 16; ++ii)
|
|
*dst++ = in->readUint16BE();
|
|
}
|
|
}
|
|
|
|
void Screen_EoB::sega_updatePaletteFaders(int palID) {
|
|
int first = 0;
|
|
int last = 3;
|
|
|
|
if (palID >= 0)
|
|
first = last = palID;
|
|
|
|
bool update = false;
|
|
for (int i = first; i <= last; ++i) {
|
|
PaletteFader &f = _palFaders[i];
|
|
f._needRefresh = false;
|
|
if (f._fadeDelay == 0 && f._brCur != f._brDest) {
|
|
f._brCur = f._brDest;
|
|
f._needRefresh = true;
|
|
}
|
|
if (f._brCur == f._brDest)
|
|
continue;
|
|
if (--f._fadeTimer)
|
|
continue;
|
|
|
|
f._brCur += f._fadeIncr;
|
|
f._fadeTimer = f._fadeDelay;
|
|
f._needRefresh = true;
|
|
}
|
|
|
|
for (int i = first; i <= last; ++i) {
|
|
if (_palFaders[i]._needRefresh) {
|
|
sega_selectPalette(-1, i, true);
|
|
update = true;
|
|
_palFaders[i]._needRefresh = false;
|
|
}
|
|
}
|
|
|
|
if (update)
|
|
updateScreen();
|
|
}
|
|
|
|
void Screen_EoB::sega_fadePalette(int delay, int16 brEnd, int dstPalID, bool waitForCompletion, bool noUpdate) {
|
|
int first = 0;
|
|
int last = 3;
|
|
uint32 tickMillis = 0;
|
|
|
|
if (dstPalID >= 0)
|
|
first = last = dstPalID;
|
|
|
|
if (!noUpdate) {
|
|
for (int i = first; i <= last; ++i) {
|
|
PaletteFader &f = _palFaders[i];
|
|
f._needRefresh = false;
|
|
if (f._brCur < brEnd)
|
|
f._fadeIncr = 1;
|
|
else if (f._brCur > brEnd)
|
|
f._fadeIncr = -1;
|
|
else
|
|
continue;
|
|
|
|
f._brDest = brEnd;
|
|
f._fadeDelay = f._fadeTimer = delay;
|
|
}
|
|
}
|
|
|
|
if (!waitForCompletion)
|
|
return;
|
|
|
|
for (bool runLoop = true; runLoop; ) {
|
|
uint32 now = _vm->_system->getMillis();
|
|
sega_updatePaletteFaders(dstPalID);
|
|
|
|
runLoop = false;
|
|
for (int i = first; i <= last; ++i) {
|
|
if (_palFaders[i]._brCur != _palFaders[i]._brDest)
|
|
runLoop = true;
|
|
}
|
|
|
|
tickMillis += 16667;
|
|
uint32 ms = tickMillis / 1000;
|
|
_vm->delayUntil(now + ms);
|
|
tickMillis -= (ms * 1000);
|
|
|
|
if (_vm->shouldQuit()) {
|
|
for (int i = first; i <= last; ++i)
|
|
_palFaders[i]._fadeDelay = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Screen_EoB::sega_paletteOps(int16 op, int16 par1, int16 par2) {
|
|
assert(op >= 0 && op <= 6);
|
|
switch (op) {
|
|
case 6:
|
|
// Force palette update and wait for completion
|
|
break;
|
|
case 5:
|
|
// Force palette update, don't wait
|
|
break;
|
|
case 4:
|
|
_specialColorReplace = par1;
|
|
break;
|
|
default:
|
|
sega_fadePalette(par2, par1, op, false);
|
|
}
|
|
}
|
|
|
|
void Screen_EoB::sega_setTextBuffer(uint8 *buffer, uint32 bufferSize) {
|
|
if (!buffer) {
|
|
_textRenderBuffer = _defaultRenderBuffer;
|
|
_textRenderBufferSize = _defaultRenderBufferSize;
|
|
} else {
|
|
_textRenderBuffer = buffer;
|
|
_textRenderBufferSize = bufferSize;
|
|
}
|
|
}
|
|
|
|
void Screen_EoB::sega_clearTextBuffer(uint8 col) {
|
|
memset(_textRenderBuffer, col, _textRenderBufferSize);
|
|
}
|
|
|
|
void Screen_EoB::sega_loadTextBackground(const uint8 *src, uint16 size) {
|
|
assert(size <= _textRenderBufferSize);
|
|
memcpy(_textRenderBuffer, src, size);
|
|
}
|
|
|
|
void Screen_EoB::sega_drawTextBox(int pW, int pH, int x, int y, int w, int h, uint8 color1, uint8 color2) {
|
|
sega_drawClippedLine(26, 5, x, y, w, 1, color1);
|
|
sega_drawClippedLine(26, 5, x, y + h - 1, w, 1, color1);
|
|
sega_drawClippedLine(26, 5, x, y, 1, h, color1);
|
|
sega_drawClippedLine(26, 5, x + w - 1, y, 1, h, color1);
|
|
sega_drawClippedLine(26, 5, x + 1, y + 1, w - 2, 1, color2);
|
|
sega_drawClippedLine(26, 5, x + 1, y + h - 2, w - 2, 1, color2);
|
|
sega_drawClippedLine(26, 5, x + 1, y + 1, 1, h - 2, color2);
|
|
sega_drawClippedLine(26, 5, x + w - 2, y + 1, 1, h - 2, color2);
|
|
}
|
|
|
|
void Screen_EoB::sega_loadTextBufferToVRAM(uint16 srcOffset, uint16 addr, int size) {
|
|
_segaRenderer->loadToVRAM(_textRenderBuffer + srcOffset, size, addr);
|
|
}
|
|
|
|
void Screen_EoB::sega_gfxScale(uint8 *out, uint16 w, uint16 h, uint16 pitch, const uint8 *in, const uint16 *stampMap, const uint16 *traceVectors) {
|
|
// Implement only the required functions. No support for stamp size other than 0 or for rotation/flipping.
|
|
while (h--) {
|
|
uint32 xt = *traceVectors++ << 8;
|
|
uint32 yt = *traceVectors++ << 8;
|
|
int16 hStep = (int16)(*traceVectors++);
|
|
int16 vStep = (int16)(*traceVectors++);
|
|
uint8 hcnt = 0;
|
|
uint8 *out2 = out;
|
|
|
|
for (int x = 0; x < w; ++x) {
|
|
uint16 s = stampMap[((yt >> 11) & 0xF0) + ((xt >> 15) & 0x0F)];
|
|
uint8 val = 0;
|
|
//uint16 rotateFlip = (s >> 11) & 0x1C;
|
|
s &= 0x7FF;
|
|
if (!(yt & 0xF80000) && !(xt & 0xF80000) && s) {
|
|
val = in[(s << 7) + ((yt >> 9) & 0x3C) + ((xt >> 8) & 0x40) + ((xt >> 12) & 3)];
|
|
if (!(xt & 0x800))
|
|
val >>= 4;
|
|
}
|
|
|
|
if (x & 1)
|
|
*out++ |= (val & 0x0F);
|
|
else
|
|
*out = val << 4;
|
|
|
|
xt += hStep;
|
|
yt += vStep;
|
|
if (++hcnt == 8) {
|
|
out = out + (pitch << 5) + 28;
|
|
hcnt = 0;
|
|
}
|
|
}
|
|
out = out2 + 4;
|
|
}
|
|
}
|
|
|
|
void Screen_EoB::sega_drawClippedLine(int pW, int pH, int x, int y, int w, int h, uint8 color) {
|
|
uint8 *dst = _textRenderBuffer;
|
|
uint8 p = (x & 1) ? 0x0F : 0xF0;
|
|
color &= p;
|
|
p = ~p;
|
|
|
|
dst += ((((y >> 3) * pW + (x >> 3)) << 5) + ((y & 7) << 2) + ((x & 7) >> 1));
|
|
|
|
while (h--) {
|
|
uint8 *dst2 = dst;
|
|
uint8 p2 = p;
|
|
uint8 col2 = color;
|
|
for (int i = x; i < x + w; ++i) {
|
|
*dst = (*dst & p) | color;
|
|
p = ~p;
|
|
color = (color << 4) | (color >> 4);
|
|
if (i & 1)
|
|
dst++;
|
|
if ((i & 7) == 7)
|
|
dst += 28;
|
|
}
|
|
dst = dst2 + 4;
|
|
color = col2;
|
|
p = p2;
|
|
if ((++y & 7) == 0)
|
|
dst = dst + (pW << 5) - 32;
|
|
}
|
|
}
|
|
|
|
uint8 *Screen_EoB::sega_convertShape(const uint8 *src, int w, int h, int pal, int hOffs) {
|
|
uint8 *shp = new uint8[(w >> 1) * h + 20];
|
|
uint8 *dst = shp;
|
|
*dst++ = 2;
|
|
*dst++ = h;
|
|
*dst++ = w >> 3;
|
|
*dst++ = h + hOffs;
|
|
*dst++ = 0;
|
|
|
|
for (int i = 1; i < 16; i++)
|
|
*dst++ = (pal << 4) | i;
|
|
|
|
const uint8 *pos = src;
|
|
for (int i = 0; i < h; ++i) {
|
|
const uint8 *pos2 = pos;
|
|
for (int ii = 0; ii < (w >> 1); ++ii) {
|
|
*dst++ = *pos;
|
|
pos += h;
|
|
}
|
|
pos = pos2 + 1;
|
|
}
|
|
|
|
return shp;
|
|
}
|
|
|
|
void Screen_EoB::sega_encodeShapesFromSprites(const uint8 **dst, const uint8 *src, int numShapes, int w, int h, int pal, bool removeSprites) {
|
|
int spriteSize = (w * h) >> 1;
|
|
_segaRenderer->loadToVRAM(src, numShapes * spriteSize, 0);
|
|
int hw = (((w >> 3) - 1) << 2) | ((h >> 3) - 1);
|
|
|
|
int cp = setCurPage(Screen_EoB::kSegaInitShapesPage);
|
|
|
|
for (int l = 0, s = 0; s < numShapes; l = s) {
|
|
for (int i = s; i < numShapes; ++i) {
|
|
_segaAnimator->initSprite(s % 80, ((s % 80) * w) % SCREEN_W, ((s % 80) / (SCREEN_W / w)) * h, ((pal << 13) | (i * (w >> 3) * (h >> 3))), hw);
|
|
if (((++s) % 80) == 0)
|
|
break;
|
|
}
|
|
|
|
_segaAnimator->update();
|
|
_segaRenderer->render(Screen_EoB::kSegaInitShapesPage, -1, -1, -1, -1, true);
|
|
|
|
for (int i = l; i < s; ++i)
|
|
dst[i] = encodeShape((((i % 80) * w) % SCREEN_W) >> 3, ((i % 80) / (SCREEN_W / w)) * h, w >> 3, h);
|
|
|
|
clearPage(Screen_EoB::kSegaInitShapesPage);
|
|
}
|
|
|
|
if (removeSprites) {
|
|
_segaAnimator->clearSprites();
|
|
_segaAnimator->update();
|
|
_segaRenderer->memsetVRAM(0, 0, numShapes * spriteSize);
|
|
}
|
|
|
|
setCurPage(cp);
|
|
}
|
|
|
|
#if SEGA_PERFORMANCE
|
|
#define mRenderLineFragment(hFlip, oddStart, oddEnd, useMask, dst, mask, src, start, end, pal) \
|
|
{ \
|
|
int rlfOffs = 0; \
|
|
if (hFlip) \
|
|
rlfOffs |= 4; \
|
|
if (oddStart) \
|
|
rlfOffs |= 2; \
|
|
if (oddEnd) \
|
|
rlfOffs |= 1; \
|
|
if (useMask) \
|
|
(this->*_renderLineFragmentM[rlfOffs])(dst, mask, src, start, end, pal); \
|
|
else \
|
|
(this->*_renderLineFragmentD[rlfOffs])(dst, src, start, end, pal); \
|
|
}
|
|
#else
|
|
#define mRenderLineFragment(hFlip, oddStart, oddEnd, useMask, dst, mask, src, start, end, pal) \
|
|
{ \
|
|
if (hFlip) \
|
|
renderLineFragment<true>(dst, mask, src, start, end, pal); \
|
|
else \
|
|
renderLineFragment<false>(dst, mask, src, start, end, pal); \
|
|
}
|
|
#endif
|
|
|
|
SegaRenderer::SegaRenderer(Screen_EoB *screen) : _screen(screen), _prioChainStart(0), _prioChainEnd(0), _pitch(64), _hScrollMode(0), _hScrollTable(0), _vScrollMode(0), _spriteTable(0), _numSpritesMax(0), _spriteMask(0)
|
|
#if SEGA_PERFORMANCE
|
|
, _renderLineFragmentD(0), _renderLineFragmentM(0)
|
|
#endif
|
|
{
|
|
_vram = new uint8[0x10000];
|
|
assert(_vram);
|
|
memset(_vram, 0, 0x10000 * sizeof(uint8));
|
|
_vsram = new uint16[40];
|
|
assert(_vsram);
|
|
memset(_vsram, 0, 40 * sizeof(uint16));
|
|
|
|
#if SEGA_PERFORMANCE
|
|
static const SegaRenderer::renderFuncD funcD[8] = {
|
|
&SegaRenderer::renderLineFragmentD<false, false, false>,
|
|
&SegaRenderer::renderLineFragmentD<false, false, true>,
|
|
&SegaRenderer::renderLineFragmentD<false, true, false>,
|
|
&SegaRenderer::renderLineFragmentD<false, true, true>,
|
|
&SegaRenderer::renderLineFragmentD<true, false, false>,
|
|
&SegaRenderer::renderLineFragmentD<true, false, true>,
|
|
&SegaRenderer::renderLineFragmentD<true, true, false>,
|
|
&SegaRenderer::renderLineFragmentD<true, true, true>
|
|
};
|
|
|
|
static const SegaRenderer::renderFuncM funcM[8] = {
|
|
&SegaRenderer::renderLineFragmentM<false, false, false>,
|
|
&SegaRenderer::renderLineFragmentM<false, false, true>,
|
|
&SegaRenderer::renderLineFragmentM<false, true, false>,
|
|
&SegaRenderer::renderLineFragmentM<false, true, true>,
|
|
&SegaRenderer::renderLineFragmentM<true, false, false>,
|
|
&SegaRenderer::renderLineFragmentM<true, false, true>,
|
|
&SegaRenderer::renderLineFragmentM<true, true, false>,
|
|
&SegaRenderer::renderLineFragmentM<true, true, true>
|
|
};
|
|
|
|
_renderLineFragmentD = funcD;
|
|
_renderLineFragmentM = funcM;
|
|
#endif
|
|
|
|
setResolution(320, 224);
|
|
}
|
|
|
|
SegaRenderer::~SegaRenderer() {
|
|
delete[] _vram;
|
|
delete[] _vsram;
|
|
delete[] _spriteMask;
|
|
}
|
|
|
|
void SegaRenderer::setResolution(int w, int h) {
|
|
assert(w == 320 || w == 256);
|
|
assert(h == 224 || h == 240);
|
|
|
|
_screenW = w;
|
|
_screenH = h;
|
|
_blocksW = w >> 3;
|
|
_blocksH = h >> 3;
|
|
_numSpritesMax = w >> 2;
|
|
|
|
delete[] _spriteMask;
|
|
_spriteMask = new uint8[w * h];
|
|
assert(_spriteMask);
|
|
memset(_spriteMask, 0, w * h * sizeof(uint8));
|
|
}
|
|
|
|
void SegaRenderer::setPlaneTableLocation(int plane, uint16 addr) {
|
|
assert(plane >= kPlaneA && plane <= kWindowPlane);
|
|
_planes[plane].nameTable = (uint16*)(&_vram[addr]);
|
|
}
|
|
|
|
void SegaRenderer::setupPlaneAB(int pixelWidth, int pixelHeigth) {
|
|
for (int i = 0; i < 2; ++i) {
|
|
if (pixelWidth != -1)
|
|
_planes[i].w = pixelWidth >> 3;
|
|
if (pixelHeigth != -1)
|
|
_planes[i].h = pixelHeigth >> 3;
|
|
_planes[i].mod = _planes[i].h;
|
|
_planes[i].nameTableSize = _planes[i].w * _planes[i].h;
|
|
}
|
|
}
|
|
|
|
void SegaRenderer::setupWindowPlane(int blockX, int blockY, int horizontalMode, int verticalMode) {
|
|
if (blockX != -1)
|
|
_planes[kWindowPlane].blockX = horizontalMode ? blockX : 0;
|
|
if (blockY != -1)
|
|
_planes[kWindowPlane].blockY = verticalMode ? blockY : 0;
|
|
_planes[kWindowPlane].w = horizontalMode ? _blocksW - blockX : blockX;
|
|
_planes[kWindowPlane].h = verticalMode ? _blocksH - blockY : blockY;
|
|
_planes[kWindowPlane].mod = _planes[kWindowPlane].blockY + _planes[kWindowPlane].h;
|
|
_planes[kWindowPlane].nameTableSize = _planes[kWindowPlane].w * _planes[kWindowPlane].h;
|
|
}
|
|
|
|
void SegaRenderer::setHScrollTableLocation(int addr) {
|
|
assert(addr <= 0xFFFF);
|
|
_hScrollTable = (uint16*)(&_vram[addr]);
|
|
}
|
|
|
|
void SegaRenderer::setSpriteTableLocation(int addr) {
|
|
assert(addr <= 0xFFFF);
|
|
_spriteTable = (uint16*)(&_vram[addr]);
|
|
}
|
|
|
|
void SegaRenderer::setPitch(int pitch) {
|
|
_pitch = pitch;
|
|
}
|
|
|
|
void SegaRenderer::setHScrollMode(int mode) {
|
|
_hScrollMode = mode;
|
|
}
|
|
|
|
void SegaRenderer::setVScrollMode(int mode) {
|
|
_vScrollMode = mode;
|
|
}
|
|
|
|
void SegaRenderer::loadToVRAM(const void *data, uint16 dataSize, uint16 addr) {
|
|
assert(data);
|
|
assert(addr + dataSize <= 0x10000);
|
|
memcpy(_vram + addr, data, dataSize);
|
|
}
|
|
|
|
void SegaRenderer::loadStreamToVRAM(Common::SeekableReadStream *in, uint16 addr, bool compressedData) {
|
|
assert(in);
|
|
uint8 *dst = _vram + addr;
|
|
|
|
if (compressedData) {
|
|
uint16 decodeSize = 0;
|
|
uint8 *data = new uint8[in->size()];
|
|
uint32 readSize = in->read(data, in->size());
|
|
decodeSize = READ_LE_UINT16(data + 2);
|
|
assert(decodeSize < readSize);
|
|
assert(decodeSize < 0x10000 - addr);
|
|
_screen->decodeBIN(data + 4, dst, decodeSize);
|
|
delete[] data;
|
|
} else {
|
|
assert(in->size() < 0x10000 - addr);
|
|
in->read(dst, in->size());
|
|
}
|
|
}
|
|
|
|
void SegaRenderer::memsetVRAM(int addr, uint8 val, int len) {
|
|
assert(addr + len <= 0x10000);
|
|
memset(_vram + addr, val, len);
|
|
void checkUpdateDirtyRects(int addr, int len);
|
|
}
|
|
|
|
void SegaRenderer::fillRectWithTiles(int vramArea, int x, int y, int w, int h, uint16 nameTblEntry, bool incr, bool topToBottom, const uint16 *patternTable) {
|
|
uint16 addr = vramArea ? (vramArea == 1 ? 0xE000 : 0xF000) : 0xC000;
|
|
if (y & 0x8000) {
|
|
y &= ~0x8000;
|
|
addr = 0xE000;
|
|
}
|
|
|
|
uint16 *dst = (uint16*)(&_vram[addr]);
|
|
dst += (y * _pitch + x);
|
|
int ptch = _pitch - w;
|
|
|
|
assert(addr + 2 * (y * _pitch + x + h * _pitch + w) <= 0xFFFF);
|
|
|
|
if (patternTable) {
|
|
while (h--) {
|
|
const uint16 *pos = patternTable;
|
|
for (int i = w; i; --i)
|
|
*dst++ = nameTblEntry + *pos++;
|
|
dst += ptch;
|
|
patternTable += w;
|
|
}
|
|
} else if (incr) {
|
|
if (topToBottom) {
|
|
while (w--) {
|
|
uint16 *dst2 = dst;
|
|
for (int i = h; i; --i) {
|
|
*dst = nameTblEntry++;
|
|
dst += _pitch;
|
|
}
|
|
dst = ++dst2;
|
|
}
|
|
} else {
|
|
while (h--) {
|
|
for (int i = w; i; --i)
|
|
*dst++ = nameTblEntry++;
|
|
dst += ptch;
|
|
}
|
|
}
|
|
} else {
|
|
if (topToBottom) {
|
|
while (w--) {
|
|
uint16 *dst2 = dst;
|
|
for (int i = h; i; --i) {
|
|
*dst = nameTblEntry;
|
|
dst += _pitch;
|
|
}
|
|
dst = ++dst2;
|
|
}
|
|
} else {
|
|
while (h--) {
|
|
for (int i = w; i; --i)
|
|
*dst++ = nameTblEntry;
|
|
dst += ptch;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SegaRenderer::writeUint16VSRAM(int addr, uint16 value) {
|
|
assert(addr < 80);
|
|
assert(!(addr & 1));
|
|
_vsram[addr >> 1] = value;
|
|
}
|
|
|
|
void SegaRenderer::writeUint8VRAM(int addr, uint8 value) {
|
|
assert(addr < 0x10000);
|
|
_vram[addr] = value;
|
|
}
|
|
|
|
void SegaRenderer::writeUint16VRAM(int addr, uint16 value) {
|
|
assert(addr < 0x10000);
|
|
*((uint16*)(_vram + addr)) = value;
|
|
}
|
|
|
|
void SegaRenderer::clearPlanes() {
|
|
for (int i = 0; i < 3; ++i) {
|
|
if (_planes[i].nameTableSize)
|
|
memset(_planes[i].nameTable, 0, _planes[i].nameTableSize * sizeof(uint16));
|
|
}
|
|
}
|
|
|
|
void SegaRenderer::render(int destPageNum, int renderBlockX, int renderBlockY, int renderBlockWidth, int renderBlockHeight, bool spritesOnly) {
|
|
if (renderBlockX == -1)
|
|
renderBlockX = 0;
|
|
if (renderBlockY == -1)
|
|
renderBlockY = 0;
|
|
if (renderBlockWidth == -1)
|
|
renderBlockWidth = _blocksW;
|
|
if (renderBlockHeight == -1)
|
|
renderBlockHeight = _blocksH;
|
|
|
|
uint8 *renderBuffer = _screen->getPagePtr(destPageNum);
|
|
// This also ensures that a dirty rect is created if necessary
|
|
_screen->fillRect(renderBlockX << 3, renderBlockY << 3, ((renderBlockX + renderBlockWidth) << 3) - 1, ((renderBlockY + renderBlockHeight) << 3) - 1, 0, destPageNum);
|
|
|
|
// Plane B
|
|
if (!spritesOnly)
|
|
renderPlanePart(kPlaneB, renderBuffer, renderBlockX, renderBlockY, renderBlockX + renderBlockWidth, renderBlockY + renderBlockHeight);
|
|
|
|
// Plane A (only draw if the nametable is not identical to that of plane B)
|
|
if (_planes[kPlaneA].nameTable != _planes[kPlaneB].nameTable && !spritesOnly) {
|
|
// If the window plane is active the rendering of plane A becomes more tedious because the window plane
|
|
// kind of replaces plane A in the space that is covered by it.
|
|
if (_planes[kWindowPlane].nameTableSize) {
|
|
SegaPlane *p = &_planes[kWindowPlane];
|
|
renderPlanePart(kPlaneA, renderBuffer, MAX<int>(0, renderBlockX), MAX<int>(0, renderBlockY), MIN<int>(p->blockX, renderBlockX + renderBlockWidth), MIN<int>(_blocksH, renderBlockY + renderBlockHeight));
|
|
renderPlanePart(kPlaneA, renderBuffer, MAX<int>(0, renderBlockX), MAX<int>(0, renderBlockY), MIN<int>(_blocksW, renderBlockX + renderBlockWidth), MIN<int>(p->blockY, renderBlockY + renderBlockHeight));
|
|
renderPlanePart(kPlaneA, renderBuffer, MAX<int>(p->blockX + p->w, renderBlockX), MAX<int>(0, renderBlockY), MIN<int>(_blocksW, renderBlockX + renderBlockWidth), MIN<int>(_blocksH, renderBlockY + renderBlockHeight));
|
|
renderPlanePart(kPlaneA, renderBuffer, MAX<int>(0, renderBlockX), MAX<int>(p->blockY + p->h, renderBlockY), MIN<int>(_blocksW, renderBlockX + renderBlockWidth), MIN<int>(_blocksH, renderBlockY + renderBlockHeight));
|
|
} else {
|
|
renderPlanePart(kPlaneA, renderBuffer, renderBlockX, renderBlockY, renderBlockX + renderBlockWidth, renderBlockY + renderBlockHeight);
|
|
}
|
|
}
|
|
|
|
// Window Plane
|
|
if (_planes[kWindowPlane].nameTableSize && !spritesOnly) {
|
|
SegaPlane *p = &_planes[kWindowPlane];
|
|
renderPlanePart(kWindowPlane, renderBuffer, MIN<int>(p->blockX, renderBlockX + renderBlockWidth), MIN<int>(p->blockY, renderBlockY + renderBlockHeight), MAX<int>(p->blockX + p->w, renderBlockX), MAX<int>(p->blockY + p->h, renderBlockY));
|
|
}
|
|
|
|
// Sprites
|
|
memset(_spriteMask, 0xFF, _screenW * _screenH * sizeof(uint8));
|
|
const uint16 *pos = _spriteTable;
|
|
for (int i = 0; i < _numSpritesMax && pos; ++i) {
|
|
int y = *pos++ & 0x3FF;
|
|
uint8 bH = ((*pos >> 8) & 3) + 1;
|
|
uint8 bW = ((*pos >> 10) & 3) + 1;
|
|
uint8 next = *pos++ & 0x7F;
|
|
uint16 pal = ((*pos >> 13) & 3) << 4;
|
|
bool prio = (*pos & 0x8000);
|
|
bool hflip = (*pos & 0x800);
|
|
bool vflip = (*pos & 0x1000);
|
|
uint16 tile = *pos++ & 0x7FF;
|
|
int x = *pos & 0x3FF;
|
|
|
|
// Sprite masking. Can't happen really, since the animator automatically adds 128 to x and y coords for all sprites.
|
|
assert(!(x == 0 && y >= 128));
|
|
|
|
assert(!hflip);
|
|
assert(!vflip);
|
|
|
|
x -= 128;
|
|
y -= 128;
|
|
|
|
/*if ((x >> 3) < renderBlockX) {
|
|
bW = MIN<int>(0, (int)bW - (renderBlockX - (x >> 3)));
|
|
x = (renderBlockX << 3);
|
|
|
|
}
|
|
|
|
if ((y >> 3) < renderBlockY) {
|
|
bH = MIN<int>(0, (int)bH - (renderBlockY - (y >> 3)));
|
|
y = (renderBlockY << 3);
|
|
}
|
|
|
|
bW = MIN<int>(bW, renderBlockWidth);
|
|
bH = MIN<int>(bH, renderBlockHeight);*/
|
|
|
|
uint8 *dst = renderBuffer + y * _screenW + x;
|
|
uint8 *msk = _spriteMask + y * _screenW + x;
|
|
|
|
for (int blX = 0; blX < bW; ++blX) {
|
|
uint8 *dst2 = dst;
|
|
uint8 *msk2 = msk;
|
|
for (int blY = 0; blY < bH; ++blY) {
|
|
renderSpriteTile(dst, msk, x + (blX << 3), y + (blY << 3), tile++, pal, vflip, hflip, prio);
|
|
dst += (_screenW << 3);
|
|
msk += (_screenW << 3);
|
|
}
|
|
dst = dst2 + 8;
|
|
msk = msk2 + 8;
|
|
}
|
|
|
|
pos = next ? &_spriteTable[next << 2] : 0;
|
|
}
|
|
|
|
// Priority Tiles
|
|
// Instead of going through all rendering passes for all planes again (only now drawing the
|
|
// prio tiles instead of the non-priority tiles) I have collected the data for the priority
|
|
// tiles on the way and put that data into a chain. Should be faster...
|
|
for (PrioTileRenderObj *e = _prioChainStart; e; e = e->_next)
|
|
mRenderLineFragment(e->_hflip, e->_start & 1, e->_end & 1, e->_mask, e->_dst, e->_mask, e->_src, e->_start, e->_end, e->_pal)
|
|
|
|
clearPrioChain();
|
|
}
|
|
|
|
void SegaRenderer::renderPlanePart(int plane, uint8 *dstBuffer, int x1, int y1, int x2, int y2) {
|
|
SegaPlane *p = &_planes[plane];
|
|
uint8 *dst = dstBuffer + (y1 << 3) * _screenW + (x1 << 3);
|
|
|
|
for (int y = y1; y < y2; ++y) {
|
|
int hScrollTableIndex = (plane == kWindowPlane) ? -1 : (_hScrollMode == kHScrollFullScreen) ? plane : (y1 << 4) + plane;
|
|
uint8 *dst2 = dst;
|
|
for (int x = x1; x < x2; ++x) {
|
|
int vScrollTableIndex = (plane == kWindowPlane) ? -1 : (_vScrollMode == kVScrollFullScreen) ? plane : (x & ~1) + plane;
|
|
uint16 vscrNt = 0;
|
|
uint16 vscrPxStart = 0;
|
|
uint16 vscrPxEnd = 8;
|
|
|
|
if (vScrollTableIndex != -1) {
|
|
vscrNt = _vsram[vScrollTableIndex] & 0x3FF;
|
|
vscrPxStart = vscrNt & 7;
|
|
vscrNt >>= 3;
|
|
}
|
|
|
|
int ty = (vscrNt + y) % p->mod;
|
|
|
|
renderPlaneTile(dst, x, &p->nameTable[ty * _pitch], vscrPxStart, vscrPxEnd, hScrollTableIndex, _pitch);
|
|
|
|
if (vscrPxStart) {
|
|
ty = (ty + 1) % p->mod;
|
|
uint16 dstOffs = (vscrPxEnd - vscrPxStart) * _screenW;
|
|
vscrPxEnd = vscrPxStart;
|
|
vscrPxStart = 0;
|
|
renderPlaneTile(dst + dstOffs, x, &p->nameTable[ty * _pitch], vscrPxStart, vscrPxEnd, hScrollTableIndex, _pitch);
|
|
}
|
|
dst += 8;
|
|
}
|
|
dst = dst2 + (_screenW << 3);
|
|
}
|
|
}
|
|
|
|
void SegaRenderer::renderPlaneTile(uint8 *dst, int ntblX, const uint16 *ntblLine, int vScrollLSBStart, int vScrollLSBEnd, int hScrollTableIndex, uint16 pitch) {
|
|
for (int bY = vScrollLSBStart; bY < vScrollLSBEnd; ++bY) {
|
|
uint8 *dst2 = dst;
|
|
uint16 hscrNt = 0;
|
|
uint16 hscrPx = 0;
|
|
|
|
if (hScrollTableIndex != -1) {
|
|
hscrNt = (-_hScrollTable[hScrollTableIndex]) & 0x3FF;
|
|
hscrPx = hscrNt & 7;
|
|
hscrNt >>= 3;
|
|
}
|
|
|
|
const uint16 *pNt = &ntblLine[(ntblX + hscrNt) % pitch];
|
|
if (pNt < (const uint16*)(&_vram[0x10000])) {
|
|
uint16 nt = *pNt;
|
|
uint16 pal = ((nt >> 13) & 3) << 4;
|
|
bool hflip = (nt & 0x800);
|
|
int y = bY % 8;
|
|
if (nt & 0x1000) // vflip
|
|
y = 7 - y;
|
|
|
|
// We skip the priority tiles here and draw them later
|
|
if (nt & 0x8000)
|
|
initPrioRenderTask(dst, 0, &_vram[((nt & 0x7FF) << 5) + (y << 2) + (hscrPx >> 1)], hscrPx, 8, pal, hflip);
|
|
else
|
|
mRenderLineFragment(hflip, hscrPx & 1, 0, 0, dst, 0, &_vram[((nt & 0x7FF) << 5) + (y << 2) + (hscrPx >> 1)], hscrPx, 8, pal);
|
|
}
|
|
|
|
if (hscrPx) {
|
|
dst += (8 - hscrPx);
|
|
pNt = &ntblLine[(ntblX + hscrNt + 1) % pitch];
|
|
if (pNt < (const uint16*)(&_vram[0x10000])) {
|
|
uint16 nt = *pNt;
|
|
uint16 pal = ((nt >> 13) & 3) << 4;
|
|
bool hflip = (nt & 0x800);
|
|
int y = bY % 8;
|
|
if (nt & 0x1000) // vflip
|
|
y = 7 - y;
|
|
|
|
// We skip the priority tiles here and draw them later
|
|
if (nt & 0x8000)
|
|
initPrioRenderTask(dst, 0, &_vram[((nt & 0x7FF) << 5) + (y << 2)], 0, hscrPx, pal, hflip);
|
|
else
|
|
mRenderLineFragment(hflip, 0, hscrPx & 1, 0, dst, 0, &_vram[((nt & 0x7FF) << 5) + (y << 2)], 0, hscrPx, pal)
|
|
}
|
|
}
|
|
|
|
if (hScrollTableIndex != -1 && _hScrollMode == kHScroll1PixelRows)
|
|
hScrollTableIndex += 2;
|
|
dst = dst2 + _screenW;
|
|
}
|
|
}
|
|
|
|
#undef vflip
|
|
|
|
void SegaRenderer::renderSpriteTile(uint8 *dst, uint8 *mask, int x, int y, uint16 tile, uint8 pal, bool vflip, bool hflip, bool prio) {
|
|
if (y <= -8 || y >= _screenH || x <= -8 || x >= _screenW)
|
|
return;
|
|
|
|
const uint8 *src = &_vram[tile << 5];
|
|
if (vflip)
|
|
src += 31;
|
|
|
|
if (y < 0) {
|
|
dst -= (y * _screenW);
|
|
mask -= (y * _screenW);
|
|
} if (x < 0) {
|
|
dst -= x;
|
|
mask -= x;
|
|
}
|
|
|
|
int xstart = CLIP<int>(-x, 0, 7);
|
|
int xend = CLIP<int>(_screenW - x, 0, 8);
|
|
src += (xstart >> 1);
|
|
|
|
int ystart = CLIP<int>(-y, 0, 7);
|
|
int yend = CLIP<int>(_screenH - y, 0, 8);
|
|
src += (ystart << 2);
|
|
|
|
for (int bY = ystart; bY < yend; ++bY) {
|
|
uint8 *dst2 = dst;
|
|
uint8 *msk2 = mask;
|
|
|
|
if (prio)
|
|
initPrioRenderTask(dst, mask, src, xstart, xend, pal, hflip);
|
|
else
|
|
mRenderLineFragment(hflip, xstart & 1, xend & 1, 1, dst, mask, src, xstart, xend, pal);
|
|
|
|
src += 4;
|
|
dst = dst2 + _screenW;
|
|
mask = msk2 + _screenW;
|
|
}
|
|
}
|
|
|
|
#if SEGA_PERFORMANCE
|
|
template<bool hflip, bool oddStart, bool oddEnd> void SegaRenderer::renderLineFragmentM(uint8 *dst, uint8 *mask, const uint8 *src, int start, int end, uint8 pal) {
|
|
if (hflip)
|
|
src += ((end - 1 - start) >> 1);
|
|
|
|
for (int i = (end - start) >> 1; i; --i) {
|
|
uint8 col = hflip ? (oddEnd ? *src-- >> 4 : *src & 0x0F) : (oddStart ? *src++ & 0x0F : *src >> 4);
|
|
uint8 col2 = hflip ? (oddEnd ? *src & 0x0F : *src-- >> 4) : (oddStart ? *src >> 4 : *src++ & 0x0F);
|
|
if (col & *mask) {
|
|
*dst = pal | col;
|
|
*mask = 0;
|
|
}
|
|
dst++;
|
|
mask++;
|
|
if (col2 & *mask) {
|
|
*dst = pal | col2;
|
|
*mask = 0;
|
|
}
|
|
dst++;
|
|
mask++;
|
|
}
|
|
if (oddStart != oddEnd) {
|
|
uint8 col = hflip ? (oddEnd ? *src-- >> 4 : *src & 0x0F) : (oddStart ? *src++ & 0x0F : *src >> 4);
|
|
if (col & *mask) {
|
|
*dst = pal | col;
|
|
*mask = 0;
|
|
}
|
|
dst++;
|
|
mask++;
|
|
}
|
|
}
|
|
|
|
template<bool hflip, bool oddStart, bool oddEnd> void SegaRenderer::renderLineFragmentD(uint8 *dst, const uint8 *src, int start, int end, uint8 pal) {
|
|
if (hflip)
|
|
src += ((end - 1 - start) >> 1);
|
|
|
|
for (int i = (end - start) >> 1; i; --i) {
|
|
uint8 col = hflip ? (oddEnd ? *src-- >> 4 : *src & 0x0F) : (oddStart ? *src++ & 0x0F : *src >> 4);
|
|
uint8 col2 = hflip ? (oddEnd ? *src & 0x0F : *src-- >> 4) : (oddStart ? *src >> 4 : *src++ & 0x0F);
|
|
if (col)
|
|
*dst = pal | col;
|
|
dst++;
|
|
if (col2)
|
|
*dst = pal | col2;
|
|
dst++;
|
|
}
|
|
if (oddStart != oddEnd) {
|
|
uint8 col = hflip ? (oddEnd ? *src-- >> 4 : *src & 0x0F) : (oddStart ? *src++ & 0x0F : *src >> 4);
|
|
if (col)
|
|
*dst = pal | col;
|
|
dst++;
|
|
}
|
|
}
|
|
#else
|
|
template<bool hflip> void SegaRenderer::renderLineFragment(uint8 *dst, uint8 *mask, const uint8 *src, int start, int end, uint8 pal) {
|
|
if (hflip) {
|
|
src += ((end - 1 - start) >> 1);
|
|
if (end & 1) {
|
|
start++;
|
|
end++;
|
|
}
|
|
}
|
|
|
|
if (mask) {
|
|
for (int bX = start; bX < end; ++bX) {
|
|
uint8 col = hflip ? ((bX & 1) ? *src-- >> 4 : *src & 0x0F) : ((bX & 1) ? *src++ & 0x0F : *src >> 4);
|
|
if (col & *mask) {
|
|
*dst = pal | col;
|
|
*mask = 0;
|
|
}
|
|
dst++;
|
|
mask++;
|
|
}
|
|
} else {
|
|
for (int bX = start; bX < end; ++bX) {
|
|
uint8 col = hflip ? ((bX & 1) ? *src-- >> 4 : *src & 0x0F) : ((bX & 1) ? *src++ & 0x0F : *src >> 4);
|
|
if (col)
|
|
*dst = pal | col;
|
|
dst++;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#undef mRenderLineFragment
|
|
|
|
void SegaRenderer::initPrioRenderTask(uint8 *dst, uint8 *mask, const uint8 *src, int start, int end, uint8 pal, bool hflip) {
|
|
#if SEGA_USE_MEMPOOL
|
|
_prioChainEnd = new (_prioRenderMemPool) PrioTileRenderObj(_prioChainEnd, dst, mask, src, start, end, pal, hflip);
|
|
#else
|
|
_prioChainEnd = new PrioTileRenderObj(_prioChainEnd, dst, mask, src, start, end, pal, hflip);
|
|
#endif
|
|
if (!_prioChainStart)
|
|
_prioChainStart = _prioChainEnd;
|
|
}
|
|
|
|
void SegaRenderer::clearPrioChain() {
|
|
while (_prioChainEnd) {
|
|
_prioChainEnd->_next = 0;
|
|
PrioTileRenderObj *e = _prioChainEnd->_pred;
|
|
#if SEGA_USE_MEMPOOL
|
|
_prioRenderMemPool.deleteChunk(_prioChainEnd);
|
|
#else
|
|
delete _prioChainEnd;
|
|
#endif
|
|
_prioChainEnd = e;
|
|
}
|
|
_prioChainStart = 0;
|
|
}
|
|
|
|
SegaAnimator::SegaAnimator(SegaRenderer *renderer) : _renderer(renderer), _needUpdate(false) {
|
|
_sprites = new Sprite[80];
|
|
assert(_sprites);
|
|
memset(_sprites, 0, sizeof(Sprite) * 80);
|
|
_tempBuffer = new uint16[320];
|
|
assert(_tempBuffer);
|
|
memset(_tempBuffer, 0, sizeof(uint16) * 320);
|
|
int linkCnt = 1;
|
|
for (int i = 1; i < 317; i += 4)
|
|
_tempBuffer[i] = linkCnt++;
|
|
clearSprites();
|
|
_renderer->memsetVRAM(0xDC00, 0, 0x400);
|
|
}
|
|
|
|
SegaAnimator::~SegaAnimator() {
|
|
delete[] _sprites;
|
|
delete[] _tempBuffer;
|
|
}
|
|
|
|
void SegaAnimator::initSprite(int id, int16 x, int16 y, uint16 nameTbl, uint16 hw) {
|
|
assert(id < 80);
|
|
Sprite &s = _sprites[id];
|
|
s.x = x;
|
|
s.y = y;
|
|
s.nameTbl = nameTbl;
|
|
s.hw = hw;
|
|
_needUpdate = true;
|
|
}
|
|
|
|
void SegaAnimator::clearSprites() {
|
|
for (Sprite *s = _sprites; s != &_sprites[80]; ++s)
|
|
s->x = 0x4000;
|
|
_needUpdate = true;
|
|
}
|
|
|
|
void SegaAnimator::moveMorphSprite(int id, uint16 nameTbl, int16 addX, int16 addY) {
|
|
assert(id < 80);
|
|
Sprite &s = _sprites[id];
|
|
s.x += addX;
|
|
s.y += addY;
|
|
s.nameTbl = nameTbl;
|
|
_needUpdate = true;
|
|
}
|
|
|
|
void SegaAnimator::moveSprites(int id, uint16 num, int16 addX, int16 addY) {
|
|
assert(id < 80);
|
|
Sprite *s = &_sprites[id];
|
|
while (num--) {
|
|
s->x += addX;
|
|
s->y += addY;
|
|
s++;
|
|
}
|
|
_needUpdate = true;
|
|
}
|
|
|
|
void SegaAnimator::moveSprites2(int id, uint16 num, int16 addX, int16 addY) {
|
|
assert(id < 80);
|
|
Sprite *s = &_sprites[id];
|
|
uint16 sbx = s->x;
|
|
uint16 sby = s->y;
|
|
while (num--) {
|
|
s->x = s->x - sbx + addX;
|
|
s->y = s->y - sby + addY;
|
|
s++;
|
|
}
|
|
_needUpdate = true;
|
|
}
|
|
|
|
void SegaAnimator::update() {
|
|
if (!_needUpdate)
|
|
return;
|
|
|
|
uint16 *dst = _tempBuffer;
|
|
for (Sprite *s = _sprites; s != &_sprites[80]; ++s) {
|
|
if (s->x == 0x4000)
|
|
continue;
|
|
*dst++ = (uint16)(s->y + 128);
|
|
*dst = (*dst & 0xFF) | (s->hw << 8);
|
|
dst++;
|
|
*dst++ = s->nameTbl;
|
|
*dst++ = (uint16)(s->x + 128);
|
|
}
|
|
|
|
for (; dst < &_tempBuffer[320]; dst += 4)
|
|
*dst = 0;
|
|
|
|
_renderer->loadToVRAM(_tempBuffer, 640, 0xDC00);
|
|
_needUpdate = false;
|
|
}
|
|
|
|
SegaCDFont::SegaCDFont(const uint16 *convTable1, const uint16 *convTable2, const uint8 *widthTable1, const uint8 *widthTable2, const uint8 *widthTable3) : Font(),
|
|
_style(0), _forceTwoByte(false), _fixedWidth(false), _convTable1(convTable1), _convTable2(convTable2), _widthTable1(widthTable1), _widthTable2(widthTable2),
|
|
_widthTable3(widthTable3), _buffer(0), _data(0), _colorMap(0), _width(12), _height(12) {
|
|
}
|
|
|
|
SegaCDFont::~SegaCDFont() {
|
|
delete[] _buffer;
|
|
}
|
|
|
|
bool SegaCDFont::load(Common::SeekableReadStream &file) {
|
|
uint32 size = file.size();
|
|
if (!size)
|
|
return false;
|
|
|
|
delete[] _buffer;
|
|
uint8 *newData = new uint8[size];
|
|
file.read(newData, size);
|
|
_buffer = newData;
|
|
|
|
return true;
|
|
}
|
|
|
|
int SegaCDFont::getCharWidth(uint16 c) const {
|
|
uint8 charWidth, charHeight, charPitch;
|
|
getGlyphData(c, charWidth, charHeight, charPitch);
|
|
return charWidth;
|
|
}
|
|
|
|
int SegaCDFont::getCharHeight(uint16 c) const {
|
|
uint8 charWidth, charHeight, charPitch;
|
|
getGlyphData(c, charWidth, charHeight, charPitch);
|
|
return charHeight;
|
|
}
|
|
|
|
void SegaCDFont::setStyles(int styles) {
|
|
assert(_buffer);
|
|
_forceTwoByte = (styles & kStyleForceTwoByte);
|
|
_data = (styles & kStyleFat) ? _buffer + 131072 : _buffer;
|
|
_fixedWidth = (styles & kStyleFixedWidth);
|
|
_style = (styles & kStyleNarrow1) ? 1 : (styles & kStyleNarrow2 ? 2 : 0);
|
|
}
|
|
|
|
void SegaCDFont::drawChar(uint16 c, byte *dst, int pitch, int xOffs, int yOffs) const {
|
|
uint8 charWidth, charHeight, charPitch;
|
|
|
|
const uint8 *pos = getGlyphData(c, charWidth, charHeight, charPitch);
|
|
uint8 p = (xOffs & 1) ? 0x0F : 0xF0;
|
|
uint8 color1 = _colorMap[1];
|
|
|
|
color1 &= p;
|
|
p = ~p;
|
|
|
|
for (int y = 0; y < charHeight; ++y) {
|
|
c = *pos++ << 8;
|
|
if (charPitch != 8) {
|
|
c |= *pos;
|
|
if (y & 1) {
|
|
c <<= 4;
|
|
pos++;
|
|
}
|
|
|
|
}
|
|
uint8 *dst2 = dst;
|
|
for (int x = xOffs; x < charPitch + xOffs; ++x) {
|
|
if (c & 0x8000)
|
|
*dst = (*dst & p) | color1;
|
|
c <<= 1;
|
|
p = ~p;
|
|
color1 = (color1 << 4) | (color1 >> 4);
|
|
if (x & 1)
|
|
dst++;
|
|
if ((x & 7) == 7)
|
|
dst += 28;
|
|
}
|
|
dst = dst2 + 4;
|
|
if ((++yOffs & 7) == 0)
|
|
dst = dst + (pitch << 5) - 32;
|
|
}
|
|
}
|
|
|
|
const uint8 *SegaCDFont::getGlyphData(uint16 c, uint8 &charWidth, uint8 &charHeight, uint8 &pitch) const {
|
|
const uint8 *res = 0;
|
|
uint16 lo = 0;
|
|
uint16 hi = 0;
|
|
|
|
if (c == 0 || c == 13) {
|
|
charWidth = charHeight = pitch = 0;
|
|
return 0;
|
|
}
|
|
|
|
if (c < 256) {
|
|
if (_forceTwoByte) {
|
|
assert(c >= 32 && c < 224);
|
|
c = _convTable2[c - 32];
|
|
hi = c >> 8;
|
|
lo = c & 0xFF;
|
|
} else {
|
|
if (c < 128) {
|
|
if (c >= 96)
|
|
c += 96;
|
|
else
|
|
c -= 32;
|
|
if (c & 0xF000)
|
|
c = 0;
|
|
} else {
|
|
if (c >= 224)
|
|
c -= 64;
|
|
else if (c >= 160)
|
|
c -= 96;
|
|
}
|
|
charWidth = charHeight = pitch = 8;
|
|
return &_data[c << 3];
|
|
}
|
|
} else {
|
|
lo = c >> 8;
|
|
hi = c & 0xFF;
|
|
}
|
|
|
|
if (lo > 0x9E) {
|
|
if (hi > 0x9F)
|
|
hi -= 176;
|
|
else
|
|
hi -= 112;
|
|
hi <<= 1;
|
|
lo -= 126;
|
|
} else {
|
|
if (hi > 0x9F)
|
|
hi -= 177;
|
|
else
|
|
hi -= 113;
|
|
hi = (hi << 1) + 1;
|
|
lo -= 31;
|
|
if (lo >= 97)
|
|
lo -= 1;
|
|
}
|
|
|
|
c = (hi << 8) | lo;
|
|
|
|
if (c >= 0x5000)
|
|
c = 0x2121;
|
|
|
|
c -= _convTable1[(c >> 8) - 32];
|
|
|
|
int vrnt = 0;
|
|
if (c >= 376 || _style == 0)
|
|
vrnt = 0;
|
|
else if (_style == 1 || c < 188 || c >= 282)
|
|
vrnt = 1;
|
|
|
|
if (vrnt == 0) {
|
|
charWidth = (!_fixedWidth && (c < 188)) ? _widthTable1[c] : 12;
|
|
charHeight = pitch = 12;
|
|
res = &_data[0x19A0 + 18 * c];
|
|
} else if (_style == 2) {
|
|
charWidth = (!_fixedWidth && (c < 188)) ? _widthTable3[c] : 12;
|
|
charHeight = pitch = 12;
|
|
res = &_data[0x3410 + 18 * c];
|
|
} else {
|
|
charWidth = (!_fixedWidth && (c < 188)) ? _widthTable2[c] : 12;
|
|
charHeight = 12;
|
|
pitch = 8;
|
|
res = &_data[0x800 + 12 * c];
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
ScrollManager::ScrollManager(SegaRenderer *renderer) : _renderer(renderer) {
|
|
_vScrollTimers = new ScrollTimer[2];
|
|
assert(_vScrollTimers);
|
|
_hScrollTimers = new ScrollTimer[2];
|
|
assert(_hScrollTimers);
|
|
}
|
|
|
|
ScrollManager::~ScrollManager() {
|
|
delete[] _vScrollTimers;
|
|
delete[] _hScrollTimers;
|
|
}
|
|
|
|
void ScrollManager::setVScrollTimers(uint16 destA, int incrA, int delayA, uint16 destB, int incrB, int delayB) {
|
|
_vScrollTimers[0]._offsDest = destA;
|
|
_vScrollTimers[0]._incr = incrA;
|
|
_vScrollTimers[0]._timer = _vScrollTimers[0]._delay = delayA;
|
|
_vScrollTimers[1]._offsDest = destB;
|
|
_vScrollTimers[1]._incr = incrB;
|
|
_vScrollTimers[1]._timer = _vScrollTimers[1]._delay = delayB;
|
|
}
|
|
|
|
void ScrollManager::setHScrollTimers(uint16 destA, int incrA, int delayA, uint16 destB, int incrB, int delayB) {
|
|
_hScrollTimers[0]._offsDest = destA;
|
|
_hScrollTimers[0]._incr = incrA;
|
|
_hScrollTimers[0]._timer = _hScrollTimers[0]._delay = delayA;
|
|
_hScrollTimers[1]._offsDest = destB;
|
|
_hScrollTimers[1]._incr = incrB;
|
|
_hScrollTimers[1]._timer = _hScrollTimers[1]._delay = delayB;
|
|
}
|
|
|
|
void ScrollManager::updateScrollTimers() {
|
|
for (int i = 0; i < 4; ++i) {
|
|
ScrollTimer &t = i < 2 ? _vScrollTimers[i] : _hScrollTimers[i - 2];
|
|
if (t._delay == 0 && t._offsCur != t._offsDest)
|
|
t._offsCur = t._offsDest;
|
|
if (t._offsCur == t._offsDest)
|
|
continue;
|
|
if (--t._timer)
|
|
continue;
|
|
|
|
t._offsCur += t._incr;
|
|
t._timer = t._delay;
|
|
}
|
|
|
|
_renderer->writeUint16VSRAM(0, _vScrollTimers[0]._offsCur);
|
|
_renderer->writeUint16VSRAM(2, _vScrollTimers[1]._offsCur);
|
|
_renderer->writeUint16VRAM(0xD800, _hScrollTimers[0]._offsCur);
|
|
_renderer->writeUint16VRAM(0xD802, _hScrollTimers[1]._offsCur);
|
|
}
|
|
|
|
} // End of namespace Kyra
|
|
|
|
#endif // ENABLE_EOB
|