diff --git a/examples/utilities/tools/render/PlotPerf.qml b/examples/utilities/tools/render/PlotPerf.qml new file mode 100644 index 0000000000..0e100e4e72 --- /dev/null +++ b/examples/utilities/tools/render/PlotPerf.qml @@ -0,0 +1,186 @@ +// +// PlotPerf.qml +// examples/utilities/tools/render +// +// Created by Sam Gateau on 3//2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +Item { + id: root + width: parent.width + height: 100 + property string title + property var config + property string parameters + + // THis is my hack to get the name of the first property and assign it to a trigger var in order to get + // a signal called whenever the value changed + property var trigger: config[parameters.split(":")[3].split("-")[0]] + + property var inputs: parameters.split(":") + property var valueScale: +inputs[0] + property var valueUnit: inputs[1] + property var valueNumDigits: inputs[2] + property var input_VALUE_OFFSET: 3 + property var valueMax : 1 + + property var _values : new Array() + property var tick : 0 + + function createValues() { + if (inputs.length > input_VALUE_OFFSET) { + for (var i = input_VALUE_OFFSET; i < inputs.length; i++) { + var varProps = inputs[i].split("-") + _values.push( { + value: varProps[0], + valueMax: 1, + numSamplesConstantMax: 0, + valueHistory: new Array(), + label: varProps[1], + color: varProps[2], + scale: (varProps.length > 3 ? varProps[3] : 1), + unit: (varProps.length > 4 ? varProps[4] : valueUnit) + }) + } + } + print("in creator" + JSON.stringify(_values)); + + } + + Component.onCompleted: { + createValues(); + print(JSON.stringify(_values)); + + } + + function pullFreshValues() { + //print("pullFreshValues"); + var VALUE_HISTORY_SIZE = 100; + var UPDATE_CANVAS_RATE = 20; + tick++; + + + var currentValueMax = 0 + for (var i = 0; i < _values.length; i++) { + + var currentVal = config[_values[i].value] * _values[i].scale; + _values[i].valueHistory.push(currentVal) + _values[i].numSamplesConstantMax++; + + if (_values[i].valueHistory.length > VALUE_HISTORY_SIZE) { + var lostValue = _values[i].valueHistory.shift(); + if (lostValue >= _values[i].valueMax) { + _values[i].valueMax *= 0.99 + _values[i].numSamplesConstantMax = 0 + } + } + + if (_values[i].valueMax < currentVal) { + _values[i].valueMax = currentVal; + _values[i].numSamplesConstantMax = 0 + } + + if (_values[i].numSamplesConstantMax > VALUE_HISTORY_SIZE) { + _values[i].numSamplesConstantMax = 0 + _values[i].valueMax *= 0.95 // lower slowly the current max if no new above max since a while + } + + if (currentValueMax < _values[i].valueMax) { + currentValueMax = _values[i].valueMax + } + } + + if ((valueMax < currentValueMax) || (tick % VALUE_HISTORY_SIZE == 0)) { + valueMax = currentValueMax; + } + + if (tick % UPDATE_CANVAS_RATE == 0) { + mycanvas.requestPaint() + } + } + onTriggerChanged: pullFreshValues() + + Canvas { + id: mycanvas + anchors.fill:parent + onPaint: { + var lineHeight = 12; + + function displayValue(val, unit) { + return (val / root.valueScale).toFixed(root.valueNumDigits) + " " + unit + } + + function pixelFromVal(val, valScale) { + return lineHeight + (height - lineHeight) * (1 - (0.9) * val / valueMax); + } + function valueFromPixel(pixY) { + return ((pixY - lineHeight) / (height - lineHeight) - 1) * valueMax / (-0.9); + } + function plotValueHistory(ctx, valHistory, color) { + var widthStep= width / (valHistory.length - 1); + + ctx.beginPath(); + ctx.strokeStyle= color; // Green path + ctx.lineWidth="2"; + ctx.moveTo(0, pixelFromVal(valHistory[0])); + + for (var i = 1; i < valHistory.length; i++) { + ctx.lineTo(i * widthStep, pixelFromVal(valHistory[i])); + } + + ctx.stroke(); + } + function displayValueLegend(ctx, val, num) { + ctx.fillStyle = val.color; + var bestValue = val.valueHistory[val.valueHistory.length -1]; + ctx.textAlign = "right"; + ctx.fillText(displayValue(bestValue, val.unit), width, (num + 2) * lineHeight * 1.5); + ctx.textAlign = "left"; + ctx.fillText(val.label, 0, (num + 2) * lineHeight * 1.5); + } + + function displayTitle(ctx, text, maxVal) { + ctx.fillStyle = "grey"; + ctx.textAlign = "right"; + ctx.fillText(displayValue(valueFromPixel(lineHeight), root.valueUnit), width, lineHeight); + + ctx.fillStyle = "white"; + ctx.textAlign = "left"; + ctx.fillText(text, 0, lineHeight); + } + function displayBackground(ctx) { + ctx.fillStyle = Qt.rgba(0, 0, 0, 0.6); + ctx.fillRect(0, 0, width, height); + + ctx.strokeStyle= "grey"; + ctx.lineWidth="2"; + + ctx.beginPath(); + ctx.moveTo(0, lineHeight + 1); + ctx.lineTo(width, lineHeight + 1); + ctx.moveTo(0, height); + ctx.lineTo(width, height); + ctx.stroke(); + } + + var ctx = getContext("2d"); + ctx.clearRect(0, 0, width, height); + ctx.font="12px Verdana"; + + displayBackground(ctx); + + for (var i = 0; i < _values.length; i++) { + plotValueHistory(ctx, _values[i].valueHistory, _values[i].color) + displayValueLegend(ctx, _values[i], i) + } + + displayTitle(ctx, title, valueMax) + } + } +} diff --git a/examples/utilities/tools/render/renderStats.js b/examples/utilities/tools/render/renderStats.js new file mode 100644 index 0000000000..2e8487ca34 --- /dev/null +++ b/examples/utilities/tools/render/renderStats.js @@ -0,0 +1,21 @@ +// +// renderStats.js +// examples/utilities/tools/render +// +// Sam Gateau, created on 3/22/2016. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// Set up the qml ui +var qml = Script.resolvePath('stats.qml'); +var window = new OverlayWindow({ + title: 'Render Stats', + source: qml, + width: 300, + height: 200 +}); +window.setPosition(500, 50); +window.closed.connect(function() { Script.stop(); }); \ No newline at end of file diff --git a/examples/utilities/tools/render/stats.qml b/examples/utilities/tools/render/stats.qml new file mode 100644 index 0000000000..142b5e25e2 --- /dev/null +++ b/examples/utilities/tools/render/stats.qml @@ -0,0 +1,69 @@ +// +// stats.qml +// examples/utilities/tools/render +// +// Created by Zach Pomerantz on 2/8/2016 +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Controls 1.4 + + +Item { + id: statsUI + anchors.fill:parent + + Column { + id: stats + spacing: 8 + anchors.fill:parent + + property var config: Render.getConfig("Stats") + + function evalEvenHeight() { + // Why do we have to do that manually ? cannot seem to find a qml / anchor / layout mode that does that ? + return (height - spacing * (children.length - 1)) / children.length + } + + PlotPerf { + title: "Num Buffers" + config: stats.config + height: parent.evalEvenHeight() + parameters: "1::0:bufferCPUCount-CPU-#00B4EF:bufferGPUCount-GPU-#1AC567" + } + PlotPerf { + title: "gpu::Buffer Memory" + config: stats.config + height: parent.evalEvenHeight() + parameters: "1048576:Mb:1:bufferCPUMemoryUsage-CPU-#00B4EF:bufferGPUMemoryUsage-GPU-#1AC567" + } + + PlotPerf { + title: "Num Textures" + config: stats.config + height: parent.evalEvenHeight() + parameters: "1::0:textureCPUCount-CPU-#00B4EF:textureGPUCount-GPU-#1AC567:frameTextureCount-Frame-#E2334D" + } + PlotPerf { + title: "gpu::Texture Memory" + config: stats.config + height: parent.evalEvenHeight() + parameters: "1048576:Mb:1:textureCPUMemoryUsage-CPU-#00B4EF:textureGPUMemoryUsage-GPU-#1AC567" + } + PlotPerf { + title: "Drawcalls" + config: stats.config + height: parent.evalEvenHeight() + parameters: "1::0:frameDrawcallCount-frame-#E2334D:frameDrawcallRate-rate-#1AC567-0.001-K/s" + } + PlotPerf { + title: "Triangles" + config: stats.config + height: parent.evalEvenHeight() + parameters: "1000:K:0:frameTriangleCount-frame-#E2334D:frameTriangleRate-rate-#1AC567-0.001-MT/s" + } + } +} diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index dd26ab2823..b14c461bc5 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -74,6 +74,11 @@ void Context::downloadFramebuffer(const FramebufferPointer& srcFramebuffer, cons _backend->downloadFramebuffer(srcFramebuffer, region, destImage); } + +void Context::getStats(ContextStats& stats) const { + _backend->getStats(stats); +} + const Backend::TransformCamera& Backend::TransformCamera::recomputeDerived(const Transform& xformView) const { _projectionInverse = glm::inverse(_projection); @@ -102,3 +107,68 @@ Backend::TransformCamera Backend::TransformCamera::getEyeCamera(int eye, const S return result; } + +// Counters for Buffer and Texture usage in GPU/Context +std::atomic Context::_bufferGPUCount{ 0 }; +std::atomic Context::_bufferGPUMemoryUsage{ 0 }; + +std::atomic Context::_textureGPUCount{ 0 }; +std::atomic Context::_textureGPUMemoryUsage{ 0 }; + +void Context::incrementBufferGPUCount() { + _bufferGPUCount++; +} +void Context::decrementBufferGPUCount() { + _bufferGPUCount--; +} +void Context::updateBufferGPUMemoryUsage(Size prevObjectSize, Size newObjectSize) { + if (prevObjectSize == newObjectSize) { + return; + } + if (newObjectSize > prevObjectSize) { + _bufferGPUMemoryUsage.fetch_add(newObjectSize - prevObjectSize); + } else { + _bufferGPUMemoryUsage.fetch_sub(prevObjectSize - newObjectSize); + } +} + +void Context::incrementTextureGPUCount() { + _textureGPUCount++; +} +void Context::decrementTextureGPUCount() { + _textureGPUCount--; +} +void Context::updateTextureGPUMemoryUsage(Size prevObjectSize, Size newObjectSize) { + if (prevObjectSize == newObjectSize) { + return; + } + if (newObjectSize > prevObjectSize) { + _textureGPUMemoryUsage.fetch_add(newObjectSize - prevObjectSize); + } else { + _textureGPUMemoryUsage.fetch_sub(prevObjectSize - newObjectSize); + } +} + +uint32_t Context::getBufferGPUCount() { + return _bufferGPUCount.load(); +} + +Context::Size Context::getBufferGPUMemoryUsage() { + return _bufferGPUMemoryUsage.load(); +} + +uint32_t Context::getTextureGPUCount() { + return _textureGPUCount.load(); +} + +Context::Size Context::getTextureGPUMemoryUsage() { + return _textureGPUMemoryUsage.load(); +} + +void Backend::incrementBufferGPUCount() { Context::incrementBufferGPUCount(); } +void Backend::decrementBufferGPUCount() { Context::decrementBufferGPUCount(); } +void Backend::updateBufferGPUMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize) { Context::updateBufferGPUMemoryUsage(prevObjectSize, newObjectSize); } +void Backend::incrementTextureGPUCount() { Context::incrementTextureGPUCount(); } +void Backend::decrementTextureGPUCount() { Context::decrementTextureGPUCount(); } +void Backend::updateTextureGPUMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize) { Context::updateTextureGPUMemoryUsage(prevObjectSize, newObjectSize); } + diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index d584f54acc..b898bddef9 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -27,6 +27,21 @@ class QImage; namespace gpu { +struct ContextStats { +public: + int _ISNumFormatChanges = 0; + int _ISNumInputBufferChanges = 0; + int _ISNumIndexBufferChanges = 0; + + int _RSNumTextureBounded = 0; + + int _DSNumDrawcalls = 0; + int _DSNumTriangles = 0; + + ContextStats() {} + ContextStats(const ContextStats& stats) = default; +}; + struct StereoState { bool _enable{ false }; bool _skybox{ false }; @@ -100,13 +115,27 @@ public: return reinterpret_cast(object.gpuObject.getGPUObject()); } + void getStats(ContextStats& stats) const { stats = _stats; } + + + + // These should only be accessed by Backend implementation to repport the buffer and texture allocations, + // they are NOT public calls + static void incrementBufferGPUCount(); + static void decrementBufferGPUCount(); + static void updateBufferGPUMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize); + static void incrementTextureGPUCount(); + static void decrementTextureGPUCount(); + static void updateTextureGPUMemoryUsage(Resource::Size prevObjectSize, Resource::Size newObjectSize); protected: StereoState _stereo; + ContextStats _stats; }; class Context { public: + using Size = Resource::Size; typedef Backend* (*CreateBackend)(); typedef bool (*MakeProgram)(Shader& shader, const Shader::BindingSet& bindings); @@ -125,6 +154,7 @@ public: ~Context(); void render(Batch& batch); + void enableStereo(bool enable = true); bool isStereo(); void setStereoProjections(const mat4 eyeProjections[2]); @@ -137,6 +167,16 @@ public: // It s here for convenience to easily capture a snapshot void downloadFramebuffer(const FramebufferPointer& srcFramebuffer, const Vec4i& region, QImage& destImage); + // Repporting stats of the context + void getStats(ContextStats& stats) const; + + + static uint32_t getBufferGPUCount(); + static Size getBufferGPUMemoryUsage(); + + static uint32_t getTextureGPUCount(); + static Size getTextureGPUMemoryUsage(); + protected: Context(const Context& context); @@ -153,6 +193,23 @@ protected: static std::once_flag _initialized; friend class Shader; + + // These should only be accessed by the Backend, they are NOT public calls + static void incrementBufferGPUCount(); + static void decrementBufferGPUCount(); + static void updateBufferGPUMemoryUsage(Size prevObjectSize, Size newObjectSize); + static void incrementTextureGPUCount(); + static void decrementTextureGPUCount(); + static void updateTextureGPUMemoryUsage(Size prevObjectSize, Size newObjectSize); + + // Buffer and Texture Counters + static std::atomic _bufferGPUCount; + static std::atomic _bufferGPUMemoryUsage; + + static std::atomic _textureGPUCount; + static std::atomic _textureGPUMemoryUsage; + + friend class Backend; }; typedef std::shared_ptr ContextPointer; diff --git a/libraries/gpu/src/gpu/GLBackend.cpp b/libraries/gpu/src/gpu/GLBackend.cpp index 2c25255a80..e847ad1a42 100644 --- a/libraries/gpu/src/gpu/GLBackend.cpp +++ b/libraries/gpu/src/gpu/GLBackend.cpp @@ -324,7 +324,10 @@ void GLBackend::do_draw(Batch& batch, size_t paramOffset) { uint32 numVertices = batch._params[paramOffset + 1]._uint; uint32 startVertex = batch._params[paramOffset + 0]._uint; glDrawArrays(mode, startVertex, numVertices); - (void) CHECK_GL_ERROR(); + _stats._DSNumTriangles += numVertices / 3; + _stats._DSNumDrawcalls++; + + (void)CHECK_GL_ERROR(); } void GLBackend::do_drawIndexed(Batch& batch, size_t paramOffset) { @@ -339,6 +342,9 @@ void GLBackend::do_drawIndexed(Batch& batch, size_t paramOffset) { GLvoid* indexBufferByteOffset = reinterpret_cast(startIndex * typeByteSize + _input._indexBufferOffset); glDrawElements(mode, numIndices, glType, indexBufferByteOffset); + _stats._DSNumTriangles += numIndices / 3; + _stats._DSNumDrawcalls++; + (void) CHECK_GL_ERROR(); } @@ -350,6 +356,9 @@ void GLBackend::do_drawInstanced(Batch& batch, size_t paramOffset) { uint32 startVertex = batch._params[paramOffset + 1]._uint; glDrawArraysInstancedARB(mode, startVertex, numVertices, numInstances); + _stats._DSNumTriangles += (numInstances * numVertices) / 3; + _stats._DSNumDrawcalls += numInstances; + (void) CHECK_GL_ERROR(); } @@ -372,6 +381,9 @@ void GLBackend::do_drawIndexedInstanced(Batch& batch, size_t paramOffset) { glDrawElementsInstanced(mode, numIndices, glType, indexBufferByteOffset, numInstances); Q_UNUSED(startInstance); #endif + _stats._DSNumTriangles += (numInstances * numIndices) / 3; + _stats._DSNumDrawcalls += numInstances; + (void)CHECK_GL_ERROR(); } @@ -382,6 +394,7 @@ void GLBackend::do_multiDrawIndirect(Batch& batch, size_t paramOffset) { GLenum mode = _primitiveToGLmode[(Primitive)batch._params[paramOffset + 1]._uint]; glMultiDrawArraysIndirect(mode, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); + _stats._DSNumDrawcalls += commandCount; #else // FIXME implement the slow path #endif @@ -396,6 +409,8 @@ void GLBackend::do_multiDrawIndexedIndirect(Batch& batch, size_t paramOffset) { GLenum indexType = _elementTypeToGLType[_input._indexBufferType]; glMultiDrawElementsIndirect(mode, indexType, reinterpret_cast(_input._indirectBufferOffset), commandCount, (GLsizei)_input._indirectBufferStride); + _stats._DSNumDrawcalls += commandCount; + #else // FIXME implement the slow path #endif diff --git a/libraries/gpu/src/gpu/GLBackend.h b/libraries/gpu/src/gpu/GLBackend.h index 39b54c109b..d4efe7fe99 100644 --- a/libraries/gpu/src/gpu/GLBackend.h +++ b/libraries/gpu/src/gpu/GLBackend.h @@ -67,6 +67,8 @@ public: GLBuffer(); ~GLBuffer(); + + void setSize(GLuint size); }; static GLBuffer* syncGPUObject(const Buffer& buffer); static GLuint getBufferID(const Buffer& buffer); @@ -77,10 +79,15 @@ public: Stamp _contentStamp; GLuint _texture; GLenum _target; - GLuint _size; GLTexture(); ~GLTexture(); + + void setSize(GLuint size); + GLuint size() const { return _size; } + + private: + GLuint _size; }; static GLTexture* syncGPUObject(const Texture& texture); static GLuint getTextureID(const TexturePointer& texture, bool sync = true); @@ -230,26 +237,11 @@ public: void do_setStateBlend(State::BlendFunction blendFunction); void do_setStateColorWriteMask(uint32 mask); - - // Repporting stats of the context - class Stats { - public: - int _ISNumFormatChanges = 0; - int _ISNumInputBufferChanges = 0; - int _ISNumIndexBufferChanges = 0; - - Stats() {} - Stats(const Stats& stats) = default; - }; - - void getStats(Stats& stats) const { stats = _stats; } - + protected: void renderPassTransfer(Batch& batch); void renderPassDraw(Batch& batch); - Stats _stats; - // Draw Stage void do_draw(Batch& batch, size_t paramOffset); void do_drawIndexed(Batch& batch, size_t paramOffset); diff --git a/libraries/gpu/src/gpu/GLBackendBuffer.cpp b/libraries/gpu/src/gpu/GLBackendBuffer.cpp index 49aeeca38e..080d743104 100755 --- a/libraries/gpu/src/gpu/GLBackendBuffer.cpp +++ b/libraries/gpu/src/gpu/GLBackendBuffer.cpp @@ -16,12 +16,21 @@ GLBackend::GLBuffer::GLBuffer() : _stamp(0), _buffer(0), _size(0) -{} +{ + Backend::incrementBufferGPUCount(); +} GLBackend::GLBuffer::~GLBuffer() { if (_buffer != 0) { glDeleteBuffers(1, &_buffer); } + Backend::updateBufferGPUMemoryUsage(_size, 0); + Backend::decrementBufferGPUCount(); +} + +void GLBackend::GLBuffer::setSize(GLuint size) { + Backend::updateBufferGPUMemoryUsage(_size, size); + _size = size; } GLBackend::GLBuffer* GLBackend::syncGPUObject(const Buffer& buffer) { @@ -46,7 +55,7 @@ GLBackend::GLBuffer* GLBackend::syncGPUObject(const Buffer& buffer) { glBufferData(GL_ARRAY_BUFFER, buffer.getSysmem().getSize(), buffer.getSysmem().readData(), GL_DYNAMIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); object->_stamp = buffer.getSysmem().getStamp(); - object->_size = (GLuint)buffer.getSysmem().getSize(); + object->setSize((GLuint)buffer.getSysmem().getSize()); //} (void) CHECK_GL_ERROR(); diff --git a/libraries/gpu/src/gpu/GLBackendPipeline.cpp b/libraries/gpu/src/gpu/GLBackendPipeline.cpp index 8c9647e0f2..046f1ff0e5 100755 --- a/libraries/gpu/src/gpu/GLBackendPipeline.cpp +++ b/libraries/gpu/src/gpu/GLBackendPipeline.cpp @@ -251,6 +251,9 @@ void GLBackend::do_setResourceTexture(Batch& batch, size_t paramOffset) { return; } + // One more True texture bound + _stats._RSNumTextureBounded++; + // Always make sure the GLObject is in sync GLTexture* object = GLBackend::syncGPUObject(*resourceTexture); if (object) { diff --git a/libraries/gpu/src/gpu/GLBackendTexture.cpp b/libraries/gpu/src/gpu/GLBackendTexture.cpp index a70904a4bf..09714b5542 100755 --- a/libraries/gpu/src/gpu/GLBackendTexture.cpp +++ b/libraries/gpu/src/gpu/GLBackendTexture.cpp @@ -19,12 +19,21 @@ GLBackend::GLTexture::GLTexture() : _texture(0), _target(GL_TEXTURE_2D), _size(0) -{} +{ + Backend::incrementTextureGPUCount(); +} GLBackend::GLTexture::~GLTexture() { if (_texture != 0) { glDeleteTextures(1, &_texture); } + Backend::updateTextureGPUMemoryUsage(_size, 0); + Backend::decrementTextureGPUCount(); +} + +void GLBackend::GLTexture::setSize(GLuint size) { + Backend::updateTextureGPUMemoryUsage(_size, size); + _size = size; } class GLTexelFormat { @@ -427,8 +436,8 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) { if (needUpdate) { if (texture.isStoredMipFaceAvailable(0)) { Texture::PixelsPointer mip = texture.accessStoredMipFace(0); - const GLvoid* bytes = mip->_sysmem.read(); - Element srcFormat = mip->_format; + const GLvoid* bytes = mip->readData(); + Element srcFormat = mip->getFormat(); GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), srcFormat); @@ -458,8 +467,8 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) { if (texture.isStoredMipFaceAvailable(0)) { Texture::PixelsPointer mip = texture.accessStoredMipFace(0); - bytes = mip->_sysmem.read(); - srcFormat = mip->_format; + bytes = mip->readData(); + srcFormat = mip->getFormat(); object->_contentStamp = texture.getDataStamp(); } @@ -483,7 +492,7 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) { object->_storageStamp = texture.getStamp(); object->_contentStamp = texture.getDataStamp(); - object->_size = (GLuint)texture.getSize(); + object->setSize((GLuint)texture.getSize()); } glBindTexture(GL_TEXTURE_2D, boundTex); @@ -507,11 +516,11 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) { for (int f = 0; f < NUM_FACES; f++) { if (texture.isStoredMipFaceAvailable(0, f)) { Texture::PixelsPointer mipFace = texture.accessStoredMipFace(0, f); - Element srcFormat = mipFace->_format; + Element srcFormat = mipFace->getFormat(); GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), srcFormat); glTexSubImage2D(FACE_LAYOUT[f], 0, texelFormat.internalFormat, texture.getWidth(), texture.getWidth(), 0, - texelFormat.format, texelFormat.type, (GLvoid*) (mipFace->_sysmem.read())); + texelFormat.format, texelFormat.type, (GLvoid*) (mipFace->readData())); // At this point the mip pixels have been loaded, we can notify texture.notifyMipFaceGPULoaded(0, f); @@ -536,11 +545,11 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) { for (int f = 0; f < NUM_FACES; f++) { if (texture.isStoredMipFaceAvailable(0, f)) { Texture::PixelsPointer mipFace = texture.accessStoredMipFace(0, f); - Element srcFormat = mipFace->_format; + Element srcFormat = mipFace->getFormat(); GLTexelFormat texelFormat = GLTexelFormat::evalGLTexelFormat(texture.getTexelFormat(), srcFormat); glTexImage2D(FACE_LAYOUT[f], 0, texelFormat.internalFormat, texture.getWidth(), texture.getWidth(), 0, - texelFormat.format, texelFormat.type, (GLvoid*) (mipFace->_sysmem.read())); + texelFormat.format, texelFormat.type, (GLvoid*) (mipFace->readData())); // At this point the mip pixels have been loaded, we can notify texture.notifyMipFaceGPULoaded(0, f); @@ -561,7 +570,7 @@ GLBackend::GLTexture* GLBackend::syncGPUObject(const Texture& texture) { object->_storageStamp = texture.getStamp(); object->_contentStamp = texture.getDataStamp(); - object->_size = (GLuint)texture.getSize(); + object->setSize((GLuint)texture.getSize()); } glBindTexture(GL_TEXTURE_CUBE_MAP, boundTex); diff --git a/libraries/gpu/src/gpu/Resource.cpp b/libraries/gpu/src/gpu/Resource.cpp index 197263f392..c793a92b72 100644 --- a/libraries/gpu/src/gpu/Resource.cpp +++ b/libraries/gpu/src/gpu/Resource.cpp @@ -16,6 +16,8 @@ #include #include +#include "Context.h" + using namespace gpu; class AllocationDebugger { @@ -232,19 +234,55 @@ Resource::Size Resource::Sysmem::append(Size size, const Byte* bytes) { return 0; } +std::atomic Buffer::_bufferCPUCount{ 0 }; +std::atomic Buffer::_bufferCPUMemoryUsage{ 0 }; + +void Buffer::updateBufferCPUMemoryUsage(Size prevObjectSize, Size newObjectSize) { + if (prevObjectSize == newObjectSize) { + return; + } + if (prevObjectSize > newObjectSize) { + _bufferCPUMemoryUsage.fetch_sub(prevObjectSize - newObjectSize); + } else { + _bufferCPUMemoryUsage.fetch_add(newObjectSize - prevObjectSize); + } +} + +uint32_t Buffer::getBufferCPUCount() { + return _bufferCPUCount.load(); +} + +Buffer::Size Buffer::getBufferCPUMemoryUsage() { + return _bufferCPUMemoryUsage.load(); +} + +uint32_t Buffer::getBufferGPUCount() { + return Context::getBufferGPUCount(); +} + +Buffer::Size Buffer::getBufferGPUMemoryUsage() { + return Context::getBufferGPUMemoryUsage(); +} + Buffer::Buffer() : Resource(), _sysmem(new Sysmem()) { + _bufferCPUCount++; + } Buffer::Buffer(Size size, const Byte* bytes) : Resource(), _sysmem(new Sysmem(size, bytes)) { + _bufferCPUCount++; + Buffer::updateBufferCPUMemoryUsage(0, _sysmem->getSize()); } Buffer::Buffer(const Buffer& buf) : Resource(), _sysmem(new Sysmem(buf.getSysmem())) { + _bufferCPUCount++; + Buffer::updateBufferCPUMemoryUsage(0, _sysmem->getSize()); } Buffer& Buffer::operator=(const Buffer& buf) { @@ -253,18 +291,27 @@ Buffer& Buffer::operator=(const Buffer& buf) { } Buffer::~Buffer() { + _bufferCPUCount--; + if (_sysmem) { + Buffer::updateBufferCPUMemoryUsage(_sysmem->getSize(), 0); delete _sysmem; _sysmem = NULL; } } Buffer::Size Buffer::resize(Size size) { - return editSysmem().resize(size); + auto prevSize = editSysmem().getSize(); + auto newSize = editSysmem().resize(size); + Buffer::updateBufferCPUMemoryUsage(prevSize, newSize); + return newSize; } Buffer::Size Buffer::setData(Size size, const Byte* data) { - return editSysmem().setData(size, data); + auto prevSize = editSysmem().getSize(); + auto newSize = editSysmem().setData(size, data); + Buffer::updateBufferCPUMemoryUsage(prevSize, newSize); + return newSize; } Buffer::Size Buffer::setSubData(Size offset, Size size, const Byte* data) { @@ -272,6 +319,9 @@ Buffer::Size Buffer::setSubData(Size offset, Size size, const Byte* data) { } Buffer::Size Buffer::append(Size size, const Byte* data) { - return editSysmem().append( size, data); + auto prevSize = editSysmem().getSize(); + auto newSize = editSysmem().append( size, data); + Buffer::updateBufferCPUMemoryUsage(prevSize, newSize); + return newSize; } diff --git a/libraries/gpu/src/gpu/Resource.h b/libraries/gpu/src/gpu/Resource.h index 3517b67203..98ad0a2d28 100644 --- a/libraries/gpu/src/gpu/Resource.h +++ b/libraries/gpu/src/gpu/Resource.h @@ -16,6 +16,7 @@ #include "Format.h" #include +#include #include #ifdef _DEBUG @@ -109,7 +110,15 @@ protected: }; class Buffer : public Resource { + static std::atomic _bufferCPUCount; + static std::atomic _bufferCPUMemoryUsage; + static void updateBufferCPUMemoryUsage(Size prevObjectSize, Size newObjectSize); + public: + static uint32_t getBufferCPUCount(); + static Size getBufferCPUMemoryUsage(); + static uint32_t getBufferGPUCount(); + static Size getBufferGPUMemoryUsage(); Buffer(); Buffer(Size size, const Byte* bytes); diff --git a/libraries/gpu/src/gpu/Texture.cpp b/libraries/gpu/src/gpu/Texture.cpp index 3f2415d240..df93cd76a5 100755 --- a/libraries/gpu/src/gpu/Texture.cpp +++ b/libraries/gpu/src/gpu/Texture.cpp @@ -12,20 +12,77 @@ #include "Texture.h" #include - -#include +#include "GPULogging.h" +#include "Context.h" using namespace gpu; + +std::atomic Texture::_textureCPUCount{ 0 }; +std::atomic Texture::_textureCPUMemoryUsage{ 0 }; + +void Texture::updateTextureCPUMemoryUsage(Size prevObjectSize, Size newObjectSize) { + if (prevObjectSize == newObjectSize) { + return; + } + if (prevObjectSize > newObjectSize) { + _textureCPUMemoryUsage.fetch_sub(prevObjectSize - newObjectSize); + } else { + _textureCPUMemoryUsage.fetch_add(newObjectSize - prevObjectSize); + } +} + +uint32_t Texture::getTextureCPUCount() { + return _textureCPUCount.load(); +} + +Texture::Size Texture::getTextureCPUMemoryUsage() { + return _textureCPUMemoryUsage.load(); +} + +uint32_t Texture::getTextureGPUCount() { + return Context::getTextureGPUCount(); +} + +Texture::Size Texture::getTextureGPUMemoryUsage() { + return Context::getTextureGPUMemoryUsage(); + +} + uint8 Texture::NUM_FACES_PER_TYPE[NUM_TYPES] = {1, 1, 1, 6}; Texture::Pixels::Pixels(const Element& format, Size size, const Byte* bytes) : - _sysmem(size, bytes), _format(format), + _sysmem(size, bytes), _isGPULoaded(false) { + Texture::updateTextureCPUMemoryUsage(0, _sysmem.getSize()); } Texture::Pixels::~Pixels() { + Texture::updateTextureCPUMemoryUsage(_sysmem.getSize(), 0); +} + +Texture::Size Texture::Pixels::resize(Size pSize) { + auto prevSize = _sysmem.getSize(); + auto newSize = _sysmem.resize(pSize); + Texture::updateTextureCPUMemoryUsage(prevSize, newSize); + return newSize; +} + +Texture::Size Texture::Pixels::setData(const Element& format, Size size, const Byte* bytes ) { + _format = format; + auto prevSize = _sysmem.getSize(); + auto newSize = _sysmem.setData(size, bytes); + Texture::updateTextureCPUMemoryUsage(prevSize, newSize); + _isGPULoaded = false; + return newSize; +} + +void Texture::Pixels::notifyGPULoaded() { + _isGPULoaded = true; + auto prevSize = _sysmem.getSize(); + auto newSize = _sysmem.resize(0); + Texture::updateTextureCPUMemoryUsage(prevSize, newSize); } void Texture::Storage::assignTexture(Texture* texture) { @@ -59,15 +116,15 @@ const Texture::PixelsPointer Texture::Storage::getMipFace(uint16 level, uint8 fa void Texture::Storage::notifyMipFaceGPULoaded(uint16 level, uint8 face) const { PixelsPointer mipFace = getMipFace(level, face); - if (mipFace && (_type != TEX_CUBE)) { - mipFace->_isGPULoaded = true; - mipFace->_sysmem.resize(0); + // Free the mips + if (mipFace) { + mipFace->notifyGPULoaded(); } } bool Texture::Storage::isMipAvailable(uint16 level, uint8 face) const { PixelsPointer mipFace = getMipFace(level, face); - return (mipFace && mipFace->_sysmem.getSize()); + return (mipFace && mipFace->getSize()); } bool Texture::Storage::allocateMip(uint16 level) { @@ -103,9 +160,7 @@ bool Texture::Storage::assignMipData(uint16 level, const Element& format, Size s auto faceBytes = bytes; Size allocated = 0; for (auto& face : mip) { - face->_format = format; - allocated += face->_sysmem.setData(sizePerFace, faceBytes); - face->_isGPULoaded = false; + allocated += face->setData(format, sizePerFace, faceBytes); faceBytes += sizePerFace; } @@ -122,9 +177,7 @@ bool Texture::Storage::assignMipFaceData(uint16 level, const Element& format, Si Size allocated = 0; if (face < mip.size()) { auto mipFace = mip[face]; - mipFace->_format = format; - allocated += mipFace->_sysmem.setData(size, bytes); - mipFace->_isGPULoaded = false; + allocated += mipFace->setData(format, size, bytes); bumpStamp(); } @@ -171,10 +224,12 @@ Texture* Texture::createFromStorage(Storage* storage) { Texture::Texture(): Resource() { + _textureCPUCount++; } Texture::~Texture() { + _textureCPUCount--; } Texture::Size Texture::resize(Type type, const Element& texelFormat, uint16 width, uint16 height, uint16 depth, uint16 numSamples, uint16 numSlices) { @@ -292,7 +347,7 @@ bool Texture::assignStoredMip(uint16 level, const Element& format, Size size, co } } - // THen check that the mem buffer passed make sense with its format + // THen check that the mem texture passed make sense with its format Size expectedSize = evalStoredMipSize(level, format); if (size == expectedSize) { _storage->assignMipData(level, format, size, bytes); @@ -323,7 +378,7 @@ bool Texture::assignStoredMipFace(uint16 level, const Element& format, Size size } } - // THen check that the mem buffer passed make sense with its format + // THen check that the mem texture passed make sense with its format Size expectedSize = evalStoredMipFaceSize(level, format); if (size == expectedSize) { _storage->assignMipFaceData(level, format, size, bytes, face); @@ -364,7 +419,7 @@ uint16 Texture::autoGenerateMips(uint16 maxMip) { uint16 Texture::getStoredMipWidth(uint16 level) const { PixelsPointer mipFace = accessStoredMipFace(level); - if (mipFace && mipFace->_sysmem.getSize()) { + if (mipFace && mipFace->getSize()) { return evalMipWidth(level); } return 0; @@ -372,7 +427,7 @@ uint16 Texture::getStoredMipWidth(uint16 level) const { uint16 Texture::getStoredMipHeight(uint16 level) const { PixelsPointer mip = accessStoredMipFace(level); - if (mip && mip->_sysmem.getSize()) { + if (mip && mip->getSize()) { return evalMipHeight(level); } return 0; @@ -380,7 +435,7 @@ uint16 Texture::getStoredMipHeight(uint16 level) const { uint16 Texture::getStoredMipDepth(uint16 level) const { PixelsPointer mipFace = accessStoredMipFace(level); - if (mipFace && mipFace->_sysmem.getSize()) { + if (mipFace && mipFace->getSize()) { return evalMipDepth(level); } return 0; @@ -388,7 +443,7 @@ uint16 Texture::getStoredMipDepth(uint16 level) const { uint32 Texture::getStoredMipNumTexels(uint16 level) const { PixelsPointer mipFace = accessStoredMipFace(level); - if (mipFace && mipFace->_sysmem.getSize()) { + if (mipFace && mipFace->getSize()) { return evalMipWidth(level) * evalMipHeight(level) * evalMipDepth(level); } return 0; @@ -396,7 +451,7 @@ uint32 Texture::getStoredMipNumTexels(uint16 level) const { uint32 Texture::getStoredMipSize(uint16 level) const { PixelsPointer mipFace = accessStoredMipFace(level); - if (mipFace && mipFace->_sysmem.getSize()) { + if (mipFace && mipFace->getSize()) { return evalMipWidth(level) * evalMipHeight(level) * evalMipDepth(level) * getTexelFormat().getSize(); } return 0; @@ -642,8 +697,8 @@ bool sphericalHarmonicsFromTexture(const gpu::Texture& cubeTexture, std::vector< // for each face of cube texture for(int face=0; face < gpu::Texture::NUM_CUBE_FACES; face++) { - auto numComponents = cubeTexture.accessStoredMipFace(0,face)->_format.getScalarCount(); - auto data = cubeTexture.accessStoredMipFace(0,face)->_sysmem.readData(); + auto numComponents = cubeTexture.accessStoredMipFace(0,face)->getFormat().getScalarCount(); + auto data = cubeTexture.accessStoredMipFace(0,face)->readData(); if (data == nullptr) { continue; } diff --git a/libraries/gpu/src/gpu/Texture.h b/libraries/gpu/src/gpu/Texture.h index e05dc84c25..80fbc867e3 100755 --- a/libraries/gpu/src/gpu/Texture.h +++ b/libraries/gpu/src/gpu/Texture.h @@ -138,7 +138,14 @@ protected: }; class Texture : public Resource { + static std::atomic _textureCPUCount; + static std::atomic _textureCPUMemoryUsage; + static void updateTextureCPUMemoryUsage(Size prevObjectSize, Size newObjectSize); public: + static uint32_t getTextureCPUCount(); + static Size getTextureCPUMemoryUsage(); + static uint32_t getTextureGPUCount(); + static Size getTextureGPUMemoryUsage(); class Usage { public: @@ -194,9 +201,21 @@ public: Pixels(const Element& format, Size size, const Byte* bytes); ~Pixels(); - Sysmem _sysmem; + const Byte* readData() const { return _sysmem.readData(); } + Size getSize() const { return _sysmem.getSize(); } + Size resize(Size pSize); + Size setData(const Element& format, Size size, const Byte* bytes ); + + const Element& getFormat() const { return _format; } + + void notifyGPULoaded(); + + protected: Element _format; + Sysmem _sysmem; bool _isGPULoaded; + + friend class Texture; }; typedef std::shared_ptr< Pixels > PixelsPointer; @@ -448,7 +467,7 @@ typedef std::shared_ptr TexturePointer; typedef std::vector< TexturePointer > Textures; - // TODO: For now TextureView works with Buffer as a place holder for the Texture. + // TODO: For now TextureView works with Texture as a place holder for the Texture. // The overall logic should be about the same except that the Texture will be a real GL Texture under the hood class TextureView { public: diff --git a/libraries/render/src/render/Engine.cpp b/libraries/render/src/render/Engine.cpp index 806c964ec0..b0329faa3e 100644 --- a/libraries/render/src/render/Engine.cpp +++ b/libraries/render/src/render/Engine.cpp @@ -17,12 +17,14 @@ #include +#include "EngineStats.h" using namespace render; Engine::Engine() : _sceneContext(std::make_shared()), _renderContext(std::make_shared()) { + addJob("Stats"); } void Engine::load() { @@ -57,4 +59,6 @@ void Engine::run() { for (auto job : _jobs) { job.run(_sceneContext, _renderContext); } + } + diff --git a/libraries/render/src/render/Engine.h b/libraries/render/src/render/Engine.h index 1af0e6d76f..d2bb42e5ff 100644 --- a/libraries/render/src/render/Engine.h +++ b/libraries/render/src/render/Engine.h @@ -16,37 +16,37 @@ #include "Context.h" #include "Task.h" - namespace render { -// The render engine holds all render tasks, and is itself a render task. -// State flows through tasks to jobs via the render and scene contexts - -// the engine should not be known from its jobs. -class Engine : public Task { -public: - Engine(); - ~Engine() = default; + // The render engine holds all render tasks, and is itself a render task. + // State flows through tasks to jobs via the render and scene contexts - + // the engine should not be known from its jobs. + class Engine : public Task { + public: - // Load any persisted settings, and set up the presets - // This should be run after adding all jobs, and before building ui - void load(); + Engine(); + ~Engine() = default; - // Register the scene - void registerScene(const ScenePointer& scene) { _sceneContext->_scene = scene; } + // Load any persisted settings, and set up the presets + // This should be run after adding all jobs, and before building ui + void load(); - // Push a RenderContext - void setRenderContext(const RenderContext& renderContext) { (*_renderContext) = renderContext; } - RenderContextPointer getRenderContext() const { return _renderContext; } + // Register the scene + void registerScene(const ScenePointer& scene) { _sceneContext->_scene = scene; } - // Render a frame - // A frame must have a scene registered and a context set to render - void run(); + // Push a RenderContext + void setRenderContext(const RenderContext& renderContext) { (*_renderContext) = renderContext; } + RenderContextPointer getRenderContext() const { return _renderContext; } -protected: - SceneContextPointer _sceneContext; - RenderContextPointer _renderContext; -}; -using EnginePointer = std::shared_ptr; + // Render a frame + // A frame must have a scene registered and a context set to render + void run(); + + protected: + SceneContextPointer _sceneContext; + RenderContextPointer _renderContext; + }; + using EnginePointer = std::shared_ptr; } diff --git a/libraries/render/src/render/EngineStats.cpp b/libraries/render/src/render/EngineStats.cpp new file mode 100644 index 0000000000..ee96e0cd86 --- /dev/null +++ b/libraries/render/src/render/EngineStats.cpp @@ -0,0 +1,49 @@ +// +// EngineStats.cpp +// render/src/render +// +// Created by Sam Gateau on 3/27/16. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#include "EngineStats.h" + +#include + +using namespace render; + +void EngineStats::run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { + // Tick time + + quint64 msecsElapsed = _frameTimer.restart(); + double frequency = 1000.0 / msecsElapsed; + + // Update the stats + auto config = std::static_pointer_cast(renderContext->jobConfig); + + config->bufferCPUCount = gpu::Buffer::getBufferCPUCount(); + config->bufferGPUCount = gpu::Buffer::getBufferGPUCount(); + config->bufferCPUMemoryUsage = gpu::Buffer::getBufferCPUMemoryUsage(); + config->bufferGPUMemoryUsage = gpu::Buffer::getBufferGPUMemoryUsage(); + + config->textureCPUCount = gpu::Texture::getTextureCPUCount(); + config->textureGPUCount = gpu::Texture::getTextureGPUCount(); + config->textureCPUMemoryUsage = gpu::Texture::getTextureCPUMemoryUsage(); + config->textureGPUMemoryUsage = gpu::Texture::getTextureGPUMemoryUsage(); + + gpu::ContextStats gpuStats(_gpuStats); + renderContext->args->_context->getStats(_gpuStats); + + config->frameDrawcallCount = _gpuStats._DSNumDrawcalls - gpuStats._DSNumDrawcalls; + config->frameDrawcallRate = config->frameDrawcallCount * frequency; + + config->frameTriangleCount = _gpuStats._DSNumTriangles - gpuStats._DSNumTriangles; + config->frameTriangleRate = config->frameTriangleCount * frequency; + + config->frameTextureCount = _gpuStats._RSNumTextureBounded - gpuStats._RSNumTextureBounded; + config->frameTextureRate = config->frameTextureCount * frequency; + + config->emitDirty(); +} diff --git a/libraries/render/src/render/EngineStats.h b/libraries/render/src/render/EngineStats.h new file mode 100644 index 0000000000..478d94855e --- /dev/null +++ b/libraries/render/src/render/EngineStats.h @@ -0,0 +1,89 @@ +// +// EngineStats.h +// render/src/render +// +// Created by Sam Gateau on 3/27/16. +// Copyright 2016 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_render_EngineStats_h +#define hifi_render_EngineStats_h + +#include + +#include + +#include "Engine.h" + +namespace render { + + // A simple job collecting global stats on the Engine / Scene / GPU + class EngineStatsConfig : public Job::Config{ + Q_OBJECT + + Q_PROPERTY(quint32 bufferCPUCount MEMBER bufferCPUCount NOTIFY dirty) + Q_PROPERTY(quint32 bufferGPUCount MEMBER bufferGPUCount NOTIFY dirty) + Q_PROPERTY(qint64 bufferCPUMemoryUsage MEMBER bufferCPUMemoryUsage NOTIFY dirty) + Q_PROPERTY(qint64 bufferGPUMemoryUsage MEMBER bufferGPUMemoryUsage NOTIFY dirty) + + Q_PROPERTY(quint32 textureCPUCount MEMBER textureCPUCount NOTIFY dirty) + Q_PROPERTY(quint32 textureGPUCount MEMBER textureGPUCount NOTIFY dirty) + Q_PROPERTY(qint64 textureCPUMemoryUsage MEMBER textureCPUMemoryUsage NOTIFY dirty) + Q_PROPERTY(qint64 textureGPUMemoryUsage MEMBER textureGPUMemoryUsage NOTIFY dirty) + + Q_PROPERTY(quint32 frameDrawcallCount MEMBER frameDrawcallCount NOTIFY dirty) + Q_PROPERTY(quint32 frameDrawcallRate MEMBER frameDrawcallRate NOTIFY dirty) + + Q_PROPERTY(quint32 frameTriangleCount MEMBER frameTriangleCount NOTIFY dirty) + Q_PROPERTY(quint32 frameTriangleRate MEMBER frameTriangleRate NOTIFY dirty) + + Q_PROPERTY(quint32 frameTextureCount MEMBER frameTextureCount NOTIFY dirty) + Q_PROPERTY(quint32 frameTextureRate MEMBER frameTextureRate NOTIFY dirty) + + + public: + EngineStatsConfig() : Job::Config(true) {} + + quint32 bufferCPUCount{ 0 }; + quint32 bufferGPUCount{ 0 }; + qint64 bufferCPUMemoryUsage{ 0 }; + qint64 bufferGPUMemoryUsage{ 0 }; + + quint32 textureCPUCount{ 0 }; + quint32 textureGPUCount{ 0 }; + qint64 textureCPUMemoryUsage{ 0 }; + qint64 textureGPUMemoryUsage{ 0 }; + + quint32 frameDrawcallCount{ 0 }; + quint32 frameDrawcallRate{ 0 }; + + quint32 frameTriangleCount{ 0 }; + quint32 frameTriangleRate{ 0 }; + + quint32 frameTextureCount{ 0 }; + quint32 frameTextureRate{ 0 }; + + void emitDirty() { emit dirty(); } + + signals: + void dirty(); + }; + + class EngineStats { + gpu::ContextStats _gpuStats; + QElapsedTimer _frameTimer; + public: + using Config = EngineStatsConfig; + using JobModel = Job::Model; + + EngineStats() { _frameTimer.start(); } + + void configure(const Config& configuration) {} + void run(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext); + }; +} + +#endif \ No newline at end of file