封装Profile:
Sandbox2D.h:ProfileResult结构体和ProfileResult容器,存储相应的信息
#pragma once
#include "YOTO.h"
class Sandbox2D :public YOTO::Layer
{public:
Sandbox2D();
virtual ~Sandbox2D() = default;
virtual void OnAttach()override;
virtual void OnDetach()override;
void OnUpdate(YOTO::Timestep ts)override;
virtual void OnImGuiRender() override;
void OnEvent(YOTO::Event& e)override;
private:
YOTO::OrthographicCameraController m_CameraController;
YOTO::Ref<YOTO::Shader> m_FlatColorShader;
YOTO::Ref<YOTO::VertexArray> m_SquareVA;
YOTO::Ref<YOTO::Texture2D>m_CheckerboardTexture;
struct ProfileResult {
const char* Name;
float Time;
};
std::vector<ProfileResult>m_ProfileResults;
glm::vec4 m_SquareColor = { 0.2f,0.3f,0.7f,1.0f };
};
Sandbox2D.cpp:实现timer并定义PROFILE_SCOPE使用(YT_PROFILE_SCOPE为网页可视化的内容,先放到这了)
#include "Sandbox2D.h"
#include <imgui/imgui.h>
#include <glm/gtc/matrix_transform.hpp>
//#include <Platform/OpenGL/OpenGLShader.h>
#include <glm/gtc/type_ptr.hpp>
#include<vector>
#include<chrono>
template<typename Fn>
class Timer {
public:
Timer(const char* name, Fn&&func)
:m_Name(name),m_Func(func),m_Stopped(false)
{
m_StartTimepoint = std::chrono::high_resolution_clock::now();
}
~Timer() {
if (!m_Stopped) {
Stop();
}
}
void Stop() {
auto endTimepoint= std::chrono::high_resolution_clock::now();
long long start = std::chrono::time_point_cast<std::chrono::microseconds>(m_StartTimepoint).time_since_epoch().count();
long long end = std::chrono::time_point_cast<std::chrono::microseconds>(endTimepoint).time_since_epoch().count();
m_Stopped = true;
float duration = (end - start)*0.001f;
m_Func({m_Name,duration});
//std::cout << "Timer:"<< m_Name << "时差:" << duration << "ms" << std::endl;
}
private:
const char* m_Name;
std::chrono::time_point<std::chrono::steady_clock>m_StartTimepoint;
bool m_Stopped;
Fn m_Func;
};
//未找到匹配的重载:auto的问题,改回原来的类型就好了
#define PROFILE_SCOPE(name) Timer timer##__LINE__(name,[&](ProfileResult profileResult) {m_ProfileResults.push_back(profileResult);})
Sandbox2D::Sandbox2D()
:Layer("Sandbox2D"), m_CameraController(1280.0f / 720.0f, true)
{
}
void Sandbox2D::OnAttach()
{
m_CheckerboardTexture = YOTO::Texture2D::Create("assets/textures/Checkerboard.png");
}
void Sandbox2D::OnDetach()
{
}
void Sandbox2D::OnUpdate(YOTO::Timestep ts)
{
YT_PROFILE_FUNCTION();
PROFILE_SCOPE("Sandbox2D::OnUpdate");
{
YT_PROFILE_SCOPE("CameraController::OnUpdate");
PROFILE_SCOPE("CameraController::OnUpdate");
//update
m_CameraController.OnUpdate(ts);
}
{
YT_PROFILE_SCOPE("Renderer Prep");
PROFILE_SCOPE("Renderer Prep");
//Render
YOTO::RenderCommand::SetClearColor({ 0.2f, 0.2f, 0.2f, 1.0f });
YOTO::RenderCommand::Clear();
}
{
YT_PROFILE_SCOPE("Renderer Draw");
PROFILE_SCOPE("Renderer Draw");
YOTO::Renderer2D::BeginScene(m_CameraController.GetCamera());
{
static glm::mat4 scale = glm::scale(glm::mat4(1.0f), glm::vec3(0.1f));
glm::vec4 redColor(0.8f, 0.3f, 0.3f, 1.0f);
glm::vec4 blueColor(0.2f, 0.3f, 0.8f, 1.0f);
/*std::dynamic_pointer_cast<YOTO::OpenGLShader>(m_FlatColorShader)->Bind();
std::dynamic_pointer_cast<YOTO::OpenGLShader>(m_FlatColorShader)->UploadUniformFloat4("u_Color", m_SquareColor);
YOTO::Renderer::Submit(m_FlatColorShader, m_SquareVA, glm::scale(glm::mat4(1.0f), glm::vec3(1.5f)));*/
YOTO::Renderer2D::DrawQuad({ -1.0f,0.0f }, { 0.8f,0.8f }, { 0.8f,0.2f,0.3f,1.0f });
YOTO::Renderer2D::DrawQuad({ 0.5f,-0.5f }, { 0.5f,0.75f }, { 0.2f,0.3f,0.8f,1.0f });
YOTO::Renderer2D::DrawQuad({ 0.0f,0.0f,-0.1f }, { 10.0f,10.0f }, m_CheckerboardTexture);
YOTO::Renderer2D::EndScene();
}
}
}
void Sandbox2D::OnImGuiRender()
{
ImGui::Begin("Setting");
ImGui::ColorEdit4("Color", glm::value_ptr(m_SquareColor));
for (auto& res : m_ProfileResults) {
char lable[50];
strcpy(lable, "%.3fms ");
strcat(lable, res.Name);
ImGui::Text(lable, res.Time);
}
m_ProfileResults.clear();
ImGui::End();
}
void Sandbox2D::OnEvent(YOTO::Event& e)
{
m_CameraController.OnEvent(e);
}
测试:
Profile网页可视化:
创建.h文件:
instrumentor.h:直接粘贴全部,实现跟封装的profile类似,但是多了生成json文件的代码
#pragma once
#include "YOTO/Core/Log.h"
#include <algorithm>
#include <chrono>
#include <fstream>
#include <iomanip>
#include <string>
#include <thread>
#include <mutex>
#include <sstream>
namespace YOTO {
using FloatingPointMicroseconds = std::chrono::duration<double, std::micro>;
struct ProfileResult
{
std::string Name;
FloatingPointMicroseconds Start;
std::chrono::microseconds ElapsedTime;
std::thread::id ThreadID;
};
struct InstrumentationSession
{
std::string Name;
};
class Instrumentor
{
public:
Instrumentor(const Instrumentor&) = delete;
Instrumentor(Instrumentor&&) = delete;
void BeginSession(const std::string& name, const std::string& filepath = "results.json")
{
std::lock_guard lock(m_Mutex);
if (m_CurrentSession)
{
// If there is already a current session, then close it before beginning new one.
// Subsequent profiling output meant for the original session will end up in the
// newly opened session instead. That's better than having badly formatted
// profiling output.
if (YOTO::Log::GetCoreLogger()) // Edge case: BeginSession() might be before Log::Init()
{
YT_CORE_ERROR("Instrumentor::BeginSession('{0}') when session '{1}' already open.", name, m_CurrentSession->Name);
}
InternalEndSession();
}
m_OutputStream.open(filepath);
if (m_OutputStream.is_open())
{
m_CurrentSession = new InstrumentationSession({ name });
WriteHeader();
}
else
{
if (YOTO::Log::GetCoreLogger()) // Edge case: BeginSession() might be before Log::Init()
{
YT_CORE_ERROR("Instrumentor could not open results file '{0}'.", filepath);
}
}
}
void EndSession()
{
std::lock_guard lock(m_Mutex);
InternalEndSession();
}
void WriteProfile(const ProfileResult& result)
{
std::stringstream json;
json << std::setprecision(3) << std::fixed;
json << ",{";
json << "\"cat\":\"function\",";
json << "\"dur\":" << (result.ElapsedTime.count()) << ',';
json << "\"name\":\"" << result.Name << "\",";
json << "\"ph\":\"X\",";
json << "\"pid\":0,";
json << "\"tid\":" << result.ThreadID << ",";
json << "\"ts\":" << result.Start.count();
json << "}";
std::lock_guard lock(m_Mutex);
if (m_CurrentSession)
{
m_OutputStream << json.str();
m_OutputStream.flush();
}
}
static Instrumentor& Get()
{
static Instrumentor instance;
return instance;
}
private:
Instrumentor()
: m_CurrentSession(nullptr)
{
}
~Instrumentor()
{
EndSession();
}
void WriteHeader()
{
m_OutputStream << "{\"otherData\": {},\"traceEvents\":[{}";
m_OutputStream.flush();
}
void WriteFooter()
{
m_OutputStream << "]}";
m_OutputStream.flush();
}
// Note: you must already own lock on m_Mutex before
// calling InternalEndSession()
void InternalEndSession()
{
if (m_CurrentSession)
{
WriteFooter();
m_OutputStream.close();
delete m_CurrentSession;
m_CurrentSession = nullptr;
}
}
private:
std::mutex m_Mutex;
InstrumentationSession* m_CurrentSession;
std::ofstream m_OutputStream;
};
class InstrumentationTimer
{
public:
InstrumentationTimer(const char* name)
: m_Name(name), m_Stopped(false)
{
m_StartTimepoint = std::chrono::steady_clock::now();
}
~InstrumentationTimer()
{
if (!m_Stopped)
Stop();
}
void Stop()
{
auto endTimepoint = std::chrono::steady_clock::now();
auto highResStart = FloatingPointMicroseconds{ m_StartTimepoint.time_since_epoch() };
auto elapsedTime = std::chrono::time_point_cast<std::chrono::microseconds>(endTimepoint).time_since_epoch() - std::chrono::time_point_cast<std::chrono::microseconds>(m_StartTimepoint).time_since_epoch();
Instrumentor::Get().WriteProfile({ m_Name, highResStart, elapsedTime, std::this_thread::get_id() });
m_Stopped = true;
}
private:
const char* m_Name;
std::chrono::time_point<std::chrono::steady_clock> m_StartTimepoint;
bool m_Stopped;
};
namespace InstrumentorUtils {
template <size_t N>
struct ChangeResult
{
char Data[N];
};
template <size_t N, size_t K>
constexpr auto CleanupOutputString(const char(&expr)[N], const char(&remove)[K])
{
ChangeResult<N> result = {};
size_t srcIndex = 0;
size_t dstIndex = 0;
while (srcIndex < N)
{
size_t matchIndex = 0;
while (matchIndex < K - 1 && srcIndex + matchIndex < N - 1 && expr[srcIndex + matchIndex] == remove[matchIndex])
matchIndex++;
if (matchIndex == K - 1)
srcIndex += matchIndex;
result.Data[dstIndex++] = expr[srcIndex] == '"' ? '\'' : expr[srcIndex];
srcIndex++;
}
return result;
}
}
}
#define YT_PROFILE 0
#if YT_PROFILE
// Resolve which function signature macro will be used. Note that this only
// is resolved when the (pre)compiler starts, so the syntax highlighting
// could mark the wrong one in your editor!
#if defined(__GNUC__) || (defined(__MWERKS__) && (__MWERKS__ >= 0x3000)) || (defined(__ICC) && (__ICC >= 600)) || defined(__ghs__)
#define YT_FUNC_SIG __PRETTY_FUNCTION__
#elif defined(__DMC__) && (__DMC__ >= 0x810)
#define YT_FUNC_SIG __PRETTY_FUNCTION__
#elif (defined(__FUNCSIG__) || (_MSC_VER))
#define YT_FUNC_SIG __FUNCSIG__
#elif (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 600)) || (defined(__IBMCPP__) && (__IBMCPP__ >= 500))
#define YT_FUNC_SIG __FUNCTION__
#elif defined(__BORLANDC__) && (__BORLANDC__ >= 0x550)
#define YT_FUNC_SIG __FUNC__
#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901)
#define YT_FUNC_SIG __func__
#elif defined(__cplusplus) && (__cplusplus >= 201103)
#define YT_FUNC_SIG __func__
#else
#define YT_FUNC_SIG "YT_FUNC_SIG unknown!"
#endif
#define YT_PROFILE_BEGIN_SESSION(name, filepath) ::YOTO::Instrumentor::Get().BeginSession(name, filepath)
#define YT_PROFILE_END_SESSION() ::YOTO::Instrumentor::Get().EndSession()
#define YT_PROFILE_SCOPE_LINE2(name, line) constexpr auto fixedName##line = ::YOTO::InstrumentorUtils::CleanupOutputString(name, "__cdecl ");\
::YOTO::InstrumentationTimer timer##line(fixedName##line.Data)
#define YT_PROFILE_SCOPE_LINE(name, line) YT_PROFILE_SCOPE_LINE2(name, line)
#define YT_PROFILE_SCOPE(name) YT_PROFILE_SCOPE_LINE(name, __LINE__)
#define YT_PROFILE_FUNCTION() YT_PROFILE_SCOPE(YT_FUNC_SIG)
#else
#define YT_PROFILE_BEGIN_SESSION(name, filepath)
#define YT_PROFILE_END_SESSION()
#define YT_PROFILE_SCOPE(name)
#define YT_PROFILE_FUNCTION()
#endif
EntryPoint.h:使用定义
#pragma once
#ifdef YT_PLATFORM_WINDOWS
#include "YOTO.h"
void main(int argc,char** argv) {
//初始化日志
YOTO::Log::Init();
//YT_CORE_ERROR("EntryPoint测试警告信息");
//int test = 1;
//YT_CLIENT_INFO("EntryPoint测试info:test={0}",test);
YT_PROFILE_BEGIN_SESSION("Start","YOTOProfile-Startup.json");
auto app = YOTO::CreateApplication();
YT_PROFILE_END_SESSION();
YT_PROFILE_BEGIN_SESSION("Runtime", "YOTOProfile-Runtime.json");
app->Run();
YT_PROFILE_END_SESSION();
YT_PROFILE_BEGIN_SESSION("Shutdown", "YOTOProfile-Shutdown.json");
delete app;
YT_PROFILE_END_SESSION();
}
#endif
ytpch.h:
#pragma once
#include<iostream>
#include<memory>
#include<utility>
#include<algorithm>
#include<functional>
#include<string>
#include<vector>
#include<unordered_map>
#include<unordered_set>
#include<sstream>
#include<array>
#include "YOTO/Core/Log.h"
#include "YOTO/Debug/instrumentor.h"
#ifdef YT_PLATFORM_WINDOWS
#include<Windows.h>
#endif // YT_PLATFORM_WINDOWS
测试:
在谷歌浏览器输入:chrome://tracing
拖入json文件:
cool,虽然看不太懂,但是文件有够大(运行了几秒就2000多k,平时使用还是用自己写的封装的叭)