Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/branches/masterserver2/src/libraries/core/GraphicsManager.cc @ 8474

Last change on this file since 8474 was 7874, checked in by landauf, 14 years ago

WindowEventListener now declares if the window's focus is active or not.
CEGUI stops highlighting menu items if the window's focus is lost, i.e. after pressing alt+tab

  • Property svn:eol-style set to native
File size: 17.5 KB
Line 
1/*
2 *   ORXONOX - the hottest 3D action shooter ever to exist
3 *                    > www.orxonox.net <
4 *
5 *
6 *   License notice:
7 *
8 *   This program is free software; you can redistribute it and/or
9 *   modify it under the terms of the GNU General Public License
10 *   as published by the Free Software Foundation; either version 2
11 *   of the License, or (at your option) any later version.
12 *
13 *   This program is distributed in the hope that it will be useful,
14 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
15 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 *   GNU General Public License for more details.
17 *
18 *   You should have received a copy of the GNU General Public License
19 *   along with this program; if not, write to the Free Software
20 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 *
22 *   Author:
23 *      Reto Grieder
24 *      Benjamin Knecht <beni_at_orxonox.net>, (C) 2007
25 *   Co-authors:
26 *      Felix Schulthess
27 *
28 */
29
30#include "GraphicsManager.h"
31
32#include <fstream>
33#include <sstream>
34#include <boost/filesystem.hpp>
35#include <boost/shared_array.hpp>
36
37#include <OgreArchiveFactory.h>
38#include <OgreArchiveManager.h>
39#include <OgreFrameListener.h>
40#include <OgreRoot.h>
41#include <OgreLogManager.h>
42#include <OgreRenderWindow.h>
43#include <OgreRenderSystem.h>
44#include <OgreResourceGroupManager.h>
45#include <OgreTextureManager.h>
46#include <OgreViewport.h>
47#include <OgreWindowEventUtilities.h>
48
49#include "SpecialConfig.h"
50#include "util/Clock.h"
51#include "util/Exception.h"
52#include "util/StringUtils.h"
53#include "util/SubString.h"
54#include "ConfigValueIncludes.h"
55#include "CoreIncludes.h"
56#include "Core.h"
57#include "Game.h"
58#include "GameMode.h"
59#include "Loader.h"
60#include "MemoryArchive.h"
61#include "PathConfig.h"
62#include "WindowEventListener.h"
63#include "XMLFile.h"
64#include "command/ConsoleCommand.h"
65
66namespace orxonox
67{
68    static const std::string __CC_printScreen_name = "printScreen";
69    DeclareConsoleCommand(__CC_printScreen_name, &prototype::void__void);
70
71    class OgreWindowEventListener : public Ogre::WindowEventListener
72    {
73    public:
74        void windowResized     (Ogre::RenderWindow* rw)
75            { orxonox::WindowEventListener::resizeWindow(rw->getWidth(), rw->getHeight()); }
76        void windowFocusChange (Ogre::RenderWindow* rw)
77            { orxonox::WindowEventListener::changeWindowFocus(rw->isActive()); }
78        void windowClosed      (Ogre::RenderWindow* rw)
79            { orxonox::Game::getInstance().stop(); }
80        void windowMoved       (Ogre::RenderWindow* rw)
81            { orxonox::WindowEventListener::moveWindow(); }
82    };
83
84    GraphicsManager* GraphicsManager::singletonPtr_s = 0;
85
86    /**
87    @brief
88        Non-initialising constructor.
89    */
90    GraphicsManager::GraphicsManager(bool bLoadRenderer)
91        : ogreWindowEventListener_(new OgreWindowEventListener())
92#if OGRE_VERSION < 0x010600
93        , memoryArchiveFactory_(new MemoryArchiveFactory())
94#endif
95        , renderWindow_(0)
96        , viewport_(0)
97    {
98        RegisterObject(GraphicsManager);
99
100        this->setConfigValues();
101
102        // Ogre setup procedure (creating Ogre::Root)
103        this->loadOgreRoot();
104
105        // At first, add the root paths of the data directories as resource locations
106        Ogre::ResourceGroupManager::getSingleton().addResourceLocation(PathConfig::getDataPathString(), "FileSystem");
107        // Load resources
108        resources_.reset(new XMLFile("DefaultResources.oxr"));
109        resources_->setLuaSupport(false);
110        Loader::open(resources_.get());
111
112        // Only for development runs
113        if (PathConfig::isDevelopmentRun())
114        {
115            Ogre::ResourceGroupManager::getSingleton().addResourceLocation(PathConfig::getExternalDataPathString(), "FileSystem");
116            extResources_.reset(new XMLFile("resources.oxr"));
117            extResources_->setLuaSupport(false);
118            Loader::open(extResources_.get());
119        }
120
121        if (bLoadRenderer)
122        {
123            // Reads the ogre config and creates the render window
124            this->upgradeToGraphics();
125        }
126    }
127
128    /**
129    @brief
130        Destruction is done by the member scoped_ptrs.
131    */
132    GraphicsManager::~GraphicsManager()
133    {
134        Loader::unload(debugOverlay_.get());
135
136        Ogre::WindowEventUtilities::removeWindowEventListener(renderWindow_, ogreWindowEventListener_.get());
137        ModifyConsoleCommand(__CC_printScreen_name).resetFunction();
138
139        // Undeclare the resources
140        Loader::unload(resources_.get());
141        if (PathConfig::isDevelopmentRun())
142            Loader::unload(extResources_.get());
143    }
144
145    void GraphicsManager::setConfigValues()
146    {
147        SetConfigValue(ogreConfigFile_,  "ogre.cfg")
148            .description("Location of the Ogre config file");
149        SetConfigValue(ogrePluginsDirectory_, specialConfig::ogrePluginsDirectory)
150            .description("Folder where the Ogre plugins are located.");
151        SetConfigValue(ogrePlugins_, specialConfig::ogrePlugins)
152            .description("Comma separated list of all plugins to load.");
153        SetConfigValue(ogreLogFile_,     "ogre.log")
154            .description("Logfile for messages from Ogre. Use \"\" to suppress log file creation.");
155        SetConfigValue(ogreLogLevelTrivial_ , 5)
156            .description("Corresponding orxonox debug level for ogre Trivial");
157        SetConfigValue(ogreLogLevelNormal_  , 4)
158            .description("Corresponding orxonox debug level for ogre Normal");
159        SetConfigValue(ogreLogLevelCritical_, 2)
160            .description("Corresponding orxonox debug level for ogre Critical");
161    }
162
163    /**
164    @brief
165        Loads the renderer and creates the render window if not yet done so.
166    @remarks
167        This operation is irreversible without recreating the GraphicsManager!
168        So if it throws you HAVE to recreate the GraphicsManager!!!
169        It therefore offers almost no exception safety.
170    */
171    void GraphicsManager::upgradeToGraphics()
172    {
173        if (renderWindow_ != NULL)
174            return;
175
176        // load all the required plugins for Ogre
177        this->loadOgrePlugins();
178
179        this->loadRenderer();
180
181#if OGRE_VERSION < 0x010600
182        // WORKAROUND: There is an incompatibility for particle scripts when trying
183        // to support both Ogre 1.4 and 1.6. The hacky solution is to create
184        // scripts for the 1.6 version and then remove the inserted "particle_system"
185        // keyword. But we need to supply these new scripts as well, which is why
186        // there is an extra Ogre::Archive dealing with it in the memory.
187        using namespace Ogre;
188        ArchiveManager::getSingleton().addArchiveFactory(memoryArchiveFactory_.get());
189        const StringVector& groups = ResourceGroupManager::getSingleton().getResourceGroups();
190        // Travers all groups
191        for (StringVector::const_iterator itGroup = groups.begin(); itGroup != groups.end(); ++itGroup)
192        {
193            FileInfoListPtr files = ResourceGroupManager::getSingleton().findResourceFileInfo(*itGroup, "*.particle");
194            for (FileInfoList::const_iterator itFile = files->begin(); itFile != files->end(); ++itFile)
195            {
196                // open file
197                Ogre::DataStreamPtr input = ResourceGroupManager::getSingleton().openResource(itFile->filename, *itGroup, false);
198                std::stringstream output;
199                // Parse file and replace "particle_system" with nothing
200                while (!input->eof())
201                {
202                    std::string line = input->getLine();
203                    size_t pos = line.find("particle_system");
204                    if (pos != std::string::npos)
205                    {
206                        // 15 is the length of "particle_system"
207                        line.replace(pos, 15, "");
208                    }
209                    output << line << std::endl;
210                }
211                // Add file to the memory archive
212                shared_array<char> data(new char[output.str().size()]);
213                // Debug optimisations
214                const std::string& outputStr = output.str();
215                char* rawData = data.get();
216                for (unsigned i = 0; i < outputStr.size(); ++i)
217                    rawData[i] = outputStr[i];
218                MemoryArchive::addFile("particle_scripts_ogre_1.4_" + *itGroup, itFile->filename, data, output.str().size());
219            }
220            if (!files->empty())
221            {
222                // Declare the files, but using a new group
223                ResourceGroupManager::getSingleton().addResourceLocation("particle_scripts_ogre_1.4_" + *itGroup,
224                    "Memory", "particle_scripts_ogre_1.4_" + *itGroup);
225            }
226        }
227#endif
228
229        // Initialise all resources (do this AFTER the renderer has been loaded!)
230        // Note: You can only do this once! Ogre will check whether a resource group has
231        // already been initialised. If you need to load resources later, you will have to
232        // choose another resource group.
233        Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();
234    }
235
236    /**
237    @brief
238        Creates the Ogre Root object and sets up the ogre log.
239    */
240    void GraphicsManager::loadOgreRoot()
241    {
242        COUT(3) << "Setting up Ogre..." << std::endl;
243
244        if (ogreConfigFile_.empty())
245        {
246            COUT(2) << "Warning: Ogre config file set to \"\". Defaulting to config.cfg" << std::endl;
247            ModifyConfigValue(ogreConfigFile_, tset, "config.cfg");
248        }
249        if (ogreLogFile_.empty())
250        {
251            COUT(2) << "Warning: Ogre log file set to \"\". Defaulting to ogre.log" << std::endl;
252            ModifyConfigValue(ogreLogFile_, tset, "ogre.log");
253        }
254
255        boost::filesystem::path ogreConfigFilepath(PathConfig::getConfigPath() / this->ogreConfigFile_);
256        boost::filesystem::path ogreLogFilepath(PathConfig::getLogPath() / this->ogreLogFile_);
257
258        // create a new logManager
259        // Ogre::Root will detect that we've already created a Log
260        ogreLogger_.reset(new Ogre::LogManager());
261        COUT(4) << "Ogre LogManager created" << std::endl;
262
263        // create our own log that we can listen to
264        Ogre::Log *myLog;
265        myLog = ogreLogger_->createLog(ogreLogFilepath.string(), true, false, false);
266        COUT(4) << "Ogre Log created" << std::endl;
267
268        myLog->setLogDetail(Ogre::LL_BOREME);
269        myLog->addListener(this);
270
271        COUT(4) << "Creating Ogre Root..." << std::endl;
272
273        // check for config file existence because Ogre displays (caught) exceptions if not
274        if (!boost::filesystem::exists(ogreConfigFilepath))
275        {
276            // create a zero sized file
277            std::ofstream creator;
278            creator.open(ogreConfigFilepath.string().c_str());
279            creator.close();
280        }
281
282        // Leave plugins file empty. We're going to do that part manually later
283        ogreRoot_.reset(new Ogre::Root("", ogreConfigFilepath.string(), ogreLogFilepath.string()));
284
285        COUT(3) << "Ogre set up done." << std::endl;
286    }
287
288    void GraphicsManager::loadOgrePlugins()
289    {
290        // just to make sure the next statement doesn't segfault
291        if (ogrePluginsDirectory_.empty())
292            ogrePluginsDirectory_ = '.';
293
294        boost::filesystem::path folder(ogrePluginsDirectory_);
295        // Do some SubString magic to get the comma separated list of plugins
296        SubString plugins(ogrePlugins_, ",", " ", false, '\\', false, '"', false, '{', '}', false, '\0');
297        // Use backslash paths on Windows! file_string() already does that though.
298        for (unsigned int i = 0; i < plugins.size(); ++i)
299            ogreRoot_->loadPlugin((folder / plugins[i]).file_string());
300    }
301
302    void GraphicsManager::loadRenderer()
303    {
304        CCOUT(4) << "Configuring Renderer" << std::endl;
305
306        if (!ogreRoot_->restoreConfig() || Core::getInstance().getOgreConfigTimestamp() > Core::getInstance().getLastLevelTimestamp())
307        {
308            if (!ogreRoot_->showConfigDialog())
309                ThrowException(InitialisationFailed, "OGRE graphics configuration dialogue canceled.");
310            else
311                Core::getInstance().updateOgreConfigTimestamp();
312        }
313
314        CCOUT(4) << "Creating render window" << std::endl;
315
316        this->renderWindow_ = ogreRoot_->initialise(true, "Orxonox");
317        // Propagate the size of the new winodw
318        this->ogreWindowEventListener_->windowResized(renderWindow_);
319
320        Ogre::WindowEventUtilities::addWindowEventListener(this->renderWindow_, ogreWindowEventListener_.get());
321
322        // create a full screen default viewport
323        // Note: This may throw when adding a viewport with an existing z-order!
324        //       But in our case we only have one viewport for now anyway, therefore
325        //       no ScopeGuards or anything to handle exceptions.
326        this->viewport_ = this->renderWindow_->addViewport(0, 0);
327
328        Ogre::TextureManager::getSingleton().setDefaultNumMipmaps(Ogre::MIP_UNLIMITED);
329
330        // add console commands
331        ModifyConsoleCommand(__CC_printScreen_name).setFunction(&GraphicsManager::printScreen, this);
332    }
333
334    void GraphicsManager::loadDebugOverlay()
335    {
336        // Load debug overlay to show info about fps and tick time
337        COUT(4) << "Loading Debug Overlay..." << std::endl;
338        debugOverlay_.reset(new XMLFile("debug.oxo"));
339        Loader::open(debugOverlay_.get());
340    }
341
342    /**
343    @note
344        A note about the Ogre::FrameListener: Even though we don't use them,
345        they still get called. However, the delta times are not correct (except
346        for timeSinceLastFrame, which is the most important). A little research
347        as shown that there is probably only one FrameListener that doesn't even
348        need the time. So we shouldn't run into problems.
349    */
350    void GraphicsManager::postUpdate(const Clock& time)
351    {
352        Ogre::FrameEvent evt;
353        evt.timeSinceLastFrame = time.getDeltaTime();
354        evt.timeSinceLastEvent = time.getDeltaTime(); // note: same time, but shouldn't matter anyway
355
356        // don't forget to call _fireFrameStarted to OGRE to make sure
357        // everything goes smoothly
358        ogreRoot_->_fireFrameStarted(evt);
359
360        // Pump messages in all registered RenderWindows
361        // This calls the WindowEventListener objects.
362        Ogre::WindowEventUtilities::messagePump();
363        // make sure the window stays active even when not focused
364        // (probably only necessary on windows)
365        this->renderWindow_->setActive(true);
366
367        // Time before rendering
368        uint64_t timeBeforeTick = time.getRealMicroseconds();
369
370        // Render frame
371        ogreRoot_->_updateAllRenderTargets();
372
373        uint64_t timeAfterTick = time.getRealMicroseconds();
374        // Subtract the time used for rendering from the tick time counter
375        Game::getInstance().subtractTickTime((int32_t)(timeAfterTick - timeBeforeTick));
376
377        // again, just to be sure OGRE works fine
378        ogreRoot_->_fireFrameEnded(evt); // note: uses the same time as _fireFrameStarted
379    }
380
381    void GraphicsManager::setCamera(Ogre::Camera* camera)
382    {
383        this->viewport_->setCamera(camera);
384    }
385
386    /**
387    @brief
388        Method called by the LogListener interface from Ogre.
389        We use it to capture Ogre log messages and handle it ourselves.
390    @param message
391        The message to be logged
392    @param lml
393        The message level the log is using
394    @param maskDebug
395        If we are printing to the console or not
396    @param logName
397        The name of this log (so you can have several listeners
398        for different logs, and identify them)
399    */
400    void GraphicsManager::messageLogged(const std::string& message,
401        Ogre::LogMessageLevel lml, bool maskDebug, const std::string& logName)
402    {
403        int orxonoxLevel;
404        std::string introduction;
405        // Do not show caught OGRE exceptions in front
406        if (message.find("EXCEPTION") != std::string::npos)
407        {
408            orxonoxLevel = OutputLevel::Debug;
409            introduction = "Ogre, caught exception: ";
410        }
411        else
412        {
413            switch (lml)
414            {
415            case Ogre::LML_TRIVIAL:
416                orxonoxLevel = this->ogreLogLevelTrivial_;
417                break;
418            case Ogre::LML_NORMAL:
419                orxonoxLevel = this->ogreLogLevelNormal_;
420                break;
421            case Ogre::LML_CRITICAL:
422                orxonoxLevel = this->ogreLogLevelCritical_;
423                break;
424            default:
425                orxonoxLevel = 0;
426            }
427            introduction = "Ogre: ";
428        }
429        OutputHandler::getOutStream(orxonoxLevel)
430            << introduction << message << std::endl;
431    }
432
433    size_t GraphicsManager::getRenderWindowHandle()
434    {
435        size_t windowHnd = 0;
436        renderWindow_->getCustomAttribute("WINDOW", &windowHnd);
437        return windowHnd;
438    }
439
440    bool GraphicsManager::isFullScreen() const
441    {
442        Ogre::ConfigOptionMap& options = ogreRoot_->getRenderSystem()->getConfigOptions();
443        if (options.find("Full Screen") != options.end())
444        {
445            if (options["Full Screen"].currentValue == "Yes")
446                return true;
447            else
448                return false;
449        }
450        else
451        {
452            COUT(0) << "Could not find 'Full Screen' render system option. Fix This!!!" << std::endl;
453            return false;
454        }
455    }
456
457    void GraphicsManager::printScreen()
458    {
459        assert(this->renderWindow_);
460        this->renderWindow_->writeContentsToTimestampedFile(PathConfig::getLogPathString() + "screenShot_", ".png");
461    }
462}
Note: See TracBrowser for help on using the repository browser.