Quick Flow
compute writes light list buffer
-> write completion and visibility barrier
-> lighting pass reads light list
offscreen pass writes HDR texture
-> target-write to shader-read transition
-> bloom pass samples HDR texture| 필요한 보장 | 질문 | 예 |
|---|---|---|
| Execution order | writer가 reader보다 먼저 끝나는가 | compute dispatch 뒤 graphics draw |
| Memory visibility | writer의 값이 reader에게 보이는가 | storage write 뒤 shader read |
| Usage / layout | resource가 다음 용도에 맞는 상태인가 | render target에서 sampled texture로 전환 |
| Aliasing ownership | 같은 메모리를 다른 resource가 쓰기 시작하는가 | transient A의 마지막 사용 뒤 B 생성 |
barrier가 표현하는 것
현대 explicit graphics API에서는 resource 상태 추적 책임이 드라이버에서 애플리케이션 쪽으로 많이 이동했습니다. texture 하나가 어느 pass에서는 render target으로 쓰이고, 다음 pass에서는 shader resource로 읽히며, 또 다른 pass에서는 copy source가 될 수 있습니다. 이 역할 변경을 명시하지 않으면 GPU가 이전 write를 언제 끝냈는지, 다음 read가 어떤 layout과 cache 상태를 기대하는지 알기 어렵습니다.
Direct3D 12의 resource barrier는 이런 상태 변화를 드라이버에 알리는 API입니다. transition barrier는 subresource가 다른 usage로 넘어갈 때 쓰고, UAV barrier는 unordered access write와 이후 접근의 순서가 중요할 때 쓰며, aliasing barrier는 같은 heap 영역을 겹쳐 쓰는 resource 사이를 구분할 때 씁니다. Vulkan의 barrier는 stage와 access mask, image layout, queue ownership을 조합해 같은 정보를 표현합니다.
G-buffer pass
normal texture: RENDER_TARGET
lighting pass
normal texture: PIXEL_SHADER_RESOURCEresource: normalTexture mip 0
writer: G-buffer fragment output
reader: lighting fragment sampling
scope: mip 0만, 해당 pass 사이만barrier는 resource 전체를 막는 주문이 아닙니다. 누가 어떤 stage에서 어떤 subresource를 write했고, 누가 다음에 어떤 stage에서 read/write하는지를 좁게 연결하는 선언입니다. image의 한 mip만 생성했는데 모든 mip/layer에 transition을 걸면 correctness는 유지할 수 있어도 불필요한 동기화와 추적 복잡도가 생길 수 있습니다.
barrier는 정합성과 성능 비용을 함께 만든다
Barrier가 없으면 결과가 매번 깨지지 않을 수도 있습니다. GPU 스케줄링, 캐시 상태, 프레임 타이밍에 따라 가끔만 깜빡이거나 특정 하드웨어에서만 재현될 수 있습니다. 반대로 barrier를 너무 넓게 잡으면 필요 없는 대기와 cache flush가 생겨 GPU 병렬성이 줄어듭니다.
상태별 판단
| 전환 | 선언해야 할 writer와 reader |
|---|---|
| render target -> sampling | color/depth attachment write -> fragment/compute shader read |
| copy destination -> sampling | transfer write -> shader read |
| storage buffer write -> indirect draw | compute write -> draw-indirect/vertex shader read |
| storage image write -> storage image read/write | 이전 storage access -> 다음 storage access |
| graphics queue -> compute queue | queue signal -> wait와 resource ownership |
| transient A -> transient B alias | A의 마지막 access -> B의 첫 access |
render graph를 쓰는 경우에도 이 논리가 사라지지 않습니다. pass의 read/write와 imported resource의 초기·최종 state를 정확히 선언하면 graph가 backend에 맞는 transition을 만들 수 있습니다. graph 밖 명령으로 resource를 만지거나 declaration을 빼면 자동 추적의 근거가 없어집니다.
재현하기 어려운 오류
Barrier 누락은 매 프레임 깨지지 않을 수 있습니다. GPU 스케줄, cache, driver, 해상도에 따라 이전 frame 데이터가 섞이거나 특정 GPU에서만 flicker할 수 있습니다. 반대로 넓은 stage/access 범위와 불필요한 queue wait는 GPU 병렬성을 줄입니다.
| 증상 | 먼저 확인할 것 |
|---|---|
| texture가 간헐적으로 검거나 이전 값 | writer access, reader access, image layout/state, clear 여부 |
| GPU validation의 layout/state 경고 | 실제 command 순서와 subresource range |
| async compute를 켰는데 더 느림 | graphics/compute queue 사이 wait와 overlap 구간 |
| transient target에서 쓰레기 값 | aliasing lifetime, store/load, 첫 write 전에 clear가 있는지 |
| indirect draw count가 가끔 틀림 | compute write와 indirect argument read 사이 barrier |
참고 링크
2 sources