npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

pixi-batch-renderer

v3.0.0

Published

Batch rendering library for PixiJS applications

Downloads

3,193

Readme

pixi-batch-renderer

Node.js CI install size dependencies

pixi-batch-renderer is a PixiJS plugin that allows you to add batch rendering to your custom display objects. I have documented each class in the PIXI.brend namespace.

Usage

In browser:

PIXI.brend.BatchRenderer

Using ES:

import { BatchRenderer } from 'pixi-batch-renderer';

Concepts

Batch rendering objects involves aggregating them into groups/batches and rendering them together with one WebGL draw call. PixiJS supports batch rendering its internal display objects - PIXI.Sprite, PIXI.Graphics, and PIXI.Mesh. However, it is difficult to extend that to custom-built display objects; it wasn't designed as an exposable API.

This library builds upon the same concept and is designed for maximum flexibility. It still uses PixiJS's batch system - a stub that enables objects to be rendered asynchronously, without loosing the order of rendering. To understand how it works, understanding these things are helpful:

  • Attribute Redirects: An attribute redirect is a data-object that tells pixi-batch-renderer how it will transform your object into a set of shader attributes.

  • Index Property: If you use indices, this will be property on your display object that holds those indices. It could also be a constant array, rather than a property on each object.

  • State Function: This function/property tells the batch renderer what WebGL state is required to render a display-object. It is optional if you're display objects use the default state (PIXI.State.for2d()).

  • Shader Function: This function generates the shader to render whole batches. It takes one argument - the batch renderer that will use the shader. You can use the BatchShaderFactory#derive() API for create one.

New features

  • Shader Templates: The BatchShaderFactory allows you to write shader "templates" containing %macros%. At runtime, you can replace these macros based with another expression. For example, the (built-in) %texturesPerBatch% macro is set to the no. of textures units in the GPU.

  • Custom uniforms: [Experimental] You can also use uniforms in the batch shader; however, this might reduce the batching efficiency if most batchable display-objects have different values for uniforms (because then they can't be batched together).

  • Modular architecture: With the modular architecture of this library, you change the behaviour of any component. The geometry composition, batch generation, and drawing stages are componentized and can be modified by providing a custom implementation to BatchRendererPluginFactory.from.

Caveat with filtered/masked objects

Before rendering itself, a PIXI.Container with filters or a mask will flush the batch renderer and will not batch itself. This is because the PixiJS batch renderer cannot batch filtered and masked objects. Although this does not break pixi-batch-renderer, it does reduce batching-efficiency. If you want to create a batch renderer that will batch filtered and masked objects too, your display-object must override render (however, you will have to derive your own batch renderer class for that):

render(renderer: PIXI.Renderer): void
{
  // If you registered the batch renderer as a plugin "pluginName", then replace <BatchRenderer> with
  // renderer.plugins.pluginName
  renderer.setObjectRenderer(<BatchRenderer>);
  <BatchRenderer>.render(this);

  for (let i = 0, j = this.children.length; i < j; i++)
  {
      this._children.render(renderer);
  }
}

Usage

Standard Pipeline

For most use cases, PIXI.brend.BatchRendererPluginFactory is all you'll need from this library. You need to do these three things:

  1. Generate the plugin class using PIXI.brend.BatchRendererPluginFactory.from

  2. Register the plugin with PixiJS's WebGL renderer

  3. Make your custom display object defer its rendering to your plugin

An example implementation would look like:

import * as PIXI from 'pixi.js';
import { AttributeRedirect, BatchRendererPluginFactory, BatchShaderFactory } from 'pixi-batch-renderer';

// ExampleFigure has two attributes: aVertex and aTextureCoord. They come from the
// vertices and uvs properties in this object. The indices are in the indices property.
class ExampleFigure extends PIXI.Container
{
  _render(renderer)
  {
    this.vertices = [x0,y0, x1,y1, x2,y2, ..., xn,yn];// variable number of vertices
    this.uvs = [u0,v0, u1,v1, u2, v2, ..., un,yn];// however, all other attributes must have equal length
    this.texture = PIXI.Texture.from("url:example");

    this.indices = [0, 1, 2, ..., n];// we could also tell our batch renderer to not use indices too :)

    renderer.setObjectRenderer(renderer.plugins["ExampleRenderer"]);
    renderer.plugins["ExampleRenderer"].render(this);
  }
}

// Define the geometry of ExampleFigure.
const attribSet = [
  new AttributeRedirect({
      source: "vertices", 
      attrib: "aVertex", 
      type: 'float32', 
      size: 2, 
      glType: PIXI.TYPES.FLOAT, 
      glSize: 2
  }),
  new AttributeRedirect({
      source: "uvs", 
      attrib: "aTextureCoord", 
      type: 'float32', 
      size: 2, 
      glType: PIXI.TYPES.FLOAT, 
      size: 2
  }),
];

// Create a shader function from a shader template!
const shaderFunction = new BatchShaderFactory(
// Vertex Shader
`
attribute vec2 aVertex;
attribute vec2 aTextureCoord;
attribute float aTextureId;

varying float vTextureId;
varying vec2 vTextureCoord;

uniform mat3 projectionMatrix;

void main()
{
    gl_Position = vec4((projectionMatrix * vec3(aVertex.xy, 1)), 0, 1);
    vTextureId = aTextureId;
    vTextureCoord = aTextureCoord;
}
`,

// Fragment Shader
`
uniform uSamplers[%texturesPerBatch%];/* %texturesPerBatch% is a macro and will become a number */\
varying float vTextureId;
varying vec2 vTextureCoord;

void main(void){
    vec4 color;

    /* get color, which is the pixel in texture uSamplers[vTextureId] @ vTextureCoord */
    for (int k = 0; k < %texturesPerBatch%; ++k)
    {
        if (int(vTextureId) == k)
            color = texture2D(uSamplers[k], vTextureCoord);

    }

    gl_FragColor = color;
}
`,
{}).derive();

// Create batch renderer class
const ExampleRenderer = BatchRendererPluginFactory.from({
    attribSet,
    indexProperty: "indices",
    textureProperty: "texture",
    texIDAttrib: "aTextureId", // this will be used to locate the texture in the fragment shader later
    shaderFunction
});

// Remember to do this before instantiating a PIXI.Application or PIXI.Renderer!
PIXI.Renderer.registerPlugin("ExampleRenderer", ExampleRenderer);

Uniforms Pipeline [Experimental]

You can take advantage of shader uniforms in batching too! pixi-batch-renderer supports this out of the box with the AggregateUniformsBatchFactory. There are two modes for uniforms:

  • Aggregation mode: Uniforms of each display-object are aggregated into an array. Then an uniform-ID attribute is uploaded (say aUniformId) that tells the shader which uniform to pick out of an array. The attribute is passed to the plugin factory via the uniformIDAttrib option.

  • No-aggregation mode: Uniforms are uploaded in one-element arrays. Display-objects with different uniform values are not batched together. This is useful if your uniform values don't differ a lot and will avoid another attribute. To use this mode, simply don't pass an uniformIDAttrib.

  1. Aggregation Mode (Example)
const { UniformRedirect, AggregateUniformsBatchFactory } = require('pixi-batch-renderer');

const shaderFunction = new BatchShaderFactory(
// Vertex Shader
`
attribute vec2 aVertex;
attribute vec2 aTextureCoord;
attribute float aTextureId;
attribute float aUniformId;

varying float vTextureId;
varying vec2 vTextureCoord;
varying float vUniformId;

uniform mat3 projectionMatrix;

void main()
{
    gl_Position = vec4((projectionMatrix * vec3(aVertex.xy, 1)), 0, 1);
    vTextureId = aTextureId;
    vTextureCoord = aTextureCoord;

    vUniformId = aUniformId;
}
`,

// Fragment Shader
`
// You can also use this in the vertex shader.
uniform shaderType[%uniformsPerBatch%];
varying float vUniformId;

uniform uSamplers[%texturesPerBatch%];/* %texturesPerBatch% is a macro and will become a number */\
varying float vTextureId;
varying vec2 vTextureCoord;

void main(void){
    vec4 color;
    float type;

    /* get color & shaderType */
    for (int k = 0; k < int(max(%texturesPerBatch%., %uniformsPerBatch%.)); ++k)
    {
        if (int(vTextureId) == k) {
            color = texture2D(uSamplers[k], vTextureCoord);
        }
        if (int(vUniformId) == k) {
            type = shaderType[vUniformId];
        }
    }


    if (type == 1)
    {
        gl_FragColor = color;
    }
    else
    {
        gl_FragColor = vec4(color.rgb * vTextureCoord.x, vTextureCoord.x);
    }
}
`,
{}).derive();

const uniformSet = [
  new UniformRedirect({ source: "type", uniform: "shadingType" });
];

const ExampleRenderer = BatchRendererPluginFactory.from({
  uniformSet,
  uniformIDAttrib: "aUniformId",

  // Previous example's stuff
  attribSet,
  indexProperty: "indices",
  textureProperty: "texture",
  texIDAttrib: "aTextureId",
  shaderFunction,

  BatchFactoryClass: AggregateUniformsBatchFactory
})
  1. No Aggregation Mode (Example)
const { UniformRedirect, AggregateUniformsBatchFactory } = require('pixi-batch-renderer');

const shaderFunction = new BatchShaderFactory(
// Vertex Shader (no changes to standard pipeline example!)
`
attribute vec2 aVertex;
attribute vec2 aTextureCoord;
attribute float aTextureId;
varying float vTextureId;
varying vec2 vTextureCoord;

uniform mat3 projectionMatrix;

void main()
{
    gl_Position = vec4((projectionMatrix * vec3(aVertex.xy, 1)), 0, 1);
    vTextureId = aTextureId;
    vTextureCoord = aTextureCoord;
}
`,

// Fragment Shader
`
// Look only one-element instead of %uniformsPerBatch%
uniform shaderType[1];
varying float vUniformId;

uniform uSamplers[%texturesPerBatch%];/* %texturesPerBatch% is a macro and will become a number */\
varying float vTextureId;
varying vec2 vTextureCoord;

void main(void){
    vec4 color;
    float type = shaderType;

    /* get color & shaderType */
    for (int k = 0; k < %texturesPerBatch%; ++k)
    {
        if (int(vTextureId) == k) {
            color = texture2D(uSamplers[k], vTextureCoord);
        }
    }

    if (type == 1)
    {
        gl_FragColor = color;
    }
    else
    {
        gl_FragColor = vec4(color.rgb * vTextureCoord.x, vTextureCoord.x);
    }
}
`,
{}).derive();

const uniformSet = [
  new UniformRedirect({ source: "type", uniform: "shaderType" });
];

const ExampleRenderer = BatchRendererPluginFactory.from({
  uniformSet,

  attribSet,
  indexProperty: "indices",
  textureProperty: "texture",
  texIDAttrib: "aTextureId",
  shaderFunction,

  BatchFactoryClass: AggregateUniformsBatchFactory
});

PIXI.Renderer.registerPlugin("erend", ExampleRenderer);

// Try using with a Sprite!
const sprite = PIXI.Sprite.from(<url>);

sprite.pluginName = "erend";
sprite.type = 0;// will fade out horizontally in shader

Advanced/Customized Batch Generation

The BatchRendererPluginFactory.from method also accepts these (optional) options that can be used to extend the behaviour of built-in components:

  • BatchFactoryClass: Child class of [StdBatchFactory]{@link https://pixijs.io/pixi-batch-renderer/PIXI.brend.StdBatchFactory.html}

  • BatchGeometryClass: Child class of [BatchGeometry]{@link https://pixijs.io/pixi-batch-renderer/PIXI.brend.BatchGeometryFactory.html}

  • BatchDrawerClass: Child class of [BatchDrawer]{@link https://pixijs.io/pixi-batch-renderer/PIXI.brend.BatchDrawer.html}

  • BatchRendererClass: If overriding a component does not meet your requirements, you can derive your own batch renderer by providing a child class of [BatchRenderer]{@link https://pixijs.io/pixi-batch-renderer/PIXI.brend.BatchRenderer.html}