no message

This commit is contained in:
gem
2025-02-18 15:21:31 +08:00
commit 2d133e56d7
1980 changed files with 465595 additions and 0 deletions

View 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

View 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
View 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
View 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
View 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