修复渲染 Y 轴镜像 + 补字体守护日志(#193 #199)#203
Merged
Merged
Conversation
added 2 commits
July 6, 2026 21:08
…193) The vertex shader mapped canvas-pixel Y (origin bottom-left, y up, per affine_transform's CG convention) straight onto wgpu NDC without accounting for wgpu's y-down viewport/framebuffer convention (NDC +1 = top row, -1 = bottom row). Every clip rendered at row (1 - center_y) instead of center_y, mirroring the whole canvas vertically across preview/playback/export/inspect (all four share this compositor). canvas_uv (mask evaluation) carried a compensating flip that had silently canceled out with the buggy NDC line, keeping mask coverage paired with the (also mirrored) framebuffer. Fixing the NDC line alone would have mirrored every mask relative to the content it clips, so both lines flip together. New regression tests (crates/opentake-render/tests/gpu_y_orientation.rs) render real GPU frames and check where content actually lands: clip_authored_near_top_renders_in_top_half, clip_authored_near_bottom_renders_in_bottom_half, top_and_bottom_authored_clips_render_on_opposite_halves, and off_center_mask_clips_to_authored_screen_region_not_mirrored (the mask/NDC pairing check). All 4 fail against the pre-fix shader and pass after. No existing opentake-render test needed an expectation change: every prior assertion used either a full-canvas clip or a dead-centered (0.5, 0.5) mask, both Y-symmetric and therefore blind to the mirror.
CosmicTextRasterizer silently renders text clips fully transparent when the host has no discoverable system fonts, with no signal that this happened. Add a has_fonts() check right after construction at each of the three production call sites (preview composite in render.rs, MCP inspect_media/inspect_timeline compositing in mcp.rs, and video export in export.rs) and eprintln a warning so a blank-text render is diagnosable instead of silent. No fallback font behavior — that's separate follow-up work.
This was referenced Jul 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
根因
#193(CRITICAL 全局 Y 轴镜像):预览/播放/导出/inspect 四路共用的 wgpu 合成 shader(
crates/opentake-render/src/gpu/shader.wgsl)顶点段,把affine_transform产出的 canvas 像素坐标(CG 语义:原点左下角、y 朝上,crates/opentake-render/src/plan/affine.rs逐行移植自上游CompositionBuilder.affineTransform,验证与上游完全一致)直接当 wgpu NDC 用:但 wgpu/WebGPU 的 NDC 虽然 y 朝上(+1=顶),其光栅化目标的视口/帧缓冲坐标系是 y 朝下(第 0 行=顶)。这一行没有做这个转换,导致
centerY小的 clip(应在画布顶部附近)渲染到帧缓冲底部附近,反之亦然——渲染行数 ≈(1 - centerY) × H,与真机实测「centerY=0.2 渲染在 ~0.78」「centerY=0.1 → ~0.85」完全吻合。级联发现:
canvas_uv(mask 用的归一化画布坐标)原写作1.0 - px.y / canvas.y——这不是独立于 NDC 的计算,而是手动补偿了上面这行的镜像 bug,让 mask 覆盖范围与(同样镜像的)帧缓冲保持配对一致。只改 NDC 一行会让这个补偿失配,mask 相对于它裁剪的内容整体镜像。用真实 GPU 渲染验证到这一步:初次只翻 NDC 后新增的 mask 回归测试当场失败(白色区域出现在错误的行),排查后确认两行必须同步翻转。修复
shader.wgsl两处协同改动:1.0 - px.y / canvas.y * 2.0(帧缓冲行与 CG 像素 y 正确配对)。canvas_uv同步去掉补偿翻转,改为px.y / canvas.y(与opentake_domain::grade::Mask::coverage及Transform.center_y的原点左上角/y 朝下语义直接对齐,不再依赖与旧 bug 的巧合抵消)。affine.rs(flip_vertical分支、旋转中心cy等)全部是 CG 像素空间内的纯代数,不依赖 wgpu NDC 约定,未改动;其自带单测(如rotate_90_maps_x_axis_to_y_axis)本身就是自洽的 CG 语义验证,与本次 shader 修复无关。新增回归测试
crates/opentake-render/tests/gpu_y_orientation.rs(真实 GPU 渲染,无 GPU 环境按现有约定 skip):clip_authored_near_top_renders_in_top_half— centerY=0.2 的实色 clip,非黑像素质心必须落在画布上半区clip_authored_near_bottom_renders_in_bottom_half— centerY=0.8 → 下半区top_and_bottom_authored_clips_render_on_opposite_halves— 交叉校验两者相对次序off_center_mask_clips_to_authored_screen_region_not_mirrored— mask 侧配套检查:圆形 mask 中心 (0.5, 0.2) 必须裁出画布顶部附近的内容,而不是镜像位置(专门捕捉"只翻一行导致 mask 与内容错配"的回归)4 个测试在修复前对着旧 shader 全部失败(用实际渲染结果验证过:质心/白色像素位置与理论推导的镜像值几乎完全吻合),修复后全部转绿。
没有任何既有测试需要修改预期:
opentake-render原有全部用例(含gpu_effects.rs的两个 mask 测试)用的都是满画布 clip 或正中心 (0.5, 0.5) 的 mask,两者对 Y 方向镜像天然不敏感,这正是这个 CRITICAL bug 此前从未被测试捕捉到的原因。#199(顺带,小)
CosmicTextRasterizer在宿主机找不到任何系统字体时,静默把文字 clip 渲染成全透明,没有任何提示。在三个生产构造点各加一次has_fonts()检查:src-tauri/src/render.rs(预览合成,GpuContext首次构造时)src-tauri/src/mcp.rs(composite_frames_jpeg,inspect_media/inspect_timeline 用)src-tauri/src/export.rs(run_export_with_control,导出用)false时eprintln!("[render] no system fonts discovered; text clips will render blank"),风格对齐现有mcp.rs内其余eprintln!("[mcp] ...")约定。不做字体 fallback(后续 issue 的事)。验证
opentake-render:72 测试全过(54 单测 + 18 GPU 集成测试,含新增 4 个)opentake-tauri:156 测试全过(147 单测 + 9 集成测试),含直接命中mcp.rs改动路径的inspect_timeline_composites_real_frames_when_gpu_availableFixes #193
Fixes #199