copyTextureToTexture errors (multiple Data3DTexture)

Hello,
I’m writing a raymarcher using two sets of 256^3 RG8UI Textures. Since the textures are “large” and the data is streamed over network I want to use an async approach so I am dividing them in 32^3 chunks and am leveraging copyTextureToTexture to write them in the texture.

My issue is that the two textures get overwritten. As far as I understand it has to do with them landing on the same texture slot (and if I’m correct I have no access of that in Three.js)

Right now I have set my shader to render one raymarching pass of a texture in the red channel and the other texture in the blue channel.
Sometimes refreshing the page shows the blue and red colors, sometimes I see the wrong texture flashing in red before being overwritten by the correct one.

I hope someone can explain me what I’m understanding wrong

This is the atlas manager I wrote, and in my material setup I don’t do much more than setting up the objects and binding the uniforms:

import \* as THREE from 'three';

export interface AtlasOptions {
  width: number;
  height: number;
  depth: number;
  tileSize: number; // e.g. 32
  bytesPerVoxel?: number; // e.g. 2 for RG8UI
  internalFormat?: THREE.PixelFormatGPU; // e.g. gl.RG8UI
  format?: THREE.PixelFormat;
  type?: THREE.TextureDataType;
  wrapMode?: THREE.Wrapping;
  magFilter?: THREE.MagnificationTextureFilter;
  minFilter?: THREE.MinificationTextureFilter;
}

export class AtlasManager {
  width: number;
  height: number;
  depth: number;
  tileSize: number;
  tilesPerAxis: number;
  maxSlots: number;
  bytesPerVoxel: number;
  internalFormat: THREE.PixelFormatGPU;
  format: THREE.PixelFormat;
  type: THREE.TextureDataType;
  wrapMode: THREE.Wrapping;
  magFilter: THREE.MagnificationTextureFilter;
  minFilter: THREE.MinificationTextureFilter;
  renderer: THREE.WebGLRenderer | null;
  tex: THREE.Data3DTexture;

  constructor(opts: AtlasOptions, renderer?: THREE.WebGLRenderer) {
    this.renderer = renderer ?? null;
    this.width = opts.width;
    this.height = opts.height;
    this.depth = opts.depth;
    this.tileSize = opts.tileSize;
    this.tilesPerAxis = Math.floor(this.width / this.tileSize);
    this.maxSlots = this.tilesPerAxis \* this.tilesPerAxis \* Math.floor(this.depth / this.tileSize);

    this.bytesPerVoxel = opts.bytesPerVoxel ?? 4;
    this.internalFormat = opts.internalFormat ?? 'RGBA8';
    this.format = opts.format ?? THREE.RGBAFormat;
    this.type = opts.type ?? THREE.UnsignedByteType;
    this.wrapMode = opts.wrapMode ?? THREE.ClampToEdgeWrapping;
    this.magFilter = opts.magFilter ?? THREE.NearestFilter;
    this.minFilter = opts.minFilter ?? THREE.NearestFilter;

    const tempTex = new Uint8Array(this.width \* this.height \* this.depth \* this.bytesPerVoxel);
    this.tex = new THREE.Data3DTexture(tempTex, this.width, this.height, this.depth);
    this.tex.internalFormat = this.internalFormat;
    this.tex.format = this.format;
    this.tex.type = this.type;
    this.tex.wrapS = this.wrapMode;
    this.tex.wrapT = this.wrapMode;
    this.tex.wrapR = this.wrapMode;
    this.tex.magFilter = this.magFilter;
    this.tex.minFilter = this.minFilter;
    this.tex.generateMipmaps = false;
    this.tex.flipY = false;
    this.tex.premultiplyAlpha = false;
    this.tex.needsUpdate = true;

    this.renderer?.initTexture(this.tex);
  }


  uploadTile(slotIndex: number, tileData: Uint8Array) {
    const x = (slotIndex % this.tilesPerAxis) \* this.tileSize;
    const y = Math.floor((slotIndex / this.tilesPerAxis) % this.tilesPerAxis) \* this.tileSize;
    const z = Math.floor(slotIndex / (this.tilesPerAxis \* this.tilesPerAxis)) \* this.tileSize;

    const tempTexture = new THREE.Data3DTexture(tileData as BufferSource, this.tileSize, this.tileSize, this.tileSize);
    tempTexture.format = this.format;
    tempTexture.type = this.type;
    tempTexture.internalFormat = this.internalFormat;
    tempTexture.needsUpdate = false;

    if (this.renderer) {
      try {
        this.renderer.copyTextureToTexture(
          tempTexture,
          this.tex,
          new THREE.Box3(
            new THREE.Vector3(0, 0, 0),
            new THREE.Vector3(this.tileSize, this.tileSize, this.tileSize)
          ),
          new THREE.Vector3(x, y, z),
          0
        );
        this.tex.needsUpdate = false;
      } catch (e) {
        console.warn('\[AtlasManager\] copyTextureToTexture3D failed', e);
      }
    }
  }
}

I got it working by adding a little delay between texture creation and renderer texture2texture copy, It feels a bit hacky but atleast it works.

Would still be happy to know how this stuff works so i get more control over it. I know I’m going to have to write different implementations for different hardware.

Now I have to find out how to get this running on mobile :slight_smile: