/* 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$ * */ /* * This code is based on Broken Sword 2.5 engine * * Copyright (c) Malte Thiesen, Daniel Queteschiner and Michael Elsdoerfer * * Licensed under GNU GPL v2 * */ #define BS_LOG_PREFIX "GRAPHICENGINE" #include "common/system.h" #include "sword25/gfx/bitmapresource.h" #include "sword25/gfx/animationresource.h" #include "sword25/gfx/fontresource.h" #include "sword25/gfx/panel.h" #include "sword25/gfx/renderobjectmanager.h" #include "sword25/gfx/screenshot.h" #include "sword25/gfx/image/renderedimage.h" #include "sword25/gfx/image/swimage.h" #include "sword25/gfx/image/vectorimage.h" #include "sword25/package/packagemanager.h" #include "sword25/kernel/inputpersistenceblock.h" #include "sword25/kernel/outputpersistenceblock.h" #include "sword25/gfx/graphicengine.h" namespace Lua { extern "C" { #include "sword25/util/lua/lua.h" #include "sword25/util/lua/lauxlib.h" } } enum { BIT_DEPTH = 32, BACKBUFFER_COUNT = 1 }; namespace Sword25 { using namespace Lua; static const uint FRAMETIME_SAMPLE_COUNT = 5; // Anzahl der Framezeiten über die, die Framezeit gemittelt wird GraphicEngine::GraphicEngine(Kernel *pKernel) : _width(0), _height(0), _bitDepth(0), m_Windowed(0), m_LastTimeStamp((uint) -1), // max. BS_INT64 um beim ersten Aufruf von _UpdateLastFrameDuration() einen Reset zu erzwingen m_LastFrameDuration(0), m_TimerActive(true), m_FrameTimeSampleSlot(0), m_RepaintedPixels(0), _thumbnail(NULL), ResourceService(pKernel) { m_FrameTimeSamples.resize(FRAMETIME_SAMPLE_COUNT); if (!RegisterScriptBindings()) BS_LOG_ERRORLN("Script bindings could not be registered."); else BS_LOGLN("Script bindings registered."); } GraphicEngine::~GraphicEngine() { _backSurface.free(); _frameBuffer.free(); delete _thumbnail; } Service *GraphicEngine_CreateObject(Kernel *pKernel) { return new GraphicEngine(pKernel); } bool GraphicEngine::Init(int width, int height, int bitDepth, int backbufferCount, bool isWindowed) { // Warnung ausgeben, wenn eine nicht unterstützte Bittiefe gewählt wurde. if (bitDepth != BIT_DEPTH) { BS_LOG_WARNINGLN("Can't use a bit depth of %d (not supported). Falling back to %d.", bitDepth, BIT_DEPTH); _bitDepth = BIT_DEPTH; } // Warnung ausgeben, wenn nicht genau ein Backbuffer gewählt wurde. if (backbufferCount != BACKBUFFER_COUNT) { BS_LOG_WARNINGLN("Can't use %d backbuffers (not supported). Falling back to %d.", backbufferCount, BACKBUFFER_COUNT); backbufferCount = BACKBUFFER_COUNT; } // Parameter in lokale Variablen kopieren _width = width; _height = height; _bitDepth = bitDepth; m_Windowed = isWindowed; _screenRect.left = 0; _screenRect.top = 0; _screenRect.right = _width; _screenRect.bottom = _height; _backSurface.create(width, height, 4); _frameBuffer.create(width, height, 4); // Standardmäßig ist Vsync an. SetVsync(true); // Layer-Manager initialisieren. _renderObjectManagerPtr.reset(new RenderObjectManager(width, height, backbufferCount + 1)); // Hauptpanel erstellen m_MainPanelPtr = _renderObjectManagerPtr->getTreeRoot()->addPanel(width, height, BS_ARGB(0, 0, 0, 0)); if (!m_MainPanelPtr.isValid()) return false; m_MainPanelPtr->setVisible(true); return true; } // ----------------------------------------------------------------------------- bool GraphicEngine::StartFrame(bool UpdateAll) { // Berechnen, wie viel Zeit seit dem letzten Frame vergangen ist. // Dieser Wert kann über GetLastFrameDuration() von Modulen abgefragt werden, die zeitabhängig arbeiten. UpdateLastFrameDuration(); // Den Layer-Manager auf den nächsten Frame vorbereiten _renderObjectManagerPtr->startFrame(); return true; } // ----------------------------------------------------------------------------- bool GraphicEngine::EndFrame() { // Scene zeichnen _renderObjectManagerPtr->render(); // FIXME: The frame buffer surface is only used as the base for creating thumbnails when saving the // game, since the _backSurface is blanked. Currently I'm doing a slightly hacky check and only // copying the back surface if line 50 (the first line after the interface area) is non-blank if (READ_LE_UINT32((byte *)_backSurface.pixels + (_backSurface.pitch * 50)) & 0xffffff) { // Make a copy of the current frame into the frame buffer Common::copy((byte *)_backSurface.pixels, (byte *)_backSurface.pixels + (_backSurface.pitch * _backSurface.h), (byte *)_frameBuffer.pixels); } g_system->updateScreen(); // Debug-Lines zeichnen if (!m_DebugLines.empty()) { #if 0 glEnable(GL_LINE_SMOOTH); glBegin(GL_LINES); Common::Array::const_iterator iter = m_DebugLines.begin(); for (; iter != m_DebugLines.end(); ++iter) { const uint &Color = (*iter).Color; const BS_Vertex &Start = (*iter).Start; const BS_Vertex &End = (*iter).End; glColor4ub((Color >> 16) & 0xff, (Color >> 8) & 0xff, Color & 0xff, Color >> 24); glVertex2d(Start.X, Start.Y); glVertex2d(End.X, End.Y); } glEnd(); glDisable(GL_LINE_SMOOTH); #endif warning("STUB: Drawing debug lines"); m_DebugLines.clear(); } // Framecounter aktualisieren m_FPSCounter.Update(); return true; } // ----------------------------------------------------------------------------- RenderObjectPtr GraphicEngine::GetMainPanel() { return m_MainPanelPtr; } // ----------------------------------------------------------------------------- void GraphicEngine::SetVsync(bool Vsync) { warning("STUB: SetVsync(%d)", Vsync); } // ----------------------------------------------------------------------------- bool GraphicEngine::GetVsync() const { warning("STUB: GetVsync()"); return true; } // ----------------------------------------------------------------------------- bool GraphicEngine::fill(const Common::Rect *fillRectPtr, uint color) { Common::Rect rect(_width - 1, _height - 1); if (fillRectPtr) { rect = *fillRectPtr; } if (fillRectPtr->width() > 0 && fillRectPtr->height() > 0) { _backSurface.fillRect(rect, color); g_system->copyRectToScreen((byte *)_backSurface.getBasePtr(fillRectPtr->left, fillRectPtr->top), _backSurface.pitch, fillRectPtr->left, fillRectPtr->top, fillRectPtr->width(), fillRectPtr->height()); } return true; } // ----------------------------------------------------------------------------- Graphics::Surface *GraphicEngine::GetScreenshot() { return &_frameBuffer; } // ----------------------------------------------------------------------------- // RESOURCE MANAGING // ----------------------------------------------------------------------------- Resource *GraphicEngine::loadResource(const Common::String &filename) { BS_ASSERT(canLoadResource(filename)); // Load image for "software buffer" (FIXME: Whatever that means?) if (filename.hasSuffix("_s.png")) { bool Result = false; SWImage *pImage = new SWImage(filename, Result); if (!Result) { delete pImage; return 0; } BitmapResource *pResource = new BitmapResource(filename, pImage); if (!pResource->isValid()) { delete pResource; return 0; } return pResource; } // Load sprite image if (filename.hasSuffix(".png") || filename.hasSuffix(".b25s")) { bool Result = false; RenderedImage *pImage = new RenderedImage(filename, Result); if (!Result) { delete pImage; return 0; } BitmapResource *pResource = new BitmapResource(filename, pImage); if (!pResource->isValid()) { delete pResource; return 0; } return pResource; } // Load vector graphics if (filename.hasSuffix(".swf")) { debug(2, "VectorImage: %s", filename.c_str()); // Pointer auf Package-Manager holen PackageManager *pPackage = Kernel::GetInstance()->GetPackage(); BS_ASSERT(pPackage); // Datei laden byte *pFileData; uint FileSize; if (!(pFileData = static_cast(pPackage->getFile(filename, &FileSize)))) { BS_LOG_ERRORLN("File \"%s\" could not be loaded.", filename.c_str()); return 0; } bool Result = false; VectorImage *pImage = new VectorImage(pFileData, FileSize, Result, filename); if (!Result) { delete pImage; delete [] pFileData; return 0; } BitmapResource *pResource = new BitmapResource(filename, pImage); if (!pResource->isValid()) { delete pResource; delete[] pFileData; return 0; } delete[] pFileData; return pResource; } // Load animation if (filename.hasSuffix("_ani.xml")) { AnimationResource *pResource = new AnimationResource(filename); if (pResource->isValid()) return pResource; else { delete pResource; return 0; } } // Load font if (filename.hasSuffix("_fnt.xml")) { FontResource *pResource = new FontResource(Kernel::GetInstance(), filename); if (pResource->IsValid()) return pResource; else { delete pResource; return 0; } } BS_LOG_ERRORLN("Service cannot load \"%s\".", filename.c_str()); return 0; } // ----------------------------------------------------------------------------- bool GraphicEngine::canLoadResource(const Common::String &filename) { return filename.hasSuffix(".png") || filename.hasSuffix("_ani.xml") || filename.hasSuffix("_fnt.xml") || filename.hasSuffix(".swf") || filename.hasSuffix(".b25s"); } // ----------------------------------------------------------------------------- // DEBUGGING // ----------------------------------------------------------------------------- void GraphicEngine::DrawDebugLine(const Vertex &Start, const Vertex &End, uint Color) { m_DebugLines.push_back(DebugLine(Start, End, Color)); } void GraphicEngine::UpdateLastFrameDuration() { // Record current time const uint currentTime = Kernel::GetInstance()->GetMilliTicks(); // Compute the elapsed time since the last frame and prevent too big ( > 250 msecs) time jumps. // These can occur when loading save states, during debugging or due to hardware inaccuracies. m_FrameTimeSamples[m_FrameTimeSampleSlot] = static_cast(currentTime - m_LastTimeStamp); if (m_FrameTimeSamples[m_FrameTimeSampleSlot] > 250000) m_FrameTimeSamples[m_FrameTimeSampleSlot] = 250000; m_FrameTimeSampleSlot = (m_FrameTimeSampleSlot + 1) % FRAMETIME_SAMPLE_COUNT; // Compute the average frame duration over multiple frames to eliminate outliers. Common::Array::const_iterator it = m_FrameTimeSamples.begin(); uint sum = *it; for (it++; it != m_FrameTimeSamples.end(); it++) sum += *it; m_LastFrameDuration = sum * 1000 / FRAMETIME_SAMPLE_COUNT; // Update m_LastTimeStamp with the current frame's timestamp m_LastTimeStamp = currentTime; } namespace { bool DoSaveScreenshot(GraphicEngine &graphicEngine, const Common::String &filename) { Graphics::Surface *data = graphicEngine.GetScreenshot(); if (!data) { BS_LOG_ERRORLN("Call to GetScreenshot() failed. Cannot save screenshot."); return false; } Common::FSNode f(filename); Common::WriteStream *stream = f.createWriteStream(); if (!stream) { BS_LOG_ERRORLN("Call to GetScreenshot() failed. Cannot save screenshot."); return false; } bool result = Screenshot::SaveToFile(data, stream); delete stream; return result; } } bool GraphicEngine::SaveScreenshot(const Common::String &filename) { return DoSaveScreenshot(*this, filename); } bool GraphicEngine::SaveThumbnailScreenshot(const Common::String &filename) { // Note: In ScumMVM, rather than saivng the thumbnail to a file, we store it in memory // until needed when creating savegame files delete _thumbnail; _thumbnail = Screenshot::createThumbnail(&_frameBuffer); return true; } void GraphicEngine::ARGBColorToLuaColor(lua_State *L, uint Color) { lua_Number Components[4] = { (Color >> 16) & 0xff, // Rot (Color >> 8) & 0xff, // Grün Color & 0xff, // Blau Color >> 24, // Alpha }; lua_newtable(L); for (uint i = 1; i <= 4; i++) { lua_pushnumber(L, i); lua_pushnumber(L, Components[i - 1]); lua_settable(L, -3); } } uint GraphicEngine::LuaColorToARGBColor(lua_State *L, int StackIndex) { #ifdef DEBUG int __startStackDepth = lua_gettop(L); #endif // Sicherstellen, dass wir wirklich eine Tabelle betrachten luaL_checktype(L, StackIndex, LUA_TTABLE); // Größe der Tabelle auslesen uint n = luaL_getn(L, StackIndex); // RGB oder RGBA Farben werden unterstützt und sonst keine if (n != 3 && n != 4) luaL_argcheck(L, 0, StackIndex, "at least 3 of the 4 color components have to be specified"); // Rote Farbkomponente auslesen lua_rawgeti(L, StackIndex, 1); uint Red = static_cast(lua_tonumber(L, -1)); if (!lua_isnumber(L, -1) || Red >= 256) luaL_argcheck(L, 0, StackIndex, "red color component must be an integer between 0 and 255"); lua_pop(L, 1); // Grüne Farbkomponente auslesen lua_rawgeti(L, StackIndex, 2); uint Green = static_cast(lua_tonumber(L, -1)); if (!lua_isnumber(L, -1) || Green >= 256) luaL_argcheck(L, 0, StackIndex, "green color component must be an integer between 0 and 255"); lua_pop(L, 1); // Blaue Farbkomponente auslesen lua_rawgeti(L, StackIndex, 3); uint Blue = static_cast(lua_tonumber(L, -1)); if (!lua_isnumber(L, -1) || Blue >= 256) luaL_argcheck(L, 0, StackIndex, "blue color component must be an integer between 0 and 255"); lua_pop(L, 1); // Alpha Farbkomponente auslesen uint Alpha = 0xff; if (n == 4) { lua_rawgeti(L, StackIndex, 4); Alpha = static_cast(lua_tonumber(L, -1)); if (!lua_isnumber(L, -1) || Alpha >= 256) luaL_argcheck(L, 0, StackIndex, "alpha color component must be an integer between 0 and 255"); lua_pop(L, 1); } #ifdef DEBUG BS_ASSERT(__startStackDepth == lua_gettop(L)); #endif return (Alpha << 24) | (Red << 16) | (Green << 8) | Blue; } bool GraphicEngine::persist(OutputPersistenceBlock &writer) { writer.write(m_TimerActive); bool result = _renderObjectManagerPtr->persist(writer); return result; } bool GraphicEngine::unpersist(InputPersistenceBlock &reader) { reader.read(m_TimerActive); _renderObjectManagerPtr->unpersist(reader); return reader.isGood(); } } // End of namespace Sword25