// // Copyright (c) 2008-2015 the Urho3D project. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // #include "../../Core/CoreEvents.h" #include "../../Core/Profiler.h" #include "../../Engine/Engine.h" #include "../../Graphics/Graphics.h" #include "../../Graphics/Renderer.h" #include "../../IO/Log.h" #include "Font.h" #include "Text.h" #include "SystemUI.h" #include "UIElement.h" #include "DebugHud.h" #include "../../DebugNew.h" namespace Atomic { namespace SystemUI { static const char* qualityTexts[] = { "Low", "Med", "High" }; static const char* shadowQualityTexts[] = { "16bit Low", "24bit Low", "16bit High", "24bit High" }; static const float FPS_UPDATE_INTERVAL = 0.5f; DebugHud::DebugHud(Context* context) : Object(context), profilerMaxDepth_(M_MAX_UNSIGNED), profilerInterval_(1000), useRendererStats_(true), mode_(DEBUGHUD_SHOW_NONE), fpsTimeSinceUpdate_(FPS_UPDATE_INTERVAL), fpsFramesSinceUpdate_(0), fps_(0) { SystemUI* ui = GetSubsystem(); UIElement* uiRoot = ui->GetRoot(); layout_ = new UIElement(context_); uiRoot->AddChild(layout_); layout_->SetSize(uiRoot->GetSize()); statsText_ = new Text(context_); statsText_->SetAlignment(HA_LEFT, VA_TOP); statsText_->SetPriority(100); statsText_->SetVisible(false); layout_->AddChild(statsText_); modeText_ = new Text(context_); modeText_->SetAlignment(HA_LEFT, VA_BOTTOM); modeText_->SetPriority(100); modeText_->SetVisible(false); layout_->AddChild(modeText_); profilerText_ = new Text(context_); profilerText_->SetAlignment(HA_RIGHT, VA_TOP); profilerText_->SetPriority(100); profilerText_->SetVisible(false); layout_->AddChild(profilerText_); SubscribeToEvent(E_POSTUPDATE, ATOMIC_HANDLER(DebugHud, HandlePostUpdate)); } DebugHud::~DebugHud() { statsText_->Remove(); modeText_->Remove(); profilerText_->Remove(); } void DebugHud::SetExtents(bool useRootExtents, const IntVector2& position, const IntVector2& size) { if (useRootExtents) { SystemUI* ui = GetSubsystem(); UIElement* uiRoot = ui->GetRoot(); layout_->SetPosition(IntVector2::ZERO); layout_->SetSize(uiRoot->GetSize()); } else { layout_->SetPosition(position); layout_->SetSize(size); } } void DebugHud::Update(float timeStep) { Graphics* graphics = GetSubsystem(); Renderer* renderer = GetSubsystem(); if (!renderer || !graphics) return; // Ensure UI-elements are not detached if (!layout_->GetParent()) { SystemUI* ui = GetSubsystem(); UIElement* uiRoot = ui->GetRoot(); uiRoot->AddChild(layout_); } if (statsText_->IsVisible()) { fpsTimeSinceUpdate_ += timeStep; ++fpsFramesSinceUpdate_; if (fpsTimeSinceUpdate_ > FPS_UPDATE_INTERVAL) { fps_ = (int)(fpsFramesSinceUpdate_ / fpsTimeSinceUpdate_); fpsFramesSinceUpdate_ = 0; fpsTimeSinceUpdate_ = 0; } unsigned primitives, batches; if (!useRendererStats_) { primitives = graphics->GetNumPrimitives(); batches = graphics->GetNumBatches(); } else { primitives = renderer->GetNumPrimitives(); batches = renderer->GetNumBatches(); } String stats; unsigned singlePassPrimitives = graphics->GetSinglePassPrimitives(); unsigned editorPrimitives = graphics->GetNumPrimitives() - renderer->GetNumPrimitives(); if (singlePassPrimitives) stats.AppendWithFormat("FPS %d\nTriangles (All passes) %u\nTriangles (Single pass) %u\nTriangles (Editor) %u\n", fps_, primitives, singlePassPrimitives, editorPrimitives); else stats.AppendWithFormat("FPS %d\nTriangles %u\n", fps_, primitives); stats.AppendWithFormat("Batches %u\nViews %u\nLights %u\nShadowmaps %u\nOccluders %u", batches, renderer->GetNumViews(), renderer->GetNumLights(true), renderer->GetNumShadowMaps(true), renderer->GetNumOccluders(true)); if (!appStats_.Empty()) { stats.Append("\n"); for (HashMap::ConstIterator i = appStats_.Begin(); i != appStats_.End(); ++i) stats.AppendWithFormat("\n%s %s", i->first_.CString(), i->second_.CString()); } statsText_->SetText(stats); } if (modeText_->IsVisible()) { String mode; mode.AppendWithFormat("Tex:%s Mat:%s Spec:%s Shadows:%s Size:%i Quality:%s Occlusion:%s Instancing:%s API:%s", qualityTexts[renderer->GetTextureQuality()], qualityTexts[renderer->GetMaterialQuality()], renderer->GetSpecularLighting() ? "On" : "Off", renderer->GetDrawShadows() ? "On" : "Off", renderer->GetShadowMapSize(), shadowQualityTexts[renderer->GetShadowQuality()], renderer->GetMaxOccluderTriangles() > 0 ? "On" : "Off", renderer->GetDynamicInstancing() ? "On" : "Off", graphics->GetApiName().CString()); modeText_->SetText(mode); } Profiler* profiler = GetSubsystem(); if (profiler) { if (profilerTimer_.GetMSec(false) >= profilerInterval_) { profilerTimer_.Reset(); if (profilerText_->IsVisible()) { String profilerOutput = profiler->PrintData(false, false, profilerMaxDepth_); profilerText_->SetText(profilerOutput); } profiler->BeginInterval(); } } } void DebugHud::SetDefaultStyle(XMLFile* style) { if (!style) return; statsText_->SetDefaultStyle(style); statsText_->SetStyle("DebugHudText"); modeText_->SetDefaultStyle(style); modeText_->SetStyle("DebugHudText"); profilerText_->SetDefaultStyle(style); profilerText_->SetStyle("DebugHudText"); } void DebugHud::SetMode(unsigned mode) { statsText_->SetVisible((mode & DEBUGHUD_SHOW_STATS) != 0); modeText_->SetVisible((mode & DEBUGHUD_SHOW_MODE) != 0); profilerText_->SetVisible((mode & DEBUGHUD_SHOW_PROFILER) != 0); mode_ = mode; } void DebugHud::CycleMode() { switch (mode_) { case DEBUGHUD_SHOW_NONE: SetMode(DEBUGHUD_SHOW_STATS); break; case DEBUGHUD_SHOW_STATS: SetMode(DEBUGHUD_SHOW_MODE); break; case DEBUGHUD_SHOW_MODE: SetMode(DEBUGHUD_SHOW_PROFILER); break; case DEBUGHUD_SHOW_PROFILER: SetMode(DEBUGHUD_SHOW_ALL); break; case DEBUGHUD_SHOW_ALL: default: SetMode(DEBUGHUD_SHOW_NONE); break; } } void DebugHud::SetProfilerMaxDepth(unsigned depth) { profilerMaxDepth_ = depth; } void DebugHud::SetProfilerInterval(float interval) { profilerInterval_ = (unsigned)Max((int)(interval * 1000.0f), 0); } void DebugHud::SetUseRendererStats(bool enable) { useRendererStats_ = enable; } void DebugHud::Toggle(unsigned mode) { SetMode(GetMode() ^ mode); } void DebugHud::ToggleAll() { Toggle(DEBUGHUD_SHOW_ALL); } XMLFile* DebugHud::GetDefaultStyle() const { return statsText_->GetDefaultStyle(false); } float DebugHud::GetProfilerInterval() const { return (float)profilerInterval_ / 1000.0f; } void DebugHud::SetAppStats(const String& label, const Variant& stats) { SetAppStats(label, stats.ToString()); } void DebugHud::SetAppStats(const String& label, const String& stats) { bool newLabel = !appStats_.Contains(label); appStats_[label] = stats; if (newLabel) appStats_.Sort(); } bool DebugHud::ResetAppStats(const String& label) { return appStats_.Erase(label); } void DebugHud::ClearAppStats() { appStats_.Clear(); } void DebugHud::HandlePostUpdate(StringHash eventType, VariantMap& eventData) { using namespace PostUpdate; Update(eventData[P_TIMESTEP].GetFloat()); } } }