summaryrefslogtreecommitdiff
path: root/indra/newview/gltfscenemanager.cpp
diff options
context:
space:
mode:
authorDave Parks <davep@lindenlab.com>2024-06-21 13:13:08 -0500
committerGitHub <noreply@github.com>2024-06-21 13:13:08 -0500
commit80ea30af1a8b38360f71c29cc45872c4399dab0d (patch)
treedb07a706a4c896b11b8abca8a7d8815797a8d0be /indra/newview/gltfscenemanager.cpp
parent9fb9e8f33cb33a1535f43b4be030009c192ea92b (diff)
#1769 gltf optimization pass (#1816)
#1814 and #1517 Fix mirror update rate and occlusion culling
Diffstat (limited to 'indra/newview/gltfscenemanager.cpp')
-rw-r--r--indra/newview/gltfscenemanager.cpp350
1 files changed, 168 insertions, 182 deletions
diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp
index e01aec0497..7b2de4d6de 100644
--- a/indra/newview/gltfscenemanager.cpp
+++ b/indra/newview/gltfscenemanager.cpp
@@ -437,6 +437,8 @@ void GLTFSceneManager::onGLTFLoadComplete(const LLUUID& id, LLAssetType::EType a
void GLTFSceneManager::update()
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
+
for (U32 i = 0; i < mObjects.size(); ++i)
{
if (mObjects[i]->isDead() || mObjects[i]->mGLTFAsset == nullptr)
@@ -552,6 +554,7 @@ void GLTFSceneManager::render(bool opaque, bool rigged, bool unlit)
void GLTFSceneManager::render(U8 variant)
{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
// just render the whole scene by traversing the whole scenegraph
// Assumes camera transform is already set and appropriate shader is already bound.
// Eventually we'll want a smarter render pipe that has pre-sorted the scene graph
@@ -563,8 +566,6 @@ void GLTFSceneManager::render(U8 variant)
render((U8) (variant | LLGLSLShader::GLTFVariant::MULTI_UV));
}
- gGL.matrixMode(LLRender::MM_MODELVIEW);
-
bool rigged = variant & LLGLSLShader::GLTFVariant::RIGGED;
for (U32 i = 0; i < mObjects.size(); ++i)
@@ -581,19 +582,11 @@ void GLTFSceneManager::render(U8 variant)
LLMatrix4a mat = mObjects[i]->getGLTFAssetToAgentTransform();
- LLMatrix4a modelview;
- modelview.loadu(gGLModelView);
-
- matMul(mat, modelview, modelview);
-
- mat4 mdv = glm::make_mat4(modelview.getF32ptr());
- asset->updateRenderTransforms(mdv);
+ // provide a modelview matrix that goes from asset to camera space
+ // (matrix palettes are in asset space)
+ gGL.loadMatrix(gGLModelView);
+ gGL.multMatrix(mat.getF32ptr());
- if (rigged)
- { // provide a modelview matrix that goes from asset to camera space for rigged render passes
- // (matrix palettes are in asset space)
- gGL.loadMatrix(glm::value_ptr(mdv));
- }
render(*asset, variant);
gGL.popMatrix();
@@ -602,176 +595,185 @@ void GLTFSceneManager::render(U8 variant)
void GLTFSceneManager::render(Asset& asset, U8 variant)
{
- bool opaque = !(variant & LLGLSLShader::GLTFVariant::ALPHA_BLEND);
- bool rigged = variant & LLGLSLShader::GLTFVariant::RIGGED;
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
- if (opaque)
+ for (U32 ds = 0; ds < 2; ++ds)
{
- gGLTFPBRMetallicRoughnessProgram.bind(variant);
- }
- else
- { // alpha shaders need all the shadow map setup etc
- gPipeline.bindDeferredShader(gGLTFPBRMetallicRoughnessProgram.mGLTFVariants[variant]);
- }
+ RenderData& rd = asset.mRenderData[ds];
+ auto& batches = rd.mBatches[variant];
- for (auto& node : asset.mNodes)
- {
- if (node.mSkin != INVALID_INDEX)
+ if (batches.empty())
{
- if (rigged)
- {
- Skin& skin = asset.mSkins[node.mSkin];
- glBindBufferBase(GL_UNIFORM_BUFFER, LLGLSLShader::UB_GLTF_JOINTS, skin.mUBO);
- }
+ return;
}
- if (node.mMesh != INVALID_INDEX)
+ LLGLDisable cull_face(ds == 1 ? GL_CULL_FACE : 0);
+
+ bool opaque = !(variant & LLGLSLShader::GLTFVariant::ALPHA_BLEND);
+ bool rigged = variant & LLGLSLShader::GLTFVariant::RIGGED;
+
+ bool shader_bound = false;
+
+ for (U32 i = 0; i < batches.size(); ++i)
{
- Mesh& mesh = asset.mMeshes[node.mMesh];
- for (auto& primitive : mesh.mPrimitives)
+ if (batches[i].mPrimitives.empty() || batches[i].mVertexBuffer.isNull())
{
- if (primitive.mShaderVariant != variant)
+ continue;
+ }
+
+ if (!shader_bound)
+ { // don't bind the shader until we know we have somthing to render
+ if (opaque)
{
- continue;
+ gGLTFPBRMetallicRoughnessProgram.bind(variant);
+ }
+ else
+ { // alpha shaders need all the shadow map setup etc
+ gPipeline.bindDeferredShader(gGLTFPBRMetallicRoughnessProgram.mGLTFVariants[variant]);
}
if (!rigged)
{
- gGL.loadMatrix((F32*)glm::value_ptr(node.mRenderMatrix));
+ glBindBufferBase(GL_UNIFORM_BUFFER, LLGLSLShader::UB_GLTF_NODES, asset.mNodesUBO);
}
- bool cull = true;
- if (primitive.mMaterial != INVALID_INDEX)
- {
- Material& material = asset.mMaterials[primitive.mMaterial];
- bind(asset, material);
- cull = !material.mDoubleSided;
- }
- else
+ glBindBufferBase(GL_UNIFORM_BUFFER, LLGLSLShader::UB_GLTF_MATERIALS, asset.mMaterialsUBO);
+
+ for (U32 i = 0; i < TEXTURE_TYPE_COUNT; ++i)
{
- LLFetchedGLTFMaterial::sDefault.bind();
+ mLastTexture[i] = -2;
}
- LLGLDisable cull_face(!cull ? GL_CULL_FACE : 0);
+ gGL.syncMatrices();
+ shader_bound = true;
+ }
+
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltfdc - set vb");
+ batches[i].mVertexBuffer->setBuffer();
+ }
+
+ S32 mat_idx = i - 1;
+ if (mat_idx != INVALID_INDEX)
+ {
+ Material& material = asset.mMaterials[mat_idx];
+ bind(asset, material);
+ }
+ else
+ {
+ LLFetchedGLTFMaterial::sDefault.bind();
+ LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::GLTF_MATERIAL_ID, -1);
+ }
+
+ for (auto& pdata : batches[i].mPrimitives)
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("GLTF draw call");
+ Node& node = asset.mNodes[pdata.mNodeIndex];
+ Mesh& mesh = asset.mMeshes[node.mMesh];
+ Primitive& primitive = mesh.mPrimitives[pdata.mPrimitiveIndex];
- primitive.mVertexBuffer->setBuffer();
- if (primitive.mVertexBuffer->getNumIndices() > 0)
+ if (rigged)
{
- primitive.mVertexBuffer->draw(primitive.mGLMode, primitive.mVertexBuffer->getNumIndices(), 0);
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltfdc - bind skin");
+ llassert(node.mSkin != INVALID_INDEX);
+ Skin& skin = asset.mSkins[node.mSkin];
+ glBindBufferBase(GL_UNIFORM_BUFFER, LLGLSLShader::UB_GLTF_JOINTS, skin.mUBO);
}
else
{
- primitive.mVertexBuffer->drawArrays(primitive.mGLMode, 0, primitive.mVertexBuffer->getNumVerts());
+ LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::GLTF_NODE_ID, pdata.mNodeIndex);
+ }
+
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gltfdc - push vb");
+
+ primitive.mVertexBuffer->drawRangeFast(primitive.mGLMode, primitive.mVertexOffset, primitive.mVertexOffset + primitive.getVertexCount() - 1, primitive.getIndexCount(), primitive.mIndexOffset);
}
}
}
}
}
-static void bindTexture(Asset& asset, S32 uniform, Material::TextureInfo& info, LLViewerTexture* fallback)
+void GLTFSceneManager::bindTexture(Asset& asset, TextureType texture_type, TextureInfo& info, LLViewerTexture* fallback)
{
- if (info.mIndex != INVALID_INDEX)
+ U8 type_idx = (U8)texture_type;
+
+ if (info.mIndex == mLastTexture[type_idx])
+ { //already bound
+ return;
+ }
+
+ S32 uniform[] =
{
- Texture& texture = asset.mTextures[info.mIndex];
+ LLShaderMgr::DIFFUSE_MAP,
+ LLShaderMgr::NORMAL_MAP,
+ LLShaderMgr::METALLIC_ROUGHNESS_MAP,
+ LLShaderMgr::OCCLUSION_MAP,
+ LLShaderMgr::EMISSIVE_MAP
+ };
- LLViewerTexture* tex = asset.mImages[texture.mSource].mTexture;
- if (tex)
+ S32 channel = LLGLSLShader::sCurBoundShaderPtr->getTextureChannel(uniform[(U8)type_idx]);
+
+ if (channel > -1)
+ {
+ glActiveTexture(GL_TEXTURE0 + channel);
+
+ if (info.mIndex != INVALID_INDEX)
{
- tex->addTextureStats(2048.f * 2048.f);
- S32 channel = LLGLSLShader::sCurBoundShaderPtr->bindTexture(uniform, tex);
+ Texture& texture = asset.mTextures[info.mIndex];
+
+ LLViewerTexture* tex = asset.mImages[texture.mSource].mTexture;
+ if (tex)
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_GLTF("gl bind texture");
+ glBindTexture(GL_TEXTURE_2D, tex->getTexName());
- if (channel != -1 && texture.mSampler != -1)
- { // set sampler state
- Sampler& sampler = asset.mSamplers[texture.mSampler];
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, sampler.mWrapS);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, sampler.mWrapT);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, sampler.mMagFilter);
+ if (channel != -1 && texture.mSampler != -1)
+ { // set sampler state
+ Sampler& sampler = asset.mSamplers[texture.mSampler];
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, sampler.mWrapS);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, sampler.mWrapT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, sampler.mMagFilter);
- // NOTE: do not set min filter. Always respect client preference for min filter
+ // NOTE: do not set min filter. Always respect client preference for min filter
+ }
+ else
+ {
+ // set default sampler state
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ }
}
else
{
- // set default sampler state
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glBindTexture(GL_TEXTURE_2D, fallback->getTexName());
}
}
else
{
- LLGLSLShader::sCurBoundShaderPtr->bindTexture(uniform, fallback);
+ glBindTexture(GL_TEXTURE_2D, fallback->getTexName());
}
}
- else
- {
- LLGLSLShader::sCurBoundShaderPtr->bindTexture(uniform, fallback);
- }
}
void GLTFSceneManager::bind(Asset& asset, Material& material)
{
- // bind for rendering (derived from LLFetchedGLTFMaterial::bind)
- // glTF 2.0 Specification 3.9.4. Alpha Coverage
- // mAlphaCutoff is only valid for LLGLTFMaterial::ALPHA_MODE_MASK
- F32 min_alpha = -1.0;
-
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_GLTF;
LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;
- if (!LLPipeline::sShadowRender || (material.mAlphaMode == Material::AlphaMode::BLEND))
- {
- if (material.mAlphaMode == Material::AlphaMode::MASK)
- {
- // dividing the alpha cutoff by transparency here allows the shader to compare against
- // the alpha value of the texture without needing the transparency value
- if (material.mPbrMetallicRoughness.mBaseColorFactor.a > 0.f)
- {
- min_alpha = material.mAlphaCutoff / material.mPbrMetallicRoughness.mBaseColorFactor.a;
- }
- else
- {
- min_alpha = 1024.f;
- }
- }
- shader->uniform1f(LLShaderMgr::MINIMUM_ALPHA, min_alpha);
- }
-
- bindTexture(asset, LLShaderMgr::DIFFUSE_MAP, material.mPbrMetallicRoughness.mBaseColorTexture, LLViewerFetchedTexture::sWhiteImagep);
-
- F32 tf[8];
- material.mPbrMetallicRoughness.mBaseColorTexture.mTextureTransform.getPacked(tf);
- shader->uniform4fv(LLShaderMgr::TEXTURE_BASE_COLOR_TRANSFORM, 2, tf);
- shader->uniform1i(LLShaderMgr::BASE_COLOR_TEXCOORD, material.mPbrMetallicRoughness.mBaseColorTexture.getTexCoord());
+ bindTexture(asset, TextureType::BASE_COLOR, material.mPbrMetallicRoughness.mBaseColorTexture, LLViewerFetchedTexture::sWhiteImagep);
if (!LLPipeline::sShadowRender)
{
- bindTexture(asset, LLShaderMgr::NORMAL_MAP, material.mNormalTexture, LLViewerFetchedTexture::sFlatNormalImagep);
- bindTexture(asset, LLShaderMgr::METALLIC_ROUGHNESS_MAP, material.mPbrMetallicRoughness.mMetallicRoughnessTexture, LLViewerFetchedTexture::sWhiteImagep);
- bindTexture(asset, LLShaderMgr::OCCLUSION_MAP, material.mOcclusionTexture, LLViewerFetchedTexture::sWhiteImagep);
- bindTexture(asset, LLShaderMgr::EMISSIVE_MAP, material.mEmissiveTexture, LLViewerFetchedTexture::sWhiteImagep);
-
- // NOTE: base color factor is baked into vertex stream
-
- shader->uniform1f(LLShaderMgr::ROUGHNESS_FACTOR, material.mPbrMetallicRoughness.mRoughnessFactor);
- shader->uniform1f(LLShaderMgr::METALLIC_FACTOR, material.mPbrMetallicRoughness.mMetallicFactor);
- shader->uniform3fv(LLShaderMgr::EMISSIVE_COLOR, 1, glm::value_ptr(material.mEmissiveFactor));
-
- material.mNormalTexture.mTextureTransform.getPacked(tf);
- shader->uniform4fv(LLShaderMgr::TEXTURE_NORMAL_TRANSFORM, 2, tf);
- shader->uniform1i(LLShaderMgr::NORMAL_TEXCOORD, material.mNormalTexture.getTexCoord());
-
- material.mPbrMetallicRoughness.mMetallicRoughnessTexture.mTextureTransform.getPacked(tf);
- shader->uniform4fv(LLShaderMgr::TEXTURE_METALLIC_ROUGHNESS_TRANSFORM, 2, tf);
- shader->uniform1i(LLShaderMgr::METALLIC_ROUGHNESS_TEXCOORD, material.mPbrMetallicRoughness.mMetallicRoughnessTexture.getTexCoord());
-
- material.mOcclusionTexture.mTextureTransform.getPacked(tf);
- shader->uniform4fv(LLShaderMgr::TEXTURE_OCCLUSION_TRANSFORM, 2, tf);
- shader->uniform1i(LLShaderMgr::OCCLUSION_TEXCOORD, material.mOcclusionTexture.getTexCoord());
-
- material.mEmissiveTexture.mTextureTransform.getPacked(tf);
- shader->uniform4fv(LLShaderMgr::TEXTURE_EMISSIVE_TRANSFORM, 2, tf);
- shader->uniform1i(LLShaderMgr::EMISSIVE_TEXCOORD, material.mEmissiveTexture.getTexCoord());
+ bindTexture(asset, TextureType::NORMAL, material.mNormalTexture, LLViewerFetchedTexture::sFlatNormalImagep);
+ bindTexture(asset, TextureType::METALLIC_ROUGHNESS, material.mPbrMetallicRoughness.mMetallicRoughnessTexture, LLViewerFetchedTexture::sWhiteImagep);
+ bindTexture(asset, TextureType::OCCLUSION, material.mOcclusionTexture, LLViewerFetchedTexture::sWhiteImagep);
+ bindTexture(asset, TextureType::EMISSIVE, material.mEmissiveTexture, LLViewerFetchedTexture::sWhiteImagep);
}
+
+ shader->uniform1i(LLShaderMgr::GLTF_MATERIAL_ID, &material - &asset.mMaterials[0]);
}
LLMatrix4a inverse(const LLMatrix4a& mat)
@@ -931,10 +933,11 @@ void renderAssetDebug(LLViewerObject* obj, Asset* asset)
// assumes modelview matrix is already set
gGL.pushMatrix();
-
// get raycast in asset space
LLMatrix4a agent_to_asset = obj->getAgentToGLTFAssetTransform();
+ gGL.multMatrix(agent_to_asset.getF32ptr());
+
vec4 start;
vec4 end;
@@ -952,7 +955,8 @@ void renderAssetDebug(LLViewerObject* obj, Asset* asset)
if (node.mMesh != INVALID_INDEX)
{
- gGL.loadMatrix((F32*)glm::value_ptr(node.mRenderMatrix));
+ gGL.pushMatrix();
+ gGL.multMatrix((F32*)glm::value_ptr(node.mAssetMatrix));
// draw bounding box of mesh primitives
if (gPipeline.hasRenderDebugMask(LLPipeline::RENDER_DEBUG_BBOXES))
@@ -995,6 +999,7 @@ void renderAssetDebug(LLViewerObject* obj, Asset* asset)
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
#endif
+ gGL.popMatrix();
}
}
@@ -1013,35 +1018,15 @@ void GLTFSceneManager::renderDebug()
gDebugProgram.bind();
+ gGL.pushMatrix();
+ gGL.loadMatrix(gGLModelView);
+
LLGLDisable cullface(GL_CULL_FACE);
LLGLEnable blend(GL_BLEND);
gGL.setSceneBlendType(LLRender::BT_ALPHA);
gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE);
gPipeline.disableLights();
- // force update all mRenderMatrix, not just nodes with meshes
- for (auto& obj : mObjects)
- {
- if (obj->isDead() || obj->mGLTFAsset == nullptr)
- {
- continue;
- }
-
- mat4 mat = glm::make_mat4(obj->getGLTFAssetToAgentTransform().getF32ptr());
-
- mat4 modelview = glm::make_mat4(gGLModelView);
-
-
- modelview = modelview * mat;
-
- Asset* asset = obj->mGLTFAsset.get();
-
- for (auto& node : asset->mNodes)
- {
- node.mRenderMatrix = modelview * node.mAssetMatrix;
- }
- }
-
for (auto& obj : mObjects)
{
if (obj->isDead() || obj->mGLTFAsset == nullptr)
@@ -1062,9 +1047,6 @@ void GLTFSceneManager::renderDebug()
LLGLDepthTest depth(GL_TRUE, i == 0 ? GL_FALSE : GL_TRUE, i == 0 ? GL_GREATER : GL_LEQUAL);
LLGLState blend(GL_BLEND, i == 0 ? GL_TRUE : GL_FALSE);
-
- gGL.pushMatrix();
-
for (auto& obj : mObjects)
{
if (obj->isDead() || obj->mGLTFAsset == nullptr)
@@ -1072,20 +1054,16 @@ void GLTFSceneManager::renderDebug()
continue;
}
- mat4 mat = glm::make_mat4(obj->getGLTFAssetToAgentTransform().getF32ptr());
+ gGL.pushMatrix();
- mat4 modelview = glm::make_mat4(gGLModelView);
-
- modelview = modelview * mat;
+ gGL.multMatrix(obj->getGLTFAssetToAgentTransform().getF32ptr());
Asset* asset = obj->mGLTFAsset.get();
for (auto& node : asset->mNodes)
{
- // force update all mRenderMatrix, not just nodes with meshes
- node.mRenderMatrix = modelview * node.mAssetMatrix;
-
- gGL.loadMatrix(glm::value_ptr(node.mRenderMatrix));
+ gGL.pushMatrix();
+ gGL.multMatrix(glm::value_ptr(node.mAssetMatrix));
// render x-axis red, y-axis green, z-axis blue
gGL.color4f(1.f, 0.f, 0.f, 0.5f);
gGL.begin(LLRender::LINES);
@@ -1121,12 +1099,12 @@ void GLTFSceneManager::renderDebug()
}
gGL.end();
gGL.flush();
+ gGL.popMatrix();
}
- }
- gGL.popMatrix();
+ gGL.popMatrix();
+ }
}
-
}
@@ -1140,28 +1118,36 @@ void GLTFSceneManager::renderDebug()
if (drawable)
{
- gGL.pushMatrix();
- Asset* asset = drawable->getVObj()->mGLTFAsset.get();
- Node* node = &asset->mNodes[node_hit];
- Primitive* primitive = &asset->mMeshes[node->mMesh].mPrimitives[primitive_hit];
- gGL.flush();
- glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
- gGL.color3f(1, 0, 1);
- drawBoxOutline(intersection, LLVector4a(0.1f, 0.1f, 0.1f, 0.f));
+ LLViewerObject* obj = drawable->getVObj();
+ if (obj)
+ {
+ gGL.pushMatrix();
+ gGL.multMatrix(obj->getGLTFAssetToAgentTransform().getF32ptr());
+ Asset* asset = obj->mGLTFAsset.get();
+ Node* node = &asset->mNodes[node_hit];
+ Primitive* primitive = &asset->mMeshes[node->mMesh].mPrimitives[primitive_hit];
- gGL.loadMatrix(glm::value_ptr(node->mRenderMatrix));
+ gGL.flush();
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
+ gGL.color3f(1, 0, 1);
+ drawBoxOutline(intersection, LLVector4a(0.1f, 0.1f, 0.1f, 0.f));
+ gGL.multMatrix(glm::value_ptr(node->mAssetMatrix));
- auto* listener = (LLVolumeOctreeListener*) primitive->mOctree->getListener(0);
- drawBoxOutline(listener->mBounds[0], listener->mBounds[1]);
+ auto* listener = (LLVolumeOctreeListener*)primitive->mOctree->getListener(0);
+ drawBoxOutline(listener->mBounds[0], listener->mBounds[1]);
- gGL.flush();
- glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
- gGL.popMatrix();
+ gGL.flush();
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
+ gGL.popMatrix();
+ }
}
}
+
+ gGL.popMatrix();
gDebugProgram.unbind();
+
}