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-interface에 10개 아이템이 있고 각각이 background color, icon, text를 가지는 상황에서 기존 drawing techniques를 사용하면 30번의 draw call이 필요하다. 하지만, sg의 경우 render를 위한 primitives를 재구성(reorganize)하기 때문에 background color, icons, text별로 각각 한 번씩, 단 3번의 draw call로 render할 수 있다. 이와 같은 batching과 state change reduction을 통해 performance를 증가시킬 수 있다.
Sg는 Qt Quick 2.0과 밀접하게 연관되어 있으며 Qt Quick 2.0없이 sg만 홀로 사용하지 않는다. Sg는 QQuickWindow클래스에 의해 관리되고 그려진다. Custom item types은 sg에 custom graphical primitives를 추가할 수 있으며 QQuickItem::updatePaintNode() 함수에서 추가한다.
Sg는 scene을 그래픽적으로 표현한 것으로 모든 아이템을 그리기에 충분한 정보를 가지는 독립적 구조이다. 한 번 구성되면 sg내 각 아이템의 state와 상관없이 그릴 수 있다. 대다수의 플랫폼에서 sg는 전용 render thread에서 그려지며 그동안 GUI thread는 다음 frame의 state를 준비한다.
Note: 이 페이지에서 표시하는 정보는 대부분 Qt Quick Scene graph의 기본 동작에 관한 것이다. 다른 sg를 porting하여 사용할 경우 적용되지 않는 개념이 있을 수 있다. 다른 sg adaptations에 대한 정보는 Scene Graph Adaptations에서 확인할 수 있다.
l Qt Quick Scene Graph Structure
Sg는 미리 정의된 node types으로 구성되며 각각은 나름의 전용 목적으로 사용된다. Sg를 scene graph라고 부르지만 더 정확한 명칭은 node tree이다. QML에서 sg는 QQuickItem types으로 구성되며 내부적으로 scene은 renderer에 의해 처리된다. Nodes 자체는 active drawing code나 paint() 함수를 가지지 않는다.
대게 node tree의 node는 기존 Qt Quick QML types을 사용하지만 users가 직접 custom content를 가지는 complete subtrees를 추가할 수도 있다.
l Nodes
users에게 가장 중요한 nodes는 QSGGeometryNode다. 이 node는 geometry와 material을 구현하여 custom graphics를 정의하는데 사용한다. geometry는 graphical primitive의 shape과 mesh를 정의하며 QSGGeometry를 사용하여 구현한다. Line, rectangle, polygon, 많은 끊어진 rectangles, 복잡한 3D mesh 등이 있다. material은 shape의 pixel을 어떻게 채울지를 정의한다.
Node 하나는 많은 children을 가질 수 있고 geometry nodes는 child-order로 그려진다. (children 뒤에 parent)
Note: child-order로 그려진다는 것은 renderer가 실제로 뭘 먼저 그리는지 순서를 말하는 것이 아니다. 단지 visual output의 z-order를 보장할 뿐이다.
- 사용할 수 있는 nodes:
n QSGClipNode - sg에서 clipping 기능을 구현
n QSGGeometryNode - sg에서 모든 visual한 item이 사용
n QSGNode – 모든 nodes의 base class
n QSGOpacityNode – nodes의 투명도를 변경하기 위해 사용
n QSGTransformNode – transformations를 구현
Sg에 custom 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
Nodes는 vitual QSGNode::preprocess() 함수를 가진다. 이 함수는 sg가 그려지기 전에 호출된다. Node를 상속받은 subclasses는 QSGNode::UsePreprocess flag를 설정하고 QSGNode::preprocess() 함수를 override하여 custom node의 final preparation을 할 수 있다.
l Node Ownership
Nodes의 소유권은 생성자 또는, sg에서 QSGNode::OwnedByParent flag를 설정함으로써 명시적으로 설정한다. 소유권을 설정하면 sg가 GUI thread 밖에 있을 때 쉽게 메모리 정리를 할 수 있으므로 소유권을 설정하는 것을 권장한다.
l Materials
Material은 QSGGeometryNode 내부의 geometry를 채우는 방법을 정의한다. Graphics shaders를 encapsulates하며 충분한 유연성을 제공한다. 하지만 대부분의 Qt Quick items는 solid color, texture같은 매우 간단한 basic materials를 사용한다.
QML item type에 custom shading을 적용하고자 하는 사람은 ShaderEffect type을 사용해서 QML에서 직접 적용시킬 수 있다.
- material 클래스 목록:
n QSGFlatColorMaterial – geometry를 단일 색상으로 rendering하는 편리한 방법
n QSGMaterial – shader program을 위해 rendering state를 encapsulates함
n QSGMaterialRhiShader
n QSGMaterialShader - renderer에서 opengl shader program을 표현
n QSGMaterialType
n QSGOpaqueTextureMaterial
n QSGTextureMaterial
n QSGVertexColorMaterial
상세 설명은 Scene Graph – Simple Material을 참고한다.
l Convenience Nodes
Sg API는 low-level이며 편의성 보다는 성능에 초점을 맞췄기 때문에 가장 간단하게 구현해도 처음부터 custom geometries와 materials를 구현하려면 적지 않은 양의 코드를 작성해야 한다. 이런 이유로, 일반적인 custom nodes를 쉽게 만들 수 있도록 몇몇 유용한 클래스를 제공한다.
Ø QSGSimpleRectNode – QSGGeometryNode의 subclass로 rectangular geometry와 solid color material을 가진다.
Ø QSGSimpleTextureNode – QSGGeometryNode의 subclass로 rectangular geometry와 texture material을 가진다.
l Scene Graph and Rendering
Sg의 rendering은 QQuickWindow클래스 내부에서 수행되며 이에 접근할 수 있는 public API는 없다. 대신 user가 rendering 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 category를 enable 한다.
Note: “threaded”와 “windows” render loop는 graphics API 구현에 따라 throttling 관련 설정이 달라진다. 예를 들어, OpenGL의 경우 swap interval을 1로 요청한다. 어떤 그래픽 드라이버는 사용자가 qt의 요청을 무시하고 다른 설정을 적용할 수 있도록 허용하기도 한다. Swap buffers와 같은 throttling 관련 작업에서 blocking을 하지 않으면 render loop가 CPU를 100% 사용할 것이다. 만약 system이 vsync-based throttling을 지원하지 않는다면 basic render loop를 사용하기를 권한다.
l Threaded Render Loop (“threaded”)
대부분의 경우 sg rendering은 전용 render thread에서 수행시킨다. 이는 multi-core processors의 병렬성을 향상시키고 지연 시간을 잘 활용할 수 있는 방법이다. 뚜렷하게 성능을 향상시킬 수 있지만 sg와 interaction을 할 시간, 장소에 제약이 생긴다.
다음은 threaded render loop와 OpenGL을 사용하여 하나의 frame을 어떻게 그리는지에 대한 간단한 개요이다. 각 단계는 다른 graphics APIs에서도 동일하다(OpenGL context 부분 제외).
1. 사용자 입력이나 animation의 결과로 QML scene에 변경사항이 생기고 QQuickItem::update() 함수가 호출된다. 새로운 frame을 준비하도록 render thread에 event를 전달한다.
2. Render thread가 새 frame을 그리기 위해 준비한다. GUI thread block을 initiates한다.
3. Render thread가 새 frame을 그리기 위해 준비하는 동안 GUI thread는 QQuickItem::update-Polish() 함수를 호출하여 items을 render하기 전 final touch-up한다.
4. GUI thread가 block된다.
5. QQuickWindow::beforeSynchronizing() signal을 발생시킨다. App은 Qt::DirectConnection으로 해당 signal과 slots을 연결하여 QQuickItem::updatePaintNode()함수가 호출되기 전에 필요한 준비를 할 수 있다. (DirectConnection으로 연결할 경우 slots은 signaling thread에서 수행된다.)
6. 이전 frame이후 변경된 모든 items에서 QQuickItem::updatePaintNode()함수를 호출하여 sg과 QML state를 동기화한다.
7. GUI thread block이 해제된다.
8. Sg가 render된다
A. QQuickWindow::beforeRendering() signal을 발생시킨다. App은 Qt::DirectConnection으로 signal slots을 연결해서 custom graphics API calls을 수행할 수 있다. 이 때 그리는 내용은 QML scene 아래에(z-order) 그려진다.
B. QSGNode::UsePreprocess가 설정된 items의 QSGNode::preprocess() 함수를 호출한다.
C. Renderer가 nodes를 처리한다.
D. Renderer는 states를 생성하고 사용중인 graphics API로 draw calls를 기록한다.
E. QQuickWindow::afterRendering() signal을 발생시킨다. App은 Qt::DirectConnection으로 signal slots을 연결해서 custom graphics API calls을 수행할 수 있다. 이 때 그리는 내용은 QML scene 위에(z-order) 그려진다.
F. 이 단계가 되면 frame이 완성된다. opengl의 경우 buffers를 교체하고 다른 graphic API의 경우(Vulkan, Metal) 현재 명령을 기록하고 command buffers를 graphics queue에 제출한다. 이후 QQuickWindow::frameSwapped() signal을 발생시킨다.
9. Sg가 render되는 동안 GUI thread는 advance animations를 수행하거나 events를 처리하는 등의 할 일을 한다.
현재 windows(opengl32.dll), linux(Mesa llvmpipe 제외), macOS, Metal, mobile platforms, Embedded Linux(EGLFS), 기타 Vulkan등에서 threaded renderer를 default로 사용하고 있다. (추후 변경될 수 있다.)
l 참고
두 점 A, B를 잇는 하나의 선분위에 점 A에서 점 B로 이동하는 점 M0가 있다. 이 때, 점 M0가 A에서 출발하여 B에 도착하기 까지의 진행도를 0에서 1사이의 값을 가지는 변수 t로 설정한다. M0가 A위에 있을 때 t=0일 것이며 B를 향해 움직임에 따라 t의 값이 증가하여 마침내 B에 도달했을 때 t=1이 될 것이다. 이 때 점 M0의 이동 궤적을 1차 Bezier curves라고 한다.
이 상황에서 점 C를 추가하고 B와 잇는다. 마찬가지로 점 B에서 점 C를 향해 이동하는 점 M1이 있고 이동함에 따른 정도를 동일한 변수 t로 표기하면 M0와 M1이 동시에 출발하고 도착하게 된다. 그 과정에서 M0와 M1을 잇는 선분을 그릴 수 있고 그 선분을 동일하게 t의 정도로 이동하는 점 N0를 그릴 수 있다.
이 점 N0가 이동하는 궤적을 2차 Bezier curves라고 한다. 마찬가지로 계속 점을 추가하여 3차, 4차 Bezier curves를 만들 수 있다.
실용적인 측면에서 일반적으로 3차 Bezier curves까지만 사용한다.
'개발 > qt' 카테고리의 다른 글
[번역] Qt 5.12 Best Practices for QML and Qt Quick (0) | 2021.11.15 |
---|