upload method

void upload(
  1. RenderingContext gl,
  2. Uint8List buffer
)

Uploads splat data from a binary buffer to a WebGL texture.

The buffer must contain data for one or more splats, where each splat uses exactly GsConst.bytesPerSplat bytes. The data is converted from the compact binary format into a 2D RGBA32F texture suitable for GPU rendering.

Immediately updates splatCount, texHeight, and texture upon successful upload.

Implementation

void upload(RenderingContext gl, Uint8List buffer) {
  assert(
    buffer.length % GsConst.bytesPerSplat == 0,
    'Splat buffer must be multiple of ${GsConst.bytesPerSplat} bytes',
  );

  final count = buffer.length ~/ GsConst.bytesPerSplat;
  const pixelsPerSplat = GsConst.pixelsPerSplat;
  final height = ((pixelsPerSplat * count) / texWidth).ceil();

  // Reuse staging
  final neededFloats = texWidth * height * 4;
  _scratch ??= Float32Array(neededFloats);
  if (_scratch!.length < neededFloats) _scratch = Float32Array(neededFloats);
  final texData = _scratch!;

  final f32 = Float32List.view(buffer.buffer);
  final u8 = Uint8List.view(buffer.buffer);
  const floatsPerSplat = GsConst.bytesPerSplat ~/ 4;

  for (var idx = 0; idx < count; idx++) {
    final x = (idx & 0x1ff) * pixelsPerSplat; // (idx & 511) * 5
    final y = idx >> 9;

    final p0 = (y * texWidth + x + 0) * 4;
    final p1 = (y * texWidth + x + 1) * 4;
    final p2 = (y * texWidth + x + 2) * 4;
    final p3 = (y * texWidth + x + 3) * 4;
    final p4 = (y * texWidth + x + 4) * 4;

    final fBase = floatsPerSplat * idx;
    final bBase = GsConst.bytesPerSplat * idx;

    // P0: pos.xyz + packed quat (u32-as-f32)
    texData[p0 + 0] = f32[fBase + 0];
    texData[p0 + 1] = f32[fBase + 1];
    texData[p0 + 2] = f32[fBase + 2];
    final qx = u8[bBase + 28 + 0];
    final qy = u8[bBase + 28 + 1];
    final qz = u8[bBase + 28 + 2];
    final qw = u8[bBase + 28 + 3];
    final packedQuat = qx | (qy << 8) | (qz << 16) | (qw << 24);
    texData[p0 + 3] = _u32AsF32(packedQuat);

    // P1: scale.xyz + packed rgba
    texData[p1 + 0] = f32[fBase + 3];
    texData[p1 + 1] = f32[fBase + 4];
    texData[p1 + 2] = f32[fBase + 5];
    final r = u8[bBase + 24 + 0];
    final g = u8[bBase + 24 + 1];
    final b = u8[bBase + 24 + 2];
    final a = u8[bBase + 24 + 3];
    final packedColor = r | (g << 8) | (b << 16) | (a << 24);
    texData[p1 + 3] = _u32AsF32(packedColor);

    // P2..P4: 12 floats of SH (we keep first 12 floats)
    final shStart = (bBase + 32) >> 2;
    texData[p2 + 0] = f32[shStart + 0];
    texData[p2 + 1] = f32[shStart + 1];
    texData[p2 + 2] = f32[shStart + 2];
    texData[p2 + 3] = f32[shStart + 3];
    texData[p3 + 0] = f32[shStart + 4];
    texData[p3 + 1] = f32[shStart + 5];
    texData[p3 + 2] = f32[shStart + 6];
    texData[p3 + 3] = f32[shStart + 7];
    texData[p4 + 0] = f32[shStart + 8];
    texData[p4 + 1] = f32[shStart + 9];
    texData[p4 + 2] = f32[shStart + 10];
    texData[p4 + 3] = f32[shStart + 11];
  }

  // Create/replace texture
  if (texture != null) {
    try {
      gl.deleteTexture(texture!);
    } catch (_) {}
  }
  texture = gl.createTexture();
  gl
    ..bindTexture(WebGL.TEXTURE_2D, texture)
    ..texParameteri(
      WebGL.TEXTURE_2D,
      WebGL.TEXTURE_WRAP_S,
      WebGL.CLAMP_TO_EDGE,
    )
    ..texParameteri(
      WebGL.TEXTURE_2D,
      WebGL.TEXTURE_WRAP_T,
      WebGL.CLAMP_TO_EDGE,
    )
    ..texParameteri(
      WebGL.TEXTURE_2D,
      WebGL.TEXTURE_MIN_FILTER,
      WebGL.NEAREST,
    )
    ..texParameteri(
      WebGL.TEXTURE_2D,
      WebGL.TEXTURE_MAG_FILTER,
      WebGL.NEAREST,
    )
    ..texImage2D(
      WebGL.TEXTURE_2D,
      0,
      WebGL.RGBA32F,
      texWidth,
      height,
      0,
      WebGL.RGBA,
      WebGL.FLOAT,
      texData,
    );

  splatCount = count;
  texHeight = height;
}