Qt Quick 씬 그래프

의 씬 그래프 Qt Quick

Qt Quick 2의 씬 그래프는 전용 씬 그래프를 사용한 다음 OpenGL ES, OpenGL, Vulkan, Metal 또는 Direct 3D와 같은 그래픽 API를 통해 트래버스하고 렌더링합니다. 기존의 명령형 페인팅 시스템(QPainter 등)이 아닌 그래픽에 씬 그래프를 사용하면 렌더링할 장면을 프레임 간에 유지할 수 있고 렌더링할 전체 프리미티브 세트를 렌더링 시작 전에 알 수 있습니다. 따라서 상태 변경을 최소화하기 위한 일괄 렌더링, 가려진 프리미티브 삭제 등 다양한 최적화를 수행할 수 있습니다.

예를 들어 사용자 인터페이스에 각 항목에 배경색, 아이콘, 텍스트가 있는 10개의 항목 목록이 포함되어 있다고 가정해 보겠습니다. 전통적인 드로잉 기법을 사용하면 30번의 그리기 호출과 비슷한 양의 상태 변경이 발생합니다. 반면 씬 그래프를 사용하면 프리미티브를 재구성하여 배경을 한 번의 호출로 모두 그린 다음 아이콘과 텍스트를 모두 그리도록 렌더링하여 총 그리기 호출 수를 3개로 줄일 수 있습니다. 이와 같은 일괄 처리 및 상태 변경 감소는 일부 하드웨어에서 성능을 크게 향상시킬 수 있습니다.

씬 그래프는 Qt Quick 2.0과 밀접하게 연결되어 있으며 독립적으로 사용할 수 없습니다. 씬 그래프는 QQuickWindow 클래스에 의해 관리 및 렌더링되며, 커스텀 아이템 유형은 QQuickItem::updatePaintNode() 호출을 통해 그래픽 프리미티브를 씬 그래프에 추가할 수 있습니다.

씬 그래프는 아이템 씬의 그래픽 표현으로, 모든 아이템을 렌더링하기에 충분한 정보를 포함하는 독립적인 구조입니다. 일단 설정되면 항목의 상태와 독립적으로 조작하고 렌더링할 수 있습니다. 많은 플랫폼에서 씬 그래프는 GUI 스레드가 다음 프레임의 상태를 준비하는 동안 전용 렌더 스레드에서 렌더링되기도 합니다.

참고: 이 페이지에 나열된 대부분의 정보는 Qt Quick 씬 그래프의 기본 동작에 관한 것입니다. software 적응과 같은 다른 장면 그래프 적응을 사용하는 경우 모든 개념이 적용되지 않을 수 있습니다. 다양한 장면 그래프 적응에 대한 자세한 내용은 장면 그래프 적응을 참조하십시오.

Qt Quick 씬 그래프 구조

씬 그래프는 미리 정의된 여러 노드 유형으로 구성되며, 각 노드는 전용 용도로 사용됩니다. 우리는 이를 씬 그래프라고 부르지만 더 정확한 정의는 노드 트리입니다. 트리는 QML 씬의 QQuickItem 유형으로 구축되며, 내부적으로 씬을 그리는 렌더러가 씬을 처리합니다. 노드 자체에는 활성 그리기 코드나 가상 paint() 함수가 포함되어 있지 않습니다.

노드 트리는 대부분 기존 Qt Quick QML 유형에 의해 내부적으로 구축되지만 사용자가 3D 모델을 나타내는 서브트리를 포함하여 자체 콘텐츠로 완전한 서브트리를 추가할 수도 있습니다.

노드

사용자에게 가장 중요한 노드는 QSGGeometryNode 노드로, 지오메트리와 재질을 정의하여 사용자 지정 그래픽을 정의하는 데 사용됩니다. 지오메트리는 QSGGeometry 을 사용하여 정의되며 그래픽 프리미티브의 모양 또는 메시를 설명합니다. 선, 직사각형, 다각형, 여러 개의 단절된 직사각형 또는 복잡한 3D 메시가 될 수 있습니다. 머티리얼은 이 모양의 픽셀이 채워지는 방식을 정의합니다.

노드에는 자식을 얼마든지 가질 수 있으며 지오메트리 노드는 자식 뒤에 부모가 있는 자식 순서로 렌더링됩니다.

참고: 렌더러의 실제 렌더링 순서에 대해서는 아무 것도 말해주지 않습니다. 시각적 출력만 보장됩니다.

사용 가능한 노드는 다음과 같습니다:

QSGClipNode

씬 그래프에서 클리핑 기능을 구현합니다.

QSGGeometryNode

씬 그래프에서 렌더링된 모든 콘텐츠에 사용됩니다.

QSGNode

씬 그래프의 모든 노드에 대한 베이스 클래스

QSGOpacityNode

노드의 불투명도를 변경하는 데 사용

QSGTransformNode

씬 그래프에서 트랜스폼을 구현합니다.

사용자 정의 노드는 QQuickItem::updatePaintNode()를 서브클래싱하고 QQuickItem::ItemHasContents 플래그를 설정하여 씬 그래프에 추가합니다.

경고: 네이티브 그래픽(OpenGL, Vulkan, Metal 등) 작업과 씬 그래프와의 상호 작용은 주로 updatePaintNode() 호출 중에 렌더링 스레드에서만 이루어지도록 하는 것이 중요합니다. 경험상 QQuickItem::updatePaintNode() 함수 안에 접두사가 "QSG"인 클래스만 사용하는 것이 좋습니다.

자세한 내용은 씬 그래프 - 커스텀 지오메트리를 참조하십시오.

전처리

노드에는 씬 그래프가 렌더링되기 전에 호출되는 가상 QSGNode::preprocess() 함수가 있습니다. 노드 서브클래스는 QSGNode::UsePreprocess 플래그를 설정하고 QSGNode::preprocess() 함수를 재정의하여 노드의 최종 준비를 수행할 수 있습니다. 예를 들어 베지어 커브를 현재 스케일 팩터에 맞는 올바른 세부 수준으로 나누거나 텍스처의 섹션을 업데이트할 수 있습니다.

노드 소유권

노드 소유권은 제작자가 명시적으로 지정하거나 씬 그래프에서 QSGNode::OwnedByParent 플래그를 설정하여 지정할 수 있습니다. 씬 그래프에 소유권을 할당하는 것이 씬 그래프가 GUI 스레드 외부에 있을 때 정리를 간소화할 수 있어 선호되는 경우가 많습니다.

머티리얼

머티리얼은 QSGGeometryNode 에서 지오메트리의 내부가 채워지는 방식을 설명합니다. 그래픽 파이프라인의 버텍스 및 프래그먼트 단계에 대한 그래픽 셰이더를 캡슐화하며, 대부분의 Qt Quick 항목 자체는 단색 및 텍스처 채우기와 같은 매우 기본적인 머티리얼만 사용하지만 달성할 수 있는 것에 충분한 유연성을 제공합니다.

QML 항목 유형에 커스텀 셰이딩만 적용하려는 사용자의 경우 ShaderEffect 유형을 사용하여 QML에서 직접 이 작업을 수행할 수 있습니다.

아래는 머티리얼 클래스의 전체 목록입니다:

QSGFlatColorMaterial

씬 그래프에서 단색 지오메트리를 렌더링하는 편리한 방법

QSGMaterial

셰이더 프로그램의 렌더링 상태를 캡슐화합니다.

QSGMaterialShader

그래픽스 API 독립 셰이더 프로그램을 나타냅니다.

QSGMaterialType

QSGMaterial과 함께 고유 유형 토큰으로 사용됨

QSGOpaqueTextureMaterial

씬 그래프에서 텍스처 지오메트리를 렌더링하는 편리한 방법

QSGTextureMaterial

씬 그래프에서 텍스처 지오메트리를 렌더링하는 편리한 방법

QSGVertexColorMaterial

씬 그래프에서 버텍스별 컬러 지오메트리를 렌더링하는 편리한 방법

편의 노드

씬 그래프 API는 로우 레벨이며 편의성보다는 성능에 중점을 두고 있습니다. 가장 기본적인 지오메트리라도 커스텀 지오메트리와 머티리얼을 처음부터 작성하려면 적지 않은 양의 코드가 필요합니다. 이러한 이유로 API에는 가장 일반적인 커스텀 노드를 쉽게 사용할 수 있도록 몇 가지 편의 클래스가 포함되어 있습니다.

씬 그래프 및 렌더링

씬 그래프의 렌더링은 QQuickWindow 클래스에서 내부적으로 이루어지며, 이에 액세스할 수 있는 공용 API는 없습니다. 그러나 렌더링 파이프라인에는 사용자가 애플리케이션 코드를 첨부할 수 있는 몇 가지 위치가 있습니다. 이를 통해 사용자 지정 씬 그래프 콘텐츠를 추가하거나 씬 그래프에서 사용 중인 그래픽 API(OpenGL, Vulkan, Metal 등)를 직접 호출하여 임의의 렌더링 명령을 삽입하는 데 사용할 수 있습니다. 통합 지점은 렌더 루프에 의해 정의됩니다.

씬 그래프 렌더러의 작동 방식에 대한 자세한 설명은 Qt Quick 씬 그래프 기본 렌더러를 참조하세요.

두 가지 렌더 루프 변형을 사용할 수 있습니다: basic threaded basic 은 싱글 스레드이고, threaded 은 전용 스레드에서 씬 그래프 렌더링을 수행합니다. Qt는 플랫폼과 사용 중인 그래픽 드라이버에 따라 적합한 루프를 선택하려고 시도합니다. 이것이 만족스럽지 않거나 테스트 목적으로 환경 변수 QSG_RENDER_LOOP 를 사용하여 지정된 루프를 강제로 사용할 수 있습니다. 사용 중인 렌더링 루프를 확인하려면 qt.scenegraph.general logging category 을 활성화하십시오.

스레드 렌더 루프('스레드')

많은 구성에서 씬 그래프 렌더링은 전용 렌더 스레드에서 이루어집니다. 이는 멀티코어 프로세서의 병렬성을 높이고 차단 스왑 버퍼 호출 대기 등의 스톨 시간을 더 잘 활용하기 위한 것입니다. 이렇게 하면 성능이 크게 향상되지만 씬 그래프와 상호 작용할 수 있는 위치와 시기에 특정 제한이 적용됩니다.

다음은 스레드 렌더 루프와 OpenGL을 사용하여 프레임이 렌더링되는 간단한 개요입니다. 이 단계는 OpenGL 컨텍스트 관련 사항을 제외하면 다른 그래픽 API에서도 동일합니다.

  1. QML 씬에서 변경이 발생하여 QQuickItem::update() 이 호출됩니다. 이는 예를 들어 애니메이션이나 사용자 입력의 결과일 수 있습니다. 이벤트가 렌더 스레드에 게시되어 새 프레임이 시작됩니다.
  2. 렌더 스레드는 새 프레임을 그릴 준비를 하고 GUI 스레드에서 블록을 시작합니다.
  3. 렌더 스레드가 새 프레임을 준비하는 동안 GUI 스레드는 QQuickItem::updatePolish()를 호출하여 항목이 렌더링되기 전에 최종 수정 작업을 수행합니다.
  4. GUI 스레드가 차단됩니다.
  5. QQuickWindow::beforeSynchronizing() 신호가 방출됩니다. 애플리케이션은 QQuickItem::updatePaintNode()을 호출하기 전에 필요한 준비를 하기 위해 이 신호에 직접 연결( Qt::DirectConnection)을 사용할 수 있습니다.
  6. QML 상태를 씬 그래프에 동기화합니다. 이 작업은 이전 프레임 이후 변경된 모든 항목에 대해 QQuickItem::updatePaintNode() 함수를 호출하여 수행됩니다. 이때가 QML 항목과 씬 그래프의 노드가 상호 작용하는 유일한 시간입니다.
  7. GUI 스레드 블록이 해제됩니다.
  8. 씬 그래프가 렌더링됩니다:
    1. QQuickWindow::beforeRendering() 신호가 방출됩니다. 애플리케이션은 이 신호에 직접 연결( Qt::DirectConnection)하여 사용자 지정 그래픽 API 호출을 사용할 수 있으며, 그러면 QML 씬 아래에 시각적으로 스택이 쌓이게 됩니다.
    2. QSGNode::UsePreprocess 을 지정한 항목은 QSGNode::preprocess() 함수가 호출됩니다.
    3. 렌더러가 노드를 처리합니다.
    4. 렌더러는 상태를 생성하고 사용 중인 그래픽 API에 대한 그리기 호출을 기록합니다.
    5. QQuickWindow::afterRendering() 신호가 방출됩니다. 애플리케이션은 이 신호에 직접 연결( Qt::DirectConnection)하여 사용자 지정 그래픽 API 호출을 실행하면 QML 씬에 시각적으로 스택이 쌓입니다.
    6. 이제 프레임이 준비되었습니다. 버퍼가 스왑되거나(OpenGL), 현재 명령이 기록되고 명령 버퍼가 그래픽 대기열에 제출됩니다(Vulkan, Metal). QQuickWindow::frameSwapped()가 방출됩니다.
  9. 렌더 스레드가 렌더링하는 동안 GUI는 애니메이션을 진행하거나 이벤트를 처리하는 등의 작업을 자유롭게 수행할 수 있습니다.

스레드 렌더러는 현재 Direct3D 11을 사용하는 Windows와 opengl32.dll을 사용하는 경우 OpenGL, Mesa llvmpipe를 제외한 Linux, Metal을 사용하는 macOS, 모바일 플랫폼, EGLFS를 사용하는 임베디드 Linux, 플랫폼에 관계없이 Vulkan에서 기본으로 사용됩니다. 이 모든 사항은 향후 릴리스에서 변경될 수 있습니다. 환경에서 QSG_RENDER_LOOP=threaded 을 설정하여 언제든지 스레드 렌더러를 강제로 사용할 수 있습니다.

비스레드 렌더 루프('기본')

비스레드 렌더 루프는 현재 시스템의 표준 opengl32.dll을 사용하지 않는 경우 OpenGL을 사용하는 Windows, OpenGL을 사용하는 macOS, WebAssembly 및 일부 드라이버를 사용하는 Linux에서 기본적으로 사용됩니다. 후자의 경우 OpenGL 드라이버와 윈도우 시스템의 모든 조합이 테스트되지 않았기 때문에 이는 대부분 예방 조치입니다.

macOS 및 OpenGL에서 스레드 렌더 루프는 macOS 10.14에서 레이어 지원 뷰를 선택하기 때문에 XCode 10(10.14 SDK) 이상으로 빌드할 때 지원되지 않습니다. Xcode 9(10.13 SDK)로 빌드하여 레이어 백을 선택 해제할 수 있으며, 이 경우 스레드 렌더 루프가 기본적으로 제공되고 사용됩니다. Metal에서는 이러한 제한이 없습니다.

웹 플랫폼은 메인 스레드 이외의 다른 스레드에서 WebGL을 사용하는 것을 제한적으로 지원하고 메인 스레드를 차단하는 것을 제한적으로 지원하기 때문에 WebAssembly에서는 스레드 렌더링 루프가 지원되지 않습니다.

비스레드 렌더 루프를 사용하는 경우에도 스레드 렌더러를 사용하는 것처럼 코드를 작성해야 하며, 그렇게 하지 않으면 코드가 이식 불가능해집니다.

다음은 비스레드 렌더러의 프레임 렌더링 시퀀스를 단순화한 그림입니다.

애니메이션 구동

위 다이어그램에서 Advance Animations 은 무엇을 의미하나요?

기본적으로 Qt Quick 애니메이션(예: NumberAnimation)은 기본 애니메이션 드라이버에 의해 구동됩니다. 이는 QObject::startTimer()와 같은 기본 시스템 타이머에 의존합니다. 타이머는 일반적으로 16밀리초 간격으로 실행됩니다. 이는 완전히 정확할 수는 없으며 기본 플랫폼의 타이머 정확도에 따라 달라지지만 렌더링과 독립적이라는 장점이 있습니다. 디스플레이 재생률과 디스플레이의 수직 동기화 활성화 여부에 관계없이 균일한 결과를 제공합니다. 이것이 basic 렌더 루프에서 애니메이션이 작동하는 방식입니다.

렌더 루프 디자인(단일 스레드 또는 다중 스레드)에 관계없이 화면 끊김이 적고 보다 정확한 결과를 제공하기 위해 렌더 루프는 자체 사용자 지정 애니메이션 드라이버를 설치하고 타이머에 의존하지 않고 advancing 의 작업을 직접 수행하기로 결정할 수 있습니다.

이것이 바로 threaded 렌더 루프가 구현하는 것입니다. 실제로는 하나가 아니라 두 개의 애니메이션 드라이버를 설치하는데, 하나는 gui 스레드( NumberAnimation)와 렌더 스레드( Animator 유형, 즉 OpacityAnimator 또는 XAnimator)와 같은 일반 애니메이션을 구동하기 위해 렌더 스레드에 하나씩 설치합니다. 이 두 가지 모두 프레임을 준비하는 동안 진행되며, 즉 애니메이션이 렌더링과 동기화됩니다. 이는 기본 그래픽 스택에 의해 디스플레이의 수직 동기화에 맞춰 프레젠테이션이 조절되기 때문에 의미가 있습니다.

따라서 위의 threaded 렌더 루프 다이어그램에서 두 스레드 모두에 명시적인 Advance animations 단계가 있습니다. 렌더 스레드의 경우, 이는 사소한 문제입니다. 스레드가 비동기화를 위해 스로틀되고 있으므로 각 프레임에서 애니메이션( Animator 유형의 경우)을 16.67밀리초가 경과한 것처럼 진행하면 시스템 타이머에 의존하는 것보다 더 정확한 결과를 얻을 수 있습니다. (60Hz 재생률에서 1000/60 밀리초인 비동기화 타이밍으로 조절할 경우 이전 프레임에서 동일한 작업이 수행된 지 대략 그 정도 시간이 지났다고 간주하는 것이 타당합니다.)

가이드(메인) 스레드의 애니메이션에도 동일한 접근 방식이 적용됩니다. 가이드 스레드와 렌더 스레드 간의 필수적인 데이터 동기화로 인해 가이드 스레드는 렌더 스레드와 동일한 속도로 효과적으로 스로틀되며, 렌더링 준비 작업의 대부분이 렌더 스레드로 오프로드되므로 애플리케이션 로직에 더 많은 헤드룸을 남겨주는 동시에 할 일이 줄어든다는 이점을 누릴 수 있습니다.

위의 예에서는 초당 60프레임을 사용했지만 Qt Quick 은 다른 재생률에 대해서도 준비되어 있습니다. QScreen 과 플랫폼에서 재생률을 쿼리합니다. 예를 들어 144Hz 화면의 경우 간격은 6.94ms입니다. 렌더 루프가 생각하는 것과 실제가 일치하지 않으면 잘못된 애니메이션 페이싱이 발생하기 때문에 동기화 기반 스로틀링이 예상대로 작동하지 않는 경우 문제가 발생할 수 있습니다.

참고: Qt 6.5부터 스레드 렌더 루프는 경과된 시간만을 기준으로 다른 애니메이션 드라이버를 선택할 수 있습니다(QElapsedTimer). 이 기능을 사용하려면 QSG_USE_SIMPLE_ANIMATION_DRIVER 환경 변수를 0이 아닌 값으로 설정하세요. 이렇게 하면 창이 여러 개일 때 QTimer 로 되돌아가기 위한 인프라가 필요하지 않고, 동기화 기반 스로틀링이 누락되거나 중단되었는지 여부를 판단하는 휴리스틱이 필요하지 않으며, 동기화 스로틀링의 모든 종류의 시간적 드리프트와 호환되고, 기본 화면의 재생률에 묶이지 않으므로 멀티스크린 설정에서 더 잘 작동할 수 있다는 이점이 있습니다. 또한 동기화 기반 스로틀링이 중단되거나 비활성화된 경우에도 렌더 스레드 애니메이션( Animator 유형)을 올바르게 구동합니다. 반면에 이 방식을 사용하면 애니메이션이 덜 부드럽게 인식될 수 있습니다. 호환성을 염두에 두고 현재 이 기능은 옵트인 기능으로 제공됩니다.

요약하면, threaded 렌더 루프는 다음 조건이 충족되는 한 끊김이 적은 부드러운 애니메이션을 제공할 것으로 예상됩니다:

  • 화면에 정확히 하나의 창( QQuickWindow 에서와 같이)이 있을 경우.
  • 언더링 그래픽 및 디스플레이 스택에서 VSync 기반 스로틀링이 예상대로 작동합니다.

표시되는 창이 하나도 없거나 두 개 이상이면 어떻게 하나요?

예를 들어 QQuickWindow 이 최소화되거나(Windows) 완전히 가려져(macOS) 렌더링 가능한 창이 없는 경우 프레임을 표시할 수 없으므로 화면 재생률에 맞춰 "작동하는" 스레드에 의존할 수 없습니다. 이 경우 threaded 렌더 루프는 애니메이션을 구동하기 위해 시스템 타이머 기반 접근 방식, 즉 basic 루프가 사용하는 메커니즘으로 일시적으로 전환됩니다.

화면에 QQuickWindow 인스턴스가 두 개 이상 있는 경우에도 마찬가지입니다. 렌더 스레드와의 동기화를 통해 GUI 스레드에서 애니메이션을 진행하는 위에 제시된 모델은 이제 여러 렌더 스레드와 여러 동기화 지점이 있기 때문에 더 이상 만족스럽지 않습니다. (창당 하나씩) 여기서도 시스템 타이머 기반 접근 방식으로 돌아가는 것이 필요한데, 그 이유는 이제 가이 스레드가 얼마나 오래 그리고 얼마나 자주 차단될지는 창에 있는 콘텐츠(애니메이션이 있는가? 얼마나 자주 업데이트되는가?) 및 그래픽 스택 동작(대기-대동기 상태로 표시되는 둘 이상의 스레드를 정확히 어떻게 처리하는가?) 등 여러 요인에 따라 달라지기 때문입니다. 안정적인 크로스 플랫폼 방식으로 창의 표시 속도(어떤 창부터 시작해야 할까요?)에 맞춰 조절되는 것을 보장할 수 없으므로 렌더링을 기반으로 애니메이션을 진행할 수 없습니다.

이러한 애니메이션 처리 메커니즘의 전환은 애플리케이션에 투명하게 공개됩니다.

동기화 기반 스로틀링이 제대로 작동하지 않거나 전역적으로 비활성화되었거나 애플리케이션이 자체적으로 비활성화한 경우에는 어떻게 해야 할까요?

threaded 렌더 루프는 그래픽 API 구현 및/또는 스로틀링을 위한 윈도우 시스템에 의존합니다(예: OpenGL(GLX, EGL, WGL)의 경우 1의 스왑 간격을 요청하거나 Direct 3D의 경우 1 간격으로 Present()를 호출하거나 Vulkan에서 프레젠테이션 모드 FIFO 를 사용하는 등의 방식으로 스로틀링합니다.

일부 그래픽 드라이버는 사용자가 이 설정을 재정의하여 Qt의 요청을 무시하고 해제할 수 있도록 허용합니다. 예를 들어 그래픽 드라이버의 시스템 전체 제어판에서 동기화와 관련된 애플리케이션의 설정을 재정의할 수 있는 경우가 이에 해당합니다. 그래픽 스택이 적절한 비동기화 기반 스로틀링을 제공하지 못할 수도 있는데, 이는 일부 가상 머신에서 발생할 수 있습니다(주로 소프트웨어 래스터화 기반 OpenGL 또는 Vulkan 구현 사용으로 인해 발생).

스왑/프레젠트 연산(또는 다른 그래픽 연산)을 차단하지 않으면 이러한 렌더 루프는 애니메이션을 너무 빠르게 진행시킬 수 있습니다. basic 렌더 루프는 항상 시스템 타이머에 의존하기 때문에 문제가 되지 않습니다. threaded 에서는 Qt 버전에 따라 동작이 달라질 수 있습니다:

  • 시스템이 비동기 기반 스로틀링을 제공할 수 없는 것으로 알려진 경우, Qt 6.4 이전에는 애플리케이션을 실행하기 전에 환경에서 QSG_RENDER_LOOP=basic 을 수동으로 설정하여 basic 렌더 루프를 사용하는 것이 유일한 옵션이었습니다.
  • Qt 6.4부터는 QSG_NO_VSYNC 환경 변수를 0이 아닌 값으로 설정하거나 창의 QSurfaceFormat::swapInterval()을 0 로 설정하면 이 문제를 완화할 수 있습니다. 실제로 어떤 영향을 미치는지 여부와 관계없이 명시적으로 vsync 기반 차단 비활성화를 요청하면 threaded 렌더 루프가 애니메이션을 구동하기 위해 vsync에 의존하는 것이 쓸데없다는 것을 인식하고 둘 이상의 창에서와 마찬가지로 시스템 타이머를 사용하는 것으로 돌아갈 수 있기 때문입니다.
  • 더 좋은 점은 Qt 6.4부터 시나리오 그래프가 몇 가지 간단한 휴리스틱을 사용하여 프레임이 "너무 빨리" 표시되고 있다는 것을 인식하고 필요한 경우 시스템 타이머로 자동 전환하려고 시도한다는 것입니다. 즉, 대부분의 경우 아무것도 할 필요가 없으며 기본 렌더링 루프가 threaded 인 경우에도 애플리케이션이 예상대로 애니메이션을 실행합니다. 애플리케이션에는 투명하지만 문제 해결 및 개발 목적으로 QSG_INFO 또는 qt.scenegraph.general 가 활성화되면 "Window 0x7ffc8489c3d0 is determined to have broken vsync throttling ..." 메시지가 인쇄되어 기록된다는 점을 알아두면 유용합니다. 이 방법은 평가할 데이터를 먼저 수집해야 하므로 작은 프레임 세트 후에만 활성화된다는 단점이 있으며, 이는 QQuickWindow 을 열 때 애플리케이션이 짧은 시간 동안 지나치게 빠른 애니메이션을 표시할 수 있다는 것을 의미합니다. 또한 가능한 모든 동기화 해제 상황을 캡처하지 못할 수도 있습니다.

그러나 설계상 이 중 어느 것도 스레드 애니메이션( Animator 유형)을 렌더링하는 데 도움이 되지 않는다는 점을 기억하세요. vsync 기반 차단이 없는 경우 일반 animations 에 대해 해결 방법이 활성화되어 있어도 기본적으로 animators 이 예상보다 빠르게 잘못 진행됩니다. 이 문제가 발생하면 QSG_USE_SIMPLE_ANIMATION_DRIVER 을 설정하여 대체 애니메이션 드라이버를 사용하는 것이 좋습니다.

참고: 두 렌더링 루프 모두 QWindow::requestUpdate()를 통해 창에 대한 업데이트를 예약하므로 vsync 대기를 비활성화하더라도 GUI(메인) 스레드의 렌더링 루프 로직 및 이벤트 처리가 반드시 스로틀이 해제되는 것은 아닙니다. 이는 대부분의 플랫폼에서 이벤트 처리를 위한 시간을 확보하기 위해 5ms GUI 스레드 타이머로 지원됩니다. macOS와 같은 일부 플랫폼에서는 새 프레임을 준비할 적절한 시간에 대한 알림을 받기 위해 플랫폼별 API(예: CVDisplayLink)를 사용하며, 이는 어떤 형태로든 디스플레이의 동기화와 연결되어 있을 가능성이 높습니다. 이는 벤치마킹 및 이와 유사한 상황에서 유용할 수 있습니다. 낮은 수준의 벤치마킹을 수행하려는 애플리케이션 및 도구의 경우 QT_QPA_UPDATE_IDLE_TIME 환경 변수를 0 으로 설정하면 GUI 스레드의 유휴 시간을 줄이는 데 도움이 될 수 있습니다. 일반적인 애플리케이션 사용의 경우 대부분의 경우 기본값으로 충분합니다.

참고: 확실하지 않은 경우 문제 해결을 위해 qt.scenegraph.generalqt.scenegraph.time.renderloop 로깅 카테고리를 활성화하면 렌더링 및 애니메이션이 예상 속도로 실행되지 않는 이유에 대한 단서를 찾을 수 있습니다.

QQuickRenderControl로 렌더링에 대한 사용자 지정 제어

QQuickRenderControl 을 사용하면 렌더링 루프를 구동하는 책임이 애플리케이션으로 이전됩니다. 이 경우 내장된 렌더링 루프가 사용되지 않습니다. 대신 적절한 시점에 폴리싱, 동기화 및 렌더링 단계를 호출하는 것은 애플리케이션의 몫입니다. 위에 표시된 것과 유사한 스레드 또는 비스레드 동작을 구현할 수 있습니다.

또한 애플리케이션은 QQuickRenderControl 와 함께 자체 QAnimationDriver를 구현하고 설치할 수 있습니다. 이렇게 하면 화면에 표시되지 않는 콘텐츠에 특히 중요할 수 있는 Qt Quick 애니메이션을 완벽하게 제어할 수 있으며, 프레임이 표시되지 않기 때문에 표시 속도와는 관련이 없습니다. 이 옵션은 선택 사항이며 기본적으로 애니메이션은 시스템 타이머에 따라 진행됩니다.

QRhi-기반 및 네이티브 3D 렌더링으로 씬 그래프 확장하기

씬 그래프는 애플리케이션에서 제공하는 그래픽 명령을 통합하는 세 가지 방법을 제공합니다:

  • 씬 그래프의 자체 렌더링 전후에 QRhi-기반 또는 OpenGL, Vulkan, Metal, Direct3D 명령을 직접 실행합니다. 이는 사실상 메인 렌더링 패스에 일련의 그리기 호출을 추가하거나 추가하는 것과 같습니다. 추가 렌더 타깃은 사용되지 않습니다.
  • 텍스처로 렌더링하고 씬 그래프에 텍스처 노드를 생성합니다. 여기에는 추가 렌더 패스 및 렌더 타깃이 포함됩니다.
  • 씬 그래프에서 QSGRenderNode 서브클래스를 인스턴스화하여 씬 그래프의 자체 렌더링과 인라인으로 드로우 호출을 발행합니다. 이는 첫 번째 접근 방식과 유사하지만 사용자 지정 그리기 호출이 씬 그래프의 명령 스트림에 효과적으로 주입됩니다.

언더레이/오버레이 모드

애플리케이션은 QQuickWindow::beforeRendering() 및 QQuickWindow::afterRendering() 신호에 연결하여 씬 그래프가 렌더링되는 것과 동일한 컨텍스트로 직접 QRhi 또는 네이티브 3D API 호출을 수행할 수 있습니다. 벌칸이나 메탈과 같은 API를 사용하는 애플리케이션은 QSGRendererInterface 을 통해 씬 그래프의 명령 버퍼와 같은 네이티브 오브젝트를 쿼리하고 적절하다고 판단되는 명령을 기록할 수 있습니다. 그러면 신호 이름에서 알 수 있듯이 사용자는 Qt Quick 씬 아래 또는 그 위에 콘텐츠를 렌더링할 수 있습니다. 이러한 방식으로 통합하면 렌더링을 수행하는 데 추가 렌더 타깃이 필요하지 않으며 비용이 많이 드는 텍스처링 단계가 필요하지 않다는 이점이 있습니다. 단점은 사용자 지정 렌더링이 Qt Quick 자체 렌더링의 시작 또는 끝에서만 실행될 수 있다는 것입니다. QQuickWindow 신호 대신 QSGRenderNode 을 사용하면 이러한 제한을 다소 완화할 수 있지만, 두 경우 모두 뎁스 테스트와 뎁스 쓰기를 활성화한 렌더링에 의존하면 사용자 지정 콘텐츠와 Qt Quick 콘텐츠의 뎁스 버퍼 사용이 서로 충돌하는 상황이 쉽게 발생할 수 있으므로 3D 콘텐츠와 뎁스 버퍼 사용에 주의를 기울여야 합니다.

Qt 6.6부터는 QRhi API가 준공개로 간주되어 애플리케이션에 제공되고 문서화되지만 호환성이 제한적으로 보장됩니다. 이를 통해 씬 그래프 자체에서 사용하는 것과 동일한 그래픽과 셰이더 추상화를 사용하여 이식 가능한 크로스 플랫폼 2D/3D 렌더링 코드를 만들 수 있습니다.

그래프 - RHI Under QML 예제에서는 QRhi 을 사용하여 언더레이/오버레이 접근 방식을 구현하는 방법에 대한 예제를 제공합니다.

그래프 - OpenGL Under QML 예제에서는 OpenGL을 사용하여 이러한 신호를 사용하는 방법에 대한 예제를 제공합니다.

그래프 - Direct3D 11 Under QML 예제에서는 Direct3D를 사용하여 이러한 신호를 사용하는 방법에 대한 예제를 제공합니다.

그래프 - 메탈 아래 QML 예제에서는 메탈을 사용하여 이러한 신호를 사용하는 방법에 대한 예제를 제공합니다.

그래프 - 벌칸 아래 QML 예제에서는 벌칸을 사용하여 이러한 신호를 사용하는 방법에 대한 예제를 제공합니다.

Qt 6.0부터는 기본 그래픽 API를 직접 사용하려면 QQuickWindow::beginExternalCommands() 및 QQuickWindow::endExternalCommands() 호출로 묶어야 합니다. 이 개념은 QPainter::beginNativePainting()에서 익숙할 수 있으며 비슷한 용도로 사용됩니다. 즉, 애플리케이션 코드가 기본 그래픽 API를 직접 사용하여 변경했을 수 있으므로 현재 기록된 렌더 패스 내의 캐시된 상태 및 상태에 대한 가정(있는 경우)이 이제 유효하지 않음을 Qt Quick 씬 그래프가 인식할 수 있도록 합니다. QRhi 을 사용할 때는 적용되지 않으며 필요하지 않습니다.

사용자 지정 OpenGL 렌더링과 씬 그래프를 혼합할 때는 애플리케이션이 버퍼가 바인딩되거나, 속성이 활성화되거나, z 버퍼 또는 스텐실 버퍼에 특수 값이 있는 상태 또는 이와 유사한 상태로 OpenGL 컨텍스트를 떠나지 않도록 하는 것이 중요합니다. 그렇게 하면 예측할 수 없는 동작이 발생할 수 있습니다.

사용자 지정 렌더링 코드는 애플리케이션의 GUI(메인) 스레드에서 실행되는 것으로 가정해서는 안 된다는 의미에서 스레드 인식이 가능해야 합니다. QQuickWindow 신호에 연결할 때 애플리케이션은 Qt::DirectConnection 을 사용해야 하며, 연결된 슬롯이 씬 그래프의 전용 렌더 스레드(있는 경우)에서 호출된다는 점을 이해해야 합니다.

텍스처 기반 접근 방식

텍스처 기반 대안은 애플리케이션이 Qt Quick 씬 내에서 일부 사용자 지정 3D 렌더링의 "평면화된" 2D 이미지를 가져야 할 때 가장 유연한 접근 방식입니다. 또한 메인 렌더 패스에서 사용하는 버퍼와 독립적인 전용 뎁스/스텐실 버퍼를 사용할 수 있습니다.

OpenGL을 사용하는 경우 레거시 편의 클래스 QQuickFramebufferObject 를 사용하여 이를 달성할 수 있습니다. QRhi-기반 커스텀 렌더러 및 OpenGL 이외의 그래픽 API도 이 접근 방식을 따를 수 있지만 QQuickFramebufferObject 은 현재 지원하지 않습니다. 다음 예시에서는 기본 API로 직접 텍스처를 생성하고 렌더링한 다음 이 리소스를 커스텀 QQuickItemQt Quick 씬에서 래핑하여 사용하는 방법을 보여 줍니다:

씬 그래프 - RHI 텍스처 항목 예시.

씬 그래프 - 벌칸 텍스처 임포트 예시.

씬 그래프 - 메탈 텍스처 임포트 예제.

인라인 접근 방식

QSGRenderNode 을 사용하면 커스텀 드로 콜이 씬 그래프의 렌더 패스 기록의 시작이나 끝이 아니라 씬 그래프의 렌더링 프로세스 중에 주입됩니다. 이는 QSGRenderNode 의 인스턴스를 기반으로 커스텀 QQuickItem 을 생성하여 QRhi 또는 OpenGL, Vulkan, Metal 또는 Direct 3D와 같은 네이티브 3D API를 통해 그래픽 명령을 실행할 수 있도록 특별히 존재하는 씬 그래프 노드를 통해 달성할 수 있습니다.

씬 그래프 - 커스텀 QSGRenderNode 예제에서 이 접근 방식을 시연합니다.

QPainter를 사용한 커스텀 아이템

QQuickItem 은 사용자가 QPainter 을 사용하여 콘텐츠를 렌더링할 수 있는 서브클래스 QQuickPaintedItem 를 제공합니다.

경고: QQuickPaintedItem 을 사용하면 소프트웨어 래스터화 또는 OpenGL 프레임버퍼 객체(FBO)를 사용하여 간접 2D 서페이스를 사용하여 콘텐츠를 렌더링하므로 렌더링은 2단계 작업입니다. 먼저 표면을 래스터화한 다음 표면을 그립니다. 씬 그래프 API를 직접 사용하면 항상 훨씬 빠릅니다.

로깅 지원

씬 그래프는 다양한 로깅 카테고리를 지원합니다. 이는 성능 문제와 버그를 추적하는 데 유용할 뿐만 아니라 Qt 기여자에게도 도움이 될 수 있습니다.

  • qt.scenegraph.time.texture - 텍스처 업로드에 소요된 시간을 기록합니다.
  • qt.scenegraph.time.compilation - 셰이더 컴파일에 소요된 시간을 기록합니다.
  • qt.scenegraph.time.renderer - 렌더러의 다양한 단계에 소요된 시간을 기록합니다.
  • qt.scenegraph.time.renderloop - 렌더링 루프의 다양한 단계에서 소요된 시간을 기록합니다. threaded 렌더 루프를 사용하면 GUI와 렌더 스레드 모두에서 다양한 프레임 준비 단계 사이에 경과된 시간을 파악할 수 있습니다. 따라서 예를 들어 vsync 기반 스로틀링과 QWindow::requestUpdate()와 같은 기타 저수준 Qt 인에이블러가 렌더링 및 프레젠테이션 파이프라인에 어떤 영향을 미치는지 확인하는 데 유용한 문제 해결 도구가 될 수도 있습니다.
  • qt.scenegraph.time.glyph - 거리 필드 글리프를 준비하는 데 소요된 시간을 기록합니다.
  • qt.scenegraph.general - 씬 그래프와 그래픽 스택의 다양한 부분에 대한 일반 정보를 기록합니다.
  • qt.scenegraph.renderloop - 렌더링에 관련된 다양한 단계에 대한 자세한 로그를 생성합니다. 이 로그 모드는 주로 Qt로 작업하는 개발자에게 유용합니다.

레거시 QSG_INFO 환경 변수도 사용할 수 있습니다. 0이 아닌 값으로 설정하면 qt.scenegraph.general 카테고리가 활성화됩니다.

참고: 그래픽 문제가 발생하거나 사용 중인 렌더 루프 또는 그래픽 API가 확실하지 않은 경우 항상 qt.scenegraph.generalqt.rhi.* 또는 QSG_INFO=1 를 활성화한 상태에서 애플리케이션을 시작하세요. 그러면 초기화 중에 몇 가지 필수 정보가 디버그 출력에 인쇄됩니다.

씬 그래프 백엔드

공개 API 외에도 씬 그래프에는 하드웨어별 적응을 수행할 수 있도록 구현을 열어주는 적응 계층이 있습니다. 이는 문서화되지 않은 내부 및 비공개 플러그인 API로, 하드웨어 적응 팀이 하드웨어를 최대한 활용할 수 있도록 해줍니다. 여기에는 다음이 포함됩니다:

  • 사용자 정의 텍스처, 특히 QQuickWindow::createTextureFromImage 구현과 ImageBorderImage 유형에서 사용하는 텍스처의 내부 표현.
  • 커스텀 렌더러; 적응 레이어를 사용하면 플러그인이 씬 그래프를 탐색하고 렌더링하는 방법을 결정하여 특정 하드웨어에 맞게 렌더링 알고리즘을 최적화하거나 성능을 향상시키는 확장 기능을 사용할 수 있습니다.
  • 텍스트 및 글꼴 렌더링을 포함한 여러 기본 QML 유형의 사용자 지정 씬 그래프 구현.
  • 사용자 지정 애니메이션 드라이버; 애니메이션 시스템이 낮은 수준의 디스플레이 수직 새로 고침에 연결하여 부드러운 렌더링을 얻을 수 있습니다.
  • 사용자 지정 렌더 루프; QML이 여러 창을 처리하는 방식을 더 잘 제어할 수 있습니다.

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.