Changeset View
Standalone View
source/renderer/InstancingModelRenderer.cpp
Show All 20 Lines | |||||
#include "graphics/Color.h" | #include "graphics/Color.h" | ||||
#include "graphics/LightEnv.h" | #include "graphics/LightEnv.h" | ||||
#include "graphics/Model.h" | #include "graphics/Model.h" | ||||
#include "graphics/ModelDef.h" | #include "graphics/ModelDef.h" | ||||
#include "lib/ogl.h" | #include "lib/ogl.h" | ||||
#include "maths/Vector3D.h" | #include "maths/Vector3D.h" | ||||
#include "maths/Vector4D.h" | #include "maths/Vector4D.h" | ||||
#include "ps/CLogger.h" | #include "ps/CLogger.h" | ||||
#include "ps/ConfigDB.h" | |||||
#include "renderer/Renderer.h" | #include "renderer/Renderer.h" | ||||
#include "renderer/RenderModifiers.h" | #include "renderer/RenderModifiers.h" | ||||
#include "renderer/VertexArray.h" | #include "renderer/VertexArray.h" | ||||
#include "third_party/mikktspace/weldmesh.h" | #include "third_party/mikktspace/weldmesh.h" | ||||
struct IModelDef : public CModelDefRPrivate | struct IModelDef : public CModelDefRPrivate | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 217 Lines • ▼ Show 20 Lines | |||||
InstancingModelRenderer::InstancingModelRenderer(bool gpuSkinning, bool calculateTangents) | InstancingModelRenderer::InstancingModelRenderer(bool gpuSkinning, bool calculateTangents) | ||||
{ | { | ||||
m = new InstancingModelRendererInternals; | m = new InstancingModelRendererInternals; | ||||
m->gpuSkinning = gpuSkinning; | m->gpuSkinning = gpuSkinning; | ||||
m->calculateTangents = calculateTangents; | m->calculateTangents = calculateTangents; | ||||
m->imodeldef = 0; | m->imodeldef = 0; | ||||
} | } | ||||
InstancingModelRenderer::~InstancingModelRenderer() | InstancingModelRenderer::~InstancingModelRenderer() | ||||
vladislavbelov: If you don't use VertexBuffer then you need to wait some my refactorings. Because it's bad to… | |||||
Done Inline ActionsThis really isn't high/mid-level code, to be honest. It's the code that actually draws stuff, it's as low-level as you get in our renderer architecture. I get your concern though, I don't really mind waiting, for now anyways. wraitii: This really isn't high/mid-level code, to be honest. It's the code that actually draws stuff… | |||||
Not Done Inline ActionsIf a level knows about models and materials then it's not the low level, it's at least middle-level. Low-level includes direct GL calls and knowing about GPU structure. vladislavbelov: If a level knows about models and materials then it's not the low level, it's at least middle… | |||||
{ | { | ||||
delete m; | delete m; | ||||
Not Done Inline ActionsShould use VertexBuffer instead. vladislavbelov: Should use `VertexBuffer` instead. | |||||
} | } | ||||
// Build modeldef data if necessary - we have no per-CModel data | // Build modeldef data if necessary - we have no per-CModel data | ||||
CModelRData* InstancingModelRenderer::CreateModelData(const void* key, CModel* model) | CModelRData* InstancingModelRenderer::CreateModelData(const void* key, CModel* model) | ||||
{ | { | ||||
CModelDefPtr mdef = model->GetModelDef(); | CModelDefPtr mdef = model->GetModelDef(); | ||||
IModelDef* imodeldef = (IModelDef*)mdef->GetRenderData(m); | IModelDef* imodeldef = (IModelDef*)mdef->GetRenderData(m); | ||||
Show All 27 Lines | |||||
// Cleanup rendering pass. | // Cleanup rendering pass. | ||||
void InstancingModelRenderer::EndPass(int UNUSED(streamflags)) | void InstancingModelRenderer::EndPass(int UNUSED(streamflags)) | ||||
{ | { | ||||
CVertexBuffer::Unbind(); | CVertexBuffer::Unbind(); | ||||
} | } | ||||
static CShaderProgram::Binding instancingTransformBinding; | |||||
// Prepare UV coordinates for this modeldef | // Prepare UV coordinates for this modeldef | ||||
void InstancingModelRenderer::PrepareModelDef(const CShaderProgramPtr& shader, int streamflags, const CModelDef& def) | void InstancingModelRenderer::PrepareModelDef(const CShaderProgramPtr& shader, int streamflags, const CModelDef& def) | ||||
{ | { | ||||
m->imodeldef = (IModelDef*)def.GetRenderData(m); | m->imodeldef = (IModelDef*)def.GetRenderData(m); | ||||
ENSURE(m->imodeldef); | ENSURE(m->imodeldef); | ||||
u8* base = m->imodeldef->m_Array.Bind(); | u8* base = m->imodeldef->m_Array.Bind(); | ||||
Show All 22 Lines | void InstancingModelRenderer::PrepareModelDef(const CShaderProgramPtr& shader, int streamflags, const CModelDef& def) | ||||
// GPU skinning requires extra attributes to compute positions/normals | // GPU skinning requires extra attributes to compute positions/normals | ||||
if (m->gpuSkinning) | if (m->gpuSkinning) | ||||
{ | { | ||||
shader->VertexAttribPointer(str_a_skinJoints, 4, GL_UNSIGNED_BYTE, GL_FALSE, stride, base + m->imodeldef->m_BlendJoints.offset); | shader->VertexAttribPointer(str_a_skinJoints, 4, GL_UNSIGNED_BYTE, GL_FALSE, stride, base + m->imodeldef->m_BlendJoints.offset); | ||||
shader->VertexAttribPointer(str_a_skinWeights, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, base + m->imodeldef->m_BlendWeights.offset); | shader->VertexAttribPointer(str_a_skinWeights, 4, GL_UNSIGNED_BYTE, GL_TRUE, stride, base + m->imodeldef->m_BlendWeights.offset); | ||||
} | } | ||||
instancingTransformBinding = shader->GetUniformBinding(str_instancingTransform); | |||||
shader->AssertPointersBound(); | shader->AssertPointersBound(); | ||||
} | } | ||||
// Render one model | // Render one model | ||||
void InstancingModelRenderer::RenderModel(const CShaderProgramPtr& shader, int UNUSED(streamflags), CModel* model, CModelRData* UNUSED(data)) | void InstancingModelRenderer::RenderModel(const CShaderProgramPtr& shader, int UNUSED(streamflags), CModel* model, CModelRData* UNUSED(data)) | ||||
{ | { | ||||
const CModelDefPtr& mdldef = model->GetModelDef(); | const CModelDefPtr& mdldef = model->GetModelDef(); | ||||
shader->Uniform(instancingTransformBinding, model->GetTransform()); | |||||
Done Inline ActionsThis might actually make ARB performance slightly worse than it should be with the GetUniformBinding, need to check wraitii: This might actually make ARB performance slightly worse than it should be with the… | |||||
if (m->gpuSkinning) | if (m->gpuSkinning) | ||||
{ | { | ||||
// Bind matrices for current animation state. | // Bind matrices for current animation state. | ||||
// Add 1 to NumBones because of the special 'root' bone. | // Add 1 to NumBones because of the special 'root' bone. | ||||
Not Done Inline ActionsShould return false in CanInstance if m->gpuSkinning. vladislavbelov: Should return `false` in `CanInstance` if `m->gpuSkinning`. | |||||
Done Inline ActionsI've actually fixed that so Instancing + GPU Skinning is working. I'll update the comment. wraitii: I've actually fixed that so Instancing + GPU Skinning is working. I'll update the comment. | |||||
// HACK: NVIDIA drivers return uniform name with "[0]", Intel Windows drivers without; | // 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 | // try uploading both names since one of them should work, and this is easier than | ||||
// canonicalising the uniform names in CShaderProgramGLSL | // canonicalising the uniform names in CShaderProgramGLSL | ||||
shader->Uniform(str_skinBlendMatrices_0, mdldef->GetNumBones() + 1, model->GetAnimatedBoneMatrices()); | shader->Uniform(str_skinBlendMatrices_0, mdldef->GetNumBones() + 1, model->GetAnimatedBoneMatrices()); | ||||
shader->Uniform(str_skinBlendMatrices, mdldef->GetNumBones() + 1, model->GetAnimatedBoneMatrices()); | shader->Uniform(str_skinBlendMatrices, mdldef->GetNumBones() + 1, model->GetAnimatedBoneMatrices()); | ||||
} | } | ||||
// render the lot | // render the lot | ||||
size_t numFaces = mdldef->GetNumFaces(); | size_t numFaces = mdldef->GetNumFaces(); | ||||
if (!g_Renderer.m_SkipSubmit) | if (!g_Renderer.m_SkipSubmit) | ||||
{ | { | ||||
// Draw with DrawRangeElements where available, since it might be more efficient | // Draw with DrawRangeElements where available, since it might be more efficient | ||||
#if CONFIG2_GLES | #if CONFIG2_GLES | ||||
Done Inline ActionsCast, magic numbers Stan: Cast, magic numbers | |||||
Done Inline ActionsMeans possible reallocation on each different model. Also it might make sense to not draw instances if their number less than 2-4. vladislavbelov: Means possible reallocation on each different model. Also it might make sense to not draw… | |||||
Done Inline Actions
That's "wanted" in that I don't want synchronisation between my draw calls, so in principle this orphans the buffer and may or may not reallocate.
I'd agree conceptually, since since we have a list of models to render already I feel like it cannot be slower. Empirically it didn't seem to change much on my system. wraitii: > Means possible reallocation on each different model.
That's "wanted" in that I don't want… | |||||
Done Inline Actions
I believe it works properly (without reallocating) only after a draw call is finished, else you'll have a new allocation for each new buffer. Since the draw call might be drawn or not. So it might make sense to collect transforms first. vladislavbelov: > That's "wanted" in that I don't want synchronisation between my draw calls, so in principle… | |||||
glDrawElements(GL_TRIANGLES, (GLsizei)numFaces*3, GL_UNSIGNED_SHORT, m->imodeldefIndexBase); | glDrawElements(GL_TRIANGLES, (GLsizei)numFaces*3, GL_UNSIGNED_SHORT, m->imodeldefIndexBase); | ||||
#else | #else | ||||
pglDrawRangeElementsEXT(GL_TRIANGLES, 0, (GLuint)m->imodeldef->m_Array.GetNumVertices()-1, | pglDrawRangeElementsEXT(GL_TRIANGLES, 0, (GLuint)m->imodeldef->m_Array.GetNumVertices()-1, | ||||
Done Inline ActionsUseless items copying. vladislavbelov: Useless items copying. | |||||
Done Inline ActionsWhat are you referring to here? I need to copy the model transform so I can glBufferSubData them? Or would you call glBufferSubData N times? wraitii: What are you referring to here? I need to copy the model transform so I can glBufferSubData… | |||||
Done Inline ActionsNo, reserving before clear might cause copying of m->transforms. vladislavbelov: No, reserving before clear might cause copying of `m->transforms`. | |||||
(GLsizei)numFaces*3, GL_UNSIGNED_SHORT, m->imodeldefIndexBase); | (GLsizei)numFaces*3, GL_UNSIGNED_SHORT, m->imodeldefIndexBase); | ||||
#endif | #endif | ||||
} | } | ||||
// bump stats | // bump stats | ||||
g_Renderer.m_Stats.m_DrawCalls++; | g_Renderer.m_Stats.m_DrawCalls++; | ||||
g_Renderer.m_Stats.m_ModelTris += numFaces; | g_Renderer.m_Stats.m_ModelTris += numFaces; | ||||
} | } | ||||
static std::vector<CMatrix3D> uniforms; | |||||
void InstancingModelRenderer::RenderInstancedModel(const CShaderProgramPtr& shader, const std::vector<CModel*>& models) | |||||
{ | |||||
if (g_Renderer.m_SkipSubmit) | |||||
return; | |||||
const CModelDefPtr& mdldef = models.front()->GetModelDef(); | |||||
if (m->gpuSkinning) | |||||
{ | |||||
// HACK: this gives the same animation to all similar modeldefs, | |||||
Not Done Inline ActionsStan: From https://github.com/0ad/0ad/blob/a4019a9d4986ab100d78e47f6696b4e61b8b2f79/source/renderer/I… | |||||
// which is somewhat obviously broken, but it renders something. | |||||
// Bind matrices for current animation state. | |||||
// Add 1 to NumBones because of the special 'root' bone. | |||||
// 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, models.front()->GetAnimatedBoneMatrices()); | |||||
shader->Uniform(str_skinBlendMatrices, mdldef->GetNumBones() + 1, models.front()->GetAnimatedBoneMatrices()); | |||||
} | |||||
size_t numFaces = mdldef->GetNumFaces(); | |||||
// Set up a uniform | |||||
uniforms.reserve(64); | |||||
if (models.size() == 1) | |||||
{ | |||||
shader->Uniform(str_instancingTransformReal, 1, &models[0]->GetTransform()); | |||||
pglDrawRangeElementsEXT(GL_TRIANGLES, 0, (GLuint)m->imodeldef->m_Array.GetNumVertices()-1, | |||||
(GLsizei)numFaces*3, GL_UNSIGNED_SHORT, m->imodeldefIndexBase); | |||||
g_Renderer.m_Stats.m_DrawCalls++; | |||||
g_Renderer.m_Stats.m_ModelTris += numFaces; | |||||
return; | |||||
} | |||||
uniforms.clear(); | |||||
for (CModel* model : models) | |||||
uniforms.emplace_back(model->GetTransform()); | |||||
int max; | |||||
CFG_GET_VAL("max_matrix_uniform", max); | |||||
for (size_t start = 0, end = std::min((size_t)max, models.size()); start < models.size(); start += max, end = std::min(start+max, models.size())) | |||||
{ | |||||
shader->Uniform(str_instancingTransformReal, end-start, uniforms.data() + start); | |||||
pglDrawElementsInstancedARB(GL_TRIANGLES, | |||||
(GLsizei)numFaces*3, | |||||
GL_UNSIGNED_SHORT, | |||||
m->imodeldefIndexBase, end-start); | |||||
g_Renderer.m_Stats.m_DrawCalls++; | |||||
g_Renderer.m_Stats.m_SavedDrawCalls += (end-start-1); | |||||
g_Renderer.m_Stats.m_ModelTris += numFaces * (end-start); | |||||
} | |||||
} |
If you don't use VertexBuffer then you need to wait some my refactorings. Because it's bad to introduce new low-level GL calls into high-level or middle-level code.