305 lines · 11.3 KB · C++
RawDownload
////  Created by Bradley Austin Davis on 2018/01/11//  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 "TextureTest.h" #include <iostream>#include <QtCore/QTemporaryFile> #include <gpu/Forward.h>#include <gl/Config.h>#include <gl/GLHelpers.h>#include <gpu/gl/GLBackend.h>#include <NumericalConstants.h> #include <quazip/quazip.h>#include <quazip/JlCompress.h> #include <test-utils/QTestExtensions.h>#include <test-utils/Utils.h> #include <ExternalResource.h> QTEST_MAIN(TextureTest) #define LOAD_TEXTURE_COUNT 100#define FAIL_AFTER_SECONDS 30 static const QString TEST_DATA(ExternalResource::getInstance()->getUrl(ExternalResource::Bucket::HF_Public, "/austin/test_data/test_ktx.zip"));static const QString TEST_DIR_NAME("{630b8f02-52af-4cdf-a896-24e472b94b28}");static const QString KTX_TEST_DIR_ENV("HIFI_KTX_TEST_DIR"); std::string vertexShaderSource = R"SHADER(OUTPUT(0, vec2, outTexCoord0); const vec4 VERTICES[] = vec4[](    vec4(-1.0, -1.0, 0.0, 1.0),    vec4( 1.0, -1.0, 0.0, 1.0),    vec4(-1.0,  1.0, 0.0, 1.0),    vec4( 1.0,  1.0, 0.0, 1.0)); void main() {    outTexCoord0 = VERTICES[gl_VertexID].xy;    outTexCoord0 += 1.0;    outTexCoord0 /= 2.0;    gl_Position = VERTICES[gl_VertexID];})SHADER"; std::string fragmentShaderSource = R"SHADER(uniform sampler2D tex; INPUT(0, vec2, inTexCoord0);OUTPUT(0, vec4, outFragColor); void main() {    outFragColor = texture(tex, inTexCoord0);    outFragColor.a = 1.0;    //outFragColor.rb = inTexCoord0;} )SHADER"; QtMessageHandler originalHandler; void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {#if defined(Q_OS_WIN)    OutputDebugStringA(message.toStdString().c_str());    OutputDebugStringA("\n");#endif    originalHandler(type, context, message);} void TextureTest::initTestCase() {    originalHandler = qInstallMessageHandler(messageHandler);    _resourcesPath = getTestResource("interface/resources");    getDefaultOpenGLSurfaceFormat();    _canvas.create();    if (!_canvas.makeCurrent()) {        qFatal("Unable to make test GL context current");    }    gl::initModuleGl();    gpu::Context::init<gpu::gl::GLBackend>();    _gpuContext = std::make_shared<gpu::Context>();     if (QProcessEnvironment::systemEnvironment().contains(KTX_TEST_DIR_ENV)) {        // For local testing with larger data sets        _resourcesPath = QProcessEnvironment::systemEnvironment().value(KTX_TEST_DIR_ENV);    } else {        _resourcesPath = QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/" + TEST_DIR_NAME;        if (!QFileInfo(_resourcesPath).exists()) {            QDir(_resourcesPath).mkpath(".");            downloadFile(TEST_DATA, [&](const QByteArray& data) {                QTemporaryFile zipFile;                if (zipFile.open()) {                    zipFile.write(data);                    zipFile.close();                }                JlCompress::extractDir(zipFile.fileName(), _resourcesPath);            });        }    }     QVERIFY(!_resourcesPath.isEmpty());     _canvas.makeCurrent();    {        auto VS = gpu::Shader::createVertex(shader::Source::generate(vertexShaderSource));        auto PS = gpu::Shader::createPixel(shader::Source::generate(fragmentShaderSource));        auto program = gpu::Shader::createProgram(VS, PS);        // If the pipeline did not exist, make it        auto state = std::make_shared<gpu::State>();        state->setCullMode(gpu::State::CULL_NONE);        state->setDepthTest({});        state->setBlendFunction({ false });        _pipeline = gpu::Pipeline::create(program, state);    }     _framebuffer.reset(gpu::Framebuffer::create("cached", gpu::Element::COLOR_SRGBA_32, _size.x, _size.y));     // Find the test textures    {        QDir resourcesDir(_resourcesPath);        auto entryList = resourcesDir.entryList({ "*.ktx" }, QDir::Filter::Files);        _textureFiles.reserve(entryList.size());        for (auto entry : entryList) {            auto textureFile = resourcesDir.absoluteFilePath(entry).toStdString();            _textureFiles.push_back(textureFile);        }    }     QVERIFY(!_textureFiles.empty());} void TextureTest::cleanupTestCase() {    _framebuffer.reset();    _pipeline.reset();    _gpuContext->recycle();    _gpuContext.reset();} std::vector<std::pair<gpu::TexturePointer, glm::ivec2>> TextureTest::loadTestTextures() const {    // Load the test textures    std::vector<std::pair<gpu::TexturePointer, glm::ivec2>> result;    size_t newTextureCount = std::min<size_t>(_textureFiles.size(), LOAD_TEXTURE_COUNT);    for (size_t i = 0; i < newTextureCount; ++i) {        const auto& textureFile = _textureFiles[i];        auto texture = gpu::Texture::unserialize(textureFile);        result.push_back(texture);    }    return result;} void TextureTest::beginFrame() {    _gpuContext->recycle();    _gpuContext->beginFrame();    gpu::doInBatch("TestWindow::beginFrame", _gpuContext, [&](gpu::Batch& batch) {        batch.setFramebuffer(_framebuffer);        batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLORS, { 0.0f, 0.1f, 0.2f, 1.0f });        batch.clearDepthFramebuffer(1e4);        batch.setViewportTransform({ 0, 0, _size.x, _size.y });    });} void TextureTest::endFrame() {    gpu::doInBatch("TestWindow::endFrame::finish", _gpuContext, [&](gpu::Batch& batch) { batch.resetStages(); });    auto framePointer = _gpuContext->endFrame();    _gpuContext->consumeFrameUpdates(framePointer);    _gpuContext->executeFrame(framePointer);    // Simulate swapbuffers with a finish    glFinish();    QThread::msleep(10);} void TextureTest::renderFrame(const std::function<void(gpu::Batch&)>& renderLambda) {    beginFrame();    gpu::doInBatch("Test::body", _gpuContext, renderLambda);    endFrame();    ++_frameCount;}extern QString getTextureMemoryPressureModeString(); void TextureTest::testTextureLoading() {    QBENCHMARK {        _frameCount = 0;        auto textures = loadTestTextures();        QVERIFY(textures.size() > 0);        auto renderTexturesLamdba = [&](gpu::Batch& batch) {            batch.setPipeline(_pipeline);            for (const auto& texture : textures) {                batch.setResourceTexture(0, texture.first);                batch.draw(gpu::TRIANGLE_STRIP, 4, 0);            }        };         size_t expectedAllocation = 0;        for (const auto& texture : textures) {            expectedAllocation += texture.first->evalTotalSize();        }        QVERIFY(textures.size() > 0);         auto reportLambda = [=] {            qDebug() << "Allowed   " << gpu::Texture::getAllowedGPUMemoryUsage();            qDebug() << "Allocated " << gpu::Context::getTextureResourceGPUMemSize();            qDebug() << "Populated " << gpu::Context::getTextureResourcePopulatedGPUMemSize();            qDebug() << "Pending   " << gpu::Context::getTexturePendingGPUTransferMemSize();            qDebug() << "State     " << getTextureMemoryPressureModeString();        };         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();        qDebug() << "Awaiting texture allocation";        while (expectedAllocation != allocatedMemory) {            doEvery(lastReport, 4, reportLambda);            failAfter(start, FAIL_AFTER_SECONDS, "Failed to allocate texture memory");            renderFrame(renderTexturesLamdba);            allocatedMemory = gpu::Context::getTextureResourceGPUMemSize();            populatedMemory = gpu::Context::getTextureResourcePopulatedGPUMemSize();        }        reportLambda();        QCOMPARE(allocatedMemory, expectedAllocation);         // Restart the timer        start = usecTimestampNow();        // Cycle frames we're fully populated        qDebug() << "Awaiting texture population";        while (allocatedMemory != populatedMemory || 0 != gpu::Context::getTexturePendingGPUTransferMemSize()) {            doEvery(lastReport, 4, reportLambda);            failAfter(start, FAIL_AFTER_SECONDS, "Failed to populate texture memory");            renderFrame();            allocatedMemory = gpu::Context::getTextureResourceGPUMemSize();            populatedMemory = gpu::Context::getTextureResourcePopulatedGPUMemSize();        }        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        qDebug() << "Awaiting texture deallocation";        while (allocatedMemory > maxMemory || allocatedMemory != populatedMemory) {            doEvery(lastReport, 4, reportLambda);            failAfter(start, FAIL_AFTER_SECONDS, "Failed to deallocate texture memory");            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        qDebug() << "Awaiting texture reallocation and repopulation";        while (allocatedMemory != expectedAllocation || allocatedMemory != populatedMemory) {            doEvery(lastReport, 4, reportLambda);            failAfter(start, FAIL_AFTER_SECONDS, "Failed to populate texture memory");            renderFrame();            allocatedMemory = gpu::Context::getTextureResourceGPUMemSize();            populatedMemory = gpu::Context::getTextureResourcePopulatedGPUMemSize();        }        reportLambda();        QCOMPARE(allocatedMemory, expectedAllocation);        QCOMPARE(populatedMemory, allocatedMemory);         textures.clear();        // Cycle frames we're fully populated        qDebug() << "Awaiting texture deallocation";        while (allocatedMemory != 0) {            failAfter(start, FAIL_AFTER_SECONDS, "Failed to clear texture memory");            renderFrame();            allocatedMemory = gpu::Context::getTextureResourceGPUMemSize();            populatedMemory = gpu::Context::getTextureResourcePopulatedGPUMemSize();        }        reportLambda();        QCOMPARE(allocatedMemory, 0);        QCOMPARE(populatedMemory, 0);        qDebug() << "Test took " << _frameCount << "frame";    }    qDebug() << "Done";}