/**************************************************************************** ** ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of Qt 3D Studio. ** ** $QT_BEGIN_LICENSE:GPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 or (at your option) any later version ** approved by the KDE Free Qt Foundation. The licenses are as published by ** the Free Software Foundation and appearing in the file LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qdragonrenderaspect_p.h" // General #include // Frontend nodes #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 #include #include #include #include #include // Frame graph nodes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Jobs #include #include // Qt3DCore #include #include #include #include #include #include // Qt Core #include #include // redefines MemoryBarrier #include #if defined(_MSC_VER) #pragma warning(disable : 4503) #endif using namespace Qt3DCore; /*! \internal QDragonRenderAspect is an experimental render aspect written in a somewhat functional programming style with a goal of improving performance and safety. It introduces: - A task system that wraps around QAspectJob and functions. It makes dependencies explicit and verified at compile time, and automates passing results from one task to the next. It also simplifies caching results between frames. - An immutable/copy-on-write wrapper around backend objects. This is introduced to simplify data sharing between different threads and improve safety. - A new container with convenience functions for iterating over new, dirty and removed objects. This is introduced to simplify tracking the dirty state of frontend objects, through their backend representation, towards loading data in memory and finally onto the CPU. This render aspect supports the features needed by Qt3D Studio Runtime, but lacks some of the features currently present in Qt3D. This is the reason why it is currently being developed in qt3d-runtime. Once feature complete, it can be ported back to Qt3D as the default render aspect. The render aspect is based on the following principles, which are meant to be broken (but try not to): - Jobs should only receive copies or const references to immutable data. - Jobs should have no access to outside state (no pointers to Renderer* or resource managers). - Jobs should be functions and not functors. The design has some benefits: - No shared state between jobs. - Gets rid of the generator concept by simplifying loading of textures in jobs. The design also has some caveats or challenges: - The copy-on-write strategy makes it hard to see exactly where a copy is made, except at runtime. However, this can be alleviated by adding debug assertions that try to detect and warn about unintentional copying. - Resource handlers, such as GLBuffer, should not be copyable and need to be changed during the course of an application. This means that neither an immutable nor copy-on-write strategy is suitable. We therefore introduced Mutable, which is a type that explicitly gets invalidated if another copy (reference) to the same resource is changed. This makes it easier to detect use of a changed resource by error. # TODO # - Add shim-functions for all QOpenGLFunctions calls so that we can create unit tests that verify the OpenGL-ish output (before the functions are translated into real functions, which of course would change with time). - Consider using the new change distribution system, while we're at it - Fix issue with "QAbstractTextureImage was shared", might need to become able able to collect changes for change arbiter at construction time. (Perhaps solved with new change system?) - skinning palette - render capture - buffer capture */ QT_BEGIN_NAMESPACE namespace Qt3DRender { namespace Dragon { QDragonRenderAspect::QDragonRenderAspect(Renderer::RenderType renderType) : m_renderer(new Renderer(renderType)) { qDebug() << "Dragon render aspect enabled"; registerBackendType(m_entities); registerBackendType(m_transforms); registerBackendType(m_textures); registerBackendType(m_textureImages); registerBackendType(m_attributes); registerBackendType(m_buffers); registerBackendType(m_cameraLenses); registerBackendType(m_renderTargets); registerBackendType(m_renderTargetOutputs); registerBackendType(m_geometries); registerBackendType(m_geometryRenderers); registerBackendType(m_renderPasses); registerBackendType(m_parameters); registerBackendType(m_effects); registerBackendType(m_materials); registerBackendType(m_techniques); registerBackendType(m_filterKeys); registerBackendType(m_shaderDatas); registerBackendType(m_shaders); registerBackendType(m_renderStates); registerBackendType(m_scene2ds); // Custom functors registerBackendType(QSharedPointer::create(this)); // Framegraph registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); // TODO add this back when MemoryBarrier is expanded correctly on MinGW... // registerBackendType( // NodeFunctorPtr::create(&m_frameGraphNodesContainer)); registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); registerBackendType( NodeFunctorPtr::create(&m_frameGraphNodesContainer)); // TODO could be done in header or initializer // TODO syntax could be improved, consider adding a createEdge/setInput function m_calculateWorldTransforms = TaskPtr>::create( calculateWorldTransforms, Self, m_entities, m_transforms, m_rootEntitySource); m_loadBuffers = TaskPtr::create(loadBuffers, Self, m_buffers); m_calculateLocalBoundingVolumes = TaskPtr::create( calculateLocalBoundingVolumes, Self, m_entities, m_attributes, m_geometryRenderers, m_geometries, m_loadBuffers); m_calculateWorldBoundingVolumes = TaskPtr::create( calculateWorldBoundingVolumes, Self, m_calculateLocalBoundingVolumes, m_calculateWorldTransforms); m_printTransforms = TaskPtr::create(printTransforms, m_calculateWorldTransforms); m_loadTextureImages = TaskPtr::create(loadTextureImages, Self, m_textureImages); m_loadTextures = TaskPtr::create(loadTextures, Self, m_textures, m_loadTextureImages); m_updateScene2Ds = TaskPtr>::create(updateScene2Ds, Self, m_scene2ds, m_snaggedTexturesSource, m_renderTargetOutputs, m_shareContextSource); // m_loadShaders = TaskPtr>::create(uploadShaders, Self, m_shaders); m_generateFrameGraph = TaskPtr::create(generateInheritanceTable>, Self, m_frameGraphNodes, m_rootFrameGraphNodeSource); m_buildRenderViews = TaskPtr::create(buildRenderViews, Self, m_generateFrameGraph, m_frameGraphNodes, m_entities, m_cameraLenses, m_renderTargets, m_renderTargetOutputs, m_renderStates); const auto calculateCameraMatrices = [](ValueContainer renderViewCameraMatrices, RenderViews renderViews, ValueContainer worldTransforms) { const auto cameraMatrixForRenderView = [worldTransforms](QNodeId id, Immutable renderView) { Q_UNUSED(id); Immutable cameraNode = renderView->cameraNode; Immutable cameraLens = renderView->cameraLens; CameraMatrices cameraMatrices; const Matrix4x4 cameraWorld = Matrix4x4(*(worldTransforms[cameraNode->peerId()])); const auto &viewMatrix = cameraLens->viewMatrix(cameraWorld); cameraMatrices.viewMatrix = viewMatrix; cameraMatrices.viewProjectionMatrix = cameraLens->projectionMatrix() * viewMatrix; //To get the eyePosition of the camera, we need to use the inverse of the // camera's worldTransform matrix. const Matrix4x4 inverseWorldTransform = viewMatrix.inverted(); const Vector3D eyePosition(inverseWorldTransform.column(3)); cameraMatrices.eyePosition = eyePosition; // Get the viewing direction of the camera. Use the normal matrix to // ensure non-uniform scale works too. const QMatrix3x3 normalMat = convertToQMatrix4x4(viewMatrix).normalMatrix(); // dir = normalize(QVector3D(0, 0, -1) * normalMat) cameraMatrices.eyeViewDirection = Vector3D(-normalMat(2, 0), -normalMat(2, 1), -normalMat(2, 2)).normalized(); return cameraMatrices; }; renderViewCameraMatrices = rebuildAll(std::move(renderViewCameraMatrices), renderViews, cameraMatrixForRenderView); return renderViewCameraMatrices; }; auto cameraMatrices = TaskPtr>::create(calculateCameraMatrices, Self, m_buildRenderViews, m_calculateWorldTransforms); auto requestContextInfoFunction = [this](GraphicsApiFilterData previous, const RenderViews &renderViews) { if (previous.m_major != 0) { // no need to do this every frame ;) // TODO use a better "flag" than the major version return previous; } auto result = m_renderer->contextInfo(renderViews); return result; }; m_requestContextInfo = TaskPtr::create(requestContextInfoFunction, Self, m_buildRenderViews); m_gatherParameters = TaskPtr::create(gatherMaterialParameters, Self, m_buildRenderViews, m_requestContextInfo, m_parameters, m_materials, m_effects, m_techniques, m_renderPasses, m_filterKeys); // TODO should filter entities by renderable // TODO should filter entities by layer // TODO should filter entities by frustum culling // TODO should filter entities by proximity m_buildRenderCommands = TaskPtr::create(buildDrawRenderCommands, Self, m_buildRenderViews, m_entities, m_materials, m_geometries, m_geometryRenderers, m_shaders, m_renderStates, m_gatherParameters); // m_applyParameters = TaskPtr::create(applyParameters, // Self, // m_buildRenderCommands, // m_calculateWorldTransforms, // m_parameters); const auto sortedCommands = TaskPtr::create(sortRenderCommands, Self, m_buildRenderCommands, m_calculateWorldBoundingVolumes, cameraMatrices); // NOTE this is the only job that needs to know about anything external (the Renderer) // It would be nice to also make this functional, but I currently have no good solution for // this. auto uploadRenderViewsFunction = [this](const TreeInfo &frameGraph, const RenderViews &renderViews, const RenderCommands &renderCommands, const LoadedTextures &loadedTextures, const LoadedBuffers &loadedBuffers, const ValueContainer &shaders, const ValueContainer &attributes, const ValueContainer ¶meters, const ValueContainer &worldTransforms, const ValueContainer &scene2ds, const ValueContainer &renderViewCameraMatrices) { // TODO how do we handle incremental changes? // TODO if nothing changed, do we really need to upload again? FrameInput data; data.renderViews = renderViews; // TODO consider joining commands and views to avoid having to look up commands based on view id data.renderCommands = renderCommands; // TODO would also be better to have camera matrices go into the same object as // views and commands to reduce lookups on render thread data.renderViewCameraMatrices = renderViewCameraMatrices; data.loadedTextures = loadedTextures; data.loadedBuffers = loadedBuffers; data.shaders = shaders; data.attributes = attributes; data.parameters = parameters; data.worldTransforms = worldTransforms; data.frameGraph = frameGraph; data.scene2ds = scene2ds; m_renderer->addLatestData(data); return true; }; m_uploadRenderViews = TaskPtr::create(uploadRenderViewsFunction, m_generateFrameGraph, m_buildRenderViews, sortedCommands, m_loadTextures, m_loadBuffers, m_shaders, m_attributes, m_parameters, m_calculateWorldTransforms, m_updateScene2Ds, cameraMatrices); // TODO would be nice to have a taskmanager that all the above are put into m_jobs = { m_calculateWorldTransforms, m_calculateLocalBoundingVolumes, m_calculateWorldBoundingVolumes, m_printTransforms, m_loadTextures, m_loadTextureImages, m_updateScene2Ds, m_loadBuffers, m_generateFrameGraph, m_buildRenderViews, m_gatherParameters, m_buildRenderCommands, m_uploadRenderViews, m_requestContextInfo, cameraMatrices, sortedCommands }; // TODO would be nice to automate this in a task-manager as well auto resetFunction = [this]() { m_entities->reset(); m_transforms->reset(); m_textureImages->reset(); m_textures->reset(); m_buffers->reset(); m_geometries->reset(); m_attributes->reset(); m_renderPasses->reset(); m_geometryRenderers->reset(); m_shaderDatas->reset(); m_shaders->reset(); m_renderStates->reset(); m_materials->reset(); m_effects->reset(); m_techniques->reset(); m_renderPasses->reset(); m_filterKeys->reset(); m_parameters->reset(); m_cameraLenses->reset(); m_renderTargets->reset(); m_renderTargetOutputs->reset(); m_renderStates->reset(); m_frameGraphNodes->reset(); return true; }; // TODO a bit annoying that we need this job at all m_resetJobs = TaskPtr::create(resetFunction); for (const auto &job : m_jobs) { m_resetJobs->addDependency(job); } m_jobs.append(m_resetJobs); } // Waits to be told to create jobs for the next frame // Called by QRenderAspect jobsToExecute context of QAspectThread // Returns all the jobs (and with proper dependency chain) required // for the rendering of the scene QVector QDragonRenderAspect::jobsToExecute(qint64 time) { Q_UNUSED(time) // TODO remove this once we figure out why the textures need more time... if (m_renderSettings == nullptr) { qWarning() << "RenderSettings missing. Dragon render aspect not starting."; return {}; } m_renderer->nextFrameSemaphore.acquire(); m_rootFrameGraphNodeSource->setInput(m_renderSettings->activeFrameGraphID()); m_rootEntitySource->setInput(rootEntityId()); m_shareContextSource->setInput(m_renderer->shareContext()); m_snaggedTexturesSource->setInput(&m_renderer->textures()); // TODO add all to a task container that generates this return m_jobs; } Renderer::Frame QDragonRenderAspect::renderSynchronous(Renderer::Frame frame) { return m_renderer->doRender(std::move(frame)); } void QDragonRenderAspect::initialize(QOpenGLContext *context) { m_renderer->initialize(context); } void QDragonRenderAspect::beginRenderShutdown() { m_renderer->beginShutdown(); } void QDragonRenderAspect::endRenderShutdown() { m_renderer->endShutdown(); } } // namespace Dragon } // namespace Qt3DRender QT_END_NAMESPACE