scummvm/engines/mtropolis/render.cpp

610 lines
20 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 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 <http://www.gnu.org/licenses/>.
*
*/
#include "graphics/surface.h"
#include "graphics/managed_surface.h"
#include "graphics/cursorman.h"
#include "mtropolis/render.h"
#include "mtropolis/runtime.h"
namespace MTropolis {
template<class TNumber, int TResolution>
struct OrderedDitherGenerator {
static void generateOrderedDither(TNumber (&pattern)[TResolution][TResolution]);
};
template<class TNumber>
struct OrderedDitherGenerator<TNumber, 1> {
static void generateOrderedDither(TNumber (&pattern)[1][1]);
};
struct RenderItem {
VisualElement *element;
size_t sceneStackDepth;
};
template<class TNumber, int TResolution>
void OrderedDitherGenerator<TNumber, TResolution>::generateOrderedDither(TNumber (&pattern)[TResolution][TResolution]) {
const int kHalfResolution = TResolution / 2;
TNumber halfRes[kHalfResolution][kHalfResolution];
OrderedDitherGenerator<TNumber, kHalfResolution>::generateOrderedDither(halfRes);
const int kHalfResNumSteps = kHalfResolution * kHalfResolution;
for (int y = 0; y < kHalfResolution; y++) {
for (int x = 0; x < kHalfResolution; x++) {
pattern[y * 2][x * 2] = halfRes[y][x];
pattern[y * 2 + 1][x * 2 + 1] = halfRes[y][x] + kHalfResNumSteps * 1;
pattern[y * 2][x * 2 + 1] = halfRes[y][x] + kHalfResNumSteps * 2;
pattern[y * 2 + 1][x * 2] = halfRes[y][x] + kHalfResNumSteps * 3;
}
}
}
template<class TNumber>
void OrderedDitherGenerator<TNumber, 1>::generateOrderedDither(TNumber (&pattern)[1][1]) {
pattern[0][0] = 0;
}
inline int quantize8To5Byte(int value, byte orderedDither16x16) {
return (value * 249 + (orderedDither16x16 << 3)) >> 11;
}
inline int quantize8To5UShort(int value, uint16 orderedDither16x16) {
return (value * 249 + orderedDither16x16) >> 11;
}
inline int expand5To8(int value) {
return (value * 33) >> 2;
}
TextStyleFlags::TextStyleFlags() : bold(false), italic(false), underline(false), outline(false), shadow(false), condensed(false), expanded(false) {
}
bool TextStyleFlags::load(uint8 dataStyleFlags) {
bold = ((dataStyleFlags & 0x01) != 0);
italic = ((dataStyleFlags & 0x02) != 0);
underline = ((dataStyleFlags & 0x03) != 0);
outline = ((dataStyleFlags & 0x04) != 0);
shadow = ((dataStyleFlags & 0x10) != 0);
condensed = ((dataStyleFlags & 0x20) != 0);
expanded = ((dataStyleFlags & 0x40) != 0);
return true;
}
MacFontFormatting::MacFontFormatting() : fontID(0), fontFlags(0), size(12) {
}
MacFontFormatting::MacFontFormatting(uint16 mff_fontID, uint8 mff_fontFlags, uint16 mff_size) : fontID(mff_fontID), fontFlags(mff_fontFlags), size(mff_size) {
}
MacFormattingSpan::MacFormattingSpan() : spanStart(0) {
}
WindowParameters::WindowParameters(Runtime *wp_runtime, int32 wp_x, int32 wp_y, int16 wp_width, int16 wp_height, const Graphics::PixelFormat &wp_format)
: runtime(wp_runtime), x(wp_x), y(wp_y), width(wp_width), height(wp_height), format(wp_format) {
}
Window::Window(const WindowParameters &windowParams)
: _runtime(windowParams.runtime), _x(windowParams.x), _y(windowParams.y), _strata(0), _isMouseTransparent(false), _isMouseVisible(true) {
_surface.reset(new Graphics::ManagedSurface(windowParams.width, windowParams.height, windowParams.format));
}
Window::~Window() {
}
int32 Window::getX() const {
return _x;
}
int32 Window::getY() const {
return _y;
}
int32 Window::getWidth() const {
return _surface->w;
}
int32 Window::getHeight() const {
return _surface->h;
}
void Window::setPosition(int32 x, int32 y) {
_x = x;
_y = y;
}
void Window::resizeWindow(int32 width, int32 height) {
Graphics::PixelFormat pixFmt = _surface->format;
_surface.reset();
_surface.reset(new Graphics::ManagedSurface(width, height, pixFmt));
}
const Common::SharedPtr<Graphics::ManagedSurface> &Window::getSurface() const {
return _surface;
}
const Graphics::PixelFormat& Window::getPixelFormat() const {
return _surface->format;
}
const Common::SharedPtr<CursorGraphic> &Window::getCursorGraphic() const {
return _cursor;
}
void Window::setCursorGraphic(const Common::SharedPtr<CursorGraphic>& cursor) {
_cursor = cursor;
}
bool Window::getMouseVisible() const {
return _isMouseVisible;
}
void Window::setMouseVisible(bool visible) {
_isMouseVisible = visible;
}
void Window::setStrata(int strata) {
_strata = strata;
}
int Window::getStrata() const {
return _strata;
}
// Mouse transparency = ignores mouse events
void Window::setMouseTransparent(bool isTransparent) {
_isMouseTransparent = isTransparent;
}
bool Window::isMouseTransparent() const {
return _isMouseTransparent;
}
void Window::close() {
Runtime *runtime = _runtime;
_runtime = nullptr;
if (runtime)
runtime->removeWindow(this);
}
void Window::detachFromRuntime() {
_runtime = nullptr;
}
void Window::onMouseDown(int32 x, int32 y, int mouseButton) {
}
void Window::onMouseMove(int32 x, int32 y) {
}
void Window::onMouseUp(int32 x, int32 y, int mouseButton) {
}
void Window::onKeyboardEvent(const Common::EventType evtType, bool repeat, const Common::KeyState &keyEvt) {
}
void Window::onAction(Actions::Action action) {
}
namespace Render {
uint32 resolveRGB(uint8 r, uint8 g, uint8 b, const Graphics::PixelFormat &fmt) {
uint32 rPlaced = (static_cast<uint32>(r) >> (8 - fmt.rBits())) << fmt.rShift;
uint32 gPlaced = (static_cast<uint32>(g) >> (8 - fmt.gBits())) << fmt.gShift;
uint32 bPlaced = (static_cast<uint32>(b) >> (8 - fmt.bBits())) << fmt.bShift;
uint32 aPlaced = (static_cast<uint32>(255) >> (8 - fmt.aBits())) << fmt.aShift;
return rPlaced | gPlaced | bPlaced | aPlaced;
}
static void recursiveCollectDrawElementsAndUpdateOrigins(const Common::Point &parentOrigin, Structural *structural, size_t sceneStackDepth, Common::Array<RenderItem> &normalBucket, Common::Array<RenderItem> &directBucket) {
Common::Point elementOrigin = parentOrigin;
if (structural->isElement()) {
Element *element = static_cast<Element *>(structural);
if (element->isVisual()) {
VisualElement *visualElement = static_cast<VisualElement *>(element);
const Common::Rect &elementRect = visualElement->getRelativeRect();
elementOrigin.x += elementRect.left;
elementOrigin.y += elementRect.top;
visualElement->setCachedAbsoluteOrigin(Common::Point(elementOrigin.x, elementOrigin.y));
RenderItem item;
item.element = visualElement;
item.sceneStackDepth = sceneStackDepth;
if (visualElement->isVisible()) {
if (visualElement->isDirectToScreen())
directBucket.push_back(item);
else
normalBucket.push_back(item);
}
}
}
for (Common::Array<Common::SharedPtr<Structural> >::const_iterator it = structural->getChildren().begin(), itEnd = structural->getChildren().end(); it != itEnd; ++it) {
recursiveCollectDrawElementsAndUpdateOrigins(elementOrigin, it->get(), sceneStackDepth, normalBucket, directBucket);
}
}
static bool renderItemLess(const RenderItem &a, const RenderItem &b) {
const uint16 aLayer = a.element->getLayer();
const uint16 bLayer = b.element->getLayer();
if (aLayer != bLayer)
return aLayer < bLayer;
return a.sceneStackDepth < b.sceneStackDepth;
}
static void renderNormalElement(const RenderItem &item, Window *mainWindow) {
item.element->render(mainWindow);
item.element->finalizeRender();
}
static void renderDirectElement(const RenderItem &item, Window *mainWindow) {
renderNormalElement(item, mainWindow); // Meh
}
void renderProject(Runtime *runtime, Window *mainWindow, bool *outSkipped) {
bool sceneChanged = runtime->isSceneGraphDirty();
Common::Array<Structural *> scenes;
runtime->getScenesInRenderOrder(scenes);
Common::Array<RenderItem> normalBucket;
Common::Array<RenderItem> directBucket;
size_t sceneStackDepth = 0;
for (Common::Array<Structural *>::const_iterator it = scenes.begin(), itEnd = scenes.end(); it != itEnd; ++it) {
recursiveCollectDrawElementsAndUpdateOrigins(Common::Point(0, 0), *it, sceneStackDepth, normalBucket, directBucket);
sceneStackDepth++;
}
Common::sort(normalBucket.begin(), normalBucket.end(), renderItemLess);
Common::sort(directBucket.begin(), directBucket.end(), renderItemLess);
if (!sceneChanged) {
for (Common::Array<RenderItem>::const_iterator it = normalBucket.begin(), itEnd = normalBucket.end(); it != itEnd; ++it) {
if (it->element->needsRender()) {
sceneChanged = true;
break;
}
}
}
if (!sceneChanged) {
for (Common::Array<RenderItem>::const_iterator it = directBucket.begin(), itEnd = directBucket.end(); it != itEnd; ++it) {
if (it->element->needsRender()) {
sceneChanged = true;
break;
}
}
}
if (sceneChanged) {
if (outSkipped)
*outSkipped = false;
for (Common::Array<RenderItem>::const_iterator it = normalBucket.begin(), itEnd = normalBucket.end(); it != itEnd; ++it)
renderNormalElement(*it, mainWindow);
for (Common::Array<RenderItem>::const_iterator it = directBucket.begin(), itEnd = directBucket.end(); it != itEnd; ++it)
renderDirectElement(*it, mainWindow);
for (const IPostEffect *postEffect : runtime->getPostEffects())
postEffect->renderPostEffect(*mainWindow->getSurface());
} else {
if (outSkipped)
*outSkipped = true;
}
runtime->clearSceneGraphDirty();
}
class DissolveOrderedDitherPatternGenerator {
public:
DissolveOrderedDitherPatternGenerator();
uint8 getNext();
void nextLine();
private:
uint8 _ditherPattern[16][16];
uint16 _x;
uint16 _y;
};
DissolveOrderedDitherPatternGenerator::DissolveOrderedDitherPatternGenerator() : _x(0), _y(0) {
OrderedDitherGenerator<uint8, 16>::generateOrderedDither(_ditherPattern);
}
inline uint8 DissolveOrderedDitherPatternGenerator::getNext() {
uint8 result = _ditherPattern[_y][_x];
uint16 newX = _x + 1;
if (newX == 16)
newX = 0;
_x = newX;
return result;
}
inline void DissolveOrderedDitherPatternGenerator::nextLine() {
_x = 0;
uint16 newY = _y + 1;
if (newY == 16)
newY = 0;
_y = newY;
}
class DissolveOrderedDitherRandomGenerator {
public:
DissolveOrderedDitherRandomGenerator();
uint8 getNext();
void nextLine();
private:
uint32 _lcgState;
};
DissolveOrderedDitherRandomGenerator::DissolveOrderedDitherRandomGenerator() : _lcgState(13) {
}
inline uint8 DissolveOrderedDitherRandomGenerator::getNext() {
_lcgState = ((_lcgState * 1103515245u) + 12345u) & 0x7fffffffu;
return (_lcgState >> 16) & 0xff;
}
inline void DissolveOrderedDitherRandomGenerator::nextLine() {
}
template<class TPixel, class TGenerator>
static void runDissolveTransitionWithType(Graphics::ManagedSurface &surface, const Graphics::ManagedSurface &oldFrame, const Graphics::ManagedSurface &newFrame, uint8 breakpoint) {
TGenerator generator;
assert(surface.format.bytesPerPixel == oldFrame.format.bytesPerPixel);
assert(surface.format.bytesPerPixel == newFrame.format.bytesPerPixel);
uint16 w = surface.w;
uint16 h = surface.h;
for (uint y = 0; y < h; y++) {
TPixel *destRow = static_cast<TPixel *>(surface.getBasePtr(0, y));
const TPixel *oldRow = static_cast<const TPixel *>(oldFrame.getBasePtr(0, y));
const TPixel *newRow = static_cast<const TPixel *>(newFrame.getBasePtr(0, y));
for (uint x = 0; x < w; x++) {
if (generator.getNext() <= breakpoint)
destRow[x] = newRow[x];
else
destRow[x] = oldRow[x];
}
generator.nextLine();
}
}
template<class TGenerator>
static void runDissolveTransition(Graphics::ManagedSurface &surface, const Graphics::ManagedSurface &oldFrame, const Graphics::ManagedSurface &newFrame, uint8 breakpoint) {
switch (surface.format.bytesPerPixel) {
case 1:
runDissolveTransitionWithType<uint8, TGenerator>(surface, oldFrame, newFrame, breakpoint);
break;
case 2:
runDissolveTransitionWithType<uint16, TGenerator>(surface, oldFrame, newFrame, breakpoint);
break;
case 4:
runDissolveTransitionWithType<uint32, TGenerator>(surface, oldFrame, newFrame, breakpoint);
break;
default:
assert(false);
break;
}
}
static void safeCopyRectToSurface(Graphics::ManagedSurface &surface, const Graphics::ManagedSurface &srcSurface, int destX, int destY, const Common::Rect subRect) {
if (subRect.width() == 0 || subRect.height() == 0)
return;
surface.copyRectToSurface(srcSurface, destX, destY, subRect);
}
void renderSceneTransition(Runtime *runtime, Window *mainWindow, const SceneTransitionEffect &effect, uint32 startTime, uint32 endTime, uint32 currentTime, const Graphics::ManagedSurface &oldFrame, const Graphics::ManagedSurface &newFrame) {
Graphics::ManagedSurface &surface = *mainWindow->getSurface();
assert(endTime > startTime);
uint32 duration = endTime - startTime;
uint16 w = surface.w;
uint16 h = surface.h;
if (effect._transitionType == SceneTransitionTypes::kSlide || effect._transitionType == SceneTransitionTypes::kWipe)
safeCopyRectToSurface(surface, oldFrame, 0, 0, Common::Rect(0, 0, w, h));
switch (effect._transitionType) {
case SceneTransitionTypes::kPatternDissolve:
runDissolveTransition<DissolveOrderedDitherPatternGenerator>(surface, oldFrame, newFrame, (currentTime - startTime) * 255 / duration);
break;
case SceneTransitionTypes::kRandomDissolve:
runDissolveTransition<DissolveOrderedDitherRandomGenerator>(surface, oldFrame, newFrame, (currentTime - startTime) * 255 / duration);
break;
case SceneTransitionTypes::kFade:
// Fade transitions fade to black and then fade from black in the new scene
warning("Fade transitions are not implemented");
break;
case SceneTransitionTypes::kSlide:
case SceneTransitionTypes::kPush: {
uint32 directionalOffset = 0;
switch (effect._transitionDirection) {
case SceneTransitionDirections::kUp:
directionalOffset = static_cast<uint32>(currentTime - startTime) * static_cast<uint32>(h) / duration;
if (effect._transitionType == SceneTransitionTypes::kPush)
safeCopyRectToSurface(surface, oldFrame, 0, 0, Common::Rect(0, directionalOffset, w, h));
safeCopyRectToSurface(surface, newFrame, 0, h - directionalOffset, Common::Rect(0, 0, w, directionalOffset));
break;
case SceneTransitionDirections::kDown:
directionalOffset = static_cast<uint32>(currentTime - startTime) * static_cast<uint32>(h) / duration;
if (effect._transitionType == SceneTransitionTypes::kPush)
safeCopyRectToSurface(surface, oldFrame, 0, directionalOffset, Common::Rect(0, 0, w, h - directionalOffset));
safeCopyRectToSurface(surface, newFrame, 0, 0, Common::Rect(0, h - directionalOffset, w, h));
break;
case SceneTransitionDirections::kLeft:
directionalOffset = static_cast<uint32>(currentTime - startTime) * static_cast<uint32>(w) / duration;
if (effect._transitionType == SceneTransitionTypes::kPush)
safeCopyRectToSurface(surface, oldFrame, 0, 0, Common::Rect(directionalOffset, 0, w, h));
safeCopyRectToSurface(surface, newFrame, w - directionalOffset, 0, Common::Rect(0, 0, directionalOffset, h));
break;
case SceneTransitionDirections::kRight:
directionalOffset = static_cast<uint32>(currentTime - startTime) * static_cast<uint32>(w) / duration;
if (effect._transitionType == SceneTransitionTypes::kPush)
safeCopyRectToSurface(surface, oldFrame, directionalOffset, 0, Common::Rect(0, 0, w - directionalOffset, h));
safeCopyRectToSurface(surface, newFrame, 0, 0, Common::Rect(w - directionalOffset, 0, w, h));
break;
default:
assert(false);
break;
}
} break;
case SceneTransitionTypes::kZoom:
warning("Zoom transitions are not implemented");
break;
case SceneTransitionTypes::kWipe:{
uint32 directionalOffset = 0;
switch (effect._transitionDirection) {
case SceneTransitionDirections::kUp:
directionalOffset = static_cast<uint32>(currentTime - startTime) * static_cast<uint32>(h) / duration;
safeCopyRectToSurface(surface, newFrame, 0, h - directionalOffset, Common::Rect(0, h - directionalOffset, w, h));
break;
case SceneTransitionDirections::kDown:
directionalOffset = static_cast<uint32>(currentTime - startTime) * static_cast<uint32>(h) / duration;
safeCopyRectToSurface(surface, newFrame, 0, 0, Common::Rect(0, 0, w, directionalOffset));
break;
case SceneTransitionDirections::kLeft:
directionalOffset = static_cast<uint32>(currentTime - startTime) * static_cast<uint32>(w) / duration;
safeCopyRectToSurface(surface, newFrame, w - directionalOffset, 0, Common::Rect(w - directionalOffset, 0, w, h));
break;
case SceneTransitionDirections::kRight:
directionalOffset = static_cast<uint32>(currentTime - startTime) * static_cast<uint32>(w) / duration;
safeCopyRectToSurface(surface, newFrame, 0, 0, Common::Rect(0, 0, directionalOffset, h));
break;
default:
assert(false);
break;
}
} break;
default:
assert(false);
break;
}
}
void convert32To16(Graphics::ManagedSurface &destSurface, const Graphics::ManagedSurface &srcSurface) {
const Graphics::PixelFormat srcFmt = srcSurface.format;
const Graphics::PixelFormat destFmt = destSurface.format;
assert(srcFmt.bytesPerPixel == 4);
assert(destFmt.bytesPerPixel == 2);
assert(destSurface.w == srcSurface.w);
assert(srcSurface.h == destSurface.h);
uint16 ditherPattern[16][16];
OrderedDitherGenerator<uint16, 16>::generateOrderedDither(ditherPattern);
for (int x = 0; x < 16; x++) {
for (int y = 0; y < 16; y++)
ditherPattern[y][x] <<= 3;
}
size_t w = srcSurface.w;
size_t h = srcSurface.h;
for (size_t y = 0; y < h; y++) {
const uint16 *ditherRow = ditherPattern[y % 16];
const uint32 *srcRow = static_cast<const uint32*>(srcSurface.getBasePtr(0, y));
uint16 *destRow = static_cast<uint16 *>(destSurface.getBasePtr(0, y));
for (size_t x = 0; x < w; x++) {
uint16 ditherOffset = ditherRow[x % 16];
uint32 packed32 = srcRow[x];
uint8 r = (packed32 >> srcFmt.rShift) & 0xff;
uint8 g = (packed32 >> srcFmt.gShift) & 0xff;
uint8 b = (packed32 >> srcFmt.bShift) & 0xff;
r = quantize8To5UShort(r, ditherOffset);
g = quantize8To5UShort(g, ditherOffset);
b = quantize8To5UShort(b, ditherOffset);
destRow[x] = (r << destFmt.rShift) | (g << destFmt.gShift) | (b << destFmt.bShift);
}
}
}
void convert16To32(Graphics::ManagedSurface &destSurface, const Graphics::ManagedSurface &srcSurface) {
const Graphics::PixelFormat srcFmt = srcSurface.format;
const Graphics::PixelFormat destFmt = destSurface.format;
assert(srcFmt.bytesPerPixel == 2);
assert(destFmt.bytesPerPixel == 4);
assert(destSurface.w == srcSurface.w);
assert(srcSurface.h == destSurface.h);
size_t w = srcSurface.w;
size_t h = srcSurface.h;
for (size_t y = 0; y < h; y++) {
const uint16 *srcRow = static_cast<const uint16 *>(srcSurface.getBasePtr(0, y));
uint32 *destRow = static_cast<uint32 *>(destSurface.getBasePtr(0, y));
for (size_t x = 0; x < w; x++) {
uint32 packed16 = srcRow[x];
uint8 r = (packed16 >> srcFmt.rShift) & 0x1f;
uint8 g = (packed16 >> srcFmt.gShift) & 0x1f;
uint8 b = (packed16 >> srcFmt.bShift) & 0x1f;
r = expand5To8(r);
g = expand5To8(g);
b = expand5To8(b);
destRow[x] = (r << destFmt.rShift) | (g << destFmt.gShift) | (b << destFmt.bShift) | (0xffu << destFmt.aShift);
}
}
}
} // End of namespace Render
} // End of namespace MTropolis