l Qt Quick Scene Graph

l The Scene Graph in Qt Quick

Qt Quick 2는 전용 scene graph(이하 sg)를 사용하며 그래픽 API(OpenGL ES, OpenGL, Vulkan, Metal, Direct 3D )sg를 순회하면서 그리게 된다. QPainter나 그와 유사한 기존의 imperative painting systems 대신 sg를 사용하면 프레임 사이에 scene을 유지할 수 있으며 rendering 전에 필요한 primitives 값을 알 수 있다. 이는 rendering의 많은 부분을 최적화한다.

예를 들어, user-interface10개 아이템이 있고 각각이 background color, icon, text를 가지는 상황에서 기존 drawing techniques를 사용하면 30번의 draw call이 필요하다. 하지만, sg의 경우 render를 위한 primitives를 재구성(reorganize)하기 때문에 background color, icons, text별로 각각 한 번씩, 3번의 draw callrender할 수 있다. 이와 같은 batchingstate change reduction을 통해 performance를 증가시킬 수 있다.

Sg Qt Quick 2.0과 밀접하게 연관되어 있으며 Qt Quick 2.0없이 sg만 홀로 사용하지 않는다. SgQQuickWindow클래스에 의해 관리되고 그려진다. Custom item typessgcustom graphical primitives를 추가할 수 있으며 QQuickItem::updatePaintNode() 함수에서 추가한다.

Sgscene을 그래픽적으로 표현한 것으로 모든 아이템을 그리기에 충분한 정보를 가지는 독립적 구조이다. 한 번 구성되면 sg내 각 아이템의 state와 상관없이 그릴 수 있다. 대다수의 플랫폼에서 sg는 전용 render thread에서 그려지며 그동안 GUI thread는 다음 framestate를 준비한다.

Note: 이 페이지에서 표시하는 정보는 대부분 Qt Quick Scene graph의 기본 동작에 관한 것이다. 다른 sgporting하여 사용할 경우 적용되지 않는 개념이 있을 수 있다. 다른 sg adaptations에 대한 정보는 Scene Graph Adaptations에서 확인할 수 있다.

l Qt Quick Scene Graph Structure

Sg는 미리 정의된 node types으로 구성되며 각각은 나름의 전용 목적으로 사용된다. Sgscene graph라고 부르지만 더 정확한 명칭은 node tree이다. QML에서 sg QQuickItem types으로 구성되며 내부적으로 scenerenderer에 의해 처리된다. Nodes 자체는 active drawing codepaint() 함수를 가지지 않는다.

대게 node treenode는 기존 Qt Quick QML types을 사용하지만 users가 직접 custom content를 가지는 complete subtrees를 추가할 수도 있다.

l Nodes

users에게 가장 중요한 nodesQSGGeometryNode. nodegeometry material을 구현하여 custom graphics를 정의하는데 사용한다. geometrygraphical primitiveshapemesh를 정의하며 QSGGeometry를 사용하여 구현한다. Line, rectangle, polygon, 많은 끊어진 rectangles, 복잡한 3D mesh 등이 있다. materialshape pixel을 어떻게 채울지를 정의한다.

Node 하나는 많은 children을 가질 수 있고 geometry nodeschild-order로 그려진다. (children 뒤에 parent)

Note: child-order로 그려진다는 것은 renderer가 실제로 뭘 먼저 그리는지 순서를 말하는 것이 아니다. 단지 visual outputz-order를 보장할 뿐이다.

-       사용할 수 있는 nodes:

n  QSGClipNode - sg에서 clipping 기능을 구현

n  QSGGeometryNode - sg에서 모든 visual item이 사용

n  QSGNode – 모든 nodesbase class

n  QSGOpacityNode – nodes의 투명도를 변경하기 위해 사용

n  QSGTransformNode – transformations를 구현

Sgcustom nodes를 추가할 때는 QQuickItem::updatePaintNode() 함수를 override하고QQuickItem::ItemHasContents flag를 설정한다.

Warning: native graphics(OpenGL, Vulkan, Metal )의 작업(operations)sg와의 상호 작용(interaction)render thread에서만(exclusively) 수행해야 한다. 가장 중요한 규칙은 QQuickItem::-udatePaintNode() 함수 안에서 “QSG” 접두사가 붙은 classes만 사용하는 것이다.

상세 설명은 Scene Graph – Custom Geometry를 참고한다.

l Preprocessing

Nodesvitual QSGNode::preprocess() 함수를 가진다. 이 함수는 sg가 그려지기 전에 호출된다. Node를 상속받은 subclassesQSGNode::UsePreprocess flag를 설정하고 QSGNode::preprocess() 함수를 override하여 custom nodefinal preparation을 할 수 있다.

l Node Ownership

Nodes의 소유권은 생성자 또는, sg에서 QSGNode::OwnedByParent flag를 설정함으로써 명시적으로 설정한다. 소유권을 설정하면 sgGUI thread 밖에 있을 때 쉽게 메모리 정리를 할 수 있으므로 소유권을 설정하는 것을 권장한다.

l Materials

MaterialQSGGeometryNode 내부의 geometry를 채우는 방법을 정의한다. Graphics shadersencapsulates하며 충분한 유연성을 제공한다. 하지만 대부분의 Qt Quick itemssolid color, texture같은 매우 간단한 basic materials를 사용한다.

QML item typecustom shading을 적용하고자 하는 사람은 ShaderEffect type을 사용해서 QML에서 직접 적용시킬 수 있다.

-       material 클래스 목록:

n  QSGFlatColorMaterial – geometry를 단일 색상으로 rendering하는 편리한 방법

n  QSGMaterial – shader program을 위해 rendering stateencapsulates

n  QSGMaterialRhiShader

n  QSGMaterialShader - renderer에서 opengl shader program을 표현

n  QSGMaterialType

n  QSGOpaqueTextureMaterial

n  QSGTextureMaterial

n  QSGVertexColorMaterial

상세 설명은 Scene Graph – Simple Material을 참고한다.

l Convenience Nodes

Sg APIlow-level이며 편의성 보다는 성능에 초점을 맞췄기 때문에 가장 간단하게 구현해도 처음부터 custom geometriesmaterials를 구현하려면 적지 않은 양의 코드를 작성해야 한다. 이런 이유로, 일반적인 custom nodes를 쉽게 만들 수 있도록 몇몇 유용한 클래스를 제공한다.

Ø  QSGSimpleRectNode – QSGGeometryNodesubclass rectangular geometrysolid color material을 가진다.

Ø  QSGSimpleTextureNode – QSGGeometryNodesubclassrectangular geometrytexture material을 가진다.

l Scene Graph and Rendering

SgrenderingQQuickWindow클래스 내부에서 수행되며 이에 접근할 수 있는 public API는 없다. 대신 userrendering pipeline에 접근할 수 있도록 몇몇 places를 제공한다. 이 위치에서 custom sg content를 추가하거나 직접 graphics API를 호출하여 원하는 rendering commands를 삽입할 수 있다. 이는 sg가 사용한다. 위치는 render loop에서 정의한다.

Sg renderer의 동작 방법에 대한 상세 내용은 Qt Quick Scene Graph Default Renderer를 참고한다.

사용 가능한 render loop variants[ “basic”, “windows”, “threaded” ] 3개가 있다. 이중 “basic"“windows”single-thread이고 “threaded”sg rendering을 위한 전용 thread를 따로 가진다. Qt는 현재 사용하는 플랫폼과 그래픽 드라이버에 기반해서 적합한 loop를 선택한다. 결과가 만족스럽지 않는 경우엔 QSG_RENDER_LOOP 환경 변수를 사용해서 원하는 loop의 사용을 강제할 수 있다. 어떤 render loop가 사용 중인지 확인하려면 qt.scenegraph.general logging categoryenable 한다.

Note: “threaded”“windows” render loop graphics API 구현에 따라 throttling 관련 설정이 달라진다. 예를 들어, OpenGL의 경우 swap interval1로 요청한다. 어떤 그래픽 드라이버는 사용자가 qt의 요청을 무시하고 다른 설정을 적용할 수 있도록 허용하기도 한다. Swap buffers와 같은 throttling 관련 작업에서 blocking을 하지 않으면 render loopCPU100% 사용할 것이다. 만약 system vsync-based throttling을 지원하지 않는다면 basic render loop를 사용하기를 권한다.

l Threaded Render Loop (“threaded”)

대부분의 경우 sg rendering은 전용 render thread에서 수행시킨다. 이는 multi-core processors의 병렬성을 향상시키고 지연 시간을 잘 활용할 수 있는 방법이다. 뚜렷하게 성능을 향상시킬 수 있지만 sg interaction을 할 시간, 장소에 제약이 생긴다.


다음은 threaded render loopOpenGL을 사용하여 하나의 frame을 어떻게 그리는지에 대한 간단한 개요이다. 각 단계는 다른 graphics APIs에서도 동일하다(OpenGL context 부분 제외).

1.     사용자 입력이나 animation의 결과로 QML scene에 변경사항이 생기고 QQuickItem::update() 함수가 호출된다. 새로운 frame을 준비하도록 render threadevent를 전달한다.

2.     Render thread가 새 frame을 그리기 위해 준비한다. GUI thread blockinitiates한다.

3.     Render thread가 새 frame을 그리기 위해 준비하는 동안 GUI threadQQuickItem::update-Polish() 함수를 호출하여 itemsrender하기 전 final touch-up한다.

4.     GUI threadblock된다.

5.     QQuickWindow::beforeSynchronizing() signal을 발생시킨다. AppQt::DirectConnection으로 해당 signalslots을 연결하여 QQuickItem::updatePaintNode()함수가 호출되기 전에 필요한 준비를 할 수 있다. (DirectConnection으로 연결할 경우 slotssignaling thread에서 수행된다.)

6.     이전 frame이후 변경된 모든 items에서 QQuickItem::updatePaintNode()함수를 호출하여 sgQML state를 동기화한다.

7.     GUI thread block이 해제된다.

8.     Sgrender된다

A.     QQuickWindow::beforeRendering() signal을 발생시킨다. AppQt::DirectConnection으로 signal slots을 연결해서 custom graphics API calls을 수행할 수 있다. 이 때 그리는 내용은 QML scene 아래에(z-order) 그려진다.

B.     QSGNode::UsePreprocess가 설정된 itemsQSGNode::preprocess() 함수를 호출한다.

C.     Renderernodes를 처리한다.

D.     Rendererstates를 생성하고 사용중인 graphics APIdraw calls를 기록한다.

E.      QQuickWindow::afterRendering() signal을 발생시킨다. AppQt::DirectConnection으로 signal slots을 연결해서 custom graphics API calls을 수행할 수 있다. 이 때 그리는 내용은 QML scene 위에(z-order) 그려진다.

F.      이 단계가 되면 frame이 완성된다. opengl의 경우 buffers를 교체하고 다른 graphic API의 경우(Vulkan, Metal) 현재 명령을 기록하고 command buffersgraphics queue에 제출한다. 이후 QQuickWindow::frameSwapped() signal을 발생시킨다.

9.     Sgrender되는 동안 GUI thread advance animations를 수행하거나 events를 처리하는 등의 할 일을 한다.

현재 windows(opengl32.dll), linux(Mesa llvmpipe 제외), macOS, Metal, mobile platforms, Embedded Linux(EGLFS), 기타 Vulkan등에서 threaded rendererdefault로 사용하고 있다. (추후 변경될 수 있다.)

l 참고

-       베지어 곡선 Bezier curves

두 점 A, B를 잇는 하나의 선분위에 점 A에서 점 B로 이동하는 점 M0가 있다. 이 때, M0A에서 출발하여 B에 도착하기 까지의 진행도를 0에서 1사이의 값을 가지는 변수 t로 설정한다. M0A위에 있을 때 t=0일 것이며 B를 향해 움직임에 따라 t의 값이 증가하여 마침내 B에 도달했을 때 t=1이 될 것이다. 이 때 점 M0의 이동 궤적을 1Bezier curves라고 한다.

이 상황에서 점 C를 추가하고 B와 잇는다. 마찬가지로 점 B에서 점 C를 향해 이동하는 점 M1이 있고 이동함에 따른 정도를 동일한 변수 t로 표기하면 M0M1이 동시에 출발하고 도착하게 된다. 그 과정에서 M0M1을 잇는 선분을 그릴 수 있고 그 선분을 동일하게 t의 정도로 이동하는 점 N0를 그릴 수 있다.

이 점 N0가 이동하는 궤적을 2Bezier curves라고 한다. 마찬가지로 계속 점을 추가하여 3, 4 Bezier curves를 만들 수 있다.

실용적인 측면에서 일반적으로 3Bezier curves까지만 사용한다.

'개발 > qt' 카테고리의 다른 글

[번역] Qt 5.12 Best Practices for QML and Qt Quick  (0) 2021.11.15

Qt 5.12 > All Qt Overviews > Best Practices > 3. Best Practices for QML and Qt Quick

QMLQt Quick이 제공하는 이점에도 불구하고 어떤 상황에선 문제가 발생하기도 한다. 다음 sections에선 몇 가지 the best practices를 설명한다. 이는 applications 개발에 도움이 될 것이다.

Custom UI Controls

유연하고 세련된 UI는 오늘날 application 성공의 key이다. QML은 이를 만족한다. Qt는 세련된 lookUI를 만드는데 필요한 기본 UI controls을 제공한다. Custom UI control을 만들기 전에 기본 UI controls를 확인하기를 권한다.

Qt Quick 자체에서 제공되는 기본 UI controls 외에도, Qt Quick Controls2가 제공하는 다양한 UI controls set이 있으며 별다른 변경없이 대부분의 common use cases를 커버할 수 있다 더불어 customization도 가능하다. 특히, Qt Quick Controls 2styling options를 제공하는데, 이는 최신 UI design trends를 따른다. UI controlsneeds를 충족하지 못할 경우에만 custom control을 만들 것을 권한다.

->  QML 칭찬. 유연하며 세련되고 다양한 기본 UI controls가 제공되는데 이걸로 어지간한 것은 다 만들 수 있다. 싫으면 customization도 가능하다.

Bundle Application Resources

대부분 applications는 풍부한 user experience를 제공하기 위해 imagesicons같은 resources을 사용하는데 종종 target OS에 따라 resources를 사용할 수 없는 문제가 발생한다. 대중적인 OS는 엄격한 보안 정책을 가지며 file system 접근을 제한한다. 이 때문에 Resourcesload하기가 어렵다. Qt는 해결방안으로 독자적인 resource system을 제공한다. Resourcesapplication binary안에 built-in 시키는 것이다. 결과적으로 target OS에 관계없이 resources에 접근할 수 있다.

예를 들어, 아래 directory structure에 대해 생각해보자.

.pro 파일에 다음 구문을 작성하여 resourcesbuilt-in 되도록 한다.

더 편리하게 wildcard syntax를 이용해서 여러 파일을 한 번에 추가할 수도 있다.

이 방법은 resources가 몇 개 안될 때 편리하다. 하지만, new fileRESOURCES에 추가될 때 마다 RESOURCES에 있는 모든 the other files 또한 recompile해야한다. 이는 files를 많이 가지고 있는 application일수록 비효율적이다. 해결 방법은 resource type에 따라 다른 qrc file로 분리하는 것이다. 예를 들어, 위의 snippet은 아래와 같이 바꿀 수 있다.

(* qml.qrc, images.qrc)

이제 QML file이 변경될 때마다 변경된 파일들만 recompile하면 된다.

때때로 resource system에서 관리하는 특정 file에 많은 control이 필요할 때가 있다. 예를 들어, image2.png 파일에 alias가 필요하다면 명시적인 qrc file로 전환할 필요가 있다. 이와 관련한 상세 내용은 Creating Resource Files에서 확인한다.

Separate UI from Logic

많은 application 개발자의 목표 중 하나는 관리가 용이한 application을 만드는 것이다. 이를 이루는 방법 중 하나는 user interfacebusiness logic으로부터 분리하는 것이다. 아래에서 applicationsUIQML로 작성해야 하는 몇 가지 이유를 설명한다.

UI 정의엔 declarative languages가 매우 적합하다.

QML codeC++보다 구문이 짧고 type에 엄격하지 않기 때문에 쓰기가 쉽다. 이는 또한, prototype 작성에 좋고 designer와 협업하기에 좋다.

QML에서 java script를 쉽게 사용할 수 있어 Eventsrespond할 수 있다.

Type에 엄격한 C++application’s logic에 적합하다. 이는 일반적으로 QML보다 C++에서 더 빠른 복잡한 계산이나 데이터 처리와 같은 작업을 수행한다.

QtQMLC++ codeintegrate하기 위한 다양한 접근법을 제공한다. 전형적인 use case로는user interfacedata list를 보여주는 case가 있다. 만약 data setstatic, simple, small 하다면 QML에서 작성한 model로도 충분하다.

다음 snippetQML에서 model을 작성한 예시이다.

Data set의 크기가 크거나 자주 변경된다면 C++을 사용해라.

Interacting with QML from C++

비록 QtC++에서 QML을 조작할 수 있도록 허용하지만 꼭 그렇게 해야만 하는 것은 아니다. 이유를 설명하기 위해 간단한 예를 확인해보자.

Pulling References from QML

Settings page를 위한 UI를 아래와 같이 작성했다고 가정한다.

예시의 buttonclick될 때 C++에서 임의의 처리를 하기를 원한다고 하자. C++objects처럼 QMLobjectschange signalsemit할 수 있다. C++에서 위의 button object를 찾기 위해 button“objectName”을 할당해야 한다.

그러면, C++에서 object를 찾을 수 있고 objectchange signalcallback을 연결할 수 있다.

이 방법으로 QML objectsreference“pulled”할 수 있다. 이 방법의 문제점은 C++ logic layerQML presentation layer에 의존한다는 점이다. 만약, QMLrefactoring하는 과정에서 “objectName”이 변경되는 등의 결과로 C++에서 QML object를 찾는 구문이 깨질 수 있다.

-> 위 예제 실행시 에러가 난다. key는 아래의 #include "main.moc"이다. QObject를 상속받은 class를 별도 파일로 구현하면 에러가 나지 않는다.

Pushing References to QML

QML RefactoringC++ Refactoring보다 쉽다. 그래서 유지보수의 고통을 줄이기 위해 C++ typesQML types을 모르게 만들어야 한다. 이는 C++ typesreferencesQML“pushing”하는 방법으로 해결할 수 있다.

이후 QML에서 C++slot을 직접적으로 호출하게 한다.

이 방법으로 C++는 나중에 QMLrefactoring하더라도 unchanged 할 수 있다.

위의 예시에선 C++ objectQMLpush하기위해 root contextcontext property로 해당 objectset했다. 이렇게 하면 the engine에 의해 load되는 모든 components에서 해당 property를 사용할 수 있다. QMLload되자마자 사용 가능해야 하는 경우 context properties를 유용하게 사용할 수 있다. 이는 QML에서 instantiated되면 안 된다.

C++ typesQML에 노출시키기 위해 어떤 방법을 사용하는 것이 좋은 지 Choosing the Correct Integration Method Between C++ and QML에서 설명한다.

Using Qt Quick Layouts

Qtlayout안에 있는 Qt Quick items을 배치하기 위해 Qt Quick Layouts를 제공한다. item positioners와 다르게 Qt Quick Layoutswindow resize시에 childrenresize할 수 있다. 이는 대게 좋은 선택지가 되지만 사용시 아래와 같은 참고 사항이 있다.

Dos

Layout itemsize를 정해야 하는 데 parentNon-Layout이라면 anchors, width, height properties를 사용하라.

Layout 바로 아래의 children을 배치하기위해 propertyattachLayout을 사용해라.

Don’ts

Itemsimplicit size가 만족스럽지 않은 경우에만 Layoutpreferred sizes를 사용하라.

Layout 바로 아래의 childrenanchors를 사용하지 마라. 그 대신, Layout.preferredWidth, Layout.preferredHeight을 사용하라.

Note: Layoutsanchorsinstantiation시 더 많은 memory와 시간을 필요로 한다. Listtable delegates, styles에서 이런 것을 사용하지 않도록 유의하라. 이경우 간단하게 x, y, width, height properties를 사용하는 것이 좋다.

Type Safety

QML에서 properties를 선언할 때 “var” type을 사용하면 쉽고 편하다.

하지만, 이런 사용은 몇 가지 단점이 있다.

만약, 변수에 잘못된 type의 값이 할당되면 error report는 사용문이 아닌 선언문을 가리킨다. , 오류 추적이 어렵다.

위에서 언급된 것과 같은 error를 잡기 위한 정적 분석이 불가하다.

코드를 읽는 이가 propertytype을 분명히 알기 어렵다.

가능한한 실제 type을 사용하도록 하라.

Performance

QMLQt Quickperformance를 위한 정보는 Performance Considerations And Suggestions에 있다.

Tools and Utilities

QMLQt Quick을 더 쉽게 사용하게 해주는 toolsutilities에 대한 정보는 Qt Quick Tools and Utilities를 참조하라.

Scene Graph

Qt Quick’s scene graph에 대한 정보는 Qt Quick Scene Graph를 참조하라.

Scalable User Interfaces

Display resolutions가 향상될수록 scalable application UI는 더욱 중요하다. 이를 위한 한 방법은 screen resolutions마다 UIcopies를 준비해서 가능한 resolution에 따라 적절한 UIload하는 것이다. 이는 비록 잘 동작하겠지만 유지보수에 overhead를 더한다.

Qt는 더 나은 solution을 제공하며 이를 따를 것을 권한다.

Qt Quick Layouts module이나 anchors를 사용하라.

Visual item에 명확한 width, height의 값을 설정하지 마라.

Application이 지원하는 display resolution 마다 imagesicons 같은 resources를 제공하라. Qt Quick Controls 2 gallery exampleqt-log.png 파일을 @2x, @3x, @4x resolutions 마다 제공한다. 이는 application이 더 높은 품질의 해상도를 제공할 수 있도록 한다. Qthigh DPI scaling feature를 명시적으로 enable한 경우 자동으로 주어진 display에 적합한 image를 선택한다.

작은 iconSVG images를 사용하라. 큰 이미지를 SVG로 하면 render 속도가 느리지만 작은 이미지는 괜찮다. Vector 이미지와 Bitmap 이미지는 resolution에 따라 여러 이미지를 제공할 필요가 없다.

“Font Awesome”과 같은 font-based icons를 사용하라. 이는 모든 display resolution에 따라 크기 변환이 가능하며 colorization도 가능하다. Qt Quick Controls 2Text Editor example이 이를 잘 보여준다.

이제 제공되는 display resolution에 따라 크기를 변환할 수 있다.

'개발 > qt' 카테고리의 다른 글

[번역] qt quick scene graph  (0) 2022.01.25

+ Recent posts