// // Created by Bradley Austin Davis on 2016/07/01 // Copyright 2014 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "GLIHelpers.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern QThread* RENDER_THREAD; static const QString DATA_SET = "https://hifi-content.s3.amazonaws.com/austin/textures.zip"; static QDir DATA_DIR = QDir(QString("h:/textures")); static QTemporaryDir* DOWNLOAD_DIR = nullptr; class FileDownloader : public QObject { Q_OBJECT public: using Handler = std::function; FileDownloader(QUrl url, const Handler& handler, QObject *parent = 0) : QObject(parent), _handler(handler) { connect(&_accessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(fileDownloaded(QNetworkReply*))); _accessManager.get(QNetworkRequest(url)); } void waitForDownload() { while (!_complete) { QCoreApplication::processEvents(); } } private slots: void fileDownloaded(QNetworkReply* pReply) { _handler(pReply->readAll()); pReply->deleteLater(); _complete = true; } private: QNetworkAccessManager _accessManager; Handler _handler; bool _complete { false }; }; class RenderThread : public GenericThread { using Parent = GenericThread; public: gl::Context _context; gpu::PipelinePointer _presentPipeline; gpu::ContextPointer _gpuContext; // initialized during window creation std::atomic _presentCount; QElapsedTimer _elapsed; std::atomic _fps{ 1 }; RateCounter<200> _fpsCounter; std::mutex _mutex; std::shared_ptr _backend; std::vector _frameTimes; size_t _frameIndex; std::mutex _frameLock; std::queue _pendingFrames; gpu::FramePointer _activeFrame; QSize _size; static const size_t FRAME_TIME_BUFFER_SIZE{ 1024 }; void submitFrame(const gpu::FramePointer& frame) { std::unique_lock lock(_frameLock); _pendingFrames.push(frame); } void initialize(QWindow* window, gl::Context& initContext) { setObjectName("RenderThread"); _context.setWindow(window); _context.create(); _context.makeCurrent(); window->setSurfaceType(QSurface::OpenGLSurface); _context.makeCurrent(_context.qglContext(), window); // GPU library init gpu::Context::init(); _gpuContext = std::make_shared(); _backend = _gpuContext->getBackend(); _context.makeCurrent(); initContext.create(); _context.doneCurrent(); std::unique_lock lock(_mutex); Parent::initialize(); _context.moveToThread(_thread); } void setup() override { RENDER_THREAD = QThread::currentThread(); // Wait until the context has been moved to this thread { std::unique_lock lock(_mutex); } _context.makeCurrent(); glewExperimental = true; glewInit(); glGetError(); //wglSwapIntervalEXT(0); _frameTimes.resize(FRAME_TIME_BUFFER_SIZE, 0); { auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); auto ps = gpu::StandardShaderLib::getDrawTexturePS(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::BindingSet slotBindings; gpu::Shader::makeProgram(*program, slotBindings); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); _presentPipeline = gpu::Pipeline::create(program, state); } //_textOverlay = new TextOverlay(glm::uvec2(800, 600)); glViewport(0, 0, 800, 600); (void)CHECK_GL_ERROR(); _elapsed.start(); } void shutdown() override { _activeFrame.reset(); while (!_pendingFrames.empty()) { _gpuContext->consumeFrameUpdates(_pendingFrames.front()); _pendingFrames.pop(); } _presentPipeline.reset(); _gpuContext.reset(); } void renderFrame(gpu::FramePointer& frame) { ++_presentCount; _context.makeCurrent(); _backend->recycle(); _backend->syncCache(); if (frame && !frame->batches.empty()) { _gpuContext->executeFrame(frame); { auto geometryCache = DependencyManager::get(); gpu::Batch presentBatch; presentBatch.setViewportTransform({ 0, 0, _size.width(), _size.height() }); presentBatch.enableStereo(false); presentBatch.resetViewTransform(); presentBatch.setFramebuffer(gpu::FramebufferPointer()); presentBatch.setResourceTexture(0, frame->framebuffer->getRenderBuffer(0)); presentBatch.setPipeline(_presentPipeline); presentBatch.draw(gpu::TRIANGLE_STRIP, 4); _gpuContext->executeBatch(presentBatch); } (void)CHECK_GL_ERROR(); } _context.makeCurrent(); _context.swapBuffers(); _fpsCounter.increment(); static size_t _frameCount{ 0 }; ++_frameCount; if (_elapsed.elapsed() >= 500) { _fps = _fpsCounter.rate(); _frameCount = 0; _elapsed.restart(); } (void)CHECK_GL_ERROR(); _context.doneCurrent(); } void report() { uint64_t total = 0; for (const auto& t : _frameTimes) { total += t; } auto averageFrameTime = total / FRAME_TIME_BUFFER_SIZE; qDebug() << "Average frame " << averageFrameTime; std::list> sortedHighFrames; for (size_t i = 0; i < _frameTimes.size(); ++i) { const auto& t = _frameTimes[i]; if (t > averageFrameTime * 6) { sortedHighFrames.push_back({ t, i } ); } } sortedHighFrames.sort(); for (const auto& p : sortedHighFrames) { qDebug() << "Long frame " << p.first << " " << p.second; } } bool process() override { std::queue pendingFrames; { std::unique_lock lock(_frameLock); pendingFrames.swap(_pendingFrames); } while (!pendingFrames.empty()) { _activeFrame = pendingFrames.front(); if (_activeFrame) { _gpuContext->consumeFrameUpdates(_activeFrame); } pendingFrames.pop(); } if (!_activeFrame) { QThread::msleep(1); return true; } { auto start = usecTimestampNow(); renderFrame(_activeFrame); auto duration = usecTimestampNow() - start; auto frameBufferIndex = _frameIndex % FRAME_TIME_BUFFER_SIZE; _frameTimes[frameBufferIndex] = duration; ++_frameIndex; if (0 == _frameIndex % FRAME_TIME_BUFFER_SIZE) { report(); } } return true; } }; // Create a simple OpenGL window that renders text in various ways class QTestWindow : public QWindow { public: //"/-17.2049,-8.08629,-19.4153/0,0.881994,0,-0.47126" static void setup() { DependencyManager::registerInheritance(); //DependencyManager::registerInheritance(); DependencyManager::set(); DependencyManager::set(NodeType::Agent); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); } struct TextureLoad { uint32_t time; QString file; QString src; }; QTestWindow() { { QStringList stringList; QFile textFile(DATA_DIR.path() + "/loads.txt"); textFile.open(QFile::ReadOnly); //... (open the file for reading, etc.) QTextStream textStream(&textFile); while (true) { QString line = textStream.readLine(); if (line.isNull()) break; else stringList.append(line); } for (QString s : stringList) { auto index = s.indexOf(" "); QString timeStr = s.left(index); auto time = timeStr.toUInt(); QString path = DATA_DIR.path() + "/" + s.right(s.length() - index).trimmed(); if (!QFileInfo(path).exists()) { continue; } qDebug() << "Path " << path; _texturesFiles.push_back({ time, path, s }); } _textures.resize(_texturesFiles.size()); } installEventFilter(this); QThreadPool::globalInstance()->setMaxThreadCount(2); QThread::currentThread()->setPriority(QThread::HighestPriority); ResourceManager::init(); setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint); _size = QSize(800, 600); _renderThread._size = _size; setGeometry(QRect(QPoint(), _size)); create(); show(); QCoreApplication::processEvents(); // Create the initial context _renderThread.initialize(this, _initContext); _initContext.makeCurrent(); // FIXME use a wait condition QThread::msleep(1000); _renderThread.submitFrame(gpu::FramePointer()); _initContext.makeCurrent(); { auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS(); auto ps = gpu::StandardShaderLib::getDrawTexturePS(); gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps); gpu::Shader::makeProgram(*program); gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(gpu::State::DepthTest(false)); state->setScissorEnable(true); _simplePipeline = gpu::Pipeline::create(program, state); } QTimer* timer = new QTimer(this); timer->setInterval(0); connect(timer, &QTimer::timeout, this, [this] { draw(); }); timer->start(); _ready = true; } virtual ~QTestWindow() { DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); ResourceManager::cleanup(); } protected: bool eventFilter(QObject *obj, QEvent *event) override { if (event->type() == QEvent::Close) { _renderThread.terminate(); } return QWindow::eventFilter(obj, event); } void keyPressEvent(QKeyEvent* event) override { switch (event->key()) { case Qt::Key_Left: prevTexture(); break; case Qt::Key_Right: nextTexture(); break; case Qt::Key_Return: reportMemory(); break; case Qt::Key_PageDown: derezTexture(); break; case Qt::Key_Home: unloadAll(); break; case Qt::Key_End: loadAll(); break; case Qt::Key_Down: loadTexture(); break; case Qt::Key_Up: unloadTexture(); break; } QWindow::keyPressEvent(event); } void keyReleaseEvent(QKeyEvent* event) override { } void mouseMoveEvent(QMouseEvent* event) override { } void resizeEvent(QResizeEvent* ev) override { resizeWindow(ev->size()); } void nextTexture() { if (_textures.empty()) { return; } auto textureCount = _textures.size(); _currentTextureIndex = (_currentTextureIndex + 1) % textureCount; loadTexture(); } void prevTexture() { if (_textures.empty()) { return; } auto textureCount = _textures.size(); _currentTextureIndex = (_currentTextureIndex + textureCount - 1) % textureCount; loadTexture(); } void reportMemory() { static GLint lastMemory = 0; GLint availableMem; glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &availableMem); qDebug() << "Memory available " << availableMem; if (lastMemory != 0) { qDebug() << "Delta " << availableMem - lastMemory; } lastMemory = availableMem; } void derezTexture() { if (!_textures[_currentTextureIndex]) { return; } auto texture = _textures[_currentTextureIndex]; texture->setMinMip(texture->minMip() + 1); } void loadTexture() { if (_textures[_currentTextureIndex]) { return; } auto file = _texturesFiles[_currentTextureIndex].file; qDebug() << "Loading texture " << file; _textures[_currentTextureIndex] = DependencyManager::get()->getImageTexture(file); } void unloadTexture() { if (_textures.empty()) { return; } _textures[_currentTextureIndex].reset(); } void loadAll() { for (size_t i = 0; i < _texturesFiles.size(); ++i) { if (_textures[i]) { continue; } auto file = _texturesFiles[i].file; qDebug() << "Loading texture " << file; _textures[i] = DependencyManager::get()->getImageTexture(file); } } void unloadAll() { for (auto& texture : _textures) { texture.reset(); } } private: size_t _currentTextureIndex { 0 }; std::vector _texturesFiles; std::vector _textures; uint16_t _fps; gpu::PipelinePointer _simplePipeline; void draw() { if (!_ready) { return; } if (!isVisible()) { return; } if (_renderCount.load() != 0 && _renderCount.load() >= _renderThread._presentCount.load()) { QThread::usleep(1); return; } _renderCount = _renderThread._presentCount.load(); update(); QSize windowSize = _size; auto framebufferCache = DependencyManager::get(); framebufferCache->setFrameBufferSize(windowSize); // Final framebuffer that will be handled to the display-plugin render(); if (_fps != _renderThread._fps) { _fps = _renderThread._fps; updateText(); } } void updateText() { setTitle(QString("FPS %1").arg(_fps)); } void update() { auto now = usecTimestampNow(); static auto last = now; auto delta = (now - last) / USECS_PER_MSEC; Q_UNUSED(delta); #if 0 if (!_textures.empty()) { const auto& front = _textureLoads.front(); if (delta >= front.time) { QFileInfo fileInfo(front.file); if (!fileInfo.exists()) { qDebug() << "Missing file " << front.file; } else { qDebug() << "Loading " << front.src; auto file = front.file.toLocal8Bit().toStdString(); processTexture(file.c_str()); _textures.push_back(DependencyManager::get()->getImageTexture(front.file)); } _textureLoads.pop(); if (_textureLoads.empty()) { qDebug() << "Done"; } } } #endif } void render() { auto& gpuContext = _renderThread._gpuContext; gpuContext->beginFrame(); gpu::doInBatch(gpuContext, [&](gpu::Batch& batch) { batch.resetStages(); }); PROFILE_RANGE(__FUNCTION__); auto framebuffer = DependencyManager::get()->getFramebuffer(); gpu::doInBatch(gpuContext, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setFramebuffer(framebuffer); batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(1, 0, 0, 1)); auto vpsize = framebuffer->getSize(); auto vppos = ivec2(0); batch.setViewportTransform(ivec4(vppos, vpsize)); if (!_textures.empty()) { batch.setResourceTexture(0, _textures[_currentTextureIndex]); } batch.setPipeline(_simplePipeline); batch.draw(gpu::TRIANGLE_STRIP, 4); }); auto frame = gpuContext->endFrame(); frame->framebuffer = framebuffer; frame->framebufferRecycler = [](const gpu::FramebufferPointer& framebuffer) { DependencyManager::get()->releaseFramebuffer(framebuffer); }; _renderThread.submitFrame(frame); if (!_renderThread.isThreaded()) { _renderThread.process(); } } void resizeWindow(const QSize& size) { _size = size; if (!_ready) { return; } _renderThread._size = size; } private: QSize _size; std::atomic _renderCount; gl::OffscreenContext _initContext; RenderThread _renderThread; ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. bool _ready { false }; }; void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { if (!message.isEmpty()) { #ifdef Q_OS_WIN OutputDebugStringA(message.toLocal8Bit().constData()); OutputDebugStringA("\n"); #endif std::cout << message.toLocal8Bit().constData() << std::endl; } } const char * LOG_FILTER_RULES = R"V0G0N( hifi.gpu=true )V0G0N"; void unzipTestData(const QByteArray& zipData) { DOWNLOAD_DIR = new QTemporaryDir(); QTemporaryDir& tempDir = *DOWNLOAD_DIR; QTemporaryFile zipFile; if (zipFile.open()) { zipFile.write(zipData); zipFile.close(); } qDebug() << zipFile.fileName(); if (!tempDir.isValid()) { qFatal("Unable to create temp dir"); } DATA_DIR = QDir(tempDir.path()); //auto files = JlCompress::getFileList(zipData); auto files = JlCompress::extractDir(zipFile.fileName(), DATA_DIR.path()); qDebug() << DATA_DIR.path(); } int main(int argc, char** argv) { QApplication app(argc, argv); QCoreApplication::setApplicationName("RenderPerf"); QCoreApplication::setOrganizationName("High Fidelity"); QCoreApplication::setOrganizationDomain("highfidelity.com"); qInstallMessageHandler(messageHandler); QLoggingCategory::setFilterRules(LOG_FILTER_RULES); if (!DATA_DIR.exists()) { FileDownloader(DATA_SET, [&](const QByteArray& data) { qDebug() << "Fetched size " << data.size(); unzipTestData(data); }).waitForDownload(); } QTestWindow::setup(); QTestWindow window; app.exec(); return 0; } #include "main.moc"