diff options
-rw-r--r-- | src/gui/opengl/qopenglengineshadermanager.cpp | 102 | ||||
-rw-r--r-- | src/gui/opengl/qopenglengineshadersource_p.h | 517 | ||||
-rw-r--r-- | src/gui/opengl/qopenglpaintengine.cpp | 130 | ||||
-rw-r--r-- | src/gui/opengl/qopenglpaintengine_p.h | 74 | ||||
-rw-r--r-- | src/gui/opengl/qopengltextureglyphcache.cpp | 4 | ||||
-rw-r--r-- | tests/auto/gui/qopengl/tst_qopengl.cpp | 72 | ||||
-rw-r--r-- | tests/auto/opengl/qgl/tst_qgl.cpp | 4 | ||||
-rw-r--r-- | tests/auto/other/lancelot/paintcommands.cpp | 2 | ||||
-rw-r--r-- | tests/auto/other/lancelot/tst_lancelot.cpp | 53 | ||||
-rw-r--r-- | tests/manual/lance/main.cpp | 9 |
10 files changed, 855 insertions, 112 deletions
diff --git a/src/gui/opengl/qopenglengineshadermanager.cpp b/src/gui/opengl/qopenglengineshadermanager.cpp index c7e457b364..dd9e8e9d1e 100644 --- a/src/gui/opengl/qopenglengineshadermanager.cpp +++ b/src/gui/opengl/qopenglengineshadermanager.cpp @@ -126,11 +126,65 @@ QOpenGLEngineSharedShaders::QOpenGLEngineSharedShaders(QOpenGLContext* context) around without having to change the order of the glsl strings. It is hoped this will make future hard-to-find runtime bugs more obvious and generally give more solid code. */ - static bool snippetsPopulated = false; - if (!snippetsPopulated) { - const char** code = qShaderSnippets; // shortcut + // Check if the user has requested an OpenGL 3.2 Core Profile or higher + // and if so use GLSL 1.50 core shaders instead of legacy ones. + const QSurfaceFormat &fmt = context->format(); + const bool isCoreProfile = fmt.profile() == QSurfaceFormat::CoreProfile && fmt.version() >= qMakePair(3,2); + + const char** code = qShaderSnippets; // shortcut + + if (isCoreProfile) { + code[MainVertexShader] = qopenglslMainVertexShader_core; + code[MainWithTexCoordsVertexShader] = qopenglslMainWithTexCoordsVertexShader_core; + code[MainWithTexCoordsAndOpacityVertexShader] = qopenglslMainWithTexCoordsAndOpacityVertexShader_core; + + code[UntransformedPositionVertexShader] = qopenglslUntransformedPositionVertexShader_core; + code[PositionOnlyVertexShader] = qopenglslPositionOnlyVertexShader_core; + code[ComplexGeometryPositionOnlyVertexShader] = qopenglslComplexGeometryPositionOnlyVertexShader_core; + code[PositionWithPatternBrushVertexShader] = qopenglslPositionWithPatternBrushVertexShader_core; + code[PositionWithLinearGradientBrushVertexShader] = qopenglslPositionWithLinearGradientBrushVertexShader_core; + code[PositionWithConicalGradientBrushVertexShader] = qopenglslPositionWithConicalGradientBrushVertexShader_core; + code[PositionWithRadialGradientBrushVertexShader] = qopenglslPositionWithRadialGradientBrushVertexShader_core; + code[PositionWithTextureBrushVertexShader] = qopenglslPositionWithTextureBrushVertexShader_core; + code[AffinePositionWithPatternBrushVertexShader] = qopenglslAffinePositionWithPatternBrushVertexShader_core; + code[AffinePositionWithLinearGradientBrushVertexShader] = qopenglslAffinePositionWithLinearGradientBrushVertexShader_core; + code[AffinePositionWithConicalGradientBrushVertexShader] = qopenglslAffinePositionWithConicalGradientBrushVertexShader_core; + code[AffinePositionWithRadialGradientBrushVertexShader] = qopenglslAffinePositionWithRadialGradientBrushVertexShader_core; + code[AffinePositionWithTextureBrushVertexShader] = qopenglslAffinePositionWithTextureBrushVertexShader_core; + + code[MainFragmentShader_CMO] = qopenglslMainFragmentShader_CMO_core; + code[MainFragmentShader_CM] = qopenglslMainFragmentShader_CM_core; + code[MainFragmentShader_MO] = qopenglslMainFragmentShader_MO_core; + code[MainFragmentShader_M] = qopenglslMainFragmentShader_M_core; + code[MainFragmentShader_CO] = qopenglslMainFragmentShader_CO_core; + code[MainFragmentShader_C] = qopenglslMainFragmentShader_C_core; + code[MainFragmentShader_O] = qopenglslMainFragmentShader_O_core; + code[MainFragmentShader] = qopenglslMainFragmentShader_core; + code[MainFragmentShader_ImageArrays] = qopenglslMainFragmentShader_ImageArrays_core; + + code[ImageSrcFragmentShader] = qopenglslImageSrcFragmentShader_core; + code[ImageSrcWithPatternFragmentShader] = qopenglslImageSrcWithPatternFragmentShader_core; + code[NonPremultipliedImageSrcFragmentShader] = qopenglslNonPremultipliedImageSrcFragmentShader_core; + code[GrayscaleImageSrcFragmentShader] = qopenglslGrayscaleImageSrcFragmentShader_core; + code[AlphaImageSrcFragmentShader] = qopenglslAlphaImageSrcFragmentShader_core; + code[CustomImageSrcFragmentShader] = qopenglslCustomSrcFragmentShader_core; // Calls "customShader", which must be appended + code[SolidBrushSrcFragmentShader] = qopenglslSolidBrushSrcFragmentShader_core; + + code[TextureBrushSrcFragmentShader] = qopenglslTextureBrushSrcFragmentShader_desktop_core; + code[TextureBrushSrcWithPatternFragmentShader] = qopenglslTextureBrushSrcWithPatternFragmentShader_core; + code[PatternBrushSrcFragmentShader] = qopenglslPatternBrushSrcFragmentShader_core; + code[LinearGradientBrushSrcFragmentShader] = qopenglslLinearGradientBrushSrcFragmentShader_core; + code[RadialGradientBrushSrcFragmentShader] = qopenglslRadialGradientBrushSrcFragmentShader_core; + code[ConicalGradientBrushSrcFragmentShader] = qopenglslConicalGradientBrushSrcFragmentShader_core; + code[ShockingPinkSrcFragmentShader] = qopenglslShockingPinkSrcFragmentShader_core; + code[NoMaskFragmentShader] = ""; + code[MaskFragmentShader] = qopenglslMaskFragmentShader_core; + code[RgbMaskFragmentShaderPass1] = qopenglslRgbMaskFragmentShaderPass1_core; + code[RgbMaskFragmentShaderPass2] = qopenglslRgbMaskFragmentShaderPass2_core; + code[RgbMaskWithGammaFragmentShader] = ""; //### + } else { code[MainVertexShader] = qopenglslMainVertexShader; code[MainWithTexCoordsVertexShader] = qopenglslMainWithTexCoordsVertexShader; code[MainWithTexCoordsAndOpacityVertexShader] = qopenglslMainWithTexCoordsAndOpacityVertexShader; @@ -182,31 +236,33 @@ QOpenGLEngineSharedShaders::QOpenGLEngineSharedShaders(QOpenGLContext* context) code[RgbMaskFragmentShaderPass1] = qopenglslRgbMaskFragmentShaderPass1; code[RgbMaskFragmentShaderPass2] = qopenglslRgbMaskFragmentShaderPass2; code[RgbMaskWithGammaFragmentShader] = ""; //### + } - code[NoCompositionModeFragmentShader] = ""; - code[MultiplyCompositionModeFragmentShader] = ""; //### - code[ScreenCompositionModeFragmentShader] = ""; //### - code[OverlayCompositionModeFragmentShader] = ""; //### - code[DarkenCompositionModeFragmentShader] = ""; //### - code[LightenCompositionModeFragmentShader] = ""; //### - code[ColorDodgeCompositionModeFragmentShader] = ""; //### - code[ColorBurnCompositionModeFragmentShader] = ""; //### - code[HardLightCompositionModeFragmentShader] = ""; //### - code[SoftLightCompositionModeFragmentShader] = ""; //### - code[DifferenceCompositionModeFragmentShader] = ""; //### - code[ExclusionCompositionModeFragmentShader] = ""; //### + // These shaders are not implemented yet and therefore are the same + // for all profiles. Implementations should make a version for both + // profiles and put the appropriate lines in the if-statement above. + code[NoCompositionModeFragmentShader] = ""; + code[MultiplyCompositionModeFragmentShader] = ""; //### + code[ScreenCompositionModeFragmentShader] = ""; //### + code[OverlayCompositionModeFragmentShader] = ""; //### + code[DarkenCompositionModeFragmentShader] = ""; //### + code[LightenCompositionModeFragmentShader] = ""; //### + code[ColorDodgeCompositionModeFragmentShader] = ""; //### + code[ColorBurnCompositionModeFragmentShader] = ""; //### + code[HardLightCompositionModeFragmentShader] = ""; //### + code[SoftLightCompositionModeFragmentShader] = ""; //### + code[DifferenceCompositionModeFragmentShader] = ""; //### + code[ExclusionCompositionModeFragmentShader] = ""; //### #if defined(QT_DEBUG) - // Check that all the elements have been filled: - for (int i = 0; i < TotalSnippetCount; ++i) { - if (Q_UNLIKELY(!qShaderSnippets[i])) { - qFatal("Shader snippet for %s (#%d) is missing!", - snippetNameStr(SnippetName(i)).constData(), i); - } + // Check that all the elements have been filled: + for (int i = 0; i < TotalSnippetCount; ++i) { + if (Q_UNLIKELY(!qShaderSnippets[i])) { + qFatal("Shader snippet for %s (#%d) is missing!", + snippetNameStr(SnippetName(i)).constData(), i); } -#endif - snippetsPopulated = true; } +#endif QByteArray vertexSource; QByteArray fragSource; diff --git a/src/gui/opengl/qopenglengineshadersource_p.h b/src/gui/opengl/qopenglengineshadersource_p.h index 1e88ac63b5..a165643839 100644 --- a/src/gui/opengl/qopenglengineshadersource_p.h +++ b/src/gui/opengl/qopenglengineshadersource_p.h @@ -58,7 +58,6 @@ QT_BEGIN_NAMESPACE - static const char* const qopenglslMainVertexShader = "\n\ void setPosition(); \n\ void main(void) \n\ @@ -532,40 +531,498 @@ static const char* const qopenglslRgbMaskFragmentShaderPass2 = "\n\ ExclusionCompositionModeFragmentShader, */ -// OpenGL 3.2 core profile versions of shaders that are used by QOpenGLTextureGlyphCache +/* + OpenGL 3.2+ Core Profile shaders + The following shader snippets are copies of the snippets above + but use the modern GLSL 1.5 keywords. New shaders should make + a snippet for both profiles and add them appropriately in the + shader manager. +*/ +static const char* const qopenglslMainVertexShader_core = + "#version 150 core\n\ + void setPosition(); \n\ + void main(void) \n\ + { \n\ + setPosition(); \n\ + }\n"; + +static const char* const qopenglslMainWithTexCoordsVertexShader_core = + "#version 150 core\n\ + in vec2 textureCoordArray; \n\ + out vec2 textureCoords; \n\ + void setPosition(); \n\ + void main(void) \n\ + { \n\ + setPosition(); \n\ + textureCoords = textureCoordArray; \n\ + }\n"; + +static const char* const qopenglslMainWithTexCoordsAndOpacityVertexShader_core = + "#version 150 core\n\ + in vec2 textureCoordArray; \n\ + in float opacityArray; \n\ + out vec2 textureCoords; \n\ + out float opacity; \n\ + void setPosition(); \n\ + void main(void) \n\ + { \n\ + setPosition(); \n\ + textureCoords = textureCoordArray; \n\ + opacity = opacityArray; \n\ + }\n"; + +// NOTE: We let GL do the perspective correction so texture lookups in the fragment +// shader are also perspective corrected. +static const char* const qopenglslPositionOnlyVertexShader_core = "\n\ + in vec2 vertexCoordsArray; \n\ + in vec3 pmvMatrix1; \n\ + in vec3 pmvMatrix2; \n\ + in vec3 pmvMatrix3; \n\ + void setPosition(void) \n\ + { \n\ + mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\ + vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\ + gl_Position = vec4(transformedPos.xy, 0.0, transformedPos.z); \n\ + }\n"; -static const char* const qopenglslMainWithTexCoordsVertexShader_core = "#version 150 core \n\ - in vec2 textureCoordArray; \n\ - out vec2 textureCoords; \n\ - void setPosition(); \n\ - void main(void) \n\ - { \n\ - setPosition(); \n\ - textureCoords = textureCoordArray; \n\ - }\n"; +static const char* const qopenglslComplexGeometryPositionOnlyVertexShader_core = "\n\ + in vec2 vertexCoordsArray; \n\ + uniform mat3 matrix; \n\ + void setPosition(void) \n\ + { \n\ + gl_Position = vec4(matrix * vec3(vertexCoordsArray, 1), 1);\n\ + } \n"; static const char* const qopenglslUntransformedPositionVertexShader_core = "\n\ - in vec4 vertexCoordsArray; \n\ - void setPosition(void) \n\ - { \n\ - gl_Position = vertexCoordsArray; \n\ - }\n"; - -static const char* const qopenglslMainFragmentShader_core = "#version 150 core \n\ - vec4 srcPixel(); \n\ - out vec4 fragColor; \n\ - void main() \n\ - { \n\ - fragColor = srcPixel(); \n\ - }\n"; + in vec4 vertexCoordsArray; \n\ + void setPosition(void) \n\ + { \n\ + gl_Position = vertexCoordsArray; \n\ + }\n"; + +// Pattern Brush - This assumes the texture size is 8x8 and thus, the inverted size is 0.125 +static const char* const qopenglslPositionWithPatternBrushVertexShader_core = "\n\ + in vec2 vertexCoordsArray; \n\ + in vec3 pmvMatrix1; \n\ + in vec3 pmvMatrix2; \n\ + in vec3 pmvMatrix3; \n\ + out vec2 patternTexCoords; \n\ + uniform vec2 halfViewportSize; \n\ + uniform vec2 invertedTextureSize; \n\ + uniform mat3 brushTransform; \n\ + void setPosition(void) \n\ + { \n\ + mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\ + vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\ + gl_Position.xy = transformedPos.xy / transformedPos.z; \n\ + vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \n\ + vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1.0); \n\ + float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \n\ + gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \n\ + patternTexCoords.xy = (hTexCoords.xy * 0.125) * invertedHTexCoordsZ; \n\ + }\n"; + +static const char* const qopenglslAffinePositionWithPatternBrushVertexShader_core + = qopenglslPositionWithPatternBrushVertexShader_core; + +static const char* const qopenglslPatternBrushSrcFragmentShader_core = "\n\ + in vec2 patternTexCoords;\n\ + uniform sampler2D brushTexture; \n\ + uniform vec4 patternColor; \n\ + vec4 srcPixel() \n\ + { \n\ + return patternColor * (1.0 - texture(brushTexture, patternTexCoords).r); \n\ + }\n"; + + +// Linear Gradient Brush +static const char* const qopenglslPositionWithLinearGradientBrushVertexShader_core = "\n\ + in vec2 vertexCoordsArray; \n\ + in vec3 pmvMatrix1; \n\ + in vec3 pmvMatrix2; \n\ + in vec3 pmvMatrix3; \n\ + out float index; \n\ + uniform vec2 halfViewportSize; \n\ + uniform vec3 linearData; \n\ + uniform mat3 brushTransform; \n\ + void setPosition() \n\ + { \n\ + mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\ + vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\ + gl_Position.xy = transformedPos.xy / transformedPos.z; \n\ + vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \n\ + vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1); \n\ + float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \n\ + gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \n\ + index = (dot(linearData.xy, hTexCoords.xy) * linearData.z) * invertedHTexCoordsZ; \n\ + }\n"; + +static const char* const qopenglslAffinePositionWithLinearGradientBrushVertexShader_core + = qopenglslPositionWithLinearGradientBrushVertexShader_core; + +static const char* const qopenglslLinearGradientBrushSrcFragmentShader_core = "\n\ + uniform sampler2D brushTexture; \n\ + in float index; \n\ + vec4 srcPixel() \n\ + { \n\ + vec2 val = vec2(index, 0.5); \n\ + return texture(brushTexture, val); \n\ + }\n"; + + +// Conical Gradient Brush +static const char* const qopenglslPositionWithConicalGradientBrushVertexShader_core = "\n\ + in vec2 vertexCoordsArray; \n\ + in vec3 pmvMatrix1; \n\ + in vec3 pmvMatrix2; \n\ + in vec3 pmvMatrix3; \n\ + out vec2 A; \n\ + uniform vec2 halfViewportSize; \n\ + uniform mat3 brushTransform; \n\ + void setPosition(void) \n\ + { \n\ + mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\ + vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\ + gl_Position.xy = transformedPos.xy / transformedPos.z; \n\ + vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \n\ + vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1); \n\ + float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \n\ + gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \n\ + A = hTexCoords.xy * invertedHTexCoordsZ; \n\ + }\n"; + +static const char* const qopenglslAffinePositionWithConicalGradientBrushVertexShader_core + = qopenglslPositionWithConicalGradientBrushVertexShader_core; + +static const char* const qopenglslConicalGradientBrushSrcFragmentShader_core = "\n\ + #define INVERSE_2PI 0.1591549430918953358 \n\ + in vec2 A; \n\ + uniform sampler2D brushTexture; \n\ + uniform float angle; \n\ + vec4 srcPixel() \n\ + { \n\ + float t; \n\ + if (abs(A.y) == abs(A.x)) \n\ + t = (atan(-A.y + 0.002, A.x) + angle) * INVERSE_2PI; \n\ + else \n\ + t = (atan(-A.y, A.x) + angle) * INVERSE_2PI; \n\ + return texture(brushTexture, vec2(t - floor(t), 0.5)); \n\ + }\n"; + + +// Radial Gradient Brush +static const char* const qopenglslPositionWithRadialGradientBrushVertexShader_core = "\n\ + in vec2 vertexCoordsArray;\n\ + in vec3 pmvMatrix1; \n\ + in vec3 pmvMatrix2; \n\ + in vec3 pmvMatrix3; \n\ + out float b; \n\ + out vec2 A; \n\ + uniform vec2 halfViewportSize; \n\ + uniform mat3 brushTransform; \n\ + uniform vec2 fmp; \n\ + uniform vec3 bradius; \n\ + void setPosition(void) \n\ + {\n\ + mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\ + vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\ + gl_Position.xy = transformedPos.xy / transformedPos.z; \n\ + vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \n\ + vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1); \n\ + float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \n\ + gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \n\ + A = hTexCoords.xy * invertedHTexCoordsZ; \n\ + b = bradius.x + 2.0 * dot(A, fmp); \n\ + }\n"; + +static const char* const qopenglslAffinePositionWithRadialGradientBrushVertexShader_core + = qopenglslPositionWithRadialGradientBrushVertexShader_core; + +static const char* const qopenglslRadialGradientBrushSrcFragmentShader_core = "\n\ + in float b; \n\ + in vec2 A; \n\ + uniform sampler2D brushTexture; \n\ + uniform float fmp2_m_radius2; \n\ + uniform float inverse_2_fmp2_m_radius2; \n\ + uniform float sqrfr; \n\ + uniform vec3 bradius; \n\ + \n\ + vec4 srcPixel() \n\ + { \n\ + float c = sqrfr-dot(A, A); \n\ + float det = b*b - 4.0*fmp2_m_radius2*c; \n\ + vec4 result = vec4(0.0); \n\ + if (det >= 0.0) { \n\ + float detSqrt = sqrt(det); \n\ + float w = max((-b - detSqrt) * inverse_2_fmp2_m_radius2, (-b + detSqrt) * inverse_2_fmp2_m_radius2); \n\ + if (bradius.y + w * bradius.z >= 0.0) \n\ + result = texture(brushTexture, vec2(w, 0.5)); \n\ + } \n\ + return result; \n\ + }\n"; + + +// Texture Brush +static const char* const qopenglslPositionWithTextureBrushVertexShader_core = "\n\ + in vec2 vertexCoordsArray; \n\ + in vec3 pmvMatrix1; \n\ + in vec3 pmvMatrix2; \n\ + in vec3 pmvMatrix3; \n\ + out vec2 brushTextureCoords; \n\ + uniform vec2 halfViewportSize; \n\ + uniform vec2 invertedTextureSize; \n\ + uniform mat3 brushTransform; \n\ + \n\ + void setPosition(void) \n\ + { \n\ + mat3 pmvMatrix = mat3(pmvMatrix1, pmvMatrix2, pmvMatrix3); \n\ + vec3 transformedPos = pmvMatrix * vec3(vertexCoordsArray.xy, 1.0); \n\ + gl_Position.xy = transformedPos.xy / transformedPos.z; \n\ + vec2 viewportCoords = (gl_Position.xy + 1.0) * halfViewportSize; \n\ + vec3 hTexCoords = brushTransform * vec3(viewportCoords, 1); \n\ + float invertedHTexCoordsZ = 1.0 / hTexCoords.z; \n\ + gl_Position = vec4(gl_Position.xy * invertedHTexCoordsZ, 0.0, invertedHTexCoordsZ); \n\ + brushTextureCoords.xy = (hTexCoords.xy * invertedTextureSize) * gl_Position.w; \n\ + }\n"; + +static const char* const qopenglslAffinePositionWithTextureBrushVertexShader_core + = qopenglslPositionWithTextureBrushVertexShader_core; + +static const char* const qopenglslTextureBrushSrcFragmentShader_desktop_core = "\n\ + in vec2 brushTextureCoords; \n\ + uniform sampler2D brushTexture; \n\ + vec4 srcPixel() \n\ + { \n\ + return texture(brushTexture, brushTextureCoords); \n\ + }\n"; + +static const char* const qopenglslTextureBrushSrcWithPatternFragmentShader_core = "\n\ + in vec2 brushTextureCoords; \n\ + uniform vec4 patternColor; \n\ + uniform sampler2D brushTexture; \n\ + vec4 srcPixel() \n\ + { \n\ + return patternColor * (1.0 - texture(brushTexture, brushTextureCoords).r); \n\ + }\n"; + +// Solid Fill Brush +static const char* const qopenglslSolidBrushSrcFragmentShader_core = "\n\ + uniform vec4 fragmentColor; \n\ + vec4 srcPixel() \n\ + { \n\ + return fragmentColor; \n\ + }\n"; static const char* const qopenglslImageSrcFragmentShader_core = "\n\ - in vec2 textureCoords; \n\ - uniform sampler2D imageTexture; \n\ - vec4 srcPixel() \n\ - { \n" - "return texture(imageTexture, textureCoords); \n" - "}\n"; + in vec2 textureCoords; \n\ + uniform sampler2D imageTexture; \n\ + vec4 srcPixel() \n\ + { \n\ + return texture(imageTexture, textureCoords); \n\ + }\n"; + +static const char* const qopenglslCustomSrcFragmentShader_core = "\n\ + in vec2 textureCoords; \n\ + uniform sampler2D imageTexture; \n\ + vec4 srcPixel() \n\ + { \n\ + return customShader(imageTexture, textureCoords); \n\ + }\n"; + +static const char* const qopenglslImageSrcWithPatternFragmentShader_core = "\n\ + in vec2 textureCoords; \n\ + uniform vec4 patternColor; \n\ + uniform sampler2D imageTexture; \n\ + vec4 srcPixel() \n\ + { \n\ + return patternColor * (1.0 - texture(imageTexture, textureCoords).r); \n\ + }\n"; + +static const char* const qopenglslNonPremultipliedImageSrcFragmentShader_core = "\n\ + in vec2 textureCoords; \n\ + uniform sampler2D imageTexture; \n\ + vec4 srcPixel() \n\ + { \n\ + vec4 sample = texture(imageTexture, textureCoords); \n\ + sample.rgb = sample.rgb * sample.a; \n\ + return sample; \n\ + }\n"; + +static const char* const qopenglslGrayscaleImageSrcFragmentShader_core = "\n\ + in vec2 textureCoords; \n\ + uniform sampler2D imageTexture; \n\ + vec4 srcPixel() \n\ + { \n\ + return texture(imageTexture, textureCoords).rrra; \n\ + }\n"; + +static const char* const qopenglslAlphaImageSrcFragmentShader_core = "\n\ + in vec2 textureCoords; \n\ + uniform sampler2D imageTexture; \n\ + vec4 srcPixel() \n\ + { \n\ + return vec4(0, 0, 0, texture(imageTexture, textureCoords).r); \n\ + }\n"; + +static const char* const qopenglslShockingPinkSrcFragmentShader_core = "\n\ + vec4 srcPixel() \n\ + { \n\ + return vec4(0.98, 0.06, 0.75, 1.0); \n\ + }\n"; + +static const char* const qopenglslMainFragmentShader_ImageArrays_core = + "#version 150 core\n\ + in float opacity; \n\ + out vec4 fragColor; \n\ + vec4 srcPixel(); \n\ + void main() \n\ + { \n\ + fragColor = srcPixel() * opacity; \n\ + }\n"; + +static const char* const qopenglslMainFragmentShader_CMO_core = + "#version 150 core\n\ + out vec4 fragColor; \n\ + uniform float globalOpacity; \n\ + vec4 srcPixel(); \n\ + vec4 applyMask(vec4); \n\ + vec4 compose(vec4); \n\ + void main() \n\ + { \n\ + fragColor = applyMask(compose(srcPixel()*globalOpacity))); \n\ + }\n"; + +static const char* const qopenglslMainFragmentShader_CM_core = + "#version 150 core\n\ + out vec4 fragColor; \n\ + vec4 srcPixel(); \n\ + vec4 applyMask(vec4); \n\ + vec4 compose(vec4); \n\ + void main() \n\ + { \n\ + fragColor = applyMask(compose(srcPixel())); \n\ + }\n"; + +static const char* const qopenglslMainFragmentShader_MO_core = + "#version 150 core\n\ + out vec4 fragColor; \n\ + uniform float globalOpacity; \n\ + vec4 srcPixel(); \n\ + vec4 applyMask(vec4); \n\ + void main() \n\ + { \n\ + fragColor = applyMask(srcPixel()*globalOpacity); \n\ + }\n"; + +static const char* const qopenglslMainFragmentShader_M_core = + "#version 150 core\n\ + out vec4 fragColor; \n\ + vec4 srcPixel(); \n\ + vec4 applyMask(vec4); \n\ + void main() \n\ + { \n\ + fragColor = applyMask(srcPixel()); \n\ + }\n"; + +static const char* const qopenglslMainFragmentShader_CO_core = + "#version 150 core\n\ + out vec4 fragColor; \n\ + uniform float globalOpacity; \n\ + vec4 srcPixel(); \n\ + vec4 compose(vec4); \n\ + void main() \n\ + { \n\ + fragColor = compose(srcPixel()*globalOpacity); \n\ + }\n"; + +static const char* const qopenglslMainFragmentShader_C_core = + "#version 150 core\n\ + out vec4 fragColor; \n\ + vec4 srcPixel(); \n\ + vec4 compose(vec4); \n\ + void main() \n\ + { \n\ + fragColor = compose(srcPixel()); \n\ + }\n"; + +static const char* const qopenglslMainFragmentShader_O_core = + "#version 150 core\n\ + out vec4 fragColor; \n\ + uniform float globalOpacity; \n\ + vec4 srcPixel(); \n\ + void main() \n\ + { \n\ + fragColor = srcPixel()*globalOpacity; \n\ + }\n"; + +static const char* const qopenglslMainFragmentShader_core = + "#version 150 core\n\ + out vec4 fragColor; \n\ + vec4 srcPixel(); \n\ + void main() \n\ + { \n\ + fragColor = srcPixel(); \n\ + }\n"; + +static const char* const qopenglslMaskFragmentShader_core = "\n\ + in vec2 textureCoords;\n\ + uniform sampler2D maskTexture;\n\ + vec4 applyMask(vec4 src) \n\ + {\n\ + vec4 mask = texture(maskTexture, textureCoords); \n\ + return src * mask.r; \n\ + }\n"; + +// For source over with subpixel antialiasing, the final color is calculated per component as follows +// (.a is alpha component, .c is red, green or blue component): +// alpha = src.a * mask.c * opacity +// dest.c = dest.c * (1 - alpha) + src.c * alpha +// +// In the first pass, calculate: dest.c = dest.c * (1 - alpha) with blend funcs: zero, 1 - source color +// In the second pass, calculate: dest.c = dest.c + src.c * alpha with blend funcs: one, one +// +// If source is a solid color (src is constant), only the first pass is needed, with blend funcs: constant, 1 - source color + +// For source composition with subpixel antialiasing, the final color is calculated per component as follows: +// alpha = src.a * mask.c * opacity +// dest.c = dest.c * (1 - mask.c) + src.c * alpha +// + +static const char* const qopenglslRgbMaskFragmentShaderPass1_core = "\n\ + in vec2 textureCoords;\n\ + uniform sampler2D maskTexture;\n\ + vec4 applyMask(vec4 src) \n\ + { \n\ + vec4 mask = texture(maskTexture, textureCoords); \n\ + return src.a * mask; \n\ + }\n"; + +static const char* const qopenglslRgbMaskFragmentShaderPass2_core = "\n\ + in vec2 textureCoords;\n\ + uniform sampler2D maskTexture;\n\ + vec4 applyMask(vec4 src) \n\ + { \n\ + vec4 mask = texture(maskTexture, textureCoords); \n\ + return src * mask; \n\ + }\n"; + +/* + Left to implement: + RgbMaskFragmentShader_core, + RgbMaskWithGammaFragmentShader_core, + + MultiplyCompositionModeFragmentShader_core, + ScreenCompositionModeFragmentShader_core, + OverlayCompositionModeFragmentShader_core, + DarkenCompositionModeFragmentShader_core, + LightenCompositionModeFragmentShader_core, + ColorDodgeCompositionModeFragmentShader_core, + ColorBurnCompositionModeFragmentShader_core, + HardLightCompositionModeFragmentShader_core, + SoftLightCompositionModeFragmentShader_core, + DifferenceCompositionModeFragmentShader_core, + ExclusionCompositionModeFragmentShader_core, +*/ QT_END_NAMESPACE diff --git a/src/gui/opengl/qopenglpaintengine.cpp b/src/gui/opengl/qopenglpaintengine.cpp index 8db806f1d5..f6bae32d54 100644 --- a/src/gui/opengl/qopenglpaintengine.cpp +++ b/src/gui/opengl/qopenglpaintengine.cpp @@ -99,6 +99,12 @@ QOpenGL2PaintEngineExPrivate::~QOpenGL2PaintEngineExPrivate() { delete shaderManager; + vertexBuffer.destroy(); + texCoordBuffer.destroy(); + opacityBuffer.destroy(); + indexBuffer.destroy(); + vao.destroy(); + if (elementIndicesVBOId != 0) { funcs.glDeleteBuffers(1, &elementIndicesVBOId); elementIndicesVBOId = 0; @@ -578,6 +584,12 @@ void QOpenGL2PaintEngineExPrivate::drawTexture(const QOpenGLRect& dest, const QO setCoords(staticVertexCoordinateArray, dest); setCoords(staticTextureCoordinateArray, srcTextureRect); + setVertexAttribArrayEnabled(QT_VERTEX_COORDS_ATTR, true); + setVertexAttribArrayEnabled(QT_TEXTURE_COORDS_ATTR, true); + + uploadData(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray, 8); + uploadData(QT_TEXTURE_COORDS_ATTR, staticTextureCoordinateArray, 8); + funcs.glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } @@ -664,6 +676,11 @@ void QOpenGL2PaintEngineExPrivate::resetGLState() float color[] = { 1.0f, 1.0f, 1.0f, 1.0f }; funcs.glVertexAttrib4fv(3, color); } + if (vao.isCreated()) { + vao.release(); + funcs.glBindBuffer(GL_ARRAY_BUFFER, 0); + funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); + } } void QOpenGL2PaintEngineEx::endNativePainting() @@ -696,16 +713,16 @@ void QOpenGL2PaintEngineExPrivate::transferMode(EngineMode newMode) } if (newMode == ImageDrawingMode) { - setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray); - setVertexAttributePointer(QT_TEXTURE_COORDS_ATTR, staticTextureCoordinateArray); + uploadData(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray, 8); + uploadData(QT_TEXTURE_COORDS_ATTR, staticTextureCoordinateArray, 8); } if (newMode == ImageArrayDrawingMode || newMode == ImageOpacityArrayDrawingMode) { - setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, (GLfloat*)vertexCoordinateArray.data()); - setVertexAttributePointer(QT_TEXTURE_COORDS_ATTR, (GLfloat*)textureCoordinateArray.data()); + uploadData(QT_VERTEX_COORDS_ATTR, (GLfloat*)vertexCoordinateArray.data(), vertexCoordinateArray.vertexCount() * 2); + uploadData(QT_TEXTURE_COORDS_ATTR, (GLfloat*)textureCoordinateArray.data(), textureCoordinateArray.vertexCount() * 2); if (newMode == ImageOpacityArrayDrawingMode) - setVertexAttributePointer(QT_OPACITY_ATTR, (GLfloat*)opacityArray.data()); + uploadData(QT_OPACITY_ATTR, (GLfloat*)opacityArray.data(), opacityArray.size()); } // This needs to change when we implement high-quality anti-aliasing... @@ -826,9 +843,10 @@ void QOpenGL2PaintEngineExPrivate::fill(const QVectorPath& path) prepareForDraw(currentBrush.isOpaque()); #ifdef QT_OPENGL_CACHE_AS_VBOS funcs.glBindBuffer(GL_ARRAY_BUFFER, cache->vbo); + uploadData(QT_VERTEX_COORD_ATTR, 0, cache->vertexCount); setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, 0); #else - setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, cache->vertices); + uploadData(QT_VERTEX_COORDS_ATTR, cache->vertices, cache->vertexCount * 2); #endif funcs.glDrawArrays(cache->primitiveType, 0, cache->vertexCount); @@ -922,6 +940,7 @@ void QOpenGL2PaintEngineExPrivate::fill(const QVectorPath& path) #ifdef QT_OPENGL_CACHE_AS_VBOS funcs.glBindBuffer(GL_ARRAY_BUFFER, cache->vbo); funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, cache->ibo); + uploadData(QT_VERTEX_COORDS_ATTR, 0, cache->vertexCount); setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, 0); if (cache->indexType == QVertexIndexVector::UnsignedInt) funcs.glDrawElements(cache->primitiveType, cache->indexCount, GL_UNSIGNED_INT, 0); @@ -930,11 +949,10 @@ void QOpenGL2PaintEngineExPrivate::fill(const QVectorPath& path) funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); funcs.glBindBuffer(GL_ARRAY_BUFFER, 0); #else - setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, cache->vertices); - if (cache->indexType == QVertexIndexVector::UnsignedInt) - funcs.glDrawElements(cache->primitiveType, cache->indexCount, GL_UNSIGNED_INT, (qint32 *)cache->indices); - else - funcs.glDrawElements(cache->primitiveType, cache->indexCount, GL_UNSIGNED_SHORT, (qint16 *)cache->indices); + uploadData(QT_VERTEX_COORDS_ATTR, cache->vertices, cache->vertexCount * 2); + const GLenum indexValueType = cache->indexType == QVertexIndexVector::UnsignedInt ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT; + const bool useIndexVbo = uploadIndexData(cache->indices, indexValueType, cache->indexCount); + funcs.glDrawElements(cache->primitiveType, cache->indexCount, indexValueType, useIndexVbo ? nullptr : cache->indices); #endif } else { @@ -959,11 +977,10 @@ void QOpenGL2PaintEngineExPrivate::fill(const QVectorPath& path) vertices[i] = float(inverseScale * polys.vertices.at(i)); prepareForDraw(currentBrush.isOpaque()); - setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, vertices.constData()); - if (funcs.hasOpenGLExtension(QOpenGLExtensions::ElementIndexUint)) - funcs.glDrawElements(GL_TRIANGLES, polys.indices.size(), GL_UNSIGNED_INT, polys.indices.data()); - else - funcs.glDrawElements(GL_TRIANGLES, polys.indices.size(), GL_UNSIGNED_SHORT, polys.indices.data()); + uploadData(QT_VERTEX_COORDS_ATTR, vertices.constData(), vertices.size()); + const GLenum indexValueType = funcs.hasOpenGLExtension(QOpenGLExtensions::ElementIndexUint) ? GL_UNSIGNED_INT : GL_UNSIGNED_SHORT; + const bool useIndexVbo = uploadIndexData(polys.indices.data(), indexValueType, polys.indices.size()); + funcs.glDrawElements(GL_TRIANGLES, polys.indices.size(), indexValueType, useIndexVbo ? nullptr : polys.indices.data()); } else { // We can't handle big, concave painter paths with OpenGL without stencil buffer. qWarning("Painter path exceeds +/-32767 pixels."); @@ -1085,7 +1102,8 @@ void QOpenGL2PaintEngineExPrivate::fillStencilWithVertexArray(const float *data, } else { funcs.glStencilFunc(GL_ALWAYS, GL_STENCIL_HIGH_BIT, 0xff); } - setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, data); + + uploadData(QT_VERTEX_COORDS_ATTR, data, count * 2); funcs.glDrawArrays(GL_TRIANGLE_STRIP, 0, count); #endif } @@ -1215,7 +1233,8 @@ bool QOpenGL2PaintEngineExPrivate::prepareForDraw(bool srcPixelsAreOpaque) void QOpenGL2PaintEngineExPrivate::composite(const QOpenGLRect& boundingRect) { setCoords(staticVertexCoordinateArray, boundingRect); - setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray); + + uploadData(QT_VERTEX_COORDS_ATTR, staticVertexCoordinateArray, 8); funcs.glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } @@ -1224,16 +1243,12 @@ void QOpenGL2PaintEngineExPrivate::drawVertexArrays(const float *data, int *stop GLenum primitive) { // Now setup the pointer to the vertex array: - setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, data); + uploadData(QT_VERTEX_COORDS_ATTR, data, stops[stopCount-1] * 2); int previousStop = 0; for (int i=0; i<stopCount; ++i) { int stop = stops[i]; -/* - qDebug("Drawing triangle fan for vertecies %d -> %d:", previousStop, stop-1); - for (int i=previousStop; i<stop; ++i) - qDebug(" %02d: [%.2f, %.2f]", i, vertexArray.data()[i].x, vertexArray.data()[i].y); -*/ + funcs.glDrawArrays(primitive, previousStop, stop - previousStop); previousStop = stop; } @@ -1325,14 +1340,9 @@ void QOpenGL2PaintEngineExPrivate::stroke(const QVectorPath &path, const QPen &p if (opaque) { prepareForDraw(opaque); - setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, stroker.vertices()); - funcs.glDrawArrays(GL_TRIANGLE_STRIP, 0, stroker.vertexCount() / 2); - -// QBrush b(Qt::green); -// d->setBrush(&b); -// d->prepareForDraw(true); -// glDrawArrays(GL_LINE_STRIP, 0, d->stroker.vertexCount() / 2); + uploadData(QT_VERTEX_COORDS_ATTR, stroker.vertices(), stroker.vertexCount()); + funcs.glDrawArrays(GL_TRIANGLE_STRIP, 0, stroker.vertexCount() / 2); } else { qreal width = qpen_widthf(pen) / 2; if (width == 0) @@ -1841,8 +1851,8 @@ void QOpenGL2PaintEngineExPrivate::drawCachedGlyphs(QFontEngine::GlyphFormat gly } if (glyphFormat != QFontEngine::Format_ARGB || recreateVertexArrays) { - setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, (GLfloat*)vertexCoordinates->data()); - setVertexAttributePointer(QT_TEXTURE_COORDS_ATTR, (GLfloat*)textureCoordinates->data()); + uploadData(QT_VERTEX_COORDS_ATTR, (GLfloat*)vertexCoordinates->data(), vertexCoordinates->vertexCount() * 2); + uploadData(QT_TEXTURE_COORDS_ATTR, (GLfloat*)textureCoordinates->data(), textureCoordinates->vertexCount() * 2); } if (!snapToPixelGrid) { @@ -1906,7 +1916,8 @@ void QOpenGL2PaintEngineExPrivate::drawCachedGlyphs(QFontEngine::GlyphFormat gly #if defined(QT_OPENGL_DRAWCACHEDGLYPHS_INDEX_ARRAY_VBO) funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, 0); #else - funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, elementIndices.data()); + const bool useIndexVbo = uploadIndexData(elementIndices.data(), GL_UNSIGNED_SHORT, 6 * numGlyphs); + funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, useIndexVbo ? nullptr : elementIndices.data()); #endif shaderManager->setMaskType(QOpenGLEngineShaderManager::SubPixelMaskPass2); @@ -1957,7 +1968,8 @@ void QOpenGL2PaintEngineExPrivate::drawCachedGlyphs(QFontEngine::GlyphFormat gly funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, 0); funcs.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); #else - funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, elementIndices.data()); + const bool useIndexVbo = uploadIndexData(elementIndices.data(), GL_UNSIGNED_SHORT, 6 * numGlyphs); + funcs.glDrawElements(GL_TRIANGLE_STRIP, 6 * numGlyphs, GL_UNSIGNED_SHORT, useIndexVbo ? nullptr : elementIndices.data()); #endif } @@ -2076,6 +2088,15 @@ bool QOpenGL2PaintEngineEx::begin(QPaintDevice *pdev) return false; } + if (d->ctx != QOpenGLContext::currentContext() + || (d->ctx && QOpenGLContext::currentContext() && d->ctx->format() != QOpenGLContext::currentContext()->format())) { + d->vertexBuffer.destroy(); + d->texCoordBuffer.destroy(); + d->opacityBuffer.destroy(); + d->indexBuffer.destroy(); + d->vao.destroy(); + } + d->ctx = QOpenGLContext::currentContext(); d->ctx->d_func()->active_engine = this; @@ -2083,6 +2104,42 @@ bool QOpenGL2PaintEngineEx::begin(QPaintDevice *pdev) d->funcs.initializeOpenGLFunctions(); + // Generate a new Vertex Array Object if we don't have one already. We can + // only hit the VAO-based path when using a core profile context. This is + // because while non-core contexts can support VAOs via extensions, legacy + // components like the QtOpenGL module do not know about VAOs. There are + // still tests for QGL-QOpenGL paint engine interoperability, so keep the + // status quo for now, and avoid introducing a VAO in non-core contexts. + const bool needsVAO = d->ctx->format().profile() == QSurfaceFormat::CoreProfile + && d->ctx->format().version() >= qMakePair(3, 2); + if (needsVAO && !d->vao.isCreated()) { + bool created = d->vao.create(); + + // If we managed to create it then we have a profile that supports VAOs + if (created) { + d->vao.bind(); + + // Generate a new Vertex Buffer Object if we don't have one already + if (!d->vertexBuffer.isCreated()) { + d->vertexBuffer.create(); + // Set its usage to StreamDraw, we will use this buffer only a few times before refilling it + d->vertexBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); + } + if (!d->texCoordBuffer.isCreated()) { + d->texCoordBuffer.create(); + d->texCoordBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); + } + if (!d->opacityBuffer.isCreated()) { + d->opacityBuffer.create(); + d->opacityBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); + } + if (!d->indexBuffer.isCreated()) { + d->indexBuffer.create(); + d->indexBuffer.setUsagePattern(QOpenGLBuffer::StreamDraw); + } + } + } + for (int i = 0; i < QT_GL_VERTEX_ARRAY_TRACKED_COUNT; ++i) d->vertexAttributeArraysEnabledState[i] = false; @@ -2164,6 +2221,9 @@ void QOpenGL2PaintEngineEx::ensureActive() Q_D(QOpenGL2PaintEngineEx); QOpenGLContext *ctx = d->ctx; + if (d->vao.isCreated()) + d->vao.bind(); + if (isActive() && ctx->d_func()->active_engine != this) { ctx->d_func()->active_engine = this; d->needsSync = true; diff --git a/src/gui/opengl/qopenglpaintengine_p.h b/src/gui/opengl/qopenglpaintengine_p.h index 807efb1ec2..679b3c0557 100644 --- a/src/gui/opengl/qopenglpaintengine_p.h +++ b/src/gui/opengl/qopenglpaintengine_p.h @@ -65,6 +65,9 @@ #include <private/qopenglextensions_p.h> +#include <QOpenGLVertexArrayObject> +#include <QOpenGLBuffer> + enum EngineMode { ImageDrawingMode, TextDrawingMode, @@ -193,7 +196,11 @@ public: snapToPixelGrid(false), nativePaintingActive(false), inverseScale(1), - lastTextureUnitUsed(QT_UNKNOWN_TEXTURE_UNIT) + lastTextureUnitUsed(QT_UNKNOWN_TEXTURE_UNIT), + vertexBuffer(QOpenGLBuffer::VertexBuffer), + texCoordBuffer(QOpenGLBuffer::VertexBuffer), + opacityBuffer(QOpenGLBuffer::VertexBuffer), + indexBuffer(QOpenGLBuffer::IndexBuffer) { } ~QOpenGL2PaintEngineExPrivate(); @@ -222,7 +229,8 @@ public: void drawCachedGlyphs(QFontEngine::GlyphFormat glyphFormat, QStaticTextItem *staticTextItem); // Calls glVertexAttributePointer if the pointer has changed - inline void setVertexAttributePointer(unsigned int arrayIndex, const GLfloat *pointer); + inline void uploadData(unsigned int arrayIndex, const GLfloat *data, GLuint count); + inline bool uploadIndexData(const void *data, GLenum indexValueType, GLuint count); // draws whatever is in the vertex array: void drawVertexArrays(const float *data, int *stops, int stopCount, GLenum primitive); @@ -313,6 +321,12 @@ public: GLenum lastTextureUnitUsed; GLuint lastTextureUsed; + QOpenGLVertexArrayObject vao; + QOpenGLBuffer vertexBuffer; + QOpenGLBuffer texCoordBuffer; + QOpenGLBuffer opacityBuffer; + QOpenGLBuffer indexBuffer; + bool needsSync; bool multisamplingAlwaysEnabled; @@ -326,17 +340,55 @@ public: }; -void QOpenGL2PaintEngineExPrivate::setVertexAttributePointer(unsigned int arrayIndex, const GLfloat *pointer) +void QOpenGL2PaintEngineExPrivate::uploadData(unsigned int arrayIndex, const GLfloat *data, GLuint count) { Q_ASSERT(arrayIndex < 3); - if (pointer == vertexAttribPointers[arrayIndex]) - return; - - vertexAttribPointers[arrayIndex] = pointer; - if (arrayIndex == QT_OPACITY_ATTR) - funcs.glVertexAttribPointer(arrayIndex, 1, GL_FLOAT, GL_FALSE, 0, pointer); - else - funcs.glVertexAttribPointer(arrayIndex, 2, GL_FLOAT, GL_FALSE, 0, pointer); + + // If a vertex array object is created we have a profile that supports them + // and we will upload the data via a QOpenGLBuffer. Otherwise we will use + // the legacy way of uploading the data via glVertexAttribPointer. + if (vao.isCreated()) { + if (arrayIndex == QT_VERTEX_COORDS_ATTR) { + vertexBuffer.bind(); + vertexBuffer.allocate(data, count * sizeof(float)); + } + if (arrayIndex == QT_TEXTURE_COORDS_ATTR) { + texCoordBuffer.bind(); + texCoordBuffer.allocate(data, count * sizeof(float)); + } + if (arrayIndex == QT_OPACITY_ATTR) { + opacityBuffer.bind(); + opacityBuffer.allocate(data, count * sizeof(float)); + } + if (arrayIndex == QT_OPACITY_ATTR) + funcs.glVertexAttribPointer(arrayIndex, 1, GL_FLOAT, GL_FALSE, 0, 0); + else + funcs.glVertexAttribPointer(arrayIndex, 2, GL_FLOAT, GL_FALSE, 0, 0); + } else { + // If we already uploaded the data we don't have to do it again + if (data == vertexAttribPointers[arrayIndex]) + return; + + // Store the data in cache and upload it to the graphics card. + vertexAttribPointers[arrayIndex] = data; + if (arrayIndex == QT_OPACITY_ATTR) + funcs.glVertexAttribPointer(arrayIndex, 1, GL_FLOAT, GL_FALSE, 0, data); + else + funcs.glVertexAttribPointer(arrayIndex, 2, GL_FLOAT, GL_FALSE, 0, data); + } +} + +bool QOpenGL2PaintEngineExPrivate::uploadIndexData(const void *data, GLenum indexValueType, GLuint count) +{ + // Follow the uploadData() logic: VBOs are used only when VAO support is available. + // Otherwise the legacy client-side pointer path is used. + if (vao.isCreated()) { + Q_ASSERT(indexValueType == GL_UNSIGNED_SHORT || indexValueType == GL_UNSIGNED_INT); + indexBuffer.bind(); + indexBuffer.allocate(data, count * (indexValueType == GL_UNSIGNED_SHORT ? sizeof(quint16) : sizeof(quint32))); + return true; + } + return false; } QT_END_NAMESPACE diff --git a/src/gui/opengl/qopengltextureglyphcache.cpp b/src/gui/opengl/qopengltextureglyphcache.cpp index afd5004cec..62b069a1d0 100644 --- a/src/gui/opengl/qopengltextureglyphcache.cpp +++ b/src/gui/opengl/qopengltextureglyphcache.cpp @@ -372,8 +372,8 @@ void QOpenGLTextureGlyphCache::resizeTextureData(int width, int height) blitProgram = m_blitProgram; } else { - pex->setVertexAttributePointer(QT_VERTEX_COORDS_ATTR, m_vertexCoordinateArray); - pex->setVertexAttributePointer(QT_TEXTURE_COORDS_ATTR, m_textureCoordinateArray); + pex->uploadData(QT_VERTEX_COORDS_ATTR, m_vertexCoordinateArray, 8); + pex->uploadData(QT_TEXTURE_COORDS_ATTR, m_textureCoordinateArray, 8); pex->shaderManager->useBlitProgram(); blitProgram = pex->shaderManager->blitProgram(); diff --git a/tests/auto/gui/qopengl/tst_qopengl.cpp b/tests/auto/gui/qopengl/tst_qopengl.cpp index 44921f68aa..7451ef92ee 100644 --- a/tests/auto/gui/qopengl/tst_qopengl.cpp +++ b/tests/auto/gui/qopengl/tst_qopengl.cpp @@ -86,6 +86,7 @@ private slots: void fboMRT_differentFormats(); void openGLPaintDevice_data(); void openGLPaintDevice(); + void openGLPaintDeviceWithChangingContext(); void aboutToBeDestroyed(); void sizeLessWindow(); void QTBUG15621_triangulatingStrokerDivZero(); @@ -948,6 +949,14 @@ void tst_QOpenGL::openGLPaintDevice_data() QTest::newRow("Using QOffscreenSurface - RGB16") << int(QSurface::Offscreen) << QImage::Format_RGB16; } +static void drawColoredRects(QPainter *p, const QSize &size) +{ + p->fillRect(0, 0, size.width() / 2, size.height() / 2, Qt::red); + p->fillRect(size.width() / 2, 0, size.width() / 2, size.height() / 2, Qt::green); + p->fillRect(size.width() / 2, size.height() / 2, size.width() / 2, size.height() / 2, Qt::blue); + p->fillRect(0, size.height() / 2, size.width() / 2, size.height() / 2, Qt::white); +} + void tst_QOpenGL::openGLPaintDevice() { #if defined(Q_OS_LINUX) && defined(Q_CC_GNU) && !defined(__x86_64__) @@ -970,10 +979,7 @@ void tst_QOpenGL::openGLPaintDevice() QImage image(size, imageFormat); QPainter p(&image); - p.fillRect(0, 0, image.width() / 2, image.height() / 2, Qt::red); - p.fillRect(image.width() / 2, 0, image.width() / 2, image.height() / 2, Qt::green); - p.fillRect(image.width() / 2, image.height() / 2, image.width() / 2, image.height() / 2, Qt::blue); - p.fillRect(0, image.height() / 2, image.width() / 2, image.height() / 2, Qt::white); + drawColoredRects(&p, image.size()); p.end(); QOpenGLFramebufferObject fbo(size); @@ -981,10 +987,7 @@ void tst_QOpenGL::openGLPaintDevice() QOpenGLPaintDevice device(size); QVERIFY(p.begin(&device)); - p.fillRect(0, 0, image.width() / 2, image.height() / 2, Qt::red); - p.fillRect(image.width() / 2, 0, image.width() / 2, image.height() / 2, Qt::green); - p.fillRect(image.width() / 2, image.height() / 2, image.width() / 2, image.height() / 2, Qt::blue); - p.fillRect(0, image.height() / 2, image.width() / 2, image.height() / 2, Qt::white); + drawColoredRects(&p, image.size()); p.end(); QImage actual = fbo.toImage().convertToFormat(imageFormat); @@ -1010,6 +1013,59 @@ void tst_QOpenGL::openGLPaintDevice() QCOMPARE(image, actual); } +void tst_QOpenGL::openGLPaintDeviceWithChangingContext() +{ + QScopedPointer<QSurface> surface(createSurface(QSurface::Window)); + const QSize size(512, 512); + + // QOpenGLPaintDevice has a thread-local paint engine. Therefore render + // twice, with a different context and device. Under the hood it will + // still use the same paint engine! + + QOpenGLContext ctx; + QVERIFY(ctx.create()); + QVERIFY(ctx.makeCurrent(surface.data())); + + QOpenGLFramebufferObject fbo(size); + QVERIFY(fbo.bind()); + + QOpenGLPaintDevice device(size); + + QPainter p; + QVERIFY(p.begin(&device)); + drawColoredRects(&p, size); + p.end(); + + QImage img1 = fbo.toImage(); + + QOpenGLContext ctx2; + // When supported, test the special case, where the second context is + // totally incompatible due to being a core profile one. + QSurfaceFormat coreFormat; + coreFormat.setVersion(3, 2); + coreFormat.setProfile(QSurfaceFormat::CoreProfile); + ctx2.setFormat(coreFormat); + if (!ctx2.create() || !ctx2.makeCurrent(surface.data())) { + ctx2.setFormat(QSurfaceFormat()); + QVERIFY(ctx2.create()); + } + + QVERIFY(ctx2.makeCurrent(surface.data())); + + QOpenGLFramebufferObject fbo2(size); + QVERIFY(fbo2.bind()); + + QOpenGLPaintDevice device2(size); + + QVERIFY(p.begin(&device2)); + drawColoredRects(&p, size); + p.end(); + + QImage img2 = fbo2.toImage(); + + QFUZZY_COMPARE_IMAGES(img1, img2); +} + void tst_QOpenGL::aboutToBeDestroyed() { QWindow window; diff --git a/tests/auto/opengl/qgl/tst_qgl.cpp b/tests/auto/opengl/qgl/tst_qgl.cpp index af0248b432..cf92c9fab6 100644 --- a/tests/auto/opengl/qgl/tst_qgl.cpp +++ b/tests/auto/opengl/qgl/tst_qgl.cpp @@ -1169,6 +1169,10 @@ void tst_QGL::currentFboSync() QGLWidget glw; glw.makeCurrent(); + // For some reason we offer inter-operatibility between QGL and QOpenGL + // paint engines. (?!) Let's check if the two engines can be used to perform + // drawing in turns on different targets within the same context. + { QGLFramebufferObject fbo1(256, 256, QGLFramebufferObject::CombinedDepthStencil); diff --git a/tests/auto/other/lancelot/paintcommands.cpp b/tests/auto/other/lancelot/paintcommands.cpp index 2e6cb09aa5..971b9b5fe7 100644 --- a/tests/auto/other/lancelot/paintcommands.cpp +++ b/tests/auto/other/lancelot/paintcommands.cpp @@ -2378,6 +2378,8 @@ void PaintCommands::command_surface_begin(QRegExp re) #ifndef QT_NO_OPENGL m_default_glcontext = QOpenGLContext::currentContext(); m_surface_glcontext = new QOpenGLContext(); + // Pick up the format from the current context; this is especially + // important in order to pick the right version/profile to test. m_surface_glcontext->setFormat(m_default_glcontext->format()); m_surface_glcontext->create(); m_surface_glcontext->makeCurrent(m_default_glcontext->surface()); diff --git a/tests/auto/other/lancelot/tst_lancelot.cpp b/tests/auto/other/lancelot/tst_lancelot.cpp index 972e5ca967..63c62bab86 100644 --- a/tests/auto/other/lancelot/tst_lancelot.cpp +++ b/tests/auto/other/lancelot/tst_lancelot.cpp @@ -53,7 +53,7 @@ private: }; void setupTestSuite(const QStringList& blacklist = QStringList()); - void runTestSuite(GraphicsEngine engine, QImage::Format format); + void runTestSuite(GraphicsEngine engine, QImage::Format format, const QSurfaceFormat &contextFormat = QSurfaceFormat()); void paint(QPaintDevice *device, GraphicsEngine engine, const QStringList &script, const QString &filePath); QStringList qpsFiles; @@ -85,8 +85,11 @@ private slots: #ifndef QT_NO_OPENGL void testOpenGL_data(); void testOpenGL(); + void testCoreOpenGL_data(); + void testCoreOpenGL(); private: bool checkSystemGLSupport(); + bool checkSystemCoreGLSupport(); #endif }; @@ -236,6 +239,32 @@ bool tst_Lancelot::checkSystemGLSupport() return true; } +bool tst_Lancelot::checkSystemCoreGLSupport() +{ + if (QOpenGLContext::openGLModuleType() != QOpenGLContext::LibGL) + return false; + + QSurfaceFormat coreFormat; + coreFormat.setVersion(3, 2); + coreFormat.setProfile(QSurfaceFormat::CoreProfile); + QWindow win; + win.setSurfaceType(QSurface::OpenGLSurface); + win.setFormat(coreFormat); + win.create(); + QOpenGLFramebufferObjectFormat fmt; + fmt.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); + fmt.setSamples(4); + QOpenGLContext ctx; + ctx.setFormat(coreFormat); + if (!ctx.create() || !ctx.makeCurrent(&win)) + return false; + QOpenGLFramebufferObject fbo(800, 800, fmt); + if (!fbo.isValid() || !fbo.bind()) + return false; + + return true; +} + void tst_Lancelot::testOpenGL_data() { if (!checkSystemGLSupport()) @@ -249,6 +278,22 @@ void tst_Lancelot::testOpenGL() { runTestSuite(OpenGL, QImage::Format_RGB32); } + +void tst_Lancelot::testCoreOpenGL_data() +{ + if (!checkSystemCoreGLSupport()) + QSKIP("System under test does not meet preconditions for Core Profile GL testing. Skipping."); + QStringList localBlacklist = QStringList() << QLatin1String("rasterops.qps"); + setupTestSuite(localBlacklist); +} + +void tst_Lancelot::testCoreOpenGL() +{ + QSurfaceFormat coreFormat; + coreFormat.setVersion(3, 2); + coreFormat.setProfile(QSurfaceFormat::CoreProfile); + runTestSuite(OpenGL, QImage::Format_RGB32, coreFormat); +} #endif @@ -263,7 +308,7 @@ void tst_Lancelot::setupTestSuite(const QStringList& blacklist) } -void tst_Lancelot::runTestSuite(GraphicsEngine engine, QImage::Format format) +void tst_Lancelot::runTestSuite(GraphicsEngine engine, QImage::Format format, const QSurfaceFormat &contextFormat) { QFETCH(QString, qpsFile); @@ -279,11 +324,13 @@ void tst_Lancelot::runTestSuite(GraphicsEngine engine, QImage::Format format) } else if (engine == OpenGL) { QWindow win; win.setSurfaceType(QSurface::OpenGLSurface); + win.setFormat(contextFormat); win.create(); QOpenGLFramebufferObjectFormat fmt; fmt.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); fmt.setSamples(4); QOpenGLContext ctx; + ctx.setFormat(contextFormat); QVERIFY(ctx.create()); QVERIFY(ctx.makeCurrent(&win)); QOpenGLFramebufferObject fbo(800, 800, fmt); @@ -304,7 +351,7 @@ void tst_Lancelot::paint(QPaintDevice *device, GraphicsEngine engine, const QStr //pcmd.setShouldDrawText(false); switch (engine) { case OpenGL: - pcmd.setType(OpenGLBufferType); + pcmd.setType(OpenGLBufferType); // version/profile is communicated through the context's format() break; case Raster: // fallthrough default: diff --git a/tests/manual/lance/main.cpp b/tests/manual/lance/main.cpp index c1ace138f9..a8fa6bd402 100644 --- a/tests/manual/lance/main.cpp +++ b/tests/manual/lance/main.cpp @@ -91,6 +91,7 @@ static void printHelp() #ifndef QT_NO_OPENGL " -opengl Paints the files to a QGLWidget (Qt4 style) on screen\n" " -glbuffer Paints the files to a QOpenGLFrameBufferObject (Qt5 style) \n" + " -coreglbuffer Paints the files to a Core Profile context QOpenGLFrameBufferObject\n" #endif #ifdef USE_CUSTOM_DEVICE " -customdevice Paints the files to the custom paint device\n" @@ -213,6 +214,7 @@ int main(int argc, char **argv) #endif DeviceType type = WidgetType; + QSurfaceFormat contextFormat; bool checkers_background = true; QImage::Format imageFormat = QImage::Format_ARGB32_Premultiplied; @@ -281,6 +283,11 @@ int main(int argc, char **argv) type = OpenGLType; else if (option == "glbuffer") type = OpenGLBufferType; + else if (option == "coreglbuffer") { + type = OpenGLBufferType; + contextFormat.setVersion(3, 2); + contextFormat.setProfile(QSurfaceFormat::CoreProfile); + } #endif #ifdef USE_CUSTOM_DEVICE else if (option == "customdevice") @@ -423,11 +430,13 @@ int main(int argc, char **argv) { QWindow win; win.setSurfaceType(QSurface::OpenGLSurface); + win.setFormat(contextFormat); win.create(); QOpenGLFramebufferObjectFormat fmt; fmt.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); fmt.setSamples(4); QOpenGLContext ctx; + ctx.setFormat(contextFormat); ctx.create(); ctx.makeCurrent(&win); QOpenGLFramebufferObject fbo(width, height, fmt); |