#include "externals.hh" #include "ui-profiling.hh" #include #include constexpr uint32_t T_Profiler::Samples; constexpr uint32_t T_Profiler::History; constexpr uint32_t T_Profiler::Invalid; T_Profiler::~T_Profiler( ) { const auto iSize{ gpuQueries_.size( ) }; if ( iSize ) { glDeleteQueries( iSize , &gpuQueries_[ 0 ] ); } } void T_Profiler::clear( ) { sections_.clear( ); samples_.clear( ); cpuStarts_.clear( ); } void T_Profiler::startFrame( ) { previous_ = Invalid; current_ = Invalid; } void T_Profiler::endFrame( ) { const auto n{ sections_.size( ) }; if ( n ) { int32_t done{ 0 }; while ( !done ) { glGetQueryObjectiv( gpuQueries_[ n * 2 - 1 ] , GL_QUERY_RESULT_AVAILABLE , &done ); } if ( gpuSamples_.size( ) < n ) { gpuSamples_.resize( n ); } for ( auto i = 0u ; i < n ; i ++ ) { uint64_t a , b; glGetQueryObjectui64v( gpuQueries_[ i * 2 ] , GL_QUERY_RESULT , &a ); glGetQueryObjectui64v( gpuQueries_[ i * 2 + 1 ] , GL_QUERY_RESULT , &b ); addSample( gpuSamples_[ i ] , b - a ); } } while ( secDurations_.size( ) < n ) { secCPUDurations_.add( 0 ); secGPUDurations_.add( 0 ); secDurations_.add( 0 ); secStarts_.add( 0 ); } for ( auto i = 0u ; i < n ; i ++ ) { const float cpuD = computeDuration( samples_[ i ] ); const float gpuD = computeDuration( gpuSamples_[ i ] ); secCPUDurations_[ i ] = cpuD; secGPUDurations_[ i ] = gpuD; secDurations_[ i ] = std::max( cpuD , gpuD ); if ( parents_[ i ] != Invalid ) { assert( parents_[ i ] < i ); secStarts_[ i ] = secStarts_[ parents_[ i ] ]; } else if ( chain_[ i ] != Invalid ) { assert( chain_[ i ] < i ); secStarts_[ i ] = secStarts_[ chain_[ i ] ] + secDurations_[ chain_[ i ] ]; } else { secStarts_[ i ] = 0; } } } void T_Profiler::start( T_String const& section ) { const auto n( sections_.size( ) ); const auto pos( find( section ) ); if ( pos == n ) { sections_.add( section ); samples_.add( T_SamplesList_{ } ); cpuStarts_.add( 0u ); chain_.add( Invalid ); parents_.add( Invalid ); } assert( previous_ == Invalid || previous_ < pos ); chain_[ pos ] = previous_; if ( current_ != Invalid ) { parents_[ pos ] = current_; previous_ = Invalid; } else { parents_[ pos ] = Invalid; } current_ = pos; struct timespec ts; clock_gettime( CLOCK_MONOTONIC , &ts ); cpuStarts_[ pos ] = ts.tv_sec * 1000000000 + ts.tv_nsec; if ( gpuQueries_.size( ) <= pos * 2 + 1 ) { extendGPUQueries( ); } glQueryCounter( gpuQueries_[ pos * 2 ] , GL_TIMESTAMP ); } void T_Profiler::end( T_String const& section ) { const auto pos( find( section ) ); const auto n( sections_.size( ) ); if ( pos == n ) { return; } struct timespec ts; glQueryCounter( gpuQueries_[ pos * 2 + 1 ] , GL_TIMESTAMP ); clock_gettime( CLOCK_MONOTONIC , &ts ); const uint64_t ended = ts.tv_sec * 1000000000 + ts.tv_nsec; addSample( samples_[ pos ] , ended - cpuStarts_[ pos ] ); previous_ = pos; current_ = Invalid; } void T_Profiler::makeUI( ) { if ( !uiEnabled_ ) { return; } auto const n( sections_.size( ) ); if ( n != secDurations_.size( ) ) { return; } auto const& dspSize( ImGui::GetIO( ).DisplaySize ); ImGui::SetNextWindowSize( ImVec2( dspSize.x , dspSize.y * .34f ) , ImGuiSetCond_Appearing ); ImGui::SetNextWindowPos( ImVec2( 0 , dspSize.y * .66f ) , ImGuiSetCond_Appearing ); ImGui::Begin( "Profiler" , &uiEnabled_ , ImGuiWindowFlags_NoCollapse ); ImGui::BeginChild( "left" , ImVec2( 240 , 0 ) , true ); float angle( 0 ); displayed_.resize( n , true ); for ( auto i = 0u ; i < n ; i ++ ) { angle = fmod( angle + 137 , 360 ); ImVec4 color( 0 , 0 , 0 , 1 ); ImGui::ColorConvertHSVtoRGB( angle / 360. , .5 , 1 , color.x , color.y , color.z ); ImGui::PushStyleColor( ImGuiCol_Text , color ); ebcl::T_StringBuilder sb; sb << sections_[ i ] << '\0'; ImGui::Checkbox( sb.data( ) , &displayed_[ i ] ); ImGui::PopStyleColor( ); if ( ImGui::IsItemHovered( ) ) { ImGui::BeginTooltip( ); char tms[ 12 ]; snprintf( tms , 12 , "%.3f" , secDurations_[ i ] ); sb.clear( ) << sections_[ i ] << '\0'; ImGui::PushStyleColor( ImGuiCol_Text , color ); ImGui::Text( "%s" , sb.data( ) ); ImGui::PopStyleColor( ); sb.clear( ) << "\nTime: " << tms << "ms\n\nCPU: "; snprintf( tms , 12 , "%.3f" , secCPUDurations_[ i ] ); sb << tms << "ms\nGPU: "; snprintf( tms , 12 , "%.3f" , secGPUDurations_[ i ] ); sb << tms << "ms" << '\0'; ImGui::Text( "%s" , sb.data( ) ); ImGui::EndTooltip( ); } ImGui::PushStyleColor( ImGuiCol_Text , color ); ImGui::SameLine( 180 ); ImGui::Text( "%.3fms" , secDurations_[ i ] ); ImGui::PopStyleColor( ); } ImGui::EndChild( ); ImGui::SameLine( ); auto* const dl( ImGui::GetWindowDrawList( ) ); const ImVec2 wp( ImGui::GetWindowPos( ) ); const ImVec2 pos( ImGui::GetCursorPos( ) ); const ImVec2 ws( ImGui::GetWindowContentRegionMax( ) ); const ImVec2 start( wp.x + pos.x , wp.y + pos.y ); auto& style( ImGui::GetStyle( ) ); const ImVec2 end( wp.x + ws.x + 1 - style.FramePadding.x , wp.y + ws.y + 1 - style.FramePadding.y ); dl->AddRectFilled( start , end , ImGui::GetColorU32( ImVec4( .5 , .5 , .5 , .8 ) ) ); // Ms marks static constexpr float msWidth = 20; float lp( 0 ); while ( lp < ws.x ) { dl->AddLine( ImVec2( start.x + lp , start.y ) , ImVec2( start.x + lp , end.y ) , ImGui::GetColorU32( ImVec4( 0 , 0 , 0 , .8 ) ) ); lp += msWidth; } // 60/30FPS line dl->AddLine( ImVec2( start.x + msWidth * 1000. / 60. , start.y ) , ImVec2( start.x + msWidth * 1000. / 60. , end.y ) , ImGui::GetColorU32( ImVec4( 0 , 1 , 0 , 1 ) ) ); dl->AddLine( ImVec2( start.x + msWidth * 1000. / 30. , start.y ) , ImVec2( start.x + msWidth * 1000. / 30. , end.y ) , ImGui::GetColorU32( ImVec4( 1 , 0 , 0 , 1 ) ) ); // Plot static constexpr float plHeight = 5; uint32_t dLine[ n ]; uint32_t cLine = 0; angle = 0; for ( auto i = 0u ; i < n ; i ++ ) { angle = fmod( angle + 137 , 360 ); if ( i != 0 ) { if ( chain_[ i ] == Invalid ) { cLine ++; } else { cLine = dLine[ chain_[ i ] ]; } } dLine[ i ] = cLine; if ( !displayed_[ i ] ) { continue; } const float lx0 = msWidth * secStarts_[ i ]; const float lx1 = msWidth * ( secStarts_[ i ] + secDurations_[ i ] ); const float lh = cLine * ( plHeight + 1 ); const float ly1 = lh + plHeight; ImVec4 color( 0 , 0 , 0 , 1 ); ImGui::ColorConvertHSVtoRGB( angle / 360. , .5 , 1 , color.x , color.y , color.z ); dl->AddRectFilled( ImVec2( start.x + lx0 , start.y + lh ) , ImVec2( start.x + lx1 , start.y + ly1 ) , ImGui::GetColorU32( color ) ); } ImGui::End( ); } void T_Profiler::extendGPUQueries( ) noexcept { const auto iSize{ gpuQueries_.size( ) }; const auto iGrowth{ gpuQueries_.growth( ) }; gpuQueries_.resize( iSize + iGrowth , 0 ); glGenQueries( iGrowth , &gpuQueries_[ iSize ] ); } void T_Profiler::addSample( T_SamplesList_& list , const uint64_t duration ) noexcept { if ( list.size( ) == 0 || ( list.size( ) < History && list[ 0 ].nSamples == Samples ) ) { list.insert( 0 , T_ProfilerSamples{ 0 , 0 } ); } else if ( list.size( ) == History && list[ 0 ].nSamples == Samples ) { for ( auto i = 1u ; i < History ; i ++ ) { list[ i ] = list[ i - 1 ]; } list[ 0 ].sum = 0; list[ 0 ].nSamples = 0; } list[ 0 ].sum += float( duration ) * 1e-6; list[ 0 ].nSamples ++; } float T_Profiler::computeDuration( T_SamplesList_ const& section ) noexcept { float total = 0; float nSamples = 0; const auto ns{ section.size( ) }; for ( auto i = 0u ; i < ns ; i ++ ) { total += section[ i ].sum; nSamples += section[ i ].nSamples; } return total / nSamples; } uint32_t T_Profiler::find( T_String const& section ) const { const auto n( sections_.size( ) ); auto pos( 0u ); while ( pos < n && sections_[ pos ] != section ) { pos ++; } return pos; }