1//************************************ bs::framework - Copyright 2018 Marko Pintera **************************************//
2//*********** Licensed under the MIT license. See LICENSE.md for full terms. This notice is not to be removed. ***********//
3#include "Profiling/BsProfilerGPU.h"
4#include "Profiling/BsRenderStats.h"
5#include "RenderAPI/BsTimerQuery.h"
6#include "RenderAPI/BsOcclusionQuery.h"
7#include "Error/BsException.h"
8
9namespace bs
10{
11 const UINT32 ProfilerGPU::MAX_QUEUE_ELEMENTS = 5;
12
13 ProfilerGPU::ProfilerGPU()
14 {
15 mReadyReports = bs_newN<GPUProfilerReport>(MAX_QUEUE_ELEMENTS);
16 }
17
18 ProfilerGPU::~ProfilerGPU()
19 {
20 while (!mUnresolvedFrames.empty())
21 {
22 ProfiledSample& frameSample = mUnresolvedFrames.front();
23
24 freeSample(frameSample);
25 mUnresolvedFrames.pop();
26 }
27
28 bs_deleteN(mReadyReports, MAX_QUEUE_ELEMENTS);
29 }
30
31 void ProfilerGPU::beginFrame()
32 {
33 if (mIsFrameActive)
34 {
35 LOGERR("Cannot begin a frame because another frame is active.");
36 return;
37 }
38
39 mFrameSample = ProfiledSample();
40 mFrameSample.name = "Frame";
41 beginSampleInternal(mFrameSample, true);
42
43 mIsFrameActive = true;
44 }
45
46 void ProfilerGPU::endFrame(bool discard)
47 {
48 if (!mActiveSamples.empty())
49 {
50 LOGERR("Attempting to end a frame while a sample is active.");
51 return;
52 }
53
54 if (!mIsFrameActive)
55 return;
56
57 endSampleInternal(mFrameSample);
58
59 if(!discard)
60 mUnresolvedFrames.push(mFrameSample);
61
62 mIsFrameActive = false;
63 }
64
65 void ProfilerGPU::beginSample(ProfilerString name)
66 {
67 if (!mIsFrameActive)
68 {
69 LOGERR("Cannot begin a sample because no frame is active.");
70 return;
71 }
72
73 auto sample = mSamplePool.construct<ProfiledSample>();
74 sample->name = std::move(name);
75 beginSampleInternal(*sample, false);
76
77 if(mActiveSamples.empty())
78 mFrameSample.children.push_back(sample);
79 else
80 {
81 ProfiledSample* parent = mActiveSamples.top();
82 parent->children.push_back(sample);
83 }
84
85 mActiveSamples.push(sample);
86 }
87
88 void ProfilerGPU::endSample(const ProfilerString& name)
89 {
90 if (mActiveSamples.empty())
91 return;
92
93 ProfiledSample* lastSample = mActiveSamples.top();
94 if (lastSample->name != name)
95 {
96 String errorStr = "Attempting to end a sample that doesn't match. Got: " +
97 String(name.c_str()) + ". Expected: " + String(lastSample->name.c_str());
98
99 LOGERR(errorStr);
100 return;
101 }
102
103 endSampleInternal(*lastSample);
104 mActiveSamples.pop();
105 }
106
107 UINT32 ProfilerGPU::getNumAvailableReports()
108 {
109 Lock lock(mMutex);
110
111 return mReportCount;
112 }
113
114 GPUProfilerReport ProfilerGPU::getNextReport()
115 {
116 Lock lock(mMutex);
117
118 if (mReportCount == 0)
119 {
120 LOGERR("No reports are available.")
121 return GPUProfilerReport();
122 }
123
124 GPUProfilerReport report = mReadyReports[mReportHeadPos];
125
126 mReportHeadPos = (mReportHeadPos + 1) % MAX_QUEUE_ELEMENTS;
127 mReportCount--;
128
129 return report;
130 }
131
132 void ProfilerGPU::_update()
133 {
134 while (!mUnresolvedFrames.empty())
135 {
136 ProfiledSample& frameSample = mUnresolvedFrames.front();
137
138 // Frame sample timer query is the last query we issued
139 // so if it is complete, we may assume all queries are complete.
140 if (frameSample.activeTimeQuery->isReady())
141 {
142 GPUProfilerReport report;
143 resolveSample(frameSample, report.frameSample);
144
145 freeSample(frameSample);
146 mUnresolvedFrames.pop();
147
148 {
149 Lock lock(mMutex);
150 mReadyReports[(mReportHeadPos + mReportCount) % MAX_QUEUE_ELEMENTS] = report;
151 if (mReportCount == MAX_QUEUE_ELEMENTS)
152 mReportHeadPos = (mReportHeadPos + 1) % MAX_QUEUE_ELEMENTS;
153 else
154 mReportCount++;
155 }
156 }
157 else
158 break;
159 }
160 }
161
162 void ProfilerGPU::freeSample(ProfiledSample& sample)
163 {
164 for(auto& entry : sample.children)
165 {
166 freeSample(*entry);
167 mSamplePool.destruct(entry);
168 }
169
170 sample.children.clear();
171
172 mFreeTimerQueries.push(sample.activeTimeQuery);
173
174 if(sample.activeOcclusionQuery)
175 mFreeOcclusionQueries.push(sample.activeOcclusionQuery);
176 }
177
178 void ProfilerGPU::resolveSample(const ProfiledSample& sample, GPUProfileSample& reportSample)
179 {
180 reportSample.name.assign(sample.name.data(), sample.name.size());
181 reportSample.timeMs = sample.activeTimeQuery->getTimeMs();
182
183 if(sample.activeOcclusionQuery)
184 reportSample.numDrawnSamples = sample.activeOcclusionQuery->getNumSamples();
185 else
186 reportSample.numDrawnSamples = 0;
187
188 reportSample.numDrawCalls = (UINT32)(sample.endStats.numDrawCalls - sample.startStats.numDrawCalls);
189 reportSample.numRenderTargetChanges = (UINT32)(sample.endStats.numRenderTargetChanges - sample.startStats.numRenderTargetChanges);
190 reportSample.numPresents = (UINT32)(sample.endStats.numPresents - sample.startStats.numPresents);
191 reportSample.numClears = (UINT32)(sample.endStats.numClears - sample.startStats.numClears);
192
193 reportSample.numVertices = (UINT32)(sample.endStats.numVertices - sample.startStats.numVertices);
194 reportSample.numPrimitives = (UINT32)(sample.endStats.numPrimitives - sample.startStats.numPrimitives);
195
196 reportSample.numPipelineStateChanges = (UINT32)(sample.endStats.numPipelineStateChanges - sample.startStats.numPipelineStateChanges);
197
198 reportSample.numGpuParamBinds = (UINT32)(sample.endStats.numGpuParamBinds - sample.startStats.numGpuParamBinds);
199 reportSample.numVertexBufferBinds = (UINT32)(sample.endStats.numVertexBufferBinds - sample.startStats.numVertexBufferBinds);
200 reportSample.numIndexBufferBinds = (UINT32)(sample.endStats.numIndexBufferBinds - sample.startStats.numIndexBufferBinds);
201
202 reportSample.numResourceWrites = (UINT32)(sample.endStats.numResourceWrites - sample.startStats.numResourceWrites);
203 reportSample.numResourceReads = (UINT32)(sample.endStats.numResourceReads - sample.startStats.numResourceReads);
204
205 reportSample.numObjectsCreated = (UINT32)(sample.endStats.numObjectsCreated - sample.startStats.numObjectsCreated);
206 reportSample.numObjectsDestroyed = (UINT32)(sample.endStats.numObjectsDestroyed - sample.startStats.numObjectsDestroyed);
207
208 for(auto& entry : sample.children)
209 {
210 reportSample.children.push_back(GPUProfileSample());
211 resolveSample(*entry, reportSample.children.back());
212 }
213 }
214
215 void ProfilerGPU::beginSampleInternal(ProfiledSample& sample, bool issueOcclusion)
216 {
217 sample.startStats = RenderStats::instance().getData();
218 sample.activeTimeQuery = getTimerQuery();
219 sample.activeTimeQuery->begin();
220
221 if(issueOcclusion)
222 {
223 sample.activeOcclusionQuery = getOcclusionQuery();
224 sample.activeOcclusionQuery->begin();
225 }
226 }
227
228 void ProfilerGPU::endSampleInternal(ProfiledSample& sample)
229 {
230 sample.endStats = RenderStats::instance().getData();
231
232 if(sample.activeOcclusionQuery)
233 sample.activeOcclusionQuery->end();
234
235 sample.activeTimeQuery->end();
236 }
237
238 SPtr<ct::TimerQuery> ProfilerGPU::getTimerQuery() const
239 {
240 if (!mFreeTimerQueries.empty())
241 {
242 SPtr<ct::TimerQuery> timerQuery = mFreeTimerQueries.top();
243 mFreeTimerQueries.pop();
244
245 return timerQuery;
246 }
247
248 return ct::TimerQuery::create();
249 }
250
251 SPtr<ct::OcclusionQuery> ProfilerGPU::getOcclusionQuery() const
252 {
253 if (!mFreeOcclusionQueries.empty())
254 {
255 SPtr<ct::OcclusionQuery> occlusionQuery = mFreeOcclusionQueries.top();
256 mFreeOcclusionQueries.pop();
257
258 return occlusionQuery;
259 }
260
261 return ct::OcclusionQuery::create(false);
262 }
263
264 ProfilerGPU& gProfilerGPU()
265 {
266 return ProfilerGPU::instance();
267 }
268}