/* Copyright (C) 2014 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 "lib/allocators/shared_ptr.h" #include "maths/Matrix3D.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/ConfigDB.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/Decal.h" #include "graphics/FontManager.h" #include "graphics/GameView.h" #include "graphics/LightEnv.h" #include "graphics/LOSTexture.h" #include "graphics/MaterialManager.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "graphics/ParticleManager.h" #include "graphics/Patch.h" #include "graphics/ShaderManager.h" #include "graphics/Terrain.h" #include "graphics/Texture.h" #include "graphics/TextureManager.h" #include "renderer/HWLightingModelRenderer.h" #include "renderer/InstancingModelRenderer.h" #include "renderer/ModelRenderer.h" #include "renderer/OverlayRenderer.h" #include "renderer/ParticleRenderer.h" #include "renderer/PostprocManager.h" #include "renderer/RenderModifiers.h" #include "renderer/ShadowMap.h" #include "renderer/SilhouetteRenderer.h" #include "renderer/SkyManager.h" #include "renderer/TerrainOverlay.h" #include "renderer/TerrainRenderer.h" #include "renderer/TimeManager.h" #include "renderer/VertexBufferManager.h" #include "renderer/WaterManager.h" #include "scriptinterface/ScriptInterface.h" extern bool g_GameRestarted; struct SScreenRect { GLint x1, y1, x2, y2; }; /////////////////////////////////////////////////////////////////////////////////// // 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_DrawCalls = 0, Row_TerrainTris, Row_WaterTris, Row_ModelTris, Row_OverlayTris, Row_BlendSplats, Row_Particles, Row_VBReserved, Row_VBAllocated, Row_TextureMemory, Row_ShadersLoaded, // 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_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_WaterTris: if (col == 0) return "# water tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_WaterTris); 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_OverlayTris: if (col == 0) return "# overlay tris"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_OverlayTris); 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_Particles: if (col == 0) return "# particles"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_Particles); return buf; case Row_VBReserved: if (col == 0) return "VB reserved"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesReserved() / 1024); return buf; case Row_VBAllocated: if (col == 0) return "VB allocated"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_VBMan.GetBytesAllocated() / 1024); return buf; case Row_TextureMemory: if (col == 0) return "textures uploaded"; sprintf_s(buf, sizeof(buf), "%lu kB", (unsigned long)g_Renderer.GetTextureManager().GetBytesUploaded() / 1024); return buf; case Row_ShadersLoaded: if (col == 0) return "shader effects loaded"; sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)g_Renderer.GetShaderManager().GetNumEffectsLoaded()); 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; /// Material manager CMaterialManager materialManager; /// Time manager CTimeManager timeManager; /// Shadow map ShadowMap shadow; /// Postprocessing effect manager CPostprocManager postprocManager; CFontManager fontManager; SilhouetteRenderer silhouetteRenderer; /// Various model renderers struct Models { // NOTE: The current renderer design (with ModelRenderer, ModelVertexRenderer, // RenderModifier, etc) is mostly a relic of an older design that implemented // the different materials and rendering modes through extensive subclassing // and hooking objects together in various combinations. // The new design uses the CShaderManager API to abstract away the details // of rendering, and uses a data-driven approach to materials, so there are // now a small number of generic subclasses instead of many specialised subclasses, // but most of the old infrastructure hasn't been refactored out yet and leads to // some unwanted complexity. // Submitted models are split on two axes: // - Normal vs Transp[arent] - alpha-blended models are stored in a separate // list so we can draw them above/below the alpha-blended water plane correctly // - Skinned vs Unskinned - with hardware lighting we don't need to // duplicate mesh data per model instance (except for skinned models), // so non-skinned models get different ModelVertexRenderers ModelRendererPtr NormalSkinned; ModelRendererPtr NormalUnskinned; // == NormalSkinned if unskinned shader instancing not supported ModelRendererPtr TranspSkinned; ModelRendererPtr TranspUnskinned; // == TranspSkinned if unskinned shader instancing not supported ModelVertexRendererPtr VertexRendererShader; ModelVertexRendererPtr VertexInstancingShader; ModelVertexRendererPtr VertexGPUSkinningShader; LitRenderModifierPtr ModShader; } Model; CShaderDefines globalContext; CRendererInternals() : IsOpen(false), ShadersDirty(true), profileTable(g_Renderer.m_Stats), textureManager(g_VFS, false, false) { } /** * 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(); #if CONFIG2_GLES #warning TODO: fix CRenderer camera handling for GLES (do not use global matrixes) #else glMatrixMode(GL_PROJECTION); glLoadMatrixf(&proj._11); glMatrixMode(GL_MODELVIEW); glLoadMatrixf(&view._11); #endif g_Renderer.SetViewport(camera.GetViewPort()); } /** * Renders all non-alpha-blended models with the given context. */ void CallModelRenderers(const CShaderDefines& context, int cullGroup, int flags) { CShaderDefines contextSkinned = context; if (g_Renderer.m_Options.m_GPUSkinning) { contextSkinned.Add(str_USE_INSTANCING, str_1); contextSkinned.Add(str_USE_GPU_SKINNING, str_1); } Model.NormalSkinned->Render(Model.ModShader, contextSkinned, cullGroup, flags); if (Model.NormalUnskinned != Model.NormalSkinned) { CShaderDefines contextUnskinned = context; contextUnskinned.Add(str_USE_INSTANCING, str_1); Model.NormalUnskinned->Render(Model.ModShader, contextUnskinned, cullGroup, flags); } } /** * Renders all alpha-blended models with the given context. */ void CallTranspModelRenderers(const CShaderDefines& context, int cullGroup, int flags) { CShaderDefines contextSkinned = context; if (g_Renderer.m_Options.m_GPUSkinning) { contextSkinned.Add(str_USE_INSTANCING, str_1); contextSkinned.Add(str_USE_GPU_SKINNING, str_1); } Model.TranspSkinned->Render(Model.ModShader, contextSkinned, cullGroup, flags); if (Model.TranspUnskinned != Model.TranspSkinned) { CShaderDefines contextUnskinned = context; contextUnskinned.Add(str_USE_INSTANCING, str_1); Model.TranspUnskinned->Render(Model.ModShader, contextUnskinned, cullGroup, 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_DisplayTerrainPriorities = false; m_SkipSubmit = false; m_Options.m_NoVBO = false; m_Options.m_RenderPath = RP_DEFAULT; m_Options.m_Shadows = false; m_Options.m_ShadowAlphaFix = true; m_Options.m_ARBProgramShadow = true; m_Options.m_ShadowPCF = false; m_Options.m_Particles = false; m_Options.m_Silhouettes = false; m_Options.m_PreferGLSL = false; m_Options.m_ForceAlphaTest = false; m_Options.m_GPUSkinning = false; m_Options.m_GenTangents = false; m_Options.m_SmoothLOS = false; m_Options.m_Postproc = false; m_Options.m_ShowSky = false; m_Options.m_DisplayFrustum = false; // TODO: be more consistent in use of the config system CFG_GET_VAL("preferglsl", m_Options.m_PreferGLSL); CFG_GET_VAL("forcealphatest", m_Options.m_ForceAlphaTest); CFG_GET_VAL("gpuskinning", m_Options.m_GPUSkinning); CFG_GET_VAL("gentangents", m_Options.m_GenTangents); CFG_GET_VAL("smoothlos", m_Options.m_SmoothLOS); CFG_GET_VAL("postproc", m_Options.m_Postproc); CStr skystring = "0 0 0"; CColor skycolor; CFG_GET_VAL("skycolor", skystring); if (skycolor.ParseString(skystring, 255.f)) SetClearColor(skycolor.AsSColor4ub()); #if CONFIG2_GLES // Override config option since GLES only supports GLSL m_Options.m_PreferGLSL = true; #endif m_ShadowZBias = 0.02f; m_ShadowMapSize = 0; m_LightEnv = NULL; m_CurrentScene = NULL; m_hCompositeAlphaMap = 0; m_Stats.Reset(); RegisterFileReloadFunc(ReloadChangedFileCB, this); } /////////////////////////////////////////////////////////////////////////////////// // CRenderer destructor CRenderer::~CRenderer() { UnregisterFileReloadFunc(ReloadChangedFileCB, this); // 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_PrettyWater = false; // now start querying extensions if (!m_Options.m_NoVBO && 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 CONFIG2_GLES m_Caps.m_Shadows = true; #else if (0 == ogl_HaveExtensions(0, "GL_ARB_shadow", "GL_ARB_depth_texture", "GL_EXT_framebuffer_object", NULL)) { if (ogl_max_tex_units >= 4) m_Caps.m_Shadows = true; } #endif #if CONFIG2_GLES m_Caps.m_PrettyWater = true; #else if (0 == ogl_HaveExtensions(0, "GL_ARB_vertex_shader", "GL_ARB_fragment_shader", "GL_EXT_framebuffer_object", NULL)) m_Caps.m_PrettyWater = true; #endif } void CRenderer::RecomputeSystemShaderDefines() { CShaderDefines defines; if (GetRenderPath() == RP_SHADER && m_Caps.m_ARBProgram) defines.Add(str_SYS_HAS_ARB, str_1); if (GetRenderPath() == RP_SHADER && m_Caps.m_VertexShader && m_Caps.m_FragmentShader) defines.Add(str_SYS_HAS_GLSL, str_1); if (m_Options.m_PreferGLSL) defines.Add(str_SYS_PREFER_GLSL, str_1); m_SystemShaderDefines = defines; } void CRenderer::ReloadShaders() { ENSURE(m->IsOpen); m->globalContext = m_SystemShaderDefines; if (m_Caps.m_Shadows && m_Options.m_Shadows) { m->globalContext.Add(str_USE_SHADOW, str_1); if (m_Caps.m_ARBProgramShadow && m_Options.m_ARBProgramShadow) m->globalContext.Add(str_USE_FP_SHADOW, str_1); if (m_Options.m_ShadowPCF) m->globalContext.Add(str_USE_SHADOW_PCF, str_1); #if !CONFIG2_GLES m->globalContext.Add(str_USE_SHADOW_SAMPLER, str_1); #endif } if (m_LightEnv) m->globalContext.Add(CStrIntern("LIGHTING_MODEL_" + m_LightEnv->GetLightingModel()), str_1); m->Model.ModShader = LitRenderModifierPtr(new ShaderRenderModifier()); bool cpuLighting = (GetRenderPath() == RP_FIXED); m->Model.VertexRendererShader = ModelVertexRendererPtr(new ShaderModelVertexRenderer(cpuLighting)); m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer(false, m_Options.m_GenTangents)); if (GetRenderPath() == RP_SHADER && m_Options.m_GPUSkinning) // TODO: should check caps and GLSL etc too { m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new InstancingModelRenderer(true, m_Options.m_GenTangents)); m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader)); m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader)); } else { m->Model.VertexGPUSkinningShader.reset(); m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader)); m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexRendererShader)); } // Use instancing renderers in shader mode if (GetRenderPath() == RP_SHADER) { m->Model.NormalUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader)); m->Model.TranspUnskinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexInstancingShader)); } else { m->Model.NormalUnskinned = m->Model.NormalSkinned; m->Model.TranspUnskinned = m->Model.TranspSkinned; } 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(); // 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("CRenderer::Open: depth bits %d",bits); glGetIntegerv(GL_STENCIL_BITS,&bits); LOGMESSAGE("CRenderer::Open: stencil bits %d",bits); glGetIntegerv(GL_ALPHA_BITS,&bits); LOGMESSAGE("CRenderer::Open: alpha bits %d",bits); // Validate the currently selected render path SetRenderPath(m_Options.m_RenderPath); RecomputeSystemShaderDefines(); // Let component renderers perform one-time initialization after graphics capabilities and // the shader path have been determined. m->overlayRenderer.Initialize(); if (m_Options.m_Postproc) m->postprocManager.Initialize(); 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; m->postprocManager.Resize(); m_WaterManager->Resize(); } ////////////////////////////////////////////////////////////////////////////////////////// // 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_SHADOWS: m_Options.m_Shadows = value; MakeShadersDirty(); break; case OPT_WATERUGLY: m_Options.m_WaterUgly = value; break; case OPT_WATERFANCYEFFECTS: m_Options.m_WaterFancyEffects = value; break; case OPT_WATERREALDEPTH: m_Options.m_WaterRealDepth = value; break; case OPT_WATERREFLECTION: m_Options.m_WaterReflection = value; break; case OPT_WATERREFRACTION: m_Options.m_WaterRefraction = value; break; case OPT_SHADOWSONWATER: m_Options.m_WaterShadows = value; break; case OPT_SHADOWPCF: m_Options.m_ShadowPCF = value; MakeShadersDirty(); break; case OPT_PARTICLES: m_Options.m_Particles = value; break; case OPT_GENTANGENTS: m_Options.m_GenTangents = value; break; case OPT_PREFERGLSL: m_Options.m_PreferGLSL = value; MakeShadersDirty(); RecomputeSystemShaderDefines(); break; case OPT_SILHOUETTES: m_Options.m_Silhouettes = value; break; case OPT_SHOWSKY: m_Options.m_ShowSky = value; break; case OPT_SMOOTHLOS: m_Options.m_SmoothLOS = value; break; case OPT_POSTPROC: m_Options.m_Postproc = value; break; case OPT_DISPLAYFRUSTUM: m_Options.m_DisplayFrustum = 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_SHADOWS: return m_Options.m_Shadows; case OPT_WATERUGLY: return m_Options.m_WaterUgly; case OPT_WATERFANCYEFFECTS: return m_Options.m_WaterFancyEffects; case OPT_WATERREALDEPTH: return m_Options.m_WaterRealDepth; case OPT_WATERREFLECTION: return m_Options.m_WaterReflection; case OPT_WATERREFRACTION: return m_Options.m_WaterRefraction; case OPT_SHADOWSONWATER: return m_Options.m_WaterShadows; case OPT_SHADOWPCF: return m_Options.m_ShadowPCF; case OPT_PARTICLES: return m_Options.m_Particles; case OPT_GENTANGENTS: return m_Options.m_GenTangents; case OPT_PREFERGLSL: return m_Options.m_PreferGLSL; case OPT_SILHOUETTES: return m_Options.m_Silhouettes; case OPT_SHOWSKY: return m_Options.m_ShowSky; case OPT_SMOOTHLOS: return m_Options.m_SmoothLOS; case OPT_POSTPROC: return m_Options.m_Postproc; case OPT_DISPLAYFRUSTUM: return m_Options.m_DisplayFrustum; default: debug_warn(L"CRenderer::GetOptionBool: unknown option"); break; } return false; } ////////////////////////////////////////////////////////////////////////////////////////// // 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 || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && m_Options.m_PreferGLSL)) rp = RP_SHADER; else rp = RP_FIXED; } if (rp == RP_SHADER) { if (!(m_Caps.m_ARBProgram || (m_Caps.m_VertexShader && m_Caps.m_FragmentShader && m_Options.m_PreferGLSL))) { LOGWARNING("Falling back to fixed function\n"); rp = RP_FIXED; } } m_Options.m_RenderPath = rp; MakeShadersDirty(); RecomputeSystemShaderDefines(); // 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("Unknown render path name '%s', assuming 'default'", name.c_str()); return RP_DEFAULT; } ////////////////////////////////////////////////////////////////////////////////////////// // 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->ShadersDirty) ReloadShaders(); m->Model.ModShader->SetShadowMap(&m->shadow); m->Model.ModShader->SetLightEnv(m_LightEnv); } ////////////////////////////////////////////////////////////////////////////////////////// void CRenderer::SetSimulation(CSimulation2* simulation) { // set current simulation context for terrain renderer m->terrainRenderer.SetSimulation(simulation); } // 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(const CShaderDefines& context) { PROFILE3_GPU("shadow map"); m->shadow.BeginRender(); { PROFILE("render patches"); glCullFace(GL_FRONT); glEnable(GL_CULL_FACE); m->terrainRenderer.RenderPatches(CULL_SHADOWS); glCullFace(GL_BACK); } CShaderDefines contextCast = context; contextCast.Add(str_MODE_SHADOWCAST, str_1); { PROFILE("render models"); m->CallModelRenderers(contextCast, CULL_SHADOWS, MODELFLAG_CASTSHADOWS); } { PROFILE("render transparent models"); // disable face-culling for two-sided models glDisable(GL_CULL_FACE); m->CallTranspModelRenderers(contextCast, CULL_SHADOWS, MODELFLAG_CASTSHADOWS); glEnable(GL_CULL_FACE); } m->shadow.EndRender(); m->SetOpenGLCamera(m_ViewCamera); } void CRenderer::RenderPatches(const CShaderDefines& context, int cullGroup) { PROFILE3_GPU("patches"); #if CONFIG2_GLES #warning TODO: implement wireface/edged rendering mode GLES #else // switch on wireframe if we need it if (m_TerrainRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } #endif // render all the patches, including blend pass if (GetRenderPath() == RP_SHADER) m->terrainRenderer.RenderTerrainShader(context, cullGroup, (m_Caps.m_Shadows && m_Options.m_Shadows) ? &m->shadow : 0); else m->terrainRenderer.RenderTerrain(cullGroup); #if !CONFIG2_GLES 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 .. pglActiveTextureARB(GL_TEXTURE0); glDisable(GL_TEXTURE_2D); glColor3f(0.5f, 0.5f, 1.0f); glLineWidth(2.0f); // render tiles edges m->terrainRenderer.RenderPatches(cullGroup); // set color for outline glColor3f(0, 0, 1); glLineWidth(4.0f); // render outline of each patch m->terrainRenderer.RenderOutlines(cullGroup); // .. and restore the renderstates glLineWidth(1.0f); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } void CRenderer::RenderModels(const CShaderDefines& context, int cullGroup) { PROFILE3_GPU("models"); int flags = 0; #if !CONFIG2_GLES if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } #endif m->CallModelRenderers(context, cullGroup, flags); #if !CONFIG2_GLES if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_ModelRenderMode == EDGED_FACES) { CShaderDefines contextWireframe = context; contextWireframe.Add(str_MODE_WIREFRAME, str_1); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); m->CallModelRenderers(contextWireframe, cullGroup, flags); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } void CRenderer::RenderTransparentModels(const CShaderDefines& context, int cullGroup, ETransparentMode transparentMode, bool disableFaceCulling) { PROFILE3_GPU("transparent models"); int flags = 0; #if !CONFIG2_GLES // switch on wireframe if we need it if (m_ModelRenderMode == WIREFRAME) { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); } #endif // disable face culling for two-sided models in sub-renders if (disableFaceCulling) glDisable(GL_CULL_FACE); CShaderDefines contextOpaque = context; contextOpaque.Add(str_ALPHABLEND_PASS_OPAQUE, str_1); CShaderDefines contextBlend = context; contextBlend.Add(str_ALPHABLEND_PASS_BLEND, str_1); if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_OPAQUE) m->CallTranspModelRenderers(contextOpaque, cullGroup, flags); if (transparentMode == TRANSPARENT || transparentMode == TRANSPARENT_BLEND) m->CallTranspModelRenderers(contextBlend, cullGroup, flags); if (disableFaceCulling) glEnable(GL_CULL_FACE); #if !CONFIG2_GLES if (m_ModelRenderMode == WIREFRAME) { // switch wireframe off again glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } else if (m_ModelRenderMode == EDGED_FACES) { CShaderDefines contextWireframe = contextOpaque; contextWireframe.Add(str_MODE_WIREFRAME, str_1); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glDisable(GL_TEXTURE_2D); m->CallTranspModelRenderers(contextWireframe, cullGroup, flags); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // 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 // - worldPlane is a clip plane in world space (worldPlane.Dot(v) >= 0 for any vector v passing the clipping test) void CRenderer::SetObliqueFrustumClipping(CCamera& camera, const CVector4D& worldPlane) const { // 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 normalMatrix = camera.m_Orientation.GetTranspose(); CVector4D camPlane = normalMatrix.Transform(worldPlane); CMatrix3D matrix = camera.GetProjection(); // 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 CVector4D q; q.X = (sgn(camPlane.X) - matrix[8]/matrix[11]) / matrix[0]; q.Y = (sgn(camPlane.Y) - matrix[9]/matrix[11]) / matrix[5]; q.Z = 1.0f/matrix[11]; q.W = (1.0f - matrix[10]/matrix[11]) / matrix[14]; // Calculate the scaled plane vector CVector4D c = camPlane * (2.0f * matrix[11] / camPlane.Dot(q)); // Replace the third row of the projection matrix matrix[2] = c.X; matrix[6] = c.Y; matrix[10] = c.Z - matrix[11]; matrix[14] = c.W; // Load it back into the camera camera.SetProjection(matrix); } void CRenderer::ComputeReflectionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const { WaterManager& wm = m->waterManager; float fov = m_ViewCamera.GetFOV(); // Expand fov slightly since ripples can reflect parts of the scene that // are slightly outside the normal camera view, and we want to avoid any // noticeable edge-filtering artifacts fov *= 1.05f; camera = 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. camera.m_Orientation.Scale(1, -1, 1); camera.m_Orientation.Translate(0, 2*wm.m_WaterHeight, 0); camera.UpdateFrustum(scissor); camera.ClipFrustum(CVector4D(0, 1, 0, -wm.m_WaterHeight)); SViewPort vp; vp.m_Height = wm.m_ReflectionTextureSize; vp.m_Width = wm.m_ReflectionTextureSize; vp.m_X = 0; vp.m_Y = 0; camera.SetViewPort(vp); camera.SetProjection(m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane(), fov); CMatrix3D scaleMat; scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); camera.m_ProjMat = scaleMat * camera.m_ProjMat; CVector4D camPlane(0, 1, 0, -wm.m_WaterHeight + 0.5f); SetObliqueFrustumClipping(camera, camPlane); } void CRenderer::ComputeRefractionCamera(CCamera& camera, const CBoundingBoxAligned& scissor) const { WaterManager& wm = m->waterManager; float fov = m_ViewCamera.GetFOV(); // Expand fov slightly since ripples can reflect parts of the scene that // are slightly outside the normal camera view, and we want to avoid any // noticeable edge-filtering artifacts fov *= 1.05f; camera = 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. camera.UpdateFrustum(scissor); camera.ClipFrustum(CVector4D(0, -1, 0, wm.m_WaterHeight + 0.5f)); // add some to avoid artifacts near steep shores. SViewPort vp; vp.m_Height = wm.m_RefractionTextureSize; vp.m_Width = wm.m_RefractionTextureSize; vp.m_X = 0; vp.m_Y = 0; camera.SetViewPort(vp); camera.SetProjection(m_ViewCamera.GetNearPlane(), m_ViewCamera.GetFarPlane(), fov); CMatrix3D scaleMat; scaleMat.SetScaling(m_Height/float(std::max(1, m_Width)), 1.0f, 1.0f); camera.m_ProjMat = scaleMat * camera.m_ProjMat; } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderReflections: render the water reflections to the reflection texture void CRenderer::RenderReflections(const CShaderDefines& context, const CBoundingBoxAligned& scissor) { PROFILE3_GPU("water reflections"); // Save the post-processing framebuffer. GLint fbo; glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fbo); WaterManager& wm = m->waterManager; // Remember old camera CCamera normalCamera = m_ViewCamera; ComputeReflectionCamera(m_ViewCamera, scissor); m->SetOpenGLCamera(m_ViewCamera); // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_ReflectionMatrix = m_ViewCamera.GetViewProjection(); float vpHeight = wm.m_ReflectionTextureSize; float vpWidth = wm.m_ReflectionTextureSize; SScreenRect screenScissor; screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vpWidth); screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vpHeight); screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vpWidth); screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vpHeight); glEnable(GL_SCISSOR_TEST); glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); // try binding the framebuffer pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, wm.m_ReflectionFbo); glClearColor(0.5f,0.5f,1.0f,0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glFrontFace(GL_CW); if (!m_Options.m_WaterReflection) { m->skyManager.RenderSky(); ogl_WarnIfError(); } else { // Render terrain and models RenderPatches(context, CULL_REFLECTIONS); ogl_WarnIfError(); RenderModels(context, CULL_REFLECTIONS); ogl_WarnIfError(); RenderTransparentModels(context, CULL_REFLECTIONS, TRANSPARENT, true); ogl_WarnIfError(); } glFrontFace(GL_CCW); // Particles are always oriented to face the camera in the vertex shader, // so they don't need the inverted glFrontFace if (m_Options.m_Particles) { RenderParticles(CULL_REFLECTIONS); ogl_WarnIfError(); } glDisable(GL_SCISSOR_TEST); // Reset old camera m_ViewCamera = normalCamera; m->SetOpenGLCamera(m_ViewCamera); // rebind post-processing frambuffer. pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); return; } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderRefractions: render the water refractions to the refraction texture void CRenderer::RenderRefractions(const CShaderDefines& context, const CBoundingBoxAligned &scissor) { PROFILE3_GPU("water refractions"); // Save the post-processing framebuffer. GLint fbo; glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &fbo); WaterManager& wm = m->waterManager; // Remember old camera CCamera normalCamera = m_ViewCamera; ComputeRefractionCamera(m_ViewCamera, scissor); CVector4D camPlane(0, -1, 0, wm.m_WaterHeight + 2.0f); SetObliqueFrustumClipping(m_ViewCamera, camPlane); m->SetOpenGLCamera(m_ViewCamera); // Save the model-view-projection matrix so the shaders can use it for projective texturing wm.m_RefractionMatrix = m_ViewCamera.GetViewProjection(); float vpHeight = wm.m_RefractionTextureSize; float vpWidth = wm.m_RefractionTextureSize; SScreenRect screenScissor; screenScissor.x1 = (GLint)floor((scissor[0].X*0.5f+0.5f)*vpWidth); screenScissor.y1 = (GLint)floor((scissor[0].Y*0.5f+0.5f)*vpHeight); screenScissor.x2 = (GLint)ceil((scissor[1].X*0.5f+0.5f)*vpWidth); screenScissor.y2 = (GLint)ceil((scissor[1].Y*0.5f+0.5f)*vpHeight); glEnable(GL_SCISSOR_TEST); glScissor(screenScissor.x1, screenScissor.y1, screenScissor.x2 - screenScissor.x1, screenScissor.y2 - screenScissor.y1); // try binding the framebuffer pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, wm.m_RefractionFbo); glClearColor(1.0f,0.0f,0.0f,0.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Render terrain and models RenderPatches(context, CULL_REFRACTIONS); ogl_WarnIfError(); RenderModels(context, CULL_REFRACTIONS); ogl_WarnIfError(); RenderTransparentModels(context, CULL_REFRACTIONS, TRANSPARENT_OPAQUE, false); ogl_WarnIfError(); glDisable(GL_SCISSOR_TEST); // Reset old camera m_ViewCamera = normalCamera; m->SetOpenGLCamera(m_ViewCamera); // rebind post-processing frambuffer. pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo); return; } void CRenderer::RenderSilhouettes(const CShaderDefines& context) { PROFILE3_GPU("silhouettes"); CShaderDefines contextOccluder = context; contextOccluder.Add(str_MODE_SILHOUETTEOCCLUDER, str_1); CShaderDefines contextDisplay = context; contextDisplay.Add(str_MODE_SILHOUETTEDISPLAY, str_1); // 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 // color. 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(CULL_SILHOUETTE_OCCLUDER); glCullFace(GL_BACK); } { PROFILE("render model occluders"); m->CallModelRenderers(contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0); } { PROFILE("render transparent occluders"); m->CallTranspModelRenderers(contextOccluder, CULL_SILHOUETTE_OCCLUDER, 0); } 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 color 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); } { PROFILE("render casters"); m->CallModelRenderers(contextDisplay, CULL_SILHOUETTE_CASTER, 0); // (This won't render transparent objects with SILHOUETTE_CASTER - 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(int cullGroup) { // Only supported in shader modes if (GetRenderPath() != RP_SHADER) return; PROFILE3_GPU("particles"); m->particleRenderer.RenderParticles(cullGroup); #if !CONFIG2_GLES 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); CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid); shaderTech->BeginPass(); CShaderProgramPtr shader = shaderTech->GetShader(); shader->Uniform(str_color, 0.0f, 1.0f, 0.0f, 1.0f); shader->Uniform(str_transform, m_ViewCamera.GetViewProjection()); m->particleRenderer.RenderBounds(cullGroup, shader); shaderTech->EndPass(); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // RenderSubmissions: force rendering of any batched objects void CRenderer::RenderSubmissions(const CBoundingBoxAligned& waterScissor) { PROFILE3("render submissions"); GetScene().GetLOSTexture().InterpolateLOS(); if (m_Options.m_Postproc) { m->postprocManager.Initialize(); m->postprocManager.CaptureRenderOutput(); } CShaderDefines context = m->globalContext; int cullGroup = CULL_DEFAULT; ogl_WarnIfError(); // Set the camera m->SetOpenGLCamera(m_ViewCamera); // Prepare model renderers { PROFILE3("prepare models"); m->Model.NormalSkinned->PrepareModels(); m->Model.TranspSkinned->PrepareModels(); if (m->Model.NormalUnskinned != m->Model.NormalSkinned) m->Model.NormalUnskinned->PrepareModels(); if (m->Model.TranspUnskinned != m->Model.TranspSkinned) m->Model.TranspUnskinned->PrepareModels(); } m->terrainRenderer.PrepareForRendering(); m->overlayRenderer.PrepareForRendering(); m->particleRenderer.PrepareForRendering(context); if (m_Caps.m_Shadows && m_Options.m_Shadows && GetRenderPath() == RP_SHADER) { RenderShadowMap(context); } { PROFILE3_GPU("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); } ogl_WarnIfError(); if (m_WaterManager->m_RenderWater) { if (waterScissor.GetVolume() > 0 && m_WaterManager->WillRenderFancyWater()) { PROFILE3_GPU("water scissor"); RenderReflections(context, waterScissor); if (m_Options.m_WaterRefraction) RenderRefractions(context, waterScissor); } } if (m_Options.m_ShowSky) { m->skyManager.RenderSky(); } // render submitted patches and models RenderPatches(context, cullGroup); ogl_WarnIfError(); // render debug-related terrain overlays ITerrainOverlay::RenderOverlaysBeforeWater(); ogl_WarnIfError(); // render other debug-related overlays before water (so they can be seen when underwater) m->overlayRenderer.RenderOverlaysBeforeWater(); ogl_WarnIfError(); RenderModels(context, cullGroup); ogl_WarnIfError(); // render water if (m_WaterManager->m_RenderWater && g_Game && waterScissor.GetVolume() > 0) { // render transparent stuff, but only the solid parts that can occlude block water RenderTransparentModels(context, cullGroup, TRANSPARENT_OPAQUE, false); ogl_WarnIfError(); m->terrainRenderer.RenderWater(context, cullGroup, &m->shadow); ogl_WarnIfError(); // render transparent stuff again, but only the blended parts that overlap water RenderTransparentModels(context, cullGroup, TRANSPARENT_BLEND, false); ogl_WarnIfError(); } else { // render transparent stuff, so it can overlap models/terrain RenderTransparentModels(context, cullGroup, TRANSPARENT, false); ogl_WarnIfError(); } // render debug-related terrain overlays ITerrainOverlay::RenderOverlaysAfterWater(cullGroup); ogl_WarnIfError(); // render some other overlays after water (so they can be displayed on top of water) m->overlayRenderer.RenderOverlaysAfterWater(); ogl_WarnIfError(); // particles are transparent so render after water if (m_Options.m_Particles) { RenderParticles(cullGroup); ogl_WarnIfError(); } if (m_Options.m_Postproc) { m->postprocManager.ApplyPostproc(); m->postprocManager.ReleaseRenderOutput(); } if (m_Options.m_Silhouettes) { RenderSilhouettes(context); } #if !CONFIG2_GLES // 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); #endif // render debug lines if (m_Options.m_DisplayFrustum) { DisplayFrustum(); m->shadow.RenderDebugBounds(); m->shadow.RenderDebugTexture(); ogl_WarnIfError(); } m->silhouetteRenderer.RenderDebugOverlays(m_ViewCamera); // render overlays that should appear on top of all other objects m->overlayRenderer.RenderForegroundOverlays(m_ViewCamera); ogl_WarnIfError(); } /////////////////////////////////////////////////////////////////////////////////////////////////// // EndFrame: signal frame end void CRenderer::EndFrame() { PROFILE3("end frame"); // empty lists m->terrainRenderer.EndFrame(); m->overlayRenderer.EndFrame(); m->particleRenderer.EndFrame(); m->silhouetteRenderer.EndFrame(); // Finish model renderers m->Model.NormalSkinned->EndFrame(); m->Model.TranspSkinned->EndFrame(); if (m->Model.NormalUnskinned != m->Model.NormalSkinned) m->Model.NormalUnskinned->EndFrame(); if (m->Model.TranspUnskinned != m->Model.TranspSkinned) m->Model.TranspUnskinned->EndFrame(); ogl_tex_bind(0, 0); { PROFILE3("error check"); int err = glGetError(); if (err) { ONCE(LOGERROR("CRenderer::EndFrame: GL errors %i occurred", err)); } } } /////////////////////////////////////////////////////////////////////////////////////////////////// // DisplayFrustum: debug displays // - white: cull camera frustum // - red: bounds of shadow casting objects void CRenderer::DisplayFrustum() { #if CONFIG2_GLES #warning TODO: implement CRenderer::DisplayFrustum for GLES #else glDepthMask(0); glDisable(GL_CULL_FACE); glDisable(GL_TEXTURE_2D); 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); #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // Text overlay rendering void CRenderer::RenderTextOverlays() { PROFILE3_GPU("text overlays"); if (m_DisplayTerrainPriorities) m->terrainRenderer.RenderPriorities(CULL_DEFAULT); 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) { m_Viewport = vp; glViewport((GLint)vp.m_X,(GLint)vp.m_Y,(GLsizei)vp.m_Width,(GLsizei)vp.m_Height); } SViewPort CRenderer::GetViewport() { return m_Viewport; } void CRenderer::Submit(CPatch* patch) { if (m_CurrentCullGroup == CULL_DEFAULT) { m->shadow.AddShadowReceiverBound(patch->GetWorldBounds()); m->silhouetteRenderer.AddOccluder(patch); } if (m_CurrentCullGroup == CULL_SHADOWS) { m->shadow.AddShadowCasterBound(patch->GetWorldBounds()); } m->terrainRenderer.Submit(m_CurrentCullGroup, patch); } void CRenderer::Submit(SOverlayLine* overlay) { // Overlays are only needed in the default cull group for now, // so just ignore submissions to any other group if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlayTexturedLine* overlay) { if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlaySprite* overlay) { if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlayQuad* overlay) { if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(SOverlaySphere* overlay) { if (m_CurrentCullGroup == CULL_DEFAULT) m->overlayRenderer.Submit(overlay); } void CRenderer::Submit(CModelDecal* decal) { // Decals can't cast shadows since they're flat on the terrain. // They can receive shadows, but the terrain under them will have // already been passed to AddShadowCasterBound, so don't bother // doing it again here. m->terrainRenderer.Submit(m_CurrentCullGroup, decal); } void CRenderer::Submit(CParticleEmitter* emitter) { m->particleRenderer.Submit(m_CurrentCullGroup, emitter); } void CRenderer::SubmitNonRecursive(CModel* model) { if (m_CurrentCullGroup == CULL_DEFAULT) { m->shadow.AddShadowReceiverBound(model->GetWorldBounds()); if (model->GetFlags() & MODELFLAG_SILHOUETTE_OCCLUDER) m->silhouetteRenderer.AddOccluder(model); if (model->GetFlags() & MODELFLAG_SILHOUETTE_DISPLAY) m->silhouetteRenderer.AddCaster(model); } if (m_CurrentCullGroup == CULL_SHADOWS) { if (!(model->GetFlags() & MODELFLAG_CASTSHADOWS)) return; m->shadow.AddShadowCasterBound(model->GetWorldBounds()); } bool requiresSkinning = (model->GetModelDef()->GetNumBones() != 0); if (model->GetMaterial().UsesAlphaBlending()) { if (requiresSkinning) m->Model.TranspSkinned->Submit(m_CurrentCullGroup, model); else m->Model.TranspUnskinned->Submit(m_CurrentCullGroup, model); } else { if (requiresSkinning) m->Model.NormalSkinned->Submit(m_CurrentCullGroup, model); else m->Model.NormalUnskinned->Submit(m_CurrentCullGroup, model); } } /////////////////////////////////////////////////////////// // Render the given scene void CRenderer::RenderScene(Scene& scene) { m_CurrentScene = &scene; CFrustum frustum = m_CullCamera.GetFrustum(); m_CurrentCullGroup = CULL_DEFAULT; scene.EnumerateObjects(frustum, this); m->particleManager.RenderSubmit(*this, frustum); if (m_Options.m_Silhouettes) { m->silhouetteRenderer.ComputeSubmissions(m_ViewCamera); m_CurrentCullGroup = CULL_DEFAULT; m->silhouetteRenderer.RenderSubmitOverlays(*this); m_CurrentCullGroup = CULL_SILHOUETTE_OCCLUDER; m->silhouetteRenderer.RenderSubmitOccluders(*this); m_CurrentCullGroup = CULL_SILHOUETTE_CASTER; m->silhouetteRenderer.RenderSubmitCasters(*this); } if (m_Caps.m_Shadows && m_Options.m_Shadows && GetRenderPath() == RP_SHADER) { m_CurrentCullGroup = CULL_SHADOWS; CFrustum shadowFrustum = m->shadow.GetShadowCasterCullFrustum(); scene.EnumerateObjects(shadowFrustum, this); } CBoundingBoxAligned waterScissor; if (m_WaterManager->m_RenderWater) { waterScissor = m->terrainRenderer.ScissorWater(CULL_DEFAULT, m_ViewCamera.GetViewProjection()); if (waterScissor.GetVolume() > 0 && m_WaterManager->WillRenderFancyWater()) { if (m_Options.m_WaterReflection) { m_CurrentCullGroup = CULL_REFLECTIONS; CCamera reflectionCamera; ComputeReflectionCamera(reflectionCamera, waterScissor); scene.EnumerateObjects(reflectionCamera.GetFrustum(), this); } if (m_Options.m_WaterRefraction) { m_CurrentCullGroup = CULL_REFRACTIONS; CCamera refractionCamera; ComputeRefractionCamera(refractionCamera, waterScissor); scene.EnumerateObjects(refractionCamera.GetFrustum(), this); } } // Render the waves to the Fancy effects texture m_WaterManager->RenderWaves(frustum); } m_CurrentCullGroup = -1; ogl_WarnIfError(); RenderSubmissions(waterScissor); m_CurrentScene = NULL; } Scene& CRenderer::GetScene() { ENSURE(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 !CONFIG2_GLES if (tex) { glEnable(GL_TEXTURE_2D); } else { glDisable(GL_TEXTURE_2D); } #endif } /////////////////////////////////////////////////////////////////////////////////////////////////// // LoadAlphaMaps: load the 14 default alpha maps, pack them into one composite texture and // calculate the coordinate of each alphamap within this packed texture // NB: A variant of this function is duplicated in TerrainTextureEntry.cpp, for use with the Shader // renderpath. This copy is kept to load the 'standard' maps for the fixed pipeline and should // be removed if/when the fixed pipeline goes. 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; AllocateAligned(data, total_w*total_h, maxSectorSize); // for each tile on row for (size_t i = 0; i < NumAlphaMaps; i++) { // get src of copy u8* src = 0; (void)ogl_tex_get_data(textures[i], &src); size_t srcstep = bpp/8; // get destination of copy u8* dst = data.get() + (i*tile_w); // for each row of image for (size_t j = 0; j < base; j++) { // duplicate first pixel *dst++ = *src; *dst++ = *src; // copy a row for (size_t k = 0; k < base; k++) { *dst++ = *src; src += srcstep; } // duplicate last pixel *dst++ = *(src-srcstep); *dst++ = *(src-srcstep); // advance write pointer for next row dst += total_w-tile_w; } m_AlphaMapCoords[i].u0 = float(i*tile_w+2) / float(total_w); m_AlphaMapCoords[i].u1 = float((i+1)*tile_w-2) / float(total_w); m_AlphaMapCoords[i].v0 = 0.0f; m_AlphaMapCoords[i].v1 = 1.0f; } for (size_t i = 0; i < NumAlphaMaps; i++) (void)ogl_tex_free(textures[i]); // upload the composite texture Tex t; (void)t.wrap(total_w, total_h, 8, TEX_GREY, data, 0); /*VfsPath filename("blendtex.png"); DynArray da; RETURN_STATUS_IF_ERR(tex_encode(&t, filename.Extension(), &da)); // write to disk //Status ret = INFO::OK; { shared_ptr file = DummySharedPtr(da.base); const ssize_t bytes_written = g_VFS->CreateFile(filename, file, da.pos); if(bytes_written > 0) ENSURE(bytes_written == (ssize_t)da.pos); //else // ret = (Status)bytes_written; } (void)da_free(&da);*/ m_hCompositeAlphaMap = ogl_tex_wrap(&t, g_VFS, key); (void)ogl_tex_set_filter(m_hCompositeAlphaMap, GL_LINEAR); (void)ogl_tex_set_wrap (m_hCompositeAlphaMap, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); int ret = ogl_tex_upload(m_hCompositeAlphaMap, GL_ALPHA, 0, 0); return ret; } /////////////////////////////////////////////////////////////////////////////////////////////////// // UnloadAlphaMaps: frees the resources allocates by LoadAlphaMaps void CRenderer::UnloadAlphaMaps() { ogl_tex_free(m_hCompositeAlphaMap); m_hCompositeAlphaMap = 0; } Status CRenderer::ReloadChangedFileCB(void* param, const VfsPath& path) { CRenderer* renderer = static_cast(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; } CTextureManager& CRenderer::GetTextureManager() { return m->textureManager; } CShaderManager& CRenderer::GetShaderManager() { return m->shaderManager; } CParticleManager& CRenderer::GetParticleManager() { return m->particleManager; } TerrainRenderer& CRenderer::GetTerrainRenderer() { return m->terrainRenderer; } CTimeManager& CRenderer::GetTimeManager() { return m->timeManager; } CMaterialManager& CRenderer::GetMaterialManager() { return m->materialManager; } CPostprocManager& CRenderer::GetPostprocManager() { return m->postprocManager; } CFontManager& CRenderer::GetFontManager() { return m->fontManager; }