Index: ps/trunk/source/ps/GameSetup/Config.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/Config.cpp (revision 9188) +++ ps/trunk/source/ps/GameSetup/Config.cpp (revision 9189) @@ -1,185 +1,183 @@ /* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "ps/ConfigDB.h" #include "ps/CConsole.h" #include "ps/GameSetup/CmdLineArgs.h" #include "lib/timer.h" #include "lib/res/sound/snd_mgr.h" #include "Config.h" // (these variables are documented in the header.) CStrW g_CursorName = L"test"; bool g_NoGLS3TC = false; bool g_NoGLAutoMipmap = false; bool g_NoGLVBO = false; -bool g_NoGLFramebufferObject = false; bool g_Shadows = false; bool g_FancyWater = false; float g_LodBias = 0.0f; float g_Gamma = 1.0f; bool g_EntGraph = false; CStr g_RenderPath = "default"; int g_xres, g_yres; bool g_VSync = false; bool g_Quickstart = false; bool g_DisableAudio = false; // flag to switch on drawing terrain overlays bool g_ShowPathfindingOverlay = false; // flag to switch on triangulation pathfinding bool g_TriPathfind = false; // If non-empty, specified map will be automatically loaded CStr g_AutostartMap = ""; //---------------------------------------------------------------------------- // config //---------------------------------------------------------------------------- // Fill in the globals from the config files. static void LoadGlobals() { CFG_GET_USER_VAL("vsync", Bool, g_VSync); CFG_GET_USER_VAL("nos3tc", Bool, g_NoGLS3TC); CFG_GET_USER_VAL("noautomipmap", Bool, g_NoGLAutoMipmap); CFG_GET_USER_VAL("novbo", Bool, g_NoGLVBO); - CFG_GET_USER_VAL("noframebufferobject", Bool, g_NoGLFramebufferObject); CFG_GET_USER_VAL("shadows", Bool, g_Shadows); CFG_GET_USER_VAL("fancywater", Bool, g_FancyWater); CFG_GET_USER_VAL("renderpath", String, g_RenderPath); CFG_GET_USER_VAL("lodbias", Float, g_LodBias); float gain = -1.0f; CFG_GET_USER_VAL("sound.mastergain", Float, gain); if(gain >= 0.0f) WARN_ERR(snd_set_master_gain(gain)); } static void ProcessCommandLineArgs(const CmdLineArgs& args) { // TODO: all these options (and the ones processed elsewhere) should // be documented somewhere for users. if (args.Has("buildarchive")) { // note: VFS init is sure to have been completed by now // (since CONFIG_Init reads from file); therefore, // it is safe to call this from here directly. // vfs_opt_rebuild_main_archive("mods/official/official1.zip", "../logs/trace.txt"); } // Handle "-conf=key:value" (potentially multiple times) std::vector conf = args.GetMultiple("conf"); for (size_t i = 0; i < conf.size(); ++i) { CStr name_value = conf[i]; if (name_value.Find(':') != -1) { CStr name = name_value.BeforeFirst(":"); CStr value = name_value.AfterFirst(":"); g_ConfigDB.CreateValue(CFG_COMMAND, name)->m_String = value; } } if (args.Has("entgraph")) g_EntGraph = true; if (args.Has("g")) { g_Gamma = args.Get("g").ToFloat(); if (g_Gamma == 0.0f) g_Gamma = 1.0f; } // if (args.Has("listfiles")) // trace_enable(true); if (args.Has("profile")) g_ConfigDB.CreateValue(CFG_COMMAND, "profile")->m_String = args.Get("profile"); if (args.Has("quickstart")) { g_Quickstart = true; g_DisableAudio = true; // do this for backward-compatibility with user expectations } if (args.Has("nosound")) g_DisableAudio = true; if (args.Has("shadows")) g_ConfigDB.CreateValue(CFG_COMMAND, "shadows")->m_String = "true"; if (args.Has("xres")) g_ConfigDB.CreateValue(CFG_COMMAND, "xres")->m_String = args.Get("xres"); if (args.Has("yres")) g_ConfigDB.CreateValue(CFG_COMMAND, "yres")->m_String = args.Get("yres"); if (args.Has("vsync")) g_ConfigDB.CreateValue(CFG_COMMAND, "vsync")->m_String = "true"; } void CONFIG_Init(const CmdLineArgs& args) { TIMER(L"CONFIG_Init"); new CConfigDB; // Load the global, default config file g_ConfigDB.SetConfigFile(CFG_DEFAULT, L"config/default.cfg"); g_ConfigDB.Reload(CFG_DEFAULT); // 216ms // Try loading the local system config file (which doesn't exist by // default) - this is designed as a way of letting developers edit the // system config without accidentally committing their changes back to SVN. g_ConfigDB.SetConfigFile(CFG_SYSTEM, L"config/local.cfg"); g_ConfigDB.Reload(CFG_SYSTEM); g_ConfigDB.SetConfigFile(CFG_USER, L"config/user.cfg"); g_ConfigDB.Reload(CFG_USER); g_ConfigDB.SetConfigFile(CFG_MOD, L"config/mod.cfg"); // No point in reloading mod.cfg here - we haven't mounted mods yet ProcessCommandLineArgs(args); // Initialise console history file int max_history_lines = 200; CFG_GET_USER_VAL("console.history.size", Int, max_history_lines); g_Console->UseHistoryFile(L"config/console.txt", max_history_lines); // Collect information from system.cfg, the profile file, // and any command-line overrides to fill in the globals. LoadGlobals(); // 64ms } Index: ps/trunk/source/ps/GameSetup/Config.h =================================================================== --- ps/trunk/source/ps/GameSetup/Config.h (revision 9188) +++ ps/trunk/source/ps/GameSetup/Config.h (revision 9189) @@ -1,70 +1,67 @@ /* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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. * * 0 A.D. 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 0 A.D. If not, see . */ #ifndef INCLUDED_PS_GAMESETUP_CONFIG #define INCLUDED_PS_GAMESETUP_CONFIG #include "ps/CStr.h" //----------------------------------------------------------------------------- // prevent various OpenGL features from being used. this allows working // around issues like buggy drivers. // when loading S3TC-compressed texture files, do not pass them directly to // OpenGL; instead, decompress them via software to regular textures. // (necessary on JW's S3 laptop graphics card -- oh, the irony) extern bool g_NoGLS3TC; // do not ask OpenGL to create mipmaps; instead, generate them in software // and upload them all manually. (potentially helpful for PT's system, where // Mesa falsely reports full S3TC support but isn't able to generate mipmaps // for them) extern bool g_NoGLAutoMipmap; // don't use VBOs. (RC: that was necessary on laptop Radeon cards) extern bool g_NoGLVBO; -// disable FBO extension in case the driver is flaky -extern bool g_NoGLFramebufferObject; - //----------------------------------------------------------------------------- // flag to switch on shadows extern bool g_Shadows; // flag to switch on reflective/refractive water extern bool g_FancyWater; extern float g_LodBias; extern float g_Gamma; extern bool g_EntGraph; // name of configured render path (depending on OpenGL extensions, this may not be // the render path that is actually in use right now) extern CStr g_RenderPath; extern int g_xres, g_yres; extern bool g_VSync; extern bool g_Quickstart; extern bool g_DisableAudio; extern CStrW g_CursorName; class CmdLineArgs; extern void CONFIG_Init(const CmdLineArgs& args); #endif // INCLUDED_PS_GAMESETUP_CONFIG Index: ps/trunk/source/ps/GameSetup/GameSetup.cpp =================================================================== --- ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 9188) +++ ps/trunk/source/ps/GameSetup/GameSetup.cpp (revision 9189) @@ -1,1100 +1,1099 @@ /* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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. * * 0 A.D. 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 0 A.D. If not, see . */ #include "precompiled.h" #include "lib/app_hooks.h" #include "lib/input.h" #include "lib/lockfree.h" #include "lib/ogl.h" #include "lib/timer.h" #include "lib/external_libraries/sdl.h" #include "lib/file/common/file_stats.h" #include "lib/res/h_mgr.h" #include "lib/res/graphics/cursor.h" #include "lib/res/sound/snd_mgr.h" #include "lib/sysdep/cursor.h" #include "lib/sysdep/cpu.h" #include "lib/sysdep/gfx.h" #include "lib/tex/tex.h" #if OS_WIN #include "lib/sysdep/os/win/wversion.h" #endif #include "ps/CConsole.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Font.h" #include "ps/Game.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Joystick.h" #include "ps/Loader.h" #include "ps/Overlay.h" #include "ps/Profile.h" #include "ps/ProfileViewer.h" #include "ps/UserReport.h" #include "ps/Util.h" #include "ps/VideoMode.h" #include "ps/World.h" #include "graphics/CinemaTrack.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/MapReader.h" #include "graphics/MaterialManager.h" #include "graphics/TerrainTextureManager.h" #include "renderer/Renderer.h" #include "renderer/VertexBufferManager.h" #include "maths/MathUtil.h" #include "simulation2/Simulation2.h" #include "scripting/ScriptingHost.h" #include "scripting/ScriptGlue.h" #include "scriptinterface/ScriptInterface.h" #include "scriptinterface/ScriptStats.h" #include "maths/scripting/JSInterface_Vector3D.h" #include "ps/scripting/JSInterface_Console.h" #include "gui/GUI.h" #include "gui/GUIManager.h" #include "gui/scripting/JSInterface_IGUIObject.h" #include "gui/scripting/JSInterface_GUITypes.h" #include "gui/scripting/ScriptFunctions.h" #include "sound/JSI_Sound.h" #include "network/NetServer.h" #include "network/NetClient.h" #include "ps/Pyrogenesis.h" // psSetLogDir #include "ps/GameSetup/Atlas.h" #include "ps/GameSetup/GameSetup.h" #include "ps/GameSetup/Paths.h" #include "ps/GameSetup/Config.h" #include "ps/GameSetup/CmdLineArgs.h" #include "ps/GameSetup/HWDetect.h" #if !(OS_WIN || OS_MACOSX) // assume all other platforms use X11 for wxWidgets #define MUST_INIT_X11 1 #include #else #define MUST_INIT_X11 0 #endif #include ERROR_GROUP(System); ERROR_TYPE(System, SDLInitFailed); ERROR_TYPE(System, VmodeFailed); ERROR_TYPE(System, RequiredExtensionsMissing); bool g_DoRenderGui = true; bool g_DoRenderLogger = true; bool g_DoRenderCursor = true; static const int SANE_TEX_QUALITY_DEFAULT = 5; // keep in sync with code static void SetTextureQuality(int quality) { int q_flags; GLint filter; retry: // keep this in sync with SANE_TEX_QUALITY_DEFAULT switch(quality) { // worst quality case 0: q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP; filter = GL_NEAREST; break; // [perf] add bilinear filtering case 1: q_flags = OGL_TEX_HALF_RES|OGL_TEX_HALF_BPP; filter = GL_LINEAR; break; // [vmem] no longer reduce resolution case 2: q_flags = OGL_TEX_HALF_BPP; filter = GL_LINEAR; break; // [vmem] add mipmaps case 3: q_flags = OGL_TEX_HALF_BPP; filter = GL_NEAREST_MIPMAP_LINEAR; break; // [perf] better filtering case 4: q_flags = OGL_TEX_HALF_BPP; filter = GL_LINEAR_MIPMAP_LINEAR; break; // [vmem] no longer reduce bpp case SANE_TEX_QUALITY_DEFAULT: q_flags = OGL_TEX_FULL_QUALITY; filter = GL_LINEAR_MIPMAP_LINEAR; break; // [perf] add anisotropy case 6: // TODO: add anisotropic filtering q_flags = OGL_TEX_FULL_QUALITY; filter = GL_LINEAR_MIPMAP_LINEAR; break; // invalid default: debug_warn(L"SetTextureQuality: invalid quality"); quality = SANE_TEX_QUALITY_DEFAULT; // careful: recursion doesn't work and we don't want to duplicate // the "sane" default values. goto retry; } ogl_tex_set_defaults(q_flags, filter); } //---------------------------------------------------------------------------- // GUI integration //---------------------------------------------------------------------------- // display progress / description in loading screen void GUI_DisplayLoadProgress(int percent, const wchar_t* pending_task) { g_ScriptingHost.GetScriptInterface().SetGlobal("g_Progress", percent, true); g_ScriptingHost.GetScriptInterface().SetGlobal("g_LoadDescription", pending_task, true); g_GUI->SendEventToAll("progress"); } void Render() { ogl_WarnIfError(); CStr skystring = "255 0 255"; CFG_GET_USER_VAL("skycolor", String, skystring); CColor skycol; GUI::ParseString(skystring.FromUTF8(), skycol); g_Renderer.SetClearColor(skycol.AsSColor4ub()); // prepare before starting the renderer frame if (g_Game && g_Game->IsGameStarted()) g_Game->GetView()->BeginFrame(); // start new frame g_Renderer.BeginFrame(); ogl_WarnIfError(); if (g_Game && g_Game->IsGameStarted()) g_Game->GetView()->Render(); ogl_WarnIfError(); // set up overlay mode glPushAttrib(GL_ENABLE_BIT); glEnable(GL_TEXTURE_2D); glDisable(GL_CULL_FACE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); ogl_WarnIfError(); g_Renderer.RenderTextOverlays(); // Temp GUI message GeeTODO PROFILE_START("render gui"); if(g_DoRenderGui) g_GUI->Draw(); PROFILE_END("render gui"); ogl_WarnIfError(); // Text: // Use the GL_ALPHA texture as the alpha channel with a flat colouring glDisable(GL_ALPHA_TEST); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // Added -- glEnable(GL_TEXTURE_2D); // -- GL glLoadIdentity(); PROFILE_START("render console"); g_Console->Render(); PROFILE_END("render console"); ogl_WarnIfError(); PROFILE_START("render logger"); if(g_DoRenderLogger) g_Logger->Render(); PROFILE_END("render logger"); ogl_WarnIfError(); // Profile information PROFILE_START("render profiling"); g_ProfileViewer.RenderProfile(); PROFILE_END("render profiling"); ogl_WarnIfError(); // Draw the cursor (or set the Windows cursor, on Windows) if (g_DoRenderCursor) { PROFILE("render cursor"); CStrW cursorName = g_CursorName; if (cursorName.empty()) { cursor_draw(g_VFS, NULL, g_mouse_x, g_yres-g_mouse_y); } else { if (cursor_draw(g_VFS, cursorName.c_str(), g_mouse_x, g_yres-g_mouse_y) < 0) LOGWARNING(L"Failed to draw cursor '%ls'", cursorName.c_str()); } } // restore glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glPopAttrib(); g_Renderer.EndFrame(); ogl_WarnIfError(); } static void RegisterJavascriptInterfaces() { // maths JSI_Vector3D::init(); // graphics CGameView::ScriptingInit(); // renderer CRenderer::ScriptingInit(); // sound JSI_Sound::ScriptingInit(); // ps JSI_Console::init(); // GUI CGUI::ScriptingInit(); GuiScriptingInit(g_ScriptingHost.GetScriptInterface()); } static void InitScripting() { TIMER(L"InitScripting"); // Create the scripting host. This needs to be done before the GUI is created. // [7ms] new ScriptingHost; RegisterJavascriptInterfaces(); } #if 0 // disabled because the file cache doesn't work (http://trac.wildfiregames.com/ticket/611) static size_t OperatingSystemFootprint() { #if OS_WIN switch(wversion_Number()) { case WVERSION_2K: case WVERSION_XP: return 150; case WVERSION_XP64: return 200; default: // don't warn about newer Windows versions case WVERSION_VISTA: return 300; case WVERSION_7: return 250; } #else return 200; #endif } static size_t ChooseCacheSize() { // (all sizes in MiB) const size_t total = os_cpu_MemorySize(); const size_t available = os_cpu_MemoryAvailable(); debug_assert(total >= available); const size_t inUse = total-available; size_t os = OperatingSystemFootprint(); debug_assert(total >= os/2); size_t apps = (inUse > os)? (inUse - os) : 0; size_t game = 300; size_t cache = 200; // plenty of memory if(os + apps + game + cache < total) { cache = total - os - apps - game; } else // below min-spec { // assume kernel+other apps will be swapped out os = 9*os/10; apps = 2*apps/3; game = 3*game/4; if(os + apps + game + cache < total) cache = total - os - apps - game; else { cache = 50; debug_printf(L"Warning: memory size (%d MiB, %d used) is rather low\n", total, available); } } // total data is currently about 500 MiB, and not all of it // is used at once, so don't use more than that. // (this also ensures the byte count will fit in size_t and // avoids using up too much address space) cache = std::min(cache, (size_t)500); debug_printf(L"Cache: %d (total: %d; available: %d)\n", cache, total, available); return cache*MiB; } #else static size_t ChooseCacheSize() { return 32*MiB; } #endif ErrorReactionInternal psDisplayError(const wchar_t* UNUSED(text), size_t UNUSED(flags)) { // If we're fullscreen, then sometimes (at least on some particular drivers on Linux) // displaying the error dialog hangs the desktop since the dialog box is behind the // fullscreen window. So we just force the game to windowed mode before displaying the dialog. // (But only if we're in the main thread, and not if we're being reentrant.) if (ThreadUtil::IsMainThread()) { static bool reentering = false; if (!reentering) { reentering = true; g_VideoMode.SetFullscreen(false); reentering = false; } } // We don't actually implement the error display here, so return appropriately return ERI_NOT_IMPLEMENTED; } static void InitVfs(const CmdLineArgs& args) { TIMER(L"InitVfs"); const Paths paths(args); OsPath logs(paths.Logs()); CreateDirectories(logs, 0700); psSetLogDir(logs); // desired location for crashlog is now known. update AppHooks ASAP // (particularly before the following error-prone operations): AppHooks hooks = {0}; hooks.bundle_logs = psBundleLogs; hooks.get_log_dir = psLogDir; hooks.display_error = psDisplayError; app_hooks_update(&hooks); const size_t cacheSize = ChooseCacheSize(); g_VFS = CreateVfs(cacheSize); g_VFS->Mount(L"screenshots/", paths.Data()/"screenshots/"); const OsPath readonlyConfig = paths.RData()/"config/"; g_VFS->Mount(L"config/", readonlyConfig); if(readonlyConfig != paths.Config()) g_VFS->Mount(L"config/", paths.Config()); g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE); // (adding XMBs to archive speeds up subsequent reads) std::vector mods = args.GetMultiple("mod"); mods.push_back("public"); if(!args.Has("onlyPublicFiles")) mods.push_back("internal"); OsPath modArchivePath = paths.Cache()/"mods"; OsPath modLoosePath = paths.RData()/"mods"; for (size_t i = 0; i < mods.size(); ++i) { size_t priority = i; size_t flags = VFS_MOUNT_WATCH|VFS_MOUNT_ARCHIVABLE|VFS_MOUNT_MUST_EXIST; OsPath modName(mods[i]); g_VFS->Mount(L"", modLoosePath / modName/"", flags, priority); g_VFS->Mount(L"", modArchivePath / modName/"", flags, priority); } // note: don't bother with g_VFS->TextRepresentation - directories // haven't yet been populated and are empty. } static void InitPs(bool setup_gui, const CStrW& gui_page, CScriptVal initData) { { // console TIMER(L"ps_console"); g_Console->UpdateScreenSize(g_xres, g_yres); // Calculate and store the line spacing CFont font(CONSOLE_FONT); g_Console->m_iFontHeight = font.GetLineSpacing(); g_Console->m_iFontWidth = font.GetCharacterWidth(L'C'); g_Console->m_charsPerPage = (size_t)(g_xres / g_Console->m_iFontWidth); // Offset by an arbitrary amount, to make it fit more nicely g_Console->m_iFontOffset = 7; } // hotkeys { TIMER(L"ps_lang_hotkeys"); LoadHotkeys(); } if (!setup_gui) { // We do actually need *some* kind of GUI loaded, so use the // (currently empty) Atlas one g_GUI->SwitchPage(L"page_atlas.xml", initData); return; } // GUI uses VFS, so this must come after VFS init. g_GUI->SwitchPage(gui_page, initData); } static void InitInput() { SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); g_Joystick.Initialise(); // register input handlers // This stack is constructed so the first added, will be the last // one called. This is important, because each of the handlers // has the potential to block events to go further down // in the chain. I.e. the last one in the list added, is the // only handler that can block all messages before they are // processed. in_add_handler(game_view_handler); in_add_handler(CProfileViewer::InputThunk); in_add_handler(conInputHandler); in_add_handler(HotkeyInputHandler); // gui_handler needs to be registered after (i.e. called before!) the // hotkey handler so that input boxes can be typed in without // setting off hotkeys. in_add_handler(gui_handler); // must be registered after (called before) the GUI which relies on these globals in_add_handler(GlobalsInputHandler); } static void ShutdownPs() { SAFE_DELETE(g_GUI); SAFE_DELETE(g_Console); // disable the special Windows cursor, or free textures for OGL cursors cursor_draw(g_VFS, 0, g_mouse_x, g_yres-g_mouse_y); } static void InitRenderer() { TIMER(L"InitRenderer"); if(g_NoGLS3TC) ogl_tex_override(OGL_TEX_S3TC, OGL_TEX_DISABLE); if(g_NoGLAutoMipmap) ogl_tex_override(OGL_TEX_AUTO_MIPMAP_GEN, OGL_TEX_DISABLE); // create renderer new CRenderer; // set renderer options from command line options - NOVBO must be set before opening the renderer g_Renderer.SetOptionBool(CRenderer::OPT_NOVBO,g_NoGLVBO); - g_Renderer.SetOptionBool(CRenderer::OPT_NOFRAMEBUFFEROBJECT,g_NoGLFramebufferObject); g_Renderer.SetOptionBool(CRenderer::OPT_SHADOWS,g_Shadows); g_Renderer.SetOptionBool(CRenderer::OPT_FANCYWATER,g_FancyWater); g_Renderer.SetRenderPath(CRenderer::GetRenderPathByName(g_RenderPath)); g_Renderer.SetOptionFloat(CRenderer::OPT_LODBIAS, g_LodBias); // create terrain related stuff new CTerrainTextureManager; // create the material manager new CMaterialManager; g_Renderer.Open(g_xres,g_yres); // Setup lighting environment. Since the Renderer accesses the // lighting environment through a pointer, this has to be done before // the first Frame. g_Renderer.SetLightEnv(&g_LightEnv); // I haven't seen the camera affecting GUI rendering and such, but the // viewport has to be updated according to the video mode SViewPort vp; vp.m_X=0; vp.m_Y=0; vp.m_Width=g_xres; vp.m_Height=g_yres; g_Renderer.SetViewport(vp); ColorActivateFastImpl(); } static void InitSDL() { if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_NOPARACHUTE) < 0) { LOGERROR(L"SDL library initialization failed: %hs", SDL_GetError()); throw PSERROR_System_SDLInitFailed(); } atexit(SDL_Quit); SDL_EnableUNICODE(1); } static void ShutdownSDL() { SDL_Quit(); sys_cursor_reset(); } void EndGame() { SAFE_DELETE(g_NetServer); SAFE_DELETE(g_NetClient); SAFE_DELETE(g_Game); } void Shutdown(int UNUSED(flags)) { EndGame(); ShutdownPs(); // Must delete g_GUI before g_ScriptingHost in_reset_handlers(); // destroy actor related stuff TIMER_BEGIN(L"shutdown actor stuff"); delete &g_MaterialManager; TIMER_END(L"shutdown actor stuff"); // destroy terrain related stuff TIMER_BEGIN(L"shutdown TexMan"); delete &g_TexMan; TIMER_END(L"shutdown TexMan"); // destroy renderer TIMER_BEGIN(L"shutdown Renderer"); delete &g_Renderer; g_VBMan.Shutdown(); TIMER_END(L"shutdown Renderer"); tex_codec_unregister_all(); TIMER_BEGIN(L"shutdown SDL"); ShutdownSDL(); TIMER_END(L"shutdown SDL"); g_VideoMode.Shutdown(); TIMER_BEGIN(L"shutdown UserReporter"); g_UserReporter.Deinitialize(); TIMER_END(L"shutdown UserReporter"); TIMER_BEGIN(L"shutdown ScriptingHost"); delete &g_ScriptingHost; TIMER_END(L"shutdown ScriptingHost"); TIMER_BEGIN(L"shutdown ConfigDB"); delete &g_ConfigDB; TIMER_END(L"shutdown ConfigDB"); // resource // first shut down all resource owners, and then the handle manager. TIMER_BEGIN(L"resource modules"); snd_shutdown(); g_VFS.reset(); // this forcibly frees all open handles (thus preventing real leaks), // and makes further access to h_mgr impossible. h_mgr_shutdown(); file_stats_dump(); TIMER_END(L"resource modules"); TIMER_BEGIN(L"shutdown misc"); timer_DisplayClientTotals(); CNetHost::Deinitialize(); SAFE_DELETE(g_ScriptStatsTable); // should be last, since the above use them SAFE_DELETE(g_Logger); delete &g_Profiler; delete &g_ProfileViewer; TIMER_END(L"shutdown misc"); } #if OS_UNIX void SetDefaultIfLocaleInvalid() { // On misconfigured systems with incorrect locale settings, we'll die // with a C++ exception when some code tries to use locales. // To avoid death, we'll detect the problem here and warn the user and // reset to the default C locale. // For informing the user of the problem, use the list of env vars that // glibc setlocale looks at. (LC_ALL is checked first, and LANG last.) const char* const LocaleEnvVars[] = { "LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MONETARY", "LC_NUMERIC", "LC_TIME", "LC_MESSAGES", "LANG" }; try { // this constructor is similar to setlocale(LC_ALL, ""), // but instead of returning NULL, it throws runtime_error // when the first locale env variable found contains an invalid value std::locale(""); } catch (std::runtime_error&) { LOGWARNING(L"Invalid locale settings"); for (size_t i = 0; i < ARRAY_SIZE(LocaleEnvVars); i++) { if (char* envval = getenv(LocaleEnvVars[i])) LOGWARNING(L" %hs=\"%hs\"", LocaleEnvVars[i], envval); else LOGWARNING(L" %hs=\"(unset)\"", LocaleEnvVars[i]); } // We should set LC_ALL since it overrides LANG if (setenv("LC_ALL", std::locale::classic().name().c_str(), 1)) debug_warn(L"Invalid locale settings, and unable to set LC_ALL env variable."); else LOGWARNING(L"Setting LC_ALL env variable to: %hs", getenv("LC_ALL")); } } #else void SetDefaultIfLocaleInvalid() { // Do nothing on Windows } #endif void EarlyInit() { // If you ever want to catch a particular allocation: //_CrtSetBreakAlloc(232647); ThreadUtil::SetMainThread(); debug_SetThreadName("main"); // add all debug_printf "tags" that we are interested in: debug_filter_add(L"TIMER"); debug_filter_add(L"HRT"); cpu_ConfigureFloatingPoint(); timer_LatchStartTime(); SetDefaultIfLocaleInvalid(); // Because we do GL calls from a secondary thread, Xlib needs to // be told to support multiple threads safely. // This is needed for Atlas, but we have to call it before any other // Xlib functions (e.g. the ones used when drawing the main menu // before launching Atlas) #if MUST_INIT_X11 int status = XInitThreads(); if (status == 0) debug_printf(L"Error enabling thread-safety via XInitThreads\n"); #endif // Initialise the low-quality rand function srand(time(NULL)); // NOTE: this rand should *not* be used for simulation! } static bool Autostart(const CmdLineArgs& args); void Init(const CmdLineArgs& args, int UNUSED(flags)) { h_mgr_init(); // Do this as soon as possible, because it chdirs // and will mess up the error reporting if anything // crashes before the working directory is set. InitVfs(args); // This must come after VFS init, which sets the current directory // (required for finding our output log files). g_Logger = new CLogger; // Special command-line mode to dump the entity schemas instead of running the game. // (This must be done after loading VFS etc, but should be done before wasting time // on anything else.) if (args.Has("dumpSchema")) { CSimulation2 sim(NULL, NULL); sim.LoadDefaultScripts(); std::ofstream f("entity.rng", std::ios_base::out | std::ios_base::trunc); f << sim.GenerateSchema(); std::cout << "Generated entity.rng\n"; exit(0); } // override ah_translate with our i18n code. AppHooks hooks = {0}; hooks.translate = psTranslate; hooks.translate_free = psTranslateFree; app_hooks_update(&hooks); // Set up the console early, so that debugging // messages can be logged to it. (The console's size // and fonts are set later in InitPs()) g_Console = new CConsole(); CNetHost::Initialize(); new CProfileViewer; new CProfileManager; // before any script code g_ScriptStatsTable = new CScriptStatsTable; g_ProfileViewer.AddRootTable(g_ScriptStatsTable); InitScripting(); // before GUI // g_ConfigDB, command line args, globals CONFIG_Init(args); if (!g_Quickstart) g_UserReporter.Initialize(); // after config } void InitGraphics(const CmdLineArgs& args, int flags) { const bool setup_vmode = (flags & INIT_HAVE_VMODE) == 0; if(setup_vmode) { InitSDL(); if (!g_VideoMode.InitSDL()) throw PSERROR_System_VmodeFailed(); // abort startup SDL_WM_SetCaption("0 A.D.", "0 A.D."); } // needed by ogl_tex to detect broken gfx card/driver combos, // but takes a while due to WMI startup, so make it optional. if(!g_Quickstart) gfx_detect(); RunHardwareDetection(); tex_codec_register_all(); const int quality = SANE_TEX_QUALITY_DEFAULT; // TODO: set value from config file SetTextureQuality(quality); ogl_WarnIfError(); if(!g_Quickstart) { WriteSystemInfo(); // note: no longer vfs_display here. it's dog-slow due to unbuffered // file output and very rarely needed. } if(g_DisableAudio) { // speed up startup by disabling all sound // (OpenAL init will be skipped). // must be called before first snd_open. snd_disable(true); } g_GUI = new CGUIManager(g_ScriptingHost.GetScriptInterface()); // (must come after SetVideoMode, since it calls ogl_Init) const char* missing = ogl_HaveExtensions(0, "GL_ARB_multitexture", "GL_EXT_draw_range_elements", "GL_ARB_texture_env_combine", "GL_ARB_texture_env_dot3", NULL); if(missing) { wchar_t buf[500]; swprintf_s(buf, ARRAY_SIZE(buf), L"The %hs extension doesn't appear to be available on your computer." L" The game may still work, though - you are welcome to try at your own risk." L" If not or it doesn't look right, upgrade your graphics card.", missing ); DEBUG_DISPLAY_ERROR(buf); // TODO: i18n } if (!ogl_HaveExtension("GL_ARB_texture_env_crossbar")) { DEBUG_DISPLAY_ERROR( L"The GL_ARB_texture_env_crossbar extension doesn't appear to be available on your computer." L" Shadows are not available and overall graphics quality might suffer." L" You are advised to try installing newer drivers and/or upgrade your graphics card."); g_Shadows = false; } ogl_WarnIfError(); InitRenderer(); InitInput(); ogl_WarnIfError(); if (!Autostart(args)) { const bool setup_gui = ((flags & INIT_NO_GUI) == 0); InitPs(setup_gui, L"page_pregame.xml", JSVAL_VOID); } } void RenderGui(bool RenderingState) { g_DoRenderGui = RenderingState; } void RenderLogger(bool RenderingState) { g_DoRenderLogger = RenderingState; } void RenderCursor(bool RenderingState) { g_DoRenderCursor = RenderingState; } static bool Autostart(const CmdLineArgs& args) { /* * Handle various command-line options, for quick testing of various features: * -autostart=mapname -- single-player * -autostart=mapname -autostart-playername=Player -autostart-host -autostart-players=2 -- multiplayer host, wait for 2 players * -autostart=mapname -autostart-playername=Player -autostart-client -autostart-ip=127.0.0.1 -- multiplayer client, connect to 127.0.0.1 * -autostart=scriptname -autostart-random=104 -- random map, seed 104 (default is 0, for random choose -1) */ CStr autoStartName = args.Get("autostart"); if (autoStartName.empty()) return false; g_Game = new CGame(); ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); CScriptValRooted attrs; scriptInterface.Eval("({})", attrs); CScriptVal settings; scriptInterface.Eval("({})", settings); CScriptVal playerData; scriptInterface.Eval("([])", playerData); // Set different attributes for random or scenario game if (args.Has("autostart-random")) { CStr seedArg = args.Get("autostart-random"); // Default seed is 0 uint32 seed = 0; if (!seedArg.empty()) { if (seedArg.compare("-1") == 0) { // Random seed value seed = rand(); } else { seed = seedArg.ToULong(); } } scriptInterface.SetProperty(attrs.get(), "script", std::string(autoStartName), false); // RMS name scriptInterface.SetProperty(attrs.get(), "mapType", std::string("random"), false); // For random map, there are special settings // TODO: Get these from command line - using defaults for now scriptInterface.SetProperty(settings.get(), "Size", 12); // Random map size (in patches) scriptInterface.SetProperty(settings.get(), "Seed", seed); // Random seed scriptInterface.SetProperty(settings.get(), "BaseTerrain", std::string("grass1_spring")); // Base terrain texture scriptInterface.SetProperty(settings.get(), "BaseHeight", 0); // Base terrain height // Define players // TODO: Get these from command line? - using defaults for now size_t numPlayers = 2; for (size_t i = 0; i < numPlayers; ++i) { CScriptVal player; scriptInterface.Eval("({})", player); scriptInterface.SetProperty(player.get(), "Civ", std::string("hele")); scriptInterface.SetPropertyInt(playerData.get(), i, player); } } else { scriptInterface.SetProperty(attrs.get(), "map", std::string(autoStartName), false); scriptInterface.SetProperty(attrs.get(), "mapType", std::string("scenario"), false); } // Set player data for AIs // attrs.settings = { PlayerData: [ { AI: ... }, ... ] }: /* * Handle command-line options for AI: * -autostart-ai=1:dummybot -autostart-ai=2:dummybot -- adds the dummybot AI to players 1 and 2 */ if (args.Has("autostart-ai")) { std::vector aiArgs = args.GetMultiple("autostart-ai"); for (size_t i = 0; i < aiArgs.size(); ++i) { CScriptVal player; scriptInterface.Eval("({})", player); int playerID = aiArgs[i].BeforeFirst(":").ToInt(); CStr name = aiArgs[i].AfterFirst(":"); scriptInterface.SetProperty(player.get(), "AI", std::string(name)); scriptInterface.SetPropertyInt(playerData.get(), playerID-1, player); } } // Add player data to map settings scriptInterface.SetProperty(settings.get(), "PlayerData", playerData); // Add map settings to game attributes scriptInterface.SetProperty(attrs.get(), "settings", settings); CScriptVal mpInitData; g_GUI->GetScriptInterface().Eval("({isNetworked:true, playerAssignments:{}})", mpInitData); g_GUI->GetScriptInterface().SetProperty(mpInitData.get(), "attribs", CScriptVal(g_GUI->GetScriptInterface().CloneValueFromOtherContext(scriptInterface, attrs.get())), false); if (args.Has("autostart-host")) { InitPs(true, L"page_loading.xml", mpInitData.get()); size_t maxPlayers = 2; if (args.Has("autostart-players")) maxPlayers = args.Get("autostart-players").ToUInt(); g_NetServer = new CNetServer(maxPlayers); g_NetServer->UpdateGameAttributes(attrs.get(), scriptInterface); bool ok = g_NetServer->SetupConnection(); debug_assert(ok); g_NetClient = new CNetClient(g_Game); // TODO: player name, etc g_NetClient->SetupConnection("127.0.0.1"); } else if (args.Has("autostart-client")) { InitPs(true, L"page_loading.xml", mpInitData.get()); g_NetClient = new CNetClient(g_Game); // TODO: player name, etc CStr ip = "127.0.0.1"; if (args.Has("autostart-ip")) ip = args.Get("autostart-ip"); bool ok = g_NetClient->SetupConnection(ip); debug_assert(ok); } else { g_Game->SetPlayerID(1); g_Game->StartGame(attrs); LDR_NonprogressiveLoad(); PSRETURN ret = g_Game->ReallyStartGame(); debug_assert(ret == PSRETURN_OK); InitPs(true, L"page_session.xml", JSVAL_VOID); } return true; } Index: ps/trunk/source/renderer/Renderer.h =================================================================== --- ps/trunk/source/renderer/Renderer.h (revision 9188) +++ ps/trunk/source/renderer/Renderer.h (revision 9189) @@ -1,493 +1,489 @@ /* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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. * * 0 A.D. 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 0 A.D. If not, see . */ /* * higher level interface on top of OpenGL to render basic objects: * terrain, models, sprites, particles etc. */ #ifndef INCLUDED_RENDERER #define INCLUDED_RENDERER #include "graphics/Camera.h" #include "graphics/SColor.h" #include "graphics/ShaderProgram.h" #include "lib/ogl.h" #include "lib/res/handle.h" #include "ps/Singleton.h" #include "scripting/ScriptableObject.h" #include "renderer/Scene.h" // necessary declarations class CPatch; class CMaterial; class CModel; class CLightEnv; class RenderPathVertexShader; class WaterManager; class SkyManager; class CTextureManager; class CShaderManager; class CParticleManager; // rendering modes enum ERenderMode { WIREFRAME, SOLID, EDGED_FACES }; // stream flags #define STREAM_POS (1 << 0) #define STREAM_NORMAL (1 << 1) #define STREAM_COLOR (1 << 2) #define STREAM_UV0 (1 << 3) #define STREAM_UV1 (1 << 4) #define STREAM_UV2 (1 << 5) #define STREAM_UV3 (1 << 6) #define STREAM_POSTOUV0 (1 << 7) #define STREAM_POSTOUV1 (1 << 8) #define STREAM_POSTOUV2 (1 << 9) #define STREAM_POSTOUV3 (1 << 10) #define STREAM_TEXGENTOUV1 (1 << 11) // access to sole renderer object #define g_Renderer CRenderer::GetSingleton() /////////////////////////////////////////////////////////////////////////////////////////// // CRenderer: base renderer class - primary interface to the rendering engine struct CRendererInternals; class CRenderer : public Singleton, public CJSObject, private SceneCollector { public: // various enumerations and renderer related constants enum { NumAlphaMaps=14 }; enum Option { OPT_NOVBO, - OPT_NOFRAMEBUFFEROBJECT, OPT_SHADOWS, OPT_FANCYWATER, OPT_LODBIAS }; enum RenderPath { // If no rendering path is configured explicitly, the renderer // will choose the path when Open() is called. RP_DEFAULT, // Classic fixed function. RP_FIXED, // Use new ARB/GLSL system RP_SHADER }; // stats class - per frame counts of number of draw calls, poly counts etc struct Stats { // set all stats to zero void Reset() { memset(this,0,sizeof(*this)); } // add given stats to this stats Stats& operator+=(const Stats& rhs) { m_Counter++; m_DrawCalls+=rhs.m_DrawCalls; m_TerrainTris+=rhs.m_TerrainTris; m_ModelTris+=rhs.m_ModelTris; m_BlendSplats+=rhs.m_BlendSplats; return *this; } // count of the number of stats added together size_t m_Counter; // number of draw calls per frame - total DrawElements + Begin/End immediate mode loops size_t m_DrawCalls; // number of terrain triangles drawn size_t m_TerrainTris; // number of (non-transparent) model triangles drawn size_t m_ModelTris; // number of splat passes for alphamapping size_t m_BlendSplats; }; // renderer options struct Options { bool m_NoVBO; - bool m_NoFramebufferObject; bool m_Shadows; bool m_FancyWater; float m_LodBias; RenderPath m_RenderPath; bool m_ShadowAlphaFix; bool m_ARBProgramShadow; } m_Options; struct Caps { bool m_VBO; bool m_ARBProgram; bool m_ARBProgramShadow; bool m_VertexShader; bool m_FragmentShader; bool m_Shadows; - bool m_DepthTextureShadows; - bool m_FramebufferObject; }; public: // constructor, destructor CRenderer(); ~CRenderer(); // open up the renderer: performs any necessary initialisation bool Open(int width,int height); // resize renderer view void Resize(int width,int height); // set/get boolean renderer option void SetOptionBool(enum Option opt,bool value); bool GetOptionBool(enum Option opt) const; void SetOptionFloat(enum Option opt, float val); void SetRenderPath(RenderPath rp); RenderPath GetRenderPath() const { return m_Options.m_RenderPath; } static CStr GetRenderPathName(RenderPath rp); static RenderPath GetRenderPathByName(const CStr& name); // return view width int GetWidth() const { return m_Width; } // return view height int GetHeight() const { return m_Height; } // return view aspect ratio float GetAspect() const { return float(m_Width)/float(m_Height); } // signal frame start void BeginFrame(); // signal frame end void EndFrame(); // set color used to clear screen in BeginFrame() void SetClearColor(SColor4ub color); // trigger a reload of shaders (when parameters they depend on have changed) void MakeShadersDirty(); /** * Set up the camera used for rendering the next scene; this includes * setting OpenGL state like viewport, projection and modelview matrices. * * @param viewCamera this camera determines the eye position for rendering * @param cullCamera this camera determines the frustum for culling in the renderer and * for shadow calculations */ void SetSceneCamera(const CCamera& viewCamera, const CCamera& cullCamera); // set the viewport void SetViewport(const SViewPort &); /** * Render the given scene immediately. * @param scene a Scene object describing what should be rendered. */ void RenderScene(Scene& scene); /** * Return the scene that is currently being rendered. * Only valid when the renderer is in a RenderScene call. */ Scene& GetScene(); /** * Render text overlays on top of the scene. * Assumes the caller has set up the GL environment for orthographic rendering * with texturing and blending. */ void RenderTextOverlays(); // set the current lighting environment; (note: the passed pointer is just copied to a variable within the renderer, // so the lightenv passed must be scoped such that it is not destructed until after the renderer is no longer rendering) void SetLightEnv(CLightEnv* lightenv) { m_LightEnv=lightenv; } // set the mode to render subsequent terrain patches void SetTerrainRenderMode(ERenderMode mode) { m_TerrainRenderMode=mode; } // get the mode to render subsequent terrain patches ERenderMode GetTerrainRenderMode() const { return m_TerrainRenderMode; } // set the mode to render subsequent models void SetModelRenderMode(ERenderMode mode) { m_ModelRenderMode=mode; } // get the mode to render subsequent models ERenderMode GetModelRenderMode() const { return m_ModelRenderMode; } // debugging void SetDisplayTerrainPriorities(bool enabled) { m_DisplayTerrainPriorities = enabled; } // bind a GL texture object to active unit void BindTexture(int unit,GLuint tex); // load the default set of alphamaps. // return a negative error code if anything along the way fails. // called via delay-load mechanism. int LoadAlphaMaps(); void UnloadAlphaMaps(); // return stats accumulated for current frame const Stats& GetStats() { return m_Stats; } // return the current light environment const CLightEnv &GetLightEnv() { return *m_LightEnv; } // return the current view camera const CCamera& GetViewCamera() const { return m_ViewCamera; } // return the current cull camera const CCamera& GetCullCamera() const { return m_CullCamera; } // get the current OpenGL model-view-projection matrix into the given float[] CMatrix3D GetModelViewProjectionMatrix(); /** * GetWaterManager: Return the renderer's water manager. * * @return the WaterManager object used by the renderer */ WaterManager* GetWaterManager() { return m_WaterManager; } /** * GetSkyManager: Return the renderer's sky manager. * * @return the SkyManager object used by the renderer */ SkyManager* GetSkyManager() { return m_SkyManager; } CTextureManager& GetTextureManager(); CShaderManager& GetShaderManager(); CParticleManager& GetParticleManager(); /** * SetFastPlayerColor: Tell the renderer which path to take for * player colored models. Both paths should provide the same visual * quality, however the slow path runs on older hardware using multi-pass. * * @param fast true if the fast path should be used from now on. If fast * is true but the OpenGL implementation does not support it, a warning * is printed and the slow path is used instead. */ void SetFastPlayerColor(bool fast); /** * GetCapabilities: Return which OpenGL capabilities are available and enabled. * * @return capabilities structure */ const Caps& GetCapabilities() const { return m_Caps; } bool GetDisableCopyShadow() const { return m_DisableCopyShadow; } static void ScriptingInit(); protected: friend struct CRendererInternals; friend class CVertexBuffer; friend class CPatchRData; friend class CDecalRData; friend class FixedFunctionModelRenderer; friend class ModelRenderer; friend class PolygonSortModelRenderer; friend class SortModelRenderer; friend class RenderPathVertexShader; friend class HWLightingModelRenderer; friend class ShaderModelRenderer; friend class InstancingModelRenderer; friend class ShaderInstancingModelRenderer; friend class TerrainRenderer; // scripting // TODO: Perhaps we could have a version of AddLocalProperty for function-driven // properties? Then we could hide these function in the private implementation class. jsval JSI_GetFastPlayerColor(JSContext*); void JSI_SetFastPlayerColor(JSContext* ctx, jsval newval); jsval JSI_GetRenderPath(JSContext*); void JSI_SetRenderPath(JSContext* ctx, jsval newval); jsval JSI_GetDepthTextureBits(JSContext*); void JSI_SetDepthTextureBits(JSContext* ctx, jsval newval); jsval JSI_GetShadows(JSContext*); void JSI_SetShadows(JSContext* ctx, jsval newval); jsval JSI_GetShadowAlphaFix(JSContext*); void JSI_SetShadowAlphaFix(JSContext* ctx, jsval newval); jsval JSI_GetSky(JSContext*); void JSI_SetSky(JSContext* ctx, jsval newval); //BEGIN: Implementation of SceneCollector void Submit(CPatch* patch); void Submit(SOverlayLine* overlay); void Submit(SOverlaySprite* overlay); void Submit(CModelDecal* decal); void Submit(CParticleEmitter* emitter); void SubmitNonRecursive(CModel* model); //END: Implementation of SceneCollector // render any batched objects void RenderSubmissions(); // patch rendering stuff void RenderPatches(); // model rendering stuff void RenderModels(); void RenderTransparentModels(); void RenderSilhouettes(); void RenderParticles(); // shadow rendering stuff void RenderShadowMap(); // render water reflection and refraction textures void RenderReflections(); void RenderRefractions(); // debugging void DisplayFrustum(); // enable oblique frustum clipping with the given clip plane void SetObliqueFrustumClipping(const CVector4D& clipPlane, int sign); void ReloadShaders(); // hotloading static LibError ReloadChangedFileCB(void* param, const VfsPath& path); // RENDERER DATA: /// Private data that is not needed by inline functions CRendererInternals* m; // view width int m_Width; // view height int m_Height; // current terrain rendering mode ERenderMode m_TerrainRenderMode; // current model rendering mode ERenderMode m_ModelRenderMode; /** * m_ViewCamera: determines the eye position for rendering * * @see CGameView::m_ViewCamera */ CCamera m_ViewCamera; /** * m_CullCamera: determines the frustum for culling and shadowmap calculations * * @see CGameView::m_ViewCamera */ CCamera m_CullCamera; // only valid inside a call to RenderScene Scene* m_CurrentScene; // color used to clear screen in BeginFrame float m_ClearColor[4]; // current lighting setup CLightEnv* m_LightEnv; // ogl_tex handle of composite alpha map (all the alpha maps packed into one texture) Handle m_hCompositeAlphaMap; // coordinates of each (untransformed) alpha map within the packed texture struct { float u0,u1,v0,v1; } m_AlphaMapCoords[NumAlphaMaps]; // card capabilities Caps m_Caps; // build card cap bits void EnumCaps(); // per-frame renderer stats Stats m_Stats; /// If false, use a multipass fallback for player colors. bool m_FastPlayerColor; /** * m_WaterManager: the WaterManager object used for water textures and settings * (e.g. water color, water height) */ WaterManager* m_WaterManager; /** * m_SkyManager: the SkyManager object used for sky textures and settings */ SkyManager* m_SkyManager; /** * m_SortAllTransparent: If true, all transparent models are * rendered using the TransparencyRenderer which performs sorting. * * Otherwise, transparent models are rendered using the faster * batching renderer when possible. */ bool m_SortAllTransparent; /** * m_DisplayFrustum: Render the cull frustum and other data that may be interesting * to evaluate culling and shadow map calculations * * Can be controlled from JS via renderer.displayFrustum */ bool m_DisplayFrustum; /** * m_DisableCopyShadow: For debugging purpose: * Disable copying of shadow data into the shadow texture (when EXT_fbo is not available) */ bool m_DisableCopyShadow; /** * Enable rendering of terrain tile priority text overlay, for debugging. */ bool m_DisplayTerrainPriorities; public: /** * m_ShadowZBias: Z bias used when rendering shadows into a depth texture. * This can be used to control shadowing artifacts. * * Can be accessed via JS as renderer.shadowZBias * ShadowMap uses this for matrix calculation. */ float m_ShadowZBias; /** * m_ShadowMapSize: Size of shadow map, or 0 for default. Typically slow but useful * for high-quality rendering. Changes don't take effect until the shadow map * is regenerated. * * Can be accessed via JS as renderer.shadowMapSize */ int m_ShadowMapSize; /** * m_SkipSubmit: Disable the actual submission of rendering commands to OpenGL. * All state setup is still performed as usual. * * Can be accessed via JS as renderer.skipSubmit */ bool m_SkipSubmit; }; #endif Index: ps/trunk/source/renderer/Renderer.cpp =================================================================== --- ps/trunk/source/renderer/Renderer.cpp (revision 9188) +++ ps/trunk/source/renderer/Renderer.cpp (revision 9189) @@ -1,2002 +1,1976 @@ /* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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. * * 0 A.D. 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 0 A.D. If not, see . */ /* * higher level interface on top of OpenGL to render basic objects: * terrain, models, sprites, particles etc. */ #include "precompiled.h" #include #include #include #include #include "Renderer.h" #include "lib/bits.h" // is_pow2 #include "lib/res/graphics/ogl_tex.h" #include "maths/Matrix3D.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/Game.h" #include "ps/Profile.h" #include "ps/Filesystem.h" #include "ps/World.h" #include "ps/Loader.h" #include "ps/ProfileViewer.h" #include "graphics/Camera.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "graphics/ParticleManager.h" #include "graphics/ShaderManager.h" #include "graphics/ShaderTechnique.h" #include "graphics/Terrain.h" #include "graphics/Texture.h" #include "graphics/TextureManager.h" #include "renderer/FixedFunctionModelRenderer.h" #include "renderer/HWLightingModelRenderer.h" #include "renderer/InstancingModelRenderer.h" #include "renderer/ModelRenderer.h" #include "renderer/OverlayRenderer.h" #include "renderer/ParticleRenderer.h" #include "renderer/PlayerRenderer.h" #include "renderer/RenderModifiers.h" #include "renderer/ShadowMap.h" #include "renderer/SkyManager.h" #include "renderer/TerrainOverlay.h" #include "renderer/TerrainRenderer.h" #include "renderer/TransparencyRenderer.h" #include "renderer/VertexBufferManager.h" #include "renderer/WaterManager.h" /////////////////////////////////////////////////////////////////////////////////// // CRendererStatsTable - Profile display of rendering stats /** * Class CRendererStatsTable: Implementation of AbstractProfileTable to * display the renderer stats in-game. * * Accesses CRenderer::m_Stats by keeping the reference passed to the * constructor. */ class CRendererStatsTable : public AbstractProfileTable { NONCOPYABLE(CRendererStatsTable); public: CRendererStatsTable(const CRenderer::Stats& st); // Implementation of AbstractProfileTable interface CStr GetName(); CStr GetTitle(); size_t GetNumberRows(); const std::vector& GetColumns(); CStr GetCellText(size_t row, size_t col); AbstractProfileTable* GetChild(size_t row); private: /// Reference to the renderer singleton's stats const CRenderer::Stats& Stats; /// Column descriptions std::vector columnDescriptions; enum { Row_Counter = 0, Row_DrawCalls, Row_TerrainTris, Row_ModelTris, Row_BlendSplats, Row_VBReserved, Row_VBAllocated, // Must be last to count number of rows NumberRows }; }; // Construction CRendererStatsTable::CRendererStatsTable(const CRenderer::Stats& st) : Stats(st) { columnDescriptions.push_back(ProfileColumn("Name", 230)); columnDescriptions.push_back(ProfileColumn("Value", 100)); } // Implementation of AbstractProfileTable interface CStr CRendererStatsTable::GetName() { return "renderer"; } CStr CRendererStatsTable::GetTitle() { return "Renderer statistics"; } size_t CRendererStatsTable::GetNumberRows() { return NumberRows; } const std::vector& CRendererStatsTable::GetColumns() { return columnDescriptions; } CStr CRendererStatsTable::GetCellText(size_t row, size_t col) { char buf[256]; switch(row) { case Row_Counter: if (col == 0) return "counter"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_Counter); return buf; case Row_DrawCalls: if (col == 0) return "# draw calls"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_DrawCalls); return buf; case Row_TerrainTris: if (col == 0) return "# terrain tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_TerrainTris); return buf; case Row_ModelTris: if (col == 0) return "# model tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_ModelTris); return buf; case Row_BlendSplats: if (col == 0) return "# blend splats"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_BlendSplats); return buf; case Row_VBReserved: if (col == 0) return "VB bytes reserved"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_VBMan.GetBytesReserved()); return buf; case Row_VBAllocated: if (col == 0) return "VB bytes allocated"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_VBMan.GetBytesAllocated()); return buf; default: return "???"; } } AbstractProfileTable* CRendererStatsTable::GetChild(size_t UNUSED(row)) { return 0; } /////////////////////////////////////////////////////////////////////////////////// // CRenderer implementation /** * Struct CRendererInternals: Truly hide data that is supposed to be hidden * in this structure so it won't even appear in header files. */ struct CRendererInternals { NONCOPYABLE(CRendererInternals); public: /// true if CRenderer::Open has been called bool IsOpen; /// true if shaders need to be reloaded bool ShadersDirty; /// Table to display renderer stats in-game via profile system CRendererStatsTable profileTable; /// Shader manager CShaderManager shaderManager; /// Water manager WaterManager waterManager; /// Sky manager SkyManager skyManager; /// Texture manager CTextureManager textureManager; /// Terrain renderer TerrainRenderer* terrainRenderer; /// Overlay renderer OverlayRenderer overlayRenderer; /// Particle manager CParticleManager particleManager; /// Particle renderer ParticleRenderer particleRenderer; /// Shadow map ShadowMap* shadow; /// Various model renderers struct Models { // The following model renderers are aliases for the appropriate real_* // model renderers (depending on hardware availability and current settings) // and must be used for actual model submission and rendering ModelRenderer* Normal; ModelRenderer* NormalInstancing; ModelRenderer* Player; ModelRenderer* PlayerInstancing; ModelRenderer* Transp; // "Palette" of available ModelRenderers. Do not use these directly for // rendering and submission; use the aliases above instead. ModelRenderer* pal_NormalFF; ModelRenderer* pal_PlayerFF; ModelRenderer* pal_TranspFF; ModelRenderer* pal_TranspSortAll; ModelRenderer* pal_NormalShader; ModelRenderer* pal_NormalInstancingShader; ModelRenderer* pal_PlayerShader; ModelRenderer* pal_PlayerInstancingShader; ModelRenderer* pal_TranspShader; ModelVertexRendererPtr VertexFF; ModelVertexRendererPtr VertexPolygonSort; ModelVertexRendererPtr VertexRendererShader; ModelVertexRendererPtr VertexInstancingShader; // generic RenderModifiers that are supposed to be used directly RenderModifierPtr ModWireframe; RenderModifierPtr ModSolidColor; RenderModifierPtr ModSolidPlayerColor; RenderModifierPtr ModTransparentDepthShadow; // RenderModifiers that are selected from the palette below RenderModifierPtr ModNormal; RenderModifierPtr ModNormalInstancing; RenderModifierPtr ModPlayer; RenderModifierPtr ModPlayerInstancing; RenderModifierPtr ModSolid; RenderModifierPtr ModSolidInstancing; RenderModifierPtr ModSolidPlayer; RenderModifierPtr ModSolidPlayerInstancing; RenderModifierPtr ModTransparent; // Palette of available RenderModifiers RenderModifierPtr ModPlainUnlit; RenderModifierPtr ModPlayerUnlit; RenderModifierPtr ModTransparentUnlit; RenderModifierPtr ModShaderSolidColor; RenderModifierPtr ModShaderSolidColorInstancing; RenderModifierPtr ModShaderSolidPlayerColor; RenderModifierPtr ModShaderSolidPlayerColorInstancing; RenderModifierPtr ModShaderSolidTex; LitRenderModifierPtr ModShaderNormal; LitRenderModifierPtr ModShaderNormalInstancing; LitRenderModifierPtr ModShaderPlayer; LitRenderModifierPtr ModShaderPlayerInstancing; LitRenderModifierPtr ModShaderTransparent; RenderModifierPtr ModShaderTransparentShadow; } Model; CRendererInternals() : IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats), textureManager(g_VFS, false, false) { terrainRenderer = new TerrainRenderer(); shadow = new ShadowMap(); Model.pal_NormalFF = 0; Model.pal_PlayerFF = 0; Model.pal_TranspFF = 0; Model.pal_TranspSortAll = 0; Model.pal_NormalShader = 0; Model.pal_NormalInstancingShader = 0; Model.pal_PlayerShader = 0; Model.pal_PlayerInstancingShader = 0; Model.pal_TranspShader = 0; Model.Normal = 0; Model.NormalInstancing = 0; Model.Player = 0; Model.PlayerInstancing = 0; Model.Transp = 0; } ~CRendererInternals() { delete shadow; delete terrainRenderer; } /** * Load the OpenGL projection and modelview matrices and the viewport according * to the given camera. */ void SetOpenGLCamera(const CCamera& camera) { CMatrix3D view; camera.m_Orientation.GetInverse(view); const CMatrix3D& proj = camera.GetProjection(); glMatrixMode(GL_PROJECTION); glLoadMatrixf(&proj._11); glMatrixMode(GL_MODELVIEW); glLoadMatrixf(&view._11); const SViewPort &vp = camera.GetViewPort(); glViewport((GLint)vp.m_X,(GLint)vp.m_Y,(GLsizei)vp.m_Width,(GLsizei)vp.m_Height); } /** * Renders all non-transparent models with the given modifiers. */ void CallModelRenderers( const RenderModifierPtr& modNormal, const RenderModifierPtr& modNormalInstancing, const RenderModifierPtr& modPlayer, const RenderModifierPtr& modPlayerInstancing, int flags) { Model.Normal->Render(modNormal, flags); if (Model.Normal != Model.NormalInstancing) Model.NormalInstancing->Render(modNormalInstancing, flags); Model.Player->Render(modPlayer, flags); if (Model.Player != Model.PlayerInstancing) Model.PlayerInstancing->Render(modPlayerInstancing, flags); } }; /////////////////////////////////////////////////////////////////////////////////// // CRenderer constructor CRenderer::CRenderer() { m = new CRendererInternals; m_WaterManager = &m->waterManager; m_SkyManager = &m->skyManager; g_ProfileViewer.AddRootTable(&m->profileTable); m_Width=0; m_Height=0; m_TerrainRenderMode=SOLID; m_ModelRenderMode=SOLID; m_ClearColor[0]=m_ClearColor[1]=m_ClearColor[2]=m_ClearColor[3]=0; m_SortAllTransparent = false; m_DisplayFrustum = false; m_DisableCopyShadow = false; m_DisplayTerrainPriorities = false; m_FastPlayerColor = true; m_SkipSubmit = false; m_Options.m_NoVBO = false; - m_Options.m_NoFramebufferObject = false; m_Options.m_RenderPath = RP_DEFAULT; m_Options.m_FancyWater = false; m_Options.m_Shadows = false; m_Options.m_ShadowAlphaFix = true; m_Options.m_ARBProgramShadow = true; m_ShadowZBias = 0.02f; m_ShadowMapSize = 0; m_LightEnv = NULL; m_CurrentScene = NULL; m_hCompositeAlphaMap = 0; AddLocalProperty(L"fancyWater", &m_Options.m_FancyWater, false); AddLocalProperty(L"horizonHeight", &m->skyManager.m_HorizonHeight, false); AddLocalProperty(L"waterMurkiness", &m->waterManager.m_Murkiness, false); AddLocalProperty(L"waterReflTintStrength", &m->waterManager.m_ReflectionTintStrength, false); AddLocalProperty(L"waterRepeatPeriod", &m->waterManager.m_RepeatPeriod, false); AddLocalProperty(L"waterShininess", &m->waterManager.m_Shininess, false); AddLocalProperty(L"waterSpecularStrength", &m->waterManager.m_SpecularStrength, false); AddLocalProperty(L"waterWaviness", &m->waterManager.m_Waviness, false); RegisterFileReloadFunc(ReloadChangedFileCB, this); } /////////////////////////////////////////////////////////////////////////////////// // CRenderer destructor CRenderer::~CRenderer() { UnregisterFileReloadFunc(ReloadChangedFileCB, this); // model rendering delete m->Model.pal_NormalFF; delete m->Model.pal_PlayerFF; delete m->Model.pal_TranspFF; delete m->Model.pal_TranspSortAll; delete m->Model.pal_NormalShader; delete m->Model.pal_NormalInstancingShader; delete m->Model.pal_PlayerShader; delete m->Model.pal_PlayerInstancingShader; delete m->Model.pal_TranspShader; // we no longer UnloadAlphaMaps / UnloadWaterTextures here - // that is the responsibility of the module that asked for // them to be loaded (i.e. CGameView). delete m; } /////////////////////////////////////////////////////////////////////////////////// // EnumCaps: build card cap bits void CRenderer::EnumCaps() { // assume support for nothing m_Caps.m_VBO = false; m_Caps.m_ARBProgram = false; m_Caps.m_ARBProgramShadow = false; m_Caps.m_VertexShader = false; m_Caps.m_FragmentShader = false; m_Caps.m_Shadows = false; - m_Caps.m_DepthTextureShadows = false; - m_Caps.m_FramebufferObject = false; // now start querying extensions if (!m_Options.m_NoVBO) { if (ogl_HaveExtension("GL_ARB_vertex_buffer_object")) { m_Caps.m_VBO=true; } } if (0 == ogl_HaveExtensions(0, "GL_ARB_vertex_program", "GL_ARB_fragment_program", NULL)) { m_Caps.m_ARBProgram = true; if (ogl_HaveExtension("GL_ARB_fragment_program_shadow")) m_Caps.m_ARBProgramShadow = true; } if (0 == ogl_HaveExtensions(0, "GL_ARB_shader_objects", "GL_ARB_shading_language_100", NULL)) { if (ogl_HaveExtension("GL_ARB_vertex_shader")) m_Caps.m_VertexShader = true; if (ogl_HaveExtension("GL_ARB_fragment_shader")) m_Caps.m_FragmentShader = true; } - if (ogl_max_tex_units >= 3) + if (0 == ogl_HaveExtensions(0, "GL_ARB_shadow", "GL_ARB_depth_texture", "GL_EXT_framebuffer_object", NULL)) { - // To render shadows plus fog-of-war in a single lighting pass (see - // TerrainRenderer.cpp) we need >= 3 TMUs. Only really ancient hardware - // doesn't support that, so instead of implementing a compatible fallback - // we'll just disable shadows entirely unless there's enough TMUs. - m_Caps.m_Shadows = true; - } - - if (0 == ogl_HaveExtensions(0, "GL_ARB_shadow", "GL_ARB_depth_texture", NULL)) { - // According to Delphi3d.net, all relevant graphics chips that support depth textures - // (i.e. Geforce3+, Radeon9500+, even i915) also have >= 4 TMUs, so this restriction - // isn't actually a restriction, and it helps with integrating depth texture - // shadows into rendering paths. if (ogl_max_tex_units >= 4) - m_Caps.m_DepthTextureShadows = true; - } - - if (!m_Options.m_NoFramebufferObject) - { - if (ogl_HaveExtension("GL_EXT_framebuffer_object")) - m_Caps.m_FramebufferObject = true; + m_Caps.m_Shadows = true; } } void CRenderer::ReloadShaders() { typedef std::map Defines; Defines defNull; Defines defBasic; if (m_Options.m_Shadows) { defBasic["USE_SHADOW"] = "1"; if (m_Caps.m_ARBProgramShadow && m_Options.m_ARBProgramShadow) defBasic["USE_FP_SHADOW"] = "1"; } if (m_LightEnv) defBasic["LIGHTING_MODEL_" + m_LightEnv->GetLightingModel()] = "1"; Defines defColored = defBasic; defColored["USE_OBJECTCOLOR"] = "1"; Defines defTransparent = defBasic; defTransparent["USE_TRANSPARENT"] = "1"; // TODO: it'd be nicer to load this technique from an XML file or something CShaderPass passTransparent0(m->shaderManager.LoadProgram("solid_tex", defNull)); passTransparent0.AlphaFunc(GL_GREATER, 0.975f); passTransparent0.ColorMask(0, 0, 0, 0); CShaderPass passTransparent1(m->shaderManager.LoadProgram("model_common", defTransparent)); passTransparent1.AlphaFunc(GL_GREATER, 0.0f); passTransparent1.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); passTransparent1.DepthMask(0); CShaderTechnique techTransparent(passTransparent0); techTransparent.AddPass(passTransparent1); CShaderPass passTransparentShadow(m->shaderManager.LoadProgram("solid_tex", defBasic)); passTransparentShadow.AlphaFunc(GL_GREATER, 0.4f); CShaderTechnique techTransparentShadow(passTransparentShadow); m->Model.ModShaderSolidColor = RenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "solid", defNull)))); m->Model.ModShaderSolidColorInstancing = RenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "solid_instancing", defNull)))); m->Model.ModShaderSolidPlayerColor = RenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "solid_player", defNull)))); m->Model.ModShaderSolidPlayerColorInstancing = RenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "solid_player_instancing", defNull)))); m->Model.ModShaderSolidTex = RenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "solid_tex", defNull)))); m->Model.ModShaderNormal = LitRenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "model_common", defBasic)))); m->Model.ModShaderNormalInstancing = LitRenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "model_common_instancing", defBasic)))); m->Model.ModShaderPlayer = LitRenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "model_common", defColored)))); m->Model.ModShaderPlayerInstancing = LitRenderModifierPtr(new ShaderRenderModifier(CShaderTechnique(m->shaderManager.LoadProgram( "model_common_instancing", defColored)))); m->Model.ModShaderTransparent = LitRenderModifierPtr(new ShaderRenderModifier( techTransparent)); m->Model.ModShaderTransparentShadow = LitRenderModifierPtr(new ShaderRenderModifier( techTransparentShadow)); m->ShadersDirty = false; } bool CRenderer::Open(int width, int height) { m->IsOpen = true; // Must query card capabilities before creating renderers that depend // on card capabilities. EnumCaps(); // model rendering m->Model.VertexFF = ModelVertexRendererPtr(new FixedFunctionModelRenderer); m->Model.VertexPolygonSort = ModelVertexRendererPtr(new PolygonSortModelRenderer); m->Model.VertexRendererShader = ModelVertexRendererPtr(new ShaderModelRenderer); m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer); m->Model.pal_NormalFF = new BatchModelRenderer(m->Model.VertexFF); m->Model.pal_PlayerFF = new BatchModelRenderer(m->Model.VertexFF); m->Model.pal_TranspFF = new SortModelRenderer(m->Model.VertexFF); m->Model.pal_TranspSortAll = new SortModelRenderer(m->Model.VertexPolygonSort); m->Model.pal_NormalShader = new BatchModelRenderer(m->Model.VertexRendererShader); m->Model.pal_NormalInstancingShader = new BatchModelRenderer(m->Model.VertexInstancingShader); m->Model.pal_PlayerShader = new BatchModelRenderer(m->Model.VertexRendererShader); m->Model.pal_PlayerInstancingShader = new BatchModelRenderer(m->Model.VertexInstancingShader); m->Model.pal_TranspShader = new SortModelRenderer(m->Model.VertexRendererShader); m->Model.ModWireframe = RenderModifierPtr(new WireframeRenderModifier); m->Model.ModPlainUnlit = RenderModifierPtr(new PlainRenderModifier); SetFastPlayerColor(true); m->Model.ModSolidColor = RenderModifierPtr(new SolidColorRenderModifier); m->Model.ModSolidPlayerColor = RenderModifierPtr(new SolidPlayerColorRender); m->Model.ModTransparentUnlit = RenderModifierPtr(new TransparentRenderModifier); m->Model.ModTransparentDepthShadow = RenderModifierPtr(new TransparentDepthShadowModifier); // Dimensions m_Width = width; m_Height = height; // set packing parameters glPixelStorei(GL_PACK_ALIGNMENT,1); glPixelStorei(GL_UNPACK_ALIGNMENT,1); // setup default state glDepthFunc(GL_LEQUAL); glEnable(GL_DEPTH_TEST); glCullFace(GL_BACK); glFrontFace(GL_CCW); glEnable(GL_CULL_FACE); GLint bits; glGetIntegerv(GL_DEPTH_BITS,&bits); LOGMESSAGE(L"CRenderer::Open: depth bits %d",bits); glGetIntegerv(GL_STENCIL_BITS,&bits); LOGMESSAGE(L"CRenderer::Open: stencil bits %d",bits); glGetIntegerv(GL_ALPHA_BITS,&bits); LOGMESSAGE(L"CRenderer::Open: alpha bits %d",bits); // Validate the currently selected render path SetRenderPath(m_Options.m_RenderPath); return true; } // resize renderer view void CRenderer::Resize(int width,int height) { // need to recreate the shadow map object to resize the shadow texture m->shadow->RecreateTexture(); m_Width = width; m_Height = height; } ////////////////////////////////////////////////////////////////////////////////////////// // SetOptionBool: set boolean renderer option void CRenderer::SetOptionBool(enum Option opt,bool value) { switch (opt) { case OPT_NOVBO: m_Options.m_NoVBO=value; break; - case OPT_NOFRAMEBUFFEROBJECT: - m_Options.m_NoFramebufferObject=value; - break; case OPT_SHADOWS: m_Options.m_Shadows=value; ReloadShaders(); break; case OPT_FANCYWATER: m_Options.m_FancyWater=value; break; default: debug_warn(L"CRenderer::SetOptionBool: unknown option"); break; } } ////////////////////////////////////////////////////////////////////////////////////////// // GetOptionBool: get boolean renderer option bool CRenderer::GetOptionBool(enum Option opt) const { switch (opt) { case OPT_NOVBO: return m_Options.m_NoVBO; - case OPT_NOFRAMEBUFFEROBJECT: - return m_Options.m_NoFramebufferObject; case OPT_SHADOWS: return m_Options.m_Shadows; case OPT_FANCYWATER: return m_Options.m_FancyWater; default: debug_warn(L"CRenderer::GetOptionBool: unknown option"); break; } return false; } void CRenderer::SetOptionFloat(enum Option opt, float val) { switch(opt) { case OPT_LODBIAS: m_Options.m_LodBias = val; break; default: debug_warn(L"CRenderer::SetOptionFloat: unknown option"); break; } } ////////////////////////////////////////////////////////////////////////////////////////// // SetRenderPath: Select the preferred render path. // This may only be called before Open(), because the layout of vertex arrays and other // data may depend on the chosen render path. void CRenderer::SetRenderPath(RenderPath rp) { if (!m->IsOpen) { // Delay until Open() is called. m_Options.m_RenderPath = rp; return; } // Renderer has been opened, so validate the selected renderpath if (rp == RP_DEFAULT) { if (m_Caps.m_ARBProgram) rp = RP_SHADER; else rp = RP_FIXED; } if (rp == RP_SHADER) { if (!m_Caps.m_ARBProgram) { LOGWARNING(L"Falling back to fixed function\n"); rp = RP_FIXED; } } m_Options.m_RenderPath = rp; // We might need to regenerate some render data after changing path if (g_Game) g_Game->GetWorld()->GetTerrain()->MakeDirty(RENDERDATA_UPDATE_COLOR); } CStr CRenderer::GetRenderPathName(RenderPath rp) { switch(rp) { case RP_DEFAULT: return "default"; case RP_FIXED: return "fixed"; case RP_SHADER: return "shader"; default: return "(invalid)"; } } CRenderer::RenderPath CRenderer::GetRenderPathByName(const CStr& name) { if (name == "fixed") return RP_FIXED; if (name == "shader") return RP_SHADER; if (name == "default") return RP_DEFAULT; LOGWARNING(L"Unknown render path name '%hs', assuming 'default'", name.c_str()); return RP_DEFAULT; } ////////////////////////////////////////////////////////////////////////////////////////// // SetFastPlayerColor void CRenderer::SetFastPlayerColor(bool fast) { m_FastPlayerColor = fast; if (m_FastPlayerColor) { if (!FastPlayerColorRender::IsAvailable()) { LOGWARNING(L"Falling back to slower player color rendering."); m_FastPlayerColor = false; } } if (m_FastPlayerColor) m->Model.ModPlayerUnlit = RenderModifierPtr(new FastPlayerColorRender); else m->Model.ModPlayerUnlit = RenderModifierPtr(new SlowPlayerColorRender); } ////////////////////////////////////////////////////////////////////////////////////////// // BeginFrame: signal frame start void CRenderer::BeginFrame() { PROFILE("begin frame"); // zero out all the per-frame stats m_Stats.Reset(); // choose model renderers for this frame if (m_Options.m_RenderPath == RP_SHADER) { if (m->ShadersDirty) ReloadShaders(); m->Model.ModShaderNormal->SetShadowMap(m->shadow); m->Model.ModShaderNormal->SetLightEnv(m_LightEnv); m->Model.ModShaderNormalInstancing->SetShadowMap(m->shadow); m->Model.ModShaderNormalInstancing->SetLightEnv(m_LightEnv); m->Model.ModShaderPlayer->SetShadowMap(m->shadow); m->Model.ModShaderPlayer->SetLightEnv(m_LightEnv); m->Model.ModShaderPlayerInstancing->SetShadowMap(m->shadow); m->Model.ModShaderPlayerInstancing->SetLightEnv(m_LightEnv); m->Model.ModShaderTransparent->SetShadowMap(m->shadow); m->Model.ModShaderTransparent->SetLightEnv(m_LightEnv); m->Model.ModNormal = m->Model.ModShaderNormal; m->Model.ModNormalInstancing = m->Model.ModShaderNormalInstancing; m->Model.ModPlayer = m->Model.ModShaderPlayer; m->Model.ModPlayerInstancing = m->Model.ModShaderPlayerInstancing; m->Model.ModSolid = m->Model.ModShaderSolidColor; m->Model.ModSolidInstancing = m->Model.ModShaderSolidColorInstancing; m->Model.ModSolidPlayer = m->Model.ModShaderSolidPlayerColor; m->Model.ModSolidPlayerInstancing = m->Model.ModShaderSolidPlayerColorInstancing; m->Model.ModTransparent = m->Model.ModShaderTransparent; m->Model.Normal = m->Model.pal_NormalShader; m->Model.NormalInstancing = m->Model.pal_NormalInstancingShader; m->Model.Player = m->Model.pal_PlayerShader; m->Model.PlayerInstancing = m->Model.pal_PlayerInstancingShader; m->Model.Transp = m->Model.pal_TranspShader; } else { m->Model.ModNormal = m->Model.ModPlainUnlit; m->Model.ModNormalInstancing = m->Model.ModPlainUnlit; m->Model.ModPlayer = m->Model.ModPlayerUnlit; m->Model.ModPlayerInstancing = m->Model.ModPlayerUnlit; m->Model.ModTransparent = m->Model.ModTransparentUnlit; m->Model.NormalInstancing = m->Model.pal_NormalFF; m->Model.Normal = m->Model.pal_NormalFF; m->Model.PlayerInstancing = m->Model.pal_PlayerFF; m->Model.Player = m->Model.pal_PlayerFF; m->Model.ModSolid = m->Model.ModSolidColor; m->Model.ModSolidInstancing = m->Model.ModSolidColor; m->Model.ModSolidPlayer = m->Model.ModSolidPlayerColor; m->Model.ModSolidPlayerInstancing = m->Model.ModSolidPlayerColor; if (m_SortAllTransparent) m->Model.Transp = m->Model.pal_TranspSortAll; else m->Model.Transp = m->Model.pal_TranspFF; } } ////////////////////////////////////////////////////////////////////////////////////////// // SetClearColor: set color used to clear screen in BeginFrame() void CRenderer::SetClearColor(SColor4ub color) { m_ClearColor[0] = float(color.R) / 255.0f; m_ClearColor[1] = float(color.G) / 255.0f; m_ClearColor[2] = float(color.B) / 255.0f; m_ClearColor[3] = float(color.A) / 255.0f; } void CRenderer::RenderShadowMap() { PROFILE("render shadow map"); m->shadow->BeginRender(); float shadowTransp = m_LightEnv->GetTerrainShadowTransparency(); glColor3f(shadowTransp, shadowTransp, shadowTransp); // Figure out transparent rendering strategy RenderModifierPtr transparentShadows; if (GetRenderPath() == RP_SHADER) { transparentShadows = m->Model.ModShaderTransparentShadow; } else { transparentShadows = m->Model.ModTransparentDepthShadow; } // Render all closed models (i.e. models where rendering back faces will produce // the correct result) glCullFace(GL_FRONT); { PROFILE("render patches"); m->terrainRenderer->RenderPatches(); } glCullFace(GL_BACK); // Render models that aren't closed glDisable(GL_CULL_FACE); { PROFILE("render models"); m->CallModelRenderers(m->Model.ModSolid, m->Model.ModSolidInstancing, m->Model.ModSolid, m->Model.ModSolidInstancing, MODELFLAG_CASTSHADOWS); } { PROFILE("render transparent models"); m->Model.Transp->Render(transparentShadows, MODELFLAG_CASTSHADOWS); } glEnable(GL_CULL_FACE); glColor3f(1.0, 1.0, 1.0); m->shadow->EndRender(); } void CRenderer::RenderPatches() { PROFILE("render patches"); // switch on wireframe if we need it if (m_TerrainRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } // render all the patches, including blend pass if (GetRenderPath() == RP_SHADER) m->terrainRenderer->RenderTerrainShader((m_Caps.m_Shadows && m_Options.m_Shadows) ? m->shadow : 0); else m->terrainRenderer->RenderTerrain(); if (m_TerrainRenderMode == WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_TerrainRenderMode == EDGED_FACES) { // edged faces: need to make a second pass over the data: // first switch on wireframe glPolygonMode(GL_FRONT_AND_BACK,GL_LINE); // setup some renderstate .. glDisable(GL_TEXTURE_2D); glColor3f(0.5f, 0.5f, 1.0f); glLineWidth(2.0f); // render tiles edges m->terrainRenderer->RenderPatches(); // set color for outline glColor3f(0, 0, 1); glLineWidth(4.0f); // render outline of each patch m->terrainRenderer->RenderOutlines(); // .. and restore the renderstates glLineWidth(1.0f); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } } void CRenderer::RenderModels() { PROFILE("render models"); if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } m->CallModelRenderers(m->Model.ModNormal, m->Model.ModNormalInstancing, m->Model.ModPlayer, m->Model.ModPlayerInstancing, 0); if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_ModelRenderMode == EDGED_FACES) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); glColor3f(1.0f, 1.0f, 0.0f); m->CallModelRenderers(m->Model.ModSolid, m->Model.ModSolidInstancing, m->Model.ModSolid, m->Model.ModSolidInstancing, 0); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } } void CRenderer::RenderTransparentModels() { PROFILE("render transparent models"); // switch on wireframe if we need it if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } m->Model.Transp->Render(m->Model.ModTransparent, 0); if (m_ModelRenderMode == WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_ModelRenderMode == EDGED_FACES) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); glColor3f(1.0f, 0.0f, 0.0f); m->Model.Transp->Render(m->Model.ModSolid, 0); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } } /////////////////////////////////////////////////////////////////////////////////////////////////// // GetModelViewProjectionMatrix: save the current OpenGL model-view-projection matrix CMatrix3D CRenderer::GetModelViewProjectionMatrix() { CMatrix3D proj; CMatrix3D view; glGetFloatv( GL_PROJECTION_MATRIX, &proj._11 ); glGetFloatv( GL_MODELVIEW_MATRIX, &view._11 ); return( proj * view ); } /////////////////////////////////////////////////////////////////////////////////////////////////// // SetObliqueFrustumClipping: change the near plane to the given clip plane (in world space) // Based on code from Game Programming Gems 5, from http://www.terathon.com/code/oblique.html // - cp is a clip plane in camera space (cp.Dot(v) = 0 for any vector v on the plane) // - sign is 1 or -1, to specify the side to clip on void CRenderer::SetObliqueFrustumClipping(const CVector4D& cp, int sign) { float matrix[16]; CVector4D q; // First, we'll convert the given clip plane to camera space, then we'll // Get the view matrix and normal matrix (top 3x3 part of view matrix) CMatrix3D viewMatrix; m_ViewCamera.m_Orientation.GetInverse(viewMatrix); CMatrix3D normalMatrix = viewMatrix; normalMatrix._14 = 0; normalMatrix._24 = 0; normalMatrix._34 = 0; normalMatrix._44 = 1; normalMatrix._41 = 0; normalMatrix._42 = 0; normalMatrix._43 = 0; // Convert the normal to camera space CVector4D planeNormal(cp.m_X, cp.m_Y, cp.m_Z, 0); planeNormal = normalMatrix.Transform(planeNormal); planeNormal.Normalize(); // Find a point on the plane: we'll take the normal times -D float oldD = cp.m_W; CVector4D pointOnPlane(-oldD * cp.m_X, -oldD * cp.m_Y, -oldD * cp.m_Z, 1); pointOnPlane = viewMatrix.Transform(pointOnPlane); float newD = -pointOnPlane.Dot(planeNormal); // Now create a clip plane from the new normal and new D CVector4D camPlane = planeNormal; camPlane.m_W = newD; // Grab the current projection matrix from OpenGL glGetFloatv(GL_PROJECTION_MATRIX, matrix); // Calculate the clip-space corner point opposite the clipping plane // as (sgn(camPlane.x), sgn(camPlane.y), 1, 1) and // transform it into camera space by multiplying it // by the inverse of the projection matrix q.m_X = (sgn(camPlane.m_X) + matrix[8]) / matrix[0]; q.m_Y = (sgn(camPlane.m_Y) + matrix[9]) / matrix[5]; q.m_Z = -1.0f; q.m_W = (1.0f + matrix[10]) / matrix[14]; // Calculate the scaled plane vector CVector4D c = camPlane * (sign * 2.0f / camPlane.Dot(q)); // Replace the third row of the projection matrix matrix[2] = c.m_X; matrix[6] = c.m_Y; matrix[10] = c.m_Z + 1.0f; matrix[14] = c.m_W; // Load it back into OpenGL glMatrixMode(GL_PROJECTION); glLoadMatrixf(matrix); glMatrixMode(GL_MODELVIEW); } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderReflections: render the water reflections to the reflection texture void CRenderer::RenderReflections() { PROFILE("render reflections"); WaterManager& wm = m->waterManager; // Remember old camera CCamera normalCamera = m_ViewCamera; // Temporarily change the camera to one that is reflected. // Also, for texturing purposes, make it render to a view port the size of the // water texture, stretch the image according to our aspect ratio so it covers // the whole screen despite being rendered into a square, and cover slightly more // of the view so we can see wavy reflections of slightly off-screen objects. m_ViewCamera.m_Orientation.Translate(0, -wm.m_WaterHeight, 0); m_ViewCamera.m_Orientation.Scale(1, -1, 1); m_ViewCamera.m_Orientation.Translate(0, wm.m_WaterHeight, 0); SViewPort vp; vp.m_Height = wm.m_ReflectionTextureSize; vp.m_Width = wm.m_ReflectionTextureSize; vp.m_X = 0; vp.m_Y = 0; m_ViewCamera.SetViewPort(vp); m_ViewCamera.SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV*1.05f); // Slightly higher than view FOV CMatrix3D scaleMat; scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); m_ViewCamera.m_ProjMat = scaleMat * m_ViewCamera.m_ProjMat; m->SetOpenGLCamera(m_ViewCamera); CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight); SetObliqueFrustumClipping(camPlane, -1); // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_ReflectionMatrix = GetModelViewProjectionMatrix(); // Disable backface culling so trees render properly (it might also be possible to flip // the culling direction here, but this seems to lead to problems) glDisable(GL_CULL_FACE); // Make the depth buffer work backwards; there seems to be some oddness with // oblique frustum clipping and the "sign" parameter here glClearDepth(0); glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glDepthFunc(GL_GEQUAL); // Render sky, terrain and models m->skyManager.RenderSky(); ogl_WarnIfError(); RenderPatches(); ogl_WarnIfError(); RenderModels(); ogl_WarnIfError(); RenderTransparentModels(); ogl_WarnIfError(); // Copy the image to a texture pglActiveTextureARB(GL_TEXTURE0_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, wm.m_ReflectionTexture); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, (GLsizei)wm.m_ReflectionTextureSize, (GLsizei)wm.m_ReflectionTextureSize); //Reset old camera and re-enable backface culling m_ViewCamera = normalCamera; m->SetOpenGLCamera(m_ViewCamera); glEnable(GL_CULL_FACE); //glClearDepth(1); //glClear(GL_DEPTH_BUFFER_BIT); //glDepthFunc(GL_LEQUAL); } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderRefractions: render the water refractions to the refraction texture void CRenderer::RenderRefractions() { PROFILE("render refractions"); WaterManager& wm = m->waterManager; // Remember old camera CCamera normalCamera = m_ViewCamera; // Temporarily change the camera to make it render to a view port the size of the // water texture, stretch the image according to our aspect ratio so it covers // the whole screen despite being rendered into a square, and cover slightly more // of the view so we can see wavy refractions of slightly off-screen objects. SViewPort vp; vp.m_Height = wm.m_RefractionTextureSize; vp.m_Width = wm.m_RefractionTextureSize; vp.m_X = 0; vp.m_Y = 0; m_ViewCamera.SetViewPort(vp); m_ViewCamera.SetProjection(CGameView::defaultNear, CGameView::defaultFar, CGameView::defaultFOV*1.05f); // Slightly higher than view FOV CMatrix3D scaleMat; scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); m_ViewCamera.m_ProjMat = scaleMat * m_ViewCamera.m_ProjMat; m->SetOpenGLCamera(m_ViewCamera); CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight); SetObliqueFrustumClipping(camPlane, -1); // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_RefractionMatrix = GetModelViewProjectionMatrix(); // Make the depth buffer work backwards; there seems to be some oddness with // oblique frustum clipping and the "sign" parameter here glClearDepth(0); glClearColor(0.5f, 0.5f, 0.5f, 1.0f); // a neutral gray to blend in with shores glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glDepthFunc(GL_GEQUAL); // Render terrain and models RenderPatches(); ogl_WarnIfError(); RenderModels(); ogl_WarnIfError(); RenderTransparentModels(); ogl_WarnIfError(); // Copy the image to a texture pglActiveTextureARB(GL_TEXTURE0_ARB); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, wm.m_RefractionTexture); glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, (GLsizei)wm.m_RefractionTextureSize, (GLsizei)wm.m_RefractionTextureSize); //Reset old camera and re-enable backface culling m_ViewCamera = normalCamera; m->SetOpenGLCamera(m_ViewCamera); glEnable(GL_CULL_FACE); glClearDepth(1); glDepthFunc(GL_LEQUAL); } void CRenderer::RenderSilhouettes() { PROFILE("render silhouettes"); // Render silhouettes of units hidden behind terrain or occluders. // To avoid breaking the standard rendering of alpha-blended objects, this // has to be done in a separate pass. // First we render all occluders into depth, then render all units with // inverted depth test so any behind an occluder will get drawn in a constant // colour. float silhouetteAlpha = 0.75f; // Silhouette blending requires an almost-universally-supported extension; // fall back to non-blended if unavailable if (!ogl_HaveExtension("GL_EXT_blend_color")) silhouetteAlpha = 1.f; glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); glColorMask(0, 0, 0, 0); // Render occluders: { PROFILE("render patches"); // To prevent units displaying silhouettes when parts of their model // protrude into the ground, only occlude with the back faces of the // terrain (so silhouettes will still display when behind hills) glCullFace(GL_FRONT); m->terrainRenderer->RenderPatches(); glCullFace(GL_BACK); } { PROFILE("render model occluders"); m->CallModelRenderers(m->Model.ModSolid, m->Model.ModSolidInstancing, m->Model.ModSolid, m->Model.ModSolidInstancing, MODELFLAG_SILHOUETTE_OCCLUDER); } { PROFILE("render transparent occluders"); if (GetRenderPath() == RP_SHADER) { glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.4f); m->Model.Transp->Render(m->Model.ModShaderSolidTex, MODELFLAG_SILHOUETTE_OCCLUDER); glDisable(GL_ALPHA_TEST); } else { // Reuse the depth shadow modifier to get alpha-tested rendering m->Model.Transp->Render(m->Model.ModTransparentDepthShadow, MODELFLAG_SILHOUETTE_OCCLUDER); } } glDepthFunc(GL_GEQUAL); glColorMask(1, 1, 1, 1); // Render more efficiently if alpha == 1 if (silhouetteAlpha == 1.f) { // Ideally we'd render objects back-to-front so nearer silhouettes would // appear on top, but sorting has non-zero cost. So we'll keep the depth // write enabled, to do the opposite - far objects will consistently appear // on top. glDepthMask(0); } else { // Since we can't sort, we'll use the stencil buffer to ensure we only draw // a pixel once (using the colour of whatever model happens to be drawn first). glEnable(GL_BLEND); glBlendFunc(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA); pglBlendColorEXT(0, 0, 0, silhouetteAlpha); glEnable(GL_STENCIL_TEST); glStencilFunc(GL_NOTEQUAL, 1, (GLuint)-1); glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE); } // TODO: For performance, we probably ought to do a quick raycasting check // to see which units are likely blocked by occluders and not bother // rendering any of the others { PROFILE("render models"); m->CallModelRenderers(m->Model.ModSolidPlayer, m->Model.ModSolidPlayerInstancing, m->Model.ModSolidPlayer, m->Model.ModSolidPlayerInstancing, MODELFLAG_SILHOUETTE_DISPLAY); // (This won't render transparent objects with SILHOUETTE_DISPLAY - will // we have any units that need that?) } // Restore state glDepthFunc(GL_LEQUAL); if (silhouetteAlpha == 1.f) { glDepthMask(1); } else { glDisable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); pglBlendColorEXT(0, 0, 0, 0); glDisable(GL_STENCIL_TEST); } } void CRenderer::RenderParticles() { // Only supported in shader modes if (GetRenderPath() != RP_SHADER) return; PROFILE("render particles"); m->particleRenderer.RenderParticles(); if (m_ModelRenderMode == EDGED_FACES) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); glColor3f(0.0f, 0.5f, 0.0f); m->particleRenderer.RenderParticles(true); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderSubmissions: force rendering of any batched objects void CRenderer::RenderSubmissions() { PROFILE("render submissions"); ogl_WarnIfError(); // Set the camera m->SetOpenGLCamera(m_ViewCamera); // Prepare model renderers PROFILE_START("prepare models"); m->Model.Normal->PrepareModels(); m->Model.Player->PrepareModels(); if (m->Model.Normal != m->Model.NormalInstancing) m->Model.NormalInstancing->PrepareModels(); if (m->Model.Player != m->Model.PlayerInstancing) m->Model.PlayerInstancing->PrepareModels(); m->Model.Transp->PrepareModels(); PROFILE_END("prepare models"); PROFILE_START("prepare terrain"); m->terrainRenderer->PrepareForRendering(); PROFILE_END("prepare terrain"); PROFILE_START("prepare overlays"); m->overlayRenderer.PrepareForRendering(); PROFILE_END("prepare overlays"); PROFILE_START("prepare particles"); m->particleRenderer.PrepareForRendering(); PROFILE_END("prepare particles"); if (m_Caps.m_Shadows && m_Options.m_Shadows && GetRenderPath() == RP_SHADER) { RenderShadowMap(); } // clear buffers PROFILE_START("clear buffers"); glClearColor(m_ClearColor[0],m_ClearColor[1],m_ClearColor[2],m_ClearColor[3]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); PROFILE_END("clear buffers"); ogl_WarnIfError(); if (m_WaterManager->m_RenderWater && m_WaterManager->WillRenderFancyWater()) { // render reflected and refracted scenes, then re-clear the screen RenderReflections(); RenderRefractions(); glClearColor(m_ClearColor[0],m_ClearColor[1],m_ClearColor[2],m_ClearColor[3]); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); } // render submitted patches and models RenderPatches(); ogl_WarnIfError(); if (g_Game) { // g_Game->GetWorld()->GetTerritoryManager()->RenderTerritories(); // TODO: implement in new sim system ogl_WarnIfError(); } // render debug-related terrain overlays TerrainOverlay::RenderOverlays(); ogl_WarnIfError(); // render other debug-related overlays before water (so they can be displayed when underwater) PROFILE_START("render overlays"); m->overlayRenderer.RenderOverlays(); PROFILE_END("render overlays"); ogl_WarnIfError(); RenderModels(); ogl_WarnIfError(); // render transparent stuff, so it can overlap models/terrain RenderTransparentModels(); ogl_WarnIfError(); // render water if (m_WaterManager->m_RenderWater && g_Game) { m->terrainRenderer->RenderWater(); ogl_WarnIfError(); // render transparent stuff again, so it can overlap the water RenderTransparentModels(); ogl_WarnIfError(); // TODO: Maybe think of a better way to deal with transparent objects; // they can appear both under and above water (seaweed vs. trees), but doing // 2 renders causes (a) inefficiency and (b) darker over-water objects (e.g. // trees) than usual because the transparent bits get overwritten twice. // This doesn't look particularly bad, but it is noticeable if you try // turning the water off. On the other hand every user will have water // on all the time, so it might not be worth worrying about. } // particles are transparent so render after water RenderParticles(); ogl_WarnIfError(); RenderSilhouettes(); // Clean up texture blend mode so particles and other things render OK // (really this should be cleaned up by whoever set it) glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); // render debug lines if (m_DisplayFrustum) { DisplayFrustum(); m->shadow->RenderDebugDisplay(); ogl_WarnIfError(); } // render overlays that should appear on top of all other objects PROFILE_START("render fg overlays"); m->overlayRenderer.RenderForegroundOverlays(m_ViewCamera); PROFILE_END("render fg overlays"); ogl_WarnIfError(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // EndFrame: signal frame end void CRenderer::EndFrame() { PROFILE("end frame"); // empty lists m->terrainRenderer->EndFrame(); m->overlayRenderer.EndFrame(); m->particleRenderer.EndFrame(); // Finish model renderers m->Model.Normal->EndFrame(); m->Model.Player->EndFrame(); if (m->Model.Normal != m->Model.NormalInstancing) m->Model.NormalInstancing->EndFrame(); if (m->Model.Player != m->Model.PlayerInstancing) m->Model.PlayerInstancing->EndFrame(); m->Model.Transp->EndFrame(); ogl_tex_bind(0, 0); if (glGetError()) { ONCE(LOGERROR(L"CRenderer::EndFrame: GL errors occurred")); } } /////////////////////////////////////////////////////////////////////////////////////////////////// // DisplayFrustum: debug displays // - white: cull camera frustum // - red: bounds of shadow casting objects void CRenderer::DisplayFrustum() { glDepthMask(0); glDisable(GL_CULL_FACE); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4ub(255,255,255,64); m_CullCamera.Render(2); glDisable(GL_BLEND); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glColor3ub(255,255,255); m_CullCamera.Render(2); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glEnable(GL_CULL_FACE); glDepthMask(1); } /////////////////////////////////////////////////////////////////////////////////////////////////// // Text overlay rendering void CRenderer::RenderTextOverlays() { PROFILE("render text overlays"); if (m_DisplayTerrainPriorities) m->terrainRenderer->RenderPriorities(); ogl_WarnIfError(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // SetSceneCamera: setup projection and transform of camera and adjust viewport to current view // The camera always represents the actual camera used to render a scene, not any virtual camera // used for shadow rendering or reflections. void CRenderer::SetSceneCamera(const CCamera& viewCamera, const CCamera& cullCamera) { m_ViewCamera = viewCamera; m_CullCamera = cullCamera; if (m_Caps.m_Shadows && m_Options.m_Shadows && GetRenderPath() == RP_SHADER) m->shadow->SetupFrame(m_CullCamera, m_LightEnv->GetSunDir()); } void CRenderer::SetViewport(const SViewPort &vp) { glViewport((GLint)vp.m_X,(GLint)vp.m_Y,(GLsizei)vp.m_Width,(GLsizei)vp.m_Height); } void CRenderer::Submit(CPatch* patch) { m->terrainRenderer->Submit(patch); } void CRenderer::Submit(SOverlayLine* overlay) { m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlaySprite* overlay) { m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(CModelDecal* decal) { m->terrainRenderer->Submit(decal); } void CRenderer::Submit(CParticleEmitter* emitter) { m->particleRenderer.Submit(emitter); } void CRenderer::SubmitNonRecursive(CModel* model) { if (model->GetFlags() & MODELFLAG_CASTSHADOWS) { // PROFILE( "updating shadow bounds" ); m->shadow->AddShadowedBound(model->GetBounds()); } // Tricky: The call to GetBounds() above can invalidate the position model->ValidatePosition(); bool canUseInstancing = false; if (model->GetModelDef()->GetNumBones() == 0) canUseInstancing = true; if (model->GetMaterial().IsPlayer()) { if (canUseInstancing) m->Model.PlayerInstancing->Submit(model); else m->Model.Player->Submit(model); } else if (model->GetMaterial().UsesAlpha()) { m->Model.Transp->Submit(model); } else { if (canUseInstancing) m->Model.NormalInstancing->Submit(model); else m->Model.Normal->Submit(model); } } /////////////////////////////////////////////////////////// // Render the given scene void CRenderer::RenderScene(Scene& scene) { m_CurrentScene = &scene; CFrustum frustum = m_CullCamera.GetFrustum(); scene.EnumerateObjects(frustum, this); m->particleManager.RenderSubmit(*this, frustum); ogl_WarnIfError(); RenderSubmissions(); m_CurrentScene = NULL; } Scene& CRenderer::GetScene() { debug_assert(m_CurrentScene); return *m_CurrentScene; } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // BindTexture: bind a GL texture object to current active unit void CRenderer::BindTexture(int unit,GLuint tex) { pglActiveTextureARB(GL_TEXTURE0+unit); glBindTexture(GL_TEXTURE_2D,tex); if (tex) { glEnable(GL_TEXTURE_2D); } else { glDisable(GL_TEXTURE_2D); } } static inline void CopyTriple(unsigned char* dst,const unsigned char* src) { dst[0]=src[0]; dst[1]=src[1]; dst[2]=src[2]; } /////////////////////////////////////////////////////////////////////////////////////////////////// // LoadAlphaMaps: load the 14 default alpha maps, pack them into one composite texture and // calculate the coordinate of each alphamap within this packed texture int CRenderer::LoadAlphaMaps() { const wchar_t* const key = L"(alpha map composite)"; Handle ht = ogl_tex_find(key); // alpha map texture had already been created and is still in memory: // reuse it, do not load again. if(ht > 0) { m_hCompositeAlphaMap = ht; return 0; } // // load all textures and store Handle in array // Handle textures[NumAlphaMaps] = {0}; VfsPath path(L"art/textures/terrain/alphamaps/standard"); const wchar_t* fnames[NumAlphaMaps] = { L"blendcircle.png", L"blendlshape.png", L"blendedge.png", L"blendedgecorner.png", L"blendedgetwocorners.png", L"blendfourcorners.png", L"blendtwooppositecorners.png", L"blendlshapecorner.png", L"blendtwocorners.png", L"blendcorner.png", L"blendtwoedges.png", L"blendthreecorners.png", L"blendushape.png", L"blendbad.png" }; size_t base = 0; // texture width/height (see below) // for convenience, we require all alpha maps to be of the same BPP // (avoids another ogl_tex_get_size call, and doesn't hurt) size_t bpp = 0; for(size_t i=0;i data = io_Allocate(total_w*total_h*3); // for each tile on row for(size_t i=0;i(param); // If an alpha map changed, and we already loaded them, then reload them if (boost::algorithm::starts_with(path.string(), L"art/textures/terrain/alphamaps/")) { if (renderer->m_hCompositeAlphaMap) { renderer->UnloadAlphaMaps(); renderer->LoadAlphaMaps(); } } return INFO::OK; } void CRenderer::MakeShadersDirty() { m->ShadersDirty = true; } /////////////////////////////////////////////////////////////////////////////////////////////////// // Scripting Interface jsval CRenderer::JSI_GetFastPlayerColor(JSContext*) { return ToJSVal(m_FastPlayerColor); } void CRenderer::JSI_SetFastPlayerColor(JSContext* ctx, jsval newval) { bool fast; if (!ToPrimitive(ctx, newval, fast)) return; SetFastPlayerColor(fast); } jsval CRenderer::JSI_GetRenderPath(JSContext*) { return ToJSVal(GetRenderPathName(m_Options.m_RenderPath)); } void CRenderer::JSI_SetRenderPath(JSContext* ctx, jsval newval) { CStr name; if (!ToPrimitive(ctx, newval, name)) return; SetRenderPath(GetRenderPathByName(name)); } jsval CRenderer::JSI_GetDepthTextureBits(JSContext*) { return ToJSVal(m->shadow->GetDepthTextureBits()); } void CRenderer::JSI_SetDepthTextureBits(JSContext* ctx, jsval newval) { int depthTextureBits; if (!ToPrimitive(ctx, newval, depthTextureBits)) return; m->shadow->SetDepthTextureBits(depthTextureBits); } jsval CRenderer::JSI_GetShadows(JSContext*) { return ToJSVal(m_Options.m_Shadows); } void CRenderer::JSI_SetShadows(JSContext* ctx, jsval newval) { if (!ToPrimitive(ctx, newval, m_Options.m_Shadows)) return; ReloadShaders(); } jsval CRenderer::JSI_GetShadowAlphaFix(JSContext*) { return ToJSVal(m_Options.m_ShadowAlphaFix); } void CRenderer::JSI_SetShadowAlphaFix(JSContext* ctx, jsval newval) { if (!ToPrimitive(ctx, newval, m_Options.m_ShadowAlphaFix)) return; m->shadow->RecreateTexture(); } jsval CRenderer::JSI_GetSky(JSContext*) { return ToJSVal(m->skyManager.GetSkySet()); } void CRenderer::JSI_SetSky(JSContext* ctx, jsval newval) { CStrW skySet; if (!ToPrimitive(ctx, newval, skySet)) return; m->skyManager.SetSkySet(skySet); } void CRenderer::ScriptingInit() { AddProperty(L"fastPlayerColor", &CRenderer::JSI_GetFastPlayerColor, &CRenderer::JSI_SetFastPlayerColor); AddProperty(L"renderpath", &CRenderer::JSI_GetRenderPath, &CRenderer::JSI_SetRenderPath); AddProperty(L"sortAllTransparent", &CRenderer::m_SortAllTransparent); AddProperty(L"displayFrustum", &CRenderer::m_DisplayFrustum); AddProperty(L"shadowZBias", &CRenderer::m_ShadowZBias); AddProperty(L"shadowMapSize", &CRenderer::m_ShadowMapSize); AddProperty(L"disableCopyShadow", &CRenderer::m_DisableCopyShadow); AddProperty(L"shadows", &CRenderer::JSI_GetShadows, &CRenderer::JSI_SetShadows); AddProperty(L"depthTextureBits", &CRenderer::JSI_GetDepthTextureBits, &CRenderer::JSI_SetDepthTextureBits); AddProperty(L"shadowAlphaFix", &CRenderer::JSI_GetShadowAlphaFix, &CRenderer::JSI_SetShadowAlphaFix); AddProperty(L"skipSubmit", &CRenderer::m_SkipSubmit); AddProperty(L"skySet", &CRenderer::JSI_GetSky, &CRenderer::JSI_SetSky); CJSObject::ScriptingInit("Renderer"); } CTextureManager& CRenderer::GetTextureManager() { return m->textureManager; } CShaderManager& CRenderer::GetShaderManager() { return m->shaderManager; } CParticleManager& CRenderer::GetParticleManager() { return m->particleManager; } Index: ps/trunk/source/renderer/ShadowMap.cpp =================================================================== --- ps/trunk/source/renderer/ShadowMap.cpp (revision 9188) +++ ps/trunk/source/renderer/ShadowMap.cpp (revision 9189) @@ -1,630 +1,597 @@ /* Copyright (C) 2009 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. 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. * * 0 A.D. 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 0 A.D. If not, see . */ /* * Shadow mapping related texture and matrix management */ #include "precompiled.h" #include "lib/bits.h" #include "lib/ogl.h" #include "ps/CLogger.h" #include "ps/Profile.h" #include "graphics/LightEnv.h" #include "maths/Bound.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" #include "renderer/Renderer.h" #include "renderer/ShadowMap.h" /////////////////////////////////////////////////////////////////////////////////////////////////// // ShadowMap implementation /** * Struct ShadowMapInternals: Internal data for the ShadowMap implementation */ struct ShadowMapInternals { // bit depth for the depth texture int DepthTextureBits; - // if non-zero, we're using EXT_framebuffer_object for shadow rendering, - // and this is the framebuffer + // the EXT_framebuffer_object framebuffer GLuint Framebuffer; // handle of shadow map GLuint Texture; // width, height of shadow map int Width, Height; // used width, height of shadow map int EffectiveWidth, EffectiveHeight; // transform light space into projected light space // in projected light space, the shadowbound box occupies the [-1..1] cube // calculated on BeginRender, after the final shadow bounds are known CMatrix3D LightProjection; // Transform world space into light space; calculated on SetupFrame CMatrix3D LightTransform; // Transform world space into texture space of the shadow map; // calculated on BeginRender, after the final shadow bounds are known CMatrix3D TextureMatrix; // transform light space into world space CMatrix3D InvLightTransform; // bounding box of shadowed objects in light space CBound ShadowBound; // Camera transformed into light space CCamera LightspaceCamera; // Some drivers (at least some Intel Mesa ones) appear to handle alpha testing // incorrectly when the FBO has only a depth attachment. // When m_ShadowAlphaFix is true, we use DummyTexture to store a useless // alpha texture which is attached to the FBO as a workaround. GLuint DummyTexture; // Helper functions void CalcShadowMatrices(); void CreateTexture(); }; /////////////////////////////////////////////////////////////////////////////////////////////////// // Construction/Destruction ShadowMap::ShadowMap() { m = new ShadowMapInternals; m->Framebuffer = 0; m->Texture = 0; m->DummyTexture = 0; m->Width = 0; m->Height = 0; m->EffectiveWidth = 0; m->EffectiveHeight = 0; m->DepthTextureBits = 0; // DepthTextureBits: 24/32 are very much faster than 16, on GeForce 4 and FX; // but they're very much slower on Radeon 9800. // In both cases, the default (no specified depth) is fast, so we just use // that by default and hope it's alright. (Otherwise, we'd probably need to // do some kind of hardware detection to work out what to use.) // Avoid using uninitialised values in AddShadowedBound if SetupFrame wasn't called first m->LightTransform.SetIdentity(); } ShadowMap::~ShadowMap() { if (m->Texture) glDeleteTextures(1, &m->Texture); if (m->DummyTexture) glDeleteTextures(1, &m->DummyTexture); if (m->Framebuffer) pglDeleteFramebuffersEXT(1, &m->Framebuffer); delete m; } /////////////////////////////////////////////////////////////////////////////////////////////////// // Force the texture/buffer/etc to be recreated, particularly when the renderer's // size has changed void ShadowMap::RecreateTexture() { if (m->Texture) glDeleteTextures(1, &m->Texture); if (m->DummyTexture) glDeleteTextures(1, &m->DummyTexture); if (m->Framebuffer) pglDeleteFramebuffersEXT(1, &m->Framebuffer); m->Texture = 0; m->DummyTexture = 0; m->Framebuffer = 0; // (Texture will be constructed in next SetupFrame) } ////////////////////////////////////////////////////////////////////////////// // SetupFrame: camera and light direction for this frame void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir) { if (!m->Texture) m->CreateTexture(); CVector3D z = lightdir; CVector3D y; CVector3D x = camera.m_Orientation.GetIn(); CVector3D eyepos = camera.m_Orientation.GetTranslation(); z.Normalize(); x -= z * z.Dot(x); if (x.Length() < 0.001) { // this is invoked if the camera and light directions almost coincide // assumption: light direction has a significant Z component x = CVector3D(1.0, 0.0, 0.0); x -= z * z.Dot(x); } x.Normalize(); y = z.Cross(x); // X axis perpendicular to light direction, flowing along with view direction m->LightTransform._11 = x.X; m->LightTransform._12 = x.Y; m->LightTransform._13 = x.Z; // Y axis perpendicular to light and view direction m->LightTransform._21 = y.X; m->LightTransform._22 = y.Y; m->LightTransform._23 = y.Z; // Z axis is in direction of light m->LightTransform._31 = z.X; m->LightTransform._32 = z.Y; m->LightTransform._33 = z.Z; // eye is at the origin of the coordinate system m->LightTransform._14 = -x.Dot(eyepos); m->LightTransform._24 = -y.Dot(eyepos); m->LightTransform._34 = -z.Dot(eyepos); m->LightTransform._41 = 0.0; m->LightTransform._42 = 0.0; m->LightTransform._43 = 0.0; m->LightTransform._44 = 1.0; m->LightTransform.GetInverse(m->InvLightTransform); m->ShadowBound.SetEmpty(); // m->LightspaceCamera = camera; m->LightspaceCamera.m_Orientation = m->LightTransform * camera.m_Orientation; m->LightspaceCamera.UpdateFrustum(); } ////////////////////////////////////////////////////////////////////////////// // AddShadowedBound: add a world-space bounding box to the bounds of shadowed // objects void ShadowMap::AddShadowedBound(const CBound& bounds) { CBound lightspacebounds; bounds.Transform(m->LightTransform, lightspacebounds); m->ShadowBound += lightspacebounds; } /////////////////////////////////////////////////////////////////////////////////////////////////// // CalcShadowMatrices: calculate required matrices for shadow map generation - the light's // projection and transformation matrices void ShadowMapInternals::CalcShadowMatrices() { CRenderer& renderer = g_Renderer; float minZ = ShadowBound[0].Z; ShadowBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum()); // minimum Z bound must not be clipped too much, because objects that lie outside // the shadow bounds cannot cast shadows either // the 2.0 is rather arbitrary: it should be big enough so that we won't accidently miss // a shadow generator, and small enough not to affect Z precision ShadowBound[0].Z = minZ - 2.0; // Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering CVector3D scale = ShadowBound[1] - ShadowBound[0]; CVector3D shift = (ShadowBound[1] + ShadowBound[0]) * -0.5; if (scale.X < 1.0) scale.X = 1.0; if (scale.Y < 1.0) scale.Y = 1.0; if (scale.Z < 1.0) scale.Z = 1.0; scale.X = 2.0 / scale.X; scale.Y = 2.0 / scale.Y; scale.Z = 2.0 / scale.Z; LightProjection.SetZero(); LightProjection._11 = scale.X; LightProjection._14 = shift.X * scale.X; LightProjection._22 = scale.Y; LightProjection._24 = shift.Y * scale.Y; LightProjection._33 = scale.Z; LightProjection._34 = shift.Z * scale.Z + renderer.m_ShadowZBias; LightProjection._44 = 1.0; // Calculate texture matrix by creating the clip space to texture coordinate matrix // and then concatenating all matrices that have been calculated so far CMatrix3D lightToTex; float texscalex = (float)EffectiveWidth / (float)Width; float texscaley = (float)EffectiveHeight / (float)Height; float texscalez = 1.0; texscalex = texscalex / (ShadowBound[1].X - ShadowBound[0].X); texscaley = texscaley / (ShadowBound[1].Y - ShadowBound[0].Y); texscalez = texscalez / (ShadowBound[1].Z - ShadowBound[0].Z); lightToTex.SetZero(); lightToTex._11 = texscalex; lightToTex._14 = -ShadowBound[0].X * texscalex; lightToTex._22 = texscaley; lightToTex._24 = -ShadowBound[0].Y * texscaley; lightToTex._33 = texscalez; lightToTex._34 = -ShadowBound[0].Z * texscalez; lightToTex._44 = 1.0; TextureMatrix = lightToTex * LightTransform; } ////////////////////////////////////////////////////////////////////////// // Create the shadow map void ShadowMapInternals::CreateTexture() { // Cleanup if (Texture) { glDeleteTextures(1, &Texture); Texture = 0; } if (DummyTexture) { glDeleteTextures(1, &DummyTexture); DummyTexture = 0; } if (Framebuffer) { pglDeleteFramebuffersEXT(1, &Framebuffer); Framebuffer = 0; } - // Prepare FBO if available - // Note: luminance is not an RGB format, so a luminance texture cannot be used - // as a color buffer - if (g_Renderer.GetCapabilities().m_FramebufferObject) - { - pglGenFramebuffersEXT(1, &Framebuffer); - } + pglGenFramebuffersEXT(1, &Framebuffer); if (g_Renderer.m_ShadowMapSize != 0) { // non-default option to override the size Width = Height = g_Renderer.m_ShadowMapSize; } else { // get shadow map size as next power of two up from view width and height Width = (int)round_up_to_pow2((unsigned)g_Renderer.GetWidth()); Height = (int)round_up_to_pow2((unsigned)g_Renderer.GetHeight()); } // Clamp to the maximum texture size Width = std::min(Width, (int)ogl_max_tex_size); Height = std::min(Height, (int)ogl_max_tex_size); - // If we're using a framebuffer object, the whole texture is available; otherwise - // we're limited to the part of the screen buffer that is actually visible - if (Framebuffer) - { - EffectiveWidth = Width; - EffectiveHeight = Height; - } - else - { - EffectiveWidth = std::min(Width, g_Renderer.GetWidth()); - EffectiveHeight = std::min(Height, g_Renderer.GetHeight()); - } + // Since we're using a framebuffer object, the whole texture is available + EffectiveWidth = Width; + EffectiveHeight = Height; const char* formatname; switch(DepthTextureBits) { case 16: formatname = "DEPTH_COMPONENT16"; break; case 24: formatname = "DEPTH_COMPONENT24"; break; case 32: formatname = "DEPTH_COMPONENT32"; break; default: formatname = "DEPTH_COMPONENT"; break; } - LOGMESSAGE(L"Creating shadow texture (size %dx%d) (format = %hs)%ls", - Width, Height, formatname, Framebuffer ? L" (using EXT_framebuffer_object)" : L""); + LOGMESSAGE(L"Creating shadow texture (size %dx%d) (format = %hs)", + Width, Height, formatname); if (g_Renderer.m_Options.m_ShadowAlphaFix) { glGenTextures(1, &DummyTexture); g_Renderer.BindTexture(0, DummyTexture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); } glGenTextures(1, &Texture); g_Renderer.BindTexture(0, Texture); GLenum format; switch(DepthTextureBits) { case 16: format = GL_DEPTH_COMPONENT16; break; case 24: format = GL_DEPTH_COMPONENT24; break; case 32: format = GL_DEPTH_COMPONENT32; break; default: format = GL_DEPTH_COMPONENT; break; } glTexImage2D(GL_TEXTURE_2D, 0, format, Width, Height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, NULL); glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_LUMINANCE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL); // set texture parameters glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER, GL_LINEAR); // bind to framebuffer object - if (Framebuffer) - { - glBindTexture(GL_TEXTURE_2D, 0); - pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, Framebuffer); + glBindTexture(GL_TEXTURE_2D, 0); + pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, Framebuffer); - pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, Texture, 0); + pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, Texture, 0); - if (g_Renderer.m_Options.m_ShadowAlphaFix) - { - pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, DummyTexture, 0); - } - else - { - glDrawBuffer(GL_NONE); - } + if (g_Renderer.m_Options.m_ShadowAlphaFix) + { + pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, DummyTexture, 0); + } + else + { + glDrawBuffer(GL_NONE); + } - glReadBuffer(GL_NONE); + glReadBuffer(GL_NONE); - GLenum status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); + GLenum status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); - pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); + pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); - if (status != GL_FRAMEBUFFER_COMPLETE_EXT) - { - LOGWARNING(L"Framebuffer object incomplete: %04d", status); + if (status != GL_FRAMEBUFFER_COMPLETE_EXT) + { + LOGWARNING(L"Framebuffer object incomplete: %04d", status); - pglDeleteFramebuffersEXT(1, &Framebuffer); - Framebuffer = 0; - EffectiveWidth = std::min(Width, g_Renderer.GetWidth()); - EffectiveHeight = std::min(Height, g_Renderer.GetHeight()); - } + // Disable shadow rendering (but let the user try again if they want) + g_Renderer.m_Options.m_Shadows = false; } } /////////////////////////////////////////////////////////////////////////////////////////////////// // Set up to render into shadow map texture void ShadowMap::BeginRender() { // HACK HACK: this depends in non-obvious ways on the behaviour of the caller // Calc remaining shadow matrices m->CalcShadowMatrices(); - if (m->Framebuffer) { PROFILE("bind framebuffer"); glBindTexture(GL_TEXTURE_2D, 0); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m->Framebuffer); } // clear buffers { PROFILE("clear depth texture"); glClear(GL_DEPTH_BUFFER_BIT); glColorMask(0,0,0,0); } // setup viewport glViewport(0, 0, m->EffectiveWidth, m->EffectiveHeight); glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadMatrixf(&m->LightProjection._11); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadMatrixf(&m->LightTransform._11); glEnable(GL_SCISSOR_TEST); glScissor(1,1, m->EffectiveWidth-2, m->EffectiveHeight-2); } /////////////////////////////////////////////////////////////////////////////////////////////////// // Finish rendering into shadow map texture void ShadowMap::EndRender() { glDisable(GL_SCISSOR_TEST); - // copy result into shadow map texture - if (m->Framebuffer) { PROFILE("unbind framebuffer"); pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); } - else - { - if (!g_Renderer.GetDisableCopyShadow()) - { - PROFILE("copy shadow texture"); - g_Renderer.BindTexture(0, m->Texture); - glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, m->EffectiveWidth, m->EffectiveHeight); - } - } glViewport(0, 0, g_Renderer.GetWidth(), g_Renderer.GetHeight()); glColorMask(1,1,1,1); // restore matrix stack glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); } /////////////////////////////////////////////////////////////////////////////////////////////////// // Retrieve the texture handle and texture matrix for shadowing GLuint ShadowMap::GetTexture() const { return m->Texture; } const CMatrix3D& ShadowMap::GetTextureMatrix() const { return m->TextureMatrix; } /////////////////////////////////////////////////////////////////////////////////////////////////// // Depth texture bits int ShadowMap::GetDepthTextureBits() const { return m->DepthTextureBits; } void ShadowMap::SetDepthTextureBits(int bits) { if (bits != m->DepthTextureBits) { if (m->Texture) { glDeleteTextures(1, &m->Texture); m->Texture = 0; } m->Width = m->Height = 0; m->DepthTextureBits = bits; } } ////////////////////////////////////////////////////////////////////////////// // RenderDebugDisplay: debug visualizations // - blue: objects in shadow void ShadowMap::RenderDebugDisplay() { glDepthMask(0); glDisable(GL_CULL_FACE); // Render shadow bound glMatrixMode(GL_MODELVIEW); glPushMatrix(); glMultMatrixf(&m->InvLightTransform._11); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4ub(0,0,255,64); m->ShadowBound.Render(); glDisable(GL_BLEND); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glColor3ub(0,0,255); m->ShadowBound.Render(); glBegin(GL_LINES); glVertex3f(0.0, 0.0, 0.0); glVertex3f(0.0, 0.0, 50.0); glEnd(); glBegin(GL_POLYGON); glVertex3f(0.0, 0.0, 50.0); glVertex3f(50.0, 0.0, 50.0); glVertex3f(0.0, 50.0, 50.0); glEnd(); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glPopMatrix(); #if 0 CMatrix3D InvTexTransform; m->TextureMatrix.GetInverse(InvTexTransform); // Render representative texture rectangle glPushMatrix(); glMultMatrixf(&InvTexTransform._11); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glColor4ub(255,0,0,64); glBegin(GL_QUADS); glVertex3f(0.0, 0.0, 0.0); glVertex3f(1.0, 0.0, 0.0); glVertex3f(1.0, 1.0, 0.0); glVertex3f(0.0, 1.0, 0.0); glEnd(); glDisable(GL_BLEND); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glColor3ub(255,0,0); glBegin(GL_QUADS); glVertex3f(0.0, 0.0, 0.0); glVertex3f(1.0, 0.0, 0.0); glVertex3f(1.0, 1.0, 0.0); glVertex3f(0.0, 1.0, 0.0); glEnd(); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); glPopMatrix(); #endif // Render the shadow map glMatrixMode(GL_PROJECTION); glPushMatrix(); glLoadIdentity(); glOrtho(0.0, 1.0, 1.0, 0.0, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glPushMatrix(); glLoadIdentity(); glDisable(GL_DEPTH_TEST); g_Renderer.BindTexture(0, m->Texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE); glColor3f(1.0f, 1.0f, 1.0f); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex2f(0.0f, 0.0f); glTexCoord2f(1.0f, 0.0f); glVertex2f(0.2f, 0.0f); glTexCoord2f(1.0f, 1.0f); glVertex2f(0.2f, 0.2f); glTexCoord2f(0.0f, 1.0f); glVertex2f(0.0f, 0.2f); glEnd(); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE); glEnable(GL_CULL_FACE); glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopMatrix(); glEnable(GL_DEPTH_TEST); glDepthMask(1); }