diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 65989b389e..d87a5f1cc9 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -413,6 +413,9 @@ void AvatarMixer::handleAvatarDataPacket(QSharedPointer message } void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer message, SharedNodePointer senderNode) { + auto nodeList = DependencyManager::get(); + nodeList->getOrCreateLinkedData(senderNode); + if (senderNode->getLinkedData()) { AvatarMixerClientData* nodeData = dynamic_cast(senderNode->getLinkedData()); if (nodeData != nullptr) { diff --git a/interface/resources/qml/MarketplaceComboBox.qml b/interface/resources/qml/MarketplaceComboBox.qml deleted file mode 100644 index fec151d755..0000000000 --- a/interface/resources/qml/MarketplaceComboBox.qml +++ /dev/null @@ -1,103 +0,0 @@ -// -// MarketplaceComboBox.qml -// -// Created by Elisa Lupin-Jimenez on 3 Aug 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 -// - -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtWebChannel 1.0 -import QtWebEngine 1.1 -import QtWebSockets 1.0 -import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel - -import "controls" -import "controls-uit" as Controls -import "styles" -import "styles-uit" - - -Rectangle { - HifiConstants { id: hifi } - id: marketplaceComboBox - anchors.fill: parent - color: hifi.colors.baseGrayShadow - property var currentUrl: "https://metaverse.highfidelity.com/marketplace" - - Controls.BaseWebView { - id: webview - url: currentUrl - anchors.top: switchMarketView.bottom - width: parent.width - height: parent.height - 40 - focus: true - - Timer { - id: zipTimer - running: false - repeat: false - interval: 1500 - property var handler; - onTriggered: handler(); - } - - property var autoCancel: 'var element = $("a.btn.cancel"); - element.click();' - - onNewViewRequested: { - var component = Qt.createComponent("Browser.qml"); - var newWindow = component.createObject(desktop); - request.openIn(newWindow.webView); - if (File.isZippedFbx(desktop.currentUrl)) { - zipTimer.handler = function() { - newWindow.destroy(); - runJavaScript(autoCancel); - } - zipTimer.start(); - } - } - - property var simpleDownload: 'var element = $("a.download-file"); - element.removeClass("download-file"); - element.removeAttr("download");' - - onLinkHovered: { - desktop.currentUrl = hoveredUrl; - // add an error message for non-fbx files - if (File.isZippedFbx(desktop.currentUrl)) { - runJavaScript(simpleDownload, function(){console.log("ran the JS");}); - } - - } - - } - - Controls.ComboBox { - id: switchMarketView - anchors.top: parent.top - anchors.right: parent.right - colorScheme: hifi.colorSchemes.dark - width: 200 - height: 40 - visible: true - model: ["Marketplace", "Clara.io"] - onCurrentIndexChanged: { - if (currentIndex === 0) { webview.url = "https://metaverse.highfidelity.com/marketplace"; } - if (currentIndex === 1) { webview.url = "https://clara.io/library"; } - } - - } - - Controls.Label { - id: switchMarketLabel - anchors.verticalCenter: switchMarketView.verticalCenter - anchors.right: switchMarketView.left - color: hifi.colors.white - text: "Explore interesting content from: " - } - -} \ No newline at end of file diff --git a/interface/resources/qml/Marketplaces.qml b/interface/resources/qml/Marketplaces.qml new file mode 100644 index 0000000000..70a20286d3 --- /dev/null +++ b/interface/resources/qml/Marketplaces.qml @@ -0,0 +1,167 @@ +// +// Marketplaces.qml +// +// Created by Elisa Lupin-Jimenez on 3 Aug 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtWebChannel 1.0 +import QtWebEngine 1.1 +import QtWebSockets 1.0 +import "qrc:///qtwebchannel/qwebchannel.js" as WebChannel + +import "controls" +import "controls-uit" as Controls +import "styles" +import "styles-uit" + + +Rectangle { + HifiConstants { id: hifi } + id: marketplace + anchors.fill: parent + property var marketplacesUrl: "../../scripts/system/html/marketplaces.html" + property int statusBarHeight: 50 + property int statusMargin: 50 + property string standardMessage: "Check out other marketplaces." + property string claraMessage: "Choose a model and click Download -> Autodesk FBX." + property string claraError: "High Fidelity only supports Autodesk FBX models." + + Controls.BaseWebView { + id: webview + url: marketplacesUrl + anchors.top: marketplace.top + width: parent.width + height: parent.height - statusBarHeight + focus: true + + Timer { + id: zipTimer + running: false + repeat: false + interval: 1500 + property var handler; + onTriggered: handler(); + } + + Timer { + id: alertTimer + running: false + repeat: false + interval: 9000 + property var handler; + onTriggered: handler(); + } + + property var autoCancel: 'var element = $("a.btn.cancel"); + element.click();' + + property var simpleDownload: 'var element = $("a.download-file"); + element.removeClass("download-file"); + element.removeAttr("download");' + + function displayErrorStatus() { + alertTimer.handler = function() { + statusLabel.text = claraMessage; + statusBar.color = hifi.colors.blueHighlight; + statusIcon.text = hifi.glyphs.info; + } + alertTimer.start(); + } + + property var notFbxHandler: 'var element = $("a.btn.btn-primary.viewer-button.download-file") + element.click();' + + // this code is for removing other file types from Clara.io's download options + //property var checkFileType: "$('[data-extension]:not([data-extension=\"fbx\"])').parent().remove()" + + onLinkHovered: { + desktop.currentUrl = hoveredUrl; + //runJavaScript(checkFileType, function(){console.log("Remove filetypes JS injection");}); + if (File.isZippedFbx(desktop.currentUrl)) { + runJavaScript(simpleDownload, function(){console.log("Download JS injection");}); + return; + } + + if (File.isZipped(desktop.currentUrl)) { + statusLabel.text = claraError; + statusBar.color = hifi.colors.redHighlight; + statusIcon.text = hifi.glyphs.alert; + runJavaScript(notFbxHandler, displayErrorStatus()); + } + + } + + onLoadingChanged: { + if (File.isClaraLink(webview.url)) { + statusLabel.text = claraMessage; + } else { + statusLabel.text = standardMessage; + } + statusBar.color = hifi.colors.blueHighlight; + statusIcon.text = hifi.glyphs.info; + } + + onNewViewRequested: { + var component = Qt.createComponent("Browser.qml"); + var newWindow = component.createObject(desktop); + request.openIn(newWindow.webView); + if (File.isZippedFbx(desktop.currentUrl)) { + runJavaScript(autoCancel); + zipTimer.handler = function() { + newWindow.destroy(); + } + zipTimer.start(); + } + } + + } + + Rectangle { + id: statusBar + anchors.top: webview.bottom + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + color: hifi.colors.blueHighlight + + Controls.Button { + id: switchMarketView + anchors.right: parent.right + anchors.rightMargin: statusMargin + anchors.verticalCenter: parent.verticalCenter + width: 150 + text: "See all markets" + onClicked: { + webview.url = "../../scripts/system/html/marketplaces.html"; + statusLabel.text = standardMessage; + } + } + + Controls.Label { + id: statusLabel + anchors.verticalCenter: switchMarketView.verticalCenter + anchors.left: parent.left + anchors.leftMargin: statusMargin + color: hifi.colors.white + text: standardMessage + size: 18 + } + + HiFiGlyphs { + id: statusIcon + anchors.right: statusLabel.left + anchors.verticalCenter: statusLabel.verticalCenter + text: hifi.glyphs.info + color: hifi.colors.white + size: hifi.fontSizes.tableHeadingIcon + } + + } + +} \ No newline at end of file diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d846a5c6d1..45feed1088 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -176,8 +176,6 @@ static const int MAX_CONCURRENT_RESOURCE_DOWNLOADS = 16; // For processing on QThreadPool, target 2 less than the ideal number of threads, leaving // 2 logical cores available for time sensitive tasks. static const int MIN_PROCESSING_THREAD_POOL_SIZE = 2; -static const int PROCESSING_THREAD_POOL_SIZE = std::max(MIN_PROCESSING_THREAD_POOL_SIZE, - QThread::idealThreadCount() - 2); static const QString SNAPSHOT_EXTENSION = ".jpg"; static const QString SVO_EXTENSION = ".svo"; @@ -537,7 +535,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : PluginContainer* pluginContainer = dynamic_cast(this); // set the container for any plugins that care PluginManager::getInstance()->setContainer(pluginContainer); - QThreadPool::globalInstance()->setMaxThreadCount(PROCESSING_THREAD_POOL_SIZE); + QThreadPool::globalInstance()->setMaxThreadCount(MIN_PROCESSING_THREAD_POOL_SIZE); thread()->setPriority(QThread::HighPriority); thread()->setObjectName("Main Thread"); @@ -707,6 +705,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : connect(addressManager.data(), &AddressManager::hostChanged, this, &Application::updateWindowTitle); connect(this, &QCoreApplication::aboutToQuit, addressManager.data(), &AddressManager::storeCurrentAddress); + connect(this, &Application::activeDisplayPluginChanged, this, &Application::updateThreadPoolCount); + // Save avatar location immediately after a teleport. connect(getMyAvatar(), &MyAvatar::positionGoneTo, DependencyManager::get().data(), &AddressManager::storeCurrentAddress); @@ -5727,3 +5727,18 @@ void Application::sendHoverLeaveEntity(QUuid id, PointerEvent event) { EntityItemID entityItemID(id); emit getEntities()->hoverLeaveEntity(entityItemID, event); } + +// FIXME? perhaps two, one for the main thread and one for the offscreen UI rendering thread? +static const int UI_RESERVED_THREADS = 1; +// Windows won't let you have all the cores +static const int OS_RESERVED_THREADS = 1; + +void Application::updateThreadPoolCount() const { + auto reservedThreads = UI_RESERVED_THREADS + OS_RESERVED_THREADS + _displayPlugin->getRequiredThreadCount(); + auto availableThreads = QThread::idealThreadCount() - reservedThreads; + auto threadPoolSize = std::max(MIN_PROCESSING_THREAD_POOL_SIZE, availableThreads); + qDebug() << "Ideal Thread Count " << QThread::idealThreadCount(); + qDebug() << "Reserved threads " << reservedThreads; + qDebug() << "Setting thread pool size to " << threadPoolSize; + QThreadPool::globalInstance()->setMaxThreadCount(threadPoolSize); +} \ No newline at end of file diff --git a/interface/src/Application.h b/interface/src/Application.h index a0c67a9e73..8bfae51179 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -286,6 +286,7 @@ public slots: bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); bool exportEntities(const QString& filename, float x, float y, float z, float scale); bool importEntities(const QString& url); + void updateThreadPoolCount() const; static void setLowVelocityFilter(bool lowVelocityFilter); Q_INVOKABLE void loadDialog(); diff --git a/libraries/audio/src/AudioRingBuffer.cpp b/libraries/audio/src/AudioRingBuffer.cpp index 96f1bbb9dd..260c682cde 100644 --- a/libraries/audio/src/AudioRingBuffer.cpp +++ b/libraries/audio/src/AudioRingBuffer.cpp @@ -24,33 +24,34 @@ #include "AudioRingBuffer.h" static const QString RING_BUFFER_OVERFLOW_DEBUG { "AudioRingBuffer::writeData has overflown the buffer. Overwriting old data." }; +static const QString DROPPED_SILENT_DEBUG { "AudioRingBuffer::addSilentSamples dropping silent samples to prevent overflow." }; -AudioRingBuffer::AudioRingBuffer(int numFrameSamples, bool randomAccessMode, int numFramesCapacity) : +AudioRingBuffer::AudioRingBuffer(int numFrameSamples, int numFramesCapacity) : + _numFrameSamples(numFrameSamples), _frameCapacity(numFramesCapacity), _sampleCapacity(numFrameSamples * numFramesCapacity), - _bufferLength(numFrameSamples * (numFramesCapacity + 1)), - _numFrameSamples(numFrameSamples), - _randomAccessMode(randomAccessMode), - _overflowCount(0) + _bufferLength(numFrameSamples * (numFramesCapacity + 1)) { if (numFrameSamples) { _buffer = new int16_t[_bufferLength]; memset(_buffer, 0, _bufferLength * sizeof(int16_t)); _nextOutput = _buffer; _endOfLastWrite = _buffer; - } else { - _buffer = NULL; - _nextOutput = NULL; - _endOfLastWrite = NULL; } - static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(RING_BUFFER_OVERFLOW_DEBUG); + static QString repeatedOverflowMessage = LogHandler::getInstance().addRepeatedMessageRegex(RING_BUFFER_OVERFLOW_DEBUG); + static QString repeatedDroppedMessage = LogHandler::getInstance().addRepeatedMessageRegex(DROPPED_SILENT_DEBUG); }; AudioRingBuffer::~AudioRingBuffer() { delete[] _buffer; } +void AudioRingBuffer::clear() { + _endOfLastWrite = _buffer; + _nextOutput = _buffer; +} + void AudioRingBuffer::reset() { clear(); _overflowCount = 0; @@ -58,109 +59,82 @@ void AudioRingBuffer::reset() { void AudioRingBuffer::resizeForFrameSize(int numFrameSamples) { delete[] _buffer; + _numFrameSamples = numFrameSamples; _sampleCapacity = numFrameSamples * _frameCapacity; _bufferLength = numFrameSamples * (_frameCapacity + 1); - _numFrameSamples = numFrameSamples; - _buffer = new int16_t[_bufferLength]; - memset(_buffer, 0, _bufferLength * sizeof(int16_t)); - if (_randomAccessMode) { - memset(_buffer, 0, _bufferLength * sizeof(int16_t)); - } - reset(); -} -void AudioRingBuffer::clear() { - _endOfLastWrite = _buffer; - _nextOutput = _buffer; + if (numFrameSamples) { + _buffer = new int16_t[_bufferLength]; + memset(_buffer, 0, _bufferLength * sizeof(int16_t)); + } else { + _buffer = nullptr; + } + + reset(); } int AudioRingBuffer::readSamples(int16_t* destination, int maxSamples) { return readData((char*)destination, maxSamples * sizeof(int16_t)) / sizeof(int16_t); } +int AudioRingBuffer::writeSamples(const int16_t* source, int maxSamples) { + return writeData((char*)source, maxSamples * sizeof(int16_t)) / sizeof(int16_t); +} + int AudioRingBuffer::readData(char *data, int maxSize) { - // only copy up to the number of samples we have available - int numReadSamples = std::min((int)(maxSize / sizeof(int16_t)), samplesAvailable()); - - // If we're in random access mode, then we consider our number of available read samples slightly - // differently. Namely, if anything has been written, we say we have as many samples as they ask for - // otherwise we say we have nothing available - if (_randomAccessMode) { - numReadSamples = _endOfLastWrite ? (maxSize / sizeof(int16_t)) : 0; - } + int maxSamples = maxSize / sizeof(int16_t); + int numReadSamples = std::min(maxSamples, samplesAvailable()); if (_nextOutput + numReadSamples > _buffer + _bufferLength) { // we're going to need to do two reads to get this data, it wraps around the edge + int numSamplesToEnd = (_buffer + _bufferLength) - _nextOutput; // read to the end of the buffer - int numSamplesToEnd = (_buffer + _bufferLength) - _nextOutput; memcpy(data, _nextOutput, numSamplesToEnd * sizeof(int16_t)); - if (_randomAccessMode) { - memset(_nextOutput, 0, numSamplesToEnd * sizeof(int16_t)); // clear it - } // read the rest from the beginning of the buffer memcpy(data + (numSamplesToEnd * sizeof(int16_t)), _buffer, (numReadSamples - numSamplesToEnd) * sizeof(int16_t)); - if (_randomAccessMode) { - memset(_buffer, 0, (numReadSamples - numSamplesToEnd) * sizeof(int16_t)); // clear it - } } else { - // read the data memcpy(data, _nextOutput, numReadSamples * sizeof(int16_t)); - if (_randomAccessMode) { - memset(_nextOutput, 0, numReadSamples * sizeof(int16_t)); // clear it - } } - // push the position of _nextOutput by the number of samples read - _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numReadSamples); + shiftReadPosition(numReadSamples); return numReadSamples * sizeof(int16_t); } -int AudioRingBuffer::writeSamples(const int16_t* source, int maxSamples) { - return writeData((const char*)source, maxSamples * sizeof(int16_t)) / sizeof(int16_t); -} - int AudioRingBuffer::writeData(const char* data, int maxSize) { - // make sure we have enough bytes left for this to be the right amount of audio - // otherwise we should not copy that data, and leave the buffer pointers where they are - int samplesToCopy = std::min((int)(maxSize / sizeof(int16_t)), _sampleCapacity); - + // only copy up to the number of samples we have capacity for + int maxSamples = maxSize / sizeof(int16_t); + int numWriteSamples = std::min(maxSamples, _sampleCapacity); int samplesRoomFor = _sampleCapacity - samplesAvailable(); - if (samplesToCopy > samplesRoomFor) { - // there's not enough room for this write. erase old data to make room for this new data - int samplesToDelete = samplesToCopy - samplesRoomFor; + + if (numWriteSamples > samplesRoomFor) { + // there's not enough room for this write. erase old data to make room for this new data + int samplesToDelete = numWriteSamples - samplesRoomFor; _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, samplesToDelete); _overflowCount++; qCDebug(audio) << qPrintable(RING_BUFFER_OVERFLOW_DEBUG); } - if (_endOfLastWrite + samplesToCopy <= _buffer + _bufferLength) { - memcpy(_endOfLastWrite, data, samplesToCopy * sizeof(int16_t)); - } else { + if (_endOfLastWrite + numWriteSamples > _buffer + _bufferLength) { + // we're going to need to do two writes to set this data, it wraps around the edge int numSamplesToEnd = (_buffer + _bufferLength) - _endOfLastWrite; + + // write to the end of the buffer memcpy(_endOfLastWrite, data, numSamplesToEnd * sizeof(int16_t)); - memcpy(_buffer, data + (numSamplesToEnd * sizeof(int16_t)), (samplesToCopy - numSamplesToEnd) * sizeof(int16_t)); + + // write the rest to the beginning of the buffer + memcpy(_buffer, data + (numSamplesToEnd * sizeof(int16_t)), (numWriteSamples - numSamplesToEnd) * sizeof(int16_t)); + } else { + memcpy(_endOfLastWrite, data, numWriteSamples * sizeof(int16_t)); } - _endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, samplesToCopy); + _endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, numWriteSamples); - return samplesToCopy * sizeof(int16_t); -} - -int16_t& AudioRingBuffer::operator[](const int index) { - return *shiftedPositionAccomodatingWrap(_nextOutput, index); -} - -const int16_t& AudioRingBuffer::operator[] (const int index) const { - return *shiftedPositionAccomodatingWrap(_nextOutput, index); -} - -void AudioRingBuffer::shiftReadPosition(unsigned int numSamples) { - _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numSamples); + return numWriteSamples * sizeof(int16_t); } int AudioRingBuffer::samplesAvailable() const { @@ -176,35 +150,31 @@ int AudioRingBuffer::samplesAvailable() const { } int AudioRingBuffer::addSilentSamples(int silentSamples) { - + // NOTE: This implementation is nearly identical to writeData save for s/memcpy/memset, refer to comments there + int numWriteSamples = std::min(silentSamples, _sampleCapacity); int samplesRoomFor = _sampleCapacity - samplesAvailable(); - if (silentSamples > samplesRoomFor) { - // there's not enough room for this write. write as many silent samples as we have room for - silentSamples = samplesRoomFor; - static const QString DROPPED_SILENT_DEBUG { - "AudioRingBuffer::addSilentSamples dropping silent samples to prevent overflow." - }; - static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex(DROPPED_SILENT_DEBUG); + if (numWriteSamples > samplesRoomFor) { + numWriteSamples = samplesRoomFor; + qCDebug(audio) << qPrintable(DROPPED_SILENT_DEBUG); } - // memset zeroes into the buffer, accomodate a wrap around the end - // push the _endOfLastWrite to the correct spot - if (_endOfLastWrite + silentSamples <= _buffer + _bufferLength) { - memset(_endOfLastWrite, 0, silentSamples * sizeof(int16_t)); - } else { + if (_endOfLastWrite + numWriteSamples > _buffer + _bufferLength) { int numSamplesToEnd = (_buffer + _bufferLength) - _endOfLastWrite; memset(_endOfLastWrite, 0, numSamplesToEnd * sizeof(int16_t)); - memset(_buffer, 0, (silentSamples - numSamplesToEnd) * sizeof(int16_t)); + memset(_buffer, 0, (numWriteSamples - numSamplesToEnd) * sizeof(int16_t)); + } else { + memset(_endOfLastWrite, 0, numWriteSamples * sizeof(int16_t)); } - _endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, silentSamples); - return silentSamples; + _endOfLastWrite = shiftedPositionAccomodatingWrap(_endOfLastWrite, numWriteSamples); + + return numWriteSamples; } int16_t* AudioRingBuffer::shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const { - + // NOTE: It is possible to shift out-of-bounds if (|numSamplesShift| > 2 * _bufferLength), but this should not occur if (numSamplesShift > 0 && position + numSamplesShift >= _buffer + _bufferLength) { // this shift will wrap the position around to the beginning of the ring return position + numSamplesShift - _bufferLength; @@ -217,13 +187,15 @@ int16_t* AudioRingBuffer::shiftedPositionAccomodatingWrap(int16_t* position, int } float AudioRingBuffer::getFrameLoudness(const int16_t* frameStart) const { + // FIXME: This is a bad measure of loudness - normal estimation uses sqrt(sum(x*x)) float loudness = 0.0f; const int16_t* sampleAt = frameStart; - const int16_t* _bufferLastAt = _buffer + _bufferLength - 1; + const int16_t* bufferLastAt = _buffer + _bufferLength - 1; for (int i = 0; i < _numFrameSamples; ++i) { loudness += (float) std::abs(*sampleAt); - sampleAt = sampleAt == _bufferLastAt ? _buffer : sampleAt + 1; + // wrap if necessary + sampleAt = sampleAt == bufferLastAt ? _buffer : sampleAt + 1; } loudness /= _numFrameSamples; loudness /= AudioConstants::MAX_SAMPLE_VALUE; @@ -238,10 +210,6 @@ float AudioRingBuffer::getFrameLoudness(ConstIterator frameStart) const { return getFrameLoudness(&(*frameStart)); } -float AudioRingBuffer::getNextOutputFrameLoudness() const { - return getFrameLoudness(_nextOutput); -} - int AudioRingBuffer::writeSamples(ConstIterator source, int maxSamples) { int samplesToCopy = std::min(maxSamples, _sampleCapacity); int samplesRoomFor = _sampleCapacity - samplesAvailable(); diff --git a/libraries/audio/src/AudioRingBuffer.h b/libraries/audio/src/AudioRingBuffer.h index 2b25b1044b..7ccb32ce10 100644 --- a/libraries/audio/src/AudioRingBuffer.h +++ b/libraries/audio/src/AudioRingBuffer.h @@ -23,73 +23,69 @@ const int DEFAULT_RING_BUFFER_FRAME_CAPACITY = 10; class AudioRingBuffer { public: - AudioRingBuffer(int numFrameSamples, bool randomAccessMode = false, int numFramesCapacity = DEFAULT_RING_BUFFER_FRAME_CAPACITY); + AudioRingBuffer(int numFrameSamples, int numFramesCapacity = DEFAULT_RING_BUFFER_FRAME_CAPACITY); ~AudioRingBuffer(); - void reset(); - void resizeForFrameSize(int numFrameSamples); + // disallow copying + AudioRingBuffer(const AudioRingBuffer&) = delete; + AudioRingBuffer(AudioRingBuffer&&) = delete; + AudioRingBuffer& operator=(const AudioRingBuffer&) = delete; + /// Invalidate any data in the buffer void clear(); - int getSampleCapacity() const { return _sampleCapacity; } - int getFrameCapacity() const { return _frameCapacity; } + /// Clear and reset the overflow count + void reset(); + /// Resize frame size (causes a reset()) + // FIXME: discards any data in the buffer + void resizeForFrameSize(int numFrameSamples); + + /// Read up to maxSamples into destination (will only read up to samplesAvailable()) + /// Returns number of read samples int readSamples(int16_t* destination, int maxSamples); + + /// Write up to maxSamples from source (will only write up to sample capacity) + /// Returns number of written samples int writeSamples(const int16_t* source, int maxSamples); - int readData(char* data, int maxSize); - int writeData(const char* data, int maxSize); + /// Write up to maxSamples silent samples (will only write until other data exists in the buffer) + /// This method will not overwrite existing data in the buffer, instead dropping silent samples that would overflow + /// Returns number of written silent samples + int addSilentSamples(int maxSamples); - int16_t& operator[](const int index); - const int16_t& operator[] (const int index) const; + /// Read up to maxSize into destination + /// Returns number of read bytes + int readData(char* destination, int maxSize); - void shiftReadPosition(unsigned int numSamples); + /// Write up to maxSize from source + /// Returns number of written bytes + int writeData(const char* source, int maxSize); - float getNextOutputFrameLoudness() const; + /// Returns a reference to the index-th sample offset from the current read sample + int16_t& operator[](const int index) { return *shiftedPositionAccomodatingWrap(_nextOutput, index); } + const int16_t& operator[] (const int index) const { return *shiftedPositionAccomodatingWrap(_nextOutput, index); } + + /// Essentially discards the next numSamples from the ring buffer + /// NOTE: This is not checked - it is possible to shift past written data + /// Use samplesAvailable() to see the distance a valid shift can go + void shiftReadPosition(unsigned int numSamples) { _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numSamples); } int samplesAvailable() const; int framesAvailable() const { return (_numFrameSamples == 0) ? 0 : samplesAvailable() / _numFrameSamples; } + float getNextOutputFrameLoudness() const { return getFrameLoudness(_nextOutput); } + int getNumFrameSamples() const { return _numFrameSamples; } + int getFrameCapacity() const { return _frameCapacity; } + int getSampleCapacity() const { return _sampleCapacity; } + /// Return times the ring buffer has overwritten old data + int getOverflowCount() const { return _overflowCount; } - int getOverflowCount() const { return _overflowCount; } /// how many times has the ring buffer has overwritten old data - - int addSilentSamples(int samples); - -private: - float getFrameLoudness(const int16_t* frameStart) const; - -protected: - // disallow copying of AudioRingBuffer objects - AudioRingBuffer(const AudioRingBuffer&); - AudioRingBuffer& operator= (const AudioRingBuffer&); - - int16_t* shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const; - - int _frameCapacity; - int _sampleCapacity; - int _bufferLength; // actual length of _buffer: will be one frame larger than _sampleCapacity - int _numFrameSamples; - int16_t* _nextOutput; - int16_t* _endOfLastWrite; - int16_t* _buffer; - bool _randomAccessMode; /// will this ringbuffer be used for random access? if so, do some special processing - - int _overflowCount; /// how many times has the ring buffer has overwritten old data - -public: - class ConstIterator { //public std::iterator < std::forward_iterator_tag, int16_t > { + class ConstIterator { public: - ConstIterator() - : _bufferLength(0), - _bufferFirst(NULL), - _bufferLast(NULL), - _at(NULL) {} - ConstIterator(int16_t* bufferFirst, int capacity, int16_t* at) - : _bufferLength(capacity), - _bufferFirst(bufferFirst), - _bufferLast(bufferFirst + capacity - 1), - _at(at) {} + ConstIterator(); + ConstIterator(int16_t* bufferFirst, int capacity, int16_t* at); ConstIterator(const ConstIterator& rhs) = default; bool isNull() const { return _at == NULL; } @@ -98,95 +94,143 @@ public: bool operator!=(const ConstIterator& rhs) { return _at != rhs._at; } const int16_t& operator*() { return *_at; } - ConstIterator& operator=(const ConstIterator& rhs) { - _bufferLength = rhs._bufferLength; - _bufferFirst = rhs._bufferFirst; - _bufferLast = rhs._bufferLast; - _at = rhs._at; - return *this; - } + ConstIterator& operator=(const ConstIterator& rhs); + ConstIterator& operator++(); + ConstIterator operator++(int); + ConstIterator& operator--(); + ConstIterator operator--(int); + const int16_t& operator[] (int i); + ConstIterator operator+(int i); + ConstIterator operator-(int i); - ConstIterator& operator++() { - _at = (_at == _bufferLast) ? _bufferFirst : _at + 1; - return *this; - } - - ConstIterator operator++(int) { - ConstIterator tmp(*this); - ++(*this); - return tmp; - } - - ConstIterator& operator--() { - _at = (_at == _bufferFirst) ? _bufferLast : _at - 1; - return *this; - } - - ConstIterator operator--(int) { - ConstIterator tmp(*this); - --(*this); - return tmp; - } - - const int16_t& operator[] (int i) { - return *atShiftedBy(i); - } - - ConstIterator operator+(int i) { - return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(i)); - } - - ConstIterator operator-(int i) { - return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(-i)); - } - - void readSamples(int16_t* dest, int numSamples) { - auto samplesToEnd = _bufferLast - _at + 1; - - if (samplesToEnd >= numSamples) { - memcpy(dest, _at, numSamples * sizeof(int16_t)); - _at += numSamples; - } else { - auto samplesFromStart = numSamples - samplesToEnd; - memcpy(dest, _at, samplesToEnd * sizeof(int16_t)); - memcpy(dest + samplesToEnd, _bufferFirst, samplesFromStart * sizeof(int16_t)); - - _at = _bufferFirst + samplesFromStart; - } - } - - void readSamplesWithFade(int16_t* dest, int numSamples, float fade) { - int16_t* at = _at; - for (int i = 0; i < numSamples; i++) { - *dest = (float)*at * fade; - ++dest; - at = (at == _bufferLast) ? _bufferFirst : at + 1; - } - } + void readSamples(int16_t* dest, int numSamples); + void readSamplesWithFade(int16_t* dest, int numSamples, float fade); private: - int16_t* atShiftedBy(int i) { - i = (_at - _bufferFirst + i) % _bufferLength; - if (i < 0) { - i += _bufferLength; - } - return _bufferFirst + i; - } + int16_t* atShiftedBy(int i); - private: int _bufferLength; int16_t* _bufferFirst; int16_t* _bufferLast; int16_t* _at; }; - ConstIterator nextOutput() const { return ConstIterator(_buffer, _bufferLength, _nextOutput); } - ConstIterator lastFrameWritten() const { return ConstIterator(_buffer, _bufferLength, _endOfLastWrite) - _numFrameSamples; } - - float getFrameLoudness(ConstIterator frameStart) const; + ConstIterator nextOutput() const; + ConstIterator lastFrameWritten() const; int writeSamples(ConstIterator source, int maxSamples); int writeSamplesWithFade(ConstIterator source, int maxSamples, float fade); + + float getFrameLoudness(ConstIterator frameStart) const; + +protected: + int16_t* shiftedPositionAccomodatingWrap(int16_t* position, int numSamplesShift) const; + float getFrameLoudness(const int16_t* frameStart) const; + + int _numFrameSamples; + int _frameCapacity; + int _sampleCapacity; + int _bufferLength; // actual _buffer length (_sampleCapacity + 1) + int _overflowCount{ 0 }; // times the ring buffer has overwritten data + + int16_t* _nextOutput{ nullptr }; + int16_t* _endOfLastWrite{ nullptr }; + int16_t* _buffer{ nullptr }; }; +// inline the iterator: +inline AudioRingBuffer::ConstIterator::ConstIterator() : + _bufferLength(0), + _bufferFirst(NULL), + _bufferLast(NULL), + _at(NULL) {} + +inline AudioRingBuffer::ConstIterator::ConstIterator(int16_t* bufferFirst, int capacity, int16_t* at) : + _bufferLength(capacity), + _bufferFirst(bufferFirst), + _bufferLast(bufferFirst + capacity - 1), + _at(at) {} + +inline AudioRingBuffer::ConstIterator& AudioRingBuffer::ConstIterator::operator=(const ConstIterator& rhs) { + _bufferLength = rhs._bufferLength; + _bufferFirst = rhs._bufferFirst; + _bufferLast = rhs._bufferLast; + _at = rhs._at; + return *this; +} + +inline AudioRingBuffer::ConstIterator& AudioRingBuffer::ConstIterator::operator++() { + _at = (_at == _bufferLast) ? _bufferFirst : _at + 1; + return *this; +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::ConstIterator::operator++(int) { + ConstIterator tmp(*this); + ++(*this); + return tmp; +} + +inline AudioRingBuffer::ConstIterator& AudioRingBuffer::ConstIterator::operator--() { + _at = (_at == _bufferFirst) ? _bufferLast : _at - 1; + return *this; +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::ConstIterator::operator--(int) { + ConstIterator tmp(*this); + --(*this); + return tmp; +} + +inline const int16_t& AudioRingBuffer::ConstIterator::operator[] (int i) { + return *atShiftedBy(i); +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::ConstIterator::operator+(int i) { + return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(i)); +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::ConstIterator::operator-(int i) { + return ConstIterator(_bufferFirst, _bufferLength, atShiftedBy(-i)); +} + +inline int16_t* AudioRingBuffer::ConstIterator::atShiftedBy(int i) { + i = (_at - _bufferFirst + i) % _bufferLength; + if (i < 0) { + i += _bufferLength; + } + return _bufferFirst + i; +} + +inline void AudioRingBuffer::ConstIterator::readSamples(int16_t* dest, int numSamples) { + auto samplesToEnd = _bufferLast - _at + 1; + + if (samplesToEnd >= numSamples) { + memcpy(dest, _at, numSamples * sizeof(int16_t)); + _at += numSamples; + } else { + auto samplesFromStart = numSamples - samplesToEnd; + memcpy(dest, _at, samplesToEnd * sizeof(int16_t)); + memcpy(dest + samplesToEnd, _bufferFirst, samplesFromStart * sizeof(int16_t)); + + _at = _bufferFirst + samplesFromStart; + } +} + +inline void AudioRingBuffer::ConstIterator::readSamplesWithFade(int16_t* dest, int numSamples, float fade) { + int16_t* at = _at; + for (int i = 0; i < numSamples; i++) { + *dest = (float)*at * fade; + ++dest; + at = (at == _bufferLast) ? _bufferFirst : at + 1; + } +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::nextOutput() const { + return ConstIterator(_buffer, _bufferLength, _nextOutput); +} + +inline AudioRingBuffer::ConstIterator AudioRingBuffer::lastFrameWritten() const { + return ConstIterator(_buffer, _bufferLength, _endOfLastWrite) - _numFrameSamples; +} + #endif // hifi_AudioRingBuffer_h diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index b908f57439..6b79879bb7 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -20,7 +20,7 @@ const int STARVE_HISTORY_CAPACITY = 50; InboundAudioStream::InboundAudioStream(int numFrameSamples, int numFramesCapacity, const Settings& settings) : - _ringBuffer(numFrameSamples, false, numFramesCapacity), + _ringBuffer(numFrameSamples, numFramesCapacity), _lastPopSucceeded(false), _lastPopOutput(), _dynamicJitterBuffers(settings._dynamicJitterBuffers), diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index afd8f7d45b..ef15861843 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -69,6 +69,8 @@ public: virtual bool wantVsync() const { return true; } void setVsyncEnabled(bool vsyncEnabled) { _vsyncEnabled = vsyncEnabled; } bool isVsyncEnabled() const { return _vsyncEnabled; } + // Three threads, one for rendering, one for texture transfers, one reserved for the GL driver + int getRequiredThreadCount() const override { return 3; } protected: friend class PresentThread; diff --git a/libraries/gl/src/gl/Context.cpp b/libraries/gl/src/gl/Context.cpp index f8712872ee..27e1e67560 100644 --- a/libraries/gl/src/gl/Context.cpp +++ b/libraries/gl/src/gl/Context.cpp @@ -205,8 +205,10 @@ void Context::create() { formatAttribs.push_back(24); formatAttribs.push_back(WGL_STENCIL_BITS_ARB); formatAttribs.push_back(8); - formatAttribs.push_back(WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB); - formatAttribs.push_back(GL_TRUE); +#ifdef NATIVE_SRGB_FRAMEBUFFER + // formatAttribs.push_back(WGL_FRAMEBUFFER_SRGB_CAPABLE_ARB); + // formatAttribs.push_back(GL_TRUE); +#endif // terminate the list formatAttribs.push_back(0); UINT numFormats; diff --git a/libraries/plugins/src/plugins/DisplayPlugin.h b/libraries/plugins/src/plugins/DisplayPlugin.h index 5111bda95f..eac08716a1 100644 --- a/libraries/plugins/src/plugins/DisplayPlugin.h +++ b/libraries/plugins/src/plugins/DisplayPlugin.h @@ -128,6 +128,7 @@ public: Present = QEvent::User + 1 }; + virtual int getRequiredThreadCount() const { return 0; } virtual bool isHmd() const { return false; } virtual int getHmdScreen() const { return -1; } /// By default, all HMDs are stereo diff --git a/libraries/script-engine/src/FileScriptingInterface.cpp b/libraries/script-engine/src/FileScriptingInterface.cpp index fa38e46d31..ad6a3cdf6f 100644 --- a/libraries/script-engine/src/FileScriptingInterface.cpp +++ b/libraries/script-engine/src/FileScriptingInterface.cpp @@ -65,13 +65,21 @@ bool FileScriptingInterface::isTempDir(QString tempDir) { folderName = "/" + testDir.section("/", -1); QString testContainer = testDir; testContainer.remove(folderName); - if (testContainer == tempContainer) return true; - return false; + return (testContainer == tempContainer); +} + +// checks whether the webview is displaying a Clara.io page for Marketplaces.qml +bool FileScriptingInterface::isClaraLink(QUrl url) { + return (url.toString().contains("clara.io") && !url.toString().contains("clara.io/signup")); } bool FileScriptingInterface::isZippedFbx(QUrl url) { - if (url.toString().contains(".zip") && url.toString().contains("fbx")) return true; - return false; + return (url.toString().endsWith("fbx.zip")); +} + +// checks whether a user tries to download a file that is not in .fbx format +bool FileScriptingInterface::isZipped(QUrl url) { + return (url.toString().endsWith(".zip")); } // this function is not in use diff --git a/libraries/script-engine/src/FileScriptingInterface.h b/libraries/script-engine/src/FileScriptingInterface.h index dd6ca3225b..d9a100b293 100644 --- a/libraries/script-engine/src/FileScriptingInterface.h +++ b/libraries/script-engine/src/FileScriptingInterface.h @@ -25,6 +25,8 @@ public: public slots: bool isZippedFbx(QUrl url); + bool isZipped(QUrl url); + bool isClaraLink(QUrl url); QString convertUrlToPath(QUrl url); void runUnzip(QString path, QUrl url); QString getTempDir(); diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 75f8c51b0e..025f879d84 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -58,6 +58,9 @@ public: void unsuppressKeyboard() override; bool isKeyboardVisible() override; + // Needs an additional thread for VR submission + int getRequiredThreadCount() const override { return Parent::getRequiredThreadCount() + 1; } + protected: bool internalActivate() override; void internalDeactivate() override; diff --git a/scripts/system/html/css/marketplaces.css b/scripts/system/html/css/marketplaces.css new file mode 100644 index 0000000000..734501f3fc --- /dev/null +++ b/scripts/system/html/css/marketplaces.css @@ -0,0 +1,98 @@ +/* +// +// 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 +*/ +body { + background: white; + padding: 0 0 0 0; + font-family:Raleway-SemiBold; +} +.marketplaces-container { + display: inline-block; + color: black; + width: 94%; + margin-left: 3%; + height: 100%; +} +.marketplaces-title { + margin-top: 45px; + margin-bottom: 20px; +} +.marketplaces-intro-text { + margin-bottom: 60px; +} +.marketplace-tile { + float:left; + width: 100%; +} +.marketplace-tile-first-column { + text-align: center; + float: left; + width: 33%; +} +.marketplace-tile-second-column { + float: left; + margin-left:4%; + width: 62%; +} +.exploreButton { + font-size: 16px !important; + width: 200px !important; + height: 45px !important; + margin-top: 20px; + margin-bottom: 30px; +} +.tile-divider { + width: 100%; + margin-left: 0%; + display: block; + height: 1px; + border: 0; + border-top: 1px solid lightgrey; + margin: 1em 0; + padding: 0; + margin-bottom: 30px; +} +.marketplace-tile-description { + margin-top: 15px; + margin-bottom: 30px; +} +.marketplace-tile-image { + margin-top:15px; + max-width: 256px; + height: 128px; + margin-bottom:60px; + -webkit-box-shadow: -1px 4px 16px 0px rgba(0, 0, 0, 0.48); + -moz-box-shadow: -1px 4px 16px 0px rgba(0, 0, 0, 0.48); + box-shadow: -1px 4px 16px 0px rgba(0, 0, 0, 0.48); +} +.marketplace-clara-steps { + padding-left: 15px; +} +.marketplace-clara-steps > li { + margin-top: 5px; +} +@media (max-width:768px) { + .marketplace-tile-first-column { + float: left; + width: 100%; + } + .marketplace-tile-second-column { + float: left; + width: 100%; + } + .exploreButton-holder { + width:100%; + text-align:center; + } + .tile-divider { + width: 100%; + margin-left: 0%; +} +.marketplace-tile-image{ + margin-bottom:15px; +} +} \ No newline at end of file diff --git a/scripts/system/html/img/clara-tile.png b/scripts/system/html/img/clara-tile.png new file mode 100644 index 0000000000..ae431dd510 Binary files /dev/null and b/scripts/system/html/img/clara-tile.png differ diff --git a/scripts/system/html/img/hifi-marketplace-tile.png b/scripts/system/html/img/hifi-marketplace-tile.png new file mode 100644 index 0000000000..9a95c081a0 Binary files /dev/null and b/scripts/system/html/img/hifi-marketplace-tile.png differ diff --git a/scripts/system/html/js/marketplaces.js b/scripts/system/html/js/marketplaces.js new file mode 100644 index 0000000000..a1b3847b3c --- /dev/null +++ b/scripts/system/html/js/marketplaces.js @@ -0,0 +1,12 @@ +function loaded() { +bindExploreButtons(); +} + +function bindExploreButtons() { + $('#exploreClaraMarketplace').on('click', function() { + window.location = "https://clara.io/library?public=true" + }) + $('#exploreHifiMarketplace').on('click', function() { + window.location = "http://www.highfidelity.com/marketplace" + }) +} \ No newline at end of file diff --git a/scripts/system/html/marketplaces.html b/scripts/system/html/marketplaces.html new file mode 100644 index 0000000000..ea85c7ec62 --- /dev/null +++ b/scripts/system/html/marketplaces.html @@ -0,0 +1,65 @@ + + + + Marketplaces + + + + + + + + + +
+

+ Marketplaces +

+
+

+ You can bring content into High Fidelity forom anywhere you want. Here are a few places that support direct import of content right now. If you'd like to suggest a Market to include here, let us know. +

+
+
+
+ +
+
+

This is the default High Fidelity marketplace. Viewing and downloading content from here is fully supported in Interface.

+
+
+
+
+
+
+
+ +
+
+

Clara.io has thousands of models available for importing into High Fidelity. Follow these steps for the best experience:

+
    +
  1. Create an account here or log in as an existing user.
  2. +
  3. Choose a model from the list and click Download -> Autodesk FBX.
  4. +
  5. After the file processes, click Download.
  6. +
  7. Add the model to your asset server, then find it from the list and choose Add To World.
  8. +
+
+ +
+
+
+
+
+ + + + + + \ No newline at end of file diff --git a/scripts/system/marketplaces/clara.js b/scripts/system/marketplaces/clara.js index 67c2d5503c..a04400497f 100644 --- a/scripts/system/marketplaces/clara.js +++ b/scripts/system/marketplaces/clara.js @@ -14,15 +14,15 @@ (function() { // BEGIN LOCAL_SCOPE var toolIconUrl = Script.resolvePath("../assets/images/tools/"); -var qml = Script.resolvePath("../../../resources/qml/MarketplaceComboBox.qml") +var qml = Script.resolvePath("../../../resources/qml/Marketplaces.qml") var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace"; var marketplaceWindow = new OverlayWindow({ title: "Marketplace", source: qml, - width: 900, - height: 700, + width: 1000, + height: 900, toolWindow: false, visible: false, }); diff --git a/tests/render-texture-load/CMakeLists.txt b/tests/render-texture-load/CMakeLists.txt new file mode 100644 index 0000000000..ecf910f434 --- /dev/null +++ b/tests/render-texture-load/CMakeLists.txt @@ -0,0 +1,28 @@ + +set(TARGET_NAME render-texture-load) + +if (WIN32) + SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217") +endif() + +# This is not a testcase -- just set it up as a regular hifi project +setup_hifi_project(Quick Gui OpenGL) +set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/") + +# link in the shared libraries +link_hifi_libraries(shared octree gl gpu gpu-gl render model model-networking networking render-utils fbx entities entities-renderer animation audio avatars script-engine physics) + +package_libraries_for_deployment() + +target_zlib() +add_dependency_external_projects(quazip) +find_package(QuaZip REQUIRED) +target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${QUAZIP_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES}) + +if (WIN32) +add_paths_to_fixup_libs(${QUAZIP_DLL_PATH}) +endif () + + +target_bullet() diff --git a/tests/render-texture-load/src/main.cpp b/tests/render-texture-load/src/main.cpp new file mode 100644 index 0000000000..fd6885c381 --- /dev/null +++ b/tests/render-texture-load/src/main.cpp @@ -0,0 +1,585 @@ +// +// 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 +#include + + +#include +#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 const QTemporaryDir DATA_DIR; + + +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(); + DependencyManager::get()->init(); + _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; + } +}; + +QString fileForPath(const QString& name) { + QCryptographicHash hash(QCryptographicHash::Md5); + hash.addData(name.toLocal8Bit().data(), name.length()); + QString hashStr = QString(hash.result().toHex()); + auto dot = name.lastIndexOf('.'); + QString extension = name.right(name.length() - dot); + QString result = DATA_DIR.path() + "/" + hashStr + extension; + return result; +} + +// 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, 0); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + DependencyManager::set(); + } + + struct TextureLoad { + uint32_t time; + QString file; + QString src; + }; + + QTestWindow() { + + _currentTexture = _textures.end(); + { + QStringList stringList; + QFile textFile("h:/textures/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 = s.right(s.length() - index).trimmed(); + path = fileForPath(path); + qDebug() << "Path " << path; + if (!QFileInfo(path).exists()) { + continue; + } + _textureLoads.push({ time, path, s }); + } + } + + 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 { + } + + void keyReleaseEvent(QKeyEvent* event) override { + } + + void mouseMoveEvent(QMouseEvent* event) override { + } + + void resizeEvent(QResizeEvent* ev) override { + resizeWindow(ev->size()); + } + +private: + std::queue _textureLoads; + std::list _textures; + std::list::iterator _currentTexture; + + 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; + if (!_textureLoads.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; + _textures.push_back(DependencyManager::get()->getImageTexture(front.file)); + _currentTexture = _textures.begin(); + } + _textureLoads.pop(); + if (_textureLoads.empty()) { + qDebug() << "Done"; + } + } + } + } + + 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 (_currentTexture != _textures.end()) { + ++_currentTexture; + } + if (_currentTexture == _textures.end()) { + _currentTexture = _textures.begin(); + } + + if (_currentTexture != _textures.end()) { + batch.setResourceTexture(0, *_currentTexture); + } + 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) { + QTemporaryFile zipFile; + if (zipFile.open()) { + zipFile.write(zipData); + zipFile.close(); + } + qDebug() << zipFile.fileName(); + if (!DATA_DIR.isValid()) { + qFatal("Unable to create temp dir"); + } + + //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); + + + 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"