aboutsummaryrefslogtreecommitdiffstats
path: root/src/runtimerender/qssgrhicontext_p.h
blob: 433c886014b3d696f82eb5bf8b6827a6ef709a02 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only

#ifndef QSSGRHICONTEXT_P_H
#define QSSGRHICONTEXT_P_H

//
//  W A R N I N G
//  -------------
//
// This file is not part of the Qt API.  It exists purely as an
// implementation detail.  This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//

#include <QtGui/rhi/qrhi.h>

#include <QtQuick3DRuntimeRender/qtquick3druntimerenderexports.h>
#include <QtQuick3DUtils/private/qssgrenderbasetypes_p.h>
#include <ssg/qssgrhicontext.h>

QT_BEGIN_NAMESPACE

struct QSSGRenderLayer;
struct QSSGRenderInstanceTable;
struct QSSGRenderModel;
struct QSSGRenderMesh;
class QSSGRenderGraphObject;

struct QSSGRhiInputAssemblerStatePrivate
{
    using InputAssemblerState = QSSGRhiGraphicsPipelineState::InputAssemblerState;
    static const InputAssemblerState &get(const QSSGRhiGraphicsPipelineState &ps) { return ps.ia; }
    static InputAssemblerState &get(QSSGRhiGraphicsPipelineState &ps) { return ps.ia; }
};

using QSSGRhiInputAssemblerState = QSSGRhiInputAssemblerStatePrivate::InputAssemblerState;

struct QSSGRhiGraphicsPipelineStatePrivate
{
    static void setShaderPipeline(QSSGRhiGraphicsPipelineState &ps, const QSSGRhiShaderPipeline *pipeline)
    {
        ps.shaderPipeline = pipeline;
    }

    static constexpr const QSSGRhiShaderPipeline *getShaderPipeline(const QSSGRhiGraphicsPipelineState &ps)
    {
        return ps.shaderPipeline;
    }
};

namespace QSSGRhiHelpers
{

inline QRhiSampler::Filter toRhi(QSSGRenderTextureFilterOp op)
{
    switch (op) {
    case QSSGRenderTextureFilterOp::Nearest:
        return QRhiSampler::Nearest;
    case QSSGRenderTextureFilterOp::Linear:
        return QRhiSampler::Linear;
    case QSSGRenderTextureFilterOp::None:
        return QRhiSampler::Linear;
    }

    Q_UNREACHABLE_RETURN(QRhiSampler::Linear);
}

inline QRhiSampler::AddressMode toRhi(QSSGRenderTextureCoordOp tiling)
{
    switch (tiling) {
    case QSSGRenderTextureCoordOp::Repeat:
        return QRhiSampler::Repeat;
    case QSSGRenderTextureCoordOp::MirroredRepeat:
        return QRhiSampler::Mirror;
    case QSSGRenderTextureCoordOp::ClampToEdge:
        return QRhiSampler::ClampToEdge;
    case QSSGRenderTextureCoordOp::Unknown:
        return QRhiSampler::ClampToEdge;
    }

    Q_UNREACHABLE_RETURN(QRhiSampler::ClampToEdge);
}

inline QRhiGraphicsPipeline::CullMode toCullMode(QSSGCullFaceMode cullFaceMode)
{
    switch (cullFaceMode) {
    case QSSGCullFaceMode::Back:
        return QRhiGraphicsPipeline::Back;
    case QSSGCullFaceMode::Front:
        return QRhiGraphicsPipeline::Front;
    case QSSGCullFaceMode::Disabled:
        return QRhiGraphicsPipeline::None;
    case QSSGCullFaceMode::FrontAndBack:
        qWarning("FrontAndBack cull mode not supported");
        return QRhiGraphicsPipeline::None;
    case QSSGCullFaceMode::Unknown:
        return QRhiGraphicsPipeline::None;
    }

    Q_UNREACHABLE_RETURN(QRhiGraphicsPipeline::None);
}

QRhiVertexInputAttribute::Format toVertexInputFormat(QSSGRenderComponentType compType, quint32 numComps);
QRhiGraphicsPipeline::Topology toTopology(QSSGRenderDrawMode drawMode);
// Fills out inputLayout.attributes[].location based on
// inputLayoutInputNames and the provided shader reflection info.
void bakeVertexInputLocations(QSSGRhiInputAssemblerState *ia, const QSSGRhiShaderPipeline &shaders, int instanceBufferBinding = 0);

} // namespace QSSGRhiHelpers

inline bool operator==(const QSSGRhiGraphicsPipelineState &a, const QSSGRhiGraphicsPipelineState &b) Q_DECL_NOTHROW
{
    const auto &ia_a = QSSGRhiInputAssemblerStatePrivate::get(a);
    const auto &ia_b = QSSGRhiInputAssemblerStatePrivate::get(b);
    return QSSGRhiGraphicsPipelineStatePrivate::getShaderPipeline(a) == QSSGRhiGraphicsPipelineStatePrivate::getShaderPipeline(b)
            && a.samples == b.samples
            && a.flags == b.flags
            && a.stencilRef == b.stencilRef
            && (std::memcmp(&a.stencilOpFrontState, &b.stencilOpFrontState, sizeof(QRhiGraphicsPipeline::StencilOpState)) == 0)
            && a.stencilWriteMask == b.stencilWriteMask
            && a.depthFunc == b.depthFunc
            && a.cullMode == b.cullMode
            && a.depthBias == b.depthBias
            && a.slopeScaledDepthBias == b.slopeScaledDepthBias
            && a.viewport == b.viewport
            && a.scissor == b.scissor
            && ia_a.topology == ia_b.topology
            && ia_a.inputLayout == ia_b.inputLayout
            && a.targetBlend.colorWrite == b.targetBlend.colorWrite
            && a.targetBlend.srcColor == b.targetBlend.srcColor
            && a.targetBlend.dstColor == b.targetBlend.dstColor
            && a.targetBlend.opColor == b.targetBlend.opColor
            && a.targetBlend.srcAlpha == b.targetBlend.srcAlpha
            && a.targetBlend.dstAlpha == b.targetBlend.dstAlpha
            && a.targetBlend.opAlpha == b.targetBlend.opAlpha
            && a.colorAttachmentCount == b.colorAttachmentCount
            && a.lineWidth == b.lineWidth
            && a.polygonMode == b.polygonMode
            && a.viewCount == b.viewCount;
}

inline bool operator!=(const QSSGRhiGraphicsPipelineState &a, const QSSGRhiGraphicsPipelineState &b) Q_DECL_NOTHROW
{
    return !(a == b);
}

inline size_t qHash(const QSSGRhiGraphicsPipelineState &s, size_t seed) Q_DECL_NOTHROW
{
    // do not bother with all fields
    return qHash(QSSGRhiGraphicsPipelineStatePrivate::getShaderPipeline(s), seed)
            ^ qHash(s.samples)
            ^ qHash(s.viewCount)
            ^ qHash(s.targetBlend.dstColor)
            ^ qHash(s.depthFunc)
            ^ qHash(s.cullMode)
            ^ qHash(s.colorAttachmentCount)
            ^ qHash(s.lineWidth)
            ^ qHash(s.polygonMode)
            ^ qHashBits(&s.stencilOpFrontState, sizeof(QRhiGraphicsPipeline::StencilOpState))
            ^ (s.flags)
            ^ (s.stencilRef << 6)
            ^ (s.stencilWriteMask << 7);
}

// The lookup keys can be somewhat complicated due to having to handle cases
// like "render a model in a shared scene between multiple View3Ds" (here both
// the View3D ('layer/cid') and the model ('model') act as the lookup key since
// while the model is the same, we still want different uniform buffers per
// View3D), or the case of shadow maps where the shadow map (there can be as
// many as lights) is taken into account too ('entry') together with an entry index
// where more resolution is needed (e.g., cube maps).
//
struct QSSGRhiDrawCallDataKey
{
    const void *cid = nullptr; // Usually the sub-pass (see usage of QSSGPassKey)
    const void *model = nullptr;
    const void *entry = nullptr;
    quintptr entryIdx = 0;
};

class Q_QUICK3DRUNTIMERENDER_EXPORT QSSGRhiBuffer
{
    Q_DISABLE_COPY(QSSGRhiBuffer)
public:
    QSSGRhiBuffer(QSSGRhiContext &context,
                  QRhiBuffer::Type type,
                  QRhiBuffer::UsageFlags usageMask,
                  quint32 stride,
                  qsizetype size,
                  QRhiCommandBuffer::IndexFormat indexFormat = QRhiCommandBuffer::IndexUInt16);

    virtual ~QSSGRhiBuffer();

    QRhiBuffer *buffer() const { return m_buffer; }
    quint32 stride() const { return m_stride; }
    quint32 numVertices() const {
        const quint32 sz = quint32(m_buffer->size());
        Q_ASSERT((sz % m_stride) == 0);
        return sz / m_stride;
    }
    QRhiCommandBuffer::IndexFormat indexFormat() const { return m_indexFormat; }

private:
    QSSGRhiContext &m_context;
    QRhiBuffer *m_buffer = nullptr;
    quint32 m_stride;
    QRhiCommandBuffer::IndexFormat m_indexFormat;
};

struct QSSGRhiShaderUniform
{
    char name[64];
    size_t size = 0;

private:
    size_t offset = SIZE_MAX;
    bool maybeExists = true;
    friend class QSSGRhiShaderPipeline;
};

struct QSSGRhiShaderUniformArray
{
    char name[64];
    size_t typeSize = 0;
    size_t itemCount = 0;

private:
    size_t offset = SIZE_MAX;
    size_t size = 0;
    bool maybeExists = true;
    friend class QSSGRhiShaderPipeline;
};

using QSSGRhiBufferPtr = std::shared_ptr<QSSGRhiBuffer>;

inline bool operator==(const QSSGRhiSamplerDescription &a, const QSSGRhiSamplerDescription &b) Q_DECL_NOTHROW
{
   return a.hTiling == b.hTiling && a.vTiling == b.vTiling && a.zTiling == b.zTiling
           && a.minFilter == b.minFilter && a.magFilter == b.magFilter
           && a.mipmap == b.mipmap;
}

inline bool operator!=(const QSSGRhiSamplerDescription &a, const QSSGRhiSamplerDescription &b) Q_DECL_NOTHROW
{
    return !(a == b);
}

struct QSSGRhiTexture
{
    QByteArray name;
    QRhiTexture *texture = nullptr;
    QSSGRhiSamplerDescription samplerDesc;
};

enum class QSSGRhiSamplerBindingHints
{
    LightProbe = 64, // must be larger than the largest value in SSGRenderableImage::Type
    ScreenTexture,
    DepthTexture,
    AoTexture,
    LightmapTexture,
    DepthTextureArray,
    ScreenTextureArray,
    AoTextureArray,

    BindingMapSize
};

// these are our current shader limits
#define QSSG_MAX_NUM_LIGHTS 15
#define QSSG_REDUCED_MAX_NUM_LIGHTS 5
#define QSSG_MAX_NUM_SHADOW_MAPS 8

// note this struct must exactly match the memory layout of the uniform block in
// funcSampleLightVars.glsllib
struct QSSGShaderLightData
{
    float position[4];
    float direction[4]; // Specifies the light direction in world coordinates.
    float diffuse[4];
    float specular[4];
    float coneAngle; // Specifies the outer cone angle of the spot light.
    float innerConeAngle; // Specifies the inner cone angle of the spot light.
    float constantAttenuation; // Specifies the constant light attenuation factor.
    float linearAttenuation; // Specifies the linear light attenuation factor.
    float quadraticAttenuation; // Specifies the quadratic light attenuation factor.
    float padding[3]; // the next light array element must start at a vec4-aligned offset
};

struct QSSGShaderLightsUniformData
{
    qint32 count = -1;
    float padding[3]; // first element must start at a vec4-aligned offset
    QSSGShaderLightData lightData[QSSG_MAX_NUM_LIGHTS];
};

// Default materials work with a regular combined image sampler for each shadowmap.
struct QSSGRhiShadowMapProperties
{
    QRhiTexture *shadowMapTexture = nullptr;
    QByteArray shadowMapTextureUniformName;
    int cachedBinding = -1; // -1 == invalid
};

class Q_QUICK3DRUNTIMERENDER_EXPORT QSSGRhiShaderPipeline
{
    Q_DISABLE_COPY(QSSGRhiShaderPipeline)
public:
    explicit QSSGRhiShaderPipeline(QSSGRhiContext &context) : m_context(context) { }

    QSSGRhiContext &context() const { return m_context; }
    bool isNull() const { return m_stages.isEmpty(); }

    enum StageFlag {
        // Indicates that this shaderpipeline object is not going to be used with
        // a QSSGRhiInputAssemblerState, i.e. bakeVertexInputLocations() will
        // not be called.
        UsedWithoutIa = 0x01
    };
    Q_DECLARE_FLAGS(StageFlags, StageFlag)

    void addStage(const QRhiShaderStage &stage, StageFlags flags = {});
    const QRhiShaderStage *cbeginStages() const { return m_stages.cbegin(); }
    const QRhiShaderStage *cendStages() const { return m_stages.cend(); }

    const QRhiShaderStage *vertexStage() const {
        for (const QRhiShaderStage &s : m_stages) {
            if (s.type() == QRhiShaderStage::Vertex)
                return &s;
        }
        return nullptr;
    }
    const QRhiShaderStage *fragmentStage() const {
        for (const QRhiShaderStage &s : m_stages) {
            if (s.type() == QRhiShaderStage::Fragment)
                return &s;
        }
        return nullptr;
    }

    int ub0Size() const { return m_ub0Size; }
    int ub0LightDataOffset() const { return m_ub0NextUBufOffset; }
    int ub0LightDataSize() const
    {
        return int(4 * sizeof(qint32) + m_lightsUniformData.count * sizeof(QSSGShaderLightData));
    }

    const QHash<QSSGRhiInputAssemblerState::InputSemantic, QShaderDescription::InOutVariable> &vertexInputs() const { return m_vertexInputs; }

    // This struct is used purely for performance. It is used to quickly store
    // and index common uniform names using the storeIndex argument in the
    // setUniform method.
    struct CommonUniformIndices
    {
        int cameraPositionIdx = -1;
        int cameraDirectionIdx = -1;
        int viewProjectionMatrixIdx = -1;
        int projectionMatrixIdx = -1;
        int inverseProjectionMatrixIdx = -1;
        int viewMatrixIdx = -1;
        int modelViewProjectionIdx = -1;
        int normalMatrixIdx = -1;
        int modelMatrixIdx = -1;
        int lightProbeOrientationIdx = -1;
        int lightProbePropertiesIdx = -1;
        int material_emissiveColorIdx = -1;
        int material_baseColorIdx = -1;
        int material_specularIdx = -1;
        int cameraPropertiesIdx = -1;
        int light_ambient_totalIdx = -1;
        int material_propertiesIdx = -1;
        int material_properties2Idx = -1;
        int material_properties3Idx = -1;
        int material_properties4Idx = -1;
        int material_properties5Idx = -1;
        int material_attenuationIdx = -1;
        int thicknessFactorIdx = -1;
        int clearcoatNormalStrengthIdx = -1;
        int clearcoatFresnelPowerIdx = -1;
        int rhiPropertiesIdx = -1;
        int displaceAmountIdx = -1;
        int boneTransformsIdx = -1;
        int boneNormalTransformsIdx = -1;
        int shadowDepthAdjustIdx = -1;
        int pointSizeIdx = -1;
        int morphWeightsIdx = -1;
        int reflectionProbeCubeMapCenter = -1;
        int reflectionProbeBoxMax = -1;
        int reflectionProbeBoxMin = -1;
        int reflectionProbeCorrection = -1;
        int specularAAIdx = -1;
        int fogColorIdx = -1;
        int fogSunColorIdx = -1;
        int fogDepthPropertiesIdx = -1;
        int fogHeightPropertiesIdx = -1;
        int fogTransmitPropertiesIdx = -1;

        struct ImageIndices
        {
            int imageRotationsUniformIndex = -1;
            int imageOffsetsUniformIndex = -1;
        };
        QVarLengthArray<ImageIndices, 16> imageIndices;
    } commonUniformIndices;

    struct InstanceLocations {
        int transform0 = -1;
        int transform1 = -1;
        int transform2 = -1;
        int color = -1;
        int data = -1;
    } instanceLocations;

    enum class UniformFlag {
        Mat3 = 0x01
    };
    Q_DECLARE_FLAGS(UniformFlags, UniformFlag)

    void setUniformValue(char *ubufData, const char *name, const QVariant &value, QSSGRenderShaderValue::Type type);
    void setUniform(char *ubufData, const char *name, const void *data, size_t size, int *storeIndex = nullptr, UniformFlags flags = {});
    void setUniformArray(char *ubufData, const char *name, const void *data, size_t itemCount, QSSGRenderShaderValue::Type type, int *storeIndex = nullptr);
    int bindingForTexture(const char *name, int hint = -1);

    void setLightsEnabled(bool enable) { m_lightsEnabled = enable; }
    bool isLightingEnabled() const { return m_lightsEnabled; }

    void resetShadowMaps() { m_shadowMaps.clear(); }
    QSSGRhiShadowMapProperties &addShadowMap() { m_shadowMaps.append(QSSGRhiShadowMapProperties()); return m_shadowMaps.last(); }
    int shadowMapCount() const { return m_shadowMaps.size(); }
    const QSSGRhiShadowMapProperties &shadowMapAt(int index) const { return m_shadowMaps[index]; }
    QSSGRhiShadowMapProperties &shadowMapAt(int index) { return m_shadowMaps[index]; }

    void ensureCombinedMainLightsUniformBuffer(QRhiBuffer **ubuf);
    void ensureUniformBuffer(QRhiBuffer **ubuf);

    void setLightProbeTexture(QRhiTexture *texture,
                              QSSGRenderTextureCoordOp hTile = QSSGRenderTextureCoordOp::ClampToEdge,
                              QSSGRenderTextureCoordOp vTile = QSSGRenderTextureCoordOp::ClampToEdge)
    {
        m_lightProbeTexture = texture; m_lightProbeHorzTile = hTile; m_lightProbeVertTile = vTile;
    }
    QRhiTexture *lightProbeTexture() const { return m_lightProbeTexture; }
    QPair<QSSGRenderTextureCoordOp, QSSGRenderTextureCoordOp> lightProbeTiling() const
    {
        return {m_lightProbeHorzTile, m_lightProbeVertTile};
    }

    void setScreenTexture(QRhiTexture *texture) { m_screenTexture = texture; }
    QRhiTexture *screenTexture() const { return m_screenTexture; }

    void setDepthTexture(QRhiTexture *texture) { m_depthTexture = texture; }
    QRhiTexture *depthTexture() const { return m_depthTexture; }

    void setSsaoTexture(QRhiTexture *texture) { m_ssaoTexture = texture; }
    QRhiTexture *ssaoTexture() const { return m_ssaoTexture; }

    void setLightmapTexture(QRhiTexture *texture) { m_lightmapTexture = texture; }
    QRhiTexture *lightmapTexture() const { return m_lightmapTexture; }

    void resetExtraTextures() { m_extraTextures.clear(); }
    void addExtraTexture(const QSSGRhiTexture &t) { m_extraTextures.append(t); }
    int extraTextureCount() const { return m_extraTextures.size(); }
    const QSSGRhiTexture &extraTextureAt(int index) const { return m_extraTextures[index]; }
    QSSGRhiTexture &extraTextureAt(int index) { return m_extraTextures[index]; }

    QSSGShaderLightsUniformData &lightsUniformData() { return m_lightsUniformData; }
    InstanceLocations instanceBufferLocations() const { return instanceLocations; }

    int offsetOfUniform(const QByteArray &name);

private:
    QSSGRhiContext &m_context;
    QVarLengthArray<QRhiShaderStage, 2> m_stages;
    int m_ub0Size = 0;
    int m_ub0NextUBufOffset = 0;
    QHash<QByteArray, QShaderDescription::BlockVariable> m_ub0;
    QHash<QSSGRhiInputAssemblerState::InputSemantic, QShaderDescription::InOutVariable> m_vertexInputs;
    QHash<QByteArray, QShaderDescription::InOutVariable> m_combinedImageSamplers;
    int m_materialImageSamplerBindings[size_t(QSSGRhiSamplerBindingHints::BindingMapSize)];

    QVarLengthArray<QSSGRhiShaderUniform, 32> m_uniforms; // members of the main (binding 0) uniform buffer
    QVarLengthArray<QSSGRhiShaderUniformArray, 8> m_uniformArrays;
    QHash<QByteArray, size_t> m_uniformIndex; // Maps uniform name to index in m_uniforms and m_uniformArrays

    // transient (per-object) data; pointers are all non-owning
    bool m_lightsEnabled = false;
    QSSGShaderLightsUniformData m_lightsUniformData;
    QVarLengthArray<QSSGRhiShadowMapProperties, QSSG_MAX_NUM_SHADOW_MAPS> m_shadowMaps;
    QRhiTexture *m_lightProbeTexture = nullptr;
    QSSGRenderTextureCoordOp m_lightProbeHorzTile = QSSGRenderTextureCoordOp::ClampToEdge;
    QSSGRenderTextureCoordOp m_lightProbeVertTile = QSSGRenderTextureCoordOp::ClampToEdge;
    QRhiTexture *m_screenTexture = nullptr;
    QRhiTexture *m_depthTexture = nullptr;
    QRhiTexture *m_ssaoTexture = nullptr;
    QRhiTexture *m_lightmapTexture = nullptr;
    QVarLengthArray<QSSGRhiTexture, 8> m_extraTextures;
};

Q_DECLARE_OPERATORS_FOR_FLAGS(QSSGRhiShaderPipeline::StageFlags)
Q_DECLARE_OPERATORS_FOR_FLAGS(QSSGRhiShaderPipeline::UniformFlags)

using QSSGRhiShaderPipelinePtr = std::shared_ptr<QSSGRhiShaderPipeline>;

class Q_QUICK3DRUNTIMERENDER_EXPORT QSSGRhiShaderResourceBindingList
{
public:
    static const int MAX_SIZE = 32;

    int p = 0;
    size_t h = 0;
    QRhiShaderResourceBinding v[MAX_SIZE];

    void clear() { p = 0; h = 0; }

    QSSGRhiShaderResourceBindingList() { }

    QSSGRhiShaderResourceBindingList(const QSSGRhiShaderResourceBindingList &other)
        : p(other.p),
        h(other.h)
    {
        for (int i = 0; i < p; ++i)
            v[i] = other.v[i];
    }

    QSSGRhiShaderResourceBindingList &operator=(const QSSGRhiShaderResourceBindingList &other) Q_DECL_NOTHROW
    {
        if (this != &other) {
            p = other.p;
            h = other.h;
            for (int i = 0; i < p; ++i)
                v[i] = other.v[i];
        }
        return *this;
    }

    void addUniformBuffer(int binding, QRhiShaderResourceBinding::StageFlags stage, QRhiBuffer *buf, int offset = 0 , int size = 0);
    void addTexture(int binding, QRhiShaderResourceBinding::StageFlags stage, QRhiTexture *tex, QRhiSampler *sampler);
};

inline bool operator==(const QSSGRhiShaderResourceBindingList &a, const QSSGRhiShaderResourceBindingList &b) Q_DECL_NOTHROW
{
    if (a.h != b.h)
        return false;
    if (a.p != b.p)
        return false;
    for (int i = 0; i < a.p; ++i) {
        if (a.v[i] != b.v[i])
            return false;
    }
    return true;
}

inline bool operator!=(const QSSGRhiShaderResourceBindingList &a, const QSSGRhiShaderResourceBindingList &b) Q_DECL_NOTHROW
{
    return !(a == b);
}

inline size_t qHash(const QSSGRhiShaderResourceBindingList &bl, size_t seed) Q_DECL_NOTHROW
{
    return bl.h ^ seed;
}

struct QSSGRhiDrawCallData
{
    QRhiBuffer *ubuf = nullptr; // owned
    QRhiShaderResourceBindings *srb = nullptr; // not owned
    QSSGRhiShaderResourceBindingList bindings;
    QRhiGraphicsPipeline *pipeline = nullptr; // not owned
    size_t renderTargetDescriptionHash = 0;
    QVector<quint32> renderTargetDescription;
    QSSGRhiGraphicsPipelineState ps;

    void reset()
    {
        delete ubuf;
        ubuf = nullptr;
        srb = nullptr;
        pipeline = nullptr;
    }
};

struct QSSGRhiRenderableTexture
{
    QRhiTexture *texture = nullptr;
    QRhiRenderBuffer *depthStencil = nullptr;
    QRhiTexture *depthTexture = nullptr; // either depthStencil or depthTexture are valid, never both
    QRhiRenderPassDescriptor *rpDesc = nullptr;
    QRhiTextureRenderTarget *rt = nullptr;
    bool isValid() const { return texture && rpDesc && rt; }
    void resetRenderTarget() {
        delete rt;
        rt = nullptr;
        delete rpDesc;
        rpDesc = nullptr;
    }
    void reset() {
        resetRenderTarget();
        delete texture;
        delete depthStencil;
        delete depthTexture;
        *this = QSSGRhiRenderableTexture();
    }
};

struct QSSGRhiSortData
{
    float d = 0.0f;
    int indexOrOffset = -1;
};

struct QSSGRhiInstanceBufferData
{
    QRhiBuffer *buffer = nullptr;
    QByteArray sortedData;
    QList<QSSGRhiSortData> sortData;
    QVector3D sortedCameraDirection;
    QVector3D cameraPosition;
    QByteArray lodData;
    int serial = -1;
    bool owned = true;
    bool sorting = false;
};

struct QSSGRhiParticleData
{
    QRhiTexture *texture = nullptr;
    QByteArray sortedData;
    QByteArray convertData;
    QList<QSSGRhiSortData> sortData;
    int particleCount = 0;
    int serial = -1;
    bool sorting = false;
};

class QSSGComputePipelineStateKey
{
public:
    QShader shader;
    QVector<quint32> srbLayoutDescription;
    struct {
        size_t srbLayoutDescriptionHash;
    } extra;
    static QSSGComputePipelineStateKey create(const QShader &shader,
                                              const QRhiShaderResourceBindings *srb)
    {
        const QVector<quint32> srbDesc = srb->serializedLayoutDescription();
        return { shader, srbDesc, { qHash(srbDesc) } };
    }
};

inline bool operator==(const QSSGComputePipelineStateKey &a, const QSSGComputePipelineStateKey &b) Q_DECL_NOTHROW
{
    return a.shader == b.shader && a.srbLayoutDescription == b.srbLayoutDescription;
}

inline bool operator!=(const QSSGComputePipelineStateKey &a, const QSSGComputePipelineStateKey &b) Q_DECL_NOTHROW
{
    return !(a == b);
}

inline size_t qHash(const QSSGComputePipelineStateKey &k, size_t seed = 0) Q_DECL_NOTHROW
{
    return qHash(k.shader, seed) ^ k.extra.srbLayoutDescriptionHash;
}

struct QSSGRhiDummyTextureKey
{
    QRhiTexture::Flags flags;
    QSize size;
    QColor color;
    int arraySize;
};

inline size_t qHash(const QSSGRhiDummyTextureKey &k, size_t seed) Q_DECL_NOTHROW
{
    return qHash(k.flags, seed)
            ^ qHash(k.size.width() ^ k.size.height() ^ k.color.red() ^ k.color.green()
                        ^ k.color.blue() ^ k.color.alpha() ^ k.arraySize);
}

inline bool operator==(const QSSGRhiDummyTextureKey &a, const QSSGRhiDummyTextureKey &b) Q_DECL_NOTHROW
{
    return a.flags == b.flags && a.size == b.size && a.color == b.color && a.arraySize == b.arraySize;
}

inline bool operator!=(const QSSGRhiDummyTextureKey &a, const QSSGRhiDummyTextureKey &b) Q_DECL_NOTHROW
{
    return !(a == b);
}

class QSSGGraphicsPipelineStateKey
{
public:
    QSSGRhiGraphicsPipelineState state;
    QVector<quint32> renderTargetDescription;
    QVector<quint32> srbLayoutDescription;
    struct {
        size_t renderTargetDescriptionHash;
        size_t srbLayoutDescriptionHash;
    } extra;
    static QSSGGraphicsPipelineStateKey create(const QSSGRhiGraphicsPipelineState &state,
                                               const QRhiRenderPassDescriptor *rpDesc,
                                               const QRhiShaderResourceBindings *srb)
    {
        const QVector<quint32> rtDesc = rpDesc->serializedFormat();
        const QVector<quint32> srbDesc = srb->serializedLayoutDescription();
        return { state, rtDesc, srbDesc, { qHash(rtDesc), qHash(srbDesc) } };
    }
};

inline bool operator==(const QSSGGraphicsPipelineStateKey &a, const QSSGGraphicsPipelineStateKey &b) Q_DECL_NOTHROW
{
    return a.state == b.state
        && a.renderTargetDescription == b.renderTargetDescription
        && a.srbLayoutDescription == b.srbLayoutDescription;
}

inline bool operator!=(const QSSGGraphicsPipelineStateKey &a, const QSSGGraphicsPipelineStateKey &b) Q_DECL_NOTHROW
{
    return !(a == b);
}

inline size_t qHash(const QSSGGraphicsPipelineStateKey &k, size_t seed) Q_DECL_NOTHROW
{
    return qHash(k.state, seed)
        ^ k.extra.renderTargetDescriptionHash
        ^ k.extra.srbLayoutDescriptionHash;
}

#define QSSGRHICTX_STAT(ctx, f) \
    for (bool qssgrhictxlog_enabled = QSSGRhiContextStats::get(*ctx).isEnabled(); qssgrhictxlog_enabled; qssgrhictxlog_enabled = false) \
        QSSGRhiContextStats::get(*ctx).f

class Q_QUICK3DRUNTIMERENDER_EXPORT QSSGRhiContextStats
{
public:
    [[nodiscard]] static QSSGRhiContextStats &get(QSSGRhiContext &rhiCtx);
    [[nodiscard]] static const QSSGRhiContextStats &get(const QSSGRhiContext &rhiCtx);

    struct DrawInfo {
        quint64 callCount = 0;
        quint64 vertexOrIndexCount = 0;
    };
    struct InstancedDrawInfo {
        quint64 callCount = 0;
        quint64 vertexOrIndexCount = 0;
        quint64 instanceCount = 0;
    };
    struct RenderPassInfo {
        QByteArray rtName;
        QSize pixelSize;
        DrawInfo indexedDraws;
        DrawInfo draws;
        InstancedDrawInfo instancedIndexedDraws;
        InstancedDrawInfo instancedDraws;
    };
    struct PerLayerInfo {
        PerLayerInfo()
        {
            externalRenderPass.rtName = QByteArrayLiteral("Qt Quick");
        }

        // The main render pass if renderMode==Offscreen, plus render passes
        // for shadow maps, postprocessing effects, etc.
        QVector<RenderPassInfo> renderPasses;

        // An Underlay/Overlay/Inline renderMode will make the View3D add stuff
        // to a render pass managed by Qt Quick. (external == not under the
        // control of Qt Quick 3D)
        RenderPassInfo externalRenderPass;

        int currentRenderPassIndex = -1;
    };
    struct GlobalInfo { // global as in per QSSGRhiContext which is per-QQuickWindow
        quint64 meshDataSize = 0;
        quint64 imageDataSize = 0;
        qint64 materialGenerationTime = 0;
        qint64 effectGenerationTime = 0;
    };

    QHash<QSSGRenderLayer *, PerLayerInfo> perLayerInfo;
    GlobalInfo globalInfo;

    QSSGRhiContextStats(QSSGRhiContext &context)
        : rhiCtx(&context)
    {
    }

    // The data collected have four consumers:
    //
    // - Printed on debug output when QSG_RENDERER_DEBUG has the relevant key.
    //   (this way the debug output from the 2D scenegraph renderer and these 3D
    //   statistics appear nicely intermixed)
    // - Passed on to the QML profiler when profiling is enabled.
    // - DebugView via QQuick3DRenderStats.
    // - When tracing is enabled
    //
    // The first two are enabled globally, but DebugView needs a dynamic
    // enable/disable since we want to collect data when a DebugView item
    // becomes visible, but not otherwise.

    static bool profilingEnabled();
    static bool rendererDebugEnabled();

    bool isEnabled() const;
    void drawIndexed(quint32 indexCount, quint32 instanceCount);
    void draw(quint32 vertexCount, quint32 instanceCount);

    void meshDataSizeChanges(quint64 newSize) // can be called outside start-stop
    {
        globalInfo.meshDataSize = newSize;
    }

    void imageDataSizeChanges(quint64 newSize) // can be called outside start-stop
    {
        globalInfo.imageDataSize = newSize;
    }

    void registerMaterialShaderGenerationTime(qint64 ms)
    {
        globalInfo.materialGenerationTime += ms;
    }

    void registerEffectShaderGenerationTime(qint64 ms)
    {
        globalInfo.effectGenerationTime += ms;
    }

    static quint64 totalDrawCallCountForPass(const QSSGRhiContextStats::RenderPassInfo &pass)
    {
        return pass.draws.callCount
                + pass.indexedDraws.callCount
                + pass.instancedDraws.callCount
                + pass.instancedIndexedDraws.callCount;
    }

    static quint64 totalVertexCountForPass(const QSSGRhiContextStats::RenderPassInfo &pass)
    {
        return pass.draws.vertexOrIndexCount
                + pass.indexedDraws.vertexOrIndexCount
                + pass.instancedDraws.vertexOrIndexCount
                + pass.instancedIndexedDraws.vertexOrIndexCount;
    }

    void start(QSSGRenderLayer *layer);
    void stop(QSSGRenderLayer *layer);
    void beginRenderPass(QRhiTextureRenderTarget *rt);
    void endRenderPass();
    void printRenderPass(const RenderPassInfo &rp);
    void cleanupLayerInfo(QSSGRenderLayer *layer);

    QSSGRhiContext *rhiCtx;
    QSSGRenderLayer *layerKey = nullptr;
    QSet<QSSGRenderLayer *> dynamicDataSources;
};

class Q_QUICK3DRUNTIMERENDER_EXPORT QSSGRhiContextPrivate
{
    Q_DECLARE_PUBLIC(QSSGRhiContext)

    explicit QSSGRhiContextPrivate(QSSGRhiContext &rhiCtx, QRhi *rhi_)
        : q_ptr(&rhiCtx)
        , m_rhi(rhi_)
        , m_stats(rhiCtx)
    {}

public:
    using Textures = QSet<QRhiTexture *>;
    using Meshes = QSet<QSSGRenderMesh *>;

    [[nodiscard]] static QSSGRhiContextPrivate *get(QSSGRhiContext *q) { return q->d_ptr.get(); }
    [[nodiscard]] static const QSSGRhiContextPrivate *get(const QSSGRhiContext *q) { return q->d_ptr.get(); }

    [[nodiscard]] static bool shaderDebuggingEnabled();
    [[nodiscard]] static bool editorMode();

    void setMainRenderPassDescriptor(QRhiRenderPassDescriptor *rpDesc);
    void setCommandBuffer(QRhiCommandBuffer *cb);
    void setRenderTarget(QRhiRenderTarget *rt);
    void setMainPassSampleCount(int samples);
    void setMainPassViewCount(int viewCount);

    void releaseCachedResources();

    void registerTexture(QRhiTexture *texture);
    void releaseTexture(QRhiTexture *texture);

    void registerMesh(QSSGRenderMesh *mesh);
    void releaseMesh(QSSGRenderMesh *mesh);

    QRhiShaderResourceBindings *srb(const QSSGRhiShaderResourceBindingList &bindings);

    QRhiGraphicsPipeline *pipeline(const QSSGRhiGraphicsPipelineState &ps,
                                   QRhiRenderPassDescriptor *rpDesc,
                                   QRhiShaderResourceBindings *srb);

    QRhiGraphicsPipeline *pipeline(const QSSGGraphicsPipelineStateKey &key,
                                   QRhiRenderPassDescriptor *rpDesc,
                                   QRhiShaderResourceBindings *srb);

    QRhiComputePipeline *computePipeline(const QShader &shader,
                                         QRhiShaderResourceBindings *srb);

    QRhiComputePipeline *computePipeline(const QSSGComputePipelineStateKey &key,
                                         QRhiShaderResourceBindings *srb);

    QSSGRhiDrawCallData &drawCallData(const QSSGRhiDrawCallDataKey &key);
    void releaseDrawCallData(QSSGRhiDrawCallData &dcd);
    void cleanupDrawCallData(const QSSGRenderModel *model);

    QSSGRhiInstanceBufferData &instanceBufferData(QSSGRenderInstanceTable *instanceTable);

    QSSGRhiInstanceBufferData &instanceBufferData(const QSSGRenderModel *model);

    QSSGRhiParticleData &particleData(const QSSGRenderGraphObject *particlesOrModel);

    QSSGRhiContext *q_ptr = nullptr;
    QRhi *m_rhi = nullptr;

    QRhiRenderPassDescriptor *m_mainRpDesc = nullptr;
    QRhiCommandBuffer *m_cb = nullptr;
    QRhiRenderTarget *m_rt = nullptr;
    Textures m_textures;
    Meshes m_meshes;
    int m_mainSamples = 1;
    int m_mainViewCount = 1;

    QVector<QPair<QSSGRhiSamplerDescription, QRhiSampler*>> m_samplers;

    QHash<QSSGRhiDrawCallDataKey, QSSGRhiDrawCallData> m_drawCallData;
    QHash<QSSGRhiShaderResourceBindingList, QRhiShaderResourceBindings *> m_srbCache;
    QHash<QSSGGraphicsPipelineStateKey, QRhiGraphicsPipeline *> m_pipelines;
    QHash<QSSGComputePipelineStateKey, QRhiComputePipeline *> m_computePipelines;
    QHash<QSSGRhiDummyTextureKey, QRhiTexture *> m_dummyTextures;
    QHash<QSSGRenderInstanceTable *, QSSGRhiInstanceBufferData> m_instanceBuffers;
    QHash<const QSSGRenderModel *, QSSGRhiInstanceBufferData> m_instanceBuffersLod;
    QHash<const QSSGRenderGraphObject *, QSSGRhiParticleData> m_particleData;
    QSSGRhiContextStats m_stats;
};

inline bool operator==(const QSSGRhiDrawCallDataKey &a, const QSSGRhiDrawCallDataKey &b) noexcept
{
    return a.cid == b.cid && a.model == b.model && a.entry == b.entry && a.entryIdx == b.entryIdx;
}

inline bool operator!=(const QSSGRhiDrawCallDataKey &a, const QSSGRhiDrawCallDataKey &b) noexcept
{
    return !(a == b);
}

inline size_t qHash(const QSSGRhiDrawCallDataKey &k, size_t seed = 0) noexcept
{
    return qHash(quintptr(k.cid)
                 ^ quintptr(k.model)
                 ^ quintptr(k.entry)
                 ^ quintptr(k.entryIdx), seed);
}

QT_END_NAMESPACE

#endif // QSSGRHICONTEXT_P_H