From ae6fbc9c6061a2c3041db5450f17f75c103bd212 Mon Sep 17 00:00:00 2001 From: Oleg Endo <5551493-oleg.endo@users.noreply.gitlab.com> Date: Fri, 17 Apr 2020 16:10:04 +0000 Subject: [PATCH] Improve 3D viewer rendering performance * Use GL vertex buffers and index buffers for 3D model rendering * Use material or average vertex color for bounding boxes instead of red * Reinstate bounding box rendering with GL vertex/index buffers * Use compact vertex/index data representation - 8-bit normals - 8-bit colors - 16-bit or 32-bit indices, depending on model size This should improve performance a bit on lower end GPUs with less memory and bandwidth. Fixes #4112 --- .../3d_model_viewer/c3d_model_viewer.cpp | 4 + .../c3d_render_ogl_legacy.cpp | 44 +- .../3d_render_ogl_legacy/c_ogl_3dmodel.cpp | 697 ++++++++++-------- .../3d_render_ogl_legacy/c_ogl_3dmodel.h | 82 ++- 4 files changed, 499 insertions(+), 328 deletions(-) diff --git a/3d-viewer/3d_model_viewer/c3d_model_viewer.cpp b/3d-viewer/3d_model_viewer/c3d_model_viewer.cpp index 4d00a8b7e1..e1bb868a0f 100644 --- a/3d-viewer/3d_model_viewer/c3d_model_viewer.cpp +++ b/3d-viewer/3d_model_viewer/c3d_model_viewer.cpp @@ -310,9 +310,13 @@ void C3D_MODEL_VIEWER::OnPaint( wxPaintEvent &event ) glTranslatef( -model_center.x, -model_center.y, -model_center.z ); + m_ogl_3dmodel->BeginDrawMulti(); + m_ogl_3dmodel->Draw_opaque(); m_ogl_3dmodel->Draw_transparent(); + m_ogl_3dmodel->EndDrawMulti(); + glPopMatrix(); } diff --git a/3d-viewer/3d_rendering/3d_render_ogl_legacy/c3d_render_ogl_legacy.cpp b/3d-viewer/3d_rendering/3d_render_ogl_legacy/c3d_render_ogl_legacy.cpp index 07a1873c58..6165afaa65 100644 --- a/3d-viewer/3d_rendering/3d_render_ogl_legacy/c3d_render_ogl_legacy.cpp +++ b/3d-viewer/3d_rendering/3d_render_ogl_legacy/c3d_render_ogl_legacy.cpp @@ -1079,6 +1079,8 @@ void C3D_RENDER_OGL_LEGACY::render_solder_mask_layer( PCB_LAYER_ID aLayerID, void C3D_RENDER_OGL_LEGACY::render_3D_models( bool aRenderTopOrBot, bool aRenderTransparentOnly ) { + C_OGL_3DMODEL::BeginDrawMulti(); + // Go for all modules for( auto module : m_boardAdapter.GetBoard()->Modules() ) { @@ -1088,6 +1090,8 @@ void C3D_RENDER_OGL_LEGACY::render_3D_models( bool aRenderTopOrBot, || ( !aRenderTopOrBot && module->IsFlipped() ) ) render_3D_module( module, aRenderTransparentOnly ); } + + C_OGL_3DMODEL::EndDrawMulti(); } @@ -1122,33 +1126,30 @@ void C3D_RENDER_OGL_LEGACY::render_3D_module( const MODULE* module, modelunit_to_3d_units_factor ); // Get the list of model files for this model - auto sM = module->Models().begin(); - auto eM = module->Models().end(); - - while( sM != eM ) + for (auto& sM : module->Models ()) { - if( !sM->m_Filename.empty() ) + if( !sM.m_Filename.empty() ) { // Check if the model is present in our cache map - if( m_3dmodel_map.find( sM->m_Filename ) != m_3dmodel_map.end() ) + auto cache_i = m_3dmodel_map.find( sM.m_Filename ); + if( cache_i != m_3dmodel_map.end() ) { - // It is not present, try get it from cache - const C_OGL_3DMODEL *modelPtr = m_3dmodel_map[ sM->m_Filename ]; - - if( modelPtr ) + if( const C_OGL_3DMODEL *modelPtr = cache_i->second ) { if( ( (!aRenderTransparentOnly) && modelPtr->Have_opaque() ) || ( aRenderTransparentOnly && modelPtr->Have_transparent() ) ) { glPushMatrix(); - glTranslatef( sM->m_Offset.x, sM->m_Offset.y, sM->m_Offset.z ); - - glRotatef( -sM->m_Rotation.z, 0.0f, 0.0f, 1.0f ); - glRotatef( -sM->m_Rotation.y, 0.0f, 1.0f, 0.0f ); - glRotatef( -sM->m_Rotation.x, 1.0f, 0.0f, 0.0f ); - - glScalef( sM->m_Scale.x, sM->m_Scale.y, sM->m_Scale.z ); + // FIXME: don't do this over and over again unless the + // values have changed. cache the matrix somewhere. + glm::mat4 mtx( 1 ); + mtx = glm::translate( mtx, { sM.m_Offset.x, sM.m_Offset.y, sM.m_Offset.z } ); + mtx = glm::rotate( mtx, glm::radians( (float)-sM.m_Rotation.z ), { 0.0f, 0.0f, 1.0f } ); + mtx = glm::rotate( mtx, glm::radians( (float)-sM.m_Rotation.y ), { 0.0f, 1.0f, 0.0f } ); + mtx = glm::rotate( mtx, glm::radians( (float)-sM.m_Rotation.x ), { 1.0f, 0.0f, 0.0f } ); + mtx = glm::scale( mtx, { sM.m_Scale.x, sM.m_Scale.y, sM.m_Scale.z } ); + glMultMatrixf( glm::value_ptr( mtx ) ); if( aRenderTransparentOnly ) modelPtr->Draw_transparent(); @@ -1160,17 +1161,16 @@ void C3D_RENDER_OGL_LEGACY::render_3D_module( const MODULE* module, glEnable( GL_BLEND ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - glLineWidth( 1 ); - modelPtr->Draw_bboxes(); - glDisable( GL_LIGHTING ); - glColor4f( 0.0f, 1.0f, 0.0f, 1.0f ); + glLineWidth( 1 ); + modelPtr->Draw_bboxes(); glLineWidth( 4 ); modelPtr->Draw_bbox(); glEnable( GL_LIGHTING ); + glDisable( GL_BLEND ); } glPopMatrix(); @@ -1178,8 +1178,6 @@ void C3D_RENDER_OGL_LEGACY::render_3D_module( const MODULE* module, } } } - - ++sM; } glPopMatrix(); diff --git a/3d-viewer/3d_rendering/3d_render_ogl_legacy/c_ogl_3dmodel.cpp b/3d-viewer/3d_rendering/3d_render_ogl_legacy/c_ogl_3dmodel.cpp index 5eb2f9f07c..c24ffd1eda 100644 --- a/3d-viewer/3d_rendering/3d_render_ogl_legacy/c_ogl_3dmodel.cpp +++ b/3d-viewer/3d_rendering/3d_render_ogl_legacy/c_ogl_3dmodel.cpp @@ -1,6 +1,7 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * + * Copyright (C) 2020 Oleg Endo * Copyright (C) 2015-2016 Mario Luzeiro * Copyright (C) 1992-2016 KiCad Developers, see AUTHORS.txt for contributors. * @@ -27,357 +28,461 @@ * @brief */ +#include #include "c_ogl_3dmodel.h" #include "ogl_legacy_utils.h" #include "../common_ogl/ogl_utils.h" #include "../3d_math.h" #include +#include +const wxChar * C_OGL_3DMODEL::m_logTrace = wxT( "KI_TRACE_EDA_OGL_3DMODEL" ); + +void C_OGL_3DMODEL::MakeBbox( const CBBOX &aBox, unsigned int aIdxOffset, + VERTEX *aVtxOut, GLuint *aIdxOut, + const glm::vec4 &aColor ) +{ + aVtxOut[0].m_pos = { aBox.Min().x, aBox.Min().y, aBox.Min().z }; + aVtxOut[1].m_pos = { aBox.Max().x, aBox.Min().y, aBox.Min().z }; + aVtxOut[2].m_pos = { aBox.Max().x, aBox.Max().y, aBox.Min().z }; + aVtxOut[3].m_pos = { aBox.Min().x, aBox.Max().y, aBox.Min().z }; + + aVtxOut[4].m_pos = { aBox.Min().x, aBox.Min().y, aBox.Max().z }; + aVtxOut[5].m_pos = { aBox.Max().x, aBox.Min().y, aBox.Max().z }; + aVtxOut[6].m_pos = { aBox.Max().x, aBox.Max().y, aBox.Max().z }; + aVtxOut[7].m_pos = { aBox.Min().x, aBox.Max().y, aBox.Max().z }; + + for( unsigned int i = 0; i < 8; ++i ) + aVtxOut[i].m_color = aVtxOut[i].m_cad_color = glm::clamp( aColor * 255.0f, 0.0f, 255.0f ); + + #define bbox_line( vtx_a, vtx_b )\ + do { *aIdxOut++ = vtx_a + aIdxOffset; \ + *aIdxOut++ = vtx_b + aIdxOffset; } while( 0 ) + + bbox_line( 0, 1 ); + bbox_line( 1, 2 ); + bbox_line( 2, 3 ); + bbox_line( 3, 0 ); + + bbox_line( 4, 5 ); + bbox_line( 5, 6 ); + bbox_line( 6, 7 ); + bbox_line( 7, 4 ); + + bbox_line( 0, 4 ); + bbox_line( 1, 5 ); + bbox_line( 2, 6 ); + bbox_line( 3, 7 ); + + #undef bbox_line +} C_OGL_3DMODEL::C_OGL_3DMODEL( const S3DMODEL &a3DModel, MATERIAL_MODE aMaterialMode ) { - m_ogl_idx_list_meshes = 0; - m_ogl_idx_list_opaque = 0; - m_ogl_idx_list_transparent = 0; - m_nr_meshes = 0; - m_meshs_bbox = NULL; + wxLogTrace( m_logTrace, wxT( "C_OGL_3DMODEL::C_OGL_3DMODEL %u meshes %u materials" ), + static_cast( a3DModel.m_MeshesSize ), + static_cast( a3DModel.m_MaterialsSize ) ); + + auto start_time = std::chrono::high_resolution_clock::now(); // Validate a3DModel pointers - wxASSERT( a3DModel.m_Materials != NULL ); - wxASSERT( a3DModel.m_Meshes != NULL ); + wxASSERT( a3DModel.m_Materials != nullptr ); + wxASSERT( a3DModel.m_Meshes != nullptr ); wxASSERT( a3DModel.m_MaterialsSize > 0 ); wxASSERT( a3DModel.m_MeshesSize > 0 ); - if( (a3DModel.m_Materials != NULL) && (a3DModel.m_Meshes != NULL) && - (a3DModel.m_MaterialsSize > 0) && (a3DModel.m_MeshesSize > 0) ) + m_material_mode = aMaterialMode; + + if( a3DModel.m_Materials == nullptr || a3DModel.m_Meshes == nullptr + || a3DModel.m_MaterialsSize == 0 || a3DModel.m_MeshesSize == 0 ) + return; + + // create empty bbox for each mesh. it will be updated when the vertices + // are copied. + m_meshes_bbox.resize( a3DModel.m_MeshesSize ); + + // copy materials for later use during rendering. + m_materials.reserve( a3DModel.m_MaterialsSize ); + + for( unsigned int i = 0; i < a3DModel.m_MaterialsSize; ++i ) + m_materials.emplace_back( a3DModel.m_Materials[i] ); + + // build temporary vertex and index buffers for bounding boxes. + // the first box is the outer box. + std::vector bbox_tmp_vertices( ( m_meshes_bbox.size() + 1 ) * bbox_vtx_count ); + std::vector bbox_tmp_indices( ( m_meshes_bbox.size() + 1 ) * bbox_idx_count ); + + // group all meshes by material. + // for each material create a combined vertex and index buffer. + // some models might have many sub-meshes. so iterate over the + // input meshes only once. + struct MESH_GROUP { - m_nr_meshes = a3DModel.m_MeshesSize; + std::vector m_vertices; + std::vector m_indices; + }; - m_meshs_bbox = new CBBOX[a3DModel.m_MeshesSize]; + std::vector mesh_groups( m_materials.size() ); - // Generate m_MeshesSize auxiliar lists to render the meshes - m_ogl_idx_list_meshes = glGenLists( a3DModel.m_MeshesSize ); + for( unsigned int mesh_i = 0; mesh_i < a3DModel.m_MeshesSize; ++mesh_i ) + { + const auto& mesh = a3DModel.m_Meshes[mesh_i]; - // Render each mesh of the model - // ///////////////////////////////////////////////////////////////////// - for( unsigned int mesh_i = 0; mesh_i < a3DModel.m_MeshesSize; ++mesh_i ) + // silently ignore meshes that have invalid material references + // or invalid geometry. + if( mesh.m_MaterialIdx >= m_materials.size() + || mesh.m_Positions == nullptr + || mesh.m_FaceIdx == nullptr + || mesh.m_Normals == nullptr + || mesh.m_FaceIdxSize == 0 + || mesh.m_VertexSize == 0 ) + continue; + + auto& mesh_group = mesh_groups[mesh.m_MaterialIdx]; + auto& material = m_materials[mesh.m_MaterialIdx]; + + if( material.IsTransparent() ) + m_have_transparent_meshes = true; + else + m_have_opaque_meshes = true; + + const unsigned int vtx_offset = mesh_group.m_vertices.size(); + mesh_group.m_vertices.resize( mesh_group.m_vertices.size() + mesh.m_VertexSize ); + + // copy vertex data and update the bounding box. + // use material color for mesh bounding box or some sort of average + // vertex color. + glm::vec3 avg_color = material.m_Diffuse; + + for( unsigned int vtx_i = 0; vtx_i < mesh.m_VertexSize; ++vtx_i ) { - if( glIsList( m_ogl_idx_list_meshes + mesh_i ) ) + m_meshes_bbox[mesh_i].Union( mesh.m_Positions[vtx_i] ); + + auto& vtx_out = mesh_group.m_vertices[vtx_offset + vtx_i]; + + vtx_out.m_pos = mesh.m_Positions[vtx_i]; + vtx_out.m_nrm = glm::clamp( glm::vec4( mesh.m_Normals[vtx_i], 1.0f ) * 127.0f, + -127.0f, 127.0f ); + + vtx_out.m_tex_uv = mesh.m_Texcoords != nullptr + ? mesh.m_Texcoords[vtx_i] + : glm::vec2 (0); + + if( mesh.m_Color != nullptr ) { - const SMESH &mesh = a3DModel.m_Meshes[mesh_i]; + avg_color = ( avg_color + mesh.m_Color[vtx_i] ) * 0.5f; - // Validate the mesh pointers - wxASSERT( mesh.m_Positions != NULL ); - wxASSERT( mesh.m_FaceIdx != NULL ); - wxASSERT( mesh.m_Normals != NULL ); + vtx_out.m_color = + glm::clamp( glm::vec4( mesh.m_Color[vtx_i], + 1 - material.m_Transparency ) * 255.0f, + 0.0f, 255.0f ); - if( (mesh.m_Positions != NULL) && - (mesh.m_Normals != NULL) && - (mesh.m_FaceIdx != NULL) && - (mesh.m_FaceIdxSize > 0) && (mesh.m_VertexSize > 0) ) - { - SFVEC4F *pColorRGBA = NULL; - - // Create the bbox for this mesh - // ///////////////////////////////////////////////////////// - m_meshs_bbox[mesh_i].Reset(); - - for( unsigned int vertex_i = 0; - vertex_i < mesh.m_VertexSize; - ++vertex_i ) - { - m_meshs_bbox[mesh_i].Union( mesh.m_Positions[vertex_i] ); - } - - // Make sure we start with client state disabled - // ///////////////////////////////////////////////////////// - glDisableClientState( GL_TEXTURE_COORD_ARRAY ); - glDisableClientState( GL_COLOR_ARRAY ); - - - // Enable arrays client states - // ///////////////////////////////////////////////////////// - glEnableClientState( GL_VERTEX_ARRAY ); - glEnableClientState( GL_NORMAL_ARRAY ); - - glVertexPointer( 3, GL_FLOAT, 0, mesh.m_Positions ); - glNormalPointer( GL_FLOAT, 0, mesh.m_Normals ); - - if( mesh.m_Color != NULL ) - { - glEnableClientState( GL_COLOR_ARRAY ); - - float transparency = 0.0f; - - if( mesh.m_MaterialIdx < a3DModel.m_MaterialsSize ) - transparency = a3DModel.m_Materials[mesh.m_MaterialIdx].m_Transparency; - - if( ( transparency > FLT_EPSILON ) - && ( aMaterialMode == MATERIAL_MODE::NORMAL ) ) - { - // Create a new array of RGBA colors - pColorRGBA = new SFVEC4F[mesh.m_VertexSize]; - - // Copy RGB array and add the Alpha value - for( unsigned int i = 0; i < mesh.m_VertexSize; ++i ) - pColorRGBA[i] = SFVEC4F( mesh.m_Color[i], - 1.0f - transparency ); - - // Load an RGBA array - glColorPointer( 4, GL_FLOAT, 0, pColorRGBA ); - } - else - { - switch( aMaterialMode ) - { - case MATERIAL_MODE::NORMAL: - case MATERIAL_MODE::DIFFUSE_ONLY: - // load the original RGB color array - glColorPointer( 3, GL_FLOAT, 0, mesh.m_Color ); - break; - case MATERIAL_MODE::CAD_MODE: - // Create a new array of RGBA colors - pColorRGBA = new SFVEC4F[mesh.m_VertexSize]; - - // Copy RGB array and add the Alpha value - for( unsigned int i = 0; i < mesh.m_VertexSize; ++i ) - { - pColorRGBA[i] = - SFVEC4F( MaterialDiffuseToColorCAD( mesh.m_Color[i] ), - 1.0f ); - } - - // Load an RGBA array - glColorPointer( 4, GL_FLOAT, 0, pColorRGBA ); - break; - default: - break; - } - } - } - - if( mesh.m_Texcoords != NULL ) - { - glEnableClientState( GL_TEXTURE_COORD_ARRAY ); - glTexCoordPointer( 2, GL_FLOAT, 0, mesh.m_Texcoords ); - } - - // Compile the display list to store triangles - // ///////////////////////////////////////////////////////// - glNewList( m_ogl_idx_list_meshes + mesh_i, GL_COMPILE ); - - // Set material properties - // ///////////////////////////////////////////////////////// - - if( mesh.m_Color != NULL ) - { - // This enables the use of the Color Pointer information - glEnable( GL_COLOR_MATERIAL ); - glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE ); - } - else - { - glDisable( GL_COLOR_MATERIAL ); - } - - if( mesh.m_MaterialIdx < a3DModel.m_MaterialsSize ) - { - switch( aMaterialMode ) - { - case MATERIAL_MODE::NORMAL: - OGL_SetMaterial( a3DModel.m_Materials[mesh.m_MaterialIdx] ); - break; - case MATERIAL_MODE::DIFFUSE_ONLY: - OGL_SetDiffuseOnlyMaterial( - a3DModel.m_Materials[mesh.m_MaterialIdx].m_Diffuse ); - break; - case MATERIAL_MODE::CAD_MODE: - OGL_SetDiffuseOnlyMaterial( - MaterialDiffuseToColorCAD( - a3DModel.m_Materials[mesh.m_MaterialIdx].m_Diffuse ) ); - break; - default: - break; - } - } - - // Draw mesh - // ///////////////////////////////////////////////////////// - glDrawElements( GL_TRIANGLES, mesh.m_FaceIdxSize, - GL_UNSIGNED_INT, mesh.m_FaceIdx ); - - glDisable( GL_COLOR_MATERIAL ); - - glEndList(); - - // Disable arrays client states - // ///////////////////////////////////////////////////////// - glDisableClientState( GL_TEXTURE_COORD_ARRAY ); - glDisableClientState( GL_COLOR_ARRAY ); - glDisableClientState( GL_NORMAL_ARRAY ); - glDisableClientState( GL_VERTEX_ARRAY ); - - glFlush(); - - delete [] pColorRGBA; - } + vtx_out.m_cad_color = + glm::clamp( glm::vec4( MaterialDiffuseToColorCAD( mesh.m_Color[vtx_i] ), + 1 ) * 255.0f, + 0.0f, 255.0f ); } - }// for each mesh - - - m_ogl_idx_list_opaque = glGenLists( 1 ); - - // Check if the generated list is valid - if( glIsList( m_ogl_idx_list_opaque ) ) - { - bool have_opaque_meshes = false; - bool have_transparent_meshes = false; - - // Compile the model display list - glNewList( m_ogl_idx_list_opaque, GL_COMPILE ); - - // Render each mesh display list (opaque first) - // ///////////////////////////////////////////////////////////////// - for( unsigned int mesh_i = 0; mesh_i < a3DModel.m_MeshesSize; ++mesh_i ) + else { - const SMESH &mesh = a3DModel.m_Meshes[mesh_i]; + // the mesh will be rendered with other meshes that might have + // vertex colors. thus, we can't enable/disable vertex colors + // for individual meshes during rendering. - if( mesh.m_MaterialIdx < a3DModel.m_MaterialsSize ) - { - const SMATERIAL &material = a3DModel.m_Materials[mesh.m_MaterialIdx]; + // if there are no vertex colors, use material color instead. + vtx_out.m_color = + glm::clamp( glm::vec4( material.m_Diffuse, + 1 - material.m_Transparency ) * 255.0f, + 0.0f, 255.0f ); - if( material.m_Transparency == 0.0f ) - { - have_opaque_meshes = true; // Flag that we have at least one opaque mesh - glCallList( m_ogl_idx_list_meshes + mesh_i ); - } - else - { - have_transparent_meshes = true; // Flag that we found a transparent mesh - } - } - } - - glEndList(); - - if( !have_opaque_meshes ) - { - // If we dont have opaque meshes, we can free the list - glDeleteLists( m_ogl_idx_list_opaque, 1 ); - m_ogl_idx_list_opaque = 0; - } - - if( have_transparent_meshes ) - { - m_ogl_idx_list_transparent = glGenLists( 1 ); - - // Check if the generated list is valid - if( glIsList( m_ogl_idx_list_transparent ) ) - { - // Compile the model display list - glNewList( m_ogl_idx_list_transparent, GL_COMPILE ); - - glEnable( GL_BLEND ); - glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); - - // Render each mesh display list - // ///////////////////////////////////////////////////////// - for( unsigned mesh_i = 0; mesh_i < a3DModel.m_MeshesSize; ++mesh_i ) - { - const SMESH &mesh = a3DModel.m_Meshes[mesh_i]; - - if( mesh.m_MaterialIdx < a3DModel.m_MaterialsSize ) - { - const SMATERIAL &material = a3DModel.m_Materials[mesh.m_MaterialIdx]; - - // Render the transparent mesh if it have a transparency value - if( material.m_Transparency != 0.0f ) - glCallList( m_ogl_idx_list_meshes + mesh_i ); - } - } - - glDisable( GL_BLEND ); - - glEndList(); - } - else - { - m_ogl_idx_list_transparent = 0; - } + vtx_out.m_cad_color = + glm::clamp( glm::vec4 ( MaterialDiffuseToColorCAD( material.m_Diffuse ), + 1 ) * 255.0f, + 0.0f, 255.0f ); } } - else + + if( m_meshes_bbox[mesh_i].IsInitialized() ) { - m_ogl_idx_list_opaque = 0; + // generate geometry for the bounding box + MakeBbox( m_meshes_bbox[mesh_i], ( mesh_i + 1 ) * bbox_vtx_count, + &bbox_tmp_vertices[( mesh_i + 1 ) * bbox_vtx_count], + &bbox_tmp_indices[( mesh_i + 1 ) * bbox_idx_count], + { avg_color, 1.0f } ); + + // bump the outer bounding box + m_model_bbox.Union( m_meshes_bbox[mesh_i] ); } - // Create the main bbox - // ///////////////////////////////////////////////////////////////////// - m_model_bbox.Reset(); - for( unsigned int mesh_i = 0; mesh_i < a3DModel.m_MeshesSize; ++mesh_i ) - m_model_bbox.Union( m_meshs_bbox[mesh_i] ); + // append indices of this mesh to the mesh group. + const unsigned int idx_offset = mesh_group.m_indices.size(); + unsigned int use_idx_count = mesh.m_FaceIdxSize; - glFlush(); + if( use_idx_count % 3 != 0 ) + { + wxLogTrace( m_logTrace, wxT( " index count %u not multiple of 3, truncating" ), + static_cast( use_idx_count ) ); + use_idx_count = ( use_idx_count / 3 ) * 3; + } + + mesh_group.m_indices.resize( mesh_group.m_indices.size() + use_idx_count ); + + for( unsigned int idx_i = 0; idx_i < use_idx_count; ++idx_i ) + { + if( mesh.m_FaceIdx[idx_i] >= mesh.m_VertexSize ) + { + wxLogTrace( m_logTrace, wxT( " index %u out of range (%u)" ), + static_cast( mesh.m_FaceIdx[idx_i] ), + static_cast( mesh.m_VertexSize ) ); + + // FIXME: should skip this triangle + } + + mesh_group.m_indices[idx_offset + idx_i] = mesh.m_FaceIdx[idx_i] + vtx_offset; + } } + + // generate geometry for the outer bounding box + if( m_model_bbox.IsInitialized() ) + MakeBbox( m_model_bbox, 0, &bbox_tmp_vertices[0], &bbox_tmp_indices[0], + { 0.0f, 1.0f, 0.0f, 1.0f } ); + + // create bounding box buffers + glGenBuffers( 1, &m_bbox_vertex_buffer ); + glBindBuffer( GL_ARRAY_BUFFER, m_bbox_vertex_buffer ); + glBufferData( GL_ARRAY_BUFFER, sizeof( VERTEX ) * bbox_tmp_vertices.size(), + bbox_tmp_vertices.data(), GL_STATIC_DRAW ); + + glGenBuffers( 1, &m_bbox_index_buffer ); + glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_bbox_index_buffer ); + + if( bbox_tmp_vertices.size() <= std::numeric_limits::max() ) + { + m_bbox_index_buffer_type = GL_UNSIGNED_SHORT; + + auto u16buf = std::make_unique( bbox_tmp_indices.size() ); + + for( unsigned int i = 0; i < bbox_tmp_indices.size(); ++i ) + u16buf[i] = static_cast( bbox_tmp_indices[i] ); + + glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof( GLushort ) * bbox_tmp_indices.size(), + u16buf.get(), GL_STATIC_DRAW ); + } + else + { + m_bbox_index_buffer_type = GL_UNSIGNED_INT; + glBufferData( GL_ELEMENT_ARRAY_BUFFER, sizeof( GLuint ) * bbox_tmp_indices.size(), + bbox_tmp_indices.data(), GL_STATIC_DRAW ); + } + + + // merge the mesh group geometry data. + unsigned int total_vertex_count = 0; + unsigned int total_index_count = 0; + + for( auto& mg : mesh_groups ) + { + total_vertex_count += mg.m_vertices.size(); + total_index_count += mg.m_indices.size(); + } + + wxLogTrace( m_logTrace, wxT( " total %u vertices, %u indices" ), + total_vertex_count, total_index_count ); + + glGenBuffers( 1, &m_vertex_buffer ); + glBindBuffer( GL_ARRAY_BUFFER, m_vertex_buffer ); + glBufferData( GL_ARRAY_BUFFER, sizeof( VERTEX ) * total_vertex_count, + nullptr, GL_STATIC_DRAW ); + + unsigned int idx_size = 0; + + if( total_vertex_count <= std::numeric_limits::max() ) + { + m_index_buffer_type = GL_UNSIGNED_SHORT; + idx_size = sizeof( GLushort ); + } + else + { + m_index_buffer_type = GL_UNSIGNED_INT; + idx_size = sizeof( GLuint ); + } + + // temporary index buffer which will contain either GLushort or GLuint + // type indices. allocate with a bit of meadow at the end. + auto tmp_idx = std::make_unique( + ( idx_size * total_index_count + 8 ) / sizeof( GLuint ) ); + + unsigned int prev_vtx_count = 0; + unsigned int idx_offset = 0; + unsigned int vtx_offset = 0; + + for( unsigned int mg_i = 0; mg_i < mesh_groups.size (); ++mg_i ) + { + auto& mg = mesh_groups[mg_i]; + auto& mat = m_materials[mg_i]; + + if( m_index_buffer_type == GL_UNSIGNED_SHORT ) + { + auto idx_out = reinterpret_cast( + reinterpret_cast( tmp_idx.get() ) + idx_offset ); + + for( auto idx : mg.m_indices ) + *idx_out++ = static_cast( idx + prev_vtx_count ); + } + else if( m_index_buffer_type == GL_UNSIGNED_INT ) + { + auto idx_out = reinterpret_cast( + reinterpret_cast( tmp_idx.get() ) + idx_offset ); + + for( auto idx : mg.m_indices ) + *idx_out++ = static_cast( idx + prev_vtx_count ); + } + + glBufferSubData( GL_ARRAY_BUFFER, + vtx_offset, + mg.m_vertices.size() * sizeof( VERTEX ), + mg.m_vertices.data() ); + + mat.m_render_idx_buffer_offset = idx_offset; + mat.m_render_idx_count = mg.m_indices.size(); + + prev_vtx_count += mg.m_vertices.size(); + idx_offset += mg.m_indices.size() * idx_size; + vtx_offset += mg.m_vertices.size() * sizeof( VERTEX ); + } + + glGenBuffers( 1, &m_index_buffer ); + glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_index_buffer ); + glBufferData( GL_ELEMENT_ARRAY_BUFFER, idx_size * total_index_count, + tmp_idx.get(), GL_STATIC_DRAW ); + + glBindBuffer( GL_ARRAY_BUFFER, 0 ); + glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 ); + + auto end_time = std::chrono::high_resolution_clock::now(); + + wxLogTrace( m_logTrace, wxT( " loaded in %u ms\n" ), + (unsigned int)std::chrono::duration_cast ( + end_time - start_time).count() ); } - -void C_OGL_3DMODEL::Draw_opaque() const +void C_OGL_3DMODEL::BeginDrawMulti() { - if( glIsList( m_ogl_idx_list_opaque ) ) - glCallList( m_ogl_idx_list_opaque ); + glEnableClientState( GL_VERTEX_ARRAY ); + glEnableClientState( GL_NORMAL_ARRAY ); + glEnableClientState( GL_COLOR_ARRAY ); + glEnableClientState( GL_TEXTURE_COORD_ARRAY ); + + glEnable( GL_COLOR_MATERIAL ); + glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE ); } - -void C_OGL_3DMODEL::Draw_transparent() const +void C_OGL_3DMODEL::EndDrawMulti() { - if( glIsList( m_ogl_idx_list_transparent ) ) - glCallList( m_ogl_idx_list_transparent ); + glDisable( GL_COLOR_MATERIAL ); + glDisableClientState( GL_VERTEX_ARRAY ); + glDisableClientState( GL_NORMAL_ARRAY ); + glDisableClientState( GL_COLOR_ARRAY ); + glDisableClientState( GL_TEXTURE_COORD_ARRAY ); + + glBindBuffer( GL_ARRAY_BUFFER, 0 ); + glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 ); } +void C_OGL_3DMODEL::Draw( bool aTransparent ) const +{ + glBindBuffer( GL_ARRAY_BUFFER, m_vertex_buffer ); + glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_index_buffer ); + + glVertexPointer( 3, GL_FLOAT, sizeof( VERTEX ), + reinterpret_cast( offsetof( VERTEX, m_pos ) ) ); + + glNormalPointer( GL_BYTE, sizeof( VERTEX ), + reinterpret_cast( offsetof( VERTEX, m_nrm ) ) ); + + glColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( VERTEX ), + reinterpret_cast( + m_material_mode == MATERIAL_MODE::CAD_MODE + ? offsetof( VERTEX, m_cad_color ) + : offsetof( VERTEX, m_color ) ) ); + + glTexCoordPointer( 2, GL_FLOAT, sizeof( VERTEX ), + reinterpret_cast( offsetof( VERTEX, m_tex_uv ) ) ); + + // BeginDrawMulti(); + + for( auto& mat : m_materials ) + { + if( mat.IsTransparent() != aTransparent ) + continue; + + switch( m_material_mode ) + { + case MATERIAL_MODE::NORMAL: + OGL_SetMaterial( mat ); + break; + + case MATERIAL_MODE::DIFFUSE_ONLY: + OGL_SetDiffuseOnlyMaterial( mat.m_Diffuse ); + break; + + case MATERIAL_MODE::CAD_MODE: + OGL_SetDiffuseOnlyMaterial( MaterialDiffuseToColorCAD( mat.m_Diffuse ) ); + break; + + default: + break; + } + + glDrawElements( GL_TRIANGLES, mat.m_render_idx_count, m_index_buffer_type, + reinterpret_cast( mat.m_render_idx_buffer_offset ) ); + } + + // EndDrawMulti(); +} + C_OGL_3DMODEL::~C_OGL_3DMODEL() { - if( glIsList( m_ogl_idx_list_opaque ) ) - glDeleteLists( m_ogl_idx_list_opaque, 1 ); - - if( glIsList( m_ogl_idx_list_transparent ) ) - glDeleteLists( m_ogl_idx_list_transparent, 1 ); - - if( glIsList( m_ogl_idx_list_meshes ) ) - glDeleteLists( m_ogl_idx_list_meshes, m_nr_meshes ); - - m_ogl_idx_list_meshes = 0; - m_ogl_idx_list_opaque = 0; - m_ogl_idx_list_transparent = 0; - - delete[] m_meshs_bbox; - m_meshs_bbox = NULL; + glDeleteBuffers( 1, &m_vertex_buffer ); + glDeleteBuffers( 1, &m_index_buffer ); + glDeleteBuffers( 1, &m_bbox_vertex_buffer ); + glDeleteBuffers( 1, &m_bbox_index_buffer ); } void C_OGL_3DMODEL::Draw_bbox() const { - OGL_draw_bbox( m_model_bbox ); + glBindBuffer( GL_ARRAY_BUFFER, m_bbox_vertex_buffer ); + glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_bbox_index_buffer ); + + glVertexPointer( 3, GL_FLOAT, sizeof( VERTEX ), + reinterpret_cast( offsetof( VERTEX, m_pos ) ) ); + + glColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( VERTEX ), + reinterpret_cast( offsetof( VERTEX, m_color ) ) ); + + glDrawElements( GL_LINES, bbox_idx_count, m_bbox_index_buffer_type, + reinterpret_cast( 0 ) ); } void C_OGL_3DMODEL::Draw_bboxes() const { - for( unsigned int mesh_i = 0; mesh_i < m_nr_meshes; ++mesh_i ) - OGL_draw_bbox( m_meshs_bbox[mesh_i] ); + glBindBuffer( GL_ARRAY_BUFFER, m_bbox_vertex_buffer ); + glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, m_bbox_index_buffer ); + + glVertexPointer( 3, GL_FLOAT, sizeof( VERTEX ), + reinterpret_cast( offsetof( VERTEX, m_pos ) ) ); + + glColorPointer( 4, GL_UNSIGNED_BYTE, sizeof( VERTEX ), + reinterpret_cast( offsetof( VERTEX, m_color ) ) ); + + unsigned int idx_size = m_bbox_index_buffer_type == GL_UNSIGNED_SHORT + ? sizeof( GLushort ) : sizeof( GLuint ); + + glDrawElements( GL_LINES, bbox_idx_count * m_meshes_bbox.size(), m_bbox_index_buffer_type, + reinterpret_cast( bbox_idx_count * idx_size ) ); } - -bool C_OGL_3DMODEL::Have_opaque() const -{ - return glIsList( m_ogl_idx_list_opaque ); -} - - -bool C_OGL_3DMODEL::Have_transparent() const -{ - return glIsList( m_ogl_idx_list_transparent ); -} diff --git a/3d-viewer/3d_rendering/3d_render_ogl_legacy/c_ogl_3dmodel.h b/3d-viewer/3d_rendering/3d_render_ogl_legacy/c_ogl_3dmodel.h index c5a2309d5d..d212081ba1 100644 --- a/3d-viewer/3d_rendering/3d_render_ogl_legacy/c_ogl_3dmodel.h +++ b/3d-viewer/3d_rendering/3d_render_ogl_legacy/c_ogl_3dmodel.h @@ -1,6 +1,7 @@ /* * This program source code file is part of KiCad, a free EDA CAD application. * + * Copyright (C) 2020 Oleg Endo * Copyright (C) 2015-2016 Mario Luzeiro * Copyright (C) 1992-2016 KiCad Developers, see AUTHORS.txt for contributors. * @@ -30,6 +31,7 @@ #ifndef _C_OGL_3DMODEL_H_ #define _C_OGL_3DMODEL_H_ +#include #include #include "../../common_ogl/openGL_includes.h" #include "../3d_render_raytracing/shapes3D/cbbox.h" @@ -51,22 +53,22 @@ public: /** * @brief Draw_opaque - render the model into the current context */ - void Draw_opaque() const; + void Draw_opaque() const { Draw( false ); } /** * @brief Draw_transparent - render the model into the current context */ - void Draw_transparent() const; + void Draw_transparent() const { Draw( true ); } /** * @brief Have_opaque - return true if have opaque meshs to render */ - bool Have_opaque() const; + bool Have_opaque() const { return m_have_opaque_meshes; } /** * @brief Have_transparent - return true if have transparent meshs to render */ - bool Have_transparent() const; + bool Have_transparent() const { return m_have_transparent_meshes; } /** * @brief Draw_bbox - draw main bounding box of the model @@ -84,14 +86,76 @@ public: */ const CBBOX &GetBBox() const { return m_model_bbox; } + /** + * @brief BeginDrawMulti - set some basic render states before drawing multiple models + */ + static void BeginDrawMulti(); + + /** + * @brief EndDrawMulti - cleanup render states after drawing multiple models + */ + static void EndDrawMulti(); + private: - GLuint m_ogl_idx_list_opaque; ///< display list for rendering opaque meshes - GLuint m_ogl_idx_list_transparent; ///< display list for rendering transparent meshes - GLuint m_ogl_idx_list_meshes; ///< display lists for all meshes. - unsigned int m_nr_meshes; ///< number of meshes of this model + static const wxChar *m_logTrace; + + // the material mode that was used to generate the rendering data. + // FIXME: this can be selected at run-time and does not require re-creation + // of the whole model objects. + MATERIAL_MODE m_material_mode; CBBOX m_model_bbox; ///< global bounding box for this model - CBBOX *m_meshs_bbox; ///< individual bbox for each mesh + std::vector m_meshes_bbox; ///< individual bbox for each mesh + + // unified vertex format for mesh rendering. + struct VERTEX + { + glm::vec3 m_pos; + glm::u8vec4 m_nrm; // only 3 components used + glm::u8vec4 m_color; // regular color + glm::u8vec4 m_cad_color; // "CAD" mode rendering color + glm::vec2 m_tex_uv; + }; + + // vertex buffer and index buffers that include all the individual meshes + // lumped together. + GLuint m_vertex_buffer = 0; + GLuint m_index_buffer = 0; + GLenum m_index_buffer_type = GL_INVALID_ENUM; + + // internal material definition + // all meshes are grouped by material for rendering purposes. + struct MATERIAL : SMATERIAL + { + unsigned int m_render_idx_buffer_offset = 0; + unsigned int m_render_idx_count = 0; + + MATERIAL( const SMATERIAL &aOther ) : SMATERIAL( aOther ) { } + bool IsTransparent() const { return m_Transparency > FLT_EPSILON; } + }; + + std::vector m_materials; + + // a model can consist of transparent and opaque parts. remember which + // ones are present during initial buffer and data setup. use it later + // during rendering. + bool m_have_opaque_meshes = false; + bool m_have_transparent_meshes = false; + + // vertex buffer and index buffer for the bounding boxes. + // the first box is always the outer box, followed by inner boxes (one for each mesh). + static constexpr unsigned int bbox_vtx_count = 8; + static constexpr unsigned int bbox_idx_count = 24; + + GLuint m_bbox_vertex_buffer = 0; + GLuint m_bbox_index_buffer = 0; + GLenum m_bbox_index_buffer_type = GL_INVALID_ENUM; + + static void MakeBbox( const CBBOX &aBox, unsigned int aIdxOffset, + VERTEX *aVtxOut, GLuint *aIdxOut, + const glm::vec4 &aColor ); + + void Draw( bool aTransparent ) const; }; #endif // _C_OGL_3DMODEL_H_