| 1 | /**************************************************************************** |
| 2 | ** |
| 3 | ** Copyright (C) 2014 Klaralvdalens Datakonsult AB (KDAB). |
| 4 | ** Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). |
| 5 | ** Contact: https://www.qt.io/licensing/ |
| 6 | ** |
| 7 | ** This file is part of the Qt3D module of the Qt Toolkit. |
| 8 | ** |
| 9 | ** $QT_BEGIN_LICENSE:LGPL$ |
| 10 | ** Commercial License Usage |
| 11 | ** Licensees holding valid commercial Qt licenses may use this file in |
| 12 | ** accordance with the commercial license agreement provided with the |
| 13 | ** Software or, alternatively, in accordance with the terms contained in |
| 14 | ** a written agreement between you and The Qt Company. For licensing terms |
| 15 | ** and conditions see https://www.qt.io/terms-conditions. For further |
| 16 | ** information use the contact form at https://www.qt.io/contact-us. |
| 17 | ** |
| 18 | ** GNU Lesser General Public License Usage |
| 19 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
| 20 | ** General Public License version 3 as published by the Free Software |
| 21 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| 22 | ** packaging of this file. Please review the following information to |
| 23 | ** ensure the GNU Lesser General Public License version 3 requirements |
| 24 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| 25 | ** |
| 26 | ** GNU General Public License Usage |
| 27 | ** Alternatively, this file may be used under the terms of the GNU |
| 28 | ** General Public License version 2.0 or (at your option) the GNU General |
| 29 | ** Public license version 3 or any later version approved by the KDE Free |
| 30 | ** Qt Foundation. The licenses are as published by the Free Software |
| 31 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| 32 | ** included in the packaging of this file. Please review the following |
| 33 | ** information to ensure the GNU General Public License requirements will |
| 34 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| 35 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
| 36 | ** |
| 37 | ** $QT_END_LICENSE$ |
| 38 | ** |
| 39 | ****************************************************************************/ |
| 40 | |
| 41 | #include "renderview_p.h" |
| 42 | #include <Qt3DRender/qmaterial.h> |
| 43 | #include <Qt3DRender/qrenderaspect.h> |
| 44 | #include <Qt3DRender/qrendertarget.h> |
| 45 | #include <Qt3DRender/qabstractlight.h> |
| 46 | #include <Qt3DRender/private/sphere_p.h> |
| 47 | |
| 48 | #include <Qt3DRender/private/cameraselectornode_p.h> |
| 49 | #include <Qt3DRender/private/framegraphnode_p.h> |
| 50 | #include <Qt3DRender/private/layerfilternode_p.h> |
| 51 | #include <Qt3DRender/private/qparameter_p.h> |
| 52 | #include <Qt3DRender/private/cameralens_p.h> |
| 53 | #include <Qt3DRender/private/effect_p.h> |
| 54 | #include <Qt3DRender/private/entity_p.h> |
| 55 | #include <Qt3DRender/private/nodemanagers_p.h> |
| 56 | #include <Qt3DRender/private/layer_p.h> |
| 57 | #include <Qt3DRender/private/renderlogging_p.h> |
| 58 | #include <Qt3DRender/private/renderpassfilternode_p.h> |
| 59 | #include <Qt3DRender/private/renderpass_p.h> |
| 60 | #include <Qt3DRender/private/geometryrenderer_p.h> |
| 61 | #include <Qt3DRender/private/techniquefilternode_p.h> |
| 62 | #include <Qt3DRender/private/viewportnode_p.h> |
| 63 | #include <Qt3DRender/private/buffermanager_p.h> |
| 64 | #include <Qt3DRender/private/geometryrenderermanager_p.h> |
| 65 | #include <Qt3DRender/private/rendercapture_p.h> |
| 66 | #include <Qt3DRender/private/buffercapture_p.h> |
| 67 | #include <Qt3DRender/private/stringtoint_p.h> |
| 68 | #include <Qt3DRender/private/renderlogging_p.h> |
| 69 | #include <Qt3DRender/private/renderstateset_p.h> |
| 70 | #include <rendercommand_p.h> |
| 71 | #include <renderer_p.h> |
| 72 | #include <graphicscontext_p.h> |
| 73 | #include <submissioncontext_p.h> |
| 74 | #include <glresourcemanagers_p.h> |
| 75 | #include <Qt3DCore/qentity.h> |
| 76 | #include <QtGui/qsurface.h> |
| 77 | #include <algorithm> |
| 78 | #include <atomic> |
| 79 | #include <gllights_p.h> |
| 80 | #include <QDebug> |
| 81 | #if defined(QT3D_RENDER_VIEW_JOB_TIMINGS) |
| 82 | #include <QElapsedTimer> |
| 83 | #endif |
| 84 | |
| 85 | QT_BEGIN_NAMESPACE |
| 86 | |
| 87 | namespace Qt3DRender { |
| 88 | namespace Render { |
| 89 | namespace OpenGL { |
| 90 | |
| 91 | namespace { |
| 92 | |
| 93 | // register our QNodeId's as a metatype during program loading |
| 94 | const int Q_DECL_UNUSED qNodeIdTypeId = qMetaTypeId<Qt3DCore::QNodeId>(); |
| 95 | |
| 96 | std::atomic_bool wasInitialized{}; |
| 97 | |
| 98 | } // anonymous namespace |
| 99 | |
| 100 | RenderView::StandardUniformsNameToTypeHash RenderView::ms_standardUniformSetters; |
| 101 | |
| 102 | |
| 103 | RenderView::StandardUniformsNameToTypeHash RenderView::initializeStandardUniformSetters() |
| 104 | { |
| 105 | RenderView::StandardUniformsNameToTypeHash setters; |
| 106 | |
| 107 | setters.insert(akey: Shader::modelMatrixNameId, avalue: ModelMatrix); |
| 108 | setters.insert(akey: Shader::viewMatrixNameId, avalue: ViewMatrix); |
| 109 | setters.insert(akey: Shader::projectionMatrixNameId, avalue: ProjectionMatrix); |
| 110 | setters.insert(akey: Shader::modelViewMatrixNameId, avalue: ModelViewMatrix); |
| 111 | setters.insert(akey: Shader::viewProjectionMatrixNameId, avalue: ViewProjectionMatrix); |
| 112 | setters.insert(akey: Shader::modelViewProjectionNameId, avalue: ModelViewProjectionMatrix); |
| 113 | setters.insert(akey: Shader::mvpNameId, avalue: ModelViewProjectionMatrix); |
| 114 | setters.insert(akey: Shader::inverseModelMatrixNameId, avalue: InverseModelMatrix); |
| 115 | setters.insert(akey: Shader::inverseViewMatrixNameId, avalue: InverseViewMatrix); |
| 116 | setters.insert(akey: Shader::inverseProjectionMatrixNameId, avalue: InverseProjectionMatrix); |
| 117 | setters.insert(akey: Shader::inverseModelViewNameId, avalue: InverseModelViewMatrix); |
| 118 | setters.insert(akey: Shader::inverseViewProjectionMatrixNameId, avalue: InverseViewProjectionMatrix); |
| 119 | setters.insert(akey: Shader::inverseModelViewProjectionNameId, avalue: InverseModelViewProjectionMatrix); |
| 120 | setters.insert(akey: Shader::modelNormalMatrixNameId, avalue: ModelNormalMatrix); |
| 121 | setters.insert(akey: Shader::modelViewNormalNameId, avalue: ModelViewNormalMatrix); |
| 122 | setters.insert(akey: Shader::viewportMatrixNameId, avalue: ViewportMatrix); |
| 123 | setters.insert(akey: Shader::inverseViewportMatrixNameId, avalue: InverseViewportMatrix); |
| 124 | setters.insert(akey: Shader::aspectRatioNameId, avalue: AspectRatio); |
| 125 | setters.insert(akey: Shader::exposureNameId, avalue: Exposure); |
| 126 | setters.insert(akey: Shader::gammaNameId, avalue: Gamma); |
| 127 | setters.insert(akey: Shader::timeNameId, avalue: Time); |
| 128 | setters.insert(akey: Shader::eyePositionNameId, avalue: EyePosition); |
| 129 | setters.insert(akey: Shader::skinningPaletteNameId, avalue: SkinningPalette); |
| 130 | |
| 131 | return setters; |
| 132 | } |
| 133 | |
| 134 | // TODO: Move this somewhere global where GraphicsContext::setViewport() can use it too |
| 135 | static QRectF resolveViewport(const QRectF &fractionalViewport, const QSize &surfaceSize) |
| 136 | { |
| 137 | return QRectF(fractionalViewport.x() * surfaceSize.width(), |
| 138 | (1.0 - fractionalViewport.y() - fractionalViewport.height()) * surfaceSize.height(), |
| 139 | fractionalViewport.width() * surfaceSize.width(), |
| 140 | fractionalViewport.height() * surfaceSize.height()); |
| 141 | } |
| 142 | |
| 143 | static Matrix4x4 getProjectionMatrix(const CameraLens *lens) |
| 144 | { |
| 145 | return lens ? lens->projection() : Matrix4x4(); |
| 146 | } |
| 147 | |
| 148 | UniformValue RenderView::standardUniformValue(RenderView::StandardUniform standardUniformType, |
| 149 | const Entity *entity) const |
| 150 | { |
| 151 | const Matrix4x4 &model = *(entity->worldTransform()); |
| 152 | |
| 153 | switch (standardUniformType) { |
| 154 | case ModelMatrix: |
| 155 | return UniformValue(model); |
| 156 | case ViewMatrix: |
| 157 | return UniformValue(m_viewMatrix); |
| 158 | case ProjectionMatrix: |
| 159 | return UniformValue(getProjectionMatrix(lens: m_renderCameraLens)); |
| 160 | case ModelViewMatrix: |
| 161 | return UniformValue(m_viewMatrix * model); |
| 162 | case ViewProjectionMatrix: |
| 163 | return UniformValue(getProjectionMatrix(lens: m_renderCameraLens) * m_viewMatrix); |
| 164 | case ModelViewProjectionMatrix: |
| 165 | return UniformValue(m_viewProjectionMatrix * model); |
| 166 | case InverseModelMatrix: |
| 167 | return UniformValue(model.inverted()); |
| 168 | case InverseViewMatrix: |
| 169 | return UniformValue(m_viewMatrix.inverted()); |
| 170 | case InverseProjectionMatrix: { |
| 171 | return UniformValue(getProjectionMatrix(lens: m_renderCameraLens).inverted()); |
| 172 | } |
| 173 | case InverseModelViewMatrix: |
| 174 | return UniformValue((m_viewMatrix * model).inverted()); |
| 175 | case InverseViewProjectionMatrix: { |
| 176 | const Matrix4x4 viewProjectionMatrix = getProjectionMatrix(lens: m_renderCameraLens) * m_viewMatrix; |
| 177 | return UniformValue(viewProjectionMatrix.inverted()); |
| 178 | } |
| 179 | case InverseModelViewProjectionMatrix: |
| 180 | return UniformValue((m_viewProjectionMatrix * model).inverted()); |
| 181 | case ModelNormalMatrix: |
| 182 | return UniformValue(convertToQMatrix4x4(v: model).normalMatrix()); |
| 183 | case ModelViewNormalMatrix: |
| 184 | return UniformValue(convertToQMatrix4x4(v: m_viewMatrix * model).normalMatrix()); |
| 185 | case ViewportMatrix: { |
| 186 | QMatrix4x4 viewportMatrix; |
| 187 | // TO DO: Implement on Matrix4x4 |
| 188 | viewportMatrix.viewport(rect: resolveViewport(fractionalViewport: m_viewport, surfaceSize: m_surfaceSize)); |
| 189 | return UniformValue(Matrix4x4(viewportMatrix)); |
| 190 | } |
| 191 | case InverseViewportMatrix: { |
| 192 | QMatrix4x4 viewportMatrix; |
| 193 | // TO DO: Implement on Matrix4x4 |
| 194 | viewportMatrix.viewport(rect: resolveViewport(fractionalViewport: m_viewport, surfaceSize: m_surfaceSize)); |
| 195 | return UniformValue(Matrix4x4(viewportMatrix.inverted())); |
| 196 | } |
| 197 | case AspectRatio: |
| 198 | return float(m_surfaceSize.width()) / std::max(a: 1.f, b: float(m_surfaceSize.height())); |
| 199 | case Exposure: |
| 200 | return UniformValue(m_renderCameraLens ? m_renderCameraLens->exposure() : 0.0f); |
| 201 | case Gamma: |
| 202 | return UniformValue(m_gamma); |
| 203 | case Time: |
| 204 | return UniformValue(float(m_renderer->time() / 1000000000.0f)); |
| 205 | case EyePosition: |
| 206 | return UniformValue(m_eyePos); |
| 207 | case SkinningPalette: { |
| 208 | const Armature *armature = entity->renderComponent<Armature>(); |
| 209 | if (!armature) { |
| 210 | qCWarning(Jobs, "Requesting skinningPalette uniform but no armature set on entity" ); |
| 211 | return UniformValue(); |
| 212 | } |
| 213 | return armature->skinningPaletteUniform(); |
| 214 | } |
| 215 | default: |
| 216 | Q_UNREACHABLE(); |
| 217 | return UniformValue(); |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | RenderView::RenderView() |
| 222 | { |
| 223 | if (Q_UNLIKELY(!wasInitialized.exchange(true))) { |
| 224 | // Needed as we can control the init order of static/global variables across compile units |
| 225 | // and this hash relies on the static StringToInt class |
| 226 | |
| 227 | RenderView::ms_standardUniformSetters = RenderView::initializeStandardUniformSetters(); |
| 228 | } |
| 229 | } |
| 230 | |
| 231 | RenderView::~RenderView() |
| 232 | { |
| 233 | } |
| 234 | |
| 235 | namespace { |
| 236 | |
| 237 | template<int SortType> |
| 238 | struct AdjacentSubRangeFinder |
| 239 | { |
| 240 | static bool adjacentSubRange(const RenderCommand &, const RenderCommand &) |
| 241 | { |
| 242 | Q_UNREACHABLE(); |
| 243 | return false; |
| 244 | } |
| 245 | }; |
| 246 | |
| 247 | template<> |
| 248 | struct AdjacentSubRangeFinder<QSortPolicy::StateChangeCost> |
| 249 | { |
| 250 | static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b) |
| 251 | { |
| 252 | return a.m_changeCost == b.m_changeCost; |
| 253 | } |
| 254 | }; |
| 255 | |
| 256 | template<> |
| 257 | struct AdjacentSubRangeFinder<QSortPolicy::BackToFront> |
| 258 | { |
| 259 | static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b) |
| 260 | { |
| 261 | return qFuzzyCompare(p1: a.m_depth, p2: b.m_depth); |
| 262 | } |
| 263 | }; |
| 264 | |
| 265 | template<> |
| 266 | struct AdjacentSubRangeFinder<QSortPolicy::Material> |
| 267 | { |
| 268 | static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b) |
| 269 | { |
| 270 | return a.m_glShader == b.m_glShader; |
| 271 | } |
| 272 | }; |
| 273 | |
| 274 | template<> |
| 275 | struct AdjacentSubRangeFinder<QSortPolicy::FrontToBack> |
| 276 | { |
| 277 | static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b) |
| 278 | { |
| 279 | return qFuzzyCompare(p1: a.m_depth, p2: b.m_depth); |
| 280 | } |
| 281 | }; |
| 282 | |
| 283 | template<> |
| 284 | struct AdjacentSubRangeFinder<QSortPolicy::Texture> |
| 285 | { |
| 286 | static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b) |
| 287 | { |
| 288 | // Two renderCommands are adjacent if one contains all the other command's textures |
| 289 | const std::vector<ShaderParameterPack::NamedResource> &texturesA = a.m_parameterPack.textures(); |
| 290 | const std::vector<ShaderParameterPack::NamedResource> &texturesB = b.m_parameterPack.textures(); |
| 291 | |
| 292 | const bool bBigger = texturesB.size() > texturesA.size(); |
| 293 | const std::vector<ShaderParameterPack::NamedResource> &smallestVector = bBigger ? texturesA : texturesB; |
| 294 | const std::vector<ShaderParameterPack::NamedResource> &biggestVector = bBigger ? texturesB : texturesA; |
| 295 | |
| 296 | const auto e = biggestVector.cend(); |
| 297 | for (const ShaderParameterPack::NamedResource &tex : smallestVector) { |
| 298 | if (std::find(first: biggestVector.begin(), last: e, val: tex) == e) |
| 299 | return false; |
| 300 | } |
| 301 | |
| 302 | return true; |
| 303 | } |
| 304 | }; |
| 305 | |
| 306 | template<typename Predicate> |
| 307 | int advanceUntilNonAdjacent(const EntityRenderCommandDataView *view, |
| 308 | const size_t beg, const size_t end, Predicate pred) |
| 309 | { |
| 310 | const std::vector<size_t> &commandIndices = view->indices; |
| 311 | const std::vector<RenderCommand> &commands = view->data.commands; |
| 312 | size_t i = beg + 1; |
| 313 | if (i < end) { |
| 314 | const size_t startIdx = commandIndices[beg]; |
| 315 | while (i < end) { |
| 316 | const size_t targetIdx = commandIndices[i]; |
| 317 | if (!pred(commands[startIdx], commands[targetIdx])) |
| 318 | break; |
| 319 | ++i; |
| 320 | } |
| 321 | } |
| 322 | return i; |
| 323 | } |
| 324 | |
| 325 | |
| 326 | template<int SortType> |
| 327 | struct SubRangeSorter |
| 328 | { |
| 329 | static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end) |
| 330 | { |
| 331 | Q_UNUSED(view) |
| 332 | Q_UNUSED(begin) |
| 333 | Q_UNUSED(end) |
| 334 | Q_UNREACHABLE(); |
| 335 | } |
| 336 | }; |
| 337 | |
| 338 | template<> |
| 339 | struct SubRangeSorter<QSortPolicy::StateChangeCost> |
| 340 | { |
| 341 | static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end) |
| 342 | { |
| 343 | std::vector<size_t> &commandIndices = view->indices; |
| 344 | const std::vector<RenderCommand> &commands = view->data.commands; |
| 345 | std::stable_sort(first: commandIndices.begin() + begin, last: commandIndices.begin() + end, |
| 346 | comp: [&commands] (const size_t &iA, const size_t &iB) { |
| 347 | const RenderCommand &a = commands[iA]; |
| 348 | const RenderCommand &b = commands[iB]; |
| 349 | return a.m_changeCost > b.m_changeCost; |
| 350 | }); |
| 351 | } |
| 352 | }; |
| 353 | |
| 354 | template<> |
| 355 | struct SubRangeSorter<QSortPolicy::BackToFront> |
| 356 | { |
| 357 | static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end) |
| 358 | { |
| 359 | std::vector<size_t> &commandIndices = view->indices; |
| 360 | const std::vector<RenderCommand> &commands = view->data.commands; |
| 361 | std::stable_sort(first: commandIndices.begin() + begin, last: commandIndices.begin() + end, |
| 362 | comp: [&commands] (const size_t &iA, const size_t &iB) { |
| 363 | const RenderCommand &a = commands[iA]; |
| 364 | const RenderCommand &b = commands[iB]; |
| 365 | return a.m_depth > b.m_depth; |
| 366 | }); |
| 367 | } |
| 368 | }; |
| 369 | |
| 370 | template<> |
| 371 | struct SubRangeSorter<QSortPolicy::Material> |
| 372 | { |
| 373 | static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end) |
| 374 | { |
| 375 | std::vector<size_t> &commandIndices = view->indices; |
| 376 | const std::vector<RenderCommand> &commands = view->data.commands; |
| 377 | std::stable_sort(first: commandIndices.begin() + begin, last: commandIndices.begin() + end, |
| 378 | comp: [&commands] (const size_t &iA, const size_t &iB) { |
| 379 | const RenderCommand &a = commands[iA]; |
| 380 | const RenderCommand &b = commands[iB]; |
| 381 | return a.m_glShader > b.m_glShader; |
| 382 | }); |
| 383 | } |
| 384 | }; |
| 385 | |
| 386 | template<> |
| 387 | struct SubRangeSorter<QSortPolicy::FrontToBack> |
| 388 | { |
| 389 | static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end) |
| 390 | { |
| 391 | std::vector<size_t> &commandIndices = view->indices; |
| 392 | const std::vector<RenderCommand> &commands = view->data.commands; |
| 393 | std::stable_sort(first: commandIndices.begin() + begin, last: commandIndices.begin() + end, |
| 394 | comp: [&commands] (const size_t &iA, const size_t &iB) { |
| 395 | const RenderCommand &a = commands[iA]; |
| 396 | const RenderCommand &b = commands[iB]; |
| 397 | return a.m_depth < b.m_depth; |
| 398 | }); |
| 399 | } |
| 400 | }; |
| 401 | |
| 402 | template<> |
| 403 | struct SubRangeSorter<QSortPolicy::Texture> |
| 404 | { |
| 405 | static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end) |
| 406 | { |
| 407 | #ifndef Q_OS_WIN |
| 408 | std::vector<size_t> &commandIndices = view->indices; |
| 409 | const std::vector<RenderCommand> &commands = view->data.commands; |
| 410 | std::stable_sort(first: commandIndices.begin() + begin, last: commandIndices.begin() + end, |
| 411 | comp: [&commands] (const int &iA, const int &iB) { |
| 412 | const RenderCommand &a = commands[iA]; |
| 413 | const RenderCommand &b = commands[iB]; |
| 414 | const std::vector<ShaderParameterPack::NamedResource> &texturesA = a.m_parameterPack.textures(); |
| 415 | const std::vector<ShaderParameterPack::NamedResource> &texturesB = b.m_parameterPack.textures(); |
| 416 | |
| 417 | const bool bBigger = texturesB.size() > texturesA.size(); |
| 418 | const std::vector<ShaderParameterPack::NamedResource> &smallestVector = bBigger ? texturesA : texturesB; |
| 419 | const std::vector<ShaderParameterPack::NamedResource> &biggestVector = bBigger ? texturesB : texturesA; |
| 420 | |
| 421 | int identicalTextureCount = 0; |
| 422 | const auto e = biggestVector.cend(); |
| 423 | for (const ShaderParameterPack::NamedResource &tex : smallestVector) { |
| 424 | if (std::find(first: biggestVector.begin(), last: e, val: tex) != e) |
| 425 | ++identicalTextureCount; |
| 426 | } |
| 427 | |
| 428 | return identicalTextureCount < smallestVector.size(); |
| 429 | }); |
| 430 | #endif |
| 431 | } |
| 432 | }; |
| 433 | |
| 434 | int findSubRange(const EntityRenderCommandDataView *view, |
| 435 | const int begin, const int end, |
| 436 | const QSortPolicy::SortType sortType) |
| 437 | { |
| 438 | switch (sortType) { |
| 439 | case QSortPolicy::StateChangeCost: |
| 440 | return advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::StateChangeCost>::adjacentSubRange); |
| 441 | case QSortPolicy::BackToFront: |
| 442 | return advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::BackToFront>::adjacentSubRange); |
| 443 | case QSortPolicy::Material: |
| 444 | return advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::Material>::adjacentSubRange); |
| 445 | case QSortPolicy::FrontToBack: |
| 446 | return advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::FrontToBack>::adjacentSubRange); |
| 447 | case QSortPolicy::Texture: |
| 448 | return advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::Texture>::adjacentSubRange); |
| 449 | case QSortPolicy::Uniform: |
| 450 | return end; |
| 451 | default: |
| 452 | Q_UNREACHABLE(); |
| 453 | return end; |
| 454 | } |
| 455 | } |
| 456 | |
| 457 | void sortByMaterial(EntityRenderCommandDataView *view, int begin, const int end) |
| 458 | { |
| 459 | // We try to arrange elements so that their rendering cost is minimized for a given shader |
| 460 | std::vector<size_t> &commandIndices = view->indices; |
| 461 | const std::vector<RenderCommand> &commands = view->data.commands; |
| 462 | int rangeEnd = advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::Material>::adjacentSubRange); |
| 463 | while (begin != end) { |
| 464 | if (begin + 1 < rangeEnd) { |
| 465 | std::stable_sort(first: commandIndices.begin() + begin + 1, last: commandIndices.begin() + rangeEnd, |
| 466 | comp: [&commands] (const int &iA, const int &iB) { |
| 467 | const RenderCommand &a = commands[iA]; |
| 468 | const RenderCommand &b = commands[iB]; |
| 469 | return a.m_material.handle() < b.m_material.handle(); |
| 470 | }); |
| 471 | } |
| 472 | begin = rangeEnd; |
| 473 | rangeEnd = advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::Material>::adjacentSubRange); |
| 474 | } |
| 475 | } |
| 476 | |
| 477 | void sortCommandRange(EntityRenderCommandDataView *view, int begin, int end, const int level, |
| 478 | const QVector<Qt3DRender::QSortPolicy::SortType> &sortingTypes) |
| 479 | { |
| 480 | if (level >= sortingTypes.size()) |
| 481 | return; |
| 482 | |
| 483 | switch (sortingTypes.at(i: level)) { |
| 484 | case QSortPolicy::StateChangeCost: |
| 485 | SubRangeSorter<QSortPolicy::StateChangeCost>::sortSubRange(view, begin, end); |
| 486 | break; |
| 487 | case QSortPolicy::BackToFront: |
| 488 | SubRangeSorter<QSortPolicy::BackToFront>::sortSubRange(view, begin, end); |
| 489 | break; |
| 490 | case QSortPolicy::Material: |
| 491 | // Groups all same shader DNA together |
| 492 | SubRangeSorter<QSortPolicy::Material>::sortSubRange(view, begin, end); |
| 493 | // Group all same material together (same parameters most likely) |
| 494 | sortByMaterial(view, begin, end); |
| 495 | break; |
| 496 | case QSortPolicy::FrontToBack: |
| 497 | SubRangeSorter<QSortPolicy::FrontToBack>::sortSubRange(view, begin, end); |
| 498 | break; |
| 499 | case QSortPolicy::Texture: |
| 500 | SubRangeSorter<QSortPolicy::Texture>::sortSubRange(view, begin, end); |
| 501 | break; |
| 502 | case QSortPolicy::Uniform: |
| 503 | break; |
| 504 | default: |
| 505 | Q_UNREACHABLE(); |
| 506 | } |
| 507 | |
| 508 | // For all sub ranges of adjacent item for sortType[i] |
| 509 | // Perform filtering with sortType[i + 1] |
| 510 | int rangeEnd = findSubRange(view, begin, end, sortType: sortingTypes.at(i: level)); |
| 511 | while (begin != end) { |
| 512 | sortCommandRange(view, begin, end: rangeEnd, level: level + 1, sortingTypes); |
| 513 | begin = rangeEnd; |
| 514 | rangeEnd = findSubRange(view, begin, end, sortType: sortingTypes.at(i: level)); |
| 515 | } |
| 516 | } |
| 517 | |
| 518 | } // anonymous |
| 519 | |
| 520 | void RenderView::sort() |
| 521 | { |
| 522 | assert(m_renderCommandDataView); |
| 523 | // Compares the bitsetKey of the RenderCommands |
| 524 | // Key[Depth | StateCost | Shader] |
| 525 | sortCommandRange(view: m_renderCommandDataView.data(), begin: 0, end: m_renderCommandDataView->size(), level: 0, sortingTypes: m_sortingTypes); |
| 526 | |
| 527 | // For RenderCommand with the same shader |
| 528 | // We compute the adjacent change cost |
| 529 | |
| 530 | // Only perform uniform minimization if we explicitly asked for it |
| 531 | if (!m_sortingTypes.contains(t: QSortPolicy::Uniform)) |
| 532 | return; |
| 533 | |
| 534 | // Minimize uniform changes |
| 535 | int i = 0; |
| 536 | std::vector<RenderCommand> &commands = m_renderCommandDataView->data.commands; |
| 537 | const std::vector<size_t> &indices = m_renderCommandDataView->indices; |
| 538 | const size_t commandSize = indices.size(); |
| 539 | |
| 540 | while (i < commandSize) { |
| 541 | size_t j = i; |
| 542 | |
| 543 | // Advance while commands share the same shader |
| 544 | while (i < commandSize && |
| 545 | commands[indices[j]].m_glShader == commands[indices[i]].m_glShader) |
| 546 | ++i; |
| 547 | |
| 548 | if (i - j > 0) { // Several commands have the same shader, so we minimize uniform changes |
| 549 | PackUniformHash cachedUniforms = commands[indices[j++]].m_parameterPack.uniforms(); |
| 550 | |
| 551 | while (j < i) { |
| 552 | // We need the reference here as we are modifying the original container |
| 553 | // not the copy |
| 554 | PackUniformHash &uniforms = commands[indices[j]].m_parameterPack.m_uniforms; |
| 555 | |
| 556 | for (size_t u = 0; u < uniforms.keys.size();) { |
| 557 | // We are comparing the values: |
| 558 | // - raw uniform values |
| 559 | // - the texture Node id if the uniform represents a texture |
| 560 | // since all textures are assigned texture units before the RenderCommands |
| 561 | // sharing the same material (shader) are rendered, we can't have the case |
| 562 | // where two uniforms, referencing the same texture eventually have 2 different |
| 563 | // texture unit values |
| 564 | const int uniformNameId = uniforms.keys.at(n: u); |
| 565 | const UniformValue &refValue = cachedUniforms.value(key: uniformNameId); |
| 566 | const UniformValue &newValue = uniforms.values.at(n: u); |
| 567 | if (newValue == refValue) { |
| 568 | uniforms.erase(idx: u); |
| 569 | } else { |
| 570 | // Record updated value so that subsequent comparison |
| 571 | // for the next command will be made againts latest |
| 572 | // uniform value |
| 573 | cachedUniforms.insert(key: uniformNameId, value: newValue); |
| 574 | ++u; |
| 575 | } |
| 576 | } |
| 577 | ++j; |
| 578 | } |
| 579 | } |
| 580 | } |
| 581 | } |
| 582 | |
| 583 | void RenderView::setRenderer(Renderer *renderer) |
| 584 | { |
| 585 | m_renderer = renderer; |
| 586 | m_manager = renderer->nodeManagers(); |
| 587 | } |
| 588 | |
| 589 | RenderStateSet *RenderView::getOrCreateStateSet() |
| 590 | { |
| 591 | if (!m_stateSet) |
| 592 | m_stateSet.reset(other: new RenderStateSet()); |
| 593 | return m_stateSet.data(); |
| 594 | } |
| 595 | |
| 596 | void RenderView::addClearBuffers(const ClearBuffers *cb) { |
| 597 | QClearBuffers::BufferTypeFlags type = cb->type(); |
| 598 | |
| 599 | if (type & QClearBuffers::StencilBuffer) { |
| 600 | m_clearStencilValue = cb->clearStencilValue(); |
| 601 | m_clearBuffer |= QClearBuffers::StencilBuffer; |
| 602 | } |
| 603 | if (type & QClearBuffers::DepthBuffer) { |
| 604 | m_clearDepthValue = cb->clearDepthValue(); |
| 605 | m_clearBuffer |= QClearBuffers::DepthBuffer; |
| 606 | } |
| 607 | // keep track of global ClearColor (if set) and collect all DrawBuffer-specific |
| 608 | // ClearColors |
| 609 | if (type & QClearBuffers::ColorBuffer) { |
| 610 | ClearBufferInfo clearBufferInfo; |
| 611 | clearBufferInfo.clearColor = cb->clearColor(); |
| 612 | |
| 613 | if (cb->clearsAllColorBuffers()) { |
| 614 | m_globalClearColorBuffer = clearBufferInfo; |
| 615 | m_clearBuffer |= QClearBuffers::ColorBuffer; |
| 616 | } else { |
| 617 | if (cb->bufferId()) { |
| 618 | const RenderTargetOutput *targetOutput = m_manager->attachmentManager()->lookupResource(id: cb->bufferId()); |
| 619 | if (targetOutput) { |
| 620 | clearBufferInfo.attchmentPoint = targetOutput->point(); |
| 621 | // Note: a job is later performed to find the drawIndex from the buffer attachment point |
| 622 | // using the AttachmentPack |
| 623 | m_specificClearColorBuffers.push_back(t: clearBufferInfo); |
| 624 | } |
| 625 | } |
| 626 | } |
| 627 | } |
| 628 | } |
| 629 | |
| 630 | // If we are there, we know that entity had a GeometryRenderer + Material |
| 631 | EntityRenderCommandData RenderView::buildDrawRenderCommands(const QVector<Entity *> &entities, |
| 632 | int offset, int count) const |
| 633 | { |
| 634 | GLShaderManager *glShaderManager = m_renderer->glResourceManagers()->glShaderManager(); |
| 635 | EntityRenderCommandData commands; |
| 636 | |
| 637 | commands.reserve(size: count); |
| 638 | |
| 639 | for (int i = 0; i < count; ++i) { |
| 640 | const int idx = offset + i; |
| 641 | Entity *entity = entities.at(i: idx); |
| 642 | GeometryRenderer *geometryRenderer = nullptr; |
| 643 | HGeometryRenderer geometryRendererHandle = entity->componentHandle<GeometryRenderer>(); |
| 644 | |
| 645 | // There is a geometry renderer with geometry |
| 646 | if ((geometryRenderer = m_manager->geometryRendererManager()->data(handle: geometryRendererHandle)) != nullptr |
| 647 | && geometryRenderer->isEnabled() |
| 648 | && !geometryRenderer->geometryId().isNull()) { |
| 649 | |
| 650 | const Qt3DCore::QNodeId materialComponentId = entity->componentUuid<Material>(); |
| 651 | const HMaterial materialHandle = entity->componentHandle<Material>(); |
| 652 | const QVector<RenderPassParameterData> renderPassData = m_parameters.value(akey: materialComponentId); |
| 653 | |
| 654 | HGeometry geometryHandle = m_manager->geometryManager()->lookupHandle(id: geometryRenderer->geometryId()); |
| 655 | Geometry *geometry = m_manager->geometryManager()->data(handle: geometryHandle); |
| 656 | |
| 657 | // 1 RenderCommand per RenderPass pass on an Entity with a Mesh |
| 658 | for (const RenderPassParameterData &passData : renderPassData) { |
| 659 | // Add the RenderPass Parameters |
| 660 | RenderCommand command = {}; |
| 661 | command.m_geometryRenderer = geometryRendererHandle; |
| 662 | command.m_geometry = geometryHandle; |
| 663 | |
| 664 | command.m_material = materialHandle; |
| 665 | // For RenderPass based states we use the globally set RenderState |
| 666 | // if no renderstates are defined as part of the pass. That means: |
| 667 | // RenderPass { renderStates: [] } will use the states defined by |
| 668 | // StateSet in the FrameGraph |
| 669 | RenderPass *pass = passData.pass; |
| 670 | if (pass->hasRenderStates()) { |
| 671 | command.m_stateSet = RenderStateSetPtr::create(); |
| 672 | addStatesToRenderStateSet(stateSet: command.m_stateSet.data(), stateIds: pass->renderStates(), manager: m_manager->renderStateManager()); |
| 673 | if (m_stateSet) |
| 674 | command.m_stateSet->merge(other: m_stateSet.data()); |
| 675 | command.m_changeCost = m_renderer->defaultRenderState()->changeCost(previousState: command.m_stateSet.data()); |
| 676 | } |
| 677 | command.m_shaderId = pass->shaderProgram(); |
| 678 | command.m_glShader = glShaderManager->lookupResource(shaderId: command.m_shaderId); |
| 679 | |
| 680 | // It takes two frames to have a valid command as we can only |
| 681 | // reference a glShader at frame n if it has been loaded at frame n - 1 |
| 682 | if (!command.m_glShader) |
| 683 | continue; |
| 684 | |
| 685 | { // Scoped to show extent |
| 686 | |
| 687 | // Update the draw command with what's going to be needed for the drawing |
| 688 | int primitiveCount = geometryRenderer->vertexCount(); |
| 689 | int estimatedCount = 0; |
| 690 | Attribute *indexAttribute = nullptr; |
| 691 | Attribute *indirectAttribute = nullptr; |
| 692 | |
| 693 | const QVector<Qt3DCore::QNodeId> attributeIds = geometry->attributes(); |
| 694 | for (Qt3DCore::QNodeId attributeId : attributeIds) { |
| 695 | Attribute *attribute = m_manager->attributeManager()->lookupResource(id: attributeId); |
| 696 | switch (attribute->attributeType()) { |
| 697 | case QAttribute::IndexAttribute: |
| 698 | indexAttribute = attribute; |
| 699 | break; |
| 700 | case QAttribute::DrawIndirectAttribute: |
| 701 | indirectAttribute = attribute; |
| 702 | break; |
| 703 | case QAttribute::VertexAttribute: |
| 704 | estimatedCount = std::max(a: int(attribute->count()), b: estimatedCount); |
| 705 | break; |
| 706 | default: |
| 707 | Q_UNREACHABLE(); |
| 708 | break; |
| 709 | } |
| 710 | } |
| 711 | |
| 712 | command.m_drawIndexed = (indexAttribute != nullptr); |
| 713 | command.m_drawIndirect = (indirectAttribute != nullptr); |
| 714 | |
| 715 | // Update the draw command with all the information required for the drawing |
| 716 | if (command.m_drawIndexed) { |
| 717 | command.m_indexAttributeDataType = GraphicsContext::glDataTypeFromAttributeDataType(dataType: indexAttribute->vertexBaseType()); |
| 718 | command.m_indexAttributeByteOffset = indexAttribute->byteOffset() + geometryRenderer->indexBufferByteOffset(); |
| 719 | } |
| 720 | |
| 721 | // Note: we only care about the primitiveCount when using direct draw calls |
| 722 | // For indirect draw calls it is assumed the buffer was properly set already |
| 723 | if (command.m_drawIndirect) { |
| 724 | command.m_indirectAttributeByteOffset = indirectAttribute->byteOffset(); |
| 725 | command.m_indirectDrawBuffer = m_manager->bufferManager()->lookupHandle(id: indirectAttribute->bufferId()); |
| 726 | } else { |
| 727 | // Use the count specified by the GeometryRender |
| 728 | // If not specify use the indexAttribute count if present |
| 729 | // Otherwise tries to use the count from the attribute with the highest count |
| 730 | if (primitiveCount == 0) { |
| 731 | if (indexAttribute) |
| 732 | primitiveCount = indexAttribute->count(); |
| 733 | else |
| 734 | primitiveCount = estimatedCount; |
| 735 | } |
| 736 | } |
| 737 | |
| 738 | command.m_primitiveCount = primitiveCount; |
| 739 | command.m_primitiveType = geometryRenderer->primitiveType(); |
| 740 | command.m_primitiveRestartEnabled = geometryRenderer->primitiveRestartEnabled(); |
| 741 | command.m_restartIndexValue = geometryRenderer->restartIndexValue(); |
| 742 | command.m_firstInstance = geometryRenderer->firstInstance(); |
| 743 | command.m_instanceCount = geometryRenderer->instanceCount(); |
| 744 | command.m_firstVertex = geometryRenderer->firstVertex(); |
| 745 | command.m_indexOffset = geometryRenderer->indexOffset(); |
| 746 | command.m_verticesPerPatch = geometryRenderer->verticesPerPatch(); |
| 747 | } // scope |
| 748 | |
| 749 | |
| 750 | commands.push_back(e: entity, |
| 751 | c: std::move(command), |
| 752 | p: std::move(passData)); |
| 753 | } |
| 754 | } |
| 755 | } |
| 756 | |
| 757 | return commands; |
| 758 | } |
| 759 | |
| 760 | EntityRenderCommandData RenderView::buildComputeRenderCommands(const QVector<Entity *> &entities, |
| 761 | int offset, int count) const |
| 762 | { |
| 763 | // If the RenderView contains only a ComputeDispatch then it cares about |
| 764 | // A ComputeDispatch is also implicitely a NoDraw operation |
| 765 | // enabled flag |
| 766 | // layer component |
| 767 | // material/effect/technique/parameters/filters/ |
| 768 | EntityRenderCommandData commands; |
| 769 | GLShaderManager *glShaderManager = m_renderer->glResourceManagers()->glShaderManager(); |
| 770 | |
| 771 | commands.reserve(size: count); |
| 772 | |
| 773 | for (int i = 0; i < count; ++i) { |
| 774 | const int idx = offset + i; |
| 775 | Entity *entity = entities.at(i: idx); |
| 776 | ComputeCommand *computeJob = nullptr; |
| 777 | HComputeCommand computeCommandHandle = entity->componentHandle<ComputeCommand>(); |
| 778 | if ((computeJob = nodeManagers()->computeJobManager()->data(handle: computeCommandHandle)) != nullptr |
| 779 | && computeJob->isEnabled()) { |
| 780 | |
| 781 | const Qt3DCore::QNodeId materialComponentId = entity->componentUuid<Material>(); |
| 782 | const QVector<RenderPassParameterData> renderPassData = m_parameters.value(akey: materialComponentId); |
| 783 | |
| 784 | // 1 RenderCommand per RenderPass pass on an Entity with a Mesh |
| 785 | for (const RenderPassParameterData &passData : renderPassData) { |
| 786 | // Add the RenderPass Parameters |
| 787 | RenderCommand command = {}; |
| 788 | RenderPass *pass = passData.pass; |
| 789 | |
| 790 | if (pass->hasRenderStates()) { |
| 791 | command.m_stateSet = RenderStateSetPtr::create(); |
| 792 | addStatesToRenderStateSet(stateSet: command.m_stateSet.data(), stateIds: pass->renderStates(), manager: m_manager->renderStateManager()); |
| 793 | |
| 794 | // Merge per pass stateset with global stateset |
| 795 | // so that the local stateset only overrides |
| 796 | if (m_stateSet != nullptr) |
| 797 | command.m_stateSet->merge(other: m_stateSet.data()); |
| 798 | command.m_changeCost = m_renderer->defaultRenderState()->changeCost(previousState: command.m_stateSet.data()); |
| 799 | } |
| 800 | command.m_shaderId = pass->shaderProgram(); |
| 801 | command.m_glShader = glShaderManager->lookupResource(shaderId: command.m_shaderId); |
| 802 | |
| 803 | // It takes two frames to have a valid command as we can only |
| 804 | // reference a glShader at frame n if it has been loaded at frame n - 1 |
| 805 | if (!command.m_glShader) |
| 806 | continue; |
| 807 | |
| 808 | command.m_computeCommand = computeCommandHandle; |
| 809 | command.m_type = RenderCommand::Compute; |
| 810 | command.m_workGroups[0] = std::max(a: m_workGroups[0], b: computeJob->x()); |
| 811 | command.m_workGroups[1] = std::max(a: m_workGroups[1], b: computeJob->y()); |
| 812 | command.m_workGroups[2] = std::max(a: m_workGroups[2], b: computeJob->z()); |
| 813 | |
| 814 | commands.push_back(e: entity, |
| 815 | c: std::move(command), |
| 816 | p: std::move(passData)); |
| 817 | } |
| 818 | } |
| 819 | } |
| 820 | |
| 821 | return commands; |
| 822 | } |
| 823 | |
| 824 | void RenderView::updateRenderCommand(const EntityRenderCommandDataSubView &subView) |
| 825 | { |
| 826 | // Note: since many threads can be building render commands |
| 827 | // we need to ensure that the UniformBlockValueBuilder they are using |
| 828 | // is only accessed from the same thread |
| 829 | UniformBlockValueBuilder *builder = new UniformBlockValueBuilder(); |
| 830 | builder->shaderDataManager = m_manager->shaderDataManager(); |
| 831 | builder->textureManager = m_manager->textureManager(); |
| 832 | m_localData.setLocalData(builder); |
| 833 | |
| 834 | subView.forEach(func: [this] (const Entity *entity, |
| 835 | const RenderPassParameterData &passData, |
| 836 | RenderCommand &command) { |
| 837 | if (command.m_type == RenderCommand::Draw) { |
| 838 | // Project the camera-to-object-center vector onto the camera |
| 839 | // view vector. This gives a depth value suitable as the key |
| 840 | // for BackToFront sorting. |
| 841 | command.m_depth = Vector3D::dotProduct(a: entity->worldBoundingVolume()->center() - m_eyePos, b: m_eyeViewDir); |
| 842 | |
| 843 | auto geometryRenderer = m_manager->geometryRendererManager()->data(handle: command.m_geometryRenderer); |
| 844 | if (geometryRenderer && !qFuzzyCompare(p1: geometryRenderer->sortIndex(), p2: -1.f)) |
| 845 | command.m_depth = geometryRenderer->sortIndex(); |
| 846 | } else { // Compute |
| 847 | // Note: if frameCount has reached 0 in the previous frame, isEnabled |
| 848 | // would be false |
| 849 | ComputeCommand *computeJob = m_manager->computeJobManager()->data(handle: command.m_computeCommand); |
| 850 | if (computeJob->runType() == QComputeCommand::Manual) |
| 851 | computeJob->updateFrameCount(); |
| 852 | } |
| 853 | |
| 854 | // setShaderAndUniforms can initialize a localData |
| 855 | // make sure this is cleared before we leave this function |
| 856 | setShaderAndUniforms(command: &command, |
| 857 | parameters: passData.parameterInfo, |
| 858 | entity); |
| 859 | }); |
| 860 | |
| 861 | // We reset the local data once we are done with it |
| 862 | m_localData.setLocalData(nullptr); |
| 863 | } |
| 864 | |
| 865 | void RenderView::updateMatrices() |
| 866 | { |
| 867 | if (m_renderCameraNode && m_renderCameraLens && m_renderCameraLens->isEnabled()) { |
| 868 | const Matrix4x4 cameraWorld = *(m_renderCameraNode->worldTransform()); |
| 869 | setViewMatrix(m_renderCameraLens->viewMatrix(worldTransform: cameraWorld)); |
| 870 | |
| 871 | setViewProjectionMatrix(m_renderCameraLens->projection() * viewMatrix()); |
| 872 | //To get the eyePosition of the camera, we need to use the inverse of the |
| 873 | //camera's worldTransform matrix. |
| 874 | const Matrix4x4 inverseWorldTransform = viewMatrix().inverted(); |
| 875 | const Vector3D eyePosition(inverseWorldTransform.column(index: 3)); |
| 876 | setEyePosition(eyePosition); |
| 877 | |
| 878 | // Get the viewing direction of the camera. Use the normal matrix to |
| 879 | // ensure non-uniform scale works too. |
| 880 | const QMatrix3x3 normalMat = convertToQMatrix4x4(v: m_viewMatrix).normalMatrix(); |
| 881 | // dir = normalize(QVector3D(0, 0, -1) * normalMat) |
| 882 | setEyeViewDirection(Vector3D(-normalMat(2, 0), -normalMat(2, 1), -normalMat(2, 2)).normalized()); |
| 883 | } |
| 884 | } |
| 885 | |
| 886 | void RenderView::setUniformValue(ShaderParameterPack &uniformPack, int nameId, const UniformValue &value) const |
| 887 | { |
| 888 | // At this point a uniform value can only be a scalar type |
| 889 | // or a Qt3DCore::QNodeId corresponding to a Texture or Image |
| 890 | // ShaderData/Buffers would be handled as UBO/SSBO and would therefore |
| 891 | // not be in the default uniform block |
| 892 | if (value.valueType() == UniformValue::NodeId) { |
| 893 | const Qt3DCore::QNodeId *nodeIds = value.constData<Qt3DCore::QNodeId>(); |
| 894 | |
| 895 | const int uniformArraySize = value.byteSize() / sizeof(Qt3DCore::QNodeId); |
| 896 | UniformValue::ValueType resourceType = UniformValue::TextureValue; |
| 897 | |
| 898 | for (int i = 0; i < uniformArraySize; ++i) { |
| 899 | const Qt3DCore::QNodeId resourceId = nodeIds[i]; |
| 900 | |
| 901 | const Texture *tex = m_manager->textureManager()->lookupResource(id: resourceId); |
| 902 | if (tex != nullptr) { |
| 903 | uniformPack.setTexture(glslNameId: nameId, uniformArrayIndex: i, id: resourceId); |
| 904 | } else { |
| 905 | const ShaderImage *img = m_manager->shaderImageManager()->lookupResource(id: resourceId); |
| 906 | if (img != nullptr) { |
| 907 | resourceType = UniformValue::ShaderImageValue; |
| 908 | uniformPack.setImage(glslNameId: nameId, uniformArrayIndex: i, id: resourceId); |
| 909 | } |
| 910 | } |
| 911 | } |
| 912 | |
| 913 | // This uniform will be overridden in SubmissionContext::setParameters |
| 914 | // and -1 values will be replaced by valid Texture or Image units |
| 915 | UniformValue uniformValue(uniformArraySize * sizeof(int), resourceType); |
| 916 | std::fill(first: uniformValue.data<int>(), last: uniformValue.data<int>() + uniformArraySize, value: -1); |
| 917 | uniformPack.setUniform(glslNameId: nameId, val: uniformValue); |
| 918 | } else { |
| 919 | uniformPack.setUniform(glslNameId: nameId, val: value); |
| 920 | } |
| 921 | } |
| 922 | |
| 923 | void RenderView::setStandardUniformValue(ShaderParameterPack &uniformPack, |
| 924 | int nameId, |
| 925 | const Entity *entity) const |
| 926 | { |
| 927 | uniformPack.setUniform(glslNameId: nameId, val: standardUniformValue(standardUniformType: ms_standardUniformSetters[nameId], entity)); |
| 928 | } |
| 929 | |
| 930 | void RenderView::setUniformBlockValue(ShaderParameterPack &uniformPack, |
| 931 | const ShaderUniformBlock &block, |
| 932 | const UniformValue &value) const |
| 933 | { |
| 934 | if (value.valueType() == UniformValue::NodeId) { |
| 935 | |
| 936 | Buffer *buffer = nullptr; |
| 937 | if ((buffer = m_manager->bufferManager()->lookupResource(id: *value.constData<Qt3DCore::QNodeId>())) != nullptr) { |
| 938 | BlockToUBO uniformBlockUBO; |
| 939 | uniformBlockUBO.m_blockIndex = block.m_index; |
| 940 | uniformBlockUBO.m_bufferID = buffer->peerId(); |
| 941 | uniformBlockUBO.m_needsUpdate = false; |
| 942 | uniformPack.setUniformBuffer(std::move(uniformBlockUBO)); |
| 943 | // Buffer update to GL buffer will be done at render time |
| 944 | } |
| 945 | } |
| 946 | } |
| 947 | |
| 948 | void RenderView::setShaderStorageValue(ShaderParameterPack &uniformPack, |
| 949 | const ShaderStorageBlock &block, |
| 950 | const UniformValue &value) const |
| 951 | { |
| 952 | if (value.valueType() == UniformValue::NodeId) { |
| 953 | Buffer *buffer = nullptr; |
| 954 | if ((buffer = m_manager->bufferManager()->lookupResource(id: *value.constData<Qt3DCore::QNodeId>())) != nullptr) { |
| 955 | BlockToSSBO shaderStorageBlock; |
| 956 | shaderStorageBlock.m_blockIndex = block.m_index; |
| 957 | shaderStorageBlock.m_bufferID = buffer->peerId(); |
| 958 | shaderStorageBlock.m_bindingIndex = block.m_binding; |
| 959 | uniformPack.setShaderStorageBuffer(shaderStorageBlock); |
| 960 | // Buffer update to GL buffer will be done at render time |
| 961 | } |
| 962 | } |
| 963 | } |
| 964 | |
| 965 | void RenderView::setDefaultUniformBlockShaderDataValue(ShaderParameterPack &uniformPack, |
| 966 | const GLShader *shader, |
| 967 | const ShaderData *shaderData, |
| 968 | const QString &structName) const |
| 969 | { |
| 970 | UniformBlockValueBuilder *builder = m_localData.localData(); |
| 971 | builder->activeUniformNamesToValue.clear(); |
| 972 | |
| 973 | // Set the view matrix to be used to transform "Transformed" properties in the ShaderData |
| 974 | builder->viewMatrix = m_viewMatrix; |
| 975 | // Force to update the whole block |
| 976 | builder->updatedPropertiesOnly = false; |
| 977 | // Retrieve names and description of each active uniforms in the uniform block |
| 978 | builder->uniforms = shader->activeUniformsForUniformBlock(blockIndex: -1); |
| 979 | // Build name-value map for the block |
| 980 | builder->buildActiveUniformNameValueMapStructHelper(rShaderData: shaderData, blockName: structName); |
| 981 | // Set uniform values for each entrie of the block name-value map |
| 982 | QHash<int, QVariant>::const_iterator activeValuesIt = builder->activeUniformNamesToValue.constBegin(); |
| 983 | const QHash<int, QVariant>::const_iterator activeValuesEnd = builder->activeUniformNamesToValue.constEnd(); |
| 984 | |
| 985 | // TO DO: Make the ShaderData store UniformValue |
| 986 | while (activeValuesIt != activeValuesEnd) { |
| 987 | setUniformValue(uniformPack, nameId: activeValuesIt.key(), value: UniformValue::fromVariant(variant: activeValuesIt.value())); |
| 988 | ++activeValuesIt; |
| 989 | } |
| 990 | } |
| 991 | |
| 992 | void RenderView::applyParameter(const Parameter *param, |
| 993 | RenderCommand *command, |
| 994 | const GLShader *shader) const noexcept |
| 995 | { |
| 996 | const int nameId = param->nameId(); |
| 997 | const UniformValue &uniformValue = param->uniformValue(); |
| 998 | const GLShader::ParameterKind kind = shader->categorizeVariable(nameId); |
| 999 | |
| 1000 | switch (kind) { |
| 1001 | case GLShader::Uniform: { |
| 1002 | setUniformValue(uniformPack&: command->m_parameterPack, nameId, value: uniformValue); |
| 1003 | break; |
| 1004 | } |
| 1005 | case GLShader::UBO: { |
| 1006 | setUniformBlockValue(uniformPack&: command->m_parameterPack, block: shader->uniformBlockForBlockNameId(blockIndex: nameId), value: uniformValue); |
| 1007 | break; |
| 1008 | } |
| 1009 | case GLShader::SSBO: { |
| 1010 | setShaderStorageValue(uniformPack&: command->m_parameterPack, block: shader->storageBlockForBlockNameId(blockNameId: nameId), value: uniformValue); |
| 1011 | break; |
| 1012 | } |
| 1013 | case GLShader::Struct: { |
| 1014 | ShaderData *shaderData = nullptr; |
| 1015 | if (uniformValue.valueType() == UniformValue::NodeId && |
| 1016 | (shaderData = m_manager->shaderDataManager()->lookupResource(id: *uniformValue.constData<Qt3DCore::QNodeId>())) != nullptr) { |
| 1017 | // Try to check if we have a struct or array matching a QShaderData parameter |
| 1018 | setDefaultUniformBlockShaderDataValue(uniformPack&: command->m_parameterPack, shader, shaderData, structName: StringToInt::lookupString(idx: nameId)); |
| 1019 | } |
| 1020 | break; |
| 1021 | } |
| 1022 | default: |
| 1023 | break; |
| 1024 | } |
| 1025 | } |
| 1026 | |
| 1027 | |
| 1028 | void RenderView::setShaderAndUniforms(RenderCommand *command, |
| 1029 | const ParameterInfoList ¶meters, |
| 1030 | const Entity *entity) const |
| 1031 | { |
| 1032 | // The VAO Handle is set directly in the renderer thread so as to avoid having to use a mutex here |
| 1033 | // Set shader, technique, and effect by basically doing : |
| 1034 | // ShaderProgramManager[MaterialManager[frontentEntity->id()]->Effect->Techniques[TechniqueFilter->name]->RenderPasses[RenderPassFilter->name]]; |
| 1035 | // The Renderer knows that if one of those is null, a default material / technique / effect as to be used |
| 1036 | |
| 1037 | // Find all RenderPasses (in order) matching values set in the RenderPassFilter |
| 1038 | // Get list of parameters for the Material, Effect, and Technique |
| 1039 | // For each ParameterBinder in the RenderPass -> create a QUniformPack |
| 1040 | // Once that works, improve that to try and minimize QUniformPack updates |
| 1041 | |
| 1042 | GLShader *shader = command->m_glShader; |
| 1043 | if (shader == nullptr || !shader->isLoaded()) |
| 1044 | return; |
| 1045 | |
| 1046 | // If we have already build the uniforms previously, we should |
| 1047 | // only update values of uniforms that have changed |
| 1048 | // If parameters add been added/removed, the command would have been rebuild |
| 1049 | // and the parameter pack would be empty |
| 1050 | const bool updateUniformsOnly = command->m_parameterPack.submissionUniformIndices().size() > 0; |
| 1051 | |
| 1052 | if (!updateUniformsOnly) { |
| 1053 | // Builds the QUniformPack, sets shader standard uniforms and store attributes name / glname bindings |
| 1054 | // If a parameter is defined and not found in the bindings it is assumed to be a binding of Uniform type with the glsl name |
| 1055 | // equals to the parameter name |
| 1056 | |
| 1057 | // Set fragData Name and index |
| 1058 | // Later on we might want to relink the shader if attachments have changed |
| 1059 | // But for now we set them once and for all |
| 1060 | if (!m_renderTarget.isNull() && !shader->isLoaded()) { |
| 1061 | QHash<QString, int> fragOutputs; |
| 1062 | const auto atts = m_attachmentPack.attachments(); |
| 1063 | for (const Attachment &att : atts) { |
| 1064 | if (att.m_point <= QRenderTargetOutput::Color15) |
| 1065 | fragOutputs.insert(akey: att.m_name, avalue: att.m_point); |
| 1066 | } |
| 1067 | // Set frag outputs in the shaders if hash not empty |
| 1068 | if (!fragOutputs.isEmpty()) |
| 1069 | shader->setFragOutputs(fragOutputs); |
| 1070 | } |
| 1071 | |
| 1072 | // Set default attributes |
| 1073 | command->m_activeAttributes = shader->attributeNamesIds(); |
| 1074 | |
| 1075 | // At this point we know whether the command is a valid draw command or not |
| 1076 | // We still need to process the uniforms as the command could be a compute command |
| 1077 | command->m_isValid = !command->m_activeAttributes.empty(); |
| 1078 | |
| 1079 | // Reserve amount of uniforms we are going to need |
| 1080 | command->m_parameterPack.reserve(uniformCount: shader->parameterPackSize()); |
| 1081 | } |
| 1082 | |
| 1083 | const size_t previousUniformCount = command->m_parameterPack.uniforms().size(); |
| 1084 | if (shader->hasActiveVariables()) { |
| 1085 | const QVector<int> &standardUniformNamesIds = shader->standardUniformNameIds(); |
| 1086 | |
| 1087 | // It only makes sense to update the standard uniforms if: |
| 1088 | // - Camera changed |
| 1089 | // - Entity transform changed |
| 1090 | // - Viewport/Surface changed |
| 1091 | |
| 1092 | for (const int uniformNameId : standardUniformNamesIds) |
| 1093 | setStandardUniformValue(uniformPack&: command->m_parameterPack, nameId: uniformNameId, entity); |
| 1094 | |
| 1095 | ParameterInfoList::const_iterator it = parameters.cbegin(); |
| 1096 | const ParameterInfoList::const_iterator parametersEnd = parameters.cend(); |
| 1097 | |
| 1098 | while (it != parametersEnd) { |
| 1099 | const Parameter *param = m_manager->data<Parameter, ParameterManager>(handle: it->handle); |
| 1100 | applyParameter(param, command, shader); |
| 1101 | ++it; |
| 1102 | } |
| 1103 | |
| 1104 | // Lights |
| 1105 | updateLightUniforms(command, entity); |
| 1106 | } |
| 1107 | |
| 1108 | const size_t actualUniformCount = command->m_parameterPack.uniforms().size(); |
| 1109 | // Prepare the ShaderParameterPack based on the active uniforms of the shader |
| 1110 | if (!updateUniformsOnly || previousUniformCount != actualUniformCount) |
| 1111 | shader->prepareUniforms(pack&: command->m_parameterPack); |
| 1112 | } |
| 1113 | |
| 1114 | void RenderView::updateLightUniforms(RenderCommand *command, const Entity *entity) const |
| 1115 | { |
| 1116 | GLShader *shader = command->m_glShader; |
| 1117 | const QVector<int> &lightUniformNamesIds = shader->lightUniformsNamesIds(); |
| 1118 | if (!lightUniformNamesIds.empty()) { |
| 1119 | // Pick which lights to take in to account. |
| 1120 | // For now decide based on the distance by taking the MAX_LIGHTS closest lights. |
| 1121 | // Replace with more sophisticated mechanisms later. |
| 1122 | // Copy vector so that we can sort it concurrently and we only want to sort the one for the current command |
| 1123 | QVector<LightSource> lightSources = m_lightSources; |
| 1124 | |
| 1125 | if (lightSources.size() > 1) { |
| 1126 | const Vector3D entityCenter = entity->worldBoundingVolume()->center(); |
| 1127 | std::sort(first: lightSources.begin(), last: lightSources.end(), |
| 1128 | comp: [&] (const LightSource &a, const LightSource &b) { |
| 1129 | const float distA = entityCenter.distanceToPoint(point: a.entity->worldBoundingVolume()->center()); |
| 1130 | const float distB = entityCenter.distanceToPoint(point: b.entity->worldBoundingVolume()->center()); |
| 1131 | return distA < distB; |
| 1132 | }); |
| 1133 | } |
| 1134 | m_lightSources = lightSources.mid(pos: 0, len: std::min(a: lightSources.size(), MAX_LIGHTS)); |
| 1135 | |
| 1136 | int lightIdx = 0; |
| 1137 | for (const LightSource &lightSource : qAsConst(t&: m_lightSources)) { |
| 1138 | if (lightIdx == MAX_LIGHTS) |
| 1139 | break; |
| 1140 | Entity *lightEntity = lightSource.entity; |
| 1141 | const Matrix4x4 lightWorldTransform = *(lightEntity->worldTransform()); |
| 1142 | const Vector3D worldPos = lightWorldTransform * Vector3D(0.0f, 0.0f, 0.0f); |
| 1143 | for (Light *light : lightSource.lights) { |
| 1144 | if (!light->isEnabled()) |
| 1145 | continue; |
| 1146 | |
| 1147 | ShaderData *shaderData = m_manager->shaderDataManager()->lookupResource(id: light->shaderData()); |
| 1148 | if (!shaderData) |
| 1149 | continue; |
| 1150 | |
| 1151 | if (lightIdx == MAX_LIGHTS) |
| 1152 | break; |
| 1153 | |
| 1154 | // Note: implicit conversion of values to UniformValue |
| 1155 | if (lightUniformNamesIds.contains(t: GLLights::LIGHT_TYPE_NAMES[lightIdx])) { |
| 1156 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: GLLights::LIGHT_POSITION_NAMES[lightIdx], value: worldPos); |
| 1157 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: GLLights::LIGHT_TYPE_NAMES[lightIdx], value: int(QAbstractLight::PointLight)); |
| 1158 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: GLLights::LIGHT_COLOR_NAMES[lightIdx], value: Vector3D(1.0f, 1.0f, 1.0f)); |
| 1159 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: GLLights::LIGHT_INTENSITY_NAMES[lightIdx], value: 0.5f); |
| 1160 | } else if (lightUniformNamesIds.contains(t: GLLights::LIGHT_TYPE_UNROLL_NAMES[lightIdx])) { |
| 1161 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: GLLights::LIGHT_POSITION_UNROLL_NAMES[lightIdx], value: worldPos); |
| 1162 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: GLLights::LIGHT_TYPE_UNROLL_NAMES[lightIdx], value: int(QAbstractLight::PointLight)); |
| 1163 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: GLLights::LIGHT_COLOR_UNROLL_NAMES[lightIdx], value: Vector3D(1.0f, 1.0f, 1.0f)); |
| 1164 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: GLLights::LIGHT_INTENSITY_UNROLL_NAMES[lightIdx], value: 0.5f); |
| 1165 | } |
| 1166 | |
| 1167 | // There is no risk in doing that even if multithreaded |
| 1168 | // since we are sure that a shaderData is unique for a given light |
| 1169 | // and won't ever be referenced as a Component either |
| 1170 | Matrix4x4 *worldTransform = lightEntity->worldTransform(); |
| 1171 | if (worldTransform) |
| 1172 | shaderData->updateWorldTransform(worldMatrix: *worldTransform); |
| 1173 | |
| 1174 | setDefaultUniformBlockShaderDataValue(uniformPack&: command->m_parameterPack, shader, shaderData, structName: GLLights::LIGHT_STRUCT_NAMES[lightIdx]); |
| 1175 | setDefaultUniformBlockShaderDataValue(uniformPack&: command->m_parameterPack, shader, shaderData, structName: GLLights::LIGHT_STRUCT_UNROLL_NAMES[lightIdx]); |
| 1176 | ++lightIdx; |
| 1177 | } |
| 1178 | } |
| 1179 | |
| 1180 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: GLLights::LIGHT_COUNT_NAME_ID, value: UniformValue(qMax(a: (m_environmentLight ? 0 : 1), b: lightIdx))); |
| 1181 | |
| 1182 | // If no active light sources and no environment light, add a default light |
| 1183 | if (m_lightSources.isEmpty() && !m_environmentLight) { |
| 1184 | // Note: implicit conversion of values to UniformValue |
| 1185 | if (lightUniformNamesIds.contains(t: GLLights::LIGHT_TYPE_NAMES[0])) { |
| 1186 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: GLLights::LIGHT_POSITION_NAMES[0], value: Vector3D(10.0f, 10.0f, 0.0f)); |
| 1187 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: GLLights::LIGHT_TYPE_NAMES[0], value: int(QAbstractLight::PointLight)); |
| 1188 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: GLLights::LIGHT_COLOR_NAMES[0], value: Vector3D(1.0f, 1.0f, 1.0f)); |
| 1189 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: GLLights::LIGHT_INTENSITY_NAMES[0], value: 0.5f); |
| 1190 | } else if (lightUniformNamesIds.contains(t: GLLights::LIGHT_TYPE_UNROLL_NAMES[lightIdx])) { |
| 1191 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: GLLights::LIGHT_POSITION_UNROLL_NAMES[0], value: Vector3D(10.0f, 10.0f, 0.0f)); |
| 1192 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: GLLights::LIGHT_TYPE_UNROLL_NAMES[0], value: int(QAbstractLight::PointLight)); |
| 1193 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: GLLights::LIGHT_COLOR_UNROLL_NAMES[0], value: Vector3D(1.0f, 1.0f, 1.0f)); |
| 1194 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: GLLights::LIGHT_INTENSITY_UNROLL_NAMES[0], value: 0.5f); |
| 1195 | } |
| 1196 | } |
| 1197 | } |
| 1198 | |
| 1199 | // Environment Light |
| 1200 | int envLightCount = 0; |
| 1201 | static const int irradianceStructId = StringToInt::lookupId(str: QLatin1String("envLight.irradiance" )); |
| 1202 | static const int specularStructId = StringToInt::lookupId(str: QLatin1String("envLight.specular" )); |
| 1203 | static const int irradianceId = StringToInt::lookupId(str: QLatin1String("envLightIrradiance" )); |
| 1204 | static const int specularId = StringToInt::lookupId(str: QLatin1String("envLightSpecular" )); |
| 1205 | if (m_environmentLight && m_environmentLight->isEnabled()) { |
| 1206 | ShaderData *shaderData = m_manager->shaderDataManager()->lookupResource(id: m_environmentLight->shaderData()); |
| 1207 | if (shaderData) { |
| 1208 | setDefaultUniformBlockShaderDataValue(uniformPack&: command->m_parameterPack, shader, shaderData, QStringLiteral("envLight" )); |
| 1209 | auto irr = |
| 1210 | shaderData->properties()["irradiance" ].value.value<Qt3DCore::QNodeId>(); |
| 1211 | auto spec = |
| 1212 | shaderData->properties()["specular" ].value.value<Qt3DCore::QNodeId>(); |
| 1213 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: irradianceId, value: irr); |
| 1214 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: specularId, value: spec); |
| 1215 | envLightCount = 1; |
| 1216 | } |
| 1217 | } else { |
| 1218 | // with some drivers, samplers (like the envbox sampler) need to be bound even though |
| 1219 | // they may not be actually used, otherwise draw calls can fail |
| 1220 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: irradianceId, value: m_renderer->submissionContext()->maxTextureUnitsCount()); |
| 1221 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: irradianceStructId, value: m_renderer->submissionContext()->maxTextureUnitsCount()); |
| 1222 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: specularId, value: m_renderer->submissionContext()->maxTextureUnitsCount()); |
| 1223 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: specularStructId, value: m_renderer->submissionContext()->maxTextureUnitsCount()); |
| 1224 | } |
| 1225 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: StringToInt::lookupId(QStringLiteral("envLightCount" )), value: envLightCount); |
| 1226 | } |
| 1227 | |
| 1228 | bool RenderView::hasBlitFramebufferInfo() const |
| 1229 | { |
| 1230 | return m_hasBlitFramebufferInfo; |
| 1231 | } |
| 1232 | |
| 1233 | void RenderView::setHasBlitFramebufferInfo(bool hasBlitFramebufferInfo) |
| 1234 | { |
| 1235 | m_hasBlitFramebufferInfo = hasBlitFramebufferInfo; |
| 1236 | } |
| 1237 | |
| 1238 | bool RenderView::shouldSkipSubmission() const |
| 1239 | { |
| 1240 | if (commandCount() > 0) |
| 1241 | return false; |
| 1242 | |
| 1243 | if (m_hasBlitFramebufferInfo) |
| 1244 | return false; |
| 1245 | |
| 1246 | if (m_isDownloadBuffersEnable) |
| 1247 | return false; |
| 1248 | |
| 1249 | if (m_showDebugOverlay) |
| 1250 | return false; |
| 1251 | |
| 1252 | if (!m_waitFences.empty() || !m_insertFenceIds.empty()) |
| 1253 | return false; |
| 1254 | |
| 1255 | if (m_clearBuffer != QClearBuffers::None) |
| 1256 | return false; |
| 1257 | |
| 1258 | if (!m_renderCaptureNodeId.isNull()) |
| 1259 | return false; |
| 1260 | |
| 1261 | return true; |
| 1262 | } |
| 1263 | |
| 1264 | BlitFramebufferInfo RenderView::blitFrameBufferInfo() const |
| 1265 | { |
| 1266 | return m_blitFrameBufferInfo; |
| 1267 | } |
| 1268 | |
| 1269 | void RenderView::setBlitFrameBufferInfo(const BlitFramebufferInfo &blitFrameBufferInfo) |
| 1270 | { |
| 1271 | m_blitFrameBufferInfo = blitFrameBufferInfo; |
| 1272 | } |
| 1273 | |
| 1274 | bool RenderView::isDownloadBuffersEnable() const |
| 1275 | { |
| 1276 | return m_isDownloadBuffersEnable; |
| 1277 | } |
| 1278 | |
| 1279 | void RenderView::setIsDownloadBuffersEnable(bool isDownloadBuffersEnable) |
| 1280 | { |
| 1281 | m_isDownloadBuffersEnable = isDownloadBuffersEnable; |
| 1282 | } |
| 1283 | |
| 1284 | } // namespace OpenGL |
| 1285 | } // namespace Render |
| 1286 | } // namespace Qt3DRender |
| 1287 | |
| 1288 | QT_END_NAMESPACE |
| 1289 | |