Published on

PixiJS 源码揭秘 - 4. 深入理解渲染系统

Authors
  • avatar
    Name
    青雲
    Twitter

AutoDetectRenderer工作原理

在PixiJS中,为了适应不同设备和浏览器的性能需求,引擎提供了一个自动检测渲染器的方法:autoDetectRenderer。这个方法可以智能地选择最佳的渲染器(WebGLRenderer 或 CanvasRenderer),以确保在各种平台上的最佳性能表现。

详细流程

初始化渲染器优先级顺序

autoDetectRenderer 接收一个配置参数options,首先根据用户提供的偏好设置(preference),初始化一个渲染器优先级列表preferredOrder。如果用户指定了首选渲染器类型,则将其放在优先级的第一个位置,其他渲染器依次排列。

let preferredOrder: string[] = []

if (options.preference) {
  preferredOrder.push(options.preference)

  renderPriority.forEach((item) => {
    if (item !== options.preference) {
      preferredOrder.push(item)
    }
  })
} else {
  preferredOrder = renderPriority.slice()
}

尝试依次选择渲染器

接下来,函数以优先级顺序依次尝试创建不同类型的渲染器。这个过程中会检查当前环境是否支持WebGPU或WebGL,并根据支持情况来选择适合的渲染器。如果当前优先项是 canvas,则直接抛出错误,因为 Canvas 渲染器尚未实现。

let RendererClass: new () => Renderer
let finalOptions: Partial<AutoDetectOptions> = {}

for (let i = 0; i < preferredOrder.length; i++) {
  const rendererType = preferredOrder[i]

  if (rendererType === 'webgpu' && (await isWebGPUSupported())) {
    const { WebGPURenderer } = await import('./gpu/WebGPURenderer')
    RendererClass = WebGPURenderer
    finalOptions = { ...options, ...options.webgpu }
    break
  } else if (
    rendererType === 'webgl' &&
    isWebGLSupported(
      options.failIfMajorPerformanceCaveat ??
        AbstractRenderer.defaultOptions.failIfMajorPerformanceCaveat
    )
  ) {
    const { WebGLRenderer } = await import('./gl/WebGLRenderer')
    RendererClass = WebGLRenderer
    finalOptions = { ...options, ...options.webgl }
    break
  } else if (rendererType === 'canvas') {
    finalOptions = { ...options }
    throw new Error('CanvasRenderer is not yet implemented')
  }
}

检查是否有支持的渲染器

在所有尝试结束后,如果仍未找到支持的渲染器,则抛出一个错误。

if (!RendererClass) {
  throw new Error('No available renderer for the current environment')
}

初始化和返回渲染器实例

选择到支持的渲染器类后,函数创建渲染器实例并调用renderer.init()方法进行初始化,最后返回渲染器实例。

const renderer = new RendererClass()
await renderer.init(finalOptions)
return renderer

WebGPU 支持检查

isWebGPUSupported函数的主要目的是检查当前浏览器是否支持 WebGPU API。其工作流程如下:

  • 检查 GPU 属性:首先,函数会通过 DOMAdapter.get().getNavigator().gpu 获取浏览器的 GPU 属性。如果该属性不存在,说明浏览器不支持 WebGPU,函数将返回 false。
  • 请求适配器:如果 GPU 属性存在,函数会尝试请求一个 GPU 适配器。通过调用 gpu.requestAdapter(options),函数会向 GPU 请求适配器。
  • 请求设备:一旦成功获取适配器,函数会调用 adapter.requestDevice(),这一步是为了实际请求 GPU 设备。如果在这个过程中出现任何错误 (例如,设备不可用) ,函数会捕获异常并返回 false。
  • 返回支持状态:如果上述步骤都成功,函数将返回 true,表示 WebGPU 被支持。
export async function isWebGPUSupported(options: GPURequestAdapterOptions = {}): Promise<boolean> {
  if (_isWebGPUSupported !== undefined) return _isWebGPUSupported

  _isWebGPUSupported = await (async (): Promise<boolean> => {
    const gpu = DOMAdapter.get().getNavigator().gpu

    if (!gpu) {
      return false
    }

    try {
      const adapter = (await gpu.requestAdapter(options)) as GPUAdapter
      await adapter.requestDevice()

      return true
    } catch (e) {
      return false
    }
  })()

  return _isWebGPUSupported
}

GPUAdapter 和 GPUDevice概念

GPUAdapter 和 GPUDevice 是 WebGPU API 中的两个核心概念,分别代表 GPU 的适配器和设备。

  1. GPUAdapter 是表示系统中物理 GPU 的接口。它的主要功能包括:

    • 适配器选择:GPUAdapter 允许开发者请求适合的 GPU 适配器,适配器可能是独立显卡或集成显卡。通过适配器,开发者可以访问 GPU 的特性和能力。
    • 设备请求:使用 GPUAdapter,开发者可以请求一个 GPUDevice,这个设备将用于执行图形渲染和计算任务。

  1. GPUDevice 是从 GPUAdapter 请求到的具体设备实例。它的主要功能包括:

    • 资源管理:GPUDevice 负责管理 GPU 中的资源,如缓冲区、纹理等。开发者可以通过设备创建和管理这些资源。
    • 命令执行:GPUDevice 还负责执行图形和计算命令。开发者可以通过设备提交命令缓冲区,进行渲染或计算操作。

WebGL 支持检查

isWebGLSupported函数用于检查浏览器对 WebGL 的支持情况,具体流程如下:

  • 检查全局状态:首先,函数检查 _isWebGLSupported 变量。如果该变量已经被定义,说明支持状态已经检查过,直接返回该值。
  • 创建上下文选项:接着,函数定义了一个上下文选项对象,包含 stencil 属性和一个可选的 failIfMajorPerformanceCaveat 属性。后者用于控制在性能出现重大问题时是否失败。
  • 创建 Canvas 和 WebGL 上下文:函数会创建一个 Canvas 元素,并尝试获取其 WebGL 上下文。如果获取成功,进一步检查该上下文的属性,确保其具有所需的特性 (例如,是否支持 stencil) 。
  • 处理上下文丢失:如果获取上下文成功,函数还会检查是否支持 WEBGL_lose_context 扩展,以便在需要时可以丢失上下文。
  • 返回支持状态:如果所有检查都通过,函数返回 true,表示 WebGL 被支持;否则返回 false。
export function isWebGLSupported(failIfMajorPerformanceCaveat?: boolean): boolean {
  if (_isWebGLSupported !== undefined) return _isWebGLSupported

  _isWebGLSupported = ((): boolean => {
    const contextOptions = {
      stencil: true,
      failIfMajorPerformanceCaveat:
        failIfMajorPerformanceCaveat ??
        AbstractRenderer.defaultOptions.failIfMajorPerformanceCaveat,
    }

    try {
      if (!DOMAdapter.get().getWebGLRenderingContext()) {
        return false
      }

      const canvas = DOMAdapter.get().createCanvas()
      let gl = canvas.getContext('webgl', contextOptions)

      const success = !!gl?.getContextAttributes()?.stencil

      if (gl) {
        const loseContext = gl.getExtension('WEBGL_lose_context')

        if (loseContext) {
          loseContext.loseContext()
        }
      }

      gl = null

      return success
    } catch (e) {
      return false
    }
  })()

  return _isWebGLSupported
}

Stencil 属性

Stencil 属性在图形渲染中用于控制像素的绘制过程,主要用于实现复杂的效果,如遮罩、剪切和图形的合成。

  1. 遮罩效果:通过设置 Stencil 缓冲区,可以定义哪些区域可以被绘制,哪些区域被遮挡。这使得开发者能够创建复杂的视觉效果,比如动态遮罩。
  2. 条件渲染:Stencil 属性允许根据特定条件决定是否绘制某个像素。这意味着可以在渲染过程中使用 Stencil 缓冲区的值来控制图形的显示。
  3. 多层次效果:利用 Stencil 属性,可以在同一场景中实现多层次的图形效果,例如在一个对象上绘制多个效果,而不需要额外的渲染调用。
  4. 性能优化:通过使用 Stencil 缓冲区,可以减少不必要的渲染操作,从而提高性能,尤其是在处理复杂场景时。

示例

async function setup() {
  // 自动检测渲染器
  const renderer = await PIXI.autoDetectRenderer({
    width: 800,
    height: 600,
    backgroundColor: 0x1099bb,
  })
  document.body.appendChild(renderer.canvas) // 创建一个舞台
  const stage = new PIXI.Container() // 加载纹理并创建精灵
  await PIXI.Assets.load('https://pixijs.io/examples/examples/assets/bunny.png')
  const texture = PIXI.Texture.from('https://pixijs.io/examples/examples/assets/bunny.png')
  const bunny = new PIXI.Sprite(texture) // 设置精灵的锚点和位置
  bunny.anchor.set(0.5)
  bunny.x = renderer.width / 2
  bunny.y = renderer.height / 2 // 将精灵添加到舞台
  stage.addChild(bunny) // 动画循环
  function animate() {
    requestAnimationFrame(animate)
    bunny.rotation += 0.01 // 旋转精灵
    renderer.render(stage) // 渲染舞台
  }

  animate() // 启动动画
}

setup()

WebGLRenderer与WebGPURenderer

在PixiJS中,WebGLRenderer和WebGPURenderer是两种主要的渲染器,分别利用WebGL和WebGPU技术来实现高效的图形渲染。

WebGLRenderer

WebGLRenderer由多个系统(Systems)和渲染管道(Render Pipes)组成,每个系统负责管理特定的任务。这些系统包括缓冲系统、几何系统、纹理系统等,确保了渲染过程的高效和灵活性。

主要系统

  • GlUboSystem:管理WebGL统一缓冲(Uniform Buffer)对象,用于在着色器之间共享数据。
  • GlBackBufferSystem:管理后台缓冲区,用于屏幕上像素的读取操作。
  • GlContextSystem:管理WebGL上下文及其扩展。
  • GlBufferSystem:管理缓冲区及其GPU资源。
  • GlTextureSystem:管理纹理及其GPU资源。
  • GlRenderTargetSystem:管理渲染目标,比如屏幕或其他纹理。
  • GlGeometrySystem:管理几何体,用于通过GPU绘制图形。
  • GlUniformGroupSystem:同步着色器属性和GPU之间数据。
  • GlShaderSystem:管理着色器程序。
  • GlEncoderSystem:管理编码器(WebGPU范例),用于绘制网格和着色器。
  • GlStateSystem:管理WebGL上下文状态,比如混合模式、深度测试等。
  • GlStencilSystem:管理模板缓冲区,主要用于遮罩。
  • GlColorMaskSystem:管理颜色掩码,用于颜色掩蔽。

WebGPURenderer

WebGPURenderer是PixiJS中利用WebGPU API的渲染器。WebGPU是下一代图形和计算API,相较于WebGL,提供了更高性能和更低级别的控制。

与WebGLRenderer类似,WebGPURenderer也由多个系统和渲染管道组成。WebGPURenderer提供了专门用于管理WebGPU功能的系统,这些系统包括设备系统、缓冲系统、着色器系统等。

主要系统

  • GpuUboSystem:管理WebGPU统一缓冲对象,用于在着色器之间共享数据。
  • GpuEncoderSystem:管理WebGPU命令编码器。
  • GpuDeviceSystem:管理WebGPU设备和其扩展。
  • GpuBufferSystem:管理缓冲区及其GPU资源。
  • GpuTextureSystem:管理纹理及其GPU资源。
  • GpuRenderTargetSystem:管理渲染目标,比如屏幕或其他纹理。
  • GpuShaderSystem:管理着色器程序。
  • GpuStateSystem:管理WebGPU管线状态,比如混合模式、深度测试等。
  • GpuColorMaskSystem:管理颜色掩码,用于颜色掩蔽。
  • GpuStencilSystem:管理模板缓冲区,主要用于遮罩。
  • PipelineSystem:管理WebGPU管线,用于渲染。
  • BindGroupSystem:管理WebGPU绑定组,这是将数据绑定到着色器的主要方式。

两者区别

  1. 渲染器类型

    • WebGLRenderer:使用 WebGL 2.0 API,适用于大多数现代浏览器。它主要依赖于 GPU 的图形渲染能力,并通过 WebGL 上下文来管理图形资源和渲染过程。
    • WebGPURenderer:基于 WebGPU API,这是一个更现代的图形 API,旨在提供更高效的图形渲染和计算能力。WebGPU 支持更细粒度的控制和更高效的资源管理。
  2. 系统与管道:两者都使用一系列系统来管理渲染过程,但所使用的系统和管道有所不同:

    • WebGLRenderer 包含一组特定于 WebGL 的系统,例如 GlUboSystem、GlBufferSystem 和 GlShaderSystem。它们负责处理 WebGL 的各种功能,如缓冲区管理、着色器编译和状态管理 。
    • WebGPURenderer 则包含 GpuUboSystem、GpuBufferSystem 和 GpuShaderSystem 等系统,这些系统专门为 WebGPU 设计,能够更高效地管理 GPU 资源和命令编码 。
  3. 初始化与配置

    • WebGLRenderer 的初始化过程涉及创建 WebGL 上下文,并配置相关的渲染管道。它的构造函数中会设置系统配置,并通过 super 调用父类的构造函数进行初始化。
    • WebGPURenderer 也遵循相似的初始化流程,但它需要请求 GPU 适配器和设备,这意味着在使用之前需要进行额外的支持检测和设备请求 。
  4. 渲染过程

    • 在 WebGLRenderer 中,渲染过程通常涉及调用 render 方法,使用 WebGL 上下文绘制场景中的对象。
    • WebGPURenderer 的渲染过程则更为复杂,涉及命令编码和提交,这使得它能够更好地利用现代 GPU 的并行处理能力。
  5. 兼容性与性能

    • WebGLRenderer 在大多数浏览器中具有良好的兼容性,适用于广泛的设备和平台,但可能在性能上受到 WebGL 的限制。
    • WebGPURenderer 则提供了更高的性能和更好的资源管理,但目前的浏览器支持情况相对较少,可能不如 WebGL 渗透率高。

WebGLRenderer 和 WebGPURenderer 各有其优缺点,适用于不同的使用场景。WebGLRenderer 更加成熟,兼容性好,而 WebGPURenderer 则提供了更先进的图形处理能力,适合未来的开发需求。

AbstractRenderer详解

AbstractRenderer是所有渲染器的基类。WebGLRendererWebGPURenderer都继承自AbstractRenderer,共享其核心功能和机制。AbstractRenderer封装了渲染器的一些共性逻辑,并提供了一系列系统(Systems)和渲染管道(Render Pipes)来管理具体的渲染任务和状态。

关键属性

  1. type:渲染器的类型(如WebGL或WebGPU)。
  2. name:渲染器的名称。
  3. runners:系统运行器(System Runners),用于管理渲染器各个生命周期阶段的系统调用。

  1. renderPipes:渲染管道(Render Pipes),管理具体的渲染任务。

  1. view:视图系统,管理与 DOM 关联的画布,负责渲染的主要输出。

  1. background:背景系统,管理视图的背景颜色和透明度。
  2. textureGenerator:生成纹理系统,管理从容器生成纹理的逻辑。
  3. _initOptions:存储初始化时的选项,确保在渲染过程中可以访问这些配置。
  4. _systemsHash:存储已添加的系统的哈希表,便于快速查找和管理。
  5. _lastObjectRendered:记录最后渲染的对象,通常用于交互管理和事件处理。
export class AbstractRenderer<
  PIPES,
  OPTIONS extends SharedRendererOptions,
  CANVAS extends ICanvas = HTMLCanvasElement,
> extends EventEmitter<{
  resize: [screenWidth: number, screenHeight: number, resolution: number]
}> {
  public static defaultOptions = {
    resolution: 1,
    failIfMajorPerformanceCaveat: false,
    roundPixels: false,
  }

  public readonly type: number
  public readonly name: string

  public _roundPixels: 0 | 1
  public readonly runners: Runners = Object.create(null) as Runners
  public readonly renderPipes = Object.create(null) as PIPES
  public view!: ViewSystem
  public background: BackgroundSystem
  public textureGenerator: GenerateTextureSystem

  protected _initOptions: OPTIONS = {} as OPTIONS
  protected config: RendererConfig

  private _systemsHash: Record<string, System> = Object.create(null)
  private _lastObjectRendered: Container
}

构造函数和初始化过程

构造函数

构造函数接收一个RendererConfig对象,用于配置系统和渲染管道,并动态加载这些组件到渲染器中。

constructor(config: RendererConfig)
{
    super();
    this.type = config.type;
    this.name = config.name;
    this.config = config;

    const combinedRunners = [...defaultRunners, ...(this.config.runners ?? [])];

    this._addRunners(...combinedRunners);
    this._unsafeEvalCheck();
}

初始化

初始化方法init用于加载环境扩展,添加系统和渲染管道,并调用各系统的初始化方法。具体分为以下步骤:

  1. 跳过扩展导入:法首先检查 options 中是否包含 skipExtensionImports 属性。如果该属性为 true,则在加载环境扩展时跳过导入。这使得开发者可以选择不加载某些扩展,以提高性能或减少依赖。
  2. 加载环境扩展:使用 loadEnvironmentExtensions 方法加载所需的扩展。这个步骤确保渲染器能够访问所需的功能和资源。
  3. 添加系统:调用 _addSystems 方法,将配置中定义的各个系统添加到渲染器中。系统负责处理渲染过程中的特定任务,如管理纹理、处理几何体等。
  4. 添加管道:调用 _addPipes 方法,将渲染管道和适配器添加到渲染器。管道用于管理渲染过程中的数据流和处理。
  5. 初始化各个系统:遍历所有已添加的系统,调用每个系统的初始化方法。此步骤确保每个系统都准备好进行渲染,并能够正确处理输入和输出。
  6. 合并选项:将默认选项与用户提供的选项合并,以确保渲染器在正确的配置下运行。合并后的选项将用于后续的渲染操作。
  7. 设置像素圆整:根据选项设置 _roundPixels 属性,指示是否在渲染时强制将坐标四舍五入为整数。这有助于避免抗锯齿效果,确保图形在屏幕上显示时的清晰度。
  8. 触发初始化运行器:最后,调用所有与初始化相关的运行器,确保在渲染器初始化完成后执行相应的逻辑。自定义插件就是在这个过程中完成初始化。
public async init(options: Partial<OPTIONS> = {})
{
    const skip = options.skipExtensionImports === true ? true : options.manageImports === false;

    await loadEnvironmentExtensions(skip);

    this._addSystems(this.config.systems);
    this._addPipes(this.config.renderPipes, this.config.renderPipeAdaptors);

    for (const systemName in this._systemsHash)
    {
        const system = this._systemsHash[systemName];
        const defaultSystemOptions = (system.constructor as any).defaultOptions;
        options = { ...defaultSystemOptions, ...options };
    }

    options = { ...AbstractRenderer.defaultOptions, ...options };
    this._roundPixels = options.roundPixels ? 1 : 0;

    for (let i = 0; i < this.runners.init.items.length; i++)
    {
        await this.runners.init.items[i].init(options);
    }

    this._initOptions = options as OPTIONS;
}

渲染、清除和销毁

渲染

render 方法的主要目的是将指定的容器或对象渲染到画布上,并处理与渲染相关的各种选项和状态。该方法接受一个参数,可以是渲染选项对象或容器:

  • options:如果是对象,则包含渲染的配置,如要渲染的容器、渲染目标等。
  • 如果是容器,则会将其封装为渲染选项。

工作流程:

  1. 处理参数:

    • 首先,检查传入的参数。如果参数是一个容器实例,方法会将其封装为一个包含 container 属性的对象。
    • 如果传入了一个 deprecated 参数 (用于兼容旧版本) ,则会发出警告,并将其处理为渲染目标。
  2. 设置渲染目标:确定渲染目标。如果没有指定目标,则使用默认的渲染目标 (通常是主画布) 。

  3. 清除颜色设置:如果渲染目标是主画布,方法会记录最后渲染的对象,并将清除颜色设置为背景颜色。

  4. 处理清除颜色:检查 clearColor 属性。如果提供了清除颜色,则将其转换为 RGBA 数组格式,确保颜色数据可以被正确使用。

  5. 更新变换矩阵:如果没有提供变换矩阵,方法会更新容器的局部变换,以确保渲染时位置和旋转等属性正确。

  6. 触发渲染事件:调用一系列运行器,以控制渲染流程的各个阶段:

    • prerender:在渲染开始前触发的事件,允许开发者在渲染之前执行自定义逻辑。
    • renderStart:渲染开始时触发的事件。
    • render:实际执行渲染操作的事件。
    • renderEnd:渲染结束时触发的事件。
    • postrender:在渲染结束后触发的事件,允许进行清理或后处理。
public render(options: RenderOptions | Container): void;
public render(container: Container, options: {renderTexture: any}): void;
public render(args: RenderOptions | Container, deprecated?: {renderTexture: any}): void
{
    let options = args;

    if (options instanceof Container)
    {
        options = { container: options };
        if (deprecated)
        {
            deprecation(v8_0_0, 'passing a second argument is deprecated, please use render options instead');
            options.target = deprecated.renderTexture;
        }
    }

    options.target ||= this.view.renderTarget;

    if (options.target === this.view.renderTarget)
    {
        this._lastObjectRendered = options.container;
        options.clearColor = this.background.colorRgba;
    }

    if (options.clearColor)
    {
        const isRGBAArray = Array.isArray(options.clearColor) && options.clearColor.length === 4;
        options.clearColor = isRGBAArray ? options.clearColor : Color.shared.setValue(options.clearColor).toArray();
    }

    if (!options.transform)
    {
        options.container.updateLocalTransform();
        options.transform = options.container.localTransform;
    }

    this.runners.prerender.emit(options);
    this.runners.renderStart.emit(options);
    this.runners.render.emit(options);
    this.runners.renderEnd.emit(options);
    this.runners.postrender.emit(options);
}

清除

clear 方法的主要目的是清除当前渲染目标,准备下一次渲染。它确保画布在每次渲染之前处于干净的状态。工作流程:

  • 参数处理:方法接受一个可选的参数 options,该参数用于指定清除的目标、颜色和模式。如果未提供,则使用默认设置。
  • 设置目标:确定要清除的渲染目标。如果没有指定目标,则使用当前的渲染目标。
  • 设置清除颜色:如果未提供清除颜色,则使用背景颜色。
  • 执行清除操作:调用渲染目标的 clear 方法,执行实际的清除操作。此步骤会根据提供的清除模式 (如全部清除或特定区域清除) 和颜色来清除画布。
public clear(options: ClearOptions = {}): void
{
    const renderer = this as unknown as Renderer;
    options.target ||= renderer.renderTarget.renderTarget;
    options.clearColor ||= this.background.colorRgba;
    options.clear ??= CLEAR.ALL;

    const { clear, clearColor, target } = options;
    Color.shared.setValue(clearColor ?? this.background.colorRgba);
    renderer.renderTarget.clear(target, clear, Color.shared.toArray() as RgbaArray);
}

销毁

destroy方法用于释放渲染器占用的资源,确保在不再使用渲染器时能够正确清理内存。这对于避免内存泄漏和确保应用程序稳定性至关重要。工作流程:

  • 触发销毁事件:首先,方法会触发与销毁相关的运行器,允许开发者在销毁之前执行自定义逻辑。
  • 销毁所有运行器:遍历所有运行器,调用它们的 destroy 方法,释放它们所占用的资源。
  • 清空系统哈希:将 _systemsHash 属性设置为 null,以清除对系统的引用,帮助垃圾回收。
  • 销毁所有管道:将 renderPipes 属性设置为 null,释放对渲染管道的引用。
public destroy(options: RendererDestroyOptions = false): void
{
    this.runners.destroy.items.reverse();
    this.runners.destroy.emit(options);

    Object.values(this.runners).forEach((runner) =>
    {
        runner.destroy();
    });

    this._systemsHash = null;
    (this.renderPipes as null) = null;
}

其他方法

resize 方法

调整渲染器的画布尺寸和分辨率。

工作流程

  • 首先,获取当前的分辨率。
  • 调用视图系统的 resize 方法,传入新的宽度、高度和分辨率。
  • 如果分辨率发生变化,触发相应的事件,通知其他系统更新。
public resize(desiredScreenWidth: number, desiredScreenHeight: number, resolution?: number): void {
    const previousResolution = this.view.resolution;

    this.view.resize(desiredScreenWidth, desiredScreenHeight, resolution);
    this.emit('resize', this.view.screen.width, this.view.screen.height, this.view.resolution);
    if (resolution !== undefined && resolution !== previousResolution) {
        this.runners.resolutionChange.emit(resolution);
    }
}

generateTexture 方法

从容器生成纹理,允许开发者将显示对象的内容转换为纹理以便于后续使用。

工作流程

  • 调用纹理生成器的 generateTexture 方法,传入选项或容器。
  • 返回生成的纹理对象。
public generateTexture(options: GenerateTextureOptions | Container): Texture {
    return this.textureGenerator.generateTexture(options);
}

_addRunners 方法

添加运行器(Runners),用于管理渲染器生命周期各个阶段的系统调用。

工作流程

  • 遍历提供的运行器 ID,创建相应的 SystemRunner 实例并存储。
private _addRunners(...runnerIds: string[]): void {
    runnerIds.forEach((runnerId) => {
        this.runners[runnerId] = new SystemRunner(runnerId);
    });
}

_addSystems 方法

将系统添加到渲染器中。

工作流程

  • 遍历系统配置,将每个系统类实例化并添加到渲染器的系统哈希表中。
private _addSystems(systems: RendererConfig['systems']): void {
    for (const { value, name } of systems) {
        this._addSystem(value, name);
    }
}

_addSystem 方法

添加单个系统到渲染器。

工作流程

  • 创建系统实例并存储在渲染器中。
  • 注册该系统到所有运行器,使其能够在渲染过程中被调用。
private _addSystem(ClassRef: SystemConstructor, name: string): this {
    const system = new ClassRef(this as unknown as Renderer);
    (this as any)[name] = system;
    this._systemsHash[name] = system;

    for (const runner of Object.values(this.runners)) {
        runner.add(system);
    }
    return this;
}

_addPipes 方法

添加渲染管道(Render Pipes)和适配器。

工作流程

  • 遍历管道配置,创建管道实例并与适配器关联。
private _addPipes(pipes: RendererConfig['renderPipes'], pipeAdaptors: RendererConfig['renderPipeAdaptors']): void {
    const adaptors = pipeAdaptors.reduce((acc, adaptor) => {
        acc[adaptor.name] = adaptor.value;
        return acc;
    }, {} as Record<string, any>);

    pipes.forEach((pipe) => {
        const PipeClass = pipe.value;
        const name = pipe.name;
        const Adaptor = adaptors[name];

        (this.renderPipes as any)[name] = new PipeClass(this as unknown as Renderer, Adaptor ? new Adaptor() : null);
    });
}

_unsafeEvalCheck 方法

检查当前环境是否支持不安全的 eval 操作。

工作流程

  • 如果当前环境不支持,抛出错误,提示开发者。
public _unsafeEvalCheck(): void {
    if (!unsafeEvalSupported()) {
        throw new Error('Current environment does not allow unsafe-eval, please use pixi.js/unsafe-eval module to enable support.');
    }
}

Runner 详解

在PixiJS渲染系统中,SystemRunner扮演着关键的角色。runners属性是AbstractRenderer中重要的一部分,用于管理渲染器生命周期各个阶段的系统调用。其类型定义为:

type Runners = { [key in DefaultRunners]: SystemRunner } & {
  [K: ({} & string) | ({} & symbol)]: SystemRunner
}

这意味着每个默认的生命周期事件(如initdestroyrender等)都有一个对应的SystemRunner实例。当这些事件被触发时,SystemRunner会通知所有注册了这些事件的系统,确保每个系统在渲染过程中的正确执行。

SystemRunner的定义

SystemRunner是一个简单而强大的工具类,允许在多个监听对象之间分发和广播事件。每个运行器都有一个指定的事件名称,当事件被触发时,所有监听对象中的相应方法都会被调用。

构造函数

constructor(name: string)
  • 功能:构造函数接受一个字符串参数 name,用于指定运行器的名称。这个名称将用于调用添加到运行器的监听器的方法。
  • 初始化:在构造函数中,items 数组被初始化为空,用于存储添加的监听器。

添加监听器

public add(item: unknown): this
  • 功能:该方法用于向运行器添加一个监听器。

  • 逻辑

    • 检查传入的 item 是否具有与运行器名称相同的方法。如果有,则将该监听器添加到 items 数组中。
    • 在添加之前,调用 remove 方法确保该监听器不会重复添加。

触发事件

public emit(a0?: unknown, a1?: unknown, a2?: unknown, a3?: unknown, a4?: unknown, a5?: unknown, a6?: unknown, a7?: unknown): this
  • 功能:该方法用于触发所有已添加的监听器。

  • 逻辑

    • 遍历 items 数组,依次调用每个监听器的方法,并传递参数。
    • 允许最多传递 8 个参数,增强了灵活性。

移除监听器

public remove(item: unknown): this
  • 功能:从运行器中移除指定的监听器。

  • 逻辑

    • 查找 items 数组中的索引,如果找到,则将该监听器从数组中移除。

检查监听器

public contains(item: unknown): boolean
  • 功能:检查指定的监听器是否已经在运行器中。
  • 返回值:返回一个布尔值,指示监听器是否存在于 items 数组中。

清空所有监听器

public removeAll(): this
  • 功能:清空运行器中的所有监听器。
  • 逻辑:通过将 items 数组的长度设置为 0 来实现。

销毁

public destroy(): void
  • 功能:释放运行器的所有资源。

  • 逻辑

    • 调用 removeAll 方法清空所有监听器,并将 items_name 属性设置为 null

其他属性

  • empty

    • 只读属性,指示运行器是否没有任何监听器。
  • name

    • 只读属性,返回运行器的名称。

架构图

下图展示了PixiJS的核心架构,包括AutoDetectRenderer如何自动选择最佳渲染器,WebGLRendererWebGPURenderer如何分别利用WebGL和WebGPU技术进行渲染,以及AbstractRenderer如何通过系统和管道来管理渲染过程中的各种任务。

总结

通过对PixiJS渲染系统的详细分析,我们了解了如何智能选择渲染器以适应不同的浏览器和设备,以及利用WebGL和WebGPU渲染器实现高效的图形处理。同时,我们深入探讨了AbstractRenderer类的构造和功能,理解了其在渲染过程中的角色和重要性。无论是自动检测最佳渲染器,还是具体实现WebGL和WebGPU渲染,这些知识都将帮助开发者更好地利用PixiJS创建丰富的图形和动画效果。