跳转至

Sorting 模块 API 参考

本参考文档涵盖了 src/sort/ 导出的公共接口层。它对应了 GPU 基数排序器 (GPURSSorter) 的 TypeScript 实现,以及与渲染器和预处理器共享的接口。

导出项 (Exports)

// src/sort/index.ts
export interface ISorter { ... }
export interface SortedSplats { ... }
export { GPURSSorter, HISTOGRAM_WG_SIZE, RS_HISTOGRAM_BLOCK_ROWS } from './radix_sort';
export type { PointCloudSortStuff } from './radix_sort';

接口 (Interfaces)

ISorter

排序器实现的契约接口。

interface ISorter {
  createSortStuff(device: GPUDevice, numPoints: number): SortedSplats;
  recordSort(sortStuff: SortedSplats, numPoints: number, encoder: GPUCommandEncoder): void;
  recordSortIndirect?(sortStuff: SortedSplats, dispatchBuffer: GPUBuffer, encoder: GPUCommandEncoder): void;
}

大多数调用者会与具体的 GPURSSorter 交互,但渲染器通过此接口存储排序器,以便在测试时替换为其他实现。

SortedSplats

interface SortedSplats {
  numPoints: number;
  sortedIndices: GPUBuffer;
  indirectBuffer: GPUBuffer;
  visibleCount?: number;
  [key: string]: any; // 实现特定的上下文
}

PointCloudSortStuff 扩展了此接口,增加了 GPURSSorter 所需的额外缓冲。

PointCloudSortStuff

interface PointCloudSortStuff extends SortedSplats {
  num_points: number;      // 兼容性的别名
  sorter_uni: GPUBuffer;   // GeneralInfo 存储缓冲
  sorter_dis: GPUBuffer;   // 间接调度 (Indirect dispatch) 缓冲
  sorter_bg: GPUBindGroup; // 基数排序管线绑定组
  sorter_bg_pre: GPUBindGroup; // 预处理绑定组
  sorter_render_bg: GPUBindGroup; // 渲染器绑定组
  internal_mem: GPUBuffer;
  key_a: GPUBuffer;
  key_b: GPUBuffer;
  payload_a: GPUBuffer;
  payload_b: GPUBuffer;
}

GPURSSorter

class GPURSSorter implements ISorter {
  static async create(device: GPUDevice, queue: GPUQueue): Promise<GPURSSorter>;
  createSortStuff(device: GPUDevice, numPoints: number): PointCloudSortStuff;
  recordSort(sortStuff: SortedSplats, numPoints: number, encoder: GPUCommandEncoder): void;
  recordSortIndirect(sortStuff: SortedSplats, dispatchBuffer: GPUBuffer, encoder: GPUCommandEncoder): void;
  recordResetIndirectBuffer(indirectBuffer: GPUBuffer, uniformBuffer: GPUBuffer, queue: GPUQueue): void;
  static createRenderBindGroupLayout(device: GPUDevice): GPUBindGroupLayout;
  static createPreprocessBindGroupLayout(device: GPUDevice): GPUBindGroupLayout;
}

GPURSSorter.create(device, queue)

  • 异步工厂方法,探测几种子组 (subgroup) 大小 (16, 32, 16, 8, 1)。
  • 为每个候选方案构建所有计算管线 (zero, histogram, prefix, scatter_even, scatter_odd)。
  • 使用 recordSort 运行 testSort(排序 8,192 个浮点数),以确保配置在当前适配器上正常工作。
  • 返回配置好的排序器,如果没有配置成功则抛出异常。

createSortStuff(device, numPoints)

  • 分配键 (key) / 负载 (payload) 的乒乓缓冲、内部暂存缓冲,以及为 numPoints(向上取整到 3840 对齐块)调整大小的 GeneralInfo + 间接缓冲。
  • 构建三个绑定组:sorter_bg(基数排序 Pass)、sorter_bg_pre(预处理器)和 sorter_render_bg(渲染器)。
  • 返回一个可以针对每个点云缓存的 PointCloudSortStuff 实例。

recordSort(sortStuff, numPoints, encoder)

  • 记录 zero -> histogram -> prefix -> scatter 过程,显式使用从 numPoints 派生的工作组数量。
  • 主要用于自测试或已知键数量的简单路径。

recordSortIndirect(sortStuff, dispatchBuffer, encoder)

  • 同样的 Pass,但使用 dispatchBuffer(通常是 sorter_dis)间接调度 zerohistogram 和两个 scatter 入口点。
  • 直接调用 recordPrefixHistogram,因为前缀和总是使用单个工作组计数。
  • 由渲染器使用;预处理负责在排序开始前写入 dispatch_x 的最终值。

recordResetIndirectBuffer(indirectBuffer, uniformBuffer, queue)

  • 将零写入间接调度缓冲和 GeneralInfo 的第一个 dword (keys_size)。
  • 在预处理之前调用,以便后续的原子增量从已知状态开始。

静态布局辅助方法 (Static layout helpers)

  • createRenderBindGroupLayout 向顶点/计算阶段暴露 sorter_unipayload_a(在渲染器中为 @group(1))。
  • createPreprocessBindGroupLayout 向预处理暴露 sorter_uni, key_a, payload_a, 和 sorter_dis@group(2))。

支持结构体 (Supporting structs)

GeneralInfo

interface GeneralInfo {
  keys_size: number;   // 由预处理写入的可见 splat 数量
  padded_size: number; // 向上取整到 3840 倍数的键数量
  passes: number;      // 对于 32 位键始终为 4
  even_pass: number;   // 由 scatter_even 处理的 Pass 的位掩码
  odd_pass: number;    // 由 scatter_odd 处理的 Pass 的位掩码
}

作为存储缓冲存储在 sorter_uni 中。排序后,渲染器将 keys_size 复制到绘制间接缓冲 (draw indirect buffer) 中。

IndirectDispatch

interface IndirectDispatch {
  dispatch_x: number;
  dispatch_y: number;
  dispatch_z: number;
}

预处理每处理 256 * 15 个 Splat 就递增一次 dispatch_x。排序器将此结构用于 dispatchWorkgroupsIndirect 调用,渲染器稍后在发出间接绘制时共享同一缓冲。

示例

```ts const sorter = await GPURSSorter.create(device, device.queue); const sortStuff = sorter.createSortStuff(device, totalPoints);

// 在预处理前重置计数器 sorter.recordResetIndirectBuffer(sortStuff.sorter_dis, sortStuff.sorter_uni, device.queue);

// ... 预处理将深度键写入 sortStuff.key_a / payload_a ...

const encoder = device.createCommandEncoder(); sorter.recordSortIndirect(sortStuff, sortStuff.sorter_dis, encoder); device.queue.submit([encoder.finish()]);

// 在渲染 Pass 中使用 pass.setBindGroup(1, sortStuff.sorter_render_bg); pass.drawIndirect(drawIndirectBuffer, 0);