// // Copyright 2019 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // OverlayWidgets.cpp: // Implements functions that interpret widget data. Data formats and limits correspond to the // Vulkan implementation (as the only implementation). They are generic enough so other backends // could respect them too, if they implement the overlay. // #include "libANGLE/Overlay.h" #include "libANGLE/Overlay_font_autogen.h" #include namespace gl { namespace { // Internally, every widget is either Text or Graph. enum class WidgetInternalType { Text, Graph, InvalidEnum, EnumCount = InvalidEnum, }; // A map that says how the API-facing widget types map to internal types. constexpr angle::PackedEnumMap kWidgetTypeToInternalMap = { {WidgetType::Count, WidgetInternalType::Text}, {WidgetType::Text, WidgetInternalType::Text}, {WidgetType::PerSecond, WidgetInternalType::Text}, {WidgetType::RunningGraph, WidgetInternalType::Graph}, {WidgetType::RunningHistogram, WidgetInternalType::Graph}, }; // Structures and limits matching uniform buffers in vulkan/shaders/src/OverlayDraw.comp. The size // of text and graph widgets is chosen such that they could fit in uniform buffers with minimum // required Vulkan size. constexpr size_t kMaxRenderableTextWidgets = 32; constexpr size_t kMaxRenderableGraphWidgets = 32; constexpr size_t kMaxTextLength = 256; constexpr size_t kMaxGraphDataSize = 256; constexpr angle::PackedEnumMap kWidgetInternalTypeMaxWidgets = { {WidgetInternalType::Text, kMaxRenderableTextWidgets}, {WidgetInternalType::Graph, kMaxRenderableGraphWidgets}, }; ANGLE_ENABLE_STRUCT_PADDING_WARNINGS // Structure matching buffer in vulkan/shaders/src/OverlayCull.comp. struct WidgetCoordinates { uint32_t coordinates[kMaxRenderableTextWidgets + kMaxRenderableGraphWidgets][4]; }; // Structures matching buffers in vulkan/shaders/src/OverlayDraw.comp. struct TextWidgetData { uint32_t coordinates[4]; float color[4]; uint32_t fontSize[3]; uint32_t padding; uint8_t text[kMaxTextLength]; }; struct GraphWidgetData { uint32_t coordinates[4]; float color[4]; uint32_t valueWidth; uint32_t padding[3]; uint32_t values[kMaxGraphDataSize]; }; struct TextWidgets { TextWidgetData widgets[kMaxRenderableTextWidgets]; }; struct GraphWidgets { GraphWidgetData widgets[kMaxRenderableGraphWidgets]; }; ANGLE_DISABLE_STRUCT_PADDING_WARNINGS uint32_t GetWidgetCoord(int32_t src, uint32_t extent) { int32_t dst = src < 0 ? extent + src : src; return std::min(std::max(dst, 0), extent - 1); } void GetWidgetCoordinates(const int32_t srcCoords[4], const gl::Extents &imageExtent, uint32_t dstCoordsOut[4]) { dstCoordsOut[0] = GetWidgetCoord(srcCoords[0], imageExtent.width); dstCoordsOut[1] = GetWidgetCoord(srcCoords[1], imageExtent.height); dstCoordsOut[2] = GetWidgetCoord(srcCoords[2], imageExtent.width); dstCoordsOut[3] = GetWidgetCoord(srcCoords[3], imageExtent.height); } void GetWidgetColor(const float srcColor[4], float dstColor[4]) { memcpy(dstColor, srcColor, 4 * sizeof(dstColor[0])); } void GetTextFontSize(int srcFontSize, uint32_t dstFontSize[3]) { // .xy contains the font glyph width/height dstFontSize[0] = overlay::kFontGlyphWidth >> srcFontSize; dstFontSize[1] = overlay::kFontGlyphHeight >> srcFontSize; // .z contains the mip dstFontSize[2] = srcFontSize; } void GetGraphValueWidth(const int32_t srcCoords[4], size_t valueCount, uint32_t *dstValueWidth) { const int32_t graphWidth = std::abs(srcCoords[2] - srcCoords[0]); // If valueCount doesn't divide graphWidth, the graph bars won't fit well in its frame. // Fix initOverlayWidgets() in that case. ASSERT(graphWidth % valueCount == 0); *dstValueWidth = graphWidth / valueCount; } void GetTextString(const std::string &src, uint8_t textOut[kMaxTextLength]) { for (size_t i = 0; i < src.length() && i < kMaxTextLength; ++i) { // The font image has 95 ASCII characters starting from ' '. textOut[i] = src[i] - ' '; } } void GetGraphValues(const std::vector srcValues, size_t startIndex, float scale, uint32_t valuesOut[kMaxGraphDataSize]) { ASSERT(srcValues.size() <= kMaxGraphDataSize); for (size_t i = 0; i < srcValues.size(); ++i) { size_t index = (startIndex + i) % srcValues.size(); valuesOut[i] = static_cast(srcValues[index] * scale); } } std::vector CreateHistogram(const std::vector values) { std::vector histogram(values.size(), 0); for (uint64_t rank : values) { ++histogram[static_cast(rank)]; } return histogram; } using OverlayWidgetCounts = angle::PackedEnumMap; using AppendWidgetDataFunc = void (*)(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts); } // namespace namespace overlay_impl { #define ANGLE_DECLARE_APPEND_WIDGET_PROC(WIDGET_ID) \ static void Append##WIDGET_ID(const overlay::Widget *widget, const gl::Extents &imageExtent, \ TextWidgetData *textWidget, GraphWidgetData *graphWidget, \ OverlayWidgetCounts *widgetCounts); // This class interprets the generic data collected in every element into a human-understandable // widget. This often means generating text specific to this item and scaling graph data to // something sensible. class AppendWidgetDataHelper { public: ANGLE_WIDGET_ID_X(ANGLE_DECLARE_APPEND_WIDGET_PROC) private: static std::ostream &OutputPerSecond(std::ostream &out, const overlay::PerSecond *perSecond); static std::ostream &OutputText(std::ostream &out, const overlay::Text *text); static std::ostream &OutputCount(std::ostream &out, const overlay::Count *count); static void AppendTextCommon(const overlay::Widget *widget, const gl::Extents &imageExtent, const std::string &text, TextWidgetData *textWidget, OverlayWidgetCounts *widgetCounts); using FormatGraphTitleFunc = std::function; static void AppendRunningGraphCommon(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts, FormatGraphTitleFunc formatFunc); using FormatHistogramTitleFunc = std::function; static void AppendRunningHistogramCommon(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts, FormatHistogramTitleFunc formatFunc); static void AppendGraphCommon(const overlay::Widget *widget, const gl::Extents &imageExtent, const std::vector runningValues, size_t startIndex, float scale, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts); }; void AppendWidgetDataHelper::AppendTextCommon(const overlay::Widget *widget, const gl::Extents &imageExtent, const std::string &text, TextWidgetData *textWidget, OverlayWidgetCounts *widgetCounts) { GetWidgetCoordinates(widget->coords, imageExtent, textWidget->coordinates); GetWidgetColor(widget->color, textWidget->color); GetTextFontSize(widget->fontSize, textWidget->fontSize); GetTextString(text, textWidget->text); ++(*widgetCounts)[WidgetInternalType::Text]; } void AppendWidgetDataHelper::AppendGraphCommon(const overlay::Widget *widget, const gl::Extents &imageExtent, const std::vector runningValues, size_t startIndex, float scale, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { const overlay::RunningGraph *widgetAsGraph = static_cast(widget); GetWidgetCoordinates(widget->coords, imageExtent, graphWidget->coordinates); GetWidgetColor(widget->color, graphWidget->color); GetGraphValueWidth(widget->coords, widgetAsGraph->runningValues.size(), &graphWidget->valueWidth); GetGraphValues(runningValues, startIndex, scale, graphWidget->values); ++(*widgetCounts)[WidgetInternalType::Graph]; } void AppendWidgetDataHelper::AppendRunningGraphCommon( const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts, AppendWidgetDataHelper::FormatGraphTitleFunc formatFunc) { const overlay::RunningGraph *graph = static_cast(widget); const overlay::Widget *matchToWidget = widget->matchToWidget; if (matchToWidget == nullptr) { matchToWidget = widget; } const overlay::RunningGraph *matchToGraph = static_cast(matchToWidget); const uint64_t maxValue = *std::max_element(graph->runningValues.begin(), graph->runningValues.end()); const uint64_t maxValueInMatchToGraph = *std::max_element(matchToGraph->runningValues.begin(), matchToGraph->runningValues.end()); const int32_t graphHeight = std::abs(widget->coords[3] - widget->coords[1]); const float graphScale = static_cast(graphHeight) / maxValueInMatchToGraph; const size_t graphSize = graph->runningValues.size(); const size_t currentIdx = graph->lastValueIndex - 1; const uint64_t curValue = graph->runningValues[(graphSize + currentIdx) % graphSize]; AppendGraphCommon(widget, imageExtent, graph->runningValues, graph->lastValueIndex + 1, graphScale, graphWidget, widgetCounts); if ((*widgetCounts)[WidgetInternalType::Text] < kWidgetInternalTypeMaxWidgets[WidgetInternalType::Text]) { std::string text = formatFunc(curValue, maxValue); AppendTextCommon(&graph->description, imageExtent, text, textWidget, widgetCounts); } } // static void AppendWidgetDataHelper::AppendRunningHistogramCommon(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts, FormatHistogramTitleFunc formatFunc) { const overlay::RunningHistogram *runningHistogram = static_cast(widget); std::vector histogram = CreateHistogram(runningHistogram->runningValues); auto peakRangeIt = std::max_element(histogram.rbegin(), histogram.rend()); const uint64_t peakRangeValue = *peakRangeIt; const int32_t graphHeight = std::abs(widget->coords[3] - widget->coords[1]); const float graphScale = static_cast(graphHeight) / peakRangeValue; auto maxValueIter = std::find_if(histogram.rbegin(), histogram.rend(), [](uint64_t value) { return value != 0; }); AppendGraphCommon(widget, imageExtent, histogram, 0, graphScale, graphWidget, widgetCounts); if ((*widgetCounts)[WidgetInternalType::Text] < kWidgetInternalTypeMaxWidgets[WidgetInternalType::Text]) { size_t peakRange = std::distance(peakRangeIt, histogram.rend() - 1); size_t maxValueRange = std::distance(maxValueIter, histogram.rend() - 1); std::string text = formatFunc(peakRange, maxValueRange, histogram.size()); AppendTextCommon(&runningHistogram->description, imageExtent, text, textWidget, widgetCounts); } } void AppendWidgetDataHelper::AppendFPS(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { const overlay::PerSecond *fps = static_cast(widget); std::ostringstream text; text << "FPS: "; OutputPerSecond(text, fps); AppendTextCommon(widget, imageExtent, text.str(), textWidget, widgetCounts); } void AppendWidgetDataHelper::AppendVulkanLastValidationMessage(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { const overlay::Text *lastValidationMessage = static_cast(widget); std::ostringstream text; text << "Last VVL Message: "; OutputText(text, lastValidationMessage); AppendTextCommon(widget, imageExtent, text.str(), textWidget, widgetCounts); } void AppendWidgetDataHelper::AppendVulkanValidationMessageCount(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { const overlay::Count *validationMessageCount = static_cast(widget); std::ostringstream text; text << "VVL Message Count: "; OutputCount(text, validationMessageCount); AppendTextCommon(widget, imageExtent, text.str(), textWidget, widgetCounts); } void AppendWidgetDataHelper::AppendVulkanRenderPassCount(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { auto format = [](uint64_t curValue, uint64_t maxValue) { std::ostringstream text; text << "RenderPass Count: " << maxValue; return text.str(); }; AppendRunningGraphCommon(widget, imageExtent, textWidget, graphWidget, widgetCounts, format); } void AppendWidgetDataHelper::AppendVulkanSecondaryCommandBufferPoolWaste( const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { auto format = [](size_t peakRange, size_t maxValueRange, size_t numRanges) { std::ostringstream text; size_t peakPercent = (peakRange * 100 + 50) / numRanges; text << "CB Pool Waste (Peak: " << peakPercent << "%)"; return text.str(); }; AppendRunningHistogramCommon(widget, imageExtent, textWidget, graphWidget, widgetCounts, format); } void AppendWidgetDataHelper::AppendVulkanRenderPassBufferCount(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { auto format = [](uint64_t curValue, uint64_t maxValue) { std::ostringstream text; text << "RP VkBuffers (Max: " << maxValue << ")"; return text.str(); }; AppendRunningGraphCommon(widget, imageExtent, textWidget, graphWidget, widgetCounts, format); } void AppendWidgetDataHelper::AppendVulkanWriteDescriptorSetCount(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { auto format = [](uint64_t curValue, uint64_t maxValue) { std::ostringstream text; text << "WriteDescriptorSet Count: " << maxValue; return text.str(); }; AppendRunningGraphCommon(widget, imageExtent, textWidget, graphWidget, widgetCounts, format); } void AppendWidgetDataHelper::AppendVulkanDescriptorSetAllocations(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { auto format = [](uint64_t curValue, uint64_t maxValue) { std::ostringstream text; text << "Descriptor Set Allocations: " << maxValue; return text.str(); }; AppendRunningGraphCommon(widget, imageExtent, textWidget, graphWidget, widgetCounts, format); } void AppendWidgetDataHelper::AppendVulkanShaderResourceDSHitRate(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { auto format = [](uint64_t curValue, uint64_t maxValue) { std::ostringstream text; text << "Shader Resource DS Hit Rate (Max: " << maxValue << "%)"; return text.str(); }; AppendRunningGraphCommon(widget, imageExtent, textWidget, graphWidget, widgetCounts, format); } void AppendWidgetDataHelper::AppendVulkanDynamicBufferAllocations(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { auto format = [](uint64_t curValue, uint64_t maxValue) { std::ostringstream text; text << "DynamicBuffer Allocations (Max: " << maxValue << ")"; return text.str(); }; AppendRunningGraphCommon(widget, imageExtent, textWidget, graphWidget, widgetCounts, format); } void AppendWidgetDataHelper::AppendVulkanTextureDescriptorCacheSize( const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { auto format = [](uint64_t curValue, uint64_t maxValue) { std::ostringstream text; text << "Total Texture Descriptor Cache Size: " << curValue; return text.str(); }; AppendRunningGraphCommon(widget, imageExtent, textWidget, graphWidget, widgetCounts, format); } void AppendWidgetDataHelper::AppendVulkanUniformDescriptorCacheSize( const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { auto format = [](uint64_t curValue, uint64_t maxValue) { std::ostringstream text; text << "Total Uniform Descriptor Cache Size: " << curValue; return text.str(); }; AppendRunningGraphCommon(widget, imageExtent, textWidget, graphWidget, widgetCounts, format); } void AppendWidgetDataHelper::AppendVulkanDescriptorCacheSize(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { auto format = [](uint64_t curValue, uint64_t maxValue) { std::ostringstream text; text << "Total Descriptor Cache Size: " << curValue; return text.str(); }; AppendRunningGraphCommon(widget, imageExtent, textWidget, graphWidget, widgetCounts, format); } void AppendWidgetDataHelper::AppendVulkanDescriptorCacheKeySize(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { const overlay::Count *countWidget = static_cast(widget); std::ostringstream text; double kb = static_cast(countWidget->count) / 1000.0; text << "DS Cache Key Size: " << std::fixed << std::setprecision(1) << kb << " kb"; AppendTextCommon(widget, imageExtent, text.str(), textWidget, widgetCounts); } void AppendWidgetDataHelper::AppendVulkanAttemptedSubmissions(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { auto format = [](uint64_t curValue, uint64_t maxValue) { std::ostringstream text; text << "Attempted submissions (peak): " << maxValue; return text.str(); }; AppendRunningGraphCommon(widget, imageExtent, textWidget, graphWidget, widgetCounts, format); } void AppendWidgetDataHelper::AppendVulkanActualSubmissions(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { auto format = [](uint64_t curValue, uint64_t maxValue) { std::ostringstream text; text << "Actual submissions (peak): " << maxValue; return text.str(); }; AppendRunningGraphCommon(widget, imageExtent, textWidget, graphWidget, widgetCounts, format); } void AppendWidgetDataHelper::AppendVulkanPipelineCacheLookups(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { auto format = [](uint64_t curValue, uint64_t maxValue) { std::ostringstream text; text << "Pipeline Cache Lookups (peak): " << maxValue; return text.str(); }; AppendRunningGraphCommon(widget, imageExtent, textWidget, graphWidget, widgetCounts, format); } void AppendWidgetDataHelper::AppendVulkanPipelineCacheMisses(const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { auto format = [](uint64_t curValue, uint64_t maxValue) { std::ostringstream text; text << "Pipeline Cache Misses (peak): " << maxValue; return text.str(); }; AppendRunningGraphCommon(widget, imageExtent, textWidget, graphWidget, widgetCounts, format); } void AppendWidgetDataHelper::AppendVulkanTotalPipelineCacheHitTimeMs( const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { const overlay::Count *totalTime = static_cast(widget); std::ostringstream text; text << "Total Pipeline Cache Hit Time: "; OutputCount(text, totalTime); text << "ms"; AppendTextCommon(widget, imageExtent, text.str(), textWidget, widgetCounts); } void AppendWidgetDataHelper::AppendVulkanTotalPipelineCacheMissTimeMs( const overlay::Widget *widget, const gl::Extents &imageExtent, TextWidgetData *textWidget, GraphWidgetData *graphWidget, OverlayWidgetCounts *widgetCounts) { const overlay::Count *totalTime = static_cast(widget); std::ostringstream text; text << "Total Pipeline Cache Miss Time: "; OutputCount(text, totalTime); text << "ms"; AppendTextCommon(widget, imageExtent, text.str(), textWidget, widgetCounts); } std::ostream &AppendWidgetDataHelper::OutputPerSecond(std::ostream &out, const overlay::PerSecond *perSecond) { return out << perSecond->lastPerSecondCount; } std::ostream &AppendWidgetDataHelper::OutputText(std::ostream &out, const overlay::Text *text) { return out << text->text; } std::ostream &AppendWidgetDataHelper::OutputCount(std::ostream &out, const overlay::Count *count) { return out << count->count; } } // namespace overlay_impl namespace { #define ANGLE_APPEND_WIDGET_MAP_PROC(WIDGET_ID) \ {WidgetId::WIDGET_ID, overlay_impl::AppendWidgetDataHelper::Append##WIDGET_ID}, constexpr angle::PackedEnumMap kWidgetIdToAppendDataFuncMap = { ANGLE_WIDGET_ID_X(ANGLE_APPEND_WIDGET_MAP_PROC)}; } // namespace namespace overlay { const Text *Widget::getDescriptionWidget() const { return nullptr; } RunningGraph::RunningGraph(size_t n) : runningValues(n, 0) {} RunningGraph::~RunningGraph() = default; const Text *RunningGraph::getDescriptionWidget() const { return &description; } } // namespace overlay size_t OverlayState::getWidgetCoordinatesBufferSize() const { return sizeof(WidgetCoordinates); } size_t OverlayState::getTextWidgetsBufferSize() const { return sizeof(TextWidgets); } size_t OverlayState::getGraphWidgetsBufferSize() const { return sizeof(GraphWidgets); } void OverlayState::fillWidgetData(const gl::Extents &imageExtents, uint8_t *textData, uint8_t *graphData, uint32_t *activeTextWidgetCountOut, uint32_t *activeGraphWidgetCountOut) const { TextWidgets *textWidgets = reinterpret_cast(textData); GraphWidgets *graphWidgets = reinterpret_cast(graphData); memset(textWidgets, overlay::kFontCharacters, sizeof(*textWidgets)); memset(graphWidgets, 0, sizeof(*graphWidgets)); OverlayWidgetCounts widgetCounts = {}; for (WidgetId id : angle::AllEnums()) { const std::unique_ptr &widget = mOverlayWidgets[id]; if (!widget->enabled) { continue; } WidgetInternalType internalType = kWidgetTypeToInternalMap[widget->type]; ASSERT(internalType != WidgetInternalType::InvalidEnum); if (widgetCounts[internalType] >= kWidgetInternalTypeMaxWidgets[internalType]) { continue; } AppendWidgetDataFunc appendFunc = kWidgetIdToAppendDataFuncMap[id]; ASSERT(appendFunc); appendFunc(widget.get(), imageExtents, &textWidgets->widgets[widgetCounts[WidgetInternalType::Text]], &graphWidgets->widgets[widgetCounts[WidgetInternalType::Graph]], &widgetCounts); } *activeTextWidgetCountOut = widgetCounts[WidgetInternalType::Text]; *activeGraphWidgetCountOut = widgetCounts[WidgetInternalType::Graph]; } } // namespace gl