ProtoPie renderer — renderer-tess 개발 문서 원문 텍스트 추출본 / 2026-09-14 # renderer-tess wgpu 렌더링 백엔드. Path / Text 는 Slug coverage, Rect / RoundedRect / Ellipse 는 analytic primitive 로 분기. WebGL2 + WebGPU 동시 지원. Runtime / 테스트 모두 lyon-free. ## Module layout ``` src/ ├── lib.rs # module declarations + pub use ├── benchmark.rs # bench corpus + variant flags + deterministic text resolver ├── pass_encoding.rs # 공유 pass encode context / first-pass clear colors ├── painter.rs # TessPainter — surface-free 렌더러 코어 │ └── painter/ │ ├── batching.rs # 노드 순회 → batch 생성 (primitive/curve/text 분기) │ ├── helpers.rs # AABB / affine / coverage helpers │ ├── types.rs # FrameStats / FrameCacheStats / TessError │ ├── upload.rs # frame-scoped GPU upload arena │ └── tests.rs ├── capture.rs # native-only capture (#[cfg(not(wasm32))]) ├── draw/ │ ├── batch.rs # RenderBatch + fill type 상수 │ ├── binding.rs # fill texture bind-group resolver │ ├── bounds.rs # LocalBounds for curve-backed geometry │ ├── curve.rs # CurveSegment IR (is_line) + path/text → curve 변환 │ ├── curve_atlas.rs # BG3 curve/band atlas (shape + glyph fronts) │ │ └── curve_atlas/{config, encoder, lifecycle, types, upload, tests}.rs │ ├── shape_cache.rs # path curve geometry cache (signature → Arc<[CurveSegment]>) │ ├── stroke.rs # StrokeExpansionPipeline + geometry re-export │ ├── stroke/ │ │ ├── expansion.rs # RFC-0203 expansion artifact + diagnostics │ │ ├── generic_stroker.rs # RFC-0202 internal stroker stages │ │ ├── generic_stroker/ │ │ │ ├── contract.rs # GenericStrokeRequest + stroke_into trait │ │ │ ├── dispatch.rs # multi-MoveTo split, outward sign 전파 │ │ │ ├── dash_path.rs # Budorick dash effect │ │ │ ├── path_measure.rs # arc-length walker │ │ │ ├── stroker.rs # by-construction stroker (caps + joins + offset rings) │ │ │ ├── cubic_offset.rs # Skia SkPathStroker cubic offset port (BSD-3) │ │ │ └── cubic_inflection.rs │ │ ├── geometry.rs # production geometry stage │ │ └── math.rs # map_path_point_to_node_space │ ├── text.rs # TextCurveResolver + TextSystem + ParleyTextResolver │ ├── uniforms.rs # FrameUniforms(BG0), DrawUniforms(BG1) │ └── vertex.rs # DrawQuadVertex (32B) ├── pipeline/ │ ├── mod.rs # PipelineCache (BlendMode × geometry-kernel) │ ├── blur_pipeline.rs # Gaussian + Dual Kawase │ └── blit_pipeline.rs # offscreen composite/blit └── shaders/ ├── shader_curve.wgsl # Slug coverage ├── shader_primitive.wgsl # analytic Rect/RoundedRect/Ellipse ├── shader_blit.wgsl # offscreen composite ├── shader_blur_gaussian.wgsl ├── shader_blur_kawase_down.wgsl └── shader_blur_kawase_up.wgsl ``` ## Public surface ```rust TessPainter ::new(gpu, config) // production (wgpu backend) ::new_with_rhi(rhi_device, rhi_queue, config) // GPU-free testing (mock RHI) begin_frame(width, height) submit_scene_with_strokes(scene, stroke_plans, resources) accumulate_fill_with_params(...) // single node fill into current batch accumulate_fill_local(...) // saveLayer 로컬 fill (offscreen) accumulate_stroke_with_params(scene, stroke_plans, ...) // planned stroke → Slug fill accumulate_stroke(scene, stroke_plans, resources, node_id) accumulate_composite(...) // offscreen → main composite flush_accumulated(encoder, view, load) // emit accumulated batches encode_blur_pass(...) // Gaussian H/V encode_kawase_down / encode_kawase_up // Dual Kawase ``` ## Bind group layout | BG | Type | Stride | Update | |----|----------------|--------|-------------------------------------| | 0 | FrameUniforms | 256B | per flush (frame ring) | | 1 | curve / primitive draw uniforms | 256B (dynamic) | per draw | | 2 | Fill texture | n/a | per draw (image / gradient) | | 3 | Curve atlas (shape or glyph) | n/a | per draw (curve geometry kernel only) | ## Fill type 상수 (`draw/batch.rs`) | 값 | 상수 | |----|-------------------------------| | 0 | `FILL_TYPE_SOLID` | | 1 | `FILL_TYPE_LINEAR_GRADIENT` | | 2 | `FILL_TYPE_RADIAL_GRADIENT` | | 3 | `FILL_TYPE_TEXTURE` | | 4 | `FILL_TYPE_ANGULAR_GRADIENT` | | 5 | `FILL_TYPE_TEXTURE_PREMULT` | ## Invariants - **Vertex color = `[1,1,1,1]`**. Geometry-only, 색상 정보 vertex 에 넣지 않음. - **Premultiplied alpha 일관**: fragment shader 가 premultiplied 출력 → BlendState `src: One, dst: OneMinusSrcAlpha`. - **Intermediate texture** 는 `FILL_TYPE_TEXTURE_PREMULT` (5) 로 샘플링, alpha 재곱 X. - **Stencil format**: Depth24PlusStencil8. - **CurveQuadVertex** = `bytemuck::Pod + Zeroable`. Uniform = `encase::ShaderType`. - **RHI 추상화 일관**: `TessPainter` 내부에 wgpu 직접 참조 없음. ## Hard rules - No `unwrap()` in production — `?` 또는 `expect("reason")`. - `unsafe` 는 `// SAFETY:` 주석 필수. - 함수 ≤ 100 lines, 구현 단위 ≤ 50 lines. - `///` doc comment 는 `pub` item 모두에 필수. - 모든 GPU 리소스 생성 시 label 명시. - Tier 2/3 blend mode 는 Normal fallback (custom shader 미구현). - 미지원 pass kind (`MaskRaster`, `ApplyMask`) 는 runtime error. ## Stroke pipeline (RFC-0202 / RFC-0203) `renderer_core::stroke::StrokePlanSet` 이 frame-shared semantic artifact 이고, `renderer-tess` 는 cache miss 때만 `StrokeExpansionPipeline` 을 실행한다. Scene traversal / pass encoding 은 `RenderNode` 를 다시 stroke-plan compile 하지 않고 frame context 의 shared plan set 을 조회한다. 1. `StrokeExpansionPipeline::expand()` — `StrokePlan` 에서 온 commands/style/dash 를 expansion request 로 받아 diagnostics + fill path 를 만든다. `fill_path`, generic stroker node-space mapping buffer, dash output buffer 는 pipeline scratch 로 재사용한다. 2. `generic_stroker::dispatch::stroke_into()` — multi-`MoveTo` split, dashed outward sign propagation. Subpath split 은 별도 slice `Vec` 없이 visitor 로 처리한다. 3. `dash_path` — Budorick 알고리즘으로 path 를 open subpath 로 자름. Solid 입력은 pass-through. 4. `stroker::stroke_path` — outer / inner offset ring + cap + join 으로 fill polygon 생성. 산출물은 `StrokeExpansionResult { fill_path, bounds, effective_style, diagnostics }` 이며 fill path 를 Slug fill 파이프라인이 paint-agnostic 으로 소비한다. Stroke 는 dedicated shader 없음. ## Text pipeline (RFC-0200 / RFC-0204 / RFC-0205 / RFC-0207 / RFC-0208) `TextSystem` 은 renderer-local text layout 과 glyph outline planning 을 소유한다. renderer-core 는 `TextStyle`, `TextBoxStyle`, `TextStyleRun` 같은 scene contract 만 보유한다. 첫 artifact 는 crate-private `TextLayoutPlan` 이며, graph/resource 가 shaped glyph run 또는 font lookup semantic 을 소비하지 않는 동안 renderer-core `TextPlan` 으로 승격하지 않는다. Rich text ranges 는 `Parley::RangedBuilder` 로 적용하고, invalid UTF-16 ranges 는 resolver 호출 전 diagnostics 로 남긴다. `TextBoxStyle` 은 layout request/cache key 에 포함하고, fixed-box vertical alignment / clipped lines 는 renderer-tess 에서 처리한다. `LocalBounds` 는 text, stroke, cached vector path curve geometry 가 공유하는 local-space bounds 타입이다. Generic bounds 는 text-specific module 이 소유하지 않는다. `TextLayoutPlan::bounds` 는 glyph ink bounds 이고 `TextLayoutPlan::layout_box` 는 node-local text box 이므로, wrapping / overflow / editing handle 판단에서 둘을 섞지 않는다. Glyph outline normalization 은 Parley baseline origin 과 raw glyph `y_max` 를 기준으로 flipped outline 을 node-local 좌표에 고정한다. Baseline / alignment / wrapping 회귀는 web-demo text benchmark 에서 픽셀 기반으로 검증한다. Text glyph 와 text decoration 은 일반 fill pipeline 을 사용하므로 solid / gradient / image fill 회귀를 web-demo 에 유지한다. Range color 는 base solid fill 이 `TextStyle::color` 와 같을 때만 glyph / decoration brush 색으로 내려가고, gradient/image fill 은 glyph / decoration brush 를 override 한다. Text stroke 는 renderer-core `TextPlan` 이 아니라 `StrokePlanTarget::Text` 를 통해 stroke semantic 만 공유한다. renderer-tess 는 glyph-local `PathCommand` outline 을 node-local 로 배치한 뒤 기존 `StrokeExpansionPipeline` 으로 fill path 를 만들고, glyph atlas / Slug fill pipeline 으로 소비한다. Stroke paint slot 은 shape stroke 와 같은 resource binding slot 을 사용하며, gradient/image stroke paint 는 glyph별 bounds 가 아니라 text stroke bounds union 으로 resolve 한다. `TessPainter` 는 positioned glyph / decoration / text-stroke curve cache 와 persistent glyph atlas 를 소유한다. 현재 shader contract 에서는 glyph-instance key 가 font/glyph identity, origin, scale, node size 를 포함해야 한다. Decoration curve key 는 node-local bounds 와 node size 를 포함한다. Text stroke curve key 는 glyph instance key 와 effective stroke geometry/dash 를 포함한다. Glyph-local atlas 는 coverage-space transform 이 생길 때 별도 설계한다. 비어 있지 않은 text 가 glyph placement 0개로 resolve되는 경우는 `TextLayoutDiagnostics::empty_layout_count` 로 관찰 가능해야 한다. `FrameStats.text` 는 text layout/glyph/positioned-curve cache 크기와 frame-local diagnostics/cache hit 합계를 보고한다. 이는 renderer-tess telemetry 이며 renderer-core `TextPlan` 승격 근거가 아니다. ### Dash policy (figma-aligned, multi-entry) - 임의 짝수 길이 pattern 지원. `[d₀, g₀, d₁, g₁, …, d_{N-1}, g_{N-1}]` 전체를 per-edge cycle scaling 으로 처리. - Corner-bearing dash 는 항상 `d₀` (첫 번째 painted entry). `d₁ … d_{N-1}` 는 edge 내부에만. - Open path 양 끝: `half(d₀)`. Internal corner: 양쪽 변의 per-edge half(d₀) 합쳐 corner-bearing piece 형성. - Figma SVG 측정 기반 regression은 `dash_path/tests.rs`에 고정한다. 상세: `docs/rfcs/0202-generic-stroker-core-design.md`, `docs/rfcs/0203-stroke-plan-expansion-pipeline.md`. ## Dependencies - wgpu 26 (백엔드, `WgpuRhi*` wrapping) - bytemuck (vertex 직렬화) - encase 0.12 (uniform std140) - glam 0.32 (수학) - parley 0.7 + skrifa 0.37 + fontique 0.7 (text shaping / outline / font registration). `cargo tree -p renderer-tess -i skrifa` 는 single-version 이어야 한다. - renderer-core / renderer-gpu / renderer-graph / renderer-resource ## 핵심 RFC - RFC-0004 — GPU 파이프라인 아키텍처 - RFC-0005 — 블렌드 모드 & 합성 - RFC-0200 — Slug + analytic primitive 아키텍처 - RFC-0202 — Generic stroker core