stk-code_catmod/src/graphics/command_buffer.hpp

675 lines
28 KiB
C++

// SuperTuxKart - a fun racing game with go-kart
// Copyright (C) 2015 SuperTuxKart-Team
//
// This program 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 3
// of the License, or (at your option) any later version.
//
// This program 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 this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#ifndef HEADER_COMMAND_BUFFER_HPP
#define HEADER_COMMAND_BUFFER_HPP
#include "graphics/draw_tools.hpp"
#include "graphics/gl_headers.hpp"
#include "graphics/material.hpp"
#include "graphics/materials.hpp"
#include "graphics/render_info.hpp"
#include "graphics/stk_mesh_scene_node.hpp"
#include "graphics/vao_manager.hpp"
#include <irrlicht.h>
#include <array>
#include <unordered_map>
struct InstanceList
{
GLMesh *m_mesh;
std::vector<irr::scene::ISceneNode*> m_scene_nodes;
};
typedef std::unordered_map <std::pair<scene::IMeshBuffer*, RenderInfo*>, InstanceList,
MeshRenderInfoHash, MeshRenderInfoEquals> SolidPassMeshMap;
typedef std::unordered_map <irr::scene::IMeshBuffer *, InstanceList > OtherMeshMap;
// ----------------------------------------------------------------------------
/** Fill origin, orientation and scale attributes
* \param node The scene node of the mesh
* \param[out] instance The instance to fill
*/
template<typename InstanceData>
void fillOriginOrientationScale(scene::ISceneNode *node, InstanceData &instance)
{
const core::matrix4 &mat = node->getAbsoluteTransformation();
const core::vector3df &Origin = mat.getTranslation();
const core::vector3df &Orientation = mat.getRotationDegrees();
const core::vector3df &Scale = mat.getScale();
instance.Origin.X = Origin.X;
instance.Origin.Y = Origin.Y;
instance.Origin.Z = Origin.Z;
instance.Orientation.X = Orientation.X;
instance.Orientation.Y = Orientation.Y;
instance.Orientation.Z = Orientation.Z;
instance.Scale.X = Scale.X;
instance.Scale.Y = Scale.Y;
instance.Scale.Z = Scale.Z;
}
// ----------------------------------------------------------------------------
template<typename InstanceData>
struct InstanceFiller
{
static void add(GLMesh *, scene::ISceneNode *, InstanceData &);
};
// ----------------------------------------------------------------------------
/** Fill a command buffer (in video RAM) with meshes data
* \param instance_list A vector of scene nodes associated with the same mesh
* \param[in,out] instance_buffer Mesh data (position, orientation, textures, etc)
* \param[in,out] command_buffer A pointer to meshes data in VRAM.
* \param[in,out] instance_buffer_offset Current offset for instance_buffer.
* Will be updated to next offset.
* \param[in,out] command_buffer_offset Current offset for command_buffer.
* Will be updated to next offset.
* \param[in,out] poly_count Number of triangles. Will be updated.
*/
template<typename T>
void FillInstances_impl(InstanceList instance_list,
T * instance_buffer,
DrawElementsIndirectCommand *command_buffer,
size_t &instance_buffer_offset,
size_t &command_buffer_offset,
size_t &poly_count)
{
// Should never be empty
GLMesh *mesh = instance_list.m_mesh;
size_t initial_offset = instance_buffer_offset;
for (unsigned i = 0; i < instance_list.m_scene_nodes.size(); i++)
{
scene::ISceneNode *node = instance_list.m_scene_nodes[i];
InstanceFiller<T>::add(mesh, node, instance_buffer[instance_buffer_offset++]);
assert(instance_buffer_offset * sizeof(T) < 10000 * sizeof(InstanceDataThreeTex));
}
DrawElementsIndirectCommand &CurrentCommand = command_buffer[command_buffer_offset++];
CurrentCommand.baseVertex = mesh->vaoBaseVertex;
CurrentCommand.count = mesh->IndexCount;
CurrentCommand.firstIndex = mesh->vaoOffset / 2;
CurrentCommand.baseInstance = initial_offset;
CurrentCommand.instanceCount = instance_buffer_offset - initial_offset;
poly_count += (instance_buffer_offset - initial_offset) * mesh->IndexCount / 3;
}
// ----------------------------------------------------------------------------
/** Bind textures for second rendering pass.
* \param mesh The mesh which owns the textures
* \param prefilled_tex Textures which have been drawn during previous rendering passes.
*/
template<typename T>
void expandTexSecondPass(const GLMesh &mesh,
const std::vector<GLuint> &prefilled_tex)
{
TexExpander<typename T::InstancedSecondPassShader>::template
expandTex(mesh, T::SecondPassTextures, prefilled_tex[0],
prefilled_tex[1], prefilled_tex[2]);
}
template<>
void expandTexSecondPass<GrassMat>(const GLMesh &mesh,
const std::vector<GLuint> &prefilled_tex);
// ----------------------------------------------------------------------------
/** Give acces textures for second rendering pass in shaders
* without first binding them in order to reduce driver overhead.
* (require GL_ARB_bindless_texture extension)
* \param handles The handles to textures which have been drawn
* during previous rendering passes.
*/
template<typename T>
void expandHandlesSecondPass(const std::vector<uint64_t> &handles)
{
uint64_t nulltex[10] = {};
HandleExpander<typename T::InstancedSecondPassShader>::template
expand(nulltex, T::SecondPassTextures,
handles[0], handles[1], handles[2]);
}
template<>
void expandHandlesSecondPass<GrassMat>(const std::vector<uint64_t> &handles);
#if !defined(USE_GLES2)
// ----------------------------------------------------------------------------
/**
* \class CommandBuffer
* \brief Template class to draw meshes with as few draw calls as possible
*
*/
template<int N>
class CommandBuffer
{
protected:
GLuint m_draw_indirect_cmd_id;
DrawElementsIndirectCommand *m_draw_indirect_cmd;
std::array<std::vector<GLMesh *>, N> m_meshes;
std::array<size_t,N> m_offset;
std::array<size_t,N> m_size;
size_t m_poly_count;
size_t m_instance_buffer_offset;
size_t m_command_buffer_offset;
void clearMeshes();
void mapIndirectBuffer();
// ------------------------------------------------------------------------
/** Send in VRAM all meshes associated with same material
* \param material_id The id of the material shared by the meshes
* \param mesh_map List of meshes
* \param[in,out] instance_buffer Meshes data (position, orientation, textures, etc)
*/
template<typename T, typename MeshMap>
void fillMaterial(int material_id,
MeshMap *mesh_map,
T *instance_buffer)
{
m_offset[material_id] = m_command_buffer_offset;
for(auto& instance_list : mesh_map[material_id])
{
FillInstances_impl<T>(instance_list.second,
instance_buffer,
m_draw_indirect_cmd,
m_instance_buffer_offset,
m_command_buffer_offset,
m_poly_count);
if (!CVS->isAZDOEnabled())
m_meshes[material_id].push_back(instance_list.second.m_mesh);
}
m_size[material_id] = m_command_buffer_offset - m_offset[material_id];
}
// ------------------------------------------------------------------------
/** Send into VRAM all meshes associated with same type of material
* \param mesh_map List of meshes to send into VRAM
* \param material_list Ids of materials: meshes associated to these materials
* will be sent into VRAM
* \param instance_type The type of material
*
*/
template<typename InstanceData, typename MeshMap>
void fillInstanceData(MeshMap *mesh_map,
const std::vector<int> &material_list,
InstanceType instance_type)
{
InstanceData *instance_buffer;
if (CVS->supportsAsyncInstanceUpload())
{
instance_buffer = (InstanceData*)VAOManager::getInstance()->
getInstanceBufferPtr(instance_type);
}
else
{
glBindBuffer(GL_ARRAY_BUFFER,
VAOManager::getInstance()->getInstanceBuffer(instance_type));
instance_buffer = (InstanceData*)
glMapBufferRange(GL_ARRAY_BUFFER, 0,
10000 * sizeof(InstanceDataThreeTex),
GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_BUFFER_BIT);
}
for(int material_id: material_list)
{
fillMaterial( material_id,
mesh_map,
instance_buffer);
}
if (!CVS->supportsAsyncInstanceUpload())
{
glUnmapBuffer(GL_ARRAY_BUFFER);
}
}
public:
CommandBuffer();
virtual ~CommandBuffer() { glDeleteBuffers(1, &m_draw_indirect_cmd_id); }
inline size_t getPolyCount() const {return m_poly_count;}
inline void bind() const
{
glBindBuffer(GL_DRAW_INDIRECT_BUFFER, m_draw_indirect_cmd_id);
}
}; //CommandBuffer
// ----------------------------------------------------------------------------
/**
* \class SolidCommandBuffer
* This class is used for rendering meshes during solid first pass
* and solid second pass.
*/
class SolidCommandBuffer: public CommandBuffer<static_cast<int>(Material::SHADERTYPE_COUNT)>
{
public:
SolidCommandBuffer();
void fill(SolidPassMeshMap *mesh_map);
// ----------------------------------------------------------------------------
/** First rendering pass; draw all meshes associated with the same material
* Require OpenGL 4.0 (or higher)
* or GL_ARB_base_instance and GL_ARB_draw_indirect extensions)
*
* \tparam T The material
* \param uniforms Uniforms needed by the shader associated with T material
*/
template<typename T, typename...Uniforms>
void drawIndirectFirstPass(Uniforms...uniforms) const
{
T::InstancedFirstPassShader::getInstance()->use();
T::InstancedFirstPassShader::getInstance()->setUniforms(uniforms...);
glBindVertexArray(VAOManager::getInstance()->getInstanceVAO(T::VertexType,
T::Instance));
for (unsigned i = 0; i < m_meshes[T::MaterialType].size(); i++)
{
GLMesh *mesh = m_meshes[T::MaterialType][i];
#ifdef DEBUG
if (mesh->VAOType != T::VertexType)
{
Log::error("CommandBuffer", "Wrong instanced vertex format (hint : %s)",
mesh->textures[0]->getName().getPath().c_str());
continue;
}
#endif
TexExpander<typename T::InstancedFirstPassShader>::template
expandTex(*mesh, T::FirstPassTextures);
if (!mesh->mb->getMaterial().BackfaceCulling)
glDisable(GL_CULL_FACE);
glDrawElementsIndirect(GL_TRIANGLES,
GL_UNSIGNED_SHORT,
(const void*)((m_offset[T::MaterialType] + i) * sizeof(DrawElementsIndirectCommand)));
if (!mesh->mb->getMaterial().BackfaceCulling)
glEnable(GL_CULL_FACE);
}
} //drawIndirectFirstPass
// ----------------------------------------------------------------------------
/** First rendering pass; draw all meshes associated with the same material
* Faster than drawIndirectFirstPass.
* Require OpenGL AZDO extensions
* \tparam T The material
* \param uniforms Uniforms needed by the shader associated with T material
*/
template<typename T, typename...Uniforms>
void multidrawFirstPass(Uniforms...uniforms) const
{
if (m_size[T::MaterialType])
{
T::InstancedFirstPassShader::getInstance()->use();
T::InstancedFirstPassShader::getInstance()->setUniforms(uniforms...);
glBindVertexArray(VAOManager::getInstance()->getInstanceVAO(T::VertexType,
T::Instance));
glMultiDrawElementsIndirect(GL_TRIANGLES,
GL_UNSIGNED_SHORT,
(const void*)(m_offset[T::MaterialType] * sizeof(DrawElementsIndirectCommand)),
(int) m_size[T::MaterialType],
sizeof(DrawElementsIndirectCommand));
}
} // multidrawFirstPass
// ----------------------------------------------------------------------------
/** Second rendering pass; draw all meshes associated with the same material
* Require OpenGL 4.0 (or higher)
* or GL_ARB_base_instance and GL_ARB_draw_indirect extensions)
*
* \tparam T The material
* \param prefilled_tex Textures filled during previous rendering passes (diffuse, depth, etc)
* \param uniforms Uniforms needed by the shader associated with T material
*/
template<typename T, typename...Uniforms>
void drawIndirectSecondPass(const std::vector<GLuint> &prefilled_tex,
Uniforms...uniforms ) const
{
T::InstancedSecondPassShader::getInstance()->use();
T::InstancedSecondPassShader::getInstance()->setUniforms(uniforms...);
glBindVertexArray(VAOManager::getInstance()->getInstanceVAO(T::VertexType,
T::Instance));
for (unsigned i = 0; i < m_meshes[T::MaterialType].size(); i++)
{
GLMesh *mesh = m_meshes[T::MaterialType][i];
expandTexSecondPass<T>(*mesh, prefilled_tex);
//TODO: next 10 lines are duplicated in draw_tools.hpp (see CustomUnrollArgs::drawMesh)
//TODO: find a way to remove duplicated code
const bool support_change_hue = (mesh->m_render_info != NULL &&
mesh->m_material != NULL);
const bool need_change_hue =
(support_change_hue && mesh->m_render_info->getHue() > 0.0f);
if (need_change_hue)
{
T::InstancedSecondPassShader::getInstance()->changeableColor
(mesh->m_render_info->getHue(),
mesh->m_material->getColorizationFactor());
}
if (!mesh->mb->getMaterial().BackfaceCulling)
glDisable(GL_CULL_FACE);
glDrawElementsIndirect(GL_TRIANGLES,
GL_UNSIGNED_SHORT,
(const void*)((m_offset[T::MaterialType] + i) * sizeof(DrawElementsIndirectCommand)));
if (!mesh->mb->getMaterial().BackfaceCulling)
glEnable(GL_CULL_FACE);
if (need_change_hue)
{
// Reset after changing
T::InstancedSecondPassShader::getInstance()->changeableColor();
}
}
} //drawIndirectSecondPass
// ----------------------------------------------------------------------------
/** Second rendering pass; draw all meshes associated with the same material
* Faster than drawIndirectSecondPass.
* Require OpenGL AZDO extensions
*
* \tparam T The material
* \param handles Handles to textures filled during previous rendering passes
* (diffuse, depth, etc)
* \param uniforms Uniforms needed by the shader associated with T material
*/
template<typename T, typename...Uniforms>
void multidraw2ndPass(const std::vector<uint64_t> &handles,
Uniforms... uniforms) const
{
if (m_size[T::MaterialType])
{
T::InstancedSecondPassShader::getInstance()->use();
T::InstancedSecondPassShader::getInstance()->setUniforms(uniforms...);
glBindVertexArray(VAOManager::getInstance()->getInstanceVAO(T::VertexType,
T::Instance));
expandHandlesSecondPass<T>(handles);
glMultiDrawElementsIndirect(GL_TRIANGLES,
GL_UNSIGNED_SHORT,
(const void*)(m_offset[T::MaterialType] * sizeof(DrawElementsIndirectCommand)),
(int) m_size[T::MaterialType],
sizeof(DrawElementsIndirectCommand));
}
} // multidraw2ndPass
// ----------------------------------------------------------------------------
/** Draw normals (debug): draw all meshes associated with the same material
* Require OpenGL 4.0 (or higher)
* or GL_ARB_base_instance and GL_ARB_draw_indirect extensions)
*
* \tparam T The material
*/
template<typename T>
void drawIndirectNormals() const
{
NormalVisualizer::getInstance()->use();
NormalVisualizer::getInstance()->setUniforms(video::SColor(255, 0, 255, 0));
glBindVertexArray(VAOManager::getInstance()->getInstanceVAO(T::VertexType,
T::Instance));
for (unsigned i = 0; i < m_meshes[T::MaterialType].size(); i++)
{
glDrawElementsIndirect(GL_TRIANGLES,
GL_UNSIGNED_SHORT,
(const void*)((m_offset[T::MaterialType] + i) * sizeof(DrawElementsIndirectCommand))); }
} // drawIndirectNormals
// ----------------------------------------------------------------------------
/** Draw normals (debug): draw all meshes associated with the same material
* Faster than drawIndirectNormals.
* Require OpenGL AZDO extensions
*
* \tparam T The material
*/
template<typename T>
void multidrawNormals() const
{
if (m_size[T::MaterialType])
{
NormalVisualizer::getInstance()->use();
NormalVisualizer::getInstance()->setUniforms(video::SColor(255, 0, 255, 0));
glBindVertexArray(VAOManager::getInstance()->getInstanceVAO(T::VertexType,
T::Instance));
glMultiDrawElementsIndirect(GL_TRIANGLES,
GL_UNSIGNED_SHORT,
(const void*)(m_offset[T::MaterialType] * sizeof(DrawElementsIndirectCommand)),
(int) m_size[T::MaterialType],
sizeof(DrawElementsIndirectCommand));
}
} // multidrawNormals
}; //SolidCommandBuffer
// ----------------------------------------------------------------------------
/**
* \class ShadowCommandBuffer
* This class is used for rendering shadows.
*/
class ShadowCommandBuffer: public CommandBuffer<4*static_cast<int>(Material::SHADERTYPE_COUNT)>
{
public:
ShadowCommandBuffer();
void fill(OtherMeshMap *mesh_map);
// ----------------------------------------------------------------------------
/** Draw shadowmaps for meshes with the same material
* Require OpenGL 4.0 (or higher)
* or GL_ARB_base_instance and GL_ARB_draw_indirect extensions)
*
* \tparam T The material
* \param uniforms Uniforms needed by the shadow shader associated with T material
* \param cascade The cascade id (see cascading shadow maps)
*/
template<typename T, typename...Uniforms>
void drawIndirect(unsigned cascade, Uniforms ...uniforms) const
{
T::InstancedShadowPassShader::getInstance()->use();
T::InstancedShadowPassShader::getInstance()->setUniforms(cascade, uniforms...);
glBindVertexArray(VAOManager::getInstance()->getInstanceVAO(T::VertexType,
InstanceTypeShadow));
int material_id = T::MaterialType + cascade * Material::SHADERTYPE_COUNT;
for (unsigned i = 0; i < m_meshes[material_id].size(); i++)
{
GLMesh *mesh = m_meshes[material_id][i];
TexExpander<typename T::InstancedShadowPassShader>::template
expandTex(*mesh, T::ShadowTextures);
glDrawElementsIndirect(GL_TRIANGLES,
GL_UNSIGNED_SHORT,
(const void*)((m_offset[material_id] + i)
* sizeof(DrawElementsIndirectCommand)));
} // for i
} // drawIndirect
// ----------------------------------------------------------------------------
/** Draw shadowmaps for meshes with the same material
* Faster than drawIndirect.
* Require OpenGL AZDO extensions
*
* \tparam T The material
* \param uniforms Uniforms needed by the shadow shader associated with T material
* \param cascade The cascade id (see cascading shadow maps)
*/
template<typename T, typename...Uniforms>
void multidrawShadow(unsigned cascade, Uniforms ...uniforms) const
{
int material_id = T::MaterialType + cascade * Material::SHADERTYPE_COUNT;
if (m_size[material_id])
{
T::InstancedShadowPassShader::getInstance()->use();
T::InstancedShadowPassShader::getInstance()->setUniforms(cascade, uniforms...);
glBindVertexArray(VAOManager::getInstance()->getInstanceVAO(T::VertexType,
InstanceTypeShadow));
glMultiDrawElementsIndirect(GL_TRIANGLES,
GL_UNSIGNED_SHORT,
(const void*)(m_offset[material_id] * sizeof(DrawElementsIndirectCommand)),
(int) m_size[material_id],
sizeof(DrawElementsIndirectCommand));
}
} // multidrawShadow
}; //ShadowCommandBuffer
// ----------------------------------------------------------------------------
/**
* \class ReflectiveShadowMapCommandBuffer
* This class is used for rendering the reflective shadow map once per track.
*/
class ReflectiveShadowMapCommandBuffer: public CommandBuffer<static_cast<int>(Material::SHADERTYPE_COUNT)>
{
public:
ReflectiveShadowMapCommandBuffer();
void fill(OtherMeshMap *mesh_map);
// ----------------------------------------------------------------------------
/** Draw reflective shadow map for meshes with the same material
* Require OpenGL 4.0 (or higher)
* or GL_ARB_base_instance and GL_ARB_draw_indirect extensions)
*
* \tparam T The material
* \param uniforms Uniforms needed by the shadow shader associated with T material
*/
template<typename T, typename...Uniforms>
void drawIndirect(Uniforms ...uniforms) const
{
T::InstancedRSMShader::getInstance()->use();
T::InstancedRSMShader::getInstance()->setUniforms(uniforms...);
glBindVertexArray(VAOManager::getInstance()->getInstanceVAO(T::VertexType,
InstanceTypeRSM));
for (unsigned i = 0; i < m_meshes[T::MaterialType].size(); i++)
{
GLMesh *mesh = m_meshes[T::MaterialType][i];
TexExpander<typename T::InstancedRSMShader>::template expandTex(*mesh, T::RSMTextures);
glDrawElementsIndirect(GL_TRIANGLES,
GL_UNSIGNED_SHORT,
(const void*)((m_offset[T::MaterialType] + i) * sizeof(DrawElementsIndirectCommand)));
}
} //drawIndirect
// ----------------------------------------------------------------------------
/** Draw reflective shadow map for meshes with the same material
* Faster than drawIndirect.
* Require OpenGL AZDO extensions
*
* \tparam T The material
* \param uniforms Uniforms needed by the shadow shader associated with T material
*/
template<typename T, typename... Uniforms>
void multidraw(Uniforms...uniforms) const
{
if (m_size[T::MaterialType])
{
T::InstancedRSMShader::getInstance()->use();
T::InstancedRSMShader::getInstance()->setUniforms(uniforms...);
glBindVertexArray(VAOManager::getInstance()->getInstanceVAO(T::VertexType,
InstanceTypeRSM));
glMultiDrawElementsIndirect(GL_TRIANGLES,
GL_UNSIGNED_SHORT,
(const void*)(m_offset[T::MaterialType] * sizeof(DrawElementsIndirectCommand)),
(int) m_size[T::MaterialType],
sizeof(DrawElementsIndirectCommand));
}
} // multidraw
}; //ReflectiveShadowMapCommandBuffer
// ----------------------------------------------------------------------------
/**
* \class InstancedColorizeShader
* Draw meshes with glow color.
*/
class InstancedColorizeShader : public Shader<InstancedColorizeShader>
{
public:
InstancedColorizeShader()
{
loadProgram(OBJECT, GL_VERTEX_SHADER, "glow_object.vert",
GL_FRAGMENT_SHADER, "glow_object.frag");
assignUniforms();
} // InstancedColorizeShader
}; // InstancedColorizeShader
// ----------------------------------------------------------------------------
/**
* \class GlowCommandBuffer
* This class is used for rendering glowing meshes.
*/
class GlowCommandBuffer: public CommandBuffer<1>
{
public:
GlowCommandBuffer();
void fill(OtherMeshMap *mesh_map);
// ----------------------------------------------------------------------------
/** Draw glowing meshes.
* Require OpenGL 4.0 (or higher)
* or GL_ARB_base_instance and GL_ARB_draw_indirect extensions)
*/
void drawIndirect() const
{
InstancedColorizeShader::getInstance()->use();
glBindVertexArray(VAOManager::getInstance()->getInstanceVAO(irr::video::EVT_STANDARD,
InstanceTypeGlow));
for (unsigned i = 0; i < m_meshes[0].size(); i++)
{
glDrawElementsIndirect(GL_TRIANGLES,
GL_UNSIGNED_SHORT,
(const void*)((m_offset[0] + i) * sizeof(DrawElementsIndirectCommand)));
}
} //drawIndirect
// ----------------------------------------------------------------------------
/** Draw glowing meshes.
* Faster than drawIndirect.
* Require OpenGL AZDO extensions
*/
void multidraw() const
{
InstancedColorizeShader::getInstance()->use();
glBindVertexArray(VAOManager::getInstance()->getInstanceVAO(irr::video::EVT_STANDARD,
InstanceTypeGlow));
if (m_size[0])
{
glMultiDrawElementsIndirect(GL_TRIANGLES,
GL_UNSIGNED_SHORT,
(const void*)(m_offset[0] * sizeof(DrawElementsIndirectCommand)),
(int) m_size[0],
sizeof(DrawElementsIndirectCommand));
}
} // multidraw
};
#endif // !defined(USE_GLES2)
#endif //HEADER_COMMAND_BUFFER_HPP