mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-07-16 03:16:54 +02:00
Merge pull request #3601 from ZappoMan/frustumCullModelParts
Implement "segregation" of mesh groups
This commit is contained in:
commit
01b64d78e3
5 changed files with 383 additions and 111 deletions
|
@ -46,7 +46,8 @@ Model::Model(QObject* parent) :
|
||||||
_url("http://invalid.com"),
|
_url("http://invalid.com"),
|
||||||
_blendNumber(0),
|
_blendNumber(0),
|
||||||
_appliedBlendNumber(0),
|
_appliedBlendNumber(0),
|
||||||
_calculatedMeshBoxesValid(false) {
|
_calculatedMeshBoxesValid(false),
|
||||||
|
_meshesGroupsKnown(false) {
|
||||||
|
|
||||||
// we may have been created in the network thread, but we live in the main thread
|
// we may have been created in the network thread, but we live in the main thread
|
||||||
moveToThread(Application::getInstance()->thread());
|
moveToThread(Application::getInstance()->thread());
|
||||||
|
@ -271,6 +272,8 @@ void Model::reset() {
|
||||||
for (int i = 0; i < _jointStates.size(); i++) {
|
for (int i = 0; i < _jointStates.size(); i++) {
|
||||||
_jointStates[i].setRotationInConstrainedFrame(geometry.joints.at(i).rotation, 0.0f);
|
_jointStates[i].setRotationInConstrainedFrame(geometry.joints.at(i).rotation, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_meshesGroupsKnown = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Model::updateGeometry() {
|
bool Model::updateGeometry() {
|
||||||
|
@ -320,6 +323,7 @@ bool Model::updateGeometry() {
|
||||||
deleteGeometry();
|
deleteGeometry();
|
||||||
_dilatedTextures.clear();
|
_dilatedTextures.clear();
|
||||||
_geometry = geometry;
|
_geometry = geometry;
|
||||||
|
_meshesGroupsKnown = false;
|
||||||
setJointStates(newJointStates);
|
setJointStates(newJointStates);
|
||||||
needToRebuild = true;
|
needToRebuild = true;
|
||||||
} else if (_jointStates.isEmpty()) {
|
} else if (_jointStates.isEmpty()) {
|
||||||
|
@ -421,6 +425,10 @@ bool Model::render(float alpha, RenderMode mode, RenderArgs* args) {
|
||||||
_dilatedTextures.append(dilated);
|
_dilatedTextures.append(dilated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_meshesGroupsKnown) {
|
||||||
|
segregateMeshGroups();
|
||||||
|
}
|
||||||
|
|
||||||
glEnableClientState(GL_VERTEX_ARRAY);
|
glEnableClientState(GL_VERTEX_ARRAY);
|
||||||
glEnableClientState(GL_NORMAL_ARRAY);
|
glEnableClientState(GL_NORMAL_ARRAY);
|
||||||
|
@ -451,11 +459,30 @@ bool Model::render(float alpha, RenderMode mode, RenderArgs* args) {
|
||||||
mode == DEFAULT_RENDER_MODE);
|
mode == DEFAULT_RENDER_MODE);
|
||||||
|
|
||||||
const float DEFAULT_ALPHA_THRESHOLD = 0.5f;
|
const float DEFAULT_ALPHA_THRESHOLD = 0.5f;
|
||||||
renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, args);
|
|
||||||
|
//renderMeshes(RenderMode mode, bool translucent, float alphaThreshold, bool hasTangents, bool hasSpecular, book isSkinned, args);
|
||||||
|
int opaqueMeshPartsRendered = 0;
|
||||||
|
opaqueMeshPartsRendered += renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, args);
|
||||||
|
opaqueMeshPartsRendered += renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, args);
|
||||||
|
opaqueMeshPartsRendered += renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, args);
|
||||||
|
opaqueMeshPartsRendered += renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, args);
|
||||||
|
opaqueMeshPartsRendered += renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, false, args);
|
||||||
|
opaqueMeshPartsRendered += renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, args);
|
||||||
|
opaqueMeshPartsRendered += renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, false, args);
|
||||||
|
opaqueMeshPartsRendered += renderMeshes(mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, args);
|
||||||
|
|
||||||
// render translucent meshes afterwards
|
// render translucent meshes afterwards
|
||||||
Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(false, true, true);
|
Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(false, true, true);
|
||||||
renderMeshes(mode, true, 0.75f, args);
|
int translucentMeshPartsRendered = 0;
|
||||||
|
const float MOSTLY_OPAQUE_THRESHOLD = 0.75f;
|
||||||
|
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, args);
|
||||||
|
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, args);
|
||||||
|
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, args);
|
||||||
|
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, args);
|
||||||
|
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_OPAQUE_THRESHOLD, true, false, false, args);
|
||||||
|
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_OPAQUE_THRESHOLD, true, false, true, args);
|
||||||
|
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_OPAQUE_THRESHOLD, true, true, false, args);
|
||||||
|
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_OPAQUE_THRESHOLD, true, false, true, args);
|
||||||
|
|
||||||
glDisable(GL_ALPHA_TEST);
|
glDisable(GL_ALPHA_TEST);
|
||||||
glEnable(GL_BLEND);
|
glEnable(GL_BLEND);
|
||||||
|
@ -465,7 +492,15 @@ bool Model::render(float alpha, RenderMode mode, RenderArgs* args) {
|
||||||
Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(true);
|
Application::getInstance()->getTextureCache()->setPrimaryDrawBuffers(true);
|
||||||
|
|
||||||
if (mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE) {
|
if (mode == DEFAULT_RENDER_MODE || mode == DIFFUSE_RENDER_MODE) {
|
||||||
renderMeshes(mode, true, 0.0f, args);
|
const float MOSTLY_TRANSPARENT_THRESHOLD = 0.0f;
|
||||||
|
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, args);
|
||||||
|
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, args);
|
||||||
|
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, args);
|
||||||
|
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, args);
|
||||||
|
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_TRANSPARENT_THRESHOLD, true, false, false, args);
|
||||||
|
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_TRANSPARENT_THRESHOLD, true, false, true, args);
|
||||||
|
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_TRANSPARENT_THRESHOLD, true, true, false, args);
|
||||||
|
translucentMeshPartsRendered += renderMeshes(mode, true, MOSTLY_TRANSPARENT_THRESHOLD, true, false, true, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
glDepthMask(true);
|
glDepthMask(true);
|
||||||
|
@ -488,6 +523,11 @@ bool Model::render(float alpha, RenderMode mode, RenderArgs* args) {
|
||||||
|
|
||||||
// restore all the default material settings
|
// restore all the default material settings
|
||||||
Application::getInstance()->setupWorldLight();
|
Application::getInstance()->setupWorldLight();
|
||||||
|
|
||||||
|
if (args) {
|
||||||
|
args->_translucentMeshPartsRendered = translucentMeshPartsRendered;
|
||||||
|
args->_opaqueMeshPartsRendered = opaqueMeshPartsRendered;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1207,6 +1247,7 @@ void Model::applyNextGeometry() {
|
||||||
// we retain a reference to the base geometry so that its reference count doesn't fall to zero
|
// we retain a reference to the base geometry so that its reference count doesn't fall to zero
|
||||||
_baseGeometry = _nextBaseGeometry;
|
_baseGeometry = _nextBaseGeometry;
|
||||||
_geometry = _nextGeometry;
|
_geometry = _nextGeometry;
|
||||||
|
_meshesGroupsKnown = false;
|
||||||
_nextBaseGeometry.reset();
|
_nextBaseGeometry.reset();
|
||||||
_nextGeometry.reset();
|
_nextGeometry.reset();
|
||||||
}
|
}
|
||||||
|
@ -1238,21 +1279,218 @@ void Model::deleteGeometry() {
|
||||||
_blendedBlendshapeCoefficients.clear();
|
_blendedBlendshapeCoefficients.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold, RenderArgs* args) {
|
void Model::segregateMeshGroups() {
|
||||||
|
_meshesTranslucentTangents.clear();
|
||||||
|
_meshesTranslucent.clear();
|
||||||
|
_meshesTranslucentTangentsSpecular.clear();
|
||||||
|
_meshesTranslucentSpecular.clear();
|
||||||
|
|
||||||
|
_meshesTranslucentTangentsSkinned.clear();
|
||||||
|
_meshesTranslucentSkinned.clear();
|
||||||
|
_meshesTranslucentTangentsSpecularSkinned.clear();
|
||||||
|
_meshesTranslucentSpecularSkinned.clear();
|
||||||
|
|
||||||
|
_meshesOpaqueTangents.clear();
|
||||||
|
_meshesOpaque.clear();
|
||||||
|
_meshesOpaqueTangentsSpecular.clear();
|
||||||
|
_meshesOpaqueSpecular.clear();
|
||||||
|
|
||||||
|
_meshesOpaqueTangentsSkinned.clear();
|
||||||
|
_meshesOpaqueSkinned.clear();
|
||||||
|
_meshesOpaqueTangentsSpecularSkinned.clear();
|
||||||
|
_meshesOpaqueSpecularSkinned.clear();
|
||||||
|
|
||||||
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
|
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
|
||||||
|
|
||||||
|
for (int i = 0; i < networkMeshes.size(); i++) {
|
||||||
|
const NetworkMesh& networkMesh = networkMeshes.at(i);
|
||||||
|
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||||
|
const MeshState& state = _meshStates.at(i);
|
||||||
|
|
||||||
|
bool translucentMesh = networkMesh.getTranslucentPartCount(mesh) == networkMesh.parts.size();
|
||||||
|
bool hasTangents = !mesh.tangents.isEmpty();
|
||||||
|
bool hasSpecular = mesh.hasSpecularTexture();
|
||||||
|
bool isSkinned = state.clusterMatrices.size() > 1;
|
||||||
|
|
||||||
|
if (translucentMesh && !hasTangents && !hasSpecular && !isSkinned) {
|
||||||
|
|
||||||
|
_meshesTranslucent.append(i);
|
||||||
|
|
||||||
|
} else if (translucentMesh && hasTangents && !hasSpecular && !isSkinned) {
|
||||||
|
|
||||||
|
_meshesTranslucentTangents.append(i);
|
||||||
|
|
||||||
|
} else if (translucentMesh && hasTangents && hasSpecular && !isSkinned) {
|
||||||
|
|
||||||
|
_meshesTranslucentTangentsSpecular.append(i);
|
||||||
|
|
||||||
|
} else if (translucentMesh && !hasTangents && hasSpecular && !isSkinned) {
|
||||||
|
|
||||||
|
_meshesTranslucentSpecular.append(i);
|
||||||
|
|
||||||
|
} else if (translucentMesh && hasTangents && !hasSpecular && isSkinned) {
|
||||||
|
|
||||||
|
_meshesTranslucentTangentsSkinned.append(i);
|
||||||
|
|
||||||
|
} else if (translucentMesh && !hasTangents && !hasSpecular && isSkinned) {
|
||||||
|
|
||||||
|
_meshesTranslucentSkinned.append(i);
|
||||||
|
|
||||||
|
} else if (translucentMesh && hasTangents && hasSpecular && isSkinned) {
|
||||||
|
|
||||||
|
_meshesTranslucentTangentsSpecularSkinned.append(i);
|
||||||
|
|
||||||
|
} else if (translucentMesh && !hasTangents && hasSpecular && isSkinned) {
|
||||||
|
|
||||||
|
_meshesTranslucentSpecularSkinned.append(i);
|
||||||
|
|
||||||
|
} else if (!translucentMesh && !hasTangents && !hasSpecular && !isSkinned) {
|
||||||
|
|
||||||
|
_meshesOpaque.append(i);
|
||||||
|
|
||||||
|
} else if (!translucentMesh && hasTangents && !hasSpecular && !isSkinned) {
|
||||||
|
|
||||||
|
_meshesOpaqueTangents.append(i);
|
||||||
|
|
||||||
|
} else if (!translucentMesh && hasTangents && hasSpecular && !isSkinned) {
|
||||||
|
|
||||||
|
_meshesOpaqueTangentsSpecular.append(i);
|
||||||
|
|
||||||
|
} else if (!translucentMesh && !hasTangents && hasSpecular && !isSkinned) {
|
||||||
|
|
||||||
|
_meshesOpaqueSpecular.append(i);
|
||||||
|
|
||||||
|
} else if (!translucentMesh && hasTangents && !hasSpecular && isSkinned) {
|
||||||
|
|
||||||
|
_meshesOpaqueTangentsSkinned.append(i);
|
||||||
|
|
||||||
|
} else if (!translucentMesh && !hasTangents && !hasSpecular && isSkinned) {
|
||||||
|
|
||||||
|
_meshesOpaqueSkinned.append(i);
|
||||||
|
|
||||||
|
} else if (!translucentMesh && hasTangents && hasSpecular && isSkinned) {
|
||||||
|
|
||||||
|
_meshesOpaqueTangentsSpecularSkinned.append(i);
|
||||||
|
|
||||||
|
} else if (!translucentMesh && !hasTangents && hasSpecular && isSkinned) {
|
||||||
|
|
||||||
|
_meshesOpaqueSpecularSkinned.append(i);
|
||||||
|
} else {
|
||||||
|
qDebug() << "unexpected!!! this mesh didn't fall into any or our groups???";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_meshesGroupsKnown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold,
|
||||||
|
bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args) {
|
||||||
|
int meshPartsRendered = 0;
|
||||||
updateVisibleJointStates();
|
updateVisibleJointStates();
|
||||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||||
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
|
const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes();
|
||||||
|
|
||||||
bool cullMeshParts = args && !Menu::getInstance()->isOptionChecked(MenuOption::DontCullMeshParts);
|
bool cullMeshParts = args && !Menu::getInstance()->isOptionChecked(MenuOption::DontCullMeshParts);
|
||||||
|
|
||||||
|
// depending on which parameters we were called with, pick the correct mesh group to render
|
||||||
|
QList<int>* whichList = NULL;
|
||||||
|
if (translucent && !hasTangents && !hasSpecular && !isSkinned) {
|
||||||
|
whichList = &_meshesTranslucent;
|
||||||
|
} else if (translucent && hasTangents && !hasSpecular && !isSkinned) {
|
||||||
|
whichList = &_meshesTranslucentTangents;
|
||||||
|
} else if (translucent && hasTangents && hasSpecular && !isSkinned) {
|
||||||
|
whichList = &_meshesTranslucentTangentsSpecular;
|
||||||
|
} else if (translucent && !hasTangents && hasSpecular && !isSkinned) {
|
||||||
|
whichList = &_meshesTranslucentSpecular;
|
||||||
|
} else if (translucent && hasTangents && !hasSpecular && isSkinned) {
|
||||||
|
whichList = &_meshesTranslucentTangentsSkinned;
|
||||||
|
} else if (translucent && !hasTangents && !hasSpecular && isSkinned) {
|
||||||
|
whichList = &_meshesTranslucentSkinned;
|
||||||
|
} else if (translucent && hasTangents && hasSpecular && isSkinned) {
|
||||||
|
whichList = &_meshesTranslucentTangentsSpecularSkinned;
|
||||||
|
} else if (translucent && !hasTangents && hasSpecular && isSkinned) {
|
||||||
|
whichList = &_meshesTranslucentSpecularSkinned;
|
||||||
|
} else if (!translucent && !hasTangents && !hasSpecular && !isSkinned) {
|
||||||
|
whichList = &_meshesOpaque;
|
||||||
|
} else if (!translucent && hasTangents && !hasSpecular && !isSkinned) {
|
||||||
|
whichList = &_meshesOpaqueTangents;
|
||||||
|
} else if (!translucent && hasTangents && hasSpecular && !isSkinned) {
|
||||||
|
whichList = &_meshesOpaqueTangentsSpecular;
|
||||||
|
} else if (!translucent && !hasTangents && hasSpecular && !isSkinned) {
|
||||||
|
whichList = &_meshesOpaqueSpecular;
|
||||||
|
} else if (!translucent && hasTangents && !hasSpecular && isSkinned) {
|
||||||
|
whichList = &_meshesOpaqueTangentsSkinned;
|
||||||
|
} else if (!translucent && !hasTangents && !hasSpecular && isSkinned) {
|
||||||
|
whichList = &_meshesOpaqueSkinned;
|
||||||
|
} else if (!translucent && hasTangents && hasSpecular && isSkinned) {
|
||||||
|
whichList = &_meshesOpaqueTangentsSpecularSkinned;
|
||||||
|
} else if (!translucent && !hasTangents && hasSpecular && isSkinned) {
|
||||||
|
whichList = &_meshesOpaqueSpecularSkinned;
|
||||||
|
} else {
|
||||||
|
qDebug() << "unexpected!!! this mesh didn't fall into any or our groups???";
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < networkMeshes.size(); i++) {
|
if (!whichList) {
|
||||||
|
qDebug() << "unexpected!!! we don't know which list of meshes to render...";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
QList<int>& list = *whichList;
|
||||||
|
|
||||||
|
ProgramObject* program = &_program;
|
||||||
|
Locations* locations = &_locations;
|
||||||
|
ProgramObject* skinProgram = &_skinProgram;
|
||||||
|
SkinLocations* skinLocations = &_skinLocations;
|
||||||
|
GLenum specularTextureUnit = 0;
|
||||||
|
if (mode == SHADOW_RENDER_MODE) {
|
||||||
|
program = &_shadowProgram;
|
||||||
|
skinProgram = &_skinShadowProgram;
|
||||||
|
skinLocations = &_skinShadowLocations;
|
||||||
|
} else if (translucent && alphaThreshold == 0.0f) {
|
||||||
|
program = &_translucentProgram;
|
||||||
|
locations = &_translucentLocations;
|
||||||
|
skinProgram = &_skinTranslucentProgram;
|
||||||
|
skinLocations = &_skinTranslucentLocations;
|
||||||
|
|
||||||
|
} else if (hasTangents) {
|
||||||
|
if (hasSpecular) {
|
||||||
|
program = &_normalSpecularMapProgram;
|
||||||
|
locations = &_normalSpecularMapLocations;
|
||||||
|
skinProgram = &_skinNormalSpecularMapProgram;
|
||||||
|
skinLocations = &_skinNormalSpecularMapLocations;
|
||||||
|
specularTextureUnit = GL_TEXTURE2;
|
||||||
|
} else {
|
||||||
|
program = &_normalMapProgram;
|
||||||
|
locations = &_normalMapLocations;
|
||||||
|
skinProgram = &_skinNormalMapProgram;
|
||||||
|
skinLocations = &_skinNormalMapLocations;
|
||||||
|
}
|
||||||
|
} else if (hasSpecular) {
|
||||||
|
program = &_specularMapProgram;
|
||||||
|
locations = &_specularMapLocations;
|
||||||
|
skinProgram = &_skinSpecularMapProgram;
|
||||||
|
skinLocations = &_skinSpecularMapLocations;
|
||||||
|
specularTextureUnit = GL_TEXTURE1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgramObject* activeProgram = program;
|
||||||
|
Locations* activeLocations = locations;
|
||||||
|
|
||||||
|
if (isSkinned) {
|
||||||
|
skinProgram->bind();
|
||||||
|
activeProgram = skinProgram;
|
||||||
|
activeLocations = skinLocations;
|
||||||
|
} else {
|
||||||
|
program->bind();
|
||||||
|
}
|
||||||
|
activeProgram->setUniformValue(activeLocations->alphaThreshold, alphaThreshold);
|
||||||
|
|
||||||
|
// i is the "index" from the original networkMeshes QVector...
|
||||||
|
foreach (int i, list) {
|
||||||
|
|
||||||
// exit early if the translucency doesn't match what we're drawing
|
// exit early if the translucency doesn't match what we're drawing
|
||||||
const NetworkMesh& networkMesh = networkMeshes.at(i);
|
const NetworkMesh& networkMesh = networkMeshes.at(i);
|
||||||
const FBXMesh& mesh = geometry.meshes.at(i);
|
const FBXMesh& mesh = geometry.meshes.at(i);
|
||||||
if (translucent ? (networkMesh.getTranslucentPartCount(mesh) == 0) :
|
|
||||||
(networkMesh.getTranslucentPartCount(mesh) == networkMesh.parts.size())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const_cast<QOpenGLBuffer&>(networkMesh.indexBuffer).bind();
|
const_cast<QOpenGLBuffer&>(networkMesh.indexBuffer).bind();
|
||||||
|
|
||||||
int vertexCount = mesh.vertices.size();
|
int vertexCount = mesh.vertices.size();
|
||||||
|
@ -1280,52 +1518,11 @@ void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold
|
||||||
|
|
||||||
const_cast<QOpenGLBuffer&>(networkMesh.vertexBuffer).bind();
|
const_cast<QOpenGLBuffer&>(networkMesh.vertexBuffer).bind();
|
||||||
|
|
||||||
ProgramObject* program = &_program;
|
|
||||||
Locations* locations = &_locations;
|
|
||||||
ProgramObject* skinProgram = &_skinProgram;
|
|
||||||
SkinLocations* skinLocations = &_skinLocations;
|
|
||||||
GLenum specularTextureUnit = 0;
|
|
||||||
if (mode == SHADOW_RENDER_MODE) {
|
|
||||||
program = &_shadowProgram;
|
|
||||||
skinProgram = &_skinShadowProgram;
|
|
||||||
skinLocations = &_skinShadowLocations;
|
|
||||||
|
|
||||||
} else if (translucent && alphaThreshold == 0.0f) {
|
|
||||||
program = &_translucentProgram;
|
|
||||||
locations = &_translucentLocations;
|
|
||||||
skinProgram = &_skinTranslucentProgram;
|
|
||||||
skinLocations = &_skinTranslucentLocations;
|
|
||||||
|
|
||||||
} else if (!mesh.tangents.isEmpty()) {
|
|
||||||
if (mesh.hasSpecularTexture()) {
|
|
||||||
program = &_normalSpecularMapProgram;
|
|
||||||
locations = &_normalSpecularMapLocations;
|
|
||||||
skinProgram = &_skinNormalSpecularMapProgram;
|
|
||||||
skinLocations = &_skinNormalSpecularMapLocations;
|
|
||||||
specularTextureUnit = GL_TEXTURE2;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
program = &_normalMapProgram;
|
|
||||||
locations = &_normalMapLocations;
|
|
||||||
skinProgram = &_skinNormalMapProgram;
|
|
||||||
skinLocations = &_skinNormalMapLocations;
|
|
||||||
}
|
|
||||||
} else if (mesh.hasSpecularTexture()) {
|
|
||||||
program = &_specularMapProgram;
|
|
||||||
locations = &_specularMapLocations;
|
|
||||||
skinProgram = &_skinSpecularMapProgram;
|
|
||||||
skinLocations = &_skinSpecularMapLocations;
|
|
||||||
specularTextureUnit = GL_TEXTURE1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MeshState& state = _meshStates.at(i);
|
|
||||||
ProgramObject* activeProgram = program;
|
|
||||||
Locations* activeLocations = locations;
|
|
||||||
glPushMatrix();
|
glPushMatrix();
|
||||||
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
Application::getInstance()->loadTranslatedViewMatrix(_translation);
|
||||||
|
|
||||||
|
const MeshState& state = _meshStates.at(i);
|
||||||
if (state.clusterMatrices.size() > 1) {
|
if (state.clusterMatrices.size() > 1) {
|
||||||
skinProgram->bind();
|
|
||||||
glUniformMatrix4fvARB(skinLocations->clusterMatrices, state.clusterMatrices.size(), false,
|
glUniformMatrix4fvARB(skinLocations->clusterMatrices, state.clusterMatrices.size(), false,
|
||||||
(const float*)state.clusterMatrices.constData());
|
(const float*)state.clusterMatrices.constData());
|
||||||
int offset = (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3) +
|
int offset = (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3) +
|
||||||
|
@ -1336,16 +1533,10 @@ void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold
|
||||||
offset + vertexCount * sizeof(glm::vec4), 4);
|
offset + vertexCount * sizeof(glm::vec4), 4);
|
||||||
skinProgram->enableAttributeArray(skinLocations->clusterIndices);
|
skinProgram->enableAttributeArray(skinLocations->clusterIndices);
|
||||||
skinProgram->enableAttributeArray(skinLocations->clusterWeights);
|
skinProgram->enableAttributeArray(skinLocations->clusterWeights);
|
||||||
activeProgram = skinProgram;
|
|
||||||
activeLocations = skinLocations;
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]);
|
glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]);
|
||||||
program->bind();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
activeProgram->setUniformValue(activeLocations->alphaThreshold, alphaThreshold);
|
|
||||||
|
|
||||||
if (mesh.blendshapes.isEmpty()) {
|
if (mesh.blendshapes.isEmpty()) {
|
||||||
if (!(mesh.tangents.isEmpty() || mode == SHADOW_RENDER_MODE)) {
|
if (!(mesh.tangents.isEmpty() || mode == SHADOW_RENDER_MODE)) {
|
||||||
activeProgram->setAttributeBuffer(activeLocations->tangent, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3);
|
activeProgram->setAttributeBuffer(activeLocations->tangent, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3);
|
||||||
|
@ -1424,11 +1615,19 @@ void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold
|
||||||
glActiveTexture(GL_TEXTURE0);
|
glActiveTexture(GL_TEXTURE0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset);
|
|
||||||
offset += part.quadIndices.size() * sizeof(int);
|
meshPartsRendered++;
|
||||||
glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, part.triangleIndices.size(),
|
|
||||||
GL_UNSIGNED_INT, (void*)offset);
|
if (part.quadIndices.size() > 0) {
|
||||||
offset += part.triangleIndices.size() * sizeof(int);
|
glDrawRangeElementsEXT(GL_QUADS, 0, vertexCount - 1, part.quadIndices.size(), GL_UNSIGNED_INT, (void*)offset);
|
||||||
|
offset += part.quadIndices.size() * sizeof(int);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (part.triangleIndices.size() > 0) {
|
||||||
|
glDrawRangeElementsEXT(GL_TRIANGLES, 0, vertexCount - 1, part.triangleIndices.size(),
|
||||||
|
GL_UNSIGNED_INT, (void*)offset);
|
||||||
|
offset += part.triangleIndices.size() * sizeof(int);
|
||||||
|
}
|
||||||
|
|
||||||
if (args) {
|
if (args) {
|
||||||
const int INDICES_PER_TRIANGLE = 3;
|
const int INDICES_PER_TRIANGLE = 3;
|
||||||
|
@ -1465,8 +1664,10 @@ void Model::renderMeshes(RenderMode mode, bool translucent, float alphaThreshold
|
||||||
}
|
}
|
||||||
glPopMatrix();
|
glPopMatrix();
|
||||||
|
|
||||||
activeProgram->release();
|
|
||||||
}
|
}
|
||||||
|
activeProgram->release();
|
||||||
|
|
||||||
|
return meshPartsRendered;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AnimationHandle::setURL(const QUrl& url) {
|
void AnimationHandle::setURL(const QUrl& url) {
|
||||||
|
|
|
@ -251,7 +251,7 @@ private:
|
||||||
|
|
||||||
void applyNextGeometry();
|
void applyNextGeometry();
|
||||||
void deleteGeometry();
|
void deleteGeometry();
|
||||||
void renderMeshes(RenderMode mode, bool translucent, float alphaThreshold = 0.5f, RenderArgs* args = NULL);
|
int renderMeshes(RenderMode mode, bool translucent, float alphaThreshold, bool hasTangents, bool hasSpecular, bool isSkinned, RenderArgs* args = NULL);
|
||||||
QVector<JointState> createJointStates(const FBXGeometry& geometry);
|
QVector<JointState> createJointStates(const FBXGeometry& geometry);
|
||||||
void initJointTransforms();
|
void initJointTransforms();
|
||||||
|
|
||||||
|
@ -332,6 +332,31 @@ private:
|
||||||
|
|
||||||
QVector<AABox> _calculatedMeshBoxes;
|
QVector<AABox> _calculatedMeshBoxes;
|
||||||
bool _calculatedMeshBoxesValid;
|
bool _calculatedMeshBoxesValid;
|
||||||
|
|
||||||
|
void segregateMeshGroups(); // used to calculate our list of translucent vs opaque meshes
|
||||||
|
|
||||||
|
bool _meshesGroupsKnown;
|
||||||
|
|
||||||
|
QList<int> _meshesTranslucent;
|
||||||
|
QList<int> _meshesTranslucentTangents;
|
||||||
|
QList<int> _meshesTranslucentTangentsSpecular;
|
||||||
|
QList<int> _meshesTranslucentSpecular;
|
||||||
|
|
||||||
|
QList<int> _meshesTranslucentSkinned;
|
||||||
|
QList<int> _meshesTranslucentTangentsSkinned;
|
||||||
|
QList<int> _meshesTranslucentTangentsSpecularSkinned;
|
||||||
|
QList<int> _meshesTranslucentSpecularSkinned;
|
||||||
|
|
||||||
|
QList<int> _meshesOpaque;
|
||||||
|
QList<int> _meshesOpaqueTangents;
|
||||||
|
QList<int> _meshesOpaqueTangentsSpecular;
|
||||||
|
QList<int> _meshesOpaqueSpecular;
|
||||||
|
|
||||||
|
QList<int> _meshesOpaqueSkinned;
|
||||||
|
QList<int> _meshesOpaqueTangentsSkinned;
|
||||||
|
QList<int> _meshesOpaqueTangentsSpecularSkinned;
|
||||||
|
QList<int> _meshesOpaqueSpecularSkinned;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(QPointer<Model>)
|
Q_DECLARE_METATYPE(QPointer<Model>)
|
||||||
|
|
|
@ -221,8 +221,30 @@ void Stats::display(
|
||||||
int totalServers = NodeList::getInstance()->size();
|
int totalServers = NodeList::getInstance()->size();
|
||||||
|
|
||||||
lines = _expanded ? 5 : 3;
|
lines = _expanded ? 5 : 3;
|
||||||
drawBackground(backgroundColor, horizontalOffset, 0, _generalStatsWidth, lines * STATS_PELS_PER_LINE + 10);
|
int columnOneWidth = _generalStatsWidth;
|
||||||
|
|
||||||
|
PerformanceTimer::tallyAllTimerRecords(); // do this even if we're not displaying them, so they don't stack up
|
||||||
|
|
||||||
|
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayTimingDetails)) {
|
||||||
|
|
||||||
|
columnOneWidth = _generalStatsWidth + _pingStatsWidth + _geoStatsWidth; // make it 3 columns wide...
|
||||||
|
// we will also include room for 1 line per timing record and a header of 4 lines
|
||||||
|
lines += 4;
|
||||||
|
|
||||||
|
const QMap<QString, PerformanceTimerRecord>& allRecords = PerformanceTimer::getAllTimerRecords();
|
||||||
|
QMapIterator<QString, PerformanceTimerRecord> i(allRecords);
|
||||||
|
while (i.hasNext()) {
|
||||||
|
i.next();
|
||||||
|
if (includeTimingRecord(i.key())) {
|
||||||
|
lines++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
drawBackground(backgroundColor, horizontalOffset, 0, columnOneWidth, lines * STATS_PELS_PER_LINE + 10);
|
||||||
horizontalOffset += 5;
|
horizontalOffset += 5;
|
||||||
|
|
||||||
|
int columnOneHorizontalOffset = horizontalOffset;
|
||||||
|
|
||||||
char serverNodes[30];
|
char serverNodes[30];
|
||||||
sprintf(serverNodes, "Servers: %d", totalServers);
|
sprintf(serverNodes, "Servers: %d", totalServers);
|
||||||
|
@ -249,6 +271,46 @@ void Stats::display(
|
||||||
verticalOffset += STATS_PELS_PER_LINE;
|
verticalOffset += STATS_PELS_PER_LINE;
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, averageMegabitsPerSecond, color);
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, averageMegabitsPerSecond, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: the display of these timing details should all be moved to JavaScript
|
||||||
|
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayTimingDetails)) {
|
||||||
|
// Timing details...
|
||||||
|
const int TIMER_OUTPUT_LINE_LENGTH = 1000;
|
||||||
|
char perfLine[TIMER_OUTPUT_LINE_LENGTH];
|
||||||
|
verticalOffset += STATS_PELS_PER_LINE * 4; // skip 4 lines to be under the other columns
|
||||||
|
drawText(columnOneHorizontalOffset, verticalOffset, scale, rotation, font,
|
||||||
|
"-------------------------------------------------------- Function "
|
||||||
|
"------------------------------------------------------- --msecs- -calls--", color);
|
||||||
|
|
||||||
|
// First iterate all the records, and for the ones that should be included, insert them into
|
||||||
|
// a new Map sorted by average time...
|
||||||
|
QMap<float, QString> sortedRecords;
|
||||||
|
const QMap<QString, PerformanceTimerRecord>& allRecords = PerformanceTimer::getAllTimerRecords();
|
||||||
|
QMapIterator<QString, PerformanceTimerRecord> i(allRecords);
|
||||||
|
|
||||||
|
while (i.hasNext()) {
|
||||||
|
i.next();
|
||||||
|
if (includeTimingRecord(i.key())) {
|
||||||
|
float averageTime = (float)i.value().getMovingAverage() / (float)USECS_PER_MSEC;
|
||||||
|
sortedRecords.insertMulti(averageTime, i.key());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QMapIterator<float, QString> j(sortedRecords);
|
||||||
|
j.toBack();
|
||||||
|
while (j.hasPrevious()) {
|
||||||
|
j.previous();
|
||||||
|
QString functionName = j.value();
|
||||||
|
const PerformanceTimerRecord& record = allRecords.value(functionName);
|
||||||
|
|
||||||
|
sprintf(perfLine, "%120s: %8.4f [%6llu]", qPrintable(functionName),
|
||||||
|
(float)record.getMovingAverage() / (float)USECS_PER_MSEC,
|
||||||
|
record.getCount());
|
||||||
|
|
||||||
|
verticalOffset += STATS_PELS_PER_LINE;
|
||||||
|
drawText(columnOneHorizontalOffset, verticalOffset, scale, rotation, font, perfLine, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
verticalOffset = 0;
|
verticalOffset = 0;
|
||||||
horizontalOffset = _lastHorizontalOffset + _generalStatsWidth +1;
|
horizontalOffset = _lastHorizontalOffset + _generalStatsWidth +1;
|
||||||
|
@ -283,7 +345,11 @@ void Stats::display(
|
||||||
}
|
}
|
||||||
|
|
||||||
lines = _expanded ? 4 : 3;
|
lines = _expanded ? 4 : 3;
|
||||||
drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10);
|
|
||||||
|
// only draw our background if column one didn't draw a wide background
|
||||||
|
if (columnOneWidth == _generalStatsWidth) {
|
||||||
|
drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10);
|
||||||
|
}
|
||||||
horizontalOffset += 5;
|
horizontalOffset += 5;
|
||||||
|
|
||||||
|
|
||||||
|
@ -319,7 +385,9 @@ void Stats::display(
|
||||||
|
|
||||||
lines = _expanded ? 8 : 3;
|
lines = _expanded ? 8 : 3;
|
||||||
|
|
||||||
drawBackground(backgroundColor, horizontalOffset, 0, _geoStatsWidth, lines * STATS_PELS_PER_LINE + 10);
|
if (columnOneWidth == _generalStatsWidth) {
|
||||||
|
drawBackground(backgroundColor, horizontalOffset, 0, _geoStatsWidth, lines * STATS_PELS_PER_LINE + 10);
|
||||||
|
}
|
||||||
horizontalOffset += 5;
|
horizontalOffset += 5;
|
||||||
|
|
||||||
char avatarPosition[200];
|
char avatarPosition[200];
|
||||||
|
@ -393,23 +461,9 @@ void Stats::display(
|
||||||
|
|
||||||
lines = _expanded ? 14 : 3;
|
lines = _expanded ? 14 : 3;
|
||||||
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) {
|
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) {
|
||||||
lines += 9; // spatial audio processing adds 1 spacing line and 8 extra lines of info
|
lines += 10; // spatial audio processing adds 1 spacing line and 8 extra lines of info
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayTimingDetails)) {
|
|
||||||
// we will also include room for 1 line per timing record and a header
|
|
||||||
lines += 1;
|
|
||||||
|
|
||||||
const QMap<QString, PerformanceTimerRecord>& allRecords = PerformanceTimer::getAllTimerRecords();
|
|
||||||
QMapIterator<QString, PerformanceTimerRecord> i(allRecords);
|
|
||||||
while (i.hasNext()) {
|
|
||||||
i.next();
|
|
||||||
if (includeTimingRecord(i.key())) {
|
|
||||||
lines++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
drawBackground(backgroundColor, horizontalOffset, 0, glWidget->width() - horizontalOffset,
|
drawBackground(backgroundColor, horizontalOffset, 0, glWidget->width() - horizontalOffset,
|
||||||
lines * STATS_PELS_PER_LINE + 10);
|
lines * STATS_PELS_PER_LINE + 10);
|
||||||
horizontalOffset += 5;
|
horizontalOffset += 5;
|
||||||
|
@ -432,6 +486,12 @@ void Stats::display(
|
||||||
verticalOffset += STATS_PELS_PER_LINE;
|
verticalOffset += STATS_PELS_PER_LINE;
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
|
||||||
|
|
||||||
|
voxelStats.str("");
|
||||||
|
voxelStats << "Mesh Parts Rendered Opaque: " << entities->getOpaqueMeshPartsRendered()
|
||||||
|
<< " Translucent:" << entities->getTranslucentMeshPartsRendered();
|
||||||
|
verticalOffset += STATS_PELS_PER_LINE;
|
||||||
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
|
||||||
|
|
||||||
|
|
||||||
// Local Voxel Memory Usage
|
// Local Voxel Memory Usage
|
||||||
voxelStats.str("");
|
voxelStats.str("");
|
||||||
|
@ -585,32 +645,6 @@ void Stats::display(
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
|
drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
PerformanceTimer::tallyAllTimerRecords();
|
|
||||||
|
|
||||||
// TODO: the display of these timing details should all be moved to JavaScript
|
|
||||||
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayTimingDetails)) {
|
|
||||||
// Timing details...
|
|
||||||
const int TIMER_OUTPUT_LINE_LENGTH = 300;
|
|
||||||
char perfLine[TIMER_OUTPUT_LINE_LENGTH];
|
|
||||||
verticalOffset += STATS_PELS_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font,
|
|
||||||
"--------------------- Function -------------------- --msecs- -calls--", color);
|
|
||||||
|
|
||||||
const QMap<QString, PerformanceTimerRecord>& allRecords = PerformanceTimer::getAllTimerRecords();
|
|
||||||
QMapIterator<QString, PerformanceTimerRecord> i(allRecords);
|
|
||||||
while (i.hasNext()) {
|
|
||||||
i.next();
|
|
||||||
if (includeTimingRecord(i.key())) {
|
|
||||||
sprintf(perfLine, "%50s: %8.4f [%6llu]", qPrintable(i.key()),
|
|
||||||
(float)i.value().getMovingAverage() / (float)USECS_PER_MSEC,
|
|
||||||
i.value().getCount());
|
|
||||||
|
|
||||||
verticalOffset += STATS_PELS_PER_LINE;
|
|
||||||
drawText(horizontalOffset, verticalOffset, scale, rotation, font, perfLine, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) {
|
if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) {
|
||||||
verticalOffset += STATS_PELS_PER_LINE; // space one line...
|
verticalOffset += STATS_PELS_PER_LINE; // space one line...
|
||||||
|
|
||||||
|
|
|
@ -179,6 +179,9 @@ void OctreeRenderer::render(RenderMode renderMode) {
|
||||||
_trianglesRendered = args._trianglesRendered;
|
_trianglesRendered = args._trianglesRendered;
|
||||||
_quadsRendered = args._quadsRendered;
|
_quadsRendered = args._quadsRendered;
|
||||||
|
|
||||||
|
_translucentMeshPartsRendered = args._translucentMeshPartsRendered;
|
||||||
|
_opaqueMeshPartsRendered = args._opaqueMeshPartsRendered;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OctreeRenderer::clear() {
|
void OctreeRenderer::clear() {
|
||||||
|
|
|
@ -74,7 +74,10 @@ public:
|
||||||
|
|
||||||
int getTrianglesRendered() const { return _trianglesRendered; }
|
int getTrianglesRendered() const { return _trianglesRendered; }
|
||||||
int getQuadsRendered() const { return _quadsRendered; }
|
int getQuadsRendered() const { return _quadsRendered; }
|
||||||
|
|
||||||
|
int getTranslucentMeshPartsRendered() const { return _translucentMeshPartsRendered; }
|
||||||
|
int getOpaqueMeshPartsRendered() const { return _opaqueMeshPartsRendered; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
Octree* _tree;
|
Octree* _tree;
|
||||||
bool _managedTree;
|
bool _managedTree;
|
||||||
|
@ -90,6 +93,9 @@ protected:
|
||||||
|
|
||||||
int _trianglesRendered;
|
int _trianglesRendered;
|
||||||
int _quadsRendered;
|
int _quadsRendered;
|
||||||
|
|
||||||
|
int _translucentMeshPartsRendered;
|
||||||
|
int _opaqueMeshPartsRendered;
|
||||||
};
|
};
|
||||||
|
|
||||||
class RenderArgs {
|
class RenderArgs {
|
||||||
|
@ -110,6 +116,9 @@ public:
|
||||||
|
|
||||||
int _trianglesRendered;
|
int _trianglesRendered;
|
||||||
int _quadsRendered;
|
int _quadsRendered;
|
||||||
|
|
||||||
|
int _translucentMeshPartsRendered;
|
||||||
|
int _opaqueMeshPartsRendered;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue