01 / 12 · Boot the renderer启动渲染器
Start with your first pixel从一个像素开始搭建
Create the project, run a known milestone, and name who owns every part of the frame.建立项目,运行确定的里程碑,说清一帧中每个部分由谁负责。
A viewer looks like a single application. Inside it are three jobs: the operating system provides a place to draw, the renderer turns data into pixels, and the interface supplies camera and time. We will build those jobs in that order. You do not need to know Gaussian calculus to clear a window.Viewer 看起来是一个应用,里面其实有三个工作:操作系统提供画布,渲染器把数据变成像素,界面提供相机与时间。我们按这个顺序搭建。不懂 Gaussian 微积分,也能先把窗口清成黑色。
1. Set up a reproducible starting point1. 先建立可复现的起点
Install Git, a current stable Rust toolchain and Node.js LTS. On Windows, Rust also needs the MSVC C++ build tools and Windows SDK. Run the commands below in a terminal. Use the repository lockfiles; this series uses wgpu 30 and winit 0.30, so an older tutorial’s method names may not compile here.安装 Git、当前稳定版 Rust 和 Node.js LTS。Windows 上 Rust 还需要 MSVC C++ 构建工具及 Windows SDK。下面的命令在终端运行。保留仓库锁文件;本系列使用 wgpu 30 和 winit 0.30,旧教程的 API 名称不一定能编译。
The reference repository already contains every milestone. The stage number selects the behavior you are studying; it is not a separate historical commit. To learn by rebuilding, add the source blocks introduced in each chapter to your own branch, then compare the resulting pixels with the reference lab. Full source snapshots are linked below each block.参考仓库已经包含所有里程碑,阶段编号用来选择学习的行为,不是不同的历史提交。要边学边重建,可以在自己的分支上逐章加入介绍的代码块,再和参考实验的像素对照。每个片段下面都能打开完整源码快照。
2. Learn six Rust words as you meet them2. 先认识六个会遇到的 Rust 概念
A struct groups values, like a camera’s position and direction. An impl gives that group methods. &T borrows a value for reading; &mut T borrows it for changing. Option<T> means the value can be absent, such as a window before startup. Result<T, E> means an operation can fail; ? returns the error to its caller instead of silently continuing.struct 把数据装在一起,例如相机的位置和方向;impl 为它添加方法。&T 表示借用来读取,&mut T 表示借用来修改。Option<T> 表示可能还没有这个值,例如启动前的窗口。Result<T, E> 表示操作可能失败;? 会把错误返回给调用者,而不是带着错误继续。
async/await lets browser GPU initialization finish later without blocking the page. Arc<Window> lets the native app and GPU surface keep shared ownership of the same window. Neither keyword draws anything. They solve lifetime and scheduling problems around the renderer.async/await 让浏览器在等待 GPU 初始化时继续响应。Arc<Window> 让桌面应用和 GPU surface 共同持有同一个窗口。这些关键字本身不会绘图,它们解决的是渲染器周围的生命周期与调度问题。
3. Draw the ownership map before the pixels3. 先画职责图,再画像素
The browser host owns a canvas, fetch and requestAnimationFrame. The native host owns a winit window and OS events. Both create a WorkshopSurface. It owns the device, queue and presentation target and calls WorkshopPass. That pass owns shaders and buffers, with no dependency on a DOM or window.浏览器宿主负责 canvas、fetch 和 requestAnimationFrame;桌面宿主负责 winit 窗口与操作系统事件。两者都创建 WorkshopSurface,由它持有 device、queue 和显示目标,并调用 WorkshopPass。这个渲染 pass 持有 shader 与 buffer,不依赖 DOM 或窗口。
A buffer is GPU memory containing numbers. A shader is a program the GPU runs on those numbers. A render pipeline connects shader entry points to rules such as blending and output format. A command encoder records work; a queue submits it. Keeping these words separate makes a black screen diagnosable.Buffer 是存放数字的 GPU 内存,shader 是 GPU 用来处理数字的程序。Render pipeline 将 shader 入口与混合、输出格式等规则连接起来。Command encoder 记录工作,queue 提交工作。把这些名词分清,黑屏时才知道该查哪里。
4. Your first success is intentionally black4. 第一张正确的画面就是黑色
Stage 0 acquires the next surface texture, opens a render pass with LoadOp::Clear(BLACK), stores the result, submits commands and presents it. It issues no geometry draw. In the browser, “Presented frames” must increase while the draw count stays zero. This distinguishes a valid clear operation from a page that never initialized.阶段 0 获取下一张 surface texture,开启 render pass,用 LoadOp::Clear(BLACK) 清屏,保存结果、提交命令并显示,不调用任何几何绘制。在浏览器里,Presented frames 应递增,而绘制数保持 0。这能区分正常清屏和根本没初始化的页面。
RUN → OBSERVE运行 → 观察
Make this step concrete把这一步运行起来
A black native window; the terminal prints the adapter backend. Press 1 to move to the triangle. For the online lab, no local setup is required.出现黑色桌面窗口,终端打印适配器后端。按 1 进入三角形。在线实验不需要安装环境。
Open GPU milestone 0 →打开 GPU 里程碑 0 →git clone https://github.com/pajama-studio/4dgaussian.git
cd 4dgaussian
cargo run --locked --example workshop -- 0Commands use the reference repository. Rust/WGSL changes need a native rebuild or npm run build:wasm, followed by a browser reload.命令以参考仓库为基础。修改 Rust/WGSL 后需重新编译桌面程序,或运行 npm run build:wasm 后刷新浏览器。
IDEA → IMPLEMENTATION想法 → 实现
Follow the running code沿着真实代码看
Excerpts are extracted from the repository at site build time. Expand only the part you are studying; full files include context and line numbers.片段在网站构建时直接从仓库提取。只展开正在研究的部分,完整文件包含上下文与行号。
src/workshop_surface.rs · L84–128
Read this as acquire → prepare → record → submit → present. Stage 0 skips the draw inside the pass.按获取 → 准备 → 记录 → 提交 → 显示的顺序读;阶段 0 跳过 pass 内的绘制。
pub fn render(&mut self, input: Frame) -> Result<Option<u32>, String> {
let frame = match self.surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(frame)
| wgpu::CurrentSurfaceTexture::Suboptimal(frame) => frame,
wgpu::CurrentSurfaceTexture::Outdated | wgpu::CurrentSurfaceTexture::Lost => {
self.surface.configure(&self.device, &self.config);
return Ok(None);
}
wgpu::CurrentSurfaceTexture::Timeout | wgpu::CurrentSurfaceTexture::Occluded => {
return Ok(None)
}
wgpu::CurrentSurfaceTexture::Validation => {
return Err(
"Surface validation failed; reload to recreate the GPU resources".into(),
)
}
};
let count =
self.pass
.prepare(&self.queue, input, [self.config.width, self.config.height])?;
let target = frame.texture.create_view(&Default::default());
let mut encoder = self.device.create_command_encoder(&Default::default());
{
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("workshop-frame"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &target,
resolve_target: None,
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::BLACK),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
self.pass.draw(&mut pass);
}
self.queue.submit([encoder.finish()]);
self.queue.present(frame);
Ok(Some(count))
}Open full source with line numbers ↗打开带行号的完整源码 ↗Real-model render pass ↗真实模型渲染 pass ↗ · Real-model shader ↗真实模型 shader ↗
Predict it, change it, explain it先预测,再修改,再解释
Change BLACK to a dark red clear color. Predict which other stages will also get that background before running them.把 BLACK 改成暗红色。运行前先预测:还有哪些阶段会跟着换背景?
Check your reasoning核对推理
Every stage uses this same render pass, so every background changes. A shader edit is unnecessary. If the triangle later appears but the clear color does not, you may be running an old binary or a different host.所有阶段共用这个 render pass,因此背景都会改变,无需修改 shader。若后面三角形出现但清屏色没变,可能运行了旧二进制或另一个宿主。
Keep the theory beside the code把原理放在代码旁边
Open the related derivation or lab ↗打开对应推导或实验 ↗