Skip to content

Texture Bundles

Loading textures can be surprisingly expensive. A 1024x1024 png may only be 100kb on disk, but when loaded into the gpu, each pixel is 4 bytes of rgba data, so that's 4mb of memory.

Bundles are a way to choose what is loaded into the gpu, to avoid hitting the VRAM memory limit (we recommend 4gb to cover lower-end devices).

ts
import { Toodle } from "@blooper.gg/toodle";

const canvas = document.querySelector("canvas")!;
const toodle = await Toodle.attach(canvas, {
  filter: "nearest",
  limits: { textureArrayLayers: 5 },
});

const produceTextures = {
  ItemApple: new URL("img/ItemApple.png", "https://toodle.gg"),
  ItemBanana: new URL("img/ItemBanana.png", "https://toodle.gg"),
  ItemBroccoli: new URL("img/ItemBroccoli.png", "https://toodle.gg"),
  ItemCherry: new URL("img/ItemCherry.png", "https://toodle.gg"),
  ItemKiwi: new URL("img/ItemKiwi.png", "https://toodle.gg"),
  ItemLemon: new URL("img/ItemLemon.png", "https://toodle.gg"),
  ItemOnion: new URL("img/ItemOnion.png", "https://toodle.gg"),
  ItemPea: new URL("img/ItemPea.png", "https://toodle.gg"),
  ItemPeach: new URL("img/ItemPeach.png", "https://toodle.gg"),
  ItemPumpkin: new URL("img/ItemPumpkin.png", "https://toodle.gg"),
  ItemRadish: new URL("img/ItemRadish.png", "https://toodle.gg"),
  ItemSpinach: new URL("img/ItemSpinach.png", "https://toodle.gg"),
  ItemTomato: new URL("img/ItemTomato.png", "https://toodle.gg"),
};

const pantryTextures = {
  ItemBaguette: new URL("img/ItemBaguette.png", "https://toodle.gg"),
  ItemCheese: new URL("img/ItemCheese.png", "https://toodle.gg"),
  ItemCoffee: new URL("img/ItemCoffee.png", "https://toodle.gg"),
  ItemButterscotchCinnamonPie: new URL(
    "img/ItemButterscotchCinnamonPie.png",
    "https://toodle.gg",
  ),
  ItemChilidog: new URL("img/ItemChilidog.png", "https://toodle.gg"),
  ItemSeaSaltIceCream: new URL(
    "img/ItemSeaSaltIceCream.png",
    "https://toodle.gg",
  ),
  ItemTurkeyLeg: new URL("img/ItemTurkeyLeg.png", "https://toodle.gg"),
};

await toodle.assets.registerBundle("produce", { textures: produceTextures });
await toodle.assets.registerBundle("pantry", { textures: pantryTextures });

await toodle.assets.loadBundle("produce");
await toodle.assets.loadBundle("pantry");

{
  const usage = toodle.assets.extra.getAtlasUsage();
  console.log("used", usage.used, "available", usage.available);
}
await toodle.assets.unloadBundle("pantry");

{
  const usage = toodle.assets.extra.getAtlasUsage();
  console.log("used", usage.used, "available", usage.available);
}

await toodle.assets.loadBundle("pantry");

toodle.startFrame();
toodle.draw(
  toodle.Quad("ItemPumpkin", {
    idealSize: { width: 100, height: 100 },
    position: { x: -60, y: 0 },
  }),
);
toodle.draw(
  toodle.Quad("ItemTurkeyLeg", {
    idealSize: { width: 100, height: 100 },
    position: { x: 60, y: 0 },
  }),
);
toodle.endFrame();

Duplicate Textures

Textures can be loaded into more than one bundle. You could have a character portrait on the main menu and character select screen, and both could be loaded into separate bundles at the same time.

ts
import { Toodle } from "@blooper.gg/toodle";

const canvas = document.querySelector("canvas")!;
const toodle = await Toodle.attach(canvas, {
  filter: "nearest",
  limits: { textureArrayLayers: 5 },
});

// Here we're going to get an Apple and some Spinach...
const produceTextures = {
  ItemApple: new URL("img/ItemApple.png", "https://toodle.gg"),
  ItemSpinach: new URL("img/ItemSpinach.png", "https://toodle.gg"),
};

// With our pantry textures holding SeaSaltIceCream and the same Spinach that can be found in our produce textures...
const pantryTextures = {
  ItemSeaSaltIceCream: new URL(
    "img/ItemSeaSaltIceCream.png",
    "https://toodle.gg",
  ),
  ItemSpinach: new URL("img/ItemSpinach.png", "https://toodle.gg"),
};

// A little extra ice cream in the freezer...
const freezerTextures = {
  ItemSeaSaltIceCream: new URL(
    "img/ItemSeaSaltIceCream.png",
    "https://toodle.gg",
  ),
};

// Bundles are registered...
await toodle.assets.registerBundle("produce", { textures: produceTextures });
await toodle.assets.registerBundle("pantry", { textures: pantryTextures });
await toodle.assets.registerBundle("freezer", { textures: freezerTextures });

// and loaded...
await toodle.assets.loadBundle("produce");
await toodle.assets.loadBundle("pantry");
await toodle.assets.loadBundle("freezer");

// With the latter then being unloaded...
await toodle.assets.unloadBundle("pantry");

toodle.startFrame();
// But a draw call to `ItemSpinach` or `SeaSaltIceCream` will still work perfectly fine!
toodle.draw(
  toodle.Quad("ItemSpinach", {
    idealSize: { width: 100, height: 100 },
    position: { x: -60, y: 0 },
  }),
);
toodle.draw(
  toodle.Quad("ItemSeaSaltIceCream", {
    idealSize: { width: 100, height: 100 },
    position: { x: 60, y: 0 },
  }),
);
toodle.endFrame();