QSkinny 0.8.0
C++/Qt UI toolkit
Loading...
Searching...
No Matches
QskGradientMaterial.cpp
1/******************************************************************************
2 * QSkinny - Copyright (C) The authors
3 * SPDX-License-Identifier: BSD-3-Clause
4 *****************************************************************************/
5
6#include "QskGradientMaterial.h"
7#include "QskFunctions.h"
8#include "QskRgbValue.h"
9#include "QskGradientDirection.h"
10#include "QskColorRamp.h"
11
12#include <qsgtexture.h>
13
14#include <cmath>
15
16// RHI shaders are supported by Qt 5.15 and Qt 6.x
17#define SHADER_RHI
18
19#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
20 // Old type of shaders only with Qt 5.x
21 #define SHADER_GL
22#endif
23
24#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
25 #include <qsgmaterialrhishader.h>
26 using RhiShader = QSGMaterialRhiShader;
27#else
28 using RhiShader = QSGMaterialShader;
29#endif
30
31namespace
32{
33 class GradientMaterial : public QskGradientMaterial
34 {
35 public:
36 GradientMaterial( QskGradient::Type type )
37 : QskGradientMaterial( type )
38 {
39 setFlag( Blending | RequiresFullMatrix );
40
41#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
42 setFlag( QSGMaterial::SupportsRhiShader, true );
43#endif
44 }
45
46 int compare( const QSGMaterial* other ) const override
47 {
48 const auto mat = static_cast< const GradientMaterial* >( other );
49
50 if ( ( spreadMode() == mat->spreadMode() ) && ( stops() == mat->stops() ) )
51 return 0;
52
53 return QSGMaterial::compare( other );
54 }
55
56#if QT_VERSION >= QT_VERSION_CHECK( 6, 0, 0 )
57 // make Qt 5/6 APIs matchaing
58
59 QSGMaterialShader* createShader(
60 QSGRendererInterface::RenderMode ) const override final
61 {
62 return createShader();
63 }
64
65 virtual QSGMaterialShader* createShader() const = 0;
66#endif
67
68 virtual bool setGradient( const QskGradient& ) = 0;
69 };
70
71#ifdef SHADER_GL
72
73 class GradientShaderGL : public QSGMaterialShader
74 {
75 public:
76 void setShaderFiles( const char* name )
77 {
78 static const QString root( ":/qskinny/shaders/" );
79
80 setShaderSourceFile( QOpenGLShader::Vertex, root + name + ".vert" );
81 setShaderSourceFile( QOpenGLShader::Fragment, root + name + ".frag" );
82 }
83
84 void initialize() override
85 {
86 m_opacityId = program()->uniformLocation( "opacity" );
87 m_matrixId = program()->uniformLocation( "matrix" );
88 }
89
90 void updateState( const RenderState& state,
91 QSGMaterial* newMaterial, QSGMaterial* ) override final
92 {
93 auto p = program();
94 auto material = static_cast< GradientMaterial* >( newMaterial );
95
96 if ( state.isOpacityDirty() )
97 p->setUniformValue( m_opacityId, state.opacity() );
98
99 if ( state.isMatrixDirty() )
100 p->setUniformValue(m_matrixId, state.combinedMatrix() );
101
102 updateUniformValues( material );
103
104 auto texture = QskColorRamp::texture(
105 nullptr, material->stops(), material->spreadMode() );
106 texture->bind();
107 }
108
109 char const* const* attributeNames() const override final
110 {
111 static const char* const attr[] = { "vertexCoord", nullptr };
112 return attr;
113 }
114
115 virtual void updateUniformValues( const GradientMaterial* ) = 0;
116
117 protected:
118 int m_opacityId = -1;
119 int m_matrixId = -1;
120 };
121#endif
122
123#ifdef SHADER_RHI
124 class GradientShaderRhi : public RhiShader
125 {
126 public:
127 void setShaderFiles( const char* name )
128 {
129 static const QString root( ":/qskinny/shaders/" );
130
131 setShaderFileName( VertexStage, root + name + ".vert.qsb" );
132 setShaderFileName( FragmentStage, root + name + ".frag.qsb" );
133 }
134
135 void updateSampledImage( RenderState& state, int binding,
136 QSGTexture* textures[], QSGMaterial* newMaterial, QSGMaterial* ) override final
137 {
138 if ( binding != 1 )
139 return;
140
141 auto material = static_cast< const GradientMaterial* >( newMaterial );
142
143 auto texture = QskColorRamp::texture(
144 state.rhi(), material->stops(), material->spreadMode() );
145
146#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
147 texture->updateRhiTexture( state.rhi(), state.resourceUpdateBatch() );
148#else
149 texture->commitTextureOperations( state.rhi(), state.resourceUpdateBatch() );
150#endif
151
152 textures[0] = texture;
153 }
154 };
155#endif
156}
157
158namespace
159{
160 class LinearMaterial final : public GradientMaterial
161 {
162 public:
163 LinearMaterial()
164 : GradientMaterial( QskGradient::Linear )
165 {
166 }
167
168 bool setGradient( const QskGradient& gradient ) override
169 {
170 bool changed = false;
171
172 if ( gradient.stops() != stops() )
173 {
174 setStops( gradient.stops() );
175 changed = true;
176 }
177
178 /*
179 When having a gradient, that does not need spreading
180 we could set QskGradient::PadSpread to potentally reduce
181 the number of color ramps. TODO ...
182 */
183
184 if ( gradient.spreadMode() != spreadMode() )
185 {
186 setSpreadMode( gradient.spreadMode() );
187 changed = true;
188 }
189
190 const auto dir = gradient.linearDirection();
191
192 const QVector4D vector( dir.x1(), dir.y1(),
193 dir.x2() - dir.x1(), dir.y2() - dir.y1() );
194
195 if ( m_gradientVector != vector )
196 {
197 m_gradientVector = vector;
198 changed = true;
199 }
200
201 return changed;
202 }
203
204 QSGMaterialType* type() const override
205 {
206 static QSGMaterialType type;
207 return &type;
208 }
209
210 int compare( const QSGMaterial* other ) const override
211 {
212 const auto mat = static_cast< const LinearMaterial* >( other );
213
214 if ( m_gradientVector != mat->m_gradientVector )
215 return QSGMaterial::compare( other );
216 else
217 return GradientMaterial::compare( other );
218 }
219
220 QSGMaterialShader* createShader() const override;
221
222 /*
223 xy: position
224 zw: relative to position ( sign matters )
225 */
226 QVector4D m_gradientVector;
227 };
228
229#ifdef SHADER_GL
230 class LinearShaderGL final : public GradientShaderGL
231 {
232 public:
233 LinearShaderGL()
234 {
235 setShaderFiles( "gradientlinear" );
236 }
237
238 void initialize() override
239 {
240 GradientShaderGL::initialize();
241 m_vectorId = program()->uniformLocation( "vector" );
242 }
243
244 void updateUniformValues( const GradientMaterial* newMaterial ) override
245 {
246 auto material = static_cast< const LinearMaterial* >( newMaterial );
247 program()->setUniformValue( m_vectorId, material->m_gradientVector );
248 }
249
250 private:
251 int m_vectorId = -1;
252 };
253#endif
254
255#ifdef SHADER_RHI
256 class LinearShaderRhi final : public GradientShaderRhi
257 {
258 public:
259 LinearShaderRhi()
260 {
261 setShaderFiles( "gradientlinear" );
262 }
263
264 bool updateUniformData( RenderState& state,
265 QSGMaterial* newMaterial, QSGMaterial* oldMaterial ) override
266 {
267 auto matNew = static_cast< LinearMaterial* >( newMaterial );
268 auto matOld = static_cast< LinearMaterial* >( oldMaterial );
269
270 Q_ASSERT( state.uniformData()->size() >= 84 );
271
272 auto data = state.uniformData()->data();
273 bool changed = false;
274
275 if ( state.isMatrixDirty() )
276 {
277 const auto matrix = state.combinedMatrix();
278 memcpy( data + 0, matrix.constData(), 64 );
279
280 changed = true;
281 }
282
283 if ( matOld == nullptr || matNew->m_gradientVector != matOld->m_gradientVector )
284 {
285 memcpy( data + 64, &matNew->m_gradientVector, 16 );
286 changed = true;
287 }
288
289 if ( state.isOpacityDirty() )
290 {
291 const float opacity = state.opacity();
292 memcpy( data + 80, &opacity, 4 );
293
294 changed = true;
295 }
296
297 return changed;
298 }
299 };
300#endif
301
302 QSGMaterialShader* LinearMaterial::createShader() const
303 {
304#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
305 if ( !( flags() & QSGMaterial::RhiShaderWanted ) )
306 return new LinearShaderGL;
307#endif
308 return new LinearShaderRhi;
309 }
310}
311
312namespace
313{
314 class RadialMaterial final : public GradientMaterial
315 {
316 public:
317 RadialMaterial()
318 : GradientMaterial( QskGradient::Radial )
319 {
320 }
321
322 QSGMaterialType* type() const override
323 {
324 static QSGMaterialType type;
325 return &type;
326 }
327
328 bool setGradient( const QskGradient& gradient ) override
329 {
330 bool changed = false;
331
332 if ( gradient.stops() != stops() )
333 {
334 setStops( gradient.stops() );
335 changed = true;
336 }
337
338 if ( gradient.spreadMode() != spreadMode() )
339 {
340 setSpreadMode( gradient.spreadMode() );
341 changed = true;
342 }
343
344 const auto dir = gradient.radialDirection();
345
346 const QVector2D pos( dir.x(), dir.y() );
347 const QVector2D radius( dir.radiusX(), dir.radiusY() );
348
349 if ( ( pos != m_center ) || ( m_radius != radius ) )
350 {
351 m_center = pos;
352 m_radius = radius;
353
354 changed = true;
355 }
356
357 return changed;
358 }
359
360 int compare( const QSGMaterial* other ) const override
361 {
362 const auto mat = static_cast< const RadialMaterial* >( other );
363
364 if ( ( m_center != mat->m_center ) || ( m_radius != mat->m_radius ) )
365 {
366 return QSGMaterial::compare( other );
367 }
368 else
369 {
370 return GradientMaterial::compare( other );
371 }
372 }
373
374 QSGMaterialShader* createShader() const override;
375
376 QVector2D m_center;
377 QVector2D m_radius;
378 };
379
380#ifdef SHADER_GL
381 class RadialShaderGL final : public GradientShaderGL
382 {
383 public:
384 RadialShaderGL()
385 {
386 setShaderFiles( "gradientradial" );
387 }
388
389 void initialize() override
390 {
391 GradientShaderGL::initialize();
392
393 auto p = program();
394
395 m_centerCoordId = p->uniformLocation( "centerCoord" );
396 m_radiusId = p->uniformLocation( "radius" );
397 }
398
399 void updateUniformValues( const GradientMaterial* newMaterial ) override
400 {
401 auto material = static_cast< const RadialMaterial* >( newMaterial );
402
403 auto p = program();
404
405 p->setUniformValue( m_centerCoordId, material->m_center );
406 p->setUniformValue( m_radiusId, material->m_radius );
407 }
408
409 private:
410 int m_centerCoordId = -1;
411 int m_radiusId = -1;
412 };
413#endif
414
415#ifdef SHADER_RHI
416 class RadialShaderRhi final : public GradientShaderRhi
417 {
418 public:
419 RadialShaderRhi()
420 {
421 setShaderFiles( "gradientradial" );
422 }
423
424 bool updateUniformData( RenderState& state,
425 QSGMaterial* newMaterial, QSGMaterial* oldMaterial) override
426 {
427 auto matNew = static_cast< RadialMaterial* >( newMaterial );
428 auto matOld = static_cast< RadialMaterial* >( oldMaterial );
429
430 Q_ASSERT( state.uniformData()->size() >= 84 );
431
432 auto data = state.uniformData()->data();
433 bool changed = false;
434
435 if ( state.isMatrixDirty() )
436 {
437 const auto matrix = state.combinedMatrix();
438 memcpy( data + 0, matrix.constData(), 64 );
439
440 changed = true;
441 }
442
443 if ( matOld == nullptr || matNew->m_center != matOld->m_center )
444 {
445 memcpy( data + 64, &matNew->m_center, 8 );
446 changed = true;
447 }
448
449 if ( matOld == nullptr || matNew->m_radius != matOld->m_radius )
450 {
451 memcpy( data + 72, &matNew->m_radius, 8 );
452 changed = true;
453 }
454
455 if ( state.isOpacityDirty() )
456 {
457 const float opacity = state.opacity();
458 memcpy( data + 80, &opacity, 4 );
459
460 changed = true;
461 }
462
463 return changed;
464 }
465 };
466#endif
467
468 QSGMaterialShader* RadialMaterial::createShader() const
469 {
470#ifdef SHADER_GL
471 if ( !( flags() & QSGMaterial::RhiShaderWanted ) )
472 return new RadialShaderGL;
473#endif
474
475 return new RadialShaderRhi;
476 }
477}
478
479namespace
480{
481 class ConicMaterial final : public GradientMaterial
482 {
483 public:
484 ConicMaterial()
485 : GradientMaterial( QskGradient::Conic )
486 {
487 }
488
489 QSGMaterialType* type() const override
490 {
491 static QSGMaterialType type;
492 return &type;
493 }
494
495 bool setGradient( const QskGradient& gradient ) override
496 {
497 bool changed = false;
498
499 if ( gradient.stops() != stops() )
500 {
501 setStops( gradient.stops() );
502 changed = true;
503 }
504
505 if ( gradient.spreadMode() != spreadMode() )
506 {
507 setSpreadMode( gradient.spreadMode() );
508 changed = true;
509 }
510
511 const auto dir = gradient.conicDirection();
512
513 float ratio = dir.aspectRatio();
514 if ( ratio <= 0.0f )
515 ratio = 1.0f;
516
517 if ( ratio != m_aspectRatio )
518 {
519 m_aspectRatio = ratio;
520 changed = true;
521 }
522
523 const QVector2D center( dir.x(), dir.y() );
524
525 if ( center != m_center )
526 {
527 m_center = center;
528 changed = true;
529 }
530
531 // Angles as ratio of a rotation
532
533 float start = fmod( dir.startAngle(), 360.0 ) / 360.0;
534 if ( start < 0.0f)
535 start += 1.0f;
536
537 float span;
538
539 if ( dir.spanAngle() >= 360.0 )
540 {
541 span = 1.0f;
542 }
543 else if ( dir.spanAngle() <= -360.0 )
544 {
545 span = -1.0f;
546 }
547 else
548 {
549 span = fmod( dir.spanAngle(), 360.0 ) / 360.0;
550 }
551
552 if ( ( start != m_start ) || ( span != m_span ) )
553 {
554 m_start = start;
555 m_span = span;
556
557 changed = true;
558 }
559
560 return changed;
561 }
562
563 int compare( const QSGMaterial* other ) const override
564 {
565 const auto mat = static_cast< const ConicMaterial* >( other );
566
567 if ( ( m_center != mat->m_center )
568 || !qskFuzzyCompare( m_aspectRatio, mat->m_aspectRatio )
569 || !qskFuzzyCompare( m_start, mat->m_start )
570 || !qskFuzzyCompare( m_span, mat->m_span ) )
571 {
572 return QSGMaterial::compare( other );
573 }
574
575 return GradientMaterial::compare( other );
576 }
577
578 QSGMaterialShader* createShader() const override;
579
580 QVector2D m_center;
581 float m_aspectRatio = 1.0;
582 float m_start = 0.0;
583 float m_span = 1.0;
584 };
585
586#ifdef SHADER_GL
587 class ConicShaderGL final : public GradientShaderGL
588 {
589 public:
590 ConicShaderGL()
591 {
592 setShaderFiles( "gradientconic" );
593 }
594
595 void initialize() override
596 {
597 GradientShaderGL::initialize();
598
599 m_centerCoordId = program()->uniformLocation( "centerCoord" );
600 m_aspectRatioId = program()->uniformLocation( "aspectRatio" );
601 m_startId = program()->uniformLocation( "start" );
602 m_spanId = program()->uniformLocation( "span" );
603 }
604
605 void updateUniformValues( const GradientMaterial* newMaterial ) override
606 {
607 auto material = static_cast< const ConicMaterial* >( newMaterial );
608
609 program()->setUniformValue( m_centerCoordId, material->m_center );
610 program()->setUniformValue( m_aspectRatioId, material->m_aspectRatio );
611 program()->setUniformValue( m_startId, material->m_start );
612 program()->setUniformValue( m_spanId, material->m_span );
613 }
614
615 private:
616 int m_centerCoordId = -1;
617 int m_aspectRatioId = -1;
618 int m_startId = -1;
619 int m_spanId = -1;
620 };
621#endif
622
623#ifdef SHADER_RHI
624 class ConicShaderRhi final : public GradientShaderRhi
625 {
626 public:
627 ConicShaderRhi()
628 {
629 setShaderFiles( "gradientconic" );
630 }
631
632 bool updateUniformData( RenderState& state,
633 QSGMaterial* newMaterial, QSGMaterial* oldMaterial ) override
634 {
635 auto matNew = static_cast< ConicMaterial* >( newMaterial );
636 auto matOld = static_cast< ConicMaterial* >( oldMaterial );
637
638 Q_ASSERT( state.uniformData()->size() >= 88 );
639
640 auto data = state.uniformData()->data();
641 bool changed = false;
642
643 if ( state.isMatrixDirty() )
644 {
645 const auto matrix = state.combinedMatrix();
646 memcpy( data + 0, matrix.constData(), 64 );
647
648 changed = true;
649 }
650
651 if ( matOld == nullptr || matNew->m_center != matOld->m_center )
652 {
653 memcpy( data + 64, &matNew->m_center, 8 );
654 changed = true;
655 }
656
657 if ( matOld == nullptr || matNew->m_aspectRatio != matOld->m_aspectRatio )
658 {
659 memcpy( data + 72, &matNew->m_aspectRatio, 4 );
660 changed = true;
661 }
662
663 if ( matOld == nullptr || matNew->m_start != matOld->m_start )
664 {
665 memcpy( data + 76, &matNew->m_start, 4 );
666 changed = true;
667 }
668
669 if ( matOld == nullptr || matNew->m_span != matOld->m_span )
670 {
671 memcpy( data + 80, &matNew->m_span, 4 );
672 changed = true;
673 }
674
675 if ( state.isOpacityDirty() )
676 {
677 const float opacity = state.opacity();
678 memcpy( data + 84, &opacity, 4 );
679
680 changed = true;
681 }
682
683 return changed;
684 }
685 };
686#endif
687
688 QSGMaterialShader* ConicMaterial::createShader() const
689 {
690#ifdef SHADER_GL
691 if ( !( flags() & QSGMaterial::RhiShaderWanted ) )
692 return new ConicShaderGL;
693#endif
694 return new ConicShaderRhi;
695 }
696}
697
698QskGradientMaterial::QskGradientMaterial( QskGradient::Type type )
699 : m_gradientType( type )
700{
701}
702
703template< typename Material >
704inline Material* qskEnsureMaterial( QskGradientMaterial* material )
705{
706 if ( material == nullptr )
707 material = new Material();
708
709 return static_cast< Material* >( material );
710}
711
712bool QskGradientMaterial::updateGradient( const QRectF& rect, const QskGradient& gradient )
713{
714 Q_ASSERT( gradient.type() == m_gradientType );
715
716 if ( gradient.type() == m_gradientType )
717 {
718 switch ( gradient.type() )
719 {
720 case QskGradient::Linear:
721 case QskGradient::Radial:
722 case QskGradient::Conic:
723 {
724 auto material = static_cast< GradientMaterial* >( this );
725 return material->setGradient( gradient.stretchedTo( rect ) );
726 }
727
728 default:
729 qWarning( "Invalid gradient type" );
730 }
731 }
732
733 return false;
734}
735
736QskGradientMaterial* QskGradientMaterial::createMaterial( QskGradient::Type gradientType )
737{
738 switch ( gradientType )
739 {
740 case QskGradient::Linear:
741 return new LinearMaterial();
742
743 case QskGradient::Radial:
744 return new RadialMaterial();
745
746 case QskGradient::Conic:
747 return new ConicMaterial();
748
749 default:
750 return nullptr;
751 }
752}