diff --git a/libraries/test-utils/CMakeLists.txt b/libraries/test-utils/CMakeLists.txt new file mode 100644 index 0000000000..2c23e96c1e --- /dev/null +++ b/libraries/test-utils/CMakeLists.txt @@ -0,0 +1,3 @@ +set(TARGET_NAME test-utils) +setup_hifi_library(Network Gui) + diff --git a/libraries/test-utils/src/test-utils/FileDownloader.cpp b/libraries/test-utils/src/test-utils/FileDownloader.cpp new file mode 100644 index 0000000000..09049e3e0c --- /dev/null +++ b/libraries/test-utils/src/test-utils/FileDownloader.cpp @@ -0,0 +1,21 @@ +#include "FileDownloader.h" + +#include +#include + +FileDownloader::FileDownloader(QUrl url, const Handler& handler, QObject* parent) : QObject(parent), _handler(handler) { + connect(&_accessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(fileDownloaded(QNetworkReply*))); + _accessManager.get(QNetworkRequest(url)); +} + +void FileDownloader::waitForDownload() { + while (!_complete) { + QCoreApplication::processEvents(); + } +} + +void FileDownloader::fileDownloaded(QNetworkReply* pReply) { + _handler(pReply->readAll()); + pReply->deleteLater(); + _complete = true; +} diff --git a/libraries/test-utils/src/test-utils/FileDownloader.h b/libraries/test-utils/src/test-utils/FileDownloader.h new file mode 100644 index 0000000000..5a618fcf45 --- /dev/null +++ b/libraries/test-utils/src/test-utils/FileDownloader.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +class FileDownloader : public QObject { + Q_OBJECT + +public: + using Handler = std::function; + + FileDownloader(QUrl url, const Handler& handler, QObject* parent = 0); + + void waitForDownload(); + +private slots: + void fileDownloaded(QNetworkReply* pReply); + +private: + QNetworkAccessManager _accessManager; + Handler _handler; + bool _complete { false }; +}; diff --git a/tests/QTestExtensions.h b/tests/QTestExtensions.h index b7b9795a9a..c43ef3f73e 100644 --- a/tests/QTestExtensions.h +++ b/tests/QTestExtensions.h @@ -13,8 +13,9 @@ #define hifi_QTestExtensions_hpp #include +#include #include - +#include #include "GLMTestUtils.h" // Implements several extensions to QtTest. @@ -302,3 +303,43 @@ inline auto errorTest (float actual, float expected, float acceptableRelativeErr QCOMPARE_WITH_LAMBDA(actual, expected, errorTest(actual, expected, relativeError)) + +inline QString getTestResource(const QString& relativePath) { + static QDir dir; + static std::once_flag once; + std::call_once(once, []{ + QFileInfo fileInfo(__FILE__); + auto parentDir = fileInfo.absoluteDir(); + auto rootDir = parentDir.absoluteFilePath(".."); + dir = QDir::cleanPath(rootDir); + }); + + return QDir::cleanPath(dir.absoluteFilePath(relativePath)); +} + +inline bool afterUsecs(uint64_t& startUsecs, uint64_t maxIntervalUecs) { + auto now = usecTimestampNow(); + auto interval = now - startUsecs; + if (interval > maxIntervalUecs) { + startUsecs = now; + return true; + } + return false; +} + +inline bool afterSecs(uint64_t& startUsecs, uint64_t maxIntervalSecs) { + return afterUsecs(startUsecs, maxIntervalSecs * USECS_PER_SECOND); +} + +template +void reportEvery(uint64_t& lastReportUsecs, uint64_t secs, F lamdba) { + if (afterSecs(lastReportUsecs, secs)) { + lamdba(); + } +} + +inline void failAfter(uint64_t startUsecs, uint64_t secs, const char* message) { + if (afterSecs(startUsecs, secs)) { + QFAIL(message); + } +} diff --git a/tests/gpu/CMakeLists.txt b/tests/gpu/CMakeLists.txt index 6974953b40..ad0eac5822 100644 --- a/tests/gpu/CMakeLists.txt +++ b/tests/gpu/CMakeLists.txt @@ -1,9 +1,17 @@ # Declare dependencies macro (setup_testcase_dependencies) # link in the shared libraries - link_hifi_libraries(shared ktx gpu gl ${PLATFORM_GL_BACKEND}) + link_hifi_libraries(shared test-utils ktx gpu gl ${PLATFORM_GL_BACKEND}) package_libraries_for_deployment() target_opengl() + target_zlib() + 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}) + add_dependency_external_projects(wasapi) + endif () endmacro () setup_hifi_testcase() diff --git a/tests/gpu/src/TextureTest.cpp b/tests/gpu/src/TextureTest.cpp index 9377a18244..f896914579 100644 --- a/tests/gpu/src/TextureTest.cpp +++ b/tests/gpu/src/TextureTest.cpp @@ -8,16 +8,27 @@ #include "TextureTest.h" +#include + #include #include #include #include #include +#include + +#include +#include + +#include "../../QTestExtensions.h" + +#pragma optimize("", off) QTEST_MAIN(TextureTest) -#pragma optimize("", off) + #define LOAD_TEXTURE_COUNT 40 -static const QDir TEST_DIR("D:/ktx_texture_test"); + +static const QString TEST_DATA("https://hifi-public.s3.amazonaws.com/austin/test_data/test_ktx.zip"); std::string vertexShaderSource = R"SHADER( #line 14 @@ -54,7 +65,10 @@ void main() { )SHADER"; +#define USE_SERVER_DATA 1 + void TextureTest::initTestCase() { + _resourcesPath = getTestResource("interface/resources"); getDefaultOpenGLSurfaceFormat(); _canvas.create(); if (!_canvas.makeCurrent()) { @@ -63,7 +77,32 @@ void TextureTest::initTestCase() { gl::initModuleGl(); gpu::Context::init(); _gpuContext = std::make_shared(); - gpu::Texture::setAllowedGPUMemoryUsage(MB_TO_BYTES(4096)); +#if USE_SERVER_DATA + if (!_testDataDir.isValid()) { + qFatal("Unable to create temp directory"); + } + + QString path = _testDataDir.path(); + FileDownloader(TEST_DATA, + [&](const QByteArray& data) { + QTemporaryFile zipFile; + if (zipFile.open()) { + zipFile.write(data); + zipFile.close(); + } + if (!_testDataDir.isValid()) { + qFatal("Unable to create temp dir"); + } + auto files = JlCompress::extractDir(zipFile.fileName(), _testDataDir.path()); + for (const auto& file : files) { + qDebug() << file; + } + }) + .waitForDownload(); + _resourcesPath = _testDataDir.path(); +#else + _resourcesPath = "D:/test_ktx"; +#endif _canvas.makeCurrent(); { @@ -80,19 +119,21 @@ void TextureTest::initTestCase() { _pipeline = gpu::Pipeline::create(program, state); } - { _framebuffer.reset(gpu::Framebuffer::create("cached", gpu::Element::COLOR_SRGBA_32, _size.x, _size.y)); } + _framebuffer.reset(gpu::Framebuffer::create("cached", gpu::Element::COLOR_SRGBA_32, _size.x, _size.y)); + // Find the test textures { - auto entryList = TEST_DIR.entryList({ "*.ktx" }, QDir::Filter::Files); + QDir resourcesDir(_resourcesPath); + auto entryList = resourcesDir.entryList({ "*.ktx" }, QDir::Filter::Files); _textureFiles.reserve(entryList.size()); for (auto entry : entryList) { - auto textureFile = TEST_DIR.absoluteFilePath(entry).toStdString(); + auto textureFile = resourcesDir.absoluteFilePath(entry).toStdString(); _textureFiles.push_back(textureFile); } } + // Load the test textures { - std::shuffle(_textureFiles.begin(), _textureFiles.end(), std::default_random_engine()); size_t newTextureCount = std::min(_textureFiles.size(), LOAD_TEXTURE_COUNT); for (size_t i = 0; i < newTextureCount; ++i) { const auto& textureFile = _textureFiles[i]; @@ -103,6 +144,9 @@ void TextureTest::initTestCase() { } void TextureTest::cleanupTestCase() { + _framebuffer.reset(); + _pipeline.reset(); + _gpuContext->recycle(); _gpuContext.reset(); } @@ -133,35 +177,8 @@ void TextureTest::renderFrame(const std::function& renderLamb endFrame(); } - -inline bool afterUsecs(uint64_t& startUsecs, uint64_t maxIntervalUecs) { - auto now = usecTimestampNow(); - auto interval = now - startUsecs; - if (interval > maxIntervalUecs) { - startUsecs = now; - return true; - } - return false; -} - -inline bool afterSecs(uint64_t& startUsecs, uint64_t maxIntervalSecs) { - return afterUsecs(startUsecs, maxIntervalSecs * USECS_PER_SECOND); -} - -template -void reportEvery(uint64_t& lastReportUsecs, uint64_t secs, F lamdba) { - if (afterSecs(lastReportUsecs, secs)) { - lamdba(); - } -} - -inline void failAfter(uint64_t startUsecs, uint64_t secs, const char* message) { - if (afterSecs(startUsecs, secs)) { - qFatal(message); - } -} - void TextureTest::testTextureLoading() { + QVERIFY(_textures.size() > 0); auto renderTexturesLamdba = [this](gpu::Batch& batch) { batch.setPipeline(_pipeline); for (const auto& texture : _textures) { @@ -170,39 +187,87 @@ void TextureTest::testTextureLoading() { } }; - size_t totalSize = 0; + size_t expectedAllocation = 0; for (const auto& texture : _textures) { - totalSize += texture->evalTotalSize(); + expectedAllocation += texture->evalTotalSize(); } + QVERIFY(_textures.size() > 0); auto reportLambda = [=] { - qDebug() << "Expected" << totalSize; - qDebug() << "Allowed " << gpu::Texture::getAllowedGPUMemoryUsage(); + qDebug() << "Allowed " << gpu::Texture::getAllowedGPUMemoryUsage(); qDebug() << "Allocated " << gpu::Context::getTextureResourceGPUMemSize(); qDebug() << "Populated " << gpu::Context::getTextureResourcePopulatedGPUMemSize(); qDebug() << "Pending " << gpu::Context::getTexturePendingGPUTransferMemSize(); }; + auto allocatedMemory = gpu::Context::getTextureResourceGPUMemSize(); + auto populatedMemory = gpu::Context::getTextureResourcePopulatedGPUMemSize(); + + // Cycle frames we're fully allocated + // We need to use the texture rendering lambda auto lastReport = usecTimestampNow(); auto start = usecTimestampNow(); - while (totalSize != gpu::Context::getTextureResourceGPUMemSize()) { + while (expectedAllocation != allocatedMemory) { reportEvery(lastReport, 4, reportLambda); failAfter(start, 10, "Failed to allocate texture memory after 10 seconds"); renderFrame(renderTexturesLamdba); + allocatedMemory = gpu::Context::getTextureResourceGPUMemSize(); + populatedMemory = gpu::Context::getTextureResourcePopulatedGPUMemSize(); } + QCOMPARE(allocatedMemory, expectedAllocation); // Restart the timer start = usecTimestampNow(); - auto allocatedMemory = gpu::Context::getTextureResourceGPUMemSize(); - auto populatedMemory = gpu::Context::getTextureResourcePopulatedGPUMemSize(); - while (allocatedMemory != populatedMemory && 0 != gpu::Context::getTexturePendingGPUTransferMemSize()) { + // Cycle frames we're fully populated + while (allocatedMemory != populatedMemory || 0 != gpu::Context::getTexturePendingGPUTransferMemSize()) { reportEvery(lastReport, 4, reportLambda); failAfter(start, 10, "Failed to populate texture memory after 10 seconds"); renderFrame(); allocatedMemory = gpu::Context::getTextureResourceGPUMemSize(); populatedMemory = gpu::Context::getTextureResourcePopulatedGPUMemSize(); } - QCOMPARE(allocatedMemory, totalSize); - QCOMPARE(populatedMemory, totalSize); -} + reportLambda(); + QCOMPARE(populatedMemory, allocatedMemory); + // FIXME workaround a race condition in the difference between populated size and the actual _populatedMip value in the texture + for (size_t i = 0; i < _textures.size(); ++i) { + renderFrame(); + } + + // Test on-demand deallocation of memory + auto maxMemory = allocatedMemory / 2; + gpu::Texture::setAllowedGPUMemoryUsage(maxMemory); + + // Restart the timer + start = usecTimestampNow(); + // Cycle frames until the allocated memory is below the max memory + while (allocatedMemory > maxMemory || allocatedMemory != populatedMemory) { + reportEvery(lastReport, 4, reportLambda); + failAfter(start, 10, "Failed to deallocate texture memory after 10 seconds"); + renderFrame(renderTexturesLamdba); + allocatedMemory = gpu::Context::getTextureResourceGPUMemSize(); + populatedMemory = gpu::Context::getTextureResourcePopulatedGPUMemSize(); + } + reportLambda(); + + // Verify that the allocation is now below the target + QVERIFY(allocatedMemory <= maxMemory); + // Verify that populated memory is the same as allocated memory + QCOMPARE(populatedMemory, allocatedMemory); + + // Restart the timer + start = usecTimestampNow(); + // Reset the max memory to automatic + gpu::Texture::setAllowedGPUMemoryUsage(0); + // Cycle frames we're fully populated + while (allocatedMemory != expectedAllocation || allocatedMemory != populatedMemory) { + reportEvery(lastReport, 4, reportLambda); + failAfter(start, 10, "Failed to populate texture memory after 10 seconds"); + renderFrame(); + allocatedMemory = gpu::Context::getTextureResourceGPUMemSize(); + populatedMemory = gpu::Context::getTextureResourcePopulatedGPUMemSize(); + } + reportLambda(); + QCOMPARE(allocatedMemory, expectedAllocation); + QCOMPARE(populatedMemory, allocatedMemory); +} diff --git a/tests/gpu/src/TextureTest.h b/tests/gpu/src/TextureTest.h index f3753f5e9b..9d3dc4ab81 100644 --- a/tests/gpu/src/TextureTest.h +++ b/tests/gpu/src/TextureTest.h @@ -9,6 +9,8 @@ #pragma once #include +#include + #include #include @@ -18,7 +20,7 @@ class TextureTest : public QObject { private: void beginFrame(); void endFrame(); - void renderFrame(const std::function& = [](gpu::Batch&){}); + void renderFrame(const std::function& = [](gpu::Batch&) {}); private slots: void initTestCase(); @@ -26,6 +28,8 @@ private slots: void testTextureLoading(); private: + QString _resourcesPath; + QTemporaryDir _testDataDir; OffscreenGLCanvas _canvas; gpu::ContextPointer _gpuContext; gpu::PipelinePointer _pipeline;