Skip to main content
Version: Next

Performance

Canvas Tile Engine is designed for large grid scenes, but the best strategy depends on whether your content is dynamic, static, visible all at once, or spread across a large world.

Automatic Optimizations​

Viewport Culling​

Draw methods skip items outside the visible viewport plus a small buffer. This applies to rectangles, circles, text, images, lines, and paths.

Spatial Indexing​

For arrays larger than 500 items, dynamic rect, circle, text, and image layers build an RBush spatial index. Viewport queries then avoid scanning the whole list each frame.

engine.drawRect(largeArrayOfTiles, 1);

Use one draw call with an array instead of thousands of draw calls.

Renderer Batching​

  • Canvas2D reduces repeated style changes while iterating visible items.
  • WebGL batches geometry into GPU draw calls.
  • Skia caches parsed colors and fonts and records frames as pictures.

Static Drawing​

Static helpers are best for large non-changing layers.

engine.drawStaticRect(items, "terrain", 0);
engine.drawStaticCircle(markers, "markers", 1);
engine.drawStaticImage(images, "decorations", 1);

Renderer behavior differs:

RendererStatic helper behavior
Canvas2DPre-renders to an offscreen canvas and blits visible portions.
WebGLDelegates to dynamic batched drawing; no offscreen cache is kept.
SkiaRecords a reusable SkPicture.
ServerUses an offscreen @napi-rs/canvas cache.

Clear caches when underlying static data changes:

engine.clearStaticCache("terrain");
engine.clearStaticCache();

React Stability​

React draw components compare items by reference. Keep large arrays stable with useMemo, useState, or external state.

const terrainItems = useMemo(() => buildTerrain(terrain), [terrain]);

<CanvasTileEngine.StaticRect items={terrainItems} cacheKey="terrain" layer={0} />;

Avoid inline arrays for large layers:

// Avoid for large data: new array identity on every render.
<CanvasTileEngine.Rect items={data.map(toRect)} layer={1} />;

Practical Rules​

  • Batch items: drawRect(items) is better than calling drawRect(item) in a loop.
  • Use dynamic draw calls for frequently changing layers.
  • Use static draw calls for large, non-changing terrain or minimap layers.
  • Keep sprite animation frame lists stable.
  • In WebGL, call renderer.invalidateTexture(source) after mutating an image/canvas source without changing its dimensions.
  • Prefer server renderer.encode() over toBuffer() in request handlers to avoid blocking.