Index: ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json =================================================================== --- ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json (revision 23940) +++ ps/trunk/binaries/data/mods/public/gui/credits/texts/programming.json (revision 23941) @@ -1,272 +1,273 @@ { "Title": "Programming", "Content": [ { "Title": "Programming managers", "List": [ { "nick": "Acumen", "name": "Stuart Walpole" }, { "nick": "Dak Lozar", "name": "Dave Loeser" }, { "nick": "h20", "name": "Daniel Wilhelm" }, { "nick": "Janwas", "name": "Jan Wassenberg" }, { "nick": "Raj", "name": "Raj Sharma" } ] }, { "Subtitle": "Special thanks to", "List": [ { "nick": "leper", "name": "Georg Kilzer" }, { "nick": "Ykkrosh", "name": "Philip Taylor" } ] }, { "List": [ { "nick": "01d55" }, { "nick": "aBothe", "name": "Alexander Bothe" }, { "nick": "Acumen", "name": "Stuart Walpole" }, { "nick": "adrian", "name": "Adrian Boguszewszki" }, { "name": "Adrian Fatol" }, { "nick": "AI-Amsterdam" }, { "nick": "Alan", "name": "Alan Kemp" }, { "nick": "Alex", "name": "Alexander Yakobovich" }, { "nick": "alpha123", "name": "Peter P. Cannici" }, { "nick": "Ampaex", "name": "Antonio Vazquez" }, { "name": "André Puel" }, { "nick": "andy5995", "name": "Andy Alt" }, { "nick": "Angen" }, { "nick": "ArnH", "name": "Arno Hemelhof" }, { "nick": "Aurium", "name": "Aurélio Heckert" }, { "nick": "badmadblacksad", "name": "Martin F" }, { "nick": "badosu", "name": "Amadeus Folego" }, { "nick": "bb", "name": "Bouke Jansen" }, { "nick": "Ben", "name": "Ben Vinegar" }, { "nick": "Bird" }, { "nick": "Blue", "name": "Richard Welsh" }, { "nick": "bmwiedemann" }, { "nick": "boeseRaupe", "name": "Michael Kluge" }, { "nick": "bog_dan_ro", "name": "BogDan Vatra" }, { "nick": "Bonk", "name": "Christopher Ebbert" }, { "nick": "Boudica" }, { "nick": "Caius", "name": "Lars Kemmann" }, { "nick": "Calefaction", "name": "Matt Holmes" }, { "nick": "Calvinh", "name": "Carl-Johan Höiby" }, { "nick": "causative", "name": "Bart Parkis" }, { "name": "Cédric Houbart" }, { "nick": "Chakakhan", "name": "Kenny Long" }, { "nick": "Clockwork-Muse", "name": "Stephen A. Imhoff" }, { "nick": "Cracker78", "name": "Chad Heim" }, { "nick": "Crynux", "name": "Stephen J. Fewer" }, { "nick": "cwprogger" }, { "nick": "cygal", "name": "Quentin Pradet" }, { "nick": "Dak Lozar", "name": "Dave Loeser" }, { "nick": "dalerank", "name": "Sergey Kushnirenko" }, { "nick": "dan", "name": "Dan Strandberg" }, { "nick": "DanCar", "name": "Daniel Cardenas" }, { "nick": "danger89", "name": "Melroy van den Berg" }, { "name": "Daniel Trevitz" }, { "nick": "Dariost", "name": "Dario Ostuni" }, { "nick": "Dave", "name": "David Protasowski" }, { "nick": "dax", "name": "Dacian Fiordean" }, { "nick": "deebee", "name": "Deepak Anthony" }, { "nick": "Deiz" }, { "nick": "Dietger", "name": "Dietger van Antwerpen" }, { "nick": "DigitalSeraphim", "name": "Nick Owens" }, { "nick": "dp304" }, { "nick": "dpiquet", "name": "Damien Piquet" }, { "nick": "dumbo" }, { "nick": "Dunedan", "name": "Daniel Roschka" }, { "nick": "dvangennip", "name": "Doménique" }, { "nick": "Echelon9", "name": "Rhys Kidd" }, { "nick": "echotangoecho" }, { "nick": "eihrul", "name": "Lee Salzman" }, { "nick": "elexis", "name": "Alexander Heinsius" }, { "nick": "EmjeR", "name": "Matthijs de Rijk" }, { "nick": "EMontana" }, { "nick": "ericb" }, { "nick": "evanssthomas", "name": "Evans Thomas" }, { "nick": "Evulant", "name": "Alexander S." }, { "nick": "fabio", "name": "Fabio Pedretti" }, { "nick": "falsevision", "name": "Mahdi Khodadadifard" }, { "nick": "fatherbushido", "name": "Nicolas Tisserand" }, { "nick": "fcxSanya", "name": "Alexander Olkhovskiy" }, { "nick": "FeXoR", "name": "Florian Finke" }, { "nick": "Fire Giant", "name": "Malte Schwarzkopf" }, { "name": "Fork AD" }, { "nick": "fpre", "name": "Frederick Stallmeyer" }, { "nick": "Freagarach" }, { "nick": "freenity", "name": "Anton Galitch" }, { "nick": "Gallaecio", "name": "Adrián Chaves" }, { "nick": "gbish (aka Iny)", "name": "Grant Bishop" }, { "nick": "Gee", "name": "Gustav Larsson" }, { "nick": "Gentz", "name": "Hal Gentz" }, { "nick": "gerbilOFdoom" }, { "nick": "godlikeldh" }, { "nick": "greybeard", "name": "Joe Cocovich" }, { "nick": "grillaz" }, { "nick": "Grugnas", "name": "Giuseppe Tranchese" }, { "nick": "gudo" }, { "nick": "Guuts", "name": "Matthew Guttag" }, { "nick": "h20", "name": "Daniel Wilhelm" }, { "nick": "Hannibal_Barca", "name": "Clive Juhász S." }, { "nick": "Haommin" }, { "nick": "happyconcepts", "name": "Ben Bird" }, { "nick": "historic_bruno", "name": "Ben Brian" }, { "nick": "idanwin" }, { "nick": "Imarok", "name": "J. S." }, { "nick": "Inari" }, { "nick": "infyquest", "name": "Vijay Kiran Kamuju" }, { "nick": "irishninja", "name": "Brian Broll" }, { "nick": "IronNerd", "name": "Matthew McMullan" }, { "nick": "Itms", "name": "Nicolas Auvray" }, { "nick": "Jaison", "name": "Marco tom Suden" }, { "nick": "jammus", "name": "James Scott" }, { "nick": "javiergodas", "name": "Javier Godas Vieitez" }, { "nick": "Jgwman" }, { "nick": "JonBaer", "name": "Jon Baer" }, { "nick": "Josh", "name": "Joshua J. Bakita" }, { "nick": "joskar", "name": "Johnny Oskarsson" }, { "nick": "jP_wanN", "name": "Jonas Platte" }, { "nick": "Jubalbarca", "name": "James Baillie" }, { "nick": "JubJub", "name": "Sebastian Vetter" }, { "nick": "jurgemaister" }, { "nick": "kabzerek", "name": "Grzegorz Kabza" }, { "nick": "Kai", "name": "Kai Chen" }, { "name": "Kareem Ergawy" }, { "nick": "kevmo", "name": "Kevin Caffrey" }, { "nick": "kezz", "name": "Graeme Kerry" }, { "nick": "kingadami", "name": "Adam Winsor" }, { "nick": "kingbasil", "name": "Giannis Fafalios" }, { "nick": "Krinkle", "name": "Timo Tijhof" }, { "nick": "lafferjm", "name": "Justin Lafferty" }, { "nick": "LeanderH", "name": "Leander Hemelhof" }, { "nick": "leper", "name": "Georg Kilzer" }, { "nick": "Link Mauve", "name": "Emmanuel Gil Peyrot" }, { "nick": "LittleDev" }, { "nick": "livingaftermidnight", "name": "Will Dull" }, { "nick": "Louhike" }, { "nick": "lsdh" }, { "nick": "Ludovic", "name": "Ludovic Rousseau" }, { "nick": "luiko", "name": "Luis Carlos Garcia Barajas" }, { "nick": "m0l0t0ph", "name": "Christoph Gielisch" }, { "nick": "madmax", "name": "Abhijit Nandy" }, { "nick": "madpilot", "name": "Guido Falsi" }, { "nick": "markcho" }, { "nick": "MarkT", "name": "Mark Thompson" }, { "nick": "Markus" }, { "nick": "Mate-86", "name": "Mate Kovacs" }, { "nick": "Matei", "name": "Matei Zaharia" }, { "nick": "MattDoerksen", "name": "Matt Doerksen" }, { "nick": "mattlott", "name": "Matt Lott" }, { "nick": "maveric", "name": "Anton Protko" }, { "nick": "Micnasty", "name": "Travis Gorkin" }, { "name": "Mikołaj \"Bajter\" Korcz" }, { "nick": "mimo" }, { "nick": "mk12", "name": "Mitchell Kember" }, { "nick": "mmayfield45", "name": "Michael Mayfield" }, { "nick": "mmoanis", "name": "Mohamed Moanis" }, { "nick": "Molotov", "name": "Dario Alvarez" }, { "nick": "mpmoreti", "name": "Marcos Paulo Moreti" }, { "nick": "mreiland", "name": "Michael Reiland" }, { "nick": "myconid" }, { "nick": "nani", "name": "S. N." }, { "nick": "nd3c3nt", "name": "Gavin Fowler" }, { "nick": "nephele" }, { "nick": "Nescio" }, { "nick": "niektb", "name": "Niek ten Brinke" }, { "nick": "njm" }, { "nick": "NoMonkey", "name": "John Mena" }, { "nick": "norsnor" }, { "nick": "notpete", "name": "Rich Cross" }, { "nick": "nwtour" }, { "nick": "odoaker", "name": "Ágoston Sipos" }, { "nick": "Offensive ePeen", "name": "Jared Ryan Bills" }, { "nick": "Ols", "name": "Oliver Whiteman" }, { "nick": "olsner", "name": "Simon Brenner" }, { "nick": "OptimusShepard", "name": "Pirmin Stanglmeier" }, { "nick": "otero" }, { "nick": "Palaxin", "name": "David A. Freitag" }, { "name": "Paul Withers" }, { "nick": "paulobezerr", "name": "Paulo George Gomes Bezerra" }, { "nick": "pcpa", "name": "Paulo Andrade" }, { "nick": "Pendingchaos" }, { "nick": "PeteVasi", "name": "Pete Vasiliauskas" }, { "nick": "pilino1234" }, { "nick": "PingvinBetyar", "name": "Schronk Tamás" }, { "nick": "plugwash", "name": "Peter Michael Green" }, { "nick": "Polakrity" }, { "nick": "Poya", "name": "Poya Manouchehri" }, { "nick": "prefect", "name": "Nicolai Hähnle" }, { "nick": "Prodigal Son" }, { "nick": "pstumpf", "name": "Pascal Stumpf" }, { "nick": "pyrolink", "name": "Andrew Decker" }, { "nick": "quantumstate", "name": "Jonathan Waller" }, { "nick": "QuickShot", "name": "Walter Krawec" }, { "nick": "quonter" }, { "nick": "qwertz" }, { "nick": "Radagast" }, { "nick": "Raj", "name": "Raj Sharma" }, { "nick": "rapidelectron", "name": "Christian Weihsbach" }, { "nick": "RedFox", "name": "Jorma Rebane" }, { "nick": "RefinedCode" }, { "nick": "Riemer" }, { "name": "Rolf Sievers" }, { "nick": "s0600204", "name": "Matthew Norwood" }, { "nick": "sacha_vrand", "name": "Sacha Vrand" }, { "nick": "SafaAlfulaij" }, { "name": "Samuel Guarnieri" }, { "nick": "Sandarac" }, { "nick": "sanderd17", "name": "Sander Deryckere" }, { "nick": "sathyam", "name": "Sathyam Vellal" }, { "nick": "sbirmi", "name": "Sharad Birmiwal" }, { "nick": "sbte", "name": "Sven Baars" }, { "nick": "scroogie", "name": "André Gemünd" }, { "nick": "scythetwirler", "name": "Casey X." }, { "nick": "serveurix" }, { "nick": "Shane", "name": "Shane Grant" }, { "nick": "shh" }, { "nick": "Silk", "name": "Josh Godsiff" }, { "nick": "silure" }, { "nick": "Simikolon", "name": "Yannick & Simon" }, { "nick": "smiley", "name": "M. L." }, { "nick": "Spahbod", "name": "Omid Davoodi" }, { "nick": "Stan", "name": "Stanislas Dolcini" }, { "nick": "Stefan" }, + { "nick": "StefanBruens", "name": "Stefan Brüns" }, { "nick": "stilz", "name": "Sławomir Zborowski" }, { "nick": "stwf", "name": "Steven Fuchs" }, { "nick": "svott", "name": "Sven Ott" }, { "nick": "t4nk004" }, { "nick": "tau" }, { "nick": "tbm", "name": "Martin Michlmayr" }, { "nick": "Teiresias" }, { "nick": "temple" }, { "nick": "texane" }, { "nick": "thamlett", "name": "Timothy Hamlett" }, { "nick": "thedrunkyak", "name": "Dan Fuhr" }, { "nick": "Tobbi" }, { "nick": "TrinityDeath", "name": "Jethro Lu" }, { "nick": "triumvir", "name": "Corin Schedler" }, { "nick": "trompetin17", "name": "Juan Guillermo" }, { "nick": "user1", "name": "A. C." }, { "nick": "usey11" }, { "nick": "vincent_c", "name": "Vincent Cheng" }, { "nick": "vinhig", "name": "Vincent Higginson" }, { "nick": "vladislavbelov", "name": "Vladislav Belov" }, { "nick": "voroskoi" }, { "nick": "vts", "name": "Jeroen DR" }, { "nick": "wacko", "name": "Andrew Spiering" }, { "nick": "WhiteTreePaladin", "name": "Brian Ashley" }, { "nick": "wraitii", "name": "Lancelot de Ferrière le Vayer" }, { "nick": "Xentelian", "name": "Mark Strawson" }, { "nick": "Xienen", "name": "Dayle Flowers" }, { "nick": "xtizer", "name": "Matt Green" }, { "nick": "yashi", "name": "Yasushi Shoji" }, { "nick": "Ykkrosh", "name": "Philip Taylor" }, { "nick": "Yves" }, { "nick": "Zeusthor", "name": "Jeffrey Tavares" }, { "nick": "zoot" }, { "nick": "zsol", "name": "Zsolt Dollenstein" }, { "nick": "ztamas", "name": "Tamas Zolnai" }, { "nick": "Zyi", "name": "Charles De Meulenaer" } ] } ] } Index: ps/trunk/source/renderer/HWLightingModelRenderer.cpp =================================================================== --- ps/trunk/source/renderer/HWLightingModelRenderer.cpp (revision 23940) +++ ps/trunk/source/renderer/HWLightingModelRenderer.cpp (revision 23941) @@ -1,314 +1,314 @@ /* Copyright (C) 2020 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/bits.h" #include "lib/ogl.h" #include "lib/sysdep/rtl.h" #include "maths/Vector3D.h" #include "graphics/Color.h" #include "graphics/LightEnv.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "graphics/ShaderProgram.h" #include "renderer/HWLightingModelRenderer.h" #include "renderer/Renderer.h" #include "renderer/RenderModifiers.h" #include "renderer/VertexArray.h" struct ShaderModelDef : public CModelDefRPrivate { /// Indices are the same for all models, so share them VertexIndexArray m_IndexArray; /// Static per-CModelDef vertex array VertexArray m_Array; /// The number of UVs is determined by the model std::vector m_UVs; ShaderModelDef(const CModelDefPtr& mdef); }; ShaderModelDef::ShaderModelDef(const CModelDefPtr& mdef) : m_IndexArray(GL_STATIC_DRAW), m_Array(GL_STATIC_DRAW) { size_t numVertices = mdef->GetNumVertices(); m_UVs.resize(mdef->GetNumUVsPerVertex()); for (size_t i = 0; i < mdef->GetNumUVsPerVertex(); ++i) { m_UVs[i].type = GL_FLOAT; m_UVs[i].elems = 2; m_Array.AddAttribute(&m_UVs[i]); } m_Array.SetNumVertices(numVertices); m_Array.Layout(); for (size_t i = 0; i < mdef->GetNumUVsPerVertex(); ++i) { VertexArrayIterator UVit = m_UVs[i].GetIterator(); ModelRenderer::BuildUV(mdef, UVit, i); } m_Array.Upload(); m_Array.FreeBackingStore(); m_IndexArray.SetNumVertices(mdef->GetNumFaces()*3); m_IndexArray.Layout(); ModelRenderer::BuildIndices(mdef, m_IndexArray.GetIterator()); m_IndexArray.Upload(); m_IndexArray.FreeBackingStore(); } struct ShaderModel : public CModelRData { /// Dynamic per-CModel vertex array VertexArray m_Array; /// Position and normals/lighting are recalculated on CPU every frame VertexArray::Attribute m_Position; VertexArray::Attribute m_Normal; // valid iff cpuLighting == false VertexArray::Attribute m_Color; // valid iff cpuLighting == true ShaderModel(const void* key) : CModelRData(key), m_Array(GL_DYNAMIC_DRAW) { } }; -struct ShaderModelRendererInternals +struct ShaderModelVertexRenderer::ShaderModelRendererInternals { bool cpuLighting; /** * Scratch space for normal vector calculation. * Only used if cpuLighting == true. * Space is reserved so we don't have to do frequent reallocations. * Allocated with rtl_AllocateAligned(normalsNumVertices*16, 16) for SSE writes. */ char* normals; size_t normalsNumVertices; /// Previously prepared modeldef ShaderModelDef* shadermodeldef; }; // Construction and Destruction ShaderModelVertexRenderer::ShaderModelVertexRenderer(bool cpuLighting) { m = new ShaderModelRendererInternals; m->cpuLighting = cpuLighting; m->normals = NULL; m->normalsNumVertices = 0; m->shadermodeldef = NULL; } ShaderModelVertexRenderer::~ShaderModelVertexRenderer() { rtl_FreeAligned(m->normals); delete m; } // Build model data (and modeldef data if necessary) CModelRData* ShaderModelVertexRenderer::CreateModelData(const void* key, CModel* model) { CModelDefPtr mdef = model->GetModelDef(); ShaderModelDef* shadermodeldef = (ShaderModelDef*)mdef->GetRenderData(m); if (!shadermodeldef) { shadermodeldef = new ShaderModelDef(mdef); mdef->SetRenderData(m, shadermodeldef); } // Build the per-model data ShaderModel* shadermodel = new ShaderModel(key); if (m->cpuLighting) { // Positions must be 16-byte aligned for SSE writes. // We can pack the color after the position; it will be corrupted by // BuildPositionAndNormals, but that's okay since we'll recompute the // colors afterwards. shadermodel->m_Color.type = GL_UNSIGNED_BYTE; shadermodel->m_Color.elems = 4; shadermodel->m_Array.AddAttribute(&shadermodel->m_Color); shadermodel->m_Position.type = GL_FLOAT; shadermodel->m_Position.elems = 3; shadermodel->m_Array.AddAttribute(&shadermodel->m_Position); } else { // Positions and normals must be 16-byte aligned for SSE writes. shadermodel->m_Position.type = GL_FLOAT; shadermodel->m_Position.elems = 4; shadermodel->m_Array.AddAttribute(&shadermodel->m_Position); shadermodel->m_Normal.type = GL_FLOAT; shadermodel->m_Normal.elems = 4; shadermodel->m_Array.AddAttribute(&shadermodel->m_Normal); } shadermodel->m_Array.SetNumVertices(mdef->GetNumVertices()); shadermodel->m_Array.Layout(); // Verify alignment ENSURE(shadermodel->m_Position.offset % 16 == 0); if (!m->cpuLighting) ENSURE(shadermodel->m_Normal.offset % 16 == 0); ENSURE(shadermodel->m_Array.GetStride() % 16 == 0); return shadermodel; } // Fill in and upload dynamic vertex array void ShaderModelVertexRenderer::UpdateModelData(CModel* model, CModelRData* data, int updateflags) { ShaderModel* shadermodel = static_cast(data); if (!m->cpuLighting && (updateflags & RENDERDATA_UPDATE_VERTICES)) { // build vertices VertexArrayIterator Position = shadermodel->m_Position.GetIterator(); VertexArrayIterator Normal = shadermodel->m_Normal.GetIterator(); ModelRenderer::BuildPositionAndNormals(model, Position, Normal); // upload everything to vertex buffer shadermodel->m_Array.Upload(); } if (m->cpuLighting && (updateflags & (RENDERDATA_UPDATE_VERTICES|RENDERDATA_UPDATE_COLOR))) { CModelDefPtr mdef = model->GetModelDef(); size_t numVertices = mdef->GetNumVertices(); // allocate working space for computing normals if (numVertices > m->normalsNumVertices) { rtl_FreeAligned(m->normals); size_t newSize = round_up_to_pow2(numVertices); m->normals = (char*)rtl_AllocateAligned(newSize*16, 16); m->normalsNumVertices = newSize; } VertexArrayIterator Position = shadermodel->m_Position.GetIterator(); VertexArrayIterator Normal = VertexArrayIterator(m->normals, 16); ModelRenderer::BuildPositionAndNormals(model, Position, Normal); VertexArrayIterator Color = shadermodel->m_Color.GetIterator(); ModelRenderer::BuildColor4ub(model, Normal, Color); // upload everything to vertex buffer shadermodel->m_Array.Upload(); } shadermodel->m_Array.PrepareForRendering(); } // Setup one rendering pass void ShaderModelVertexRenderer::BeginPass(int streamflags) { if (m->cpuLighting) ENSURE(streamflags == (streamflags & (STREAM_POS | STREAM_UV0 | STREAM_UV1 | STREAM_COLOR))); else ENSURE(streamflags == (streamflags & (STREAM_POS | STREAM_UV0 | STREAM_UV1 | STREAM_NORMAL))); } // Cleanup one rendering pass void ShaderModelVertexRenderer::EndPass(int UNUSED(streamflags)) { CVertexBuffer::Unbind(); } // Prepare UV coordinates for this modeldef void ShaderModelVertexRenderer::PrepareModelDef(const CShaderProgramPtr& shader, int streamflags, const CModelDef& def) { m->shadermodeldef = (ShaderModelDef*)def.GetRenderData(m); ENSURE(m->shadermodeldef); u8* base = m->shadermodeldef->m_Array.Bind(); GLsizei stride = (GLsizei)m->shadermodeldef->m_Array.GetStride(); if (streamflags & STREAM_UV0) shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, stride, base + m->shadermodeldef->m_UVs[0].offset); if ((streamflags & STREAM_UV1) && def.GetNumUVsPerVertex() >= 2) shader->TexCoordPointer(GL_TEXTURE1, 2, GL_FLOAT, stride, base + m->shadermodeldef->m_UVs[1].offset); } // Render one model void ShaderModelVertexRenderer::RenderModel(const CShaderProgramPtr& shader, int streamflags, CModel* model, CModelRData* data) { const CModelDefPtr& mdldef = model->GetModelDef(); ShaderModel* shadermodel = static_cast(data); u8* base = shadermodel->m_Array.Bind(); GLsizei stride = (GLsizei)shadermodel->m_Array.GetStride(); u8* indexBase = m->shadermodeldef->m_IndexArray.Bind(); if (streamflags & STREAM_POS) shader->VertexPointer(3, GL_FLOAT, stride, base + shadermodel->m_Position.offset); if (streamflags & STREAM_NORMAL) shader->NormalPointer(GL_FLOAT, stride, base + shadermodel->m_Normal.offset); if (streamflags & STREAM_COLOR) shader->ColorPointer(3, GL_UNSIGNED_BYTE, stride, base + shadermodel->m_Color.offset); shader->AssertPointersBound(); // render the lot size_t numFaces = mdldef->GetNumFaces(); if (!g_Renderer.m_SkipSubmit) { // Draw with DrawRangeElements where available, since it might be more efficient #if CONFIG2_GLES glDrawElements(GL_TRIANGLES, (GLsizei)numFaces*3, GL_UNSIGNED_SHORT, indexBase); #else pglDrawRangeElementsEXT(GL_TRIANGLES, 0, (GLuint)mdldef->GetNumVertices()-1, (GLsizei)numFaces*3, GL_UNSIGNED_SHORT, indexBase); #endif } // bump stats g_Renderer.m_Stats.m_DrawCalls++; g_Renderer.m_Stats.m_ModelTris += numFaces; } Index: ps/trunk/source/renderer/HWLightingModelRenderer.h =================================================================== --- ps/trunk/source/renderer/HWLightingModelRenderer.h (revision 23940) +++ ps/trunk/source/renderer/HWLightingModelRenderer.h (revision 23941) @@ -1,55 +1,54 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2020 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 . */ /* * ModelVertexRenderer that transforms models on the CPU but performs * lighting in a vertex shader. */ #ifndef INCLUDED_HWLIGHTINGMODELRENDERER #define INCLUDED_HWLIGHTINGMODELRENDERER #include "renderer/ModelVertexRenderer.h" -struct ShaderModelRendererInternals; - /** * Render animated models using a ShaderRenderModifier. * This computes and binds per-vertex data; the modifier is responsible * for setting any shader uniforms etc. */ class ShaderModelVertexRenderer : public ModelVertexRenderer { public: ShaderModelVertexRenderer(bool cpuLighting); ~ShaderModelVertexRenderer(); // Implementations CModelRData* CreateModelData(const void* key, CModel* model); void UpdateModelData(CModel* model, CModelRData* data, int updateflags); void BeginPass(int streamflags); void EndPass(int streamflags); void PrepareModelDef(const CShaderProgramPtr& shader, int streamflags, const CModelDef& def); void RenderModel(const CShaderProgramPtr& shader, int streamflags, CModel* model, CModelRData* data); protected: + struct ShaderModelRendererInternals; ShaderModelRendererInternals* m; }; #endif // INCLUDED_HWLIGHTINGMODELRENDERER Index: ps/trunk/source/renderer/ModelRenderer.cpp =================================================================== --- ps/trunk/source/renderer/ModelRenderer.cpp (revision 23940) +++ ps/trunk/source/renderer/ModelRenderer.cpp (revision 23941) @@ -1,773 +1,773 @@ /* Copyright (C) 2020 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 "graphics/Color.h" #include "graphics/LightEnv.h" #include "graphics/Material.h" #include "graphics/Model.h" #include "graphics/ModelDef.h" #include "graphics/ShaderManager.h" #include "graphics/TextureManager.h" #include "lib/allocators/allocator_adapters.h" #include "lib/allocators/arena.h" #include "lib/hash.h" #include "lib/ogl.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" #include "ps/CLogger.h" #include "ps/Profile.h" #include "renderer/MikktspaceWrap.h" #include "renderer/ModelRenderer.h" #include "renderer/ModelVertexRenderer.h" #include "renderer/Renderer.h" #include "renderer/RenderModifiers.h" #include "renderer/SkyManager.h" #include "renderer/TimeManager.h" #include "renderer/WaterManager.h" #if ARCH_X86_X64 # include "lib/sysdep/arch/x86_x64/x86_x64.h" #endif /////////////////////////////////////////////////////////////////////////////////////////////// // ModelRenderer implementation #if ARCH_X86_X64 static bool g_EnableSSE = false; #endif void ModelRenderer::Init() { #if ARCH_X86_X64 if (x86_x64::Cap(x86_x64::CAP_SSE)) g_EnableSSE = true; #endif } // Helper function to copy object-space position and normal vectors into arrays. void ModelRenderer::CopyPositionAndNormals( const CModelDefPtr& mdef, const VertexArrayIterator& Position, const VertexArrayIterator& Normal) { size_t numVertices = mdef->GetNumVertices(); SModelVertex* vertices = mdef->GetVertices(); for (size_t j = 0; j < numVertices; ++j) { Position[j] = vertices[j].m_Coords; Normal[j] = vertices[j].m_Norm; } } // Helper function to transform position and normal vectors into world-space. void ModelRenderer::BuildPositionAndNormals( CModel* model, const VertexArrayIterator& Position, const VertexArrayIterator& Normal) { CModelDefPtr mdef = model->GetModelDef(); size_t numVertices = mdef->GetNumVertices(); SModelVertex* vertices = mdef->GetVertices(); if (model->IsSkinned()) { // boned model - calculate skinned vertex positions/normals // Avoid the noisy warnings that occur inside SkinPoint/SkinNormal in // some broken situations if (numVertices && vertices[0].m_Blend.m_Bone[0] == 0xff) { LOGERROR("Model %s is boned with unboned animation", mdef->GetName().string8()); return; } #if HAVE_SSE if (g_EnableSSE) { CModelDef::SkinPointsAndNormals_SSE(numVertices, Position, Normal, vertices, mdef->GetBlendIndices(), model->GetAnimatedBoneMatrices()); } else #endif { CModelDef::SkinPointsAndNormals(numVertices, Position, Normal, vertices, mdef->GetBlendIndices(), model->GetAnimatedBoneMatrices()); } } else { PROFILE("software transform"); // just copy regular positions, transform normals to world space const CMatrix3D& transform = model->GetTransform(); const CMatrix3D& invtransform = model->GetInvTransform(); for (size_t j = 0; j < numVertices; ++j) { transform.Transform(vertices[j].m_Coords, Position[j]); invtransform.RotateTransposed(vertices[j].m_Norm, Normal[j]); } } } // Helper function for lighting void ModelRenderer::BuildColor4ub( CModel* model, const VertexArrayIterator& Normal, const VertexArrayIterator& Color) { PROFILE("lighting vertices"); CModelDefPtr mdef = model->GetModelDef(); size_t numVertices = mdef->GetNumVertices(); const CLightEnv& lightEnv = g_Renderer.GetLightEnv(); CColor shadingColor = model->GetShadingColor(); for (size_t j = 0; j < numVertices; ++j) { RGBColor tempcolor = lightEnv.EvaluateUnitScaled(Normal[j]); tempcolor.X *= shadingColor.r; tempcolor.Y *= shadingColor.g; tempcolor.Z *= shadingColor.b; Color[j] = ConvertRGBColorTo4ub(tempcolor); } } void ModelRenderer::GenTangents(const CModelDefPtr& mdef, std::vector& newVertices, bool gpuSkinning) { MikkTSpace ms(mdef, newVertices, gpuSkinning); ms.Generate(); } // Copy UV coordinates void ModelRenderer::BuildUV( const CModelDefPtr& mdef, const VertexArrayIterator& UV, int UVset) { size_t numVertices = mdef->GetNumVertices(); SModelVertex* vertices = mdef->GetVertices(); for (size_t j = 0; j < numVertices; ++j) { UV[j][0] = vertices[j].m_UVs[UVset * 2]; UV[j][1] = 1.0 - vertices[j].m_UVs[UVset * 2 + 1]; } } // Build default indices array. void ModelRenderer::BuildIndices( const CModelDefPtr& mdef, const VertexArrayIterator& Indices) { size_t idxidx = 0; SModelFace* faces = mdef->GetFaces(); for (size_t j = 0; j < mdef->GetNumFaces(); ++j) { SModelFace& face = faces[j]; Indices[idxidx++] = face.m_Verts[0]; Indices[idxidx++] = face.m_Verts[1]; Indices[idxidx++] = face.m_Verts[2]; } } /////////////////////////////////////////////////////////////////////////////////////////////// // ShaderModelRenderer implementation /** * Internal data of the ShaderModelRenderer. * * Separated into the source file to increase implementation hiding (and to * avoid some causes of recompiles). */ -struct ShaderModelRendererInternals +struct ShaderModelRenderer::ShaderModelRendererInternals { ShaderModelRendererInternals(ShaderModelRenderer* r) : m_Renderer(r) { } /// Back-link to "our" renderer ShaderModelRenderer* m_Renderer; /// ModelVertexRenderer used for vertex transformations ModelVertexRendererPtr vertexRenderer; /// List of submitted models for rendering in this frame std::vector submissions[CRenderer::CULL_MAX]; }; // Construction/Destruction ShaderModelRenderer::ShaderModelRenderer(ModelVertexRendererPtr vertexrenderer) { m = new ShaderModelRendererInternals(this); m->vertexRenderer = vertexrenderer; } ShaderModelRenderer::~ShaderModelRenderer() { delete m; } // Submit one model. void ShaderModelRenderer::Submit(int cullGroup, CModel* model) { CModelRData* rdata = (CModelRData*)model->GetRenderData(); // Ensure model data is valid const void* key = m->vertexRenderer.get(); if (!rdata || rdata->GetKey() != key) { rdata = m->vertexRenderer->CreateModelData(key, model); model->SetRenderData(rdata); model->SetDirty(~0u); } m->submissions[cullGroup].push_back(model); } // Call update for all submitted models and enter the rendering phase void ShaderModelRenderer::PrepareModels() { for (int cullGroup = 0; cullGroup < CRenderer::CULL_MAX; ++cullGroup) { for (size_t i = 0; i < m->submissions[cullGroup].size(); ++i) { CModel* model = m->submissions[cullGroup][i]; model->ValidatePosition(); CModelRData* rdata = static_cast(model->GetRenderData()); ENSURE(rdata->GetKey() == m->vertexRenderer.get()); m->vertexRenderer->UpdateModelData(model, rdata, rdata->m_UpdateFlags); rdata->m_UpdateFlags = 0; } } } // Clear the submissions list void ShaderModelRenderer::EndFrame() { for (int cullGroup = 0; cullGroup < CRenderer::CULL_MAX; ++cullGroup) m->submissions[cullGroup].clear(); } // Helper structs for ShaderModelRenderer::Render(): struct SMRSortByDistItem { size_t techIdx; CModel* model; float dist; }; struct SMRBatchModel { bool operator()(CModel* a, CModel* b) { if (a->GetModelDef() < b->GetModelDef()) return true; if (b->GetModelDef() < a->GetModelDef()) return false; if (a->GetMaterial().GetDiffuseTexture() < b->GetMaterial().GetDiffuseTexture()) return true; if (b->GetMaterial().GetDiffuseTexture() < a->GetMaterial().GetDiffuseTexture()) return false; return a->GetMaterial().GetStaticUniforms() < b->GetMaterial().GetStaticUniforms(); } }; struct SMRCompareSortByDistItem { bool operator()(const SMRSortByDistItem& a, const SMRSortByDistItem& b) { // Prefer items with greater distance, so we draw back-to-front return (a.dist > b.dist); // (Distances will almost always be distinct, so we don't need to bother // tie-breaking on modeldef/texture/etc) } }; struct SMRMaterialBucketKey { SMRMaterialBucketKey(CStrIntern effect, const CShaderDefines& defines) : effect(effect), defines(defines) { } CStrIntern effect; CShaderDefines defines; bool operator==(const SMRMaterialBucketKey& b) const { return (effect == b.effect && defines == b.defines); } private: SMRMaterialBucketKey& operator=(const SMRMaterialBucketKey&); }; struct SMRMaterialBucketKeyHash { size_t operator()(const SMRMaterialBucketKey& key) const { size_t hash = 0; hash_combine(hash, key.effect.GetHash()); hash_combine(hash, key.defines.GetHash()); return hash; } }; struct SMRTechBucket { CShaderTechniquePtr tech; CModel** models; size_t numModels; // Model list is stored as pointers, not as a std::vector, // so that sorting lists of this struct is fast }; struct SMRCompareTechBucket { bool operator()(const SMRTechBucket& a, const SMRTechBucket& b) { return a.tech < b.tech; } }; void ShaderModelRenderer::Render(const RenderModifierPtr& modifier, const CShaderDefines& context, int cullGroup, int flags) { if (m->submissions[cullGroup].empty()) return; CMatrix3D worldToCam; g_Renderer.GetViewCamera().GetOrientation().GetInverse(worldToCam); /* * Rendering approach: * * m->submissions contains the list of CModels to render. * * The data we need to render a model is: * - CShaderTechnique * - CTexture * - CShaderUniforms * - CModelDef (mesh data) * - CModel (model instance data) * * For efficient rendering, we need to batch the draw calls to minimise state changes. * (Uniform and texture changes are assumed to be cheaper than binding new mesh data, * and shader changes are assumed to be most expensive.) * First, group all models that share a technique to render them together. * Within those groups, sub-group by CModelDef. * Within those sub-groups, sub-sub-group by CTexture. * Within those sub-sub-groups, sub-sub-sub-group by CShaderUniforms. * * Alpha-blended models have to be sorted by distance from camera, * then we can batch as long as the order is preserved. * Non-alpha-blended models can be arbitrarily reordered to maximise batching. * * For each model, the CShaderTechnique is derived from: * - The current global 'context' defines * - The CModel's material's defines * - The CModel's material's shader effect name * * There are a smallish number of materials, and a smaller number of techniques. * * To minimise technique lookups, we first group models by material, * in 'materialBuckets' (a hash table). * * For each material bucket we then look up the appropriate shader technique. * If the technique requires sort-by-distance, the model is added to the * 'sortByDistItems' list with its computed distance. * Otherwise, the bucket's list of models is sorted by modeldef+texture+uniforms, * then the technique and model list is added to 'techBuckets'. * * 'techBuckets' is then sorted by technique, to improve batching when multiple * materials map onto the same technique. * * (Note that this isn't perfect batching: we don't sort across models in * multiple buckets that share a technique. In practice that shouldn't reduce * batching much (we rarely have one mesh used with multiple materials), * and it saves on copying and lets us sort smaller lists.) * * Extra tech buckets are added for the sorted-by-distance models without reordering. * Finally we render by looping over each tech bucket, then looping over the model * list in each, rebinding the GL state whenever it changes. */ Allocators::DynamicArena arena(256 * KiB); using ModelListAllocator = ProxyAllocator; using ModelList_t = std::vector; using MaterialBuckets_t = std::unordered_map< SMRMaterialBucketKey, ModelList_t, SMRMaterialBucketKeyHash, std::equal_to, ProxyAllocator< std::pair, Allocators::DynamicArena> >; MaterialBuckets_t materialBuckets((MaterialBuckets_t::allocator_type(arena))); { PROFILE3("bucketing by material"); for (size_t i = 0; i < m->submissions[cullGroup].size(); ++i) { CModel* model = m->submissions[cullGroup][i]; uint32_t condFlags = 0; const CShaderConditionalDefines& condefs = model->GetMaterial().GetConditionalDefines(); for (size_t j = 0; j < condefs.GetSize(); ++j) { const CShaderConditionalDefines::CondDefine& item = condefs.GetItem(j); int type = item.m_CondType; switch (type) { case DCOND_DISTANCE: { CVector3D modelpos = model->GetTransform().GetTranslation(); float dist = worldToCam.Transform(modelpos).Z; float dmin = item.m_CondArgs[0]; float dmax = item.m_CondArgs[1]; if ((dmin < 0 || dist >= dmin) && (dmax < 0 || dist < dmax)) condFlags |= (1 << j); break; } } } CShaderDefines defs = model->GetMaterial().GetShaderDefines(condFlags); SMRMaterialBucketKey key(model->GetMaterial().GetShaderEffect(), defs); MaterialBuckets_t::iterator it = materialBuckets.find(key); if (it == materialBuckets.end()) { std::pair inserted = materialBuckets.insert( std::make_pair(key, ModelList_t(ModelList_t::allocator_type(arena)))); inserted.first->second.reserve(32); inserted.first->second.push_back(model); } else { it->second.push_back(model); } } } typedef ProxyAllocator SortByDistItemsAllocator; std::vector sortByDistItems((SortByDistItemsAllocator(arena))); typedef ProxyAllocator SortByTechItemsAllocator; std::vector sortByDistTechs((SortByTechItemsAllocator(arena))); // indexed by sortByDistItems[i].techIdx // (which stores indexes instead of CShaderTechniquePtr directly // to avoid the shared_ptr copy cost when sorting; maybe it'd be better // if we just stored raw CShaderTechnique* and assumed the shader manager // will keep it alive long enough) typedef ProxyAllocator TechBucketsAllocator; std::vector techBuckets((TechBucketsAllocator(arena))); { PROFILE3("processing material buckets"); for (MaterialBuckets_t::iterator it = materialBuckets.begin(); it != materialBuckets.end(); ++it) { CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(it->first.effect, context, it->first.defines); // Skip invalid techniques (e.g. from data file errors) if (!tech) continue; if (tech->GetSortByDistance()) { // Add the tech into a vector so we can index it // (There might be duplicates in this list, but that doesn't really matter) if (sortByDistTechs.empty() || sortByDistTechs.back() != tech) sortByDistTechs.push_back(tech); size_t techIdx = sortByDistTechs.size() - 1; // Add each model into sortByDistItems for (size_t i = 0; i < it->second.size(); ++i) { SMRSortByDistItem itemWithDist; itemWithDist.techIdx = techIdx; CModel* model = it->second[i]; itemWithDist.model = model; CVector3D modelpos = model->GetTransform().GetTranslation(); itemWithDist.dist = worldToCam.Transform(modelpos).Z; sortByDistItems.push_back(itemWithDist); } } else { // Sort model list by modeldef+texture, for batching // TODO: This only sorts by base texture. While this is an OK approximation // for most cases (as related samplers are usually used together), it would be better // to take all the samplers into account when sorting here. std::sort(it->second.begin(), it->second.end(), SMRBatchModel()); // Add a tech bucket pointing at this model list SMRTechBucket techBucket = { tech, &it->second[0], it->second.size() }; techBuckets.push_back(techBucket); } } } { PROFILE3("sorting tech buckets"); // Sort by technique, for better batching std::sort(techBuckets.begin(), techBuckets.end(), SMRCompareTechBucket()); } // List of models corresponding to sortByDistItems[i].model // (This exists primarily because techBuckets wants a CModel**; // we could avoid the cost of copying into this list by adding // a stride length into techBuckets and not requiring contiguous CModel*s) std::vector sortByDistModels((ModelListAllocator(arena))); if (!sortByDistItems.empty()) { { PROFILE3("sorting items by dist"); std::sort(sortByDistItems.begin(), sortByDistItems.end(), SMRCompareSortByDistItem()); } { PROFILE3("batching dist-sorted items"); sortByDistModels.reserve(sortByDistItems.size()); // Find runs of distance-sorted models that share a technique, // and create a new tech bucket for each run size_t start = 0; // start of current run size_t currentTechIdx = sortByDistItems[start].techIdx; for (size_t end = 0; end < sortByDistItems.size(); ++end) { sortByDistModels.push_back(sortByDistItems[end].model); size_t techIdx = sortByDistItems[end].techIdx; if (techIdx != currentTechIdx) { // Start of a new run - push the old run into a new tech bucket SMRTechBucket techBucket = { sortByDistTechs[currentTechIdx], &sortByDistModels[start], end - start }; techBuckets.push_back(techBucket); start = end; currentTechIdx = techIdx; } } // Add the tech bucket for the final run SMRTechBucket techBucket = { sortByDistTechs[currentTechIdx], &sortByDistModels[start], sortByDistItems.size() - start }; techBuckets.push_back(techBucket); } } { PROFILE3("rendering bucketed submissions"); size_t idxTechStart = 0; // This vector keeps track of texture changes during rendering. It is kept outside the // loops to avoid excessive reallocations. The token allocation of 64 elements // should be plenty, though it is reallocated below (at a cost) if necessary. typedef ProxyAllocator TextureListAllocator; std::vector currentTexs((TextureListAllocator(arena))); currentTexs.reserve(64); // texBindings holds the identifier bindings in the shader, which can no longer be defined // statically in the ShaderRenderModifier class. texBindingNames uses interned strings to // keep track of when bindings need to be reevaluated. typedef ProxyAllocator BindingListAllocator; std::vector texBindings((BindingListAllocator(arena))); texBindings.reserve(64); typedef ProxyAllocator BindingNamesListAllocator; std::vector texBindingNames((BindingNamesListAllocator(arena))); texBindingNames.reserve(64); while (idxTechStart < techBuckets.size()) { CShaderTechniquePtr currentTech = techBuckets[idxTechStart].tech; // Find runs [idxTechStart, idxTechEnd) in techBuckets of the same technique size_t idxTechEnd; for (idxTechEnd = idxTechStart + 1; idxTechEnd < techBuckets.size(); ++idxTechEnd) { if (techBuckets[idxTechEnd].tech != currentTech) break; } // For each of the technique's passes, render all the models in this run for (int pass = 0; pass < currentTech->GetNumPasses(); ++pass) { currentTech->BeginPass(pass); const CShaderProgramPtr& shader = currentTech->GetShader(pass); int streamflags = shader->GetStreamFlags(); modifier->BeginPass(shader); m->vertexRenderer->BeginPass(streamflags); // When the shader technique changes, textures need to be // rebound, so ensure there are no remnants from the last pass. // (the vector size is set to 0, but memory is not freed) currentTexs.clear(); texBindings.clear(); texBindingNames.clear(); CModelDef* currentModeldef = NULL; CShaderUniforms currentStaticUniforms; for (size_t idx = idxTechStart; idx < idxTechEnd; ++idx) { CModel** models = techBuckets[idx].models; size_t numModels = techBuckets[idx].numModels; for (size_t i = 0; i < numModels; ++i) { CModel* model = models[i]; if (flags && !(model->GetFlags() & flags)) continue; const CMaterial::SamplersVector& samplers = model->GetMaterial().GetSamplers(); size_t samplersNum = samplers.size(); // make sure the vectors are the right virtual sizes, and also // reallocate if there are more samplers than expected. if (currentTexs.size() != samplersNum) { currentTexs.resize(samplersNum, NULL); texBindings.resize(samplersNum, CShaderProgram::Binding()); texBindingNames.resize(samplersNum, CStrIntern()); // ensure they are definitely empty std::fill(texBindings.begin(), texBindings.end(), CShaderProgram::Binding()); std::fill(currentTexs.begin(), currentTexs.end(), (CTexture*)NULL); std::fill(texBindingNames.begin(), texBindingNames.end(), CStrIntern()); } // bind the samplers to the shader for (size_t s = 0; s < samplersNum; ++s) { const CMaterial::TextureSampler& samp = samplers[s]; // check that the handles are current // and reevaluate them if necessary if (texBindingNames[s] != samp.Name || !texBindings[s].Active()) { texBindings[s] = shader->GetTextureBinding(samp.Name); texBindingNames[s] = samp.Name; } // same with the actual sampler bindings CTexture* newTex = samp.Sampler.get(); if (texBindings[s].Active() && newTex != currentTexs[s]) { shader->BindTexture(texBindings[s], newTex->GetHandle()); currentTexs[s] = newTex; } } // Bind modeldef when it changes CModelDef* newModeldef = model->GetModelDef().get(); if (newModeldef != currentModeldef) { currentModeldef = newModeldef; m->vertexRenderer->PrepareModelDef(shader, streamflags, *currentModeldef); } // Bind all uniforms when any change CShaderUniforms newStaticUniforms = model->GetMaterial().GetStaticUniforms(); if (newStaticUniforms != currentStaticUniforms) { currentStaticUniforms = newStaticUniforms; currentStaticUniforms.BindUniforms(shader); } const CShaderRenderQueries& renderQueries = model->GetMaterial().GetRenderQueries(); for (size_t q = 0; q < renderQueries.GetSize(); ++q) { CShaderRenderQueries::RenderQuery rq = renderQueries.GetItem(q); if (rq.first == RQUERY_TIME) { CShaderProgram::Binding binding = shader->GetUniformBinding(rq.second); if (binding.Active()) { double time = g_Renderer.GetTimeManager().GetGlobalTime(); shader->Uniform(binding, time, 0.0f, 0.0f, 0.0f); } } else if (rq.first == RQUERY_WATER_TEX) { WaterManager* WaterMgr = g_Renderer.GetWaterManager(); double time = WaterMgr->m_WaterTexTimer; double period = 1.6; int curTex = static_cast(time * 60.0 / period) % 60; if (WaterMgr->m_RenderWater && WaterMgr->WillRenderFancyWater()) shader->BindTexture(str_waterTex, WaterMgr->m_NormalMap[curTex]); else shader->BindTexture(str_waterTex, g_Renderer.GetTextureManager().GetErrorTexture()); } else if (rq.first == RQUERY_SKY_CUBE) { shader->BindTexture(str_skyCube, g_Renderer.GetSkyManager()->GetSkyCube()); } } modifier->PrepareModel(shader, model); CModelRData* rdata = static_cast(model->GetRenderData()); ENSURE(rdata->GetKey() == m->vertexRenderer.get()); m->vertexRenderer->RenderModel(shader, streamflags, model, rdata); } } m->vertexRenderer->EndPass(streamflags); currentTech->EndPass(pass); } idxTechStart = idxTechEnd; } } } Index: ps/trunk/source/renderer/ModelRenderer.h =================================================================== --- ps/trunk/source/renderer/ModelRenderer.h (revision 23940) +++ ps/trunk/source/renderer/ModelRenderer.h (revision 23941) @@ -1,276 +1,274 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2020 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 . */ /* * Home to the ModelRenderer class, an abstract base class that manages * a per-frame list of submitted models, as well as simple helper * classes. */ #ifndef INCLUDED_MODELRENDERER #define INCLUDED_MODELRENDERER #include #include "graphics/MeshManager.h" #include "graphics/RenderableObject.h" #include "graphics/SColor.h" #include "renderer/VertexArray.h" class RenderModifier; typedef shared_ptr RenderModifierPtr; class LitRenderModifier; typedef shared_ptr LitRenderModifierPtr; class ModelVertexRenderer; typedef shared_ptr ModelVertexRendererPtr; class ModelRenderer; typedef shared_ptr ModelRendererPtr; class CModel; class CShaderDefines; /** * Class CModelRData: Render data that is maintained per CModel. * ModelRenderer implementations may derive from this class to store * per-CModel data. * * The main purpose of this class over CRenderData is to track which * ModelRenderer the render data belongs to (via the key that is passed * to the constructor). When a model changes the renderer it uses * (e.g. via run-time modification of the renderpath configuration), * the old ModelRenderer's render data is supposed to be replaced by * the new data. */ class CModelRData : public CRenderData { public: CModelRData(const void* key) : m_Key(key) { } /** * GetKey: Retrieve the key that can be used to identify the * ModelRenderer that created this data. * * @return The opaque key that was passed to the constructor. */ const void* GetKey() const { return m_Key; } private: /// The key for model renderer identification const void* m_Key; }; /** * Class ModelRenderer: Abstract base class for all model renders. * * A ModelRenderer manages a per-frame list of models. * * It is supposed to be derived in order to create new ways in which * the per-frame list of models can be managed (for batching, for * transparent rendering, etc.) or potentially for rarely used special * effects. * * A typical ModelRenderer will delegate vertex transformation/setup * to a ModelVertexRenderer. * It will delegate fragment stage setup to a RenderModifier. * * For most purposes, you should use a BatchModelRenderer with * specialized ModelVertexRenderer and RenderModifier implementations. * * It is suggested that a derived class implement the provided generic * Render function, however in some cases it may be necessary to supply * a Render function with a different prototype. * * ModelRenderer also contains a number of static helper functions * for building vertex arrays. */ class ModelRenderer { public: ModelRenderer() { } virtual ~ModelRenderer() { } /** * Initialise global settings. * Should be called before using the class. */ static void Init(); /** * Submit: Submit a model for rendering this frame. * * preconditions : The model must not have been submitted to any * ModelRenderer in this frame. Submit may only be called * after EndFrame and before PrepareModels. * * @param model The model that will be added to the list of models * submitted this frame. */ virtual void Submit(int cullGroup, CModel* model) = 0; /** * PrepareModels: Calculate renderer data for all previously * submitted models. * * Must be called before any rendering calls and after all models * for this frame have been submitted. */ virtual void PrepareModels() = 0; /** * EndFrame: Remove all models from the list of submitted * models. */ virtual void EndFrame() = 0; /** * Render: Render submitted models, using the given RenderModifier to setup * the fragment stage. * * @note It is suggested that derived model renderers implement and use * this Render functions. However, a highly specialized model renderer * may need to "disable" this function and provide its own Render function * with a different prototype. * * preconditions : PrepareModels must be called after all models have been * submitted and before calling Render. * * @param modifier The RenderModifier that specifies the fragment stage. * @param flags If flags is 0, all submitted models are rendered. * If flags is non-zero, only models that contain flags in their * CModel::GetFlags() are rendered. */ virtual void Render(const RenderModifierPtr& modifier, const CShaderDefines& context, int cullGroup, int flags) = 0; /** * CopyPositionAndNormals: Copy unanimated object-space vertices and * normals into the given vertex array. * * @param mdef The underlying CModelDef that contains mesh data. * @param Position Points to the array that will receive * position vectors. The array behind the iterator * must be large enough to hold model->GetModelDef()->GetNumVertices() * vertices. * @param Normal Points to the array that will receive normal vectors. * The array behind the iterator must be as large as the Position array. */ static void CopyPositionAndNormals( const CModelDefPtr& mdef, const VertexArrayIterator& Position, const VertexArrayIterator& Normal); /** * BuildPositionAndNormals: Build animated vertices and normals, * transformed into world space. * * @param model The model that is to be transformed. * @param Position Points to the array that will receive * transformed position vectors. The array behind the iterator * must be large enough to hold model->GetModelDef()->GetNumVertices() * vertices. It must allow 16 bytes to be written to each element * (i.e. provide 4 bytes of padding after each CVector3D). * @param Normal Points to the array that will receive transformed * normal vectors. The array behind the iterator must be as large as * the Position array. */ static void BuildPositionAndNormals( CModel* model, const VertexArrayIterator& Position, const VertexArrayIterator& Normal); /** * BuildColor4ub: Build lighting colors for the given model, * based on previously calculated world space normals. * * @param model The model that is to be lit. * @param Normal Array of the model's normal vectors, animated and * transformed into world space. * @param Color Points to the array that will receive the lit vertex color. * The array behind the iterator must large enough to hold * model->GetModelDef()->GetNumVertices() vertices. */ static void BuildColor4ub( CModel* model, const VertexArrayIterator& Normal, const VertexArrayIterator& Color); /** * BuildUV: Copy UV coordinates into the given vertex array. * * @param mdef The model def. * @param UV Points to the array that will receive UV coordinates. * The array behind the iterator must large enough to hold * mdef->GetNumVertices() vertices. */ static void BuildUV( const CModelDefPtr& mdef, const VertexArrayIterator& UV, int UVset); /** * BuildIndices: Create the indices array for the given CModelDef. * * @param mdef The model definition object. * @param Indices The index array, must be able to hold * mdef->GetNumFaces()*3 elements. */ static void BuildIndices( const CModelDefPtr& mdef, const VertexArrayIterator& Indices); /** * GenTangents: Generate tangents for the given CModelDef. * * @param mdef The model definition object. * @param newVertices An out vector of the unindexed vertices with tangents added. * The new vertices cannot be used with existing face index and must be welded/reindexed. */ static void GenTangents(const CModelDefPtr& mdef, std::vector& newVertices, bool gpuSkinning); }; - -struct ShaderModelRendererInternals; - /** * Implementation of ModelRenderer that loads the appropriate shaders for * rendering each model, and that batches by shader (and by mesh and texture). * * Note that the term "Shader" is somewhat misleading, as this handled * fixed-function rendering using the same API as real GLSL/ARB shaders. */ class ShaderModelRenderer : public ModelRenderer { friend struct ShaderModelRendererInternals; public: ShaderModelRenderer(ModelVertexRendererPtr vertexrender); virtual ~ShaderModelRenderer(); // Batching implementations virtual void Submit(int cullGroup, CModel* model); virtual void PrepareModels(); virtual void EndFrame(); virtual void Render(const RenderModifierPtr& modifier, const CShaderDefines& context, int cullGroup, int flags); private: + struct ShaderModelRendererInternals; ShaderModelRendererInternals* m; }; #endif // INCLUDED_MODELRENDERER