no message
This commit is contained in:
395
cocos/profiler/DebugRenderer.cpp
Normal file
395
cocos/profiler/DebugRenderer.cpp
Normal file
@@ -0,0 +1,395 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
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 "DebugRenderer.h"
|
||||
#include <algorithm>
|
||||
#include "Profiler.h"
|
||||
#include "application/ApplicationManager.h"
|
||||
#include "base/Log.h"
|
||||
#include "base/UTF8.h"
|
||||
#include "base/memory/Memory.h"
|
||||
#include "core/assets/BitmapFont.h"
|
||||
#include "core/assets/FreeTypeFont.h"
|
||||
#include "math/Vec2.h"
|
||||
#include "platform/interfaces/modules/Device.h"
|
||||
#include "platform/interfaces/modules/ISystemWindow.h"
|
||||
#include "platform/interfaces/modules/ISystemWindowManager.h"
|
||||
#include "renderer/gfx-base/GFXDescriptorSet.h"
|
||||
#include "renderer/gfx-base/GFXDevice.h"
|
||||
#include "renderer/pipeline/Define.h"
|
||||
#include "renderer/pipeline/GlobalDescriptorSetManager.h"
|
||||
#include "renderer/pipeline/PipelineSceneData.h"
|
||||
#include "renderer/pipeline/PipelineStateManager.h"
|
||||
#include "renderer/pipeline/RenderPipeline.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
constexpr uint32_t DEBUG_FONT_SIZE = 10U;
|
||||
constexpr uint32_t DEBUG_MAX_CHARACTERS = 10000U;
|
||||
constexpr uint32_t DEBUG_VERTICES_PER_CHAR = 6U;
|
||||
|
||||
inline uint32_t getFontIndex(bool bold, bool italic) {
|
||||
/**
|
||||
* Regular
|
||||
* Bold
|
||||
* Italic
|
||||
* BoldItalic
|
||||
*/
|
||||
uint32_t index = 0;
|
||||
index |= bold ? 1 : 0;
|
||||
index |= italic ? 2 : 0;
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
inline ccstd::string getFontPath(uint32_t index) {
|
||||
static const ccstd::string UUIDS[DEBUG_FONT_COUNT] = {
|
||||
"OpenSans-Regular", //"OpenSans-Regular",
|
||||
"OpenSans-Bold", //"OpenSans-Bold",
|
||||
"OpenSans-Italic", //"OpenSans-Italic",
|
||||
"OpenSans-BoldItalic", //"OpenSans-BoldItalic"
|
||||
};
|
||||
|
||||
auto *asset = BuiltinResMgr::getInstance()->getAsset(UUIDS[index]);
|
||||
|
||||
return asset->getNativeUrl();
|
||||
}
|
||||
|
||||
struct DebugVertex {
|
||||
DebugVertex() = default;
|
||||
DebugVertex(const Vec2 &pos, const Vec2 &tuv, gfx::Color clr)
|
||||
: position(pos), uv(tuv), color(clr) {}
|
||||
|
||||
Vec2 position;
|
||||
Vec2 uv;
|
||||
gfx::Color color;
|
||||
};
|
||||
|
||||
struct DebugBatch {
|
||||
DebugBatch(gfx::Device *device, bool bd, bool it, gfx::Texture *tex)
|
||||
: bold(bd), italic(it), texture(tex) {
|
||||
gfx::DescriptorSetLayoutInfo info;
|
||||
info.bindings.push_back({0, gfx::DescriptorType::SAMPLER_TEXTURE, 1, gfx::ShaderStageFlagBit::FRAGMENT});
|
||||
|
||||
descriptorSetLayout = device->createDescriptorSetLayout(info);
|
||||
descriptorSet = device->createDescriptorSet({descriptorSetLayout});
|
||||
|
||||
auto *sampler = device->getSampler({
|
||||
gfx::Filter::LINEAR,
|
||||
gfx::Filter::LINEAR,
|
||||
gfx::Filter::NONE,
|
||||
gfx::Address::CLAMP,
|
||||
gfx::Address::CLAMP,
|
||||
gfx::Address::CLAMP,
|
||||
});
|
||||
|
||||
descriptorSet->bindSampler(0, sampler);
|
||||
descriptorSet->bindTexture(0, texture);
|
||||
descriptorSet->update();
|
||||
}
|
||||
|
||||
~DebugBatch() {
|
||||
CC_SAFE_DESTROY_AND_DELETE(descriptorSet);
|
||||
CC_SAFE_DESTROY_AND_DELETE(descriptorSetLayout);
|
||||
}
|
||||
|
||||
inline bool match(bool b, bool i, gfx::Texture *tex) const {
|
||||
return bold == b && italic == i && texture == tex;
|
||||
}
|
||||
|
||||
std::vector<DebugVertex> vertices;
|
||||
bool bold{false};
|
||||
bool italic{false};
|
||||
gfx::Texture *texture{nullptr};
|
||||
gfx::DescriptorSet *descriptorSet{nullptr};
|
||||
gfx::DescriptorSetLayout *descriptorSetLayout{nullptr};
|
||||
};
|
||||
|
||||
class DebugVertexBuffer {
|
||||
public:
|
||||
inline void init(gfx::Device *device, uint32_t maxVertices, const gfx::AttributeList &attributes) {
|
||||
_maxVertices = maxVertices;
|
||||
_buffer = device->createBuffer({gfx::BufferUsageBit::VERTEX | gfx::BufferUsageBit::TRANSFER_DST,
|
||||
gfx::MemoryUsageBit::DEVICE,
|
||||
static_cast<uint32_t>(_maxVertices * sizeof(DebugVertex)),
|
||||
static_cast<uint32_t>(sizeof(DebugVertex))});
|
||||
|
||||
gfx::InputAssemblerInfo info;
|
||||
info.attributes = attributes;
|
||||
info.vertexBuffers.push_back(_buffer);
|
||||
_inputAssembler = device->createInputAssembler(info);
|
||||
CC_PROFILE_MEMORY_INC(DebugVertexBuffer, static_cast<uint32_t>(_maxVertices * sizeof(DebugVertex)));
|
||||
}
|
||||
|
||||
inline void update() {
|
||||
if (empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<DebugVertex> vertices;
|
||||
for (auto *batch : _batches) {
|
||||
vertices.insert(vertices.end(), batch->vertices.begin(), batch->vertices.end());
|
||||
}
|
||||
|
||||
const auto count = std::min(static_cast<uint32_t>(vertices.size()), _maxVertices);
|
||||
const auto size = static_cast<uint32_t>(count * sizeof(DebugVertex));
|
||||
_buffer->update(&vertices[0], size);
|
||||
}
|
||||
|
||||
inline void destroy() {
|
||||
for (auto *batch : _batches) {
|
||||
CC_SAFE_DELETE(batch);
|
||||
}
|
||||
|
||||
CC_SAFE_DESTROY_AND_DELETE(_buffer);
|
||||
CC_SAFE_DESTROY_AND_DELETE(_inputAssembler);
|
||||
CC_PROFILE_MEMORY_DEC(DebugVertexBuffer, static_cast<uint32_t>(_maxVertices * sizeof(DebugVertex)));
|
||||
}
|
||||
|
||||
DebugBatch &getOrCreateBatch(gfx::Device *device, bool bold, bool italic, gfx::Texture *texture) {
|
||||
for (auto *batch : _batches) {
|
||||
if (batch->match(bold, italic, texture)) {
|
||||
return *batch;
|
||||
}
|
||||
}
|
||||
|
||||
auto *batch = ccnew DebugBatch(device, bold, italic, texture);
|
||||
_batches.push_back(batch);
|
||||
|
||||
return *batch;
|
||||
}
|
||||
|
||||
inline bool empty() const {
|
||||
return std::all_of(_batches.begin(),
|
||||
_batches.end(),
|
||||
[](const DebugBatch *batch) { return batch->vertices.empty(); });
|
||||
}
|
||||
inline void reset() {
|
||||
for (auto *batch : _batches) {
|
||||
batch->vertices.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t _maxVertices{0U};
|
||||
std::vector<DebugBatch *> _batches;
|
||||
gfx::Buffer *_buffer{nullptr};
|
||||
gfx::InputAssembler *_inputAssembler{nullptr};
|
||||
|
||||
friend class DebugRenderer;
|
||||
};
|
||||
|
||||
DebugRendererInfo::DebugRendererInfo()
|
||||
: fontSize(DEBUG_FONT_SIZE), maxCharacters(DEBUG_MAX_CHARACTERS) {
|
||||
}
|
||||
|
||||
DebugRenderer *DebugRenderer::instance = nullptr;
|
||||
DebugRenderer *DebugRenderer::getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
DebugRenderer::DebugRenderer() {
|
||||
DebugRenderer::instance = this;
|
||||
}
|
||||
|
||||
DebugRenderer::~DebugRenderer() {
|
||||
DebugRenderer::instance = nullptr;
|
||||
}
|
||||
|
||||
void DebugRenderer::activate(gfx::Device *device, const DebugRendererInfo &info) {
|
||||
_device = device;
|
||||
|
||||
static const gfx::AttributeList ATTRIBUTES = {
|
||||
{"a_position", gfx::Format::RG32F},
|
||||
{"a_texCoord", gfx::Format::RG32F},
|
||||
{"a_color", gfx::Format::RGBA32F}};
|
||||
|
||||
_buffer = ccnew DebugVertexBuffer();
|
||||
_buffer->init(_device, info.maxCharacters * DEBUG_VERTICES_PER_CHAR, ATTRIBUTES);
|
||||
|
||||
const auto *window = CC_GET_MAIN_SYSTEM_WINDOW();
|
||||
const auto width = window->getViewSize().width * Device::getDevicePixelRatio();
|
||||
auto fontSize = static_cast<uint32_t>(width / 800.0F * info.fontSize);
|
||||
fontSize = fontSize < 10U ? 10U : (fontSize > 20U ? 20U : fontSize);
|
||||
|
||||
for (auto i = 0U; i < _fonts.size(); i++) {
|
||||
_fonts[i].font = ccnew FreeTypeFont(getFontPath(i));
|
||||
_fonts[i].face = _fonts[i].font->createFace(FontFaceInfo(fontSize));
|
||||
_fonts[i].invTextureSize = {1.0F / _fonts[i].face->getTextureWidth(), 1.0F / _fonts[i].face->getTextureHeight()};
|
||||
}
|
||||
}
|
||||
|
||||
void DebugRenderer::render(gfx::RenderPass *renderPass, gfx::CommandBuffer *cmdBuff, pipeline::PipelineSceneData *sceneData) {
|
||||
CC_PROFILE(DebugRendererRender);
|
||||
if (!_buffer || _buffer->empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &pass = sceneData->getDebugRendererPass();
|
||||
const auto &shader = sceneData->getDebugRendererShader();
|
||||
|
||||
auto *pso = pipeline::PipelineStateManager::getOrCreatePipelineState(pass, shader, _buffer->_inputAssembler, renderPass);
|
||||
cmdBuff->bindPipelineState(pso);
|
||||
cmdBuff->bindInputAssembler(_buffer->_inputAssembler);
|
||||
|
||||
uint32_t offset = 0U;
|
||||
for (auto *batch : _buffer->_batches) {
|
||||
auto count = std::min(static_cast<uint32_t>(batch->vertices.size()), _buffer->_maxVertices - offset);
|
||||
if (count == 0U) {
|
||||
break;
|
||||
}
|
||||
|
||||
gfx::DrawInfo drawInfo;
|
||||
drawInfo.firstVertex = offset;
|
||||
drawInfo.vertexCount = count;
|
||||
|
||||
cmdBuff->bindDescriptorSet(pipeline::materialSet, batch->descriptorSet);
|
||||
cmdBuff->draw(drawInfo);
|
||||
|
||||
offset += count;
|
||||
}
|
||||
|
||||
// reset all debug data for next frame
|
||||
_buffer->reset();
|
||||
}
|
||||
|
||||
void DebugRenderer::destroy() {
|
||||
CC_SAFE_DESTROY_AND_DELETE(_buffer);
|
||||
|
||||
for (auto &iter : _fonts) {
|
||||
CC_SAFE_DELETE(iter.font);
|
||||
}
|
||||
}
|
||||
|
||||
void DebugRenderer::update() {
|
||||
if (_buffer) {
|
||||
_buffer->update();
|
||||
}
|
||||
}
|
||||
|
||||
void DebugRenderer::addText(const ccstd::string &text, const Vec2 &screenPos) {
|
||||
addText(text, screenPos, DebugTextInfo());
|
||||
}
|
||||
|
||||
void DebugRenderer::addText(const ccstd::string &text, const Vec2 &screenPos, const DebugTextInfo &info) {
|
||||
uint32_t index = getFontIndex(info.bold, info.italic);
|
||||
auto &fontInfo = _fonts[index];
|
||||
auto *face = fontInfo.face;
|
||||
|
||||
if (!_buffer || !face || text.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::u32string unicodeText;
|
||||
bool success = StringUtils::UTF8ToUTF32(text, unicodeText);
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto offsetX = screenPos.x;
|
||||
auto offsetY = screenPos.y;
|
||||
const auto scale = info.scale;
|
||||
const auto lineHeight = face->getLineHeight() * scale;
|
||||
const auto &invTextureSize = fontInfo.invTextureSize;
|
||||
|
||||
for (char32_t code : unicodeText) {
|
||||
if (code == '\r') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (code == '\n') {
|
||||
offsetX = screenPos.x;
|
||||
offsetY += lineHeight;
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto *glyph = face->getGlyph(code);
|
||||
if (!glyph) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (glyph->width > 0U && glyph->height > 0U) {
|
||||
auto &batch = _buffer->getOrCreateBatch(_device, info.bold, info.italic, face->getTexture(glyph->page));
|
||||
|
||||
Vec4 rect{offsetX + static_cast<float>(glyph->bearingX) * scale,
|
||||
offsetY - static_cast<float>(glyph->bearingY) * scale,
|
||||
static_cast<float>(glyph->width) * scale,
|
||||
static_cast<float>(glyph->height) * scale};
|
||||
Vec4 uv{static_cast<float>(glyph->x) * invTextureSize.x,
|
||||
static_cast<float>(glyph->y) * invTextureSize.y,
|
||||
static_cast<float>(glyph->width) * invTextureSize.x,
|
||||
static_cast<float>(glyph->height) * invTextureSize.y};
|
||||
|
||||
if (info.shadow) {
|
||||
for (auto x = 1U; x <= info.shadowThickness; x++) {
|
||||
for (auto y = 1U; y <= info.shadowThickness; y++) {
|
||||
Vec4 shadowRect(rect.x + x, rect.y + y, rect.z, rect.w);
|
||||
addQuad(batch, shadowRect, uv, info.shadowColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addQuad(batch, rect, uv, info.color);
|
||||
}
|
||||
|
||||
offsetX += glyph->advance * scale;
|
||||
|
||||
#ifdef USE_KERNING
|
||||
if (i < unicodeText.size() - 1) {
|
||||
offsetX += face->getKerning(code, unicodeText[i + 1]) * scale;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t DebugRenderer::getLineHeight(bool bold, bool italic) {
|
||||
uint32_t index = getFontIndex(bold, italic);
|
||||
auto &fontInfo = _fonts[index];
|
||||
|
||||
if (fontInfo.face) {
|
||||
return fontInfo.face->getLineHeight();
|
||||
}
|
||||
|
||||
return 0U;
|
||||
}
|
||||
|
||||
void DebugRenderer::addQuad(DebugBatch &batch, const Vec4 &rect, const Vec4 &uv, gfx::Color color) {
|
||||
DebugVertex quad[4] = {
|
||||
{Vec2(rect.x, rect.y), Vec2(uv.x, uv.y), color},
|
||||
{Vec2(rect.x + rect.z, rect.y), Vec2(uv.x + uv.z, uv.y), color},
|
||||
{Vec2(rect.x, rect.y + rect.w), Vec2(uv.x, uv.y + uv.w), color},
|
||||
{Vec2(rect.x + rect.z, rect.y + rect.w), Vec2(uv.x + uv.z, uv.y + uv.w), color}};
|
||||
|
||||
// first triangle
|
||||
batch.vertices.emplace_back(quad[0]);
|
||||
batch.vertices.emplace_back(quad[1]);
|
||||
batch.vertices.emplace_back(quad[2]);
|
||||
|
||||
// second triangle
|
||||
batch.vertices.emplace_back(quad[1]);
|
||||
batch.vertices.emplace_back(quad[3]);
|
||||
batch.vertices.emplace_back(quad[2]);
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
110
cocos/profiler/DebugRenderer.h
Normal file
110
cocos/profiler/DebugRenderer.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
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.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
#include <math/Vec2.h>
|
||||
#include <math/Vec4.h>
|
||||
#include "base/std/container/array.h"
|
||||
#include "base/std/container/string.h"
|
||||
#include "renderer/gfx-base/GFXDef-common.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
namespace pipeline {
|
||||
class PipelineSceneData;
|
||||
}
|
||||
|
||||
namespace gfx {
|
||||
class Device;
|
||||
class RenderPass;
|
||||
class CommandBuffer;
|
||||
} // namespace gfx
|
||||
|
||||
class Font;
|
||||
class FontFace;
|
||||
class DebugVertexBuffer;
|
||||
struct DebugBatch;
|
||||
|
||||
struct DebugRendererInfo {
|
||||
DebugRendererInfo();
|
||||
|
||||
uint32_t fontSize{0U};
|
||||
uint32_t maxCharacters{0U};
|
||||
};
|
||||
|
||||
struct DebugTextInfo {
|
||||
DebugTextInfo() = default;
|
||||
|
||||
gfx::Color color{1.0F, 1.0F, 1.0F, 1.0F};
|
||||
bool bold{false};
|
||||
bool italic{false};
|
||||
bool shadow{false};
|
||||
uint32_t shadowThickness{1U};
|
||||
gfx::Color shadowColor{0.0F, 0.0F, 0.0F, 1.0F};
|
||||
float scale{1.0F};
|
||||
};
|
||||
|
||||
struct DebugFontInfo {
|
||||
Font *font{nullptr};
|
||||
FontFace *face{nullptr};
|
||||
Vec2 invTextureSize{0.0F, 0.0F};
|
||||
};
|
||||
|
||||
constexpr uint32_t DEBUG_FONT_COUNT = 4U;
|
||||
using DebugFontArray = ccstd::array<DebugFontInfo, DEBUG_FONT_COUNT>;
|
||||
|
||||
class DebugRenderer {
|
||||
public:
|
||||
static DebugRenderer *getInstance();
|
||||
|
||||
DebugRenderer();
|
||||
DebugRenderer(const DebugRenderer &) = delete;
|
||||
DebugRenderer(DebugRenderer &&) = delete;
|
||||
DebugRenderer &operator=(const DebugRenderer &) = delete;
|
||||
DebugRenderer &operator=(DebugRenderer &&) = delete;
|
||||
~DebugRenderer();
|
||||
|
||||
void activate(gfx::Device *device, const DebugRendererInfo &info = DebugRendererInfo());
|
||||
void render(gfx::RenderPass *renderPass, gfx::CommandBuffer *cmdBuff, pipeline::PipelineSceneData *sceneData);
|
||||
void destroy();
|
||||
void update();
|
||||
|
||||
void addText(const ccstd::string &text, const Vec2 &screenPos);
|
||||
void addText(const ccstd::string &text, const Vec2 &screenPos, const DebugTextInfo &info);
|
||||
|
||||
private:
|
||||
static void addQuad(DebugBatch &batch, const Vec4 &rect, const Vec4 &uv, gfx::Color color);
|
||||
uint32_t getLineHeight(bool bold = false, bool italic = false);
|
||||
|
||||
static DebugRenderer *instance;
|
||||
gfx::Device *_device{nullptr};
|
||||
DebugVertexBuffer *_buffer{nullptr};
|
||||
DebugFontArray _fonts;
|
||||
|
||||
friend class Profiler;
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
|
||||
#define CC_DEBUG_RENDERER cc::DebugRenderer::getInstance()
|
||||
250
cocos/profiler/GameStats.h
Normal file
250
cocos/profiler/GameStats.h
Normal file
@@ -0,0 +1,250 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
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.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
#include "base/StringUtil.h"
|
||||
#include "base/std/container/string.h"
|
||||
#include "base/std/container/unordered_map.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
struct TimeCounter {
|
||||
// current frame
|
||||
uint64_t max{0U};
|
||||
uint64_t time{0U};
|
||||
uint32_t count{0U};
|
||||
|
||||
// last interval
|
||||
uint64_t intervalMax{0U};
|
||||
uint64_t intervalTime{0U};
|
||||
uint32_t intervalCount{0U};
|
||||
uint32_t framesOfInterval{0U};
|
||||
|
||||
// whole time
|
||||
uint64_t totalMax{0U};
|
||||
uint64_t totalTime{0U};
|
||||
uint32_t totalCount{0U};
|
||||
|
||||
// frame & whole data used to display
|
||||
uint64_t frameMaxDisplay{0U};
|
||||
uint64_t frameTimeDisplay{0U};
|
||||
uint32_t frameCountDisplay{0U};
|
||||
|
||||
uint64_t totalMaxDisplay{0U};
|
||||
uint64_t totalTimeDisplay{0U};
|
||||
uint32_t totalCountDisplay{0U};
|
||||
|
||||
inline void operator+=(uint64_t value) {
|
||||
max = std::max(max, value);
|
||||
time += value;
|
||||
count++;
|
||||
}
|
||||
|
||||
inline void onFrameBegin() {
|
||||
// reset current frame
|
||||
max = 0U;
|
||||
time = 0U;
|
||||
count = 0U;
|
||||
}
|
||||
|
||||
inline void onFrameEnd() {
|
||||
// update last interval
|
||||
intervalMax = std::max(intervalMax, max);
|
||||
intervalTime += time;
|
||||
intervalCount += count;
|
||||
framesOfInterval++;
|
||||
|
||||
// update whole time
|
||||
totalMax = std::max(totalMax, max);
|
||||
totalTime += time;
|
||||
totalCount += count;
|
||||
}
|
||||
|
||||
inline void onIntervalUpdate() {
|
||||
// update display data
|
||||
frameMaxDisplay = intervalMax;
|
||||
frameTimeDisplay = framesOfInterval ? intervalTime / framesOfInterval : 0U;
|
||||
frameCountDisplay = framesOfInterval ? intervalCount / framesOfInterval : 0U;
|
||||
|
||||
totalMaxDisplay = totalMax;
|
||||
totalTimeDisplay = totalTime;
|
||||
totalCountDisplay = totalCount;
|
||||
|
||||
// reset interval data
|
||||
intervalMax = 0U;
|
||||
intervalTime = 0U;
|
||||
intervalCount = 0U;
|
||||
framesOfInterval = 0U;
|
||||
}
|
||||
};
|
||||
|
||||
struct MemoryCounter {
|
||||
uint64_t total{0U}; // memory in current frame
|
||||
uint32_t count{0U}; // count in current frame
|
||||
uint64_t totalMax{0U}; // max memory during whole time
|
||||
|
||||
inline void operator=(uint64_t value) {
|
||||
total = value;
|
||||
totalMax = std::max(totalMax, total);
|
||||
count = 1;
|
||||
}
|
||||
|
||||
inline void operator+=(uint64_t value) {
|
||||
total += value;
|
||||
totalMax = std::max(totalMax, total);
|
||||
count++;
|
||||
}
|
||||
|
||||
inline void operator-=(uint64_t value) {
|
||||
total -= value;
|
||||
count--;
|
||||
}
|
||||
};
|
||||
|
||||
struct ObjectCounter {
|
||||
uint32_t total{0U}; // count in current frame
|
||||
uint32_t lastTotal{0U}; // count in last frame
|
||||
uint32_t totalMax{0U}; // max count during whole time
|
||||
|
||||
inline void operator=(uint32_t value) {
|
||||
total = value;
|
||||
totalMax = std::max(totalMax, total);
|
||||
}
|
||||
|
||||
inline void operator+=(uint32_t value) {
|
||||
total += value;
|
||||
totalMax = std::max(totalMax, total);
|
||||
}
|
||||
|
||||
inline void operator-=(uint32_t value) {
|
||||
total -= value;
|
||||
}
|
||||
|
||||
inline void onFrameBegin() {
|
||||
total = 0U;
|
||||
}
|
||||
|
||||
inline void onFrameEnd() {
|
||||
lastTotal = total;
|
||||
}
|
||||
};
|
||||
|
||||
// assume update in main thread only.
|
||||
struct CoreStats {
|
||||
uint32_t fps{0U};
|
||||
float frameTime{0.0F};
|
||||
ccstd::string gfx;
|
||||
bool multiThread{true};
|
||||
bool occlusionQuery{false};
|
||||
bool shadowMap{false};
|
||||
uint32_t screenWidth{0U};
|
||||
uint32_t screenHeight{0U};
|
||||
};
|
||||
|
||||
struct MemoryStats {
|
||||
// memory stats
|
||||
std::mutex mutex;
|
||||
ccstd::unordered_map<ccstd::string, MemoryCounter> memories;
|
||||
|
||||
inline void update(const ccstd::string &name, uint64_t value) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
memories[name] = value;
|
||||
}
|
||||
|
||||
inline void inc(const ccstd::string &name, uint64_t value) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
memories[name] += value;
|
||||
}
|
||||
|
||||
inline void dec(const ccstd::string &name, uint64_t value) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
memories[name] -= value;
|
||||
}
|
||||
};
|
||||
|
||||
// assume update in main thread only.
|
||||
struct ObjectStats {
|
||||
// render stats: drawcalls, instances, triangles, etc
|
||||
ccstd::unordered_map<ccstd::string, ObjectCounter> renders;
|
||||
// object stats
|
||||
ccstd::unordered_map<ccstd::string, ObjectCounter> objects;
|
||||
|
||||
inline void onFrameBegin() {
|
||||
for (auto &item : renders) {
|
||||
item.second.onFrameBegin();
|
||||
}
|
||||
|
||||
for (auto &item : objects) {
|
||||
item.second.onFrameBegin();
|
||||
}
|
||||
}
|
||||
|
||||
inline void onFrameEnd() {
|
||||
for (auto &item : renders) {
|
||||
item.second.onFrameEnd();
|
||||
}
|
||||
|
||||
for (auto &item : objects) {
|
||||
item.second.onFrameEnd();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class StatsUtil {
|
||||
public:
|
||||
static inline ccstd::string formatBytes(uint64_t bytes) {
|
||||
if (bytes >= 1024 * 1024 * 1024) {
|
||||
return StringUtil::format("%.3fG", bytes / (1024.0F * 1024.0F * 1024.0F));
|
||||
} else if (bytes >= 1024 * 1024) {
|
||||
return StringUtil::format("%.3fM", bytes / (1024.0F * 1024.0F));
|
||||
} else if (bytes >= 1024) {
|
||||
return StringUtil::format("%.3fK", bytes / 1024.0F);
|
||||
} else {
|
||||
return StringUtil::format("%llu", bytes);
|
||||
}
|
||||
}
|
||||
|
||||
static inline ccstd::string formatTime(uint64_t time) {
|
||||
if (time >= 1000000) {
|
||||
return StringUtil::format("%.3fs", time / 1000000.0F);
|
||||
} else {
|
||||
return StringUtil::format("%.3fms", time / 1000.0F);
|
||||
}
|
||||
}
|
||||
|
||||
static inline ccstd::string formatName(uint32_t depth, const ccstd::string &name) {
|
||||
if (depth > 0U) {
|
||||
return StringUtil::format("%*s%s", depth * 2, " ", name.c_str());
|
||||
}
|
||||
return name;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
413
cocos/profiler/Profiler.cpp
Normal file
413
cocos/profiler/Profiler.cpp
Normal file
@@ -0,0 +1,413 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
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 "Profiler.h"
|
||||
#if CC_USE_DEBUG_RENDERER
|
||||
#include "DebugRenderer.h"
|
||||
#endif
|
||||
#include "application/ApplicationManager.h"
|
||||
#include "base/Log.h"
|
||||
#include "base/Macros.h"
|
||||
#include "base/memory/MemoryHook.h"
|
||||
#include "core/Root.h"
|
||||
#include "core/assets/Font.h"
|
||||
#include "gfx-base/GFXDevice.h"
|
||||
#include "platform/FileUtils.h"
|
||||
#include "platform/interfaces/modules/Device.h"
|
||||
#include "platform/interfaces/modules/ISystemWindow.h"
|
||||
#include "platform/interfaces/modules/ISystemWindowManager.h"
|
||||
#include "renderer/GFXDeviceManager.h"
|
||||
#include "renderer/pipeline/PipelineSceneData.h"
|
||||
#include "renderer/pipeline/custom/RenderInterfaceTypes.h"
|
||||
#include "scene/Shadow.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
/**
|
||||
* ProfilerBlock
|
||||
*/
|
||||
class ProfilerBlock {
|
||||
public:
|
||||
ProfilerBlock(ProfilerBlock *parent, const std::string_view &name)
|
||||
: _parent(parent), _name(name) {}
|
||||
~ProfilerBlock();
|
||||
|
||||
inline void begin() { _timer.reset(); }
|
||||
inline void end() { _item += _timer.getMicroseconds(); }
|
||||
ProfilerBlock *getOrCreateChild(const std::string_view &name);
|
||||
void onFrameBegin();
|
||||
void onFrameEnd();
|
||||
void doIntervalUpdate();
|
||||
|
||||
private:
|
||||
ProfilerBlock *_parent{nullptr};
|
||||
std::vector<ProfilerBlock *> _children;
|
||||
utils::Timer _timer;
|
||||
ccstd::string _name;
|
||||
TimeCounter _item;
|
||||
|
||||
friend class Profiler;
|
||||
};
|
||||
|
||||
ProfilerBlock::~ProfilerBlock() {
|
||||
for (auto *child : _children) {
|
||||
CC_SAFE_DELETE(child);
|
||||
}
|
||||
_children.clear();
|
||||
}
|
||||
|
||||
ProfilerBlock *ProfilerBlock::getOrCreateChild(const std::string_view &name) {
|
||||
for (auto *child : _children) {
|
||||
if (child->_name == name) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
auto *child = ccnew ProfilerBlock(this, name);
|
||||
_children.push_back(child);
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
void ProfilerBlock::onFrameBegin() { //NOLINT(misc-no-recursion)
|
||||
_item.onFrameBegin();
|
||||
|
||||
for (auto *child : _children) {
|
||||
child->onFrameBegin();
|
||||
}
|
||||
}
|
||||
|
||||
void ProfilerBlock::onFrameEnd() { //NOLINT(misc-no-recursion)
|
||||
for (auto *child : _children) {
|
||||
child->onFrameEnd();
|
||||
}
|
||||
|
||||
_item.onFrameEnd();
|
||||
}
|
||||
|
||||
void ProfilerBlock::doIntervalUpdate() { //NOLINT(misc-no-recursion)
|
||||
_item.onIntervalUpdate();
|
||||
|
||||
for (auto *child : _children) {
|
||||
child->doIntervalUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
struct ProfilerBlockDepth {
|
||||
ProfilerBlock *block{nullptr};
|
||||
uint32_t depth{0U};
|
||||
};
|
||||
|
||||
/**
|
||||
* Profiler
|
||||
*/
|
||||
Profiler *Profiler::instance = nullptr;
|
||||
Profiler *Profiler::getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
Profiler::Profiler() {
|
||||
_mainThreadId = std::this_thread::get_id();
|
||||
_root = ccnew ProfilerBlock(nullptr, "MainThread");
|
||||
_current = _root;
|
||||
|
||||
Profiler::instance = this;
|
||||
}
|
||||
|
||||
Profiler::~Profiler() {
|
||||
CC_SAFE_DELETE(_root);
|
||||
_current = nullptr;
|
||||
Profiler::instance = nullptr;
|
||||
}
|
||||
|
||||
void Profiler::setEnable(ShowOption option, bool b) {
|
||||
auto flag = static_cast<uint32_t>(option);
|
||||
if (b) {
|
||||
_options |= flag;
|
||||
} else {
|
||||
_options &= ~flag;
|
||||
}
|
||||
}
|
||||
|
||||
bool Profiler::isEnabled(ShowOption option) const {
|
||||
auto flag = static_cast<uint32_t>(option);
|
||||
return (_options & flag) == flag;
|
||||
}
|
||||
|
||||
void Profiler::beginFrame() {
|
||||
_objectStats.onFrameBegin();
|
||||
|
||||
_current = _root;
|
||||
_root->onFrameBegin();
|
||||
_root->begin();
|
||||
}
|
||||
|
||||
void Profiler::endFrame() {
|
||||
CC_ASSERT_EQ(_current, _root); // Call stack data is not matched.
|
||||
|
||||
_root->end();
|
||||
_root->onFrameEnd();
|
||||
|
||||
_objectStats.onFrameEnd();
|
||||
}
|
||||
|
||||
void Profiler::update() {
|
||||
CC_PROFILE(ProfilerUpdate);
|
||||
|
||||
// update interval every 0.5 seconds.
|
||||
const auto intervalUpdate = _timer.getSeconds() >= 0.5F;
|
||||
if (intervalUpdate) {
|
||||
_timer.reset();
|
||||
|
||||
doIntervalUpdate();
|
||||
_root->doIntervalUpdate();
|
||||
}
|
||||
|
||||
doFrameUpdate();
|
||||
|
||||
printStats();
|
||||
}
|
||||
|
||||
void Profiler::doIntervalUpdate() {
|
||||
const auto *pipeline = Root::getInstance()->getPipeline();
|
||||
const auto *root = Root::getInstance();
|
||||
const auto *sceneData = pipeline->getPipelineSceneData();
|
||||
const auto *shadows = sceneData->getShadows();
|
||||
const auto *window = CC_GET_MAIN_SYSTEM_WINDOW();
|
||||
const auto viewSize = window->getViewSize() * Device::getDevicePixelRatio();
|
||||
|
||||
_coreStats.fps = root->getFps();
|
||||
_coreStats.frameTime = root->getFrameTime() * 1000.0F;
|
||||
_coreStats.gfx = gfx::DeviceManager::getGFXName();
|
||||
_coreStats.multiThread = gfx::DeviceManager::isDetachDeviceThread();
|
||||
_coreStats.occlusionQuery = pipeline->isOcclusionQueryEnabled();
|
||||
_coreStats.shadowMap = shadows != nullptr && shadows->isEnabled() && shadows->getType() == scene::ShadowType::SHADOW_MAP;
|
||||
_coreStats.screenWidth = static_cast<uint32_t>(viewSize.width);
|
||||
_coreStats.screenHeight = static_cast<uint32_t>(viewSize.height);
|
||||
}
|
||||
|
||||
void Profiler::doFrameUpdate() {
|
||||
auto *device = gfx::Device::getInstance();
|
||||
|
||||
CC_PROFILE_RENDER_UPDATE(DrawCalls, device->getNumDrawCalls());
|
||||
CC_PROFILE_RENDER_UPDATE(Instances, device->getNumInstances());
|
||||
CC_PROFILE_RENDER_UPDATE(Triangles, device->getNumTris());
|
||||
|
||||
#if USE_MEMORY_LEAK_DETECTOR
|
||||
CC_PROFILE_MEMORY_UPDATE(HeapMemory, GMemoryHook.getTotalSize());
|
||||
#endif
|
||||
}
|
||||
|
||||
void Profiler::printStats() {
|
||||
#if CC_USE_DEBUG_RENDERER
|
||||
auto *renderer = CC_DEBUG_RENDERER;
|
||||
const auto *window = CC_GET_MAIN_SYSTEM_WINDOW();
|
||||
const auto viewSize = window->getViewSize() * Device::getDevicePixelRatio();
|
||||
const auto width = viewSize.width;
|
||||
const auto lineHeight = renderer->getLineHeight();
|
||||
const auto columnWidth = width / 12.0F; // divide column numbers
|
||||
const auto leftOffset = width * 0.01F;
|
||||
float lines = 1.0F;
|
||||
float leftLines = 1.0F;
|
||||
float rightLines = 1.0F;
|
||||
|
||||
const DebugTextInfo coreInfo = {{1.0F, 0.0F, 0.0F, 1.0F}, true, false, true, 1U, {0.0F, 0.0F, 0.0F, 1.0F}, 1.0F};
|
||||
const DebugTextInfo titleInfo = {{1.0F, 1.0F, 0.0F, 1.0F}, true, false, true, 1U, {0.0F, 0.0F, 0.0F, 1.0F}, 1.0F};
|
||||
const DebugTextInfo textInfos[2] = {
|
||||
{{1.0F, 1.0F, 1.0F, 1.0F}, false, false, true, 1U, {0.0F, 0.0F, 0.0F, 1.0F}, 1.0F},
|
||||
{{0.8F, 0.8F, 0.8F, 1.0F}, false, false, true, 1U, {0.0F, 0.0F, 0.0F, 1.0F}, 1.0F},
|
||||
};
|
||||
|
||||
if (isEnabled(ShowOption::CORE_STATS)) {
|
||||
float coreOffset = columnWidth * 2.0F;
|
||||
auto coreStats = StringUtil::format(
|
||||
"FPS: %u "
|
||||
"Frame: %.1fms "
|
||||
"GFX: %s "
|
||||
"MultiThread: %s "
|
||||
"Occlusion: %s "
|
||||
"ShadowMap: %s "
|
||||
"ScreenSize: [%u * %u] ",
|
||||
_coreStats.fps,
|
||||
_coreStats.frameTime,
|
||||
_coreStats.gfx.c_str(),
|
||||
_coreStats.multiThread ? "On" : "Off",
|
||||
_coreStats.occlusionQuery ? "On" : "Off",
|
||||
_coreStats.shadowMap ? "On" : "Off",
|
||||
_coreStats.screenWidth,
|
||||
_coreStats.screenHeight);
|
||||
|
||||
renderer->addText(StringUtil::format("CoreStats"), {leftOffset, lineHeight * lines}, coreInfo);
|
||||
renderer->addText(coreStats, {coreOffset, lineHeight * lines}, coreInfo);
|
||||
lines++;
|
||||
|
||||
lines += 0.5F;
|
||||
}
|
||||
|
||||
if (isEnabled(ShowOption::OBJECT_STATS)) {
|
||||
leftLines = lines;
|
||||
float yOffset = lineHeight * leftLines;
|
||||
float countOffset = columnWidth * 2;
|
||||
float totalMaxOffset = columnWidth * 3;
|
||||
|
||||
renderer->addText("ObjectStats", {leftOffset, yOffset}, titleInfo);
|
||||
renderer->addText("Count", {countOffset, yOffset}, titleInfo);
|
||||
renderer->addText("TotalMax", {totalMaxOffset, yOffset}, titleInfo);
|
||||
leftLines++;
|
||||
|
||||
for (auto &iter : _objectStats.renders) {
|
||||
auto &item = iter.second;
|
||||
yOffset = lineHeight * leftLines;
|
||||
|
||||
renderer->addText(iter.first, {leftOffset, yOffset}, textInfos[0]);
|
||||
renderer->addText(StringUtil::format("%u", item.lastTotal), {countOffset, yOffset}, textInfos[0]);
|
||||
renderer->addText(StringUtil::format("%u", item.totalMax), {totalMaxOffset, yOffset}, textInfos[0]);
|
||||
leftLines++;
|
||||
}
|
||||
|
||||
for (auto &iter : _objectStats.objects) {
|
||||
auto &item = iter.second;
|
||||
yOffset = lineHeight * leftLines;
|
||||
|
||||
renderer->addText(iter.first, {leftOffset, yOffset}, textInfos[0]);
|
||||
renderer->addText(StringUtil::format("%u", item.lastTotal), {countOffset, yOffset}, textInfos[0]);
|
||||
renderer->addText(StringUtil::format("%u", item.totalMax), {totalMaxOffset, yOffset}, textInfos[0]);
|
||||
leftLines++;
|
||||
}
|
||||
|
||||
leftLines += 0.5F;
|
||||
}
|
||||
|
||||
if (isEnabled(ShowOption::MEMORY_STATS)) {
|
||||
rightLines = lines;
|
||||
float yOffset = lineHeight * rightLines;
|
||||
float memoryOffset = width * 0.5F;
|
||||
float totalOffset = memoryOffset + columnWidth * 2;
|
||||
float countOffset = memoryOffset + columnWidth * 3;
|
||||
float totalMaxOffset = memoryOffset + columnWidth * 4;
|
||||
|
||||
renderer->addText("MemoryStats", {memoryOffset, yOffset}, titleInfo);
|
||||
renderer->addText("Total", {totalOffset, yOffset}, titleInfo);
|
||||
renderer->addText("Count", {countOffset, yOffset}, titleInfo);
|
||||
renderer->addText("TotalMax", {totalMaxOffset, yOffset}, titleInfo);
|
||||
rightLines++;
|
||||
|
||||
ccstd::unordered_map<ccstd::string, MemoryCounter> memories;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_memoryStats.mutex);
|
||||
memories = _memoryStats.memories;
|
||||
}
|
||||
|
||||
for (auto &iter : memories) {
|
||||
auto &item = iter.second;
|
||||
yOffset = lineHeight * rightLines;
|
||||
|
||||
renderer->addText(iter.first, {memoryOffset, yOffset}, textInfos[0]);
|
||||
renderer->addText(StatsUtil::formatBytes(item.total), {totalOffset, yOffset}, textInfos[0]);
|
||||
renderer->addText(StringUtil::format("%u", item.count), {countOffset, yOffset}, textInfos[0]);
|
||||
renderer->addText(StatsUtil::formatBytes(item.totalMax), {totalMaxOffset, yOffset}, textInfos[0]);
|
||||
rightLines++;
|
||||
}
|
||||
|
||||
rightLines += 0.5F;
|
||||
}
|
||||
|
||||
if (isEnabled(ShowOption::PERFORMANCE_STATS)) {
|
||||
lines = std::max(leftLines, rightLines);
|
||||
float yOffset = lineHeight * lines;
|
||||
float frameTimeOffset = columnWidth * 4;
|
||||
float frameMaxOffset = columnWidth * 5;
|
||||
float frameCountOffset = columnWidth * 6;
|
||||
float frameAgerageOffset = columnWidth * 7;
|
||||
float totalTimeOffset = columnWidth * 8;
|
||||
float totalMaxOffset = columnWidth * 9;
|
||||
float totalCountOffset = columnWidth * 10;
|
||||
float totalAgerageOffset = columnWidth * 11;
|
||||
|
||||
renderer->addText("PerformanceStats", {leftOffset, yOffset}, titleInfo);
|
||||
renderer->addText("FrameTime", {frameTimeOffset, yOffset}, titleInfo);
|
||||
renderer->addText("Max", {frameMaxOffset, yOffset}, titleInfo);
|
||||
renderer->addText("Count", {frameCountOffset, yOffset}, titleInfo);
|
||||
renderer->addText("Average", {frameAgerageOffset, yOffset}, titleInfo);
|
||||
renderer->addText("WholeTime", {totalTimeOffset, yOffset}, titleInfo);
|
||||
renderer->addText("Max", {totalMaxOffset, yOffset}, titleInfo);
|
||||
renderer->addText("Count", {totalCountOffset, yOffset}, titleInfo);
|
||||
renderer->addText("Average", {totalAgerageOffset, yOffset}, titleInfo);
|
||||
lines++;
|
||||
|
||||
std::vector<ProfilerBlockDepth> blocks;
|
||||
for (auto *child : _root->_children) {
|
||||
gatherBlocks(child, 0U, blocks);
|
||||
}
|
||||
|
||||
uint32_t colorIndex = 0;
|
||||
for (auto &iter : blocks) {
|
||||
yOffset = lineHeight * lines;
|
||||
const auto *block = iter.block;
|
||||
const auto &item = block->_item;
|
||||
|
||||
uint64_t frameAverageDisplay = item.frameCountDisplay ? item.frameTimeDisplay / item.frameCountDisplay : 0U;
|
||||
uint64_t totalAverageDisplay = item.totalCountDisplay ? item.totalTimeDisplay / item.totalCountDisplay : 0U;
|
||||
|
||||
renderer->addText(StatsUtil::formatName(iter.depth, block->_name), {leftOffset, yOffset}, textInfos[colorIndex]);
|
||||
renderer->addText(StatsUtil::formatTime(item.frameTimeDisplay), {frameTimeOffset, yOffset}, textInfos[colorIndex]);
|
||||
renderer->addText(StatsUtil::formatTime(item.frameMaxDisplay), {frameMaxOffset, yOffset}, textInfos[colorIndex]);
|
||||
renderer->addText(StringUtil::format("%u", item.frameCountDisplay), {frameCountOffset, yOffset}, textInfos[colorIndex]);
|
||||
renderer->addText(StatsUtil::formatTime(frameAverageDisplay), {frameAgerageOffset, yOffset}, textInfos[colorIndex]);
|
||||
renderer->addText(StatsUtil::formatTime(item.totalTimeDisplay), {totalTimeOffset, yOffset}, textInfos[colorIndex]);
|
||||
renderer->addText(StatsUtil::formatTime(item.totalMaxDisplay), {totalMaxOffset, yOffset}, textInfos[colorIndex]);
|
||||
renderer->addText(StringUtil::format("%u", item.totalCountDisplay), {totalCountOffset, yOffset}, textInfos[colorIndex]);
|
||||
renderer->addText(StatsUtil::formatTime(totalAverageDisplay), {totalAgerageOffset, yOffset}, textInfos[colorIndex]);
|
||||
colorIndex = (colorIndex + 1) & 0x01;
|
||||
lines++;
|
||||
}
|
||||
|
||||
lines += 0.5F;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Profiler::beginBlock(const std::string_view &name) {
|
||||
if (isMainThread()) {
|
||||
_current = _current->getOrCreateChild(name);
|
||||
_current->begin();
|
||||
}
|
||||
}
|
||||
|
||||
void Profiler::endBlock() {
|
||||
if (isMainThread()) {
|
||||
_current->end();
|
||||
_current = _current->_parent;
|
||||
}
|
||||
}
|
||||
|
||||
void Profiler::gatherBlocks(ProfilerBlock *parent, uint32_t depth, std::vector<ProfilerBlockDepth> &outBlocks) { //NOLINT(misc-no-recursion)
|
||||
outBlocks.push_back({parent, depth});
|
||||
|
||||
for (auto *child : parent->_children) {
|
||||
gatherBlocks(child, depth + 1U, outBlocks);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace cc
|
||||
193
cocos/profiler/Profiler.h
Normal file
193
cocos/profiler/Profiler.h
Normal file
@@ -0,0 +1,193 @@
|
||||
/****************************************************************************
|
||||
Copyright (c) 2021-2023 Xiamen Yaji Software Co., Ltd.
|
||||
|
||||
http://www.cocos.com
|
||||
|
||||
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.
|
||||
****************************************************************************/
|
||||
|
||||
#pragma once
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include "GameStats.h"
|
||||
#include "base/Config.h"
|
||||
#include "base/Timer.h"
|
||||
#include "gfx-base/GFXDef-common.h"
|
||||
|
||||
namespace cc {
|
||||
|
||||
class ProfilerBlock;
|
||||
struct ProfilerBlockDepth;
|
||||
|
||||
enum class ShowOption : uint32_t {
|
||||
CORE_STATS = 0x01,
|
||||
MEMORY_STATS = 0x02,
|
||||
OBJECT_STATS = 0x04,
|
||||
PERFORMANCE_STATS = 0x08,
|
||||
ALL = CORE_STATS | MEMORY_STATS | OBJECT_STATS | PERFORMANCE_STATS,
|
||||
};
|
||||
|
||||
/**
|
||||
* Profiler
|
||||
*/
|
||||
class Profiler {
|
||||
public:
|
||||
static Profiler *getInstance();
|
||||
|
||||
Profiler();
|
||||
~Profiler();
|
||||
Profiler(const Profiler &) = delete;
|
||||
Profiler(Profiler &&) = delete;
|
||||
Profiler &operator=(const Profiler &) = delete;
|
||||
Profiler &operator=(Profiler &&) = delete;
|
||||
|
||||
void setEnable(ShowOption option, bool b = true);
|
||||
bool isEnabled(ShowOption option) const;
|
||||
|
||||
void beginFrame();
|
||||
void endFrame();
|
||||
void update();
|
||||
|
||||
inline bool isMainThread() const { return _mainThreadId == std::this_thread::get_id(); }
|
||||
inline MemoryStats &getMemoryStats() { return _memoryStats; }
|
||||
inline ObjectStats &getObjectStats() { return _objectStats; }
|
||||
|
||||
private:
|
||||
static void doFrameUpdate();
|
||||
|
||||
void doIntervalUpdate();
|
||||
void printStats();
|
||||
|
||||
void beginBlock(const std::string_view &name);
|
||||
void endBlock();
|
||||
void gatherBlocks(ProfilerBlock *parent, uint32_t depth, std::vector<ProfilerBlockDepth> &outBlocks);
|
||||
|
||||
static Profiler *instance;
|
||||
uint32_t _options{static_cast<uint32_t>(ShowOption::ALL)};
|
||||
utils::Timer _timer;
|
||||
CoreStats _coreStats;
|
||||
MemoryStats _memoryStats;
|
||||
ObjectStats _objectStats;
|
||||
ProfilerBlock *_root{nullptr};
|
||||
ProfilerBlock *_current{nullptr};
|
||||
std::thread::id _mainThreadId;
|
||||
|
||||
friend class AutoProfiler;
|
||||
};
|
||||
|
||||
/**
|
||||
* AutoProfiler: profile code block automatically
|
||||
*/
|
||||
class AutoProfiler {
|
||||
public:
|
||||
AutoProfiler(Profiler *profiler, const std::string_view &name)
|
||||
: _profiler(profiler) {
|
||||
_profiler->beginBlock(name);
|
||||
}
|
||||
|
||||
~AutoProfiler() {
|
||||
_profiler->endBlock();
|
||||
}
|
||||
|
||||
private:
|
||||
Profiler *_profiler{nullptr};
|
||||
};
|
||||
|
||||
} // namespace cc
|
||||
|
||||
/**
|
||||
* Profiler is used through macros only, if CC_USE_PROFILER is 0, there is no side effects on performance.
|
||||
*/
|
||||
#if CC_USE_PROFILER
|
||||
#define CC_PROFILER cc::Profiler::getInstance()
|
||||
#define CC_PROFILER_SET_ENABLE(option, b) \
|
||||
if (CC_PROFILER) { \
|
||||
CC_PROFILER->setEnable(option, (b)); \
|
||||
}
|
||||
#define CC_PROFILER_IS_ENABLED(option) \
|
||||
if (CC_PROFILER) { \
|
||||
CC_PROFILER->isEnabled(option); \
|
||||
}
|
||||
#define CC_PROFILER_UPDATE \
|
||||
if (CC_PROFILER) { \
|
||||
CC_PROFILER->update(); \
|
||||
}
|
||||
#define CC_PROFILER_BEGIN_FRAME \
|
||||
if (CC_PROFILER) { \
|
||||
CC_PROFILER->beginFrame(); \
|
||||
}
|
||||
#define CC_PROFILER_END_FRAME \
|
||||
if (CC_PROFILER) { \
|
||||
CC_PROFILER->endFrame(); \
|
||||
}
|
||||
#define CC_PROFILE(name) cc::AutoProfiler auto_profiler_##name(CC_PROFILER, #name)
|
||||
#define CC_PROFILE_MEMORY_UPDATE(name, count) \
|
||||
if (CC_PROFILER) { \
|
||||
CC_PROFILER->getMemoryStats().update(#name, (count)); \
|
||||
}
|
||||
#define CC_PROFILE_MEMORY_INC(name, count) \
|
||||
if (CC_PROFILER) { \
|
||||
CC_PROFILER->getMemoryStats().inc(#name, (count)); \
|
||||
}
|
||||
#define CC_PROFILE_MEMORY_DEC(name, count) \
|
||||
if (CC_PROFILER) { \
|
||||
CC_PROFILER->getMemoryStats().dec(#name, (count)); \
|
||||
}
|
||||
#define CC_PROFILE_RENDER_UPDATE(name, count) \
|
||||
if (CC_PROFILER && CC_PROFILER->isMainThread()) { \
|
||||
CC_PROFILER->getObjectStats().renders[#name] = (count); \
|
||||
}
|
||||
#define CC_PROFILE_RENDER_INC(name, count) \
|
||||
if (CC_PROFILER && CC_PROFILER->isMainThread()) { \
|
||||
CC_PROFILER->getObjectStats().renders[#name] += (count); \
|
||||
}
|
||||
#define CC_PROFILE_RENDER_DEC(name, count) \
|
||||
if (CC_PROFILER && CC_PROFILER->isMainThread()) { \
|
||||
CC_PROFILER->getObjectStats().renders[#name] -= (count); \
|
||||
}
|
||||
#define CC_PROFILE_OBJECT_UPDATE(name, count) \
|
||||
if (CC_PROFILER && CC_PROFILER->isMainThread()) { \
|
||||
CC_PROFILER->getObjectStats().objects[#name] = (count); \
|
||||
}
|
||||
#define CC_PROFILE_OBJECT_INC(name, count) \
|
||||
if (CC_PROFILER && CC_PROFILER->isMainThread()) { \
|
||||
C_PROFILER->getObjectStats().objects[#name] += (count); \
|
||||
}
|
||||
#define CC_PROFILE_OBJECT_DEC(name, count) \
|
||||
if (CC_PROFILER && CC_PROFILER->isMainThread()) { \
|
||||
CC_PROFILER->getObjectStats().objects[#name] -= (count); \
|
||||
}
|
||||
#else
|
||||
#define CC_PROFILER
|
||||
#define CC_PROFILER_SET_ENABLE(option, b)
|
||||
#define CC_PROFILER_IS_ENABLED(option) false
|
||||
#define CC_PROFILER_UPDATE
|
||||
#define CC_PROFILER_BEGIN_FRAME
|
||||
#define CC_PROFILER_END_FRAME
|
||||
#define CC_PROFILE(name)
|
||||
#define CC_PROFILE_MEMORY_UPDATE(name, count)
|
||||
#define CC_PROFILE_MEMORY_INC(name, count)
|
||||
#define CC_PROFILE_MEMORY_DEC(name, count)
|
||||
#define CC_PROFILE_RENDER_UPDATE(name, count)
|
||||
#define CC_PROFILE_RENDER_INC(name, count)
|
||||
#define CC_PROFILE_RENDER_DEC(name, count)
|
||||
#define CC_PROFILE_OBJECT_UPDATE(name, count)
|
||||
#define CC_PROFILE_OBJECT_INC(name, count)
|
||||
#define CC_PROFILE_OBJECT_DEC(name, count)
|
||||
#endif
|
||||
Reference in New Issue
Block a user