jayinlab

이 블로그의 콘텐츠는 AI가 작성·정리합니다.

clFinish의 내부 구현 — Fence, Semaphore, Event 완전 분해

2026-04-13

clFinish(queue) — “큐의 모든 명령이 끝날 때까지 기다려라.” 한 줄짜리 API지만, 내부에서는 Vulkan Fence, PM4 EVENT_WRITE, OS 대기 기본 요소들이 연쇄적으로 동작한다.


clFinish() 내부 연쇄 흐름 시뮬레이터
Step 0 / 7
시작 "다음 단계"를 눌러 clFinish()의 내부를 따라가세요.
App
(CPU)
대기 중
Driver
(ANGLE)
대기 중
GPU
Command
대기 중
GPU
Execution
대기 중
OS /
Kernel
대기 중

전체 흐름

clFinish(queue)
  │
  ├─ ANGLE: vkQueueSubmit + VkFence 생성
  │         IT_EVENT_WRITE 패킷 ring buffer에 삽입
  │
  ├─ GPU: 모든 이전 명령 실행
  │       IT_EVENT_WRITE → fence memory 값 갱신
  │
  └─ CPU: vkWaitForFences → OS가 대기
          fence 값 변경 감지 → 스레드 재개

각 동기화 기본 요소

Fence — CPU가 GPU 완료를 기다릴 때

// Vulkan fence 흐름
VkFence fence;
vkCreateFence(device, &fenceInfo, NULL, &fence);

vkQueueSubmit(queue, 1, &submitInfo, fence);  // GPU 작업 제출 + fence 연결

vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);  // CPU 블로킹 대기
// ← 여기서 CPU 스레드가 잠든다 (OS 레벨 대기)
// GPU가 완료하면 fence에 signal → OS가 스레드 깨움

clFinish는 내부적으로 이 패턴을 사용한다.

sequenceDiagram
    participant App as Application (CPU)
    participant Driver as Driver
    participant GPU
    App->>Driver: clFinish(queue)
    Driver->>Driver: VkFence 생성
    Driver->>GPU: vkQueueSubmit (IT_EVENT_WRITE 포함)
    Driver->>App: vkWaitForFences (CPU 블로킹)
    Note over App: CPU 스레드 잠듦
    GPU->>GPU: 이전 명령 모두 실행
    GPU->>Driver: IT_EVENT_WRITE → fence memory 값 갱신
    Driver->>App: fence signal 감지 → 스레드 깨움
    Note over App: clFinish 반환

Semaphore — GPU와 GPU 사이 동기화

Fence가 CPU-GPU 사이라면, Semaphore는 GPU Queue와 Queue 사이다.

// VkSemaphore: Queue A 완료 후 Queue B 시작
vkQueueSubmit(queueA, 1, &submitA, VK_NULL_HANDLE);  // signal semaphore

VkSubmitInfo submitB = {
    .waitSemaphoreCount = 1,
    .pWaitSemaphores = &semaphore,    // queueA가 signal할 때까지 대기
    .pWaitDstStageMask = &waitStage,
};
vkQueueSubmit(queueB, 1, &submitB, fence);
sequenceDiagram
    participant QA as Queue A (GPU)
    participant QB as Queue B (GPU)
    participant CPU
    QA->>QA: 작업 실행
    QA->>QB: Semaphore signal (완료)
    QB-->>QB: Semaphore wait 해제
    QB->>QB: 작업 시작
    QB->>CPU: Fence signal

Event — 파이프라인 내부의 세밀한 동기화

VkEvent는 Fence/Semaphore보다 세밀하다. 파이프라인 내부의 특정 스테이지가 끝났는지를 체크한다.

vkCmdSetEvent(cmdBuf, event, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
// ... 다른 명령들 ...
vkCmdWaitEvents(cmdBuf, 1, &event,
    VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,   // src
    VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,   // dst
    ...);

IT_EVENT_WRITE — PM4 수준에서 무슨 일이 일어나는가

clFinishvkQueueSubmit → 드라이버가 ring buffer에 IT_EVENT_WRITE 패킷을 삽입한다.

IT_EVENT_WRITE 패킷:
  Header: Type=3, Opcode=0x46, Count=3
  EVENT_TYPE: CACHE_FLUSH_AND_INV  ← 캐시 flush + invalidate
  ADDRESS_LO: fence 메모리 주소 하위 32bit
  ADDRESS_HI: fence 메모리 주소 상위 32bit

GPU CP가 이 패킷을 처리하면:

  1. 이전 모든 명령의 메모리 쓰기가 flush된다
  2. 지정한 메모리 주소에 완료 값을 쓴다
  3. CPU는 이 메모리 주소를 polling하거나 OS interrupt로 감지한다

세 가지 동기화 기본 요소 비교

FenceSemaphoreEvent
대상CPU ↔ GPUGPU ↔ GPU (Queue 간)GPU 내부 (파이프라인 내)
세밀도submit 단위submit 단위stage 단위
CPU 블로킹O (vkWaitForFences)XX
OpenCL 대응clFinish, clWaitForEvents(없음, 내부 처리)clSetUserEventStatus
PM4 패킷IT_EVENT_WRITEIT_RELEASE_MEMIT_EVENT_WRITE

OpenCL 관점 정리

OpenCL API내부 동작
clFinish(queue)VkFence + vkWaitForFences (CPU 블로킹)
clFlush(queue)vkQueueSubmit (제출만, 대기 없음)
clWaitForEvents(events)VkEvent 또는 Fence의 조합
clEnqueueBarrierWithWaitListvkCmdPipelineBarrier

관련 글

관련 용어

[[command-queue]], [[command-buffer]], [[ring-buffer]], [[pm4-packet]]