// Copyright (C) 2015 Klaralvdalens Datakonsult AB (KDAB). // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #include "testrenderer.h" class tst_RenderShader : public Qt3DCore::QBackendNodeTester { Q_OBJECT private slots: void hasCoherentInitialState(); void matchesFrontendPeer(); void cleanupLeavesACoherentState(); void dealWithPropertyChanges_data(); void dealWithPropertyChanges(); void dealWithFormatChanges(); void checkSetRendererDirtyOnInitialization(); void allowToChangeShaderCode_data(); void allowToChangeShaderCode(); void checkShaderManager(); }; Qt3DRender::QShaderProgram *createFrontendShader(Qt3DRender::QShaderProgram::Format format = Qt3DRender::QShaderProgram::GLSL) { Qt3DRender::QShaderProgram *shader = new Qt3DRender::QShaderProgram(); shader->setFormat(format); shader->setVertexShaderCode(QByteArrayLiteral( "#version 150"\ "in vec3 vertexPosition;"\ "in vec3 vertexColor; "\ "out vec3 color;"\ "void main()"\ "{"\ " color = vertexColor;"\ " gl_Position = vec4( vertexPosition, 1.0 );"\ "}")); shader->setFragmentShaderCode(QByteArrayLiteral( "#version 150"\ "in vec3 color;"\ "out vec4 fragColor;"\ "void main()"\ "{"\ " fragColor = vec4( color, 1.0 );"\ "}")); return shader; } void tst_RenderShader::hasCoherentInitialState() { Qt3DRender::Render::Shader *shader = new Qt3DRender::Render::Shader(); QCOMPARE(shader->status(), Qt3DRender::QShaderProgram::NotReady); QCOMPARE(shader->format(), Qt3DRender::QShaderProgram::GLSL); QVERIFY(shader->log().isEmpty()); QCOMPARE(shader->isDirty(), false); } void tst_RenderShader::matchesFrontendPeer() { QScopedPointer frontend(createFrontendShader()); TestRenderer renderer; Qt3DRender::Render::Shader backend; backend.setRenderer(&renderer); simulateInitializationSync(frontend.data(), &backend); QCOMPARE(backend.isDirty(), true); for (int i = Qt3DRender::QShaderProgram::Vertex; i <= Qt3DRender::QShaderProgram::Compute; ++i) QCOMPARE(backend.shaderCode()[i], frontend->shaderCode(static_cast(i))); QCOMPARE(backend.format(), frontend->format()); } void tst_RenderShader::cleanupLeavesACoherentState() { QScopedPointer frontend(createFrontendShader(Qt3DRender::QShaderProgram::SPIRV)); TestRenderer renderer; Qt3DRender::Render::Shader shader; shader.setRenderer(&renderer); simulateInitializationSync(frontend.data(), &shader); shader.cleanup(); QCOMPARE(shader.isDirty(), false); QCOMPARE(shader.status(), Qt3DRender::QShaderProgram::NotReady); QCOMPARE(shader.format(), Qt3DRender::QShaderProgram::GLSL); } void tst_RenderShader::dealWithPropertyChanges_data() { QTest::addColumn("type"); QTest::newRow("vertex") << Qt3DRender::QShaderProgram::Vertex; QTest::newRow("tessControl") << Qt3DRender::QShaderProgram::TessellationControl; QTest::newRow("tessEval") << Qt3DRender::QShaderProgram::TessellationEvaluation; QTest::newRow("geometry") << Qt3DRender::QShaderProgram::Geometry; QTest::newRow("fragment") << Qt3DRender::QShaderProgram::Fragment; QTest::newRow("compute") << Qt3DRender::QShaderProgram::Compute; } void tst_RenderShader::dealWithPropertyChanges() { // GIVEN QFETCH(Qt3DRender::QShaderProgram::ShaderType, type); Qt3DRender::Render::Shader backend; Qt3DRender::QShaderProgram shader; TestRenderer renderer; backend.setRenderer(&renderer); simulateInitializationSync(&shader, &backend); // WHEN shader.setShaderCode(type, QByteArrayLiteral("foo")); backend.syncFromFrontEnd(&shader, false); // THEN QCOMPARE(backend.shaderCode().at(type), QByteArrayLiteral("foo")); QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty); QCOMPARE(backend.isDirty(), true); renderer.resetDirty(); QCOMPARE(renderer.dirtyBits(), 0); backend.unsetDirty(); // WHEN shader.setShaderCode(type, QByteArrayLiteral("foo")); backend.syncFromFrontEnd(&shader, false); // THEN QCOMPARE(backend.shaderCode().at(type), QByteArrayLiteral("foo")); QCOMPARE(renderer.dirtyBits(), 0); QCOMPARE(backend.isDirty(), false); // WHEN shader.setShaderCode(type, QByteArrayLiteral("bar")); backend.syncFromFrontEnd(&shader, false); // THEN QCOMPARE(backend.shaderCode().at(type), QByteArrayLiteral("bar")); QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty); renderer.resetDirty(); QCOMPARE(backend.isDirty(), true); } void tst_RenderShader::dealWithFormatChanges() { // GIVEN Qt3DRender::Render::Shader backend; Qt3DRender::QShaderProgram shader; TestRenderer renderer; backend.setRenderer(&renderer); simulateInitializationSync(&shader, &backend); // WHEN shader.setFormat(Qt3DRender::QShaderProgram::GLSL); backend.syncFromFrontEnd(&shader, false); // THEN QCOMPARE(backend.format(), Qt3DRender::QShaderProgram::GLSL); QCOMPARE(backend.isDirty(), false); QCOMPARE(renderer.dirtyBits(), 0); // WHEN shader.setFormat(Qt3DRender::QShaderProgram::SPIRV); backend.syncFromFrontEnd(&shader, false); // THEN QCOMPARE(backend.format(), Qt3DRender::QShaderProgram::SPIRV); QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty); QCOMPARE(backend.isDirty(), true); renderer.resetDirty(); backend.unsetDirty(); // WHEN shader.setFormat(Qt3DRender::QShaderProgram::SPIRV); backend.syncFromFrontEnd(&shader, false); // THEN QCOMPARE(backend.isDirty(), false); QCOMPARE(renderer.dirtyBits(), 0); } void tst_RenderShader::checkSetRendererDirtyOnInitialization() { // GIVEN QScopedPointer frontend(createFrontendShader()); Qt3DRender::Render::Shader shader; TestRenderer renderer; shader.setRenderer(&renderer); // THEN QCOMPARE(renderer.dirtyBits(), 0); // WHEN simulateInitializationSync(frontend.data(), &shader); // THEN QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty); } void tst_RenderShader::allowToChangeShaderCode_data() { dealWithPropertyChanges_data(); } void tst_RenderShader::allowToChangeShaderCode() { // GIVEN QFETCH(Qt3DRender::QShaderProgram::ShaderType, type); Qt3DRender::Render::Shader backend; TestRenderer renderer; backend.setRenderer(&renderer); // WHEN backend.setShaderCode(type, QByteArrayLiteral("foo")); // THEN QCOMPARE(backend.shaderCode().at(type), QByteArrayLiteral("foo")); QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty); renderer.resetDirty(); // WHEN backend.setShaderCode(type, QByteArrayLiteral("foo")); // THEN QCOMPARE(backend.shaderCode().at(type), QByteArrayLiteral("foo")); QCOMPARE(renderer.dirtyBits(), 0); renderer.resetDirty(); // WHEN backend.setShaderCode(type, QByteArrayLiteral("bar")); // THEN QCOMPARE(backend.shaderCode().at(type), QByteArrayLiteral("bar")); QCOMPARE(renderer.dirtyBits(), Qt3DRender::Render::AbstractRenderer::ShadersDirty); renderer.resetDirty(); } void tst_RenderShader::checkShaderManager() { // GIVEN Qt3DRender::QShaderProgram shader; TestRenderer renderer; Qt3DRender::Render::ShaderManager manager; Qt3DRender::Render::ShaderFunctor creationFunctor(&renderer, &manager); // THEN QVERIFY(manager.shaderIdsToCleanup().isEmpty()); // WHEN auto backend = creationFunctor.create(shader.id()); // THEN QVERIFY(backend != nullptr); QVERIFY(manager.shaderIdsToCleanup().isEmpty()); { // WHEN auto sameBackend = creationFunctor.get(shader.id()); // THEN QCOMPARE(backend, sameBackend); } // WHEN creationFunctor.destroy(shader.id()); // THEN -> Should be in list of ids to remove and return null on get QVERIFY(manager.hasShaderIdToCleanup(shader.id())); QVERIFY(creationFunctor.get(shader.id()) == nullptr); // WHEN -> Should be removed from list of ids to remove creationFunctor.create(shader.id()); // THEN QVERIFY(manager.shaderIdsToCleanup().isEmpty()); QCOMPARE(creationFunctor.get(shader.id()), backend); } QTEST_APPLESS_MAIN(tst_RenderShader) #include "tst_shader.moc"