From 2c1e29780da41bba13ac84ad01472d550e6d02e8 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sun, 27 Sep 2026 14:21:39 +0100 Subject: Draw text from an atlas the CPU fills, composed by the GPU Each size's symbols are drawn on the CPU into one 8-bit atlas, every pixel the exact area its curves cover, and text is a quad per glyph reading it. A 1080p page of 11,382 glyphs composes in 1.07 ms at 16 px (it took 525 ms computing coverage per fragment), and the composed pixels are the CPU raster's exactly (worst difference 0 for DejaVu's ASCII, 1 for Noto's 11,172 Hangul syllables). Computing coverage on the GPU lost on both counts: a compute pass filling the atlas took 5-13x longer than one Cortex-A76 core (35 against 2.7 ms at 16 px), and its faster sorting-network form computed whole rows wrongly under this Mesa V3D. Coverage now has one implementation, in C, and the shaders only compose. vega_symbol_raster takes a row stride to draw into the atlas in place. --- docs/design/002-symbol-format.md | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) (limited to 'docs/design/002-symbol-format.md') diff --git a/docs/design/002-symbol-format.md b/docs/design/002-symbol-format.md index 3d9074b..253b37c 100644 --- a/docs/design/002-symbol-format.md +++ b/docs/design/002-symbol-format.md @@ -94,27 +94,35 @@ em² 대비 평균 2.6e-5, 최악 1.5e-4 차이다. SVG는 Adwaita 기호 아이 ## GPU (Raspberry Pi 5 V3D 7.1, OpenGL ES 3.1, Mesa 26.2.2) -`symbols/symbols-gl.c`는 파일의 점 표를 그대로 저장 버퍼 하나로 올리고, 심볼의 상자를 덮는 사각형(인스턴스 하나)의 픽셀마다 -`vega_coverage`와 같은 계산을 한다. 올릴 때 심볼마다 가로 띠 16개를 만들어 띠마다 걸치는 곡선(을 시작하는 점의 번호)을 적는다. -픽셀은 제 행이 걸친 띠의 곡선만 읽고, 여러 띠에 걸친 곡선은 첫 띠에서만 센다. 띠 소속은 두 배 좌표의 정수 나눗셈이라 CPU와 -셰이더가 같게 가른다. 넓이는 행을 벗어난 곡선을 거르지 않고 왼쪽 곡선도 dy로 세야 하므로 띠는 가로로만 자른다. +`symbols/symbols-gl.c`: 한 크기에서 쓸 심볼들을 CPU가 아틀라스(8비트 한 장)에 정확 넓이로 그리고(`vega_symbol_raster`), 글은 +그 아틀라스를 읽는 사각형(글리프마다 인스턴스 하나, 정수 픽셀의 펜 위치)으로 GPU가 합성한다. 크기가 계속 바뀌는 경우(확대 +애니메이션)는 그 크기의 아틀라스를 다시 그린다. 경로는 하나다. -`vega-symbols gpu`로 잰 것(표면 없는 EGL 문맥, 8비트 빨강 대상): +`vega-symbols gpu`로 잰 것(표면 없는 EGL 문맥). 심볼마다 제 칸의 펜 위치에 합성한 것을 읽어 같은 픽셀의 CPU 래스터와 댄다. | | 12px | 16px | 32px | 64px | |---|---|---|---|---| -| CPU 참조와 평균 차이 / 최악 | 0.095 / 3 | 0.096 / 4 | 0.001 / 1 | 0.001 / 1 | -| 1920×1080 한 쪽(글리프 수) | 19,779 | 11,029 | 2,698 | 647 | -| 한 쪽 그리기 | 757 ms | 525 ms | 249 ms | 142 ms | +| DejaVu ASCII: 합성과 CPU 래스터의 최악 차이 | 0 | 0 | 0 | 0 | +| 아틀라스 채우기(95자, CPU 한 스레드) | 2.1 ms | 2.7 ms | 6.9 ms | 20.9 ms | +| 1920×1080 한 쪽 합성(글리프 수) | 1.48 ms (20,212) | 1.07 ms (11,382) | 0.61 ms (2,805) | 0.44 ms (645) | +| Noto 한글 11,172자: 채우기 / 한 쪽 합성 / 최악 | 0.68 s / 1.56 ms / 0 | 1.06 s / 1.45 ms / 1 | 1.83 s(24px) / 1.00 ms / 0 | | -맞게 그리지만 매 프레임 직접 그리기에는 수십 배 느리다. 넓이 함수를 사소한 식으로 바꾸면 16px 쪽이 91 ms라서 시간의 83%가 -넓이 계산이다. 셰이더의 교차점 배열(`float ts[10]`, 동적 첨자와 삽입 정렬)을 레지스터 안의 정렬망(vec4 min/max)으로 바꾸면 -2.3배 빨라지지만(229 ms), 이 드라이버에서 프래그먼트 셰이더로 컴파일하면 몇몇 행 전체가 틀린 값을 낸다(최악 255). 같은 함수를 -계산 셰이더에서 돌리면 무작위 곡선 백만 개에서 CPU와 5.7e-6 안에서 같고, 프래그먼트의 `main` 전체를 계산 셰이더로 옮겨도 틀린 -픽셀이 없다. 그래서 프래그먼트 쪽은 정렬망 없이 두었다. +이렇게 되기까지 GPU로 넓이를 계산하는 두 길을 쟀다. 둘 다 CPU보다 느렸다. -다음: 크기마다 글리프를 한 번 계산 셰이더로 아틀라스에 그리고(여기서는 정렬망이 맞다), 글은 아틀라스를 읽는 사각형으로 그린다. -UI의 글은 크기가 몇 가지뿐이므로 거의 모든 프레임이 텍스처 읽기다. 크기가 계속 바뀌는 경우(확대 애니메이션)만 직접 그리기를 쓴다. +- **프래그먼트마다 직접.** 심볼의 상자를 덮는 사각형의 픽셀마다 띠(가로 16개, 올릴 때 CPU가 곡선을 나눠 적는다) 안의 + 곡선의 넓이를 더했다. CPU 참조와 평균 0.001–0.1단계로 맞았지만 1080p 한 쪽이 142–757 ms였다. 시간의 83%가 넓이 + 계산이었다. 교차점 배열(`float ts[10]`, 동적 첨자와 삽입 정렬)을 레지스터 안의 정렬망(vec4 min/max)으로 바꾸면 2.3배 + 빨라졌지만 이 드라이버에서 몇몇 행 전체가 틀린 값을 냈다. +- **계산 셰이더로 아틀라스.** 같은 정렬망 판이 정수 픽셀에 맞춘 격자에서(글리프의 기준선이 픽셀 경계와 겹친다) 틀렸다. + 기준선 바로 아래 행이 통째로 달랐다. 같은 곡선 입력을 C로 옮긴 판과, 넓이 함수 하나만 도는 계산 셰이더는 맞게 계산한다. + 그 함수를 띠의 반복문 안에 넣은 셰이더에서만 틀리므로 드라이버 컴파일러 쪽으로 본다(최소 재현은 아직 없다). 삽입 정렬 판은 + 맞지만 16px 95자에 35 ms, 64px에 119 ms였다. CPU(Cortex-A76 한 코어)는 같은 일을 2.7 ms와 20.9 ms에 한다. + +그래서 넓이는 C 구현 하나로 계산하고, GPU는 합성만 한다. 셰이더에 넓이 계산이 없어 드라이버 결함에 걸릴 곳도 없다. +GPU 계산이 빠른 장치에서 다시 잴 수 있도록 측정은 남긴다(이 문서와 커밋 기록). + +다음: 필요할 때 글리프를 더하는 아틀라스(화면에 나온 것만 굽기). 한글 한 글리프의 곡선은 약 54개이고 CPU 래스터는 픽셀마다 +모든 곡선을 본다. 행마다 걸치는 곡선만 보면 몇 배 빨라질 것이다. ## 도구 @@ -123,5 +131,5 @@ UI의 글은 크기가 몇 가지뿐이므로 거의 모든 프레임이 텍스 호(`A`)는 4분의 1 바퀴 이하의 3차 곡선으로, 3차 곡선은 2차 곡선으로 나눈다. 칠해진 경로만 받는다. 변환, 불투명도, evenodd, 획, 경로 밖의 도형을 쓰는 아이콘은 이름을 대고 거부한다(빠뜨리고 그리면 틀린 그림이 된다). 해석은 여기서 한 번이다. `compare`(FreeType과 픽셀 차이, 여는 시간, 윤곽 하나에 닿는 시간), `info`(심볼마다 점·곡선 수, 경계, 넓이), `render`(PGM), -`gpu`(GPU와 CPU 참조의 차이, 한 쪽 그리는 시간)는 +`gpu`(GPU가 합성한 것과 CPU 래스터의 차이, 아틀라스를 채우고 한 쪽을 합성하는 시간)는 검증과 측정용이다. `tests/check.py`의 10번이 이것들로 형식을 지킨다. -- cgit v1.2.3