/* 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 . * */ #include "graphics/surface.h" #include "graphics/managed_surface.h" #include "graphics/cursorman.h" #include "mtropolis/render.h" #include "mtropolis/runtime.h" namespace MTropolis { template struct OrderedDitherGenerator { static void generateOrderedDither(TNumber (&pattern)[TResolution][TResolution]); }; template struct OrderedDitherGenerator { static void generateOrderedDither(TNumber (&pattern)[1][1]); }; struct RenderItem { VisualElement *element; size_t sceneStackDepth; }; template void OrderedDitherGenerator::generateOrderedDither(TNumber (&pattern)[TResolution][TResolution]) { const int kHalfResolution = TResolution / 2; TNumber halfRes[kHalfResolution][kHalfResolution]; OrderedDitherGenerator::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 void OrderedDitherGenerator::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 &Window::getSurface() const { return _surface; } const Graphics::PixelFormat& Window::getPixelFormat() const { return _surface->format; } const Common::SharedPtr &Window::getCursorGraphic() const { return _cursor; } void Window::setCursorGraphic(const Common::SharedPtr& 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(r) >> (8 - fmt.rBits())) << fmt.rShift; uint32 gPlaced = (static_cast(g) >> (8 - fmt.gBits())) << fmt.gShift; uint32 bPlaced = (static_cast(b) >> (8 - fmt.bBits())) << fmt.bShift; uint32 aPlaced = (static_cast(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 &normalBucket, Common::Array &directBucket) { Common::Point elementOrigin = parentOrigin; if (structural->isElement()) { Element *element = static_cast(structural); if (element->isVisual()) { VisualElement *visualElement = static_cast(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 >::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 scenes; runtime->getScenesInRenderOrder(scenes); Common::Array normalBucket; Common::Array directBucket; size_t sceneStackDepth = 0; for (Common::Array::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::const_iterator it = normalBucket.begin(), itEnd = normalBucket.end(); it != itEnd; ++it) { if (it->element->needsRender()) { sceneChanged = true; break; } } } if (!sceneChanged) { for (Common::Array::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::const_iterator it = normalBucket.begin(), itEnd = normalBucket.end(); it != itEnd; ++it) renderNormalElement(*it, mainWindow); for (Common::Array::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::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 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(surface.getBasePtr(0, y)); const TPixel *oldRow = static_cast(oldFrame.getBasePtr(0, y)); const TPixel *newRow = static_cast(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 static void runDissolveTransition(Graphics::ManagedSurface &surface, const Graphics::ManagedSurface &oldFrame, const Graphics::ManagedSurface &newFrame, uint8 breakpoint) { switch (surface.format.bytesPerPixel) { case 1: runDissolveTransitionWithType(surface, oldFrame, newFrame, breakpoint); break; case 2: runDissolveTransitionWithType(surface, oldFrame, newFrame, breakpoint); break; case 4: runDissolveTransitionWithType(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(surface, oldFrame, newFrame, (currentTime - startTime) * 255 / duration); break; case SceneTransitionTypes::kRandomDissolve: runDissolveTransition(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(currentTime - startTime) * static_cast(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(currentTime - startTime) * static_cast(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(currentTime - startTime) * static_cast(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(currentTime - startTime) * static_cast(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(currentTime - startTime) * static_cast(h) / duration; safeCopyRectToSurface(surface, newFrame, 0, h - directionalOffset, Common::Rect(0, h - directionalOffset, w, h)); break; case SceneTransitionDirections::kDown: directionalOffset = static_cast(currentTime - startTime) * static_cast(h) / duration; safeCopyRectToSurface(surface, newFrame, 0, 0, Common::Rect(0, 0, w, directionalOffset)); break; case SceneTransitionDirections::kLeft: directionalOffset = static_cast(currentTime - startTime) * static_cast(w) / duration; safeCopyRectToSurface(surface, newFrame, w - directionalOffset, 0, Common::Rect(w - directionalOffset, 0, w, h)); break; case SceneTransitionDirections::kRight: directionalOffset = static_cast(currentTime - startTime) * static_cast(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::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(srcSurface.getBasePtr(0, y)); uint16 *destRow = static_cast(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(srcSurface.getBasePtr(0, y)); uint32 *destRow = static_cast(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