| 1 | // Copyright (C) 2020 Klaralvdalens Datakonsult AB (KDAB). |
| 2 | // Copyright (C) 2016 The Qt Company Ltd and/or its subsidiary(-ies). |
| 3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
| 4 | |
| 5 | #include "renderview_p.h" |
| 6 | #include <Qt3DRender/qmaterial.h> |
| 7 | #include <Qt3DRender/qrenderaspect.h> |
| 8 | #include <Qt3DRender/qrendertarget.h> |
| 9 | #include <Qt3DRender/qabstractlight.h> |
| 10 | #include <Qt3DRender/private/sphere_p.h> |
| 11 | |
| 12 | #include <Qt3DRender/private/cameraselectornode_p.h> |
| 13 | #include <Qt3DRender/private/framegraphnode_p.h> |
| 14 | #include <Qt3DRender/private/layerfilternode_p.h> |
| 15 | #include <Qt3DRender/private/qparameter_p.h> |
| 16 | #include <Qt3DRender/private/cameralens_p.h> |
| 17 | #include <Qt3DRender/private/effect_p.h> |
| 18 | #include <Qt3DRender/private/entity_p.h> |
| 19 | #include <Qt3DRender/private/nodemanagers_p.h> |
| 20 | #include <Qt3DRender/private/layer_p.h> |
| 21 | #include <Qt3DRender/private/renderlogging_p.h> |
| 22 | #include <Qt3DRender/private/renderpassfilternode_p.h> |
| 23 | #include <Qt3DRender/private/renderpass_p.h> |
| 24 | #include <Qt3DRender/private/geometryrenderer_p.h> |
| 25 | #include <Qt3DRender/private/techniquefilternode_p.h> |
| 26 | #include <Qt3DRender/private/viewportnode_p.h> |
| 27 | #include <Qt3DRender/private/buffermanager_p.h> |
| 28 | #include <Qt3DRender/private/geometryrenderermanager_p.h> |
| 29 | #include <Qt3DRender/private/rendercapture_p.h> |
| 30 | #include <Qt3DRender/private/buffercapture_p.h> |
| 31 | #include <Qt3DRender/private/stringtoint_p.h> |
| 32 | #include <Qt3DRender/private/renderlogging_p.h> |
| 33 | #include <Qt3DRender/private/renderstateset_p.h> |
| 34 | #include <Qt3DRender/private/uniformblockbuilder_p.h> |
| 35 | #include <Qt3DRender/private/clearbuffers_p.h> |
| 36 | #include <Qt3DRender/private/rendertargetselectornode_p.h> |
| 37 | #include <Qt3DRender/private/sortpolicy_p.h> |
| 38 | #include <Qt3DRender/private/techniquefilternode_p.h> |
| 39 | #include <Qt3DRender/private/managers_p.h> |
| 40 | #include <Qt3DRender/private/shaderdata_p.h> |
| 41 | #include <Qt3DRender/private/statesetnode_p.h> |
| 42 | #include <Qt3DRender/private/dispatchcompute_p.h> |
| 43 | #include <Qt3DRender/private/rendersurfaceselector_p.h> |
| 44 | #include <Qt3DRender/private/rendercapture_p.h> |
| 45 | #include <Qt3DRender/private/buffercapture_p.h> |
| 46 | #include <Qt3DRender/private/techniquemanager_p.h> |
| 47 | #include <Qt3DRender/private/blitframebuffer_p.h> |
| 48 | #include <Qt3DRender/private/rendercapture_p.h> |
| 49 | |
| 50 | #include <rendercommand_p.h> |
| 51 | #include <renderer_p.h> |
| 52 | #include <submissioncontext_p.h> |
| 53 | #include <rhiresourcemanagers_p.h> |
| 54 | #include <Qt3DCore/qentity.h> |
| 55 | #include <QtGui/qsurface.h> |
| 56 | #include <algorithm> |
| 57 | #include <atomic> |
| 58 | #include <cstdlib> |
| 59 | #include <QDebug> |
| 60 | #if defined(QT3D_RENDER_VIEW_JOB_TIMINGS) |
| 61 | #include <QElapsedTimer> |
| 62 | #endif |
| 63 | |
| 64 | QT_BEGIN_NAMESPACE |
| 65 | |
| 66 | namespace Qt3DRender { |
| 67 | namespace Render { |
| 68 | namespace Rhi { |
| 69 | |
| 70 | namespace { |
| 71 | |
| 72 | // register our QNodeId's as a metatype during program loading |
| 73 | Q_DECL_UNUSED const int qNodeIdTypeId = qMetaTypeId<Qt3DCore::QNodeId>(); |
| 74 | |
| 75 | const int MAX_LIGHTS = 8; |
| 76 | |
| 77 | #define LIGHT_POSITION_NAME QLatin1String(".position") |
| 78 | #define LIGHT_TYPE_NAME QLatin1String(".type") |
| 79 | #define LIGHT_COLOR_NAME QLatin1String(".color") |
| 80 | #define LIGHT_INTENSITY_NAME QLatin1String(".intensity") |
| 81 | |
| 82 | int LIGHT_COUNT_NAME_ID = 0; |
| 83 | int LIGHT_POSITION_NAMES[MAX_LIGHTS]; |
| 84 | int LIGHT_TYPE_NAMES[MAX_LIGHTS]; |
| 85 | int LIGHT_COLOR_NAMES[MAX_LIGHTS]; |
| 86 | int LIGHT_INTENSITY_NAMES[MAX_LIGHTS]; |
| 87 | QString LIGHT_STRUCT_NAMES[MAX_LIGHTS]; |
| 88 | |
| 89 | int LIGHT_POSITION_UNROLL_NAMES[MAX_LIGHTS]; |
| 90 | int LIGHT_TYPE_UNROLL_NAMES[MAX_LIGHTS]; |
| 91 | int LIGHT_COLOR_UNROLL_NAMES[MAX_LIGHTS]; |
| 92 | int LIGHT_INTENSITY_UNROLL_NAMES[MAX_LIGHTS]; |
| 93 | QString LIGHT_STRUCT_UNROLL_NAMES[MAX_LIGHTS]; |
| 94 | |
| 95 | std::atomic_bool wasInitialized {}; |
| 96 | |
| 97 | } // anonymous namespace |
| 98 | |
| 99 | // TODO: Move this somewhere global where GraphicsContext::setViewport() can use it too |
| 100 | static QRectF resolveViewport(const QRectF &fractionalViewport, const QSize &surfaceSize) |
| 101 | { |
| 102 | return QRectF(fractionalViewport.x() * surfaceSize.width(), |
| 103 | (1.0 - fractionalViewport.y() - fractionalViewport.height()) |
| 104 | * surfaceSize.height(), |
| 105 | fractionalViewport.width() * surfaceSize.width(), |
| 106 | fractionalViewport.height() * surfaceSize.height()); |
| 107 | } |
| 108 | |
| 109 | static Matrix4x4 getProjectionMatrix(const CameraLens *lens) |
| 110 | { |
| 111 | Matrix4x4 m; |
| 112 | if (lens) |
| 113 | m = lens->projection(); |
| 114 | return m; |
| 115 | } |
| 116 | |
| 117 | /*! |
| 118 | \internal |
| 119 | Walks up the framegraph tree from \a fgLeaf and builds up as much state |
| 120 | as possible and populates \a rv. For cases where we can't get the specific state |
| 121 | (e.g. because it depends upon more than just the framegraph) we store the data from |
| 122 | the framegraph that will be needed to later when the rest of the data becomes available |
| 123 | */ |
| 124 | void RenderView::setRenderViewConfigFromFrameGraphLeafNode(RenderView *rv, const FrameGraphNode *fgLeaf) |
| 125 | { |
| 126 | // The specific RenderPass to be used is also dependent upon the Effect and TechniqueFilter |
| 127 | // which is referenced by the Material which is referenced by the RenderMesh. So we can |
| 128 | // only store the filter info in the RenderView structure and use it to do the resolving |
| 129 | // when we build the RenderCommand list. |
| 130 | const NodeManagers *manager = rv->nodeManagers(); |
| 131 | const FrameGraphNode *node = fgLeaf; |
| 132 | |
| 133 | while (node) { |
| 134 | FrameGraphNode::FrameGraphNodeType type = node->nodeType(); |
| 135 | if (node->isEnabled()) |
| 136 | switch (type) { |
| 137 | case FrameGraphNode::InvalidNodeType: |
| 138 | // A base FrameGraphNode, can be used for grouping purposes |
| 139 | break; |
| 140 | case FrameGraphNode::CameraSelector: |
| 141 | // Can be set only once and we take camera nearest to the leaf node |
| 142 | if (!rv->renderCameraLens()) { |
| 143 | const CameraSelector *cameraSelector = |
| 144 | static_cast<const CameraSelector *>(node); |
| 145 | Entity *camNode = manager->renderNodesManager()->lookupResource( |
| 146 | id: cameraSelector->cameraUuid()); |
| 147 | if (camNode) { |
| 148 | CameraLens *lens = camNode->renderComponent<CameraLens>(); |
| 149 | rv->setRenderCameraEntity(camNode); |
| 150 | if (lens && lens->isEnabled()) { |
| 151 | rv->setRenderCameraLens(lens); |
| 152 | // ViewMatrix and ProjectionMatrix are computed |
| 153 | // later in updateMatrices() |
| 154 | // since at this point the transformation matrices |
| 155 | // may not yet have been updated |
| 156 | } |
| 157 | } |
| 158 | } |
| 159 | break; |
| 160 | |
| 161 | case FrameGraphNode::LayerFilter: // Can be set multiple times in the tree |
| 162 | rv->appendLayerFilter(layerFilterId: static_cast<const LayerFilterNode *>(node)->peerId()); |
| 163 | break; |
| 164 | |
| 165 | case FrameGraphNode::ProximityFilter: // Can be set multiple times in the tree |
| 166 | rv->appendProximityFilterId(proximityFilterId: node->peerId()); |
| 167 | break; |
| 168 | |
| 169 | case FrameGraphNode::RenderPassFilter: |
| 170 | // Can be set once |
| 171 | // TODO: Amalgamate all render pass filters from leaf to root |
| 172 | if (!rv->renderPassFilter()) |
| 173 | rv->setRenderPassFilter(static_cast<const RenderPassFilter *>(node)); |
| 174 | break; |
| 175 | |
| 176 | case FrameGraphNode::RenderTarget: { |
| 177 | // Can be set once and we take render target nearest to the leaf node |
| 178 | const RenderTargetSelector *targetSelector = |
| 179 | static_cast<const RenderTargetSelector *>(node); |
| 180 | Qt3DCore::QNodeId renderTargetUid = targetSelector->renderTargetUuid(); |
| 181 | |
| 182 | // Note: we ignore the render target outputs the RenderTargetSelector |
| 183 | // might specify as we can't handle that with RHI |
| 184 | |
| 185 | // Add renderTarget Handle |
| 186 | if (!rv->renderTargetId()) { |
| 187 | rv->setRenderTargetId(renderTargetUid); |
| 188 | } |
| 189 | break; |
| 190 | } |
| 191 | |
| 192 | case FrameGraphNode::ClearBuffers: { |
| 193 | const ClearBuffers *cbNode = static_cast<const ClearBuffers *>(node); |
| 194 | rv->addClearBuffers(cb: cbNode); |
| 195 | break; |
| 196 | } |
| 197 | |
| 198 | case FrameGraphNode::TechniqueFilter: |
| 199 | // Can be set once |
| 200 | // TODO Amalgamate all technique filters from leaf to root |
| 201 | if (!rv->techniqueFilter()) |
| 202 | rv->setTechniqueFilter(static_cast<const TechniqueFilter *>(node)); |
| 203 | break; |
| 204 | |
| 205 | case FrameGraphNode::Viewport: { |
| 206 | // If the Viewport has already been set in a lower node |
| 207 | // Make it so that the new viewport is actually |
| 208 | // a subregion relative to that of the parent viewport |
| 209 | const ViewportNode *vpNode = static_cast<const ViewportNode *>(node); |
| 210 | rv->setViewport(ViewportNode::computeViewport(childViewport: rv->viewport(), parentViewport: vpNode)); |
| 211 | rv->setGamma(vpNode->gamma()); |
| 212 | break; |
| 213 | } |
| 214 | |
| 215 | case FrameGraphNode::SortMethod: { |
| 216 | const Render::SortPolicy *sortPolicy = |
| 217 | static_cast<const Render::SortPolicy *>(node); |
| 218 | rv->addSortType(sortTypes: sortPolicy->sortTypes()); |
| 219 | break; |
| 220 | } |
| 221 | |
| 222 | case FrameGraphNode::SubtreeEnabler: |
| 223 | // Has no meaning here. SubtreeEnabler was used |
| 224 | // in a prior step to filter the list of RenderViewJobs |
| 225 | break; |
| 226 | |
| 227 | case FrameGraphNode::StateSet: { |
| 228 | const Render::StateSetNode *rStateSet = static_cast<const Render::StateSetNode *>(node); |
| 229 | // Add states from new stateSet we might be missing |
| 230 | // but don' t override existing states (lower StateSetNode always has priority) |
| 231 | if (rStateSet->hasRenderStates()) { |
| 232 | // Create global RenderStateSet for renderView if no stateSet was set before |
| 233 | RenderStateSet *stateSet = rv->getOrCreateStateSet(); |
| 234 | addStatesToRenderStateSet(stateSet, stateIds: rStateSet->renderStates(), manager: manager->renderStateManager()); |
| 235 | } |
| 236 | break; |
| 237 | } |
| 238 | |
| 239 | case FrameGraphNode::NoDraw: { |
| 240 | rv->setNoDraw(true); |
| 241 | break; |
| 242 | } |
| 243 | |
| 244 | case FrameGraphNode::FrustumCulling: { |
| 245 | rv->setFrustumCulling(true); |
| 246 | break; |
| 247 | } |
| 248 | |
| 249 | case FrameGraphNode::ComputeDispatch: { |
| 250 | const Render::DispatchCompute *dispatchCompute = |
| 251 | static_cast<const Render::DispatchCompute *>(node); |
| 252 | rv->setCompute(true); |
| 253 | rv->setComputeWorkgroups(x: dispatchCompute->x(), y: dispatchCompute->y(), |
| 254 | z: dispatchCompute->z()); |
| 255 | break; |
| 256 | } |
| 257 | |
| 258 | case FrameGraphNode::Lighting: { |
| 259 | // TODO |
| 260 | break; |
| 261 | } |
| 262 | |
| 263 | case FrameGraphNode::Surface: { |
| 264 | // Use the surface closest to leaf node |
| 265 | if (rv->surface() == nullptr) { |
| 266 | const Render::RenderSurfaceSelector *surfaceSelector = |
| 267 | static_cast<const Render::RenderSurfaceSelector *>(node); |
| 268 | rv->setSurface(surfaceSelector->surface()); |
| 269 | rv->setSurfaceSize(surfaceSelector->renderTargetSize() |
| 270 | * surfaceSelector->devicePixelRatio()); |
| 271 | rv->setDevicePixelRatio(surfaceSelector->devicePixelRatio()); |
| 272 | } |
| 273 | break; |
| 274 | } |
| 275 | case FrameGraphNode::RenderCapture: { |
| 276 | auto *renderCapture = const_cast<Render::RenderCapture *>( |
| 277 | static_cast<const Render::RenderCapture *>(node)); |
| 278 | if (rv->renderCaptureNodeId().isNull() && renderCapture->wasCaptureRequested()) { |
| 279 | rv->setRenderCaptureNodeId(renderCapture->peerId()); |
| 280 | rv->setRenderCaptureRequest(renderCapture->takeCaptureRequest()); |
| 281 | } |
| 282 | break; |
| 283 | } |
| 284 | |
| 285 | case FrameGraphNode::MemoryBarrier: { |
| 286 | // Not available in rhi |
| 287 | break; |
| 288 | } |
| 289 | |
| 290 | case FrameGraphNode::BufferCapture: { |
| 291 | auto *bufferCapture = const_cast<Render::BufferCapture *>( |
| 292 | static_cast<const Render::BufferCapture *>(node)); |
| 293 | if (bufferCapture != nullptr) |
| 294 | rv->setIsDownloadBuffersEnable(bufferCapture->isEnabled()); |
| 295 | break; |
| 296 | } |
| 297 | |
| 298 | case FrameGraphNode::BlitFramebuffer: { |
| 299 | const Render::BlitFramebuffer *blitFramebufferNode = |
| 300 | static_cast<const Render::BlitFramebuffer *>(node); |
| 301 | rv->setHasBlitFramebufferInfo(true); |
| 302 | BlitFramebufferInfo bfbInfo; |
| 303 | bfbInfo.sourceRenderTargetId = blitFramebufferNode->sourceRenderTargetId(); |
| 304 | bfbInfo.destinationRenderTargetId = |
| 305 | blitFramebufferNode->destinationRenderTargetId(); |
| 306 | bfbInfo.sourceRect = blitFramebufferNode->sourceRect(); |
| 307 | bfbInfo.destinationRect = blitFramebufferNode->destinationRect(); |
| 308 | bfbInfo.sourceAttachmentPoint = blitFramebufferNode->sourceAttachmentPoint(); |
| 309 | bfbInfo.destinationAttachmentPoint = |
| 310 | blitFramebufferNode->destinationAttachmentPoint(); |
| 311 | bfbInfo.interpolationMethod = blitFramebufferNode->interpolationMethod(); |
| 312 | rv->setBlitFrameBufferInfo(bfbInfo); |
| 313 | break; |
| 314 | } |
| 315 | |
| 316 | case FrameGraphNode::WaitFence: { |
| 317 | // Not available in rhi |
| 318 | break; |
| 319 | } |
| 320 | |
| 321 | case FrameGraphNode::SetFence: { |
| 322 | // Not available in rhi |
| 323 | break; |
| 324 | } |
| 325 | |
| 326 | case FrameGraphNode::NoPicking: |
| 327 | // Nothing to do RenderView wise for NoPicking |
| 328 | break; |
| 329 | |
| 330 | case FrameGraphNode::DebugOverlay: |
| 331 | // Not supported yet with RHI |
| 332 | break; |
| 333 | |
| 334 | default: |
| 335 | // Should never get here |
| 336 | qCWarning(Backend) << "Unhandled FrameGraphNode type" ; |
| 337 | } |
| 338 | |
| 339 | node = node->parent(); |
| 340 | } |
| 341 | } |
| 342 | |
| 343 | RenderView::RenderView() |
| 344 | { |
| 345 | if (Q_UNLIKELY(!wasInitialized.exchange(true))) { |
| 346 | // Needed as we can control the init order of static/global variables across compile units |
| 347 | // and this hash relies on the static StringToInt class |
| 348 | |
| 349 | LIGHT_COUNT_NAME_ID = StringToInt::lookupId(str: QLatin1String("lightCount" )); |
| 350 | for (int i = 0; i < MAX_LIGHTS; ++i) { |
| 351 | Q_STATIC_ASSERT_X(MAX_LIGHTS < 10, "can't use the QChar trick anymore" ); |
| 352 | LIGHT_STRUCT_NAMES[i] = |
| 353 | QLatin1String("lights[" ) + QLatin1Char(char('0' + i)) + QLatin1Char(']'); |
| 354 | LIGHT_POSITION_NAMES[i] = |
| 355 | StringToInt::lookupId(str: LIGHT_STRUCT_NAMES[i] + LIGHT_POSITION_NAME); |
| 356 | LIGHT_TYPE_NAMES[i] = StringToInt::lookupId(str: LIGHT_STRUCT_NAMES[i] + LIGHT_TYPE_NAME); |
| 357 | LIGHT_COLOR_NAMES[i] = StringToInt::lookupId(str: LIGHT_STRUCT_NAMES[i] + LIGHT_COLOR_NAME); |
| 358 | LIGHT_INTENSITY_NAMES[i] = |
| 359 | StringToInt::lookupId(str: LIGHT_STRUCT_NAMES[i] + LIGHT_INTENSITY_NAME); |
| 360 | |
| 361 | LIGHT_STRUCT_UNROLL_NAMES[i] = |
| 362 | QLatin1String("light_" ) + QLatin1Char(char('0' + i)); |
| 363 | LIGHT_POSITION_UNROLL_NAMES[i] = |
| 364 | StringToInt::lookupId(str: LIGHT_STRUCT_UNROLL_NAMES[i] + LIGHT_POSITION_NAME); |
| 365 | LIGHT_TYPE_UNROLL_NAMES[i] = StringToInt::lookupId(str: LIGHT_STRUCT_UNROLL_NAMES[i] + LIGHT_TYPE_NAME); |
| 366 | LIGHT_COLOR_UNROLL_NAMES[i] = StringToInt::lookupId(str: LIGHT_STRUCT_UNROLL_NAMES[i] + LIGHT_COLOR_NAME); |
| 367 | LIGHT_INTENSITY_UNROLL_NAMES[i] = |
| 368 | StringToInt::lookupId(str: LIGHT_STRUCT_UNROLL_NAMES[i] + LIGHT_INTENSITY_NAME); |
| 369 | } |
| 370 | } |
| 371 | } |
| 372 | |
| 373 | RenderView::~RenderView() |
| 374 | { |
| 375 | } |
| 376 | |
| 377 | namespace { |
| 378 | |
| 379 | template<int SortType> |
| 380 | struct AdjacentSubRangeFinder |
| 381 | { |
| 382 | static bool adjacentSubRange(const RenderCommand &, const RenderCommand &) |
| 383 | { |
| 384 | Q_UNREACHABLE_RETURN(false); |
| 385 | } |
| 386 | }; |
| 387 | |
| 388 | template<> |
| 389 | struct AdjacentSubRangeFinder<QSortPolicy::StateChangeCost> |
| 390 | { |
| 391 | static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b) |
| 392 | { |
| 393 | return a.m_changeCost == b.m_changeCost; |
| 394 | } |
| 395 | }; |
| 396 | |
| 397 | template<> |
| 398 | struct AdjacentSubRangeFinder<QSortPolicy::BackToFront> |
| 399 | { |
| 400 | static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b) |
| 401 | { |
| 402 | return qFuzzyCompare(p1: a.m_depth, p2: b.m_depth); |
| 403 | } |
| 404 | }; |
| 405 | |
| 406 | template<> |
| 407 | struct AdjacentSubRangeFinder<QSortPolicy::Material> |
| 408 | { |
| 409 | static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b) |
| 410 | { |
| 411 | return a.m_rhiShader == b.m_rhiShader; |
| 412 | } |
| 413 | }; |
| 414 | |
| 415 | template<> |
| 416 | struct AdjacentSubRangeFinder<QSortPolicy::FrontToBack> |
| 417 | { |
| 418 | static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b) |
| 419 | { |
| 420 | return qFuzzyCompare(p1: a.m_depth, p2: b.m_depth); |
| 421 | } |
| 422 | }; |
| 423 | |
| 424 | template<> |
| 425 | struct AdjacentSubRangeFinder<QSortPolicy::Texture> |
| 426 | { |
| 427 | static bool adjacentSubRange(const RenderCommand &a, const RenderCommand &b) |
| 428 | { |
| 429 | // Two renderCommands are adjacent if one contains all the other command's textures |
| 430 | const std::vector<ShaderParameterPack::NamedResource> &texturesA = a.m_parameterPack.textures(); |
| 431 | const std::vector<ShaderParameterPack::NamedResource> &texturesB = b.m_parameterPack.textures(); |
| 432 | |
| 433 | const bool bBigger = texturesB.size() > texturesA.size(); |
| 434 | const std::vector<ShaderParameterPack::NamedResource> &smallestVector = bBigger ? texturesA : texturesB; |
| 435 | const std::vector<ShaderParameterPack::NamedResource> &biggestVector = bBigger ? texturesB : texturesA; |
| 436 | |
| 437 | const auto e = biggestVector.cend(); |
| 438 | for (const ShaderParameterPack::NamedResource &tex : smallestVector) { |
| 439 | if (std::find(first: biggestVector.begin(), last: e, val: tex) == e) |
| 440 | return false; |
| 441 | } |
| 442 | |
| 443 | return true; |
| 444 | } |
| 445 | }; |
| 446 | |
| 447 | template<typename Predicate> |
| 448 | int advanceUntilNonAdjacent(const EntityRenderCommandDataView *view, |
| 449 | const size_t beg, const size_t end, Predicate pred) |
| 450 | { |
| 451 | const std::vector<size_t> &commandIndices = view->indices; |
| 452 | const std::vector<RenderCommand> &commands = view->data.commands; |
| 453 | size_t i = beg + 1; |
| 454 | if (i < end) { |
| 455 | const size_t startIdx = commandIndices[beg]; |
| 456 | while (i < end) { |
| 457 | const size_t targetIdx = commandIndices[i]; |
| 458 | if (!pred(commands[startIdx], commands[targetIdx])) |
| 459 | break; |
| 460 | ++i; |
| 461 | } |
| 462 | } |
| 463 | return int(i); |
| 464 | } |
| 465 | |
| 466 | |
| 467 | template<int SortType> |
| 468 | struct SubRangeSorter |
| 469 | { |
| 470 | static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end) |
| 471 | { |
| 472 | Q_UNUSED(view); |
| 473 | Q_UNUSED(begin); |
| 474 | Q_UNUSED(end); |
| 475 | Q_UNREACHABLE(); |
| 476 | } |
| 477 | }; |
| 478 | |
| 479 | template<> |
| 480 | struct SubRangeSorter<QSortPolicy::StateChangeCost> |
| 481 | { |
| 482 | static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end) |
| 483 | { |
| 484 | std::vector<size_t> &commandIndices = view->indices; |
| 485 | const std::vector<RenderCommand> &commands = view->data.commands; |
| 486 | std::stable_sort(first: commandIndices.begin() + begin, last: commandIndices.begin() + end, |
| 487 | comp: [&commands] (const size_t &iA, const size_t &iB) { |
| 488 | const RenderCommand &a = commands[iA]; |
| 489 | const RenderCommand &b = commands[iB]; |
| 490 | return a.m_changeCost > b.m_changeCost; |
| 491 | }); |
| 492 | } |
| 493 | }; |
| 494 | |
| 495 | template<> |
| 496 | struct SubRangeSorter<QSortPolicy::BackToFront> |
| 497 | { |
| 498 | static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end) |
| 499 | { |
| 500 | std::vector<size_t> &commandIndices = view->indices; |
| 501 | const std::vector<RenderCommand> &commands = view->data.commands; |
| 502 | std::stable_sort(first: commandIndices.begin() + begin, last: commandIndices.begin() + end, |
| 503 | comp: [&commands] (const size_t &iA, const size_t &iB) { |
| 504 | const RenderCommand &a = commands[iA]; |
| 505 | const RenderCommand &b = commands[iB]; |
| 506 | return a.m_depth > b.m_depth; |
| 507 | }); |
| 508 | } |
| 509 | }; |
| 510 | |
| 511 | template<> |
| 512 | struct SubRangeSorter<QSortPolicy::Material> |
| 513 | { |
| 514 | static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end) |
| 515 | { |
| 516 | std::vector<size_t> &commandIndices = view->indices; |
| 517 | const std::vector<RenderCommand> &commands = view->data.commands; |
| 518 | std::stable_sort(first: commandIndices.begin() + begin, last: commandIndices.begin() + end, |
| 519 | comp: [&commands] (const size_t &iA, const size_t &iB) { |
| 520 | const RenderCommand &a = commands[iA]; |
| 521 | const RenderCommand &b = commands[iB]; |
| 522 | return a.m_rhiShader > b.m_rhiShader; |
| 523 | }); |
| 524 | } |
| 525 | }; |
| 526 | |
| 527 | template<> |
| 528 | struct SubRangeSorter<QSortPolicy::FrontToBack> |
| 529 | { |
| 530 | static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end) |
| 531 | { |
| 532 | std::vector<size_t> &commandIndices = view->indices; |
| 533 | const std::vector<RenderCommand> &commands = view->data.commands; |
| 534 | std::stable_sort(first: commandIndices.begin() + begin, last: commandIndices.begin() + end, |
| 535 | comp: [&commands] (const size_t &iA, const size_t &iB) { |
| 536 | const RenderCommand &a = commands[iA]; |
| 537 | const RenderCommand &b = commands[iB]; |
| 538 | return a.m_depth < b.m_depth; |
| 539 | }); |
| 540 | } |
| 541 | }; |
| 542 | |
| 543 | template<> |
| 544 | struct SubRangeSorter<QSortPolicy::Texture> |
| 545 | { |
| 546 | static void sortSubRange(EntityRenderCommandDataView *view, size_t begin, const size_t end) |
| 547 | { |
| 548 | #ifndef Q_OS_WIN |
| 549 | std::vector<size_t> &commandIndices = view->indices; |
| 550 | const std::vector<RenderCommand> &commands = view->data.commands; |
| 551 | std::stable_sort(first: commandIndices.begin() + begin, last: commandIndices.begin() + end, |
| 552 | comp: [&commands] (const int &iA, const int &iB) { |
| 553 | const RenderCommand &a = commands[iA]; |
| 554 | const RenderCommand &b = commands[iB]; |
| 555 | const std::vector<ShaderParameterPack::NamedResource> &texturesA = a.m_parameterPack.textures(); |
| 556 | const std::vector<ShaderParameterPack::NamedResource> &texturesB = b.m_parameterPack.textures(); |
| 557 | |
| 558 | const bool bBigger = texturesB.size() > texturesA.size(); |
| 559 | const std::vector<ShaderParameterPack::NamedResource> &smallestVector = bBigger ? texturesA : texturesB; |
| 560 | const std::vector<ShaderParameterPack::NamedResource> &biggestVector = bBigger ? texturesB : texturesA; |
| 561 | |
| 562 | size_t identicalTextureCount = 0; |
| 563 | const auto e = biggestVector.cend(); |
| 564 | for (const ShaderParameterPack::NamedResource &tex : smallestVector) { |
| 565 | if (std::find(first: biggestVector.begin(), last: e, val: tex) != e) |
| 566 | ++identicalTextureCount; |
| 567 | } |
| 568 | |
| 569 | return identicalTextureCount < smallestVector.size(); |
| 570 | }); |
| 571 | #else |
| 572 | Q_UNUSED(view); |
| 573 | Q_UNUSED(begin); |
| 574 | Q_UNUSED(end); |
| 575 | #endif |
| 576 | } |
| 577 | }; |
| 578 | |
| 579 | int findSubRange(const EntityRenderCommandDataView *view, |
| 580 | const int begin, const int end, |
| 581 | const QSortPolicy::SortType sortType) |
| 582 | { |
| 583 | switch (sortType) { |
| 584 | case QSortPolicy::StateChangeCost: |
| 585 | return advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::StateChangeCost>::adjacentSubRange); |
| 586 | case QSortPolicy::BackToFront: |
| 587 | return advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::BackToFront>::adjacentSubRange); |
| 588 | case QSortPolicy::Material: |
| 589 | return advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::Material>::adjacentSubRange); |
| 590 | case QSortPolicy::FrontToBack: |
| 591 | return advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::FrontToBack>::adjacentSubRange); |
| 592 | case QSortPolicy::Texture: |
| 593 | return advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::Texture>::adjacentSubRange); |
| 594 | case QSortPolicy::Uniform: |
| 595 | return end; |
| 596 | default: |
| 597 | Q_UNREACHABLE_RETURN(end); |
| 598 | } |
| 599 | } |
| 600 | |
| 601 | void sortByMaterial(EntityRenderCommandDataView *view, int begin, const int end) |
| 602 | { |
| 603 | // We try to arrange elements so that their rendering cost is minimized for a given shader |
| 604 | std::vector<size_t> &commandIndices = view->indices; |
| 605 | const std::vector<RenderCommand> &commands = view->data.commands; |
| 606 | int rangeEnd = advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::Material>::adjacentSubRange); |
| 607 | while (begin != end) { |
| 608 | if (begin + 1 < rangeEnd) { |
| 609 | std::stable_sort(first: commandIndices.begin() + begin + 1, last: commandIndices.begin() + rangeEnd, |
| 610 | comp: [&commands] (const size_t &iA, const size_t &iB) { |
| 611 | const RenderCommand &a = commands[iA]; |
| 612 | const RenderCommand &b = commands[iB]; |
| 613 | return a.m_material.handle() < b.m_material.handle(); |
| 614 | }); |
| 615 | } |
| 616 | begin = rangeEnd; |
| 617 | rangeEnd = advanceUntilNonAdjacent(view, beg: begin, end, pred: AdjacentSubRangeFinder<QSortPolicy::Material>::adjacentSubRange); |
| 618 | } |
| 619 | } |
| 620 | |
| 621 | void sortCommandRange(EntityRenderCommandDataView *view, int begin, int end, const size_t level, |
| 622 | const std::vector<Qt3DRender::QSortPolicy::SortType> &sortingTypes) |
| 623 | { |
| 624 | if (level >= sortingTypes.size()) |
| 625 | return; |
| 626 | |
| 627 | switch (sortingTypes.at(n: level)) { |
| 628 | case QSortPolicy::StateChangeCost: |
| 629 | SubRangeSorter<QSortPolicy::StateChangeCost>::sortSubRange(view, begin, end); |
| 630 | break; |
| 631 | case QSortPolicy::BackToFront: |
| 632 | SubRangeSorter<QSortPolicy::BackToFront>::sortSubRange(view, begin, end); |
| 633 | break; |
| 634 | case QSortPolicy::Material: |
| 635 | // Groups all same shader DNA together |
| 636 | SubRangeSorter<QSortPolicy::Material>::sortSubRange(view, begin, end); |
| 637 | // Group all same material together (same parameters most likely) |
| 638 | sortByMaterial(view, begin, end); |
| 639 | break; |
| 640 | case QSortPolicy::FrontToBack: |
| 641 | SubRangeSorter<QSortPolicy::FrontToBack>::sortSubRange(view, begin, end); |
| 642 | break; |
| 643 | case QSortPolicy::Texture: |
| 644 | SubRangeSorter<QSortPolicy::Texture>::sortSubRange(view, begin, end); |
| 645 | break; |
| 646 | case QSortPolicy::Uniform: |
| 647 | break; |
| 648 | default: |
| 649 | Q_UNREACHABLE(); |
| 650 | } |
| 651 | |
| 652 | // For all sub ranges of adjacent item for sortType[i] |
| 653 | // Perform filtering with sortType[i + 1] |
| 654 | int rangeEnd = findSubRange(view, begin, end, sortType: sortingTypes.at(n: level)); |
| 655 | while (begin != end) { |
| 656 | sortCommandRange(view, begin, end: rangeEnd, level: level + 1, sortingTypes); |
| 657 | begin = rangeEnd; |
| 658 | rangeEnd = findSubRange(view, begin, end, sortType: sortingTypes.at(n: level)); |
| 659 | } |
| 660 | } |
| 661 | |
| 662 | } // anonymous |
| 663 | |
| 664 | void RenderView::sort() |
| 665 | { |
| 666 | assert(m_renderCommandDataView); |
| 667 | // Compares the bitsetKey of the RenderCommands |
| 668 | // Key[Depth | StateCost | Shader] |
| 669 | sortCommandRange(view: m_renderCommandDataView.data(), begin: 0, end: int(m_renderCommandDataView->size()), level: 0, sortingTypes: m_sortingTypes); |
| 670 | |
| 671 | // For RenderCommand with the same shader |
| 672 | // We compute the adjacent change cost |
| 673 | |
| 674 | // Only perform uniform minimization if we explicitly asked for it |
| 675 | if (!Qt3DCore::contains(destination: m_sortingTypes, element: QSortPolicy::Uniform)) |
| 676 | return; |
| 677 | |
| 678 | // Minimize uniform changes |
| 679 | size_t i = 0; |
| 680 | std::vector<RenderCommand> &commands = m_renderCommandDataView->data.commands; |
| 681 | const std::vector<size_t> &indices = m_renderCommandDataView->indices; |
| 682 | const size_t commandSize = indices.size(); |
| 683 | |
| 684 | while (i < commandSize) { |
| 685 | size_t j = i; |
| 686 | |
| 687 | // Advance while commands share the same shader |
| 688 | while (i < commandSize && |
| 689 | commands[indices[j]].m_rhiShader == commands[indices[i]].m_rhiShader) |
| 690 | ++i; |
| 691 | |
| 692 | if (i - j > 0) { // Several commands have the same shader, so we minimize uniform changes |
| 693 | PackUniformHash cachedUniforms = commands[indices[j++]].m_parameterPack.uniforms(); |
| 694 | |
| 695 | while (j < i) { |
| 696 | // We need the reference here as we are modifying the original container |
| 697 | // not the copy |
| 698 | PackUniformHash &uniforms = commands[indices[j]].m_parameterPack.m_uniforms; |
| 699 | |
| 700 | for (size_t u = 0; u < uniforms.keys.size();) { |
| 701 | // We are comparing the values: |
| 702 | // - raw uniform values |
| 703 | // - the texture Node id if the uniform represents a texture |
| 704 | // since all textures are assigned texture units before the RenderCommands |
| 705 | // sharing the same material (shader) are rendered, we can't have the case |
| 706 | // where two uniforms, referencing the same texture eventually have 2 different |
| 707 | // texture unit values |
| 708 | const int uniformNameId = uniforms.keys.at(n: u); |
| 709 | const UniformValue &refValue = cachedUniforms.value(key: uniformNameId); |
| 710 | const UniformValue &newValue = uniforms.values.at(n: u); |
| 711 | if (newValue == refValue) { |
| 712 | uniforms.erase(idx: int(u)); |
| 713 | } else { |
| 714 | // Record updated value so that subsequent comparison |
| 715 | // for the next command will be made againts latest |
| 716 | // uniform value |
| 717 | cachedUniforms.insert(key: uniformNameId, value: newValue); |
| 718 | ++u; |
| 719 | } |
| 720 | } |
| 721 | ++j; |
| 722 | } |
| 723 | } |
| 724 | } |
| 725 | } |
| 726 | |
| 727 | void RenderView::setRenderer(Renderer *renderer) |
| 728 | { |
| 729 | m_renderer = renderer; |
| 730 | m_manager = renderer->nodeManagers(); |
| 731 | } |
| 732 | |
| 733 | RenderStateSet *RenderView::getOrCreateStateSet() |
| 734 | { |
| 735 | if (!m_stateSet) |
| 736 | m_stateSet.reset(other: new RenderStateSet()); |
| 737 | return m_stateSet.data(); |
| 738 | } |
| 739 | |
| 740 | void RenderView::addClearBuffers(const ClearBuffers *cb) |
| 741 | { |
| 742 | QClearBuffers::BufferTypeFlags type = cb->type(); |
| 743 | |
| 744 | if (type & QClearBuffers::StencilBuffer) { |
| 745 | m_clearStencilValue = cb->clearStencilValue(); |
| 746 | m_clearBuffer |= QClearBuffers::StencilBuffer; |
| 747 | } |
| 748 | if (type & QClearBuffers::DepthBuffer) { |
| 749 | m_clearDepthValue = cb->clearDepthValue(); |
| 750 | m_clearBuffer |= QClearBuffers::DepthBuffer; |
| 751 | } |
| 752 | // keep track of global ClearColor (if set) and collect all DrawBuffer-specific |
| 753 | // ClearColors |
| 754 | if (type & QClearBuffers::ColorBuffer) { |
| 755 | ClearBufferInfo clearBufferInfo; |
| 756 | clearBufferInfo.clearColor = cb->clearColor(); |
| 757 | |
| 758 | if (cb->clearsAllColorBuffers()) { |
| 759 | m_globalClearColorBuffer = clearBufferInfo; |
| 760 | m_clearBuffer |= QClearBuffers::ColorBuffer; |
| 761 | } else { |
| 762 | if (cb->bufferId()) { |
| 763 | const RenderTargetOutput *targetOutput = |
| 764 | m_manager->attachmentManager()->lookupResource(id: cb->bufferId()); |
| 765 | if (targetOutput) { |
| 766 | clearBufferInfo.attchmentPoint = targetOutput->point(); |
| 767 | // Note: a job is later performed to find the drawIndex from the buffer |
| 768 | // attachment point using the AttachmentPack |
| 769 | m_specificClearColorBuffers.push_back(x: clearBufferInfo); |
| 770 | } |
| 771 | } |
| 772 | } |
| 773 | } |
| 774 | } |
| 775 | |
| 776 | // If we are there, we know that entity had a GeometryRenderer + Material |
| 777 | EntityRenderCommandData RenderView::buildDrawRenderCommands(const Entity **entities, |
| 778 | int offset, int count) const |
| 779 | { |
| 780 | EntityRenderCommandData commands; |
| 781 | |
| 782 | commands.reserve(size: count); |
| 783 | |
| 784 | for (int i = 0; i < count; ++i) { |
| 785 | const int idx = offset + i; |
| 786 | const Entity *entity = entities[idx]; |
| 787 | GeometryRenderer *geometryRenderer = nullptr; |
| 788 | HGeometryRenderer geometryRendererHandle = entity->componentHandle<GeometryRenderer>(); |
| 789 | |
| 790 | // There is a geometry renderer with geometry |
| 791 | if ((geometryRenderer = m_manager->geometryRendererManager()->data(handle: geometryRendererHandle)) |
| 792 | != nullptr |
| 793 | && geometryRenderer->isEnabled() && !geometryRenderer->geometryId().isNull()) { |
| 794 | |
| 795 | const Qt3DCore::QNodeId materialComponentId = entity->componentUuid<Material>(); |
| 796 | const HMaterial materialHandle = entity->componentHandle<Material>(); |
| 797 | const std::vector<RenderPassParameterData> renderPassData = |
| 798 | m_parameters.value(key: materialComponentId); |
| 799 | |
| 800 | HGeometry geometryHandle = |
| 801 | m_manager->geometryManager()->lookupHandle(id: geometryRenderer->geometryId()); |
| 802 | Geometry *geometry = m_manager->geometryManager()->data(handle: geometryHandle); |
| 803 | |
| 804 | if (geometry == nullptr) |
| 805 | continue; |
| 806 | |
| 807 | // 1 RenderCommand per RenderPass pass on an Entity with a Mesh |
| 808 | for (const RenderPassParameterData &passData : renderPassData) { |
| 809 | // Add the RenderPass Parameters |
| 810 | RenderCommand command = {}; |
| 811 | command.m_geometryRenderer = geometryRendererHandle; |
| 812 | command.m_geometry = geometryHandle; |
| 813 | |
| 814 | command.m_material = materialHandle; |
| 815 | // For RenderPass based states we use the globally set RenderState |
| 816 | // if no renderstates are defined as part of the pass. That means: |
| 817 | // RenderPass { renderStates: [] } will use the states defined by |
| 818 | // StateSet in the FrameGraph |
| 819 | RenderPass *pass = passData.pass; |
| 820 | if (pass->hasRenderStates()) { |
| 821 | command.m_stateSet = RenderStateSetPtr::create(); |
| 822 | addStatesToRenderStateSet(stateSet: command.m_stateSet.data(), stateIds: pass->renderStates(), |
| 823 | manager: m_manager->renderStateManager()); |
| 824 | if (m_stateSet != nullptr) |
| 825 | command.m_stateSet->merge(other: m_stateSet.get()); |
| 826 | command.m_changeCost = |
| 827 | m_renderer->defaultRenderState()->changeCost(previousState: command.m_stateSet.data()); |
| 828 | } |
| 829 | command.m_shaderId = pass->shaderProgram(); |
| 830 | |
| 831 | // At submission time, shaderId is used to retrieve a RHIShader |
| 832 | // No point in continuing if shaderId is null |
| 833 | if (!command.m_shaderId) |
| 834 | continue; |
| 835 | |
| 836 | // We try to resolve the m_rhiShader here. If the shader exist, |
| 837 | // it won't be null and will allow us to full process the |
| 838 | // command over a single frame. Otherwise, the shader will be |
| 839 | // loaded at the next submission time and the command will only |
| 840 | // be fully valid on the next frame. Additionally, that way, if |
| 841 | // a commands keeps being rebuilt, frame after frame, it will |
| 842 | // still be visible on screen as long as the shader exists |
| 843 | RHIShaderManager *rhiShaderManager = m_renderer->rhiResourceManagers()->rhiShaderManager(); |
| 844 | command.m_rhiShader = rhiShaderManager->lookupResource(shaderId: command.m_shaderId); |
| 845 | |
| 846 | { // Scoped to show extent |
| 847 | |
| 848 | // Build of list of Attribute Layout information which |
| 849 | // allows use to compare the layout of geometries against |
| 850 | // one another. |
| 851 | // { name, classification, stride, offset, divisor } |
| 852 | |
| 853 | // Update the draw command with what's going to be needed for the drawing |
| 854 | int primitiveCount = geometryRenderer->vertexCount(); |
| 855 | int estimatedCount = 0; |
| 856 | Attribute *indexAttribute = nullptr; |
| 857 | Attribute *indirectAttribute = nullptr; |
| 858 | |
| 859 | const QList<Qt3DCore::QNodeId> attributeIds = geometry->attributes(); |
| 860 | command.m_attributeInfo.clear(); |
| 861 | command.m_attributeInfo.reserve(n: attributeIds.size()); |
| 862 | for (Qt3DCore::QNodeId attributeId : attributeIds) { |
| 863 | using namespace Qt3DCore; |
| 864 | |
| 865 | Attribute *attribute = |
| 866 | m_manager->attributeManager()->lookupResource(id: attributeId); |
| 867 | switch (attribute->attributeType()) { |
| 868 | case QAttribute::IndexAttribute: |
| 869 | indexAttribute = attribute; |
| 870 | break; |
| 871 | case QAttribute::DrawIndirectAttribute: |
| 872 | indirectAttribute = attribute; |
| 873 | break; |
| 874 | case QAttribute::VertexAttribute: |
| 875 | estimatedCount = std::max(a: int(attribute->count()), b: estimatedCount); |
| 876 | break; |
| 877 | default: |
| 878 | Q_UNREACHABLE(); |
| 879 | break; |
| 880 | } |
| 881 | |
| 882 | if (attribute->attributeType() == QAttribute::VertexAttribute) { |
| 883 | command.m_attributeInfo.push_back(x: { .nameId: attribute->nameId(), |
| 884 | .classification: attribute->divisor() == 0 ? QRhiVertexInputBinding::PerVertex : QRhiVertexInputBinding::PerInstance, |
| 885 | .stride: size_t(attribute->byteStride()), |
| 886 | .offset: size_t(attribute->byteOffset()), |
| 887 | .divisor: size_t(attribute->divisor()) }); |
| 888 | } |
| 889 | } |
| 890 | |
| 891 | // Sort attributes by name so that same attributes added |
| 892 | // in different order would still result in the same geometeyLayout key |
| 893 | std::sort(first: command.m_attributeInfo.begin(), |
| 894 | last: command.m_attributeInfo.end(), |
| 895 | comp: [] (const AttributeInfo &a, const AttributeInfo &b) { |
| 896 | return a.nameId < b.nameId; |
| 897 | }); |
| 898 | |
| 899 | command.m_drawIndexed = (indexAttribute != nullptr); |
| 900 | command.m_drawIndirect = (indirectAttribute != nullptr); |
| 901 | command.indexAttribute = nullptr; |
| 902 | command.indexBuffer = nullptr; |
| 903 | command.pipeline = std::monostate{}; |
| 904 | |
| 905 | // Update the draw command with all the information required for the drawing |
| 906 | if (command.m_drawIndexed) { |
| 907 | command.m_indexAttributeDataType = indexAttribute->vertexBaseType(); |
| 908 | command.m_indexAttributeByteOffset = indexAttribute->byteOffset() |
| 909 | + geometryRenderer->indexBufferByteOffset(); |
| 910 | } |
| 911 | |
| 912 | // Note: we only care about the primitiveCount when using direct draw calls |
| 913 | // For indirect draw calls it is assumed the buffer was properly set already |
| 914 | if (command.m_drawIndirect) { |
| 915 | command.m_indirectAttributeByteOffset = indirectAttribute->byteOffset(); |
| 916 | command.m_indirectDrawBuffer = m_manager->bufferManager()->lookupHandle( |
| 917 | id: indirectAttribute->bufferId()); |
| 918 | } else { |
| 919 | // Use the count specified by the GeometryRender |
| 920 | // If not specify use the indexAttribute count if present |
| 921 | // Otherwise tries to use the count from the attribute with the highest |
| 922 | // count |
| 923 | if (primitiveCount == 0) { |
| 924 | if (indexAttribute) |
| 925 | primitiveCount = indexAttribute->count(); |
| 926 | else |
| 927 | primitiveCount = estimatedCount; |
| 928 | } |
| 929 | } |
| 930 | |
| 931 | command.m_primitiveCount = primitiveCount; |
| 932 | command.m_primitiveType = geometryRenderer->primitiveType(); |
| 933 | command.m_primitiveRestartEnabled = geometryRenderer->primitiveRestartEnabled(); |
| 934 | command.m_restartIndexValue = geometryRenderer->restartIndexValue(); |
| 935 | command.m_firstInstance = geometryRenderer->firstInstance(); |
| 936 | command.m_instanceCount = geometryRenderer->instanceCount(); |
| 937 | command.m_firstVertex = geometryRenderer->firstVertex(); |
| 938 | command.m_indexOffset = geometryRenderer->indexOffset(); |
| 939 | command.m_verticesPerPatch = geometryRenderer->verticesPerPatch(); |
| 940 | } // scope |
| 941 | |
| 942 | commands.push_back(e: entity, c: std::move(command), p: passData); |
| 943 | } |
| 944 | } |
| 945 | } |
| 946 | |
| 947 | return commands; |
| 948 | } |
| 949 | |
| 950 | EntityRenderCommandData RenderView::buildComputeRenderCommands(const Entity **entities, |
| 951 | int offset, int count) const |
| 952 | { |
| 953 | // If the RenderView contains only a ComputeDispatch then it cares about |
| 954 | // A ComputeDispatch is also implicitely a NoDraw operation |
| 955 | // enabled flag |
| 956 | // layer component |
| 957 | // material/effect/technique/parameters/filters/ |
| 958 | EntityRenderCommandData commands; |
| 959 | |
| 960 | commands.reserve(size: count); |
| 961 | |
| 962 | for (int i = 0; i < count; ++i) { |
| 963 | const int idx = offset + i; |
| 964 | const Entity *entity = entities[idx]; |
| 965 | ComputeCommand *computeJob = nullptr; |
| 966 | HComputeCommand computeCommandHandle = entity->componentHandle<ComputeCommand>(); |
| 967 | if ((computeJob = nodeManagers()->computeJobManager()->data(handle: computeCommandHandle)) |
| 968 | != nullptr |
| 969 | && computeJob->isEnabled()) { |
| 970 | |
| 971 | const Qt3DCore::QNodeId materialComponentId = entity->componentUuid<Material>(); |
| 972 | const std::vector<RenderPassParameterData> &renderPassData = |
| 973 | m_parameters.value(key: materialComponentId); |
| 974 | |
| 975 | // 1 RenderCommand per RenderPass pass on an Entity with a Mesh |
| 976 | for (const RenderPassParameterData &passData : renderPassData) { |
| 977 | // Add the RenderPass Parameters |
| 978 | RenderCommand command = {}; |
| 979 | RenderPass *pass = passData.pass; |
| 980 | |
| 981 | if (pass->hasRenderStates()) { |
| 982 | command.m_stateSet = RenderStateSetPtr::create(); |
| 983 | addStatesToRenderStateSet(stateSet: command.m_stateSet.data(), stateIds: pass->renderStates(), |
| 984 | manager: m_manager->renderStateManager()); |
| 985 | |
| 986 | // Merge per pass stateset with global stateset |
| 987 | // so that the local stateset only overrides |
| 988 | if (m_stateSet != nullptr) |
| 989 | command.m_stateSet->merge(other: m_stateSet.get()); |
| 990 | command.m_changeCost = |
| 991 | m_renderer->defaultRenderState()->changeCost(previousState: command.m_stateSet.data()); |
| 992 | } |
| 993 | command.m_shaderId = pass->shaderProgram(); |
| 994 | |
| 995 | // At submission time, shaderId is used to retrieve a RHIShader |
| 996 | // No point in continuing if shaderId is null |
| 997 | if (!command.m_shaderId) |
| 998 | continue; |
| 999 | |
| 1000 | // We try to resolve the m_rhiShader here. If the shader exist, |
| 1001 | // it won't be null and will allow us to full process the |
| 1002 | // command over a single frame. Otherwise, the shader will be |
| 1003 | // loaded at the next submission time and the command will only |
| 1004 | // be fully valid on the next frame. Additionally, that way, if |
| 1005 | // a commands keeps being rebuilt, frame after frame, it will |
| 1006 | // still be visible on screen as long as the shader exists |
| 1007 | RHIShaderManager *rhiShaderManager = m_renderer->rhiResourceManagers()->rhiShaderManager(); |
| 1008 | command.m_rhiShader = rhiShaderManager->lookupResource(shaderId: command.m_shaderId); |
| 1009 | |
| 1010 | command.m_computeCommand = computeCommandHandle; |
| 1011 | command.m_type = RenderCommand::Compute; |
| 1012 | command.m_workGroups[0] = std::max(a: m_workGroups[0], b: computeJob->x()); |
| 1013 | command.m_workGroups[1] = std::max(a: m_workGroups[1], b: computeJob->y()); |
| 1014 | command.m_workGroups[2] = std::max(a: m_workGroups[2], b: computeJob->z()); |
| 1015 | |
| 1016 | commands.push_back(e: entity, c: std::move(command), p: passData); |
| 1017 | } |
| 1018 | } |
| 1019 | } |
| 1020 | |
| 1021 | return commands; |
| 1022 | } |
| 1023 | |
| 1024 | namespace |
| 1025 | { |
| 1026 | void copyNormalMatrix(float(&destination)[12], const float* src) noexcept { |
| 1027 | destination[0] = src[0 * 3 + 0]; |
| 1028 | destination[1] = src[0 * 3 + 1]; |
| 1029 | destination[2] = src[0 * 3 + 2]; |
| 1030 | |
| 1031 | destination[4] = src[1 * 3 + 0]; |
| 1032 | destination[5] = src[1 * 3 + 1]; |
| 1033 | destination[6] = src[1 * 3 + 2]; |
| 1034 | |
| 1035 | destination[8] = src[2 * 3 + 0]; |
| 1036 | destination[9] = src[2 * 3 + 1]; |
| 1037 | destination[10] = src[2 * 3 + 2]; |
| 1038 | } |
| 1039 | } |
| 1040 | |
| 1041 | void RenderView::updateRenderCommand(const EntityRenderCommandDataSubView &subView) |
| 1042 | { |
| 1043 | // Update RenderViewUBO (Qt3D standard uniforms) |
| 1044 | const bool yIsUp = m_renderer->submissionContext()->rhi()->isYUpInNDC(); |
| 1045 | |
| 1046 | const Matrix4x4 clipCorrectionMatrix = Matrix4x4(m_renderer->submissionContext()->rhi()->clipSpaceCorrMatrix()); |
| 1047 | const Matrix4x4 unCorrectedProjectionMatrix = getProjectionMatrix(lens: m_renderCameraLens); |
| 1048 | const Matrix4x4 projectionMatrix = clipCorrectionMatrix * unCorrectedProjectionMatrix; |
| 1049 | const Matrix4x4 inverseViewMatrix = m_viewMatrix.inverted(); |
| 1050 | const Matrix4x4 inversedProjectionMatrix = projectionMatrix.inverted(); |
| 1051 | const Matrix4x4 viewProjectionMatrix = (projectionMatrix * m_viewMatrix); |
| 1052 | const Matrix4x4 inversedViewProjectionMatrix = viewProjectionMatrix.inverted(); |
| 1053 | { |
| 1054 | memcpy(dest: &m_renderViewUBO.viewMatrix, src: &m_viewMatrix, n: sizeof(Matrix4x4)); |
| 1055 | memcpy(dest: &m_renderViewUBO.projectionMatrix, src: &projectionMatrix, n: sizeof(Matrix4x4)); |
| 1056 | memcpy(dest: &m_renderViewUBO.clipCorrectionMatrix, src: &clipCorrectionMatrix, n: sizeof(Matrix4x4)); |
| 1057 | memcpy(dest: &m_renderViewUBO.uncorrectedProjectionMatrix, src: &unCorrectedProjectionMatrix, n: sizeof(Matrix4x4)); |
| 1058 | memcpy(dest: &m_renderViewUBO.viewProjectionMatrix, src: &viewProjectionMatrix, n: sizeof(Matrix4x4)); |
| 1059 | memcpy(dest: &m_renderViewUBO.inverseViewMatrix, src: &inverseViewMatrix, n: sizeof(Matrix4x4)); |
| 1060 | memcpy(dest: &m_renderViewUBO.inverseProjectionMatrix, src: &inversedProjectionMatrix, |
| 1061 | n: sizeof(Matrix4x4)); |
| 1062 | memcpy(dest: &m_renderViewUBO.inverseViewProjectionMatrix, src: &inversedViewProjectionMatrix, |
| 1063 | n: sizeof(Matrix4x4)); |
| 1064 | { |
| 1065 | QMatrix4x4 viewportMatrix; |
| 1066 | // TO DO: Implement on Matrix4x4 |
| 1067 | viewportMatrix.viewport(rect: resolveViewport(fractionalViewport: m_viewport, surfaceSize: m_surfaceSize)); |
| 1068 | Matrix4x4 vpMatrix(viewportMatrix); |
| 1069 | Matrix4x4 invVpMatrix = vpMatrix.inverted(); |
| 1070 | memcpy(dest: &m_renderViewUBO.viewportMatrix, src: &vpMatrix, n: sizeof(Matrix4x4)); |
| 1071 | memcpy(dest: &m_renderViewUBO.inverseViewportMatrix, src: &invVpMatrix, n: sizeof(Matrix4x4)); |
| 1072 | } |
| 1073 | memcpy(dest: &m_renderViewUBO.textureTransformMatrix, src: m_renderer->textureTransform(), |
| 1074 | n: sizeof(float) * 4); |
| 1075 | |
| 1076 | memcpy(dest: &m_renderViewUBO.eyePosition, src: &m_eyePos, n: sizeof(float) * 3); |
| 1077 | const float ratio = |
| 1078 | float(m_surfaceSize.width()) / std::max(a: 1.f, b: float(m_surfaceSize.height())); |
| 1079 | memcpy(dest: &m_renderViewUBO.aspectRatio, src: &ratio, n: sizeof(float)); |
| 1080 | memcpy(dest: &m_renderViewUBO.gamma, src: &m_gamma, n: sizeof(float)); |
| 1081 | const float exposure = |
| 1082 | m_renderCameraLens ? m_renderCameraLens->exposure() : 0.0f; |
| 1083 | memcpy(dest: &m_renderViewUBO.exposure, src: &exposure, n: sizeof(float)); |
| 1084 | const float timeValue = float(m_renderer->time() / 1000000000.0f); |
| 1085 | memcpy(dest: &m_renderViewUBO.time, src: &timeValue, n: sizeof(float)); |
| 1086 | |
| 1087 | const float yUpNDC = yIsUp ? 1.0f : 0.0f; |
| 1088 | const float yUpFBO = m_renderer->submissionContext()->rhi()->isYUpInFramebuffer() ? 1.0f : 0.0f; |
| 1089 | memcpy(dest: &m_renderViewUBO.yUpInNDC, src: &yUpNDC, n: sizeof(float)); |
| 1090 | memcpy(dest: &m_renderViewUBO.yUpInFBO, src: &yUpFBO, n: sizeof(float)); |
| 1091 | } |
| 1092 | |
| 1093 | subView.forEach(func: [&] (const Entity *entity, |
| 1094 | const RenderPassParameterData &passData, |
| 1095 | RenderCommand &command) { |
| 1096 | |
| 1097 | // Pick which lights to take in to account. |
| 1098 | // For now decide based on the distance by taking the MAX_LIGHTS closest lights. |
| 1099 | // Replace with more sophisticated mechanisms later. |
| 1100 | // Copy vector so that we can sort it concurrently and we only want to sort the one for the |
| 1101 | // current command |
| 1102 | std::vector<LightSource> lightSources; |
| 1103 | EnvironmentLight *environmentLight = nullptr; |
| 1104 | |
| 1105 | if (command.m_type == RenderCommand::Draw) { |
| 1106 | // Project the camera-to-object-center vector onto the camera |
| 1107 | // view vector. This gives a depth value suitable as the key |
| 1108 | // for BackToFront sorting. |
| 1109 | command.m_depth = Vector3D::dotProduct( |
| 1110 | a: entity->worldBoundingVolume()->center() - m_eyePos, b: m_eyeViewDir); |
| 1111 | |
| 1112 | auto geometryRenderer = m_manager->geometryRendererManager()->data(handle: command.m_geometryRenderer); |
| 1113 | if (geometryRenderer && !qFuzzyCompare(p1: geometryRenderer->sortIndex(), p2: -1.f)) |
| 1114 | command.m_depth = geometryRenderer->sortIndex(); |
| 1115 | |
| 1116 | environmentLight = m_environmentLight; |
| 1117 | lightSources = m_lightSources; |
| 1118 | |
| 1119 | if (lightSources.size() > 1) { |
| 1120 | const Vector3D entityCenter = entity->worldBoundingVolume()->center(); |
| 1121 | std::sort(first: lightSources.begin(), last: lightSources.end(), |
| 1122 | comp: [&](const LightSource &a, const LightSource &b) { |
| 1123 | const float distA = entityCenter.distanceToPoint( |
| 1124 | point: a.entity->worldBoundingVolume()->center()); |
| 1125 | const float distB = entityCenter.distanceToPoint( |
| 1126 | point: b.entity->worldBoundingVolume()->center()); |
| 1127 | return distA < distB; |
| 1128 | }); |
| 1129 | m_lightSources = {lightSources.begin(), lightSources.begin() + std::min(a: lightSources.size(), b: size_t(MAX_LIGHTS)) }; |
| 1130 | } |
| 1131 | } else { // Compute |
| 1132 | // Note: if frameCount has reached 0 in the previous frame, isEnabled |
| 1133 | // would be false |
| 1134 | ComputeCommand *computeJob = |
| 1135 | m_manager->computeJobManager()->data(handle: command.m_computeCommand); |
| 1136 | if (computeJob->runType() == QComputeCommand::Manual) |
| 1137 | computeJob->updateFrameCount(); |
| 1138 | } |
| 1139 | |
| 1140 | ParameterInfoList globalParameters = passData.parameterInfo; |
| 1141 | // setShaderAndUniforms can initialize a localData |
| 1142 | // make sure this is cleared before we leave this function |
| 1143 | |
| 1144 | setShaderAndUniforms(command: &command, parameters&: globalParameters, entity, activeLightSources: lightSources, environmentLight); |
| 1145 | |
| 1146 | // Update CommandUBO (Qt3D standard uniforms) |
| 1147 | const Matrix4x4 worldTransform = *(entity->worldTransform()); |
| 1148 | const Matrix4x4 inverseWorldTransform = worldTransform.inverted(); |
| 1149 | const QMatrix3x3 modelNormalMatrix = convertToQMatrix4x4(v: worldTransform).normalMatrix(); |
| 1150 | const Matrix4x4 modelViewMatrix = m_viewMatrix * worldTransform; |
| 1151 | const QMatrix3x3 modelViewNormalMatrix = convertToQMatrix4x4(v: modelViewMatrix).normalMatrix(); |
| 1152 | const Matrix4x4 inverseModelViewMatrix = modelViewMatrix.inverted(); |
| 1153 | const Matrix4x4 mvp = projectionMatrix * modelViewMatrix; |
| 1154 | const Matrix4x4 inverseModelViewProjection = mvp.inverted(); |
| 1155 | { |
| 1156 | memcpy(dest: &command.m_commandUBO.modelMatrix, src: &worldTransform, n: sizeof(Matrix4x4)); |
| 1157 | memcpy(dest: &command.m_commandUBO.inverseModelMatrix, src: &inverseWorldTransform, |
| 1158 | n: sizeof(Matrix4x4)); |
| 1159 | memcpy(dest: &command.m_commandUBO.modelViewMatrix, src: &modelViewMatrix, n: sizeof(Matrix4x4)); |
| 1160 | copyNormalMatrix(destination&: command.m_commandUBO.modelNormalMatrix, src: modelNormalMatrix.constData()); |
| 1161 | memcpy(dest: &command.m_commandUBO.inverseModelViewMatrix, src: &inverseModelViewMatrix, |
| 1162 | n: sizeof(Matrix4x4)); |
| 1163 | memcpy(dest: &command.m_commandUBO.mvp, src: &mvp, n: sizeof(Matrix4x4)); |
| 1164 | memcpy(dest: &command.m_commandUBO.inverseModelViewProjectionMatrix, |
| 1165 | src: &inverseModelViewProjection, n: sizeof(Matrix4x4)); |
| 1166 | copyNormalMatrix(destination&: command.m_commandUBO.modelViewNormalMatrix, src: modelViewNormalMatrix.constData()); |
| 1167 | |
| 1168 | const Armature *armature = entity->renderComponent<Armature>(); |
| 1169 | if (armature) { |
| 1170 | const UniformValue &skinningPalette = armature->skinningPaletteUniform(); |
| 1171 | memcpy(dest: &command.m_commandUBO.skinningPalette, src: skinningPalette.constData<float>(), |
| 1172 | n: qMin<size_t>(a: skinningPalette.byteSize(), b: 100 * 16 * sizeof(float))); |
| 1173 | } |
| 1174 | } |
| 1175 | }); |
| 1176 | } |
| 1177 | |
| 1178 | void RenderView::updateMatrices() |
| 1179 | { |
| 1180 | if (m_renderCameraNode && m_renderCameraLens |
| 1181 | && m_renderCameraLens->isEnabled()) { |
| 1182 | auto transform = m_renderCameraNode->renderComponent<Transform>(); |
| 1183 | if (m_renderCameraNode->isParentLessTransform() && transform && transform->hasViewMatrix()) { |
| 1184 | // optimization: if the entity is a QCamera and it doesn't have a parent with a transform component, |
| 1185 | // then we use the frontend version of the viewMatrix to avoid extra calculations that may introduce |
| 1186 | // rounding errors |
| 1187 | setViewMatrix(transform->viewMatrix()); |
| 1188 | } else { |
| 1189 | const Matrix4x4 cameraWorld = *(m_renderCameraNode->worldTransform()); |
| 1190 | setViewMatrix(m_renderCameraLens->viewMatrix(worldTransform: cameraWorld)); |
| 1191 | } |
| 1192 | |
| 1193 | setViewProjectionMatrix(m_renderCameraLens->projection() * viewMatrix()); |
| 1194 | // To get the eyePosition of the camera, we need to use the inverse of the |
| 1195 | // camera's worldTransform matrix. |
| 1196 | const Matrix4x4 inverseWorldTransform = viewMatrix().inverted(); |
| 1197 | const Vector3D eyePosition(inverseWorldTransform.column(index: 3)); |
| 1198 | setEyePosition(eyePosition); |
| 1199 | |
| 1200 | // Get the viewing direction of the camera. Use the normal matrix to |
| 1201 | // ensure non-uniform scale works too. |
| 1202 | const QMatrix3x3 normalMat = convertToQMatrix4x4(v: m_viewMatrix).normalMatrix(); |
| 1203 | // dir = normalize(QVector3D(0, 0, -1) * normalMat) |
| 1204 | setEyeViewDirection( |
| 1205 | Vector3D(-normalMat(2, 0), -normalMat(2, 1), -normalMat(2, 2)).normalized()); |
| 1206 | } |
| 1207 | } |
| 1208 | |
| 1209 | void RenderView::setUniformValue(ShaderParameterPack &uniformPack, int nameId, |
| 1210 | const UniformValue &value) const |
| 1211 | { |
| 1212 | // At this point a uniform value can only be a scalar type |
| 1213 | // or a Qt3DCore::QNodeId corresponding to a Texture or Image |
| 1214 | // ShaderData/Buffers would be handled as UBO/SSBO and would therefore |
| 1215 | // not be in the default uniform block |
| 1216 | if (value.valueType() == UniformValue::NodeId) { |
| 1217 | const Qt3DCore::QNodeId *nodeIds = value.constData<Qt3DCore::QNodeId>(); |
| 1218 | |
| 1219 | const int uniformArraySize = value.byteSize() / sizeof(Qt3DCore::QNodeId); |
| 1220 | UniformValue::ValueType resourceType = UniformValue::TextureValue; |
| 1221 | |
| 1222 | for (int i = 0; i < uniformArraySize; ++i) { |
| 1223 | const Qt3DCore::QNodeId resourceId = nodeIds[i]; |
| 1224 | |
| 1225 | const Texture *tex = m_manager->textureManager()->lookupResource(id: resourceId); |
| 1226 | if (tex != nullptr) { |
| 1227 | uniformPack.setTexture(glslNameId: nameId, uniformArrayIndex: i, id: resourceId); |
| 1228 | } else { |
| 1229 | const ShaderImage *img = |
| 1230 | m_manager->shaderImageManager()->lookupResource(id: resourceId); |
| 1231 | if (img != nullptr) { |
| 1232 | resourceType = UniformValue::ShaderImageValue; |
| 1233 | uniformPack.setImage(glslNameId: nameId, uniformArrayIndex: i, id: resourceId); |
| 1234 | } |
| 1235 | } |
| 1236 | } |
| 1237 | |
| 1238 | // This uniform will be overridden in SubmissionContext::setParameters |
| 1239 | // and -1 values will be replaced by valid Texture or Image units |
| 1240 | UniformValue uniformValue(uniformArraySize * sizeof(int), resourceType); |
| 1241 | std::fill(first: uniformValue.data<int>(), last: uniformValue.data<int>() + uniformArraySize, value: -1); |
| 1242 | uniformPack.setUniform(glslNameId: nameId, val: uniformValue); |
| 1243 | } else { |
| 1244 | uniformPack.setUniform(glslNameId: nameId, val: value); |
| 1245 | } |
| 1246 | } |
| 1247 | |
| 1248 | void RenderView::setUniformBlockValue(ShaderParameterPack &uniformPack, const RHIShader *shader, |
| 1249 | const ShaderUniformBlock &block, |
| 1250 | const UniformValue &value) const |
| 1251 | { |
| 1252 | Q_UNUSED(shader); |
| 1253 | |
| 1254 | if (value.valueType() == UniformValue::NodeId) { |
| 1255 | |
| 1256 | Buffer *buffer = nullptr; |
| 1257 | if ((buffer = m_manager->bufferManager()->lookupResource( |
| 1258 | id: *value.constData<Qt3DCore::QNodeId>())) |
| 1259 | != nullptr) { |
| 1260 | BlockToUBO uniformBlockUBO; |
| 1261 | uniformBlockUBO.m_blockIndex = block.m_index; |
| 1262 | uniformBlockUBO.m_bindingIndex = block.m_binding; |
| 1263 | uniformBlockUBO.m_bufferID = buffer->peerId(); |
| 1264 | uniformBlockUBO.m_needsUpdate = false; |
| 1265 | uniformPack.setUniformBuffer(std::move(uniformBlockUBO)); |
| 1266 | // Buffer update to GL buffer will be done at render time |
| 1267 | } |
| 1268 | } |
| 1269 | } |
| 1270 | |
| 1271 | void RenderView::setShaderStorageValue(ShaderParameterPack &uniformPack, const RHIShader *shader, |
| 1272 | const ShaderStorageBlock &block, |
| 1273 | const UniformValue &value) const |
| 1274 | { |
| 1275 | Q_UNUSED(shader); |
| 1276 | if (value.valueType() == UniformValue::NodeId) { |
| 1277 | Buffer *buffer = nullptr; |
| 1278 | if ((buffer = m_manager->bufferManager()->lookupResource( |
| 1279 | id: *value.constData<Qt3DCore::QNodeId>())) |
| 1280 | != nullptr) { |
| 1281 | BlockToSSBO shaderStorageBlock; |
| 1282 | shaderStorageBlock.m_blockIndex = block.m_index; |
| 1283 | shaderStorageBlock.m_bufferID = buffer->peerId(); |
| 1284 | shaderStorageBlock.m_bindingIndex = block.m_binding; |
| 1285 | uniformPack.setShaderStorageBuffer(shaderStorageBlock); |
| 1286 | // Buffer update to RHI buffer will be done at render time |
| 1287 | } |
| 1288 | } |
| 1289 | } |
| 1290 | |
| 1291 | void RenderView::setShaderDataValue(ShaderParameterPack &uniformPack, |
| 1292 | const ShaderUniformBlock &block, |
| 1293 | const Qt3DCore::QNodeId &shaderDataId) const |
| 1294 | { |
| 1295 | if (block.m_binding >= 0) { |
| 1296 | ShaderDataForUBO shaderDataForUBO; |
| 1297 | shaderDataForUBO.m_shaderDataID = shaderDataId; |
| 1298 | shaderDataForUBO.m_bindingIndex = block.m_binding; |
| 1299 | uniformPack.setShaderDataForUBO(shaderDataForUBO); |
| 1300 | } |
| 1301 | } |
| 1302 | |
| 1303 | void RenderView::setDefaultUniformBlockShaderDataValue(ShaderParameterPack &uniformPack, |
| 1304 | const RHIShader *shader, |
| 1305 | ShaderData *shaderData, |
| 1306 | const QString &structName) const |
| 1307 | { |
| 1308 | UniformBlockValueBuilder builder(shader->uniformsNamesIds(), |
| 1309 | m_manager->shaderDataManager(), |
| 1310 | m_manager->textureManager(), |
| 1311 | m_viewMatrix); |
| 1312 | |
| 1313 | // Build name-value map for the block |
| 1314 | builder.buildActiveUniformNameValueMapStructHelper(rShaderData: shaderData, blockName: structName); |
| 1315 | // Set uniform values for each entrie of the block name-value map |
| 1316 | QHash<int, QVariant>::const_iterator activeValuesIt = |
| 1317 | builder.activeUniformNamesToValue.constBegin(); |
| 1318 | const QHash<int, QVariant>::const_iterator activeValuesEnd = |
| 1319 | builder.activeUniformNamesToValue.constEnd(); |
| 1320 | |
| 1321 | // TO DO: Make the ShaderData store UniformValue |
| 1322 | while (activeValuesIt != activeValuesEnd) { |
| 1323 | setUniformValue(uniformPack, nameId: activeValuesIt.key(), |
| 1324 | value: UniformValue::fromVariant(variant: activeValuesIt.value())); |
| 1325 | ++activeValuesIt; |
| 1326 | } |
| 1327 | } |
| 1328 | |
| 1329 | void RenderView::applyParameter(const Parameter *param, RenderCommand *command, |
| 1330 | const RHIShader *shader) const noexcept |
| 1331 | { |
| 1332 | const int nameId = param->nameId(); |
| 1333 | const UniformValue &uniformValue = param->uniformValue(); |
| 1334 | const RHIShader::ParameterKind parameterKind = shader->categorizeVariable(nameId); |
| 1335 | |
| 1336 | switch (parameterKind) { |
| 1337 | case RHIShader::Uniform: { |
| 1338 | setUniformValue(uniformPack&: command->m_parameterPack, nameId, value: uniformValue); |
| 1339 | break; |
| 1340 | } |
| 1341 | case RHIShader::UBO: { |
| 1342 | setUniformBlockValue(uniformPack&: command->m_parameterPack, shader, |
| 1343 | block: shader->uniformBlockForBlockNameId(blockIndex: nameId), value: uniformValue); |
| 1344 | break; |
| 1345 | } |
| 1346 | case RHIShader::SSBO: { |
| 1347 | setShaderStorageValue(uniformPack&: command->m_parameterPack, shader, |
| 1348 | block: shader->storageBlockForBlockNameId(blockNameId: nameId), value: uniformValue); |
| 1349 | break; |
| 1350 | } |
| 1351 | case RHIShader::Struct: { |
| 1352 | // Structs will have to be converted to UBOs later on |
| 1353 | ShaderData *shaderData = nullptr; |
| 1354 | |
| 1355 | if (uniformValue.valueType() == UniformValue::NodeId |
| 1356 | && (shaderData = m_manager->shaderDataManager()->lookupResource( |
| 1357 | id: *uniformValue.constData<Qt3DCore::QNodeId>())) |
| 1358 | != nullptr) { |
| 1359 | // We need to find the Block associated with the uniform name |
| 1360 | setShaderDataValue(uniformPack&: command->m_parameterPack, |
| 1361 | block: shader->uniformBlockForInstanceNameId(instanceNameId: nameId), shaderDataId: shaderData->peerId()); |
| 1362 | } |
| 1363 | break; |
| 1364 | } |
| 1365 | } |
| 1366 | } |
| 1367 | |
| 1368 | void RenderView::setShaderAndUniforms(RenderCommand *command, ParameterInfoList ¶meters, |
| 1369 | const Entity *entity, |
| 1370 | const std::vector<LightSource> &activeLightSources, |
| 1371 | EnvironmentLight *environmentLight) const |
| 1372 | { |
| 1373 | Q_UNUSED(entity); |
| 1374 | |
| 1375 | // The VAO Handle is set directly in the renderer thread so as to avoid having to use a mutex |
| 1376 | // here Set shader, technique, and effect by basically doing : |
| 1377 | // ShaderProgramManager[MaterialManager[frontentEntity->id()]->Effect->Techniques[TechniqueFilter->name]->RenderPasses[RenderPassFilter->name]]; |
| 1378 | // The Renderer knows that if one of those is null, a default material / technique / effect as |
| 1379 | // to be used |
| 1380 | |
| 1381 | // Find all RenderPasses (in order) matching values set in the RenderPassFilter |
| 1382 | // Get list of parameters for the Material, Effect, and Technique |
| 1383 | // For each ParameterBinder in the RenderPass -> create a QUniformPack |
| 1384 | // Once that works, improve that to try and minimize QUniformPack updates |
| 1385 | |
| 1386 | RHIShader *shader = command->m_rhiShader; |
| 1387 | if (shader == nullptr || !shader->isLoaded()) |
| 1388 | return; |
| 1389 | |
| 1390 | // If we have already build the uniforms previously, we should |
| 1391 | // only update values of uniforms that have changed |
| 1392 | // If parameters add been added/removed, the command would have been rebuild |
| 1393 | // and the parameter pack would be empty |
| 1394 | const bool updateUniformsOnly = !command->m_parameterPack.submissionUniformIndices().empty(); |
| 1395 | |
| 1396 | if (!updateUniformsOnly) { |
| 1397 | // Builds the QUniformPack, sets shader standard uniforms and store attributes name / glname |
| 1398 | // bindings If a parameter is defined and not found in the bindings it is assumed to be a |
| 1399 | // binding of Uniform type with the glsl name equals to the parameter name |
| 1400 | |
| 1401 | // Set fragData Name and index |
| 1402 | // Later on we might want to relink the shader if attachments have changed |
| 1403 | // But for now we set them once and for all |
| 1404 | if (!m_renderTarget.isNull() && !shader->isLoaded()) { |
| 1405 | QHash<QString, int> fragOutputs; |
| 1406 | const auto atts = m_attachmentPack.attachments(); |
| 1407 | for (const Attachment &att : atts) { |
| 1408 | if (att.m_point <= QRenderTargetOutput::Color15) |
| 1409 | fragOutputs.insert(key: att.m_name, value: att.m_point); |
| 1410 | } |
| 1411 | // Set frag outputs in the shaders if hash not empty |
| 1412 | if (!fragOutputs.isEmpty()) |
| 1413 | shader->setFragOutputs(fragOutputs); |
| 1414 | } |
| 1415 | // Set default attributes |
| 1416 | command->m_activeAttributes = shader->attributeNamesIds(); |
| 1417 | |
| 1418 | // At this point we know whether the command is a valid draw command or not |
| 1419 | // We still need to process the uniforms as the command could be a compute command |
| 1420 | command->m_isValid = (!command->m_activeAttributes.empty()) || (command->m_type == RenderCommand::CommandType::Compute); |
| 1421 | } |
| 1422 | |
| 1423 | if (shader->hasActiveVariables()) { |
| 1424 | // Unlike the GL engine, the standard uniforms are set a bit before this function, |
| 1425 | // in RenderView::updateRenderCommand |
| 1426 | |
| 1427 | // Parameters remaining could be |
| 1428 | // -> uniform scalar / vector |
| 1429 | // -> uniform struct / arrays |
| 1430 | // -> uniform block / array (4.3) |
| 1431 | // -> ssbo block / array (4.3) |
| 1432 | |
| 1433 | ParameterInfoList::const_iterator it = parameters.cbegin(); |
| 1434 | const ParameterInfoList::const_iterator parametersEnd = parameters.cend(); |
| 1435 | |
| 1436 | while (it != parametersEnd) { |
| 1437 | const Parameter *param = m_manager->data<Parameter, ParameterManager>(handle: it->handle); |
| 1438 | applyParameter(param, command, shader); |
| 1439 | ++it; |
| 1440 | } |
| 1441 | |
| 1442 | // Lights |
| 1443 | int lightIdx = 0; |
| 1444 | for (const LightSource &lightSource : activeLightSources) { |
| 1445 | if (lightIdx == MAX_LIGHTS) |
| 1446 | break; |
| 1447 | const Entity *lightEntity = lightSource.entity; |
| 1448 | const Matrix4x4 lightWorldTransform = *(lightEntity->worldTransform()); |
| 1449 | const Vector3D worldPos = lightWorldTransform.map(point: Vector3D(0.0f, 0.0f, 0.0f)); |
| 1450 | for (Light *light : lightSource.lights) { |
| 1451 | if (!light->isEnabled()) |
| 1452 | continue; |
| 1453 | |
| 1454 | ShaderData *shaderData = |
| 1455 | m_manager->shaderDataManager()->lookupResource(id: light->shaderData()); |
| 1456 | if (!shaderData) |
| 1457 | continue; |
| 1458 | |
| 1459 | if (lightIdx == MAX_LIGHTS) |
| 1460 | break; |
| 1461 | |
| 1462 | // Note: implicit conversion of values to UniformValue |
| 1463 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_POSITION_NAMES[lightIdx], |
| 1464 | value: worldPos); |
| 1465 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_TYPE_NAMES[lightIdx], |
| 1466 | value: int(QAbstractLight::PointLight)); |
| 1467 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_COLOR_NAMES[lightIdx], |
| 1468 | value: Vector3D(1.0f, 1.0f, 1.0f)); |
| 1469 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_INTENSITY_NAMES[lightIdx], |
| 1470 | value: 0.5f); |
| 1471 | // Unrolled |
| 1472 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_POSITION_UNROLL_NAMES[lightIdx], |
| 1473 | value: worldPos); |
| 1474 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_TYPE_UNROLL_NAMES[lightIdx], |
| 1475 | value: int(QAbstractLight::PointLight)); |
| 1476 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_COLOR_UNROLL_NAMES[lightIdx], |
| 1477 | value: Vector3D(1.0f, 1.0f, 1.0f)); |
| 1478 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_INTENSITY_UNROLL_NAMES[lightIdx], value: 0.5f); |
| 1479 | |
| 1480 | // There is no risk in doing that even if multithreaded |
| 1481 | // since we are sure that a shaderData is unique for a given light |
| 1482 | // and won't ever be referenced as a Component either |
| 1483 | const Matrix4x4 *worldTransform = lightEntity->worldTransform(); |
| 1484 | if (worldTransform) |
| 1485 | shaderData->updateWorldTransform(worldMatrix: *worldTransform); |
| 1486 | |
| 1487 | setDefaultUniformBlockShaderDataValue(uniformPack&: command->m_parameterPack, shader, |
| 1488 | shaderData, structName: LIGHT_STRUCT_NAMES[lightIdx]); |
| 1489 | ++lightIdx; |
| 1490 | } |
| 1491 | } |
| 1492 | |
| 1493 | if (shader->hasUniform(nameId: LIGHT_COUNT_NAME_ID)) |
| 1494 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_COUNT_NAME_ID, |
| 1495 | value: UniformValue(qMax(a: (environmentLight ? 0 : 1), b: lightIdx))); |
| 1496 | |
| 1497 | // If no active light sources and no environment light, add a default light |
| 1498 | if (activeLightSources.empty() && !environmentLight) { |
| 1499 | // Note: implicit conversion of values to UniformValue |
| 1500 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_POSITION_NAMES[0], |
| 1501 | value: Vector3D(10.0f, 10.0f, 0.0f)); |
| 1502 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_TYPE_NAMES[0], |
| 1503 | value: int(QAbstractLight::PointLight)); |
| 1504 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_COLOR_NAMES[0], |
| 1505 | value: Vector3D(1.0f, 1.0f, 1.0f)); |
| 1506 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_INTENSITY_NAMES[0], value: 0.5f); |
| 1507 | // Unrolled |
| 1508 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_POSITION_UNROLL_NAMES[0], |
| 1509 | value: Vector3D(10.0f, 10.0f, 0.0f)); |
| 1510 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_TYPE_UNROLL_NAMES[0], |
| 1511 | value: int(QAbstractLight::PointLight)); |
| 1512 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_COLOR_UNROLL_NAMES[0], |
| 1513 | value: Vector3D(1.0f, 1.0f, 1.0f)); |
| 1514 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: LIGHT_INTENSITY_UNROLL_NAMES[0], value: 0.5f); |
| 1515 | } |
| 1516 | |
| 1517 | // Environment Light |
| 1518 | int envLightCount = 0; |
| 1519 | if (environmentLight && environmentLight->isEnabled()) { |
| 1520 | static const int irradianceStructId = |
| 1521 | StringToInt::lookupId(str: QLatin1String("envLight_irradiance" )); |
| 1522 | static const int specularStructId = |
| 1523 | StringToInt::lookupId(str: QLatin1String("envLight_specular" )); |
| 1524 | static const int irradianceId = |
| 1525 | StringToInt::lookupId(str: QLatin1String("envLightIrradiance" )); |
| 1526 | static const int specularId = |
| 1527 | StringToInt::lookupId(str: QLatin1String("envLightSpecular" )); |
| 1528 | ShaderData *shaderData = m_manager->shaderDataManager()->lookupResource( |
| 1529 | id: environmentLight->shaderData()); |
| 1530 | if (shaderData) { |
| 1531 | envLightCount = 1; |
| 1532 | |
| 1533 | // ("specularSize", "irradiance", "irradianceSize", "specular") |
| 1534 | auto irr = |
| 1535 | shaderData->properties()["irradiance" ].value.value<Qt3DCore::QNodeId>(); |
| 1536 | auto spec = |
| 1537 | shaderData->properties()["specular" ].value.value<Qt3DCore::QNodeId>(); |
| 1538 | |
| 1539 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: irradianceId, value: irr); |
| 1540 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: irradianceStructId, value: irr); |
| 1541 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: specularId, value: spec); |
| 1542 | setUniformValue(uniformPack&: command->m_parameterPack, nameId: specularStructId, value: spec); |
| 1543 | } |
| 1544 | } |
| 1545 | setUniformValue(uniformPack&: command->m_parameterPack, |
| 1546 | nameId: StringToInt::lookupId(QStringLiteral("envLightCount" )), value: envLightCount); |
| 1547 | } |
| 1548 | } |
| 1549 | |
| 1550 | bool RenderView::hasBlitFramebufferInfo() const |
| 1551 | { |
| 1552 | return m_hasBlitFramebufferInfo; |
| 1553 | } |
| 1554 | |
| 1555 | void RenderView::setHasBlitFramebufferInfo(bool hasBlitFramebufferInfo) |
| 1556 | { |
| 1557 | m_hasBlitFramebufferInfo = hasBlitFramebufferInfo; |
| 1558 | } |
| 1559 | |
| 1560 | BlitFramebufferInfo RenderView::blitFrameBufferInfo() const |
| 1561 | { |
| 1562 | return m_blitFrameBufferInfo; |
| 1563 | } |
| 1564 | |
| 1565 | void RenderView::setBlitFrameBufferInfo(const BlitFramebufferInfo &blitFrameBufferInfo) |
| 1566 | { |
| 1567 | m_blitFrameBufferInfo = blitFrameBufferInfo; |
| 1568 | } |
| 1569 | |
| 1570 | bool RenderView::isDownloadBuffersEnable() const |
| 1571 | { |
| 1572 | return m_isDownloadBuffersEnable; |
| 1573 | } |
| 1574 | |
| 1575 | void RenderView::setIsDownloadBuffersEnable(bool isDownloadBuffersEnable) |
| 1576 | { |
| 1577 | m_isDownloadBuffersEnable = isDownloadBuffersEnable; |
| 1578 | } |
| 1579 | |
| 1580 | } // namespace Rhi |
| 1581 | } // namespace Render |
| 1582 | } // namespace Qt3DRender |
| 1583 | |
| 1584 | QT_END_NAMESPACE |
| 1585 | |