第 6 章只讲两件事:参数表与默认值(6.1)、参数对齐与打包(6.2)。剩下的——着色器怎么编译、一次编译出多少变体、渲染管线怎么组织——本卷一个字都不讲,一律外链给 graphics 专题。这一节不是「内容」,而是边界:把「什么归本章讲」编码成一张可断言的路由表,越界就报警。
6.3 划界:编译与渲染原理外链
6.3.1 什么归本章讲,什么一律外链
为什么要把边界写成代码?因为越界是默认行为。一个朴素的路由器看到「着色器编译」里有「编译」两个字,就会把它收进本卷——这正是撞车的开始。划界要反过来做:先把原理主题全部标成 external,且排在 this-book 规则之前,首个命中的规则胜出;只有真正属于本章的主题才判给 this-book。
判不出来的主题返回 null,逼你去补规则,而不是静默归到本章。这张表越严,本卷与 graphics 专题的分工越清楚。
6.3.2 外链清单:三个原理之家
本章涉及但不展开的三个原理主题,各自外链到一篇既有文章:
| 主题域 | 归属 | 外链目标 |
|---|---|---|
| 着色器编译 / 编译产物 / 编译期 | external | 着色器编译与 SPIR-V 工具链 |
| shader 变体爆炸 / 关键字变体 | external | Mesh Shader 新一代几何管线 |
| 渲染管线原理 / 帧图 / 管线阶段 | external | 渲染图与帧调度架构 |
这三篇是原理的家:讲清楚「为什么」,本卷只讲「美术侧怎么落成规范与账」。要原理,去那三篇;要参数表、对齐与打包,留在本章。
6.3.3 划界路由表(D 层)
路由表本身是一份 D 层数据:规则按顺序排列,external 在前、this-book 在后;verifiedUrls 是已核验的绝对 URL 清单:
{
"project": "stardust-inn",
"ruleSet": "chapter-scope",
"specRef": "config/visual_spec.json",
"note": "第 6 章划界路由表:把「什么归本章讲(this-book)、什么一律外链(external)」编码成可断言的数据。external 规则必须排在 this-book 规则之前(首个命中胜出)。三条 external 规则的路径两两不同、全部取自已核验清单,且都在 public/ 产物中存在。",
"verifiedUrls": [
"/graphics-shader-compilation-pipeline/",
"/graphics-mesh-shaders-nextgen/",
"/graphics-render-graph/",
"/posts/graphics/"
],
"probes": [
"着色器编译",
"shader 变体爆炸",
"渲染管线原理",
"材质参数表",
"参数对齐与打包",
"材质槽预算",
"完全没听过的主题"
],
"rules": [
{ "id": "shader-compile", "owner": "external", "path": "/graphics-shader-compilation-pipeline/", "match": ["着色器编译", "shader 编译", "编译产物", "编译期"] },
{ "id": "shader-variant", "owner": "external", "path": "/graphics-mesh-shaders-nextgen/", "match": ["变体爆炸", "shader 变体", "关键字变体", "permutation"] },
{ "id": "render-pipeline", "owner": "external", "path": "/graphics-render-graph/", "match": ["渲染管线原理", "管线阶段", "帧图", "render graph"] },
{ "id": "material-param-table", "owner": "this-book", "path": null, "match": ["材质参数表", "参数表", "默认值", "参数契约"] },
{ "id": "param-packing", "owner": "this-book", "path": null, "match": ["参数对齐", "参数打包", "寄存器排布", "常量缓冲"] },
{ "id": "material-slot-budget", "owner": "this-book", "path": null, "match": ["材质槽", "材质预算"] }
]
}
三条 external 规则的路径两两不同、且全部取自已核验清单——这正是 §9.1 的不变量:external 规则条数 == 既有文章条数。
6.3.4 S 层:路由与产物核对
S 层脚本对一串探针主题逐个路由,并逐条核对 external 路径在 public/ 产物中真实存在:
#!/usr/bin/env python3
# file: sim/ta06_scope.py
# tier: S
# verify: /opt/homebrew/bin/python3 sim/ta06_scope.py
"""6.3 第 6 章划界路由表(S 层)。本章只讲材质参数表与默认值、参数对齐与打包;着色器编译、
变体爆炸、渲染管线原理一律外链,本卷不重推。本脚本把这张划界表跑成可断言的账:对一串探针主题
逐个路由落 out/ta06_scope.json;断言 external 规则数 == 去重后的 external 路径数(路径两两不同);
逐条核对每个 external 路径在仓库 public/ 产物中真实存在(页面查 index.html)。
"""
import hashlib
import json
import os
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
REPO = "/Users/bingrong.yan/projects/birdor/plumephp"
PUBLIC = os.path.join(REPO, "public")
CONFIG = os.path.join(ROOT, "config/ta06_scope.json")
OUT = os.path.join(ROOT, "out")
def route(rules, topic):
"""首个命中的规则胜出。"""
for r in rules:
if any(k in topic for k in r["match"]):
return {"topic": topic, "owner": r["owner"], "path": r["path"], "ruleId": r["id"]}
return {"topic": topic, "owner": None, "path": None, "ruleId": None}
def path_exists(url):
"""页面目标查 index.html;带扩展名的目标查文件本身。"""
tail = url.rstrip("/").split("/")[-1]
if "." in tail:
return os.path.isfile(os.path.join(PUBLIC, url.lstrip("/")))
return os.path.exists(os.path.join(PUBLIC, url.strip("/"), "index.html"))
def main():
cfg = json.load(open(CONFIG, encoding="utf-8"))
os.makedirs(OUT, exist_ok=True)
rules = cfg["rules"]
externals = [r for r in rules if r["owner"] == "external"]
paths = [r["path"] for r in externals]
verified = set(cfg["verifiedUrls"])
missing = [p for p in paths if not path_exists(p)]
not_verified = [p for p in paths if p not in verified]
probes = [route(rules, t) for t in cfg["probes"]]
routed = [p for p in probes if p["owner"]]
digest = hashlib.sha256("\n".join(
f"{r['id']}|{r['owner']}|{r['path']}" for r in rules).encode("utf-8")).hexdigest()[:16]
summary = {
"project": cfg["project"], "ruleSet": cfg["ruleSet"],
"ruleCount": len(rules), "externalRuleCount": len(externals),
"distinctExternalPaths": len(set(paths)), "thisBookRuleCount": len(rules) - len(externals),
"pathsPairwiseDistinct": len(paths) == len(set(paths)),
"allPathsInVerifiedList": not not_verified, "allPathsInPublic": not missing,
"missingPaths": missing, "probes": probes, "routedCount": len(routed),
"digest": digest,
}
with open(os.path.join(OUT, "ta06_scope.json"), "w", encoding="utf-8") as f:
json.dump(summary, f, ensure_ascii=False, indent=2, sort_keys=True)
f.write("\n")
print(f"rules={len(rules)} external={len(externals)} thisBook={len(rules) - len(externals)}")
print(f"distinctExternalPaths={len(set(paths))} pairwiseDistinct={summary['pathsPairwiseDistinct']} "
f"allInVerified={summary['allPathsInVerifiedList']} allInPublic={summary['allPathsInPublic']}")
for p in probes:
print(f" {p['topic']:<16} -> {str(p['owner']):<10} {p['path'] or '-'} ({p['ruleId'] or '-'})")
print(f"digest={digest}")
print("wrote out/ta06_scope.json")
ok = summary["pathsPairwiseDistinct"] and summary["allPathsInVerifiedList"] and summary["allPathsInPublic"]
return 0 if ok else 1
if __name__ == "__main__":
sys.exit(main())
实跑 /opt/homebrew/bin/python3 sim/ta06_scope.py:
rules=6 external=3 thisBook=3
distinctExternalPaths=3 pairwiseDistinct=True allInVerified=True allInPublic=True
着色器编译 -> external /graphics-shader-compilation-pipeline/ (shader-compile)
shader 变体爆炸 -> external /graphics-mesh-shaders-nextgen/ (shader-variant)
渲染管线原理 -> external /graphics-render-graph/ (render-pipeline)
材质参数表 -> this-book - (material-param-table)
参数对齐与打包 -> this-book - (param-packing)
材质槽预算 -> this-book - (material-slot-budget)
完全没听过的主题 -> None - (-)
digest=7285b015fed5ee53
wrote out/ta06_scope.json
3 条 external、3 条 this-book;三条外链路径两两不同、全在已核验清单内、且都在 public/ 产物里真实存在(allInPublic=True)。探针里「着色器编译」判给 external、「参数对齐与打包」判给 this-book、「完全没听过的主题」返回 None——越界与漏网都被这张表挡住。
6.3.5 P 层内核
同一份路由表,TypeScript 侧独立实现一遍:
// file: core/src/ta06/scope.ts
// tier: P
// verify: node --test 'core/test/ta06_*.test.ts'
// 6.3 第 6 章划界路由表的 TS 内核。与 sim/ta06_scope.py 同一套数据(config/ta06_scope.json):
// 首个命中的规则胜出,external 规则必须排在 this-book 之前。external 路径取自已核验清单。
// 只允许可擦除 TS 语法(禁 enum / namespace / 参数属性 / 装饰器)。
export type Owner = "this-book" | "external";
export type ScopeRule = { id: string; owner: Owner; path: string | null; match: string[] };
export type RouteResult = { topic: string; owner: Owner | null; path: string | null; ruleId: string | null };
export function route(rules: ScopeRule[], topic: string): RouteResult {
for (const r of rules) {
if (r.match.some((k) => topic.includes(k))) {
return { topic, owner: r.owner, path: r.path, ruleId: r.id };
}
}
return { topic, owner: null, path: null, ruleId: null };
}
export function externalRules(rules: ScopeRule[]): ScopeRule[] {
return rules.filter((r) => r.owner === "external");
}
export function externalPaths(rules: ScopeRule[]): string[] {
return externalRules(rules).map((r) => r.path as string);
}
6.3.6 P 层测试:不变量与负例
P 层测试断言 §9.1 的三条不变量,并用「朴素路由器」做负例:
// file: core/test/ta06_scope.test.ts
// tier: P
// verify: node --test 'core/test/ta06_*.test.ts'
// 划界路由表断言:external 路径两两不同、条数等于去重条数、全部在已核验清单内;探针路由与
// Python 产物逐位一致。负例是核心 —— 朴素路由器把「着色器编译」判成 this-book,必须被拦住。
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { route, externalRules, externalPaths } from "../src/ta06/scope.ts";
import type { ScopeRule, Owner } from "../src/ta06/scope.ts";
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
const cfg = JSON.parse(readFileSync(resolve(ROOT, "config/ta06_scope.json"), "utf8"));
const py = JSON.parse(readFileSync(resolve(ROOT, "out/ta06_scope.json"), "utf8"));
const rules = cfg.rules as ScopeRule[];
test("不变量:external 路径两两不同、条数等于去重条数、全部在已核验清单内", () => {
const paths = externalPaths(rules);
assert.equal(paths.length, new Set(paths).size, "external 路径有重复");
assert.equal(externalRules(rules).length, paths.length, "external 规则数与路径数不符");
const verified = new Set(cfg.verifiedUrls as string[]);
for (const p of paths) assert.ok(verified.has(p), `${p} 不在已核验清单`);
assert.equal(py.pathsPairwiseDistinct, true);
assert.equal(py.allPathsInVerifiedList, true);
assert.equal(py.allPathsInPublic, true);
});
test("跨语言:TS 探针路由与 Python 产物逐位一致", () => {
const mine = (cfg.probes as string[]).map((t) => route(rules, t));
assert.deepEqual(mine, py.probes);
assert.equal(py.routedCount, 6);
assert.equal(py.digest, "7285b015fed5ee53");
});
test("正例:本章地盘判给 this-book,原理判给 external", () => {
assert.equal(route(rules, "材质参数表")?.owner, "this-book");
assert.equal(route(rules, "参数对齐与打包")?.owner, "this-book");
assert.equal(route(rules, "材质槽预算")?.owner, "this-book");
assert.equal(route(rules, "着色器编译")?.owner, "external");
assert.equal(route(rules, "shader 变体爆炸")?.owner, "external");
assert.equal(route(rules, "渲染管线原理")?.owner, "external");
});
test("负例:朴素路由器把「着色器编译」判成 this-book,真路由表必须不这么判", () => {
// 朴素路由器:见到「编译」就收进本卷 —— 正是划界要防的越界
const naive = (t: string): Owner => (t.includes("编译") || t.includes("管线") ? "this-book" : "external");
assert.equal(naive("着色器编译"), "this-book"); // 朴素路由器确实会错判
const r = route(rules, "着色器编译");
assert.ok(r);
assert.notEqual(r.owner, "this-book");
assert.equal(r.owner, "external");
assert.equal(r.path, "/graphics-shader-compilation-pipeline/");
});
test("负例:「参数对齐」含「对齐」但不得被任何 external 规则抢走", () => {
const r = route(rules, "参数对齐");
assert.equal(r.ruleId, "param-packing");
assert.equal(r.owner, "this-book");
});
test("未覆盖主题返回 null(需要新增规则,而不是静默归到本章)", () => {
assert.equal(route(rules, "完全没听过的主题").owner, null);
});
实跑 node --test 'core/test/ta06_*.test.ts'(ta06_* 三节共 13 条,此处只列本节相关的 6 条与汇总):
✔ 不变量:external 路径两两不同、条数等于去重条数、全部在已核验清单内
✔ 跨语言:TS 探针路由与 Python 产物逐位一致
✔ 正例:本章地盘判给 this-book,原理判给 external
✔ 负例:朴素路由器把「着色器编译」判成 this-book,真路由表必须不这么判
✔ 负例:「参数对齐」含「对齐」但不得被任何 external 规则抢走
✔ 未覆盖主题返回 null(需要新增规则,而不是静默归到本章)
ℹ tests 13
ℹ pass 13
ℹ fail 0
digest=7285b015fed5ee53,两侧逐位一致。这张表不是文档,是门禁:新增一条「属于本章」的主题时,先改 config/ta06_scope.json 再跑两侧;判不出来的主题返回 null,构建期就会提醒你。边界靠断言守住,不靠自觉。
阅读导航:上一节:6.2 参数对齐与打包 · 下一节:7.1 LOD 分级与误差指标 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。