Index: binaries/data/mods/public/shaders/glsl/model_common.vs =================================================================== --- binaries/data/mods/public/shaders/glsl/model_common.vs +++ binaries/data/mods/public/shaders/glsl/model_common.vs @@ -12,7 +12,6 @@ uniform vec3 sunDir; uniform vec3 sunColor; #endif -uniform mat4 instancingTransform; #if USE_WIND uniform vec4 sim_time; @@ -56,6 +55,9 @@ attribute vec4 a_skinWeights; #endif +#if USE_INSTANCING +attribute mat4 a_instancing; +#endif vec4 fakeCos(vec4 x) { @@ -77,16 +79,16 @@ n += vec3(m * vec4(a_normal, 0.0)) * a_skinWeights[i]; } } - vec4 position = instancingTransform * vec4(p, 1.0); - mat3 normalMatrix = mat3(instancingTransform[0].xyz, instancingTransform[1].xyz, instancingTransform[2].xyz); + vec4 position = a_instancing * vec4(p, 1.0); + mat3 normalMatrix = mat3(a_instancing[0].xyz, a_instancing[1].xyz, a_instancing[2].xyz); vec3 normal = normalMatrix * normalize(n); #if (USE_NORMAL_MAP || USE_PARALLAX) vec3 tangent = normalMatrix * a_tangent.xyz; #endif #else #if (USE_INSTANCING) - vec4 position = instancingTransform * vec4(a_vertex, 1.0); - mat3 normalMatrix = mat3(instancingTransform[0].xyz, instancingTransform[1].xyz, instancingTransform[2].xyz); + vec4 position = a_instancing * vec4(a_vertex, 1.0); + mat3 normalMatrix = mat3(a_instancing[0].xyz, a_instancing[1].xyz, a_instancing[2].xyz); vec3 normal = normalMatrix * a_normal; #if (USE_NORMAL_MAP || USE_PARALLAX) vec3 tangent = normalMatrix * a_tangent.xyz; @@ -102,7 +104,7 @@ vec2 wind = windData.xy; // fractional part of model position, clamped to >.4 - vec4 modelPos = instancingTransform[3]; + vec4 modelPos = a_instancing[3]; modelPos = fract(modelPos); modelPos = clamp(modelPos, 0.4, 1.0); @@ -113,7 +115,7 @@ // these determine the speed of the wind's "cosine" waves. cosVec.w = 0.0; cosVec.x = sim_time.x * modelPos[0] + position.x; - cosVec.y = sim_time.x * modelPos[2] / 3.0 + instancingTransform[3][0]; + cosVec.y = sim_time.x * modelPos[2] / 3.0 + a_instancing[3][0]; cosVec.z = sim_time.x * abswind / 4.0 + position.z; // calculate "cosines" in parallel, using a smoothed triangle wave Index: binaries/data/mods/public/shaders/glsl/model_common.xml =================================================================== --- binaries/data/mods/public/shaders/glsl/model_common.xml +++ binaries/data/mods/public/shaders/glsl/model_common.xml @@ -5,14 +5,18 @@ - + - + - + + + + + Index: binaries/data/mods/public/shaders/glsl/model_solid.xml =================================================================== --- binaries/data/mods/public/shaders/glsl/model_solid.xml +++ binaries/data/mods/public/shaders/glsl/model_solid.xml @@ -6,6 +6,10 @@ + + + + Index: binaries/data/mods/public/shaders/glsl/model_solid_player.xml =================================================================== --- binaries/data/mods/public/shaders/glsl/model_solid_player.xml +++ binaries/data/mods/public/shaders/glsl/model_solid_player.xml @@ -6,6 +6,10 @@ + + + + Index: binaries/data/mods/public/shaders/glsl/model_solid_tex.xml =================================================================== --- binaries/data/mods/public/shaders/glsl/model_solid_tex.xml +++ binaries/data/mods/public/shaders/glsl/model_solid_tex.xml @@ -8,6 +8,10 @@ + + + + Index: binaries/data/mods/public/shaders/glsl/model_water.vs =================================================================== --- binaries/data/mods/public/shaders/glsl/model_water.vs +++ binaries/data/mods/public/shaders/glsl/model_water.vs @@ -7,19 +7,17 @@ uniform vec3 cameraPos; uniform vec3 sunDir; uniform vec3 sunColor; -uniform mat4 instancingTransform; uniform float sim_time; uniform vec2 translation; attribute vec3 a_vertex; attribute vec3 a_normal; -#if USE_INSTANCING - attribute vec4 a_tangent; -#endif attribute vec2 a_uv0; attribute vec2 a_uv1; +attribute mat4 a_instancing; + varying vec4 worldPos; varying vec4 v_tex; @@ -31,7 +29,7 @@ void main() { - worldPos = instancingTransform * vec4(a_vertex, 1.0); + worldPos = a_instancing * vec4(a_vertex, 1.0); v_tex.xy = (a_uv0 + worldPos.xz) / 5.0 + sim_time * translation; Index: binaries/data/mods/public/shaders/glsl/model_water.xml =================================================================== --- binaries/data/mods/public/shaders/glsl/model_water.xml +++ binaries/data/mods/public/shaders/glsl/model_water.xml @@ -5,12 +5,15 @@ - + - - + + + + + Index: binaries/data/mods/public/shaders/glsl/model_waterfall.vs =================================================================== --- binaries/data/mods/public/shaders/glsl/model_waterfall.vs +++ binaries/data/mods/public/shaders/glsl/model_waterfall.vs @@ -7,7 +7,6 @@ uniform vec3 cameraPos; uniform vec3 sunDir; uniform vec3 sunColor; -uniform mat4 instancingTransform; uniform float sim_time; uniform vec2 translation; @@ -17,6 +16,8 @@ attribute vec2 a_uv0; attribute vec2 a_uv1; +attribute mat4 a_instancing; + varying vec4 worldPos; varying vec4 v_tex; varying vec3 v_half; @@ -26,7 +27,7 @@ void main() { - worldPos = instancingTransform * vec4(a_vertex, 1.0); + worldPos = a_instancing * vec4(a_vertex, 1.0); v_tex.xy = a_uv0 + sim_time * translation; v_transp = a_uv1.x; @@ -39,7 +40,7 @@ vec3 sunVec = -sunDir; v_half = normalize(sunVec + normalize(eyeVec)); - mat3 normalMatrix = mat3(instancingTransform[0].xyz, instancingTransform[1].xyz, instancingTransform[2].xyz); + mat3 normalMatrix = mat3(a_instancing[0].xyz, a_instancing[1].xyz, a_instancing[2].xyz); v_normal = normalMatrix * a_normal; v_lighting = max(0.0, dot(v_normal, -sunDir)) * sunColor; Index: binaries/data/mods/public/shaders/glsl/model_waterfall.xml =================================================================== --- binaries/data/mods/public/shaders/glsl/model_waterfall.xml +++ binaries/data/mods/public/shaders/glsl/model_waterfall.xml @@ -5,12 +5,15 @@ - + - - + + + + + Index: source/graphics/Model.h =================================================================== --- source/graphics/Model.h +++ source/graphics/Model.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -112,6 +112,8 @@ // get the currently playing animation, if any CSkeletonAnim* GetAnimation() const { return m_Anim; } + float GetAnimTime() const { return m_AnimTime; } + // set the animation state to be the same as from another; both models should // be compatible types (same type of skeleton) void CopyAnimationFrom(CModel* source); Index: source/graphics/ShaderProgram.h =================================================================== --- source/graphics/ShaderProgram.h +++ source/graphics/ShaderProgram.h @@ -189,7 +189,7 @@ virtual void TexCoordPointer(GLenum texture, GLint size, GLenum type, GLsizei stride, const void* pointer); virtual void VertexAttribPointer(attrib_id_t id, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const void* pointer); virtual void VertexAttribIPointer(attrib_id_t id, GLint size, GLenum type, GLsizei stride, const void* pointer); - + virtual void VertexAttribDivisor(attrib_id_t id, GLuint divisor); /** * Checks that all the required vertex attributes have been set. * Call this before calling glDrawArrays/glDrawElements etc to avoid potential crashes. Index: source/graphics/ShaderProgram.cpp =================================================================== --- source/graphics/ShaderProgram.cpp +++ source/graphics/ShaderProgram.cpp @@ -676,6 +676,16 @@ } } + virtual void VertexAttribDivisor(attrib_id_t id, GLuint divisor) + { + std::map::iterator it = m_VertexAttribs.find(id); + if (it != m_VertexAttribs.end()) + { + pglVertexAttribDivisorARB(it->second, divisor); + } + } + + virtual std::vector GetFileDependencies() const { return m_FileDependencies; @@ -832,6 +842,11 @@ debug_warn("Shader type doesn't support VertexAttribIPointer"); } +void CShaderProgram::VertexAttribDivisor(attrib_id_t UNUSED(id), GLuint UNUSED(divisor)) +{ + debug_warn("Shader type doesn't support VertexAttribDivisor"); +} + #if CONFIG2_GLES // These should all be overridden by CShaderProgramGLSL Index: source/lib/external_libraries/glext_funcs.h =================================================================== --- source/lib/external_libraries/glext_funcs.h +++ source/lib/external_libraries/glext_funcs.h @@ -364,6 +364,10 @@ FUNC2(void, glBindFragDataLocationEXT, glBindFragDataLocation, "3.0", (GLuint program, GLuint colorNumber, const char *name)) FUNC2(GLint, glGetFragDataLocationEXT, glGetFragDataLocation, "3.0", (GLuint program, const char *name)) +// GL_ARB_draw_instanced / GL 3.3 +FUNC2(void, glDrawElementsInstancedARB, glDrawElementsInstanced, "3.3", (GLenum mode, GLsizei count, GLenum type, const GLvoid * indices, GLsizei primcount)) +FUNC2(void, glVertexAttribDivisorARB, glVertexAttribDivisor, "3.3", (GLuint index, GLuint divisor)) + // GL_ARB_occlusion_query / GL1.5: FUNC2(void, glGenQueriesARB, glGenQueries, "1.5", (GLsizei n, GLuint *ids)) FUNC2(void, glDeleteQueriesARB, glDeleteQueries, "1.5", (GLsizei n, const GLuint *ids)) Index: source/ps/CStrInternStatic.h =================================================================== --- source/ps/CStrInternStatic.h +++ source/ps/CStrInternStatic.h @@ -67,6 +67,10 @@ X(WATERTYPE_LAKE) X2(_emptystring, "") X(a_apexPosition) +X(a_instancing) +X(a_instancing1) +X(a_instancing2) +X(a_instancing3) X(a_otherPosition) X(a_retreatPosition) X(a_skinJoints) Index: source/ps/GameSetup/GameSetup.cpp =================================================================== --- source/ps/GameSetup/GameSetup.cpp +++ source/ps/GameSetup/GameSetup.cpp @@ -337,6 +337,7 @@ g_Renderer.EndFrame(); PROFILE2_ATTR("draw calls: %d", (int)g_Renderer.GetStats().m_DrawCalls); + PROFILE2_ATTR("saved draw calls: %d", (int)g_Renderer.GetStats().m_SavedDrawCalls); PROFILE2_ATTR("terrain tris: %d", (int)g_Renderer.GetStats().m_TerrainTris); PROFILE2_ATTR("water tris: %d", (int)g_Renderer.GetStats().m_WaterTris); PROFILE2_ATTR("model tris: %d", (int)g_Renderer.GetStats().m_ModelTris); Index: source/renderer/HWLightingModelRenderer.h =================================================================== --- source/renderer/HWLightingModelRenderer.h +++ source/renderer/HWLightingModelRenderer.h @@ -40,10 +40,13 @@ CModelRData* CreateModelData(const void* key, CModel* model); void UpdateModelData(CModel* model, CModelRData* data, int updateflags); - void BeginPass(int streamflags); + bool CanInstance() const { return false; } + + void BeginPass(const CShaderProgramPtr& shader, 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); + void RenderModel(const CShaderProgramPtr& shader, int streamflags, CModel* model); + void RenderModels(const CShaderProgramPtr& shader, int streamflags, const std::vector& models); protected: struct ShaderModelRendererInternals; Index: source/renderer/HWLightingModelRenderer.cpp =================================================================== --- source/renderer/HWLightingModelRenderer.cpp +++ source/renderer/HWLightingModelRenderer.cpp @@ -174,7 +174,7 @@ // Setup one rendering pass -void ShaderModelVertexRenderer::BeginPass(int streamflags) +void ShaderModelVertexRenderer::BeginPass(const CShaderProgramPtr& UNUSED(shader), int streamflags) { ENSURE(streamflags == (streamflags & (STREAM_POS | STREAM_UV0 | STREAM_UV1 | STREAM_NORMAL))); } @@ -205,10 +205,10 @@ // Render one model -void ShaderModelVertexRenderer::RenderModel(const CShaderProgramPtr& shader, int streamflags, CModel* model, CModelRData* data) +void ShaderModelVertexRenderer::RenderModel(const CShaderProgramPtr& shader, int streamflags, CModel* model) { const CModelDefPtr& mdldef = model->GetModelDef(); - ShaderModel* shadermodel = static_cast(data); + ShaderModel* shadermodel = static_cast(model->GetRenderData()); u8* base = shadermodel->m_Array.Bind(); GLsizei stride = (GLsizei)shadermodel->m_Array.GetStride(); @@ -242,3 +242,8 @@ g_Renderer.m_Stats.m_ModelTris += numFaces; } +void ShaderModelVertexRenderer::RenderModels(const CShaderProgramPtr& shader, int streamflags, const std::vector& models) +{ + for (CModel* model : models) + RenderModel(shader, streamflags, model); +} Index: source/renderer/InstancingModelRenderer.h =================================================================== --- source/renderer/InstancingModelRenderer.h +++ source/renderer/InstancingModelRenderer.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -35,17 +35,19 @@ class InstancingModelRenderer : public ModelVertexRenderer { public: - InstancingModelRenderer(bool gpuSkinning, bool calculateTangents); + InstancingModelRenderer(bool gpuSkinning, bool calculateTangents, bool realInstancing); ~InstancingModelRenderer(); // Implementations CModelRData* CreateModelData(const void* key, CModel* model); void UpdateModelData(CModel* model, CModelRData* data, int updateflags); - void BeginPass(int streamflags); + void BeginPass(const CShaderProgramPtr& shader, 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); + bool CanInstance() const; + void RenderModel(const CShaderProgramPtr& shader, int streamflags, CModel* model); + void RenderModels(const CShaderProgramPtr& shader, int streamflags, const std::vector& models); protected: InstancingModelRendererInternals* m; Index: source/renderer/InstancingModelRenderer.cpp =================================================================== --- source/renderer/InstancingModelRenderer.cpp +++ source/renderer/InstancingModelRenderer.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -239,32 +239,47 @@ struct InstancingModelRendererInternals { bool gpuSkinning; - bool calculateTangents; + bool realInstancing; /// Previously prepared modeldef IModelDef* imodeldef; /// Index base for imodeldef u8* imodeldefIndexBase; -}; + // Used in fake instancing. + CShaderProgram::Binding bindingInstancingTransform; + // Used in real instancing. + GLuint buff; + std::vector transforms; +}; // Construction and Destruction -InstancingModelRenderer::InstancingModelRenderer(bool gpuSkinning, bool calculateTangents) +InstancingModelRenderer::InstancingModelRenderer(bool gpuSkinning, bool calculateTangents, bool realInstancing) { m = new InstancingModelRendererInternals; m->gpuSkinning = gpuSkinning; m->calculateTangents = calculateTangents; + m->realInstancing = realInstancing; m->imodeldef = 0; + m->transforms.reserve(64); + + pglGenBuffersARB(1, &m->buff); } InstancingModelRenderer::~InstancingModelRenderer() { + pglDeleteBuffersARB(1, &m->buff); delete m; } +bool InstancingModelRenderer::CanInstance() const +{ + return m->realInstancing; +} + // Build modeldef data if necessary - we have no per-CModel data CModelRData* InstancingModelRenderer::CreateModelData(const void* key, CModel* model) { @@ -291,11 +306,18 @@ // We have no per-CModel data } - // Setup one rendering pass. -void InstancingModelRenderer::BeginPass(int streamflags) +void InstancingModelRenderer::BeginPass(const CShaderProgramPtr& shader, int streamflags) { ENSURE(streamflags == (streamflags & (STREAM_POS|STREAM_NORMAL|STREAM_UV0|STREAM_UV1))); + + if (m->realInstancing) + { + shader->VertexAttribDivisor(str_a_instancing, 1); + shader->VertexAttribDivisor(str_a_instancing1, 1); + shader->VertexAttribDivisor(str_a_instancing2, 1); + shader->VertexAttribDivisor(str_a_instancing3, 1); + } } // Cleanup rendering pass. @@ -343,14 +365,25 @@ shader->VertexAttribPointer(str_a_skinWeights, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, base + m->imodeldef->m_BlendWeights.offset); } + if (!m->realInstancing) + m->bindingInstancingTransform = shader->GetUniformBinding(str_instancingTransform); + shader->AssertPointersBound(); } // Render one model -void InstancingModelRenderer::RenderModel(const CShaderProgramPtr& shader, int UNUSED(streamflags), CModel* model, CModelRData* UNUSED(data)) +void InstancingModelRenderer::RenderModel(const CShaderProgramPtr& shader, int streamflags, CModel* model) { - const CModelDefPtr& mdldef = model->GetModelDef(); + RenderModels(shader, streamflags, std::vector{ model }); +} + +void InstancingModelRenderer::RenderModels(const CShaderProgramPtr& shader, int UNUSED(streamflags), const std::vector& models) +{ + if (g_Renderer.m_SkipSubmit) + return; + + const CModelDefPtr& mdldef = models.front()->GetModelDef(); if (m->gpuSkinning) { @@ -359,26 +392,42 @@ // HACK: NVIDIA drivers return uniform name with "[0]", Intel Windows drivers without; // try uploading both names since one of them should work, and this is easier than // canonicalising the uniform names in CShaderProgramGLSL - shader->Uniform(str_skinBlendMatrices_0, mdldef->GetNumBones() + 1, model->GetAnimatedBoneMatrices()); - shader->Uniform(str_skinBlendMatrices, mdldef->GetNumBones() + 1, model->GetAnimatedBoneMatrices()); + shader->Uniform(str_skinBlendMatrices_0, mdldef->GetNumBones() + 1, models.front()->GetAnimatedBoneMatrices()); + shader->Uniform(str_skinBlendMatrices, mdldef->GetNumBones() + 1, models.front()->GetAnimatedBoneMatrices()); + } + + if (m->realInstancing) + { + // Set up a uniform + m->transforms.clear(); + for (CModel* model : models) + m->transforms.emplace_back(model->GetTransform()); + + pglBindBufferARB(GL_ARRAY_BUFFER, m->buff); + // The OpenGL doc states that buffer orphaninh should ideally be done with the same buffer size, + // so this uses a minimum size. + const size_t arbitrary_instance_threshold = 50; + pglBufferDataARB(GL_ARRAY_BUFFER, std::max(64 * arbitrary_instance_threshold, 64 * models.size()), nullptr, GL_STREAM_DRAW); + pglBufferSubDataARB(GL_ARRAY_BUFFER, 0, 64 * models.size(), m->transforms.data()); + + shader->VertexAttribPointer(str_a_instancing, 4, GL_FLOAT, GL_FALSE, 64, (void*)(0)); + shader->VertexAttribPointer(str_a_instancing1, 4, GL_FLOAT, GL_FALSE, 64, (void*)(0+16)); + shader->VertexAttribPointer(str_a_instancing2, 4, GL_FLOAT, GL_FALSE, 64, (void*)(0+32)); + shader->VertexAttribPointer(str_a_instancing3, 4, GL_FLOAT, GL_FALSE, 64, (void*)(0+48)); + } + else + { + ENSURE(models.size() == 1); + shader->Uniform(m->bindingInstancingTransform, models.front()->GetTransform()); } - // render the lot size_t numFaces = mdldef->GetNumFaces(); + if (models.size() > 1) + pglDrawElementsInstancedARB(GL_TRIANGLES, (GLsizei)numFaces*3, GL_UNSIGNED_SHORT, m->imodeldefIndexBase, models.size()); + else + pglDrawRangeElementsEXT(GL_TRIANGLES, 0, (GLuint)mdldef->GetNumVertices()-1, (GLsizei)numFaces*3, GL_UNSIGNED_SHORT, m->imodeldefIndexBase); - 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, m->imodeldefIndexBase); -#else - pglDrawRangeElementsEXT(GL_TRIANGLES, 0, (GLuint)m->imodeldef->m_Array.GetNumVertices()-1, - (GLsizei)numFaces*3, GL_UNSIGNED_SHORT, m->imodeldefIndexBase); -#endif - } - - // bump stats g_Renderer.m_Stats.m_DrawCalls++; - g_Renderer.m_Stats.m_ModelTris += numFaces; - + g_Renderer.m_Stats.m_SavedDrawCalls += models.size() - 1; + g_Renderer.m_Stats.m_ModelTris += numFaces * models.size(); } Index: source/renderer/ModelRenderer.cpp =================================================================== --- source/renderer/ModelRenderer.cpp +++ source/renderer/ModelRenderer.cpp @@ -277,6 +277,11 @@ if (b->GetMaterial().GetDiffuseTexture() < a->GetMaterial().GetDiffuseTexture()) return false; + if (a->GetPlayerID() < b->GetPlayerID()) + return true; + if (b->GetPlayerID() < a->GetPlayerID()) + return false; + return a->GetMaterial().GetStaticUniforms() < b->GetMaterial().GetStaticUniforms(); } }; @@ -605,6 +610,10 @@ std::vector texBindingNames((BindingNamesListAllocator(arena))); texBindingNames.reserve(64); + // TODO C++20: use span for ModelRenderers and use an Arena allocator here. + std::vector keptModels; + keptModels.reserve(64); + while (idxTechStart < techBuckets.size()) { CShaderTechniquePtr currentTech = techBuckets[idxTechStart].tech; @@ -627,7 +636,7 @@ modifier->BeginPass(shader); - m->vertexRenderer->BeginPass(streamflags); + m->vertexRenderer->BeginPass(shader, streamflags); // When the shader technique changes, textures need to be // rebound, so ensure there are no remnants from the last pass. @@ -636,8 +645,31 @@ texBindings.clear(); texBindingNames.clear(); - CModelDef* currentModeldef = NULL; + CModelDef* currentModeldef = nullptr; CShaderUniforms currentStaticUniforms; + bool rq_water = false; + bool rq_skycube = false; + bool rq_time = false; + + CSkeletonAnim* currentAnim = nullptr; + float currentAnimTime = 0.f; + + CColor shadingcolor; + player_id_t playerid = INVALID_PLAYER; + + // This must be called before changing state. + auto RenderKeptModels = [this, &shader, &keptModels, &modifier, &streamflags]() + { + if (keptModels.empty()) + return; +#if 0 + printf("Rendered %i %s, anim %p\n", keptModels.size(), keptModels.front()->GetModelDef()->GetName().string8().c_str(), (void*)keptModels.front()->GetAnimation()); +#endif + + modifier->PrepareModel(shader, keptModels.front()); + m->vertexRenderer->RenderModels(shader, streamflags, keptModels); + keptModels.clear(); + }; for (size_t idx = idxTechStart; idx < idxTechEnd; ++idx) { @@ -684,6 +716,8 @@ CTexture* newTex = samp.Sampler.get(); if (texBindings[s].Active() && newTex != currentTexs[s]) { + RenderKeptModels(); + shader->BindTexture(texBindings[s], newTex->GetHandle()); currentTexs[s] = newTex; } @@ -693,6 +727,8 @@ CModelDef* newModeldef = model->GetModelDef().get(); if (newModeldef != currentModeldef) { + RenderKeptModels(); + currentModeldef = newModeldef; m->vertexRenderer->PrepareModelDef(shader, streamflags, *currentModeldef); } @@ -701,49 +737,80 @@ CShaderUniforms newStaticUniforms = model->GetMaterial().GetStaticUniforms(); if (newStaticUniforms != currentStaticUniforms) { + RenderKeptModels(); + currentStaticUniforms = newStaticUniforms; currentStaticUniforms.BindUniforms(shader); } const CShaderRenderQueries& renderQueries = model->GetMaterial().GetRenderQueries(); + // So long as the shader technique isn't reset, + // the render query uniforms & texture bindings will remain valid. + // It also doesn't matter if they're bound too early. for (size_t q = 0; q < renderQueries.GetSize(); ++q) { CShaderRenderQueries::RenderQuery rq = renderQueries.GetItem(q); - if (rq.first == RQUERY_TIME) + // Time is an uniform - once set, we're good until the shader program is changed + // and it does not matter much if we bind too early either. + if (rq.first == RQUERY_TIME && !rq_time) { CShaderProgram::Binding binding = shader->GetUniformBinding(rq.second); if (binding.Active()) { + rq_time = true; double time = g_Renderer.GetTimeManager().GetGlobalTime(); shader->Uniform(binding, time, 0.0f, 0.0f, 0.0f); } + } - else if (rq.first == RQUERY_WATER_TEX) + else if (rq.first == RQUERY_WATER_TEX && !rq_water) { + rq_water = true; 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) + else if (rq.first == RQUERY_SKY_CUBE && !rq_skycube) { + rq_skycube = true; shader->BindTexture(str_skyCube, g_Renderer.GetSkyManager()->GetSkyCube()); } } - modifier->PrepareModel(shader, model); + if (model->GetPlayerID() != playerid || model->GetShadingColor() != shadingcolor) + { + RenderKeptModels(); + playerid = model->GetPlayerID(); + shadingcolor = model->GetShadingColor(); + modifier->PrepareModel(shader, model); + } + + /** + * During instanced rendering, we need to make sure all models + * are on the same frame of animation (if relevant). + */ + if (m->vertexRenderer->CanInstance() && + (model->GetAnimation() != currentAnim || (model->GetAnimation() && model->GetAnimTime() != currentAnimTime))) + { + RenderKeptModels(); + currentAnim = model->GetAnimation(); + currentAnimTime = model->GetAnimTime(); + } CModelRData* rdata = static_cast(model->GetRenderData()); ENSURE(rdata->GetKey() == m->vertexRenderer.get()); - - m->vertexRenderer->RenderModel(shader, streamflags, model, rdata); - } + if (m->vertexRenderer->CanInstance()) + keptModels.emplace_back(model); + else + m->vertexRenderer->RenderModel(shader, streamflags, model); + } // numModels loop + RenderKeptModels(); } m->vertexRenderer->EndPass(streamflags); Index: source/renderer/ModelVertexRenderer.h =================================================================== --- source/renderer/ModelVertexRenderer.h +++ source/renderer/ModelVertexRenderer.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -101,7 +101,7 @@ * * @param streamflags Vertex streams required by the fragment stage. */ - virtual void BeginPass(int streamflags) = 0; + virtual void BeginPass(const CShaderProgramPtr& shader, int streamflags) = 0; /** @@ -133,6 +133,7 @@ */ virtual void PrepareModelDef(const CShaderProgramPtr& shader, int streamflags, const CModelDef& def) = 0; + virtual bool CanInstance() const = 0; /** * RenderModel: Invoke the rendering commands for the given model. @@ -153,7 +154,8 @@ * that use the same CModelDef object and the same texture must * succeed. */ - virtual void RenderModel(const CShaderProgramPtr& shader, int streamflags, CModel* model, CModelRData* data) = 0; + virtual void RenderModel(const CShaderProgramPtr& shader, int streamflags, CModel* model) = 0; + virtual void RenderModels(const CShaderProgramPtr& shader, int streamflags, const std::vector& models) = 0; }; Index: source/renderer/RenderModifiers.h =================================================================== --- source/renderer/RenderModifiers.h +++ source/renderer/RenderModifiers.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2012 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -126,7 +126,6 @@ void PrepareModel(const CShaderProgramPtr& shader, CModel* model); private: - CShaderProgram::Binding m_BindingInstancingTransform; CShaderProgram::Binding m_BindingShadingColor; CShaderProgram::Binding m_BindingPlayerColor; }; Index: source/renderer/RenderModifiers.cpp =================================================================== --- source/renderer/RenderModifiers.cpp +++ source/renderer/RenderModifiers.cpp @@ -97,16 +97,12 @@ shader->Uniform(str_losTransform, los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f); } - m_BindingInstancingTransform = shader->GetUniformBinding(str_instancingTransform); m_BindingShadingColor = shader->GetUniformBinding(str_shadingColor); m_BindingPlayerColor = shader->GetUniformBinding(str_playerColor); } void ShaderRenderModifier::PrepareModel(const CShaderProgramPtr& shader, CModel* model) { - if (m_BindingInstancingTransform.Active()) - shader->Uniform(m_BindingInstancingTransform, model->GetTransform()); - if (m_BindingShadingColor.Active()) shader->Uniform(m_BindingShadingColor, model->GetShadingColor()); Index: source/renderer/Renderer.h =================================================================== --- source/renderer/Renderer.h +++ source/renderer/Renderer.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2021 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -90,6 +90,8 @@ void Reset() { memset(this, 0, sizeof(*this)); } // number of draw calls per frame - total DrawElements + Begin/End immediate mode loops size_t m_DrawCalls; + // Number of saved draw calls via instancing each frame. + size_t m_SavedDrawCalls; // number of terrain triangles drawn size_t m_TerrainTris; // number of water triangles drawn @@ -112,6 +114,7 @@ bool m_FragmentShader; bool m_Shadows; bool m_PrettyWater; + bool m_Instancing; }; public: Index: source/renderer/Renderer.cpp =================================================================== --- source/renderer/Renderer.cpp +++ source/renderer/Renderer.cpp @@ -114,6 +114,7 @@ enum { Row_DrawCalls = 0, + Row_SavedDrawCalls, Row_TerrainTris, Row_WaterTris, Row_ModelTris, @@ -171,6 +172,12 @@ sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_DrawCalls); return buf; + case Row_SavedDrawCalls: + if (col == 0) + return "# saved draw calls"; + sprintf_s(buf, sizeof(buf), "%lu", (unsigned long)Stats.m_SavedDrawCalls); + return buf; + case Row_TerrainTris: if (col == 0) return "# terrain tris"; @@ -473,6 +480,7 @@ m_Caps.m_FragmentShader = false; m_Caps.m_Shadows = false; m_Caps.m_PrettyWater = false; + m_Caps.m_Instancing = false; // now start querying extensions if (!g_RenderingOptions.GetNoVBO() && ogl_HaveExtension("GL_ARB_vertex_buffer_object")) @@ -493,6 +501,9 @@ m_Caps.m_FragmentShader = true; } + if (0 == ogl_HaveExtensions(0, "GL_ARB_draw_instanced", "GL_ARB_instanced_arrays", NULL)) + m_Caps.m_Instancing = true; + #if CONFIG2_GLES m_Caps.m_Shadows = true; #else @@ -552,11 +563,11 @@ ENSURE(g_RenderingOptions.GetRenderPath() != RenderPath::FIXED); m->Model.VertexRendererShader = ModelVertexRendererPtr(new ShaderModelVertexRenderer()); - m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer(false, g_RenderingOptions.GetPreferGLSL())); + m->Model.VertexInstancingShader = ModelVertexRendererPtr(new InstancingModelRenderer(false, g_RenderingOptions.GetPreferGLSL(), g_RenderingOptions.GetPreferGLSL())); if (g_RenderingOptions.GetGPUSkinning()) // TODO: should check caps and GLSL etc too { - m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new InstancingModelRenderer(true, g_RenderingOptions.GetPreferGLSL())); + m->Model.VertexGPUSkinningShader = ModelVertexRendererPtr(new InstancingModelRenderer(true, g_RenderingOptions.GetPreferGLSL(), g_RenderingOptions.GetPreferGLSL())); m->Model.NormalSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader)); m->Model.TranspSkinned = ModelRendererPtr(new ShaderModelRenderer(m->Model.VertexGPUSkinningShader)); }