diff --git a/libraries/gl/src/gl/OffscreenGLCanvas.cpp b/libraries/gl/src/gl/OffscreenGLCanvas.cpp index 91f7954943..6598a26c99 100644 --- a/libraries/gl/src/gl/OffscreenGLCanvas.cpp +++ b/libraries/gl/src/gl/OffscreenGLCanvas.cpp @@ -103,12 +103,15 @@ void OffscreenGLCanvas::onMessageLogged(const QOpenGLDebugMessage& debugMessage) bool OffscreenGLCanvas::makeCurrent() { bool result = _context->makeCurrent(_offscreenSurface); - std::call_once(_reportOnce, []{ - qCDebug(glLogging) << "GL Version: " << QString((const char*) glGetString(GL_VERSION)); - qCDebug(glLogging) << "GL Shader Language Version: " << QString((const char*) glGetString(GL_SHADING_LANGUAGE_VERSION)); - qCDebug(glLogging) << "GL Vendor: " << QString((const char*) glGetString(GL_VENDOR)); - qCDebug(glLogging) << "GL Renderer: " << QString((const char*) glGetString(GL_RENDERER)); - }); + if (glGetString) { + std::call_once(_reportOnce, [] { + qCDebug(glLogging) << "GL Version: " << QString((const char*)glGetString(GL_VERSION)); + qCDebug(glLogging) << "GL Shader Language Version: " + << QString((const char*)glGetString(GL_SHADING_LANGUAGE_VERSION)); + qCDebug(glLogging) << "GL Vendor: " << QString((const char*)glGetString(GL_VENDOR)); + qCDebug(glLogging) << "GL Renderer: " << QString((const char*)glGetString(GL_RENDERER)); + }); + } return result; } diff --git a/tests/gpu/CMakeLists.txt b/tests/gpu/CMakeLists.txt new file mode 100644 index 0000000000..6974953b40 --- /dev/null +++ b/tests/gpu/CMakeLists.txt @@ -0,0 +1,9 @@ +# Declare dependencies +macro (setup_testcase_dependencies) + # link in the shared libraries + link_hifi_libraries(shared ktx gpu gl ${PLATFORM_GL_BACKEND}) + package_libraries_for_deployment() + target_opengl() +endmacro () + +setup_hifi_testcase() diff --git a/tests/gpu/src/TextureTest.cpp b/tests/gpu/src/TextureTest.cpp new file mode 100644 index 0000000000..9377a18244 --- /dev/null +++ b/tests/gpu/src/TextureTest.cpp @@ -0,0 +1,208 @@ +// +// 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 +#include +#include +#include +#include + +QTEST_MAIN(TextureTest) +#pragma optimize("", off) +#define LOAD_TEXTURE_COUNT 40 +static const QDir TEST_DIR("D:/ktx_texture_test"); + +std::string vertexShaderSource = R"SHADER( +#line 14 +layout(location = 0) out 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( +#line 28 + +uniform sampler2D tex; + +layout(location = 0) in vec2 inTexCoord0; +layout(location = 0) out vec4 outFragColor; + +void main() { + outFragColor = texture(tex, inTexCoord0); + outFragColor.a = 1.0; + //outFragColor.rb = inTexCoord0; +} + +)SHADER"; + +void TextureTest::initTestCase() { + getDefaultOpenGLSurfaceFormat(); + _canvas.create(); + if (!_canvas.makeCurrent()) { + qFatal("Unable to make test GL context current"); + } + gl::initModuleGl(); + gpu::Context::init(); + _gpuContext = std::make_shared(); + gpu::Texture::setAllowedGPUMemoryUsage(MB_TO_BYTES(4096)); + + _canvas.makeCurrent(); + { + auto VS = gpu::Shader::createVertex(vertexShaderSource); + auto PS = gpu::Shader::createPixel(fragmentShaderSource); + auto program = gpu::Shader::createProgram(VS, PS); + gpu::Shader::BindingSet slotBindings; + gpu::Shader::makeProgram(*program, slotBindings); + // If the pipeline did not exist, make it + auto state = std::make_shared(); + 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)); } + + { + auto entryList = TEST_DIR.entryList({ "*.ktx" }, QDir::Filter::Files); + _textureFiles.reserve(entryList.size()); + for (auto entry : entryList) { + auto textureFile = TEST_DIR.absoluteFilePath(entry).toStdString(); + _textureFiles.push_back(textureFile); + } + } + + { + 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]; + auto texture = gpu::Texture::unserialize(textureFile); + _textures.push_back(texture); + } + } +} + +void TextureTest::cleanupTestCase() { + _gpuContext.reset(); +} + +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& renderLambda) { + beginFrame(); + gpu::doInBatch("Test::body", _gpuContext, renderLambda); + 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() { + auto renderTexturesLamdba = [this](gpu::Batch& batch) { + batch.setPipeline(_pipeline); + for (const auto& texture : _textures) { + batch.setResourceTexture(0, texture); + batch.draw(gpu::TRIANGLE_STRIP, 4, 0); + } + }; + + size_t totalSize = 0; + for (const auto& texture : _textures) { + totalSize += texture->evalTotalSize(); + } + + auto reportLambda = [=] { + qDebug() << "Expected" << totalSize; + qDebug() << "Allowed " << gpu::Texture::getAllowedGPUMemoryUsage(); + qDebug() << "Allocated " << gpu::Context::getTextureResourceGPUMemSize(); + qDebug() << "Populated " << gpu::Context::getTextureResourcePopulatedGPUMemSize(); + qDebug() << "Pending " << gpu::Context::getTexturePendingGPUTransferMemSize(); + }; + + auto lastReport = usecTimestampNow(); + auto start = usecTimestampNow(); + while (totalSize != gpu::Context::getTextureResourceGPUMemSize()) { + reportEvery(lastReport, 4, reportLambda); + failAfter(start, 10, "Failed to allocate texture memory after 10 seconds"); + renderFrame(renderTexturesLamdba); + } + + // Restart the timer + start = usecTimestampNow(); + auto allocatedMemory = gpu::Context::getTextureResourceGPUMemSize(); + auto populatedMemory = gpu::Context::getTextureResourcePopulatedGPUMemSize(); + 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); +} + diff --git a/tests/gpu/src/TextureTest.h b/tests/gpu/src/TextureTest.h new file mode 100644 index 0000000000..f3753f5e9b --- /dev/null +++ b/tests/gpu/src/TextureTest.h @@ -0,0 +1,37 @@ +// +// Created by Bradley Austin Davis on 2018/05/08 +// Copyright 2013-2018 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 +// + +#pragma once + +#include +#include +#include + +class TextureTest : public QObject { + Q_OBJECT + +private: + void beginFrame(); + void endFrame(); + void renderFrame(const std::function& = [](gpu::Batch&){}); + +private slots: + void initTestCase(); + void cleanupTestCase(); + void testTextureLoading(); + +private: + OffscreenGLCanvas _canvas; + gpu::ContextPointer _gpuContext; + gpu::PipelinePointer _pipeline; + gpu::FramebufferPointer _framebuffer; + gpu::TexturePointer _colorBuffer, _depthBuffer; + const glm::uvec2 _size{ 640, 480 }; + std::vector _textureFiles; + std::vector _textures; +};