text: import Constants from 'vtk.js/Sources/Rendering/OpenGL/Texture/Constants';
  import macro from 'vtk.js/Sources/macro';
  import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
  import * as vtkMath from 'vtk.js/Sources/Common/Core/Math';
  import vtkViewNode from 'vtk.js/Sources/Rendering/SceneGraph/ViewNode';
  
  const { Wrap, Filter } = Constants;
  const { VtkDataTypes } = vtkDataArray;
  const { vtkDebugMacro, vtkErrorMacro, vtkWarningMacro } = macro;
  
  // ----------------------------------------------------------------------------
  // vtkOpenGLTexture methods
  // ----------------------------------------------------------------------------
  
  function vtkOpenGLTexture(publicAPI, model) {
    // Set our className
    model.classHierarchy.push('vtkOpenGLTexture');
    // Renders myself
    publicAPI.render = (renWin = null) => {
      if (renWin) {
        model.openGLRenderWindow = renWin;
      } else {
        model.openGLRenderer = publicAPI.getFirstAncestorOfType(
          'vtkOpenGLRenderer'
        );
        // sync renderable properties
        model.openGLRenderWindow = model.openGLRenderer.getParent();
      }
      model.context = model.openGLRenderWindow.getContext();
      if (model.renderable.getInterpolate()) {
        if (model.generateMipmap) {
          publicAPI.setMinificationFilter(Filter.LINEAR_MIPMAP_LINEAR);
        } else {
          publicAPI.setMinificationFilter(Filter.LINEAR);
        }
        publicAPI.setMagnificationFilter(Filter.LINEAR);
      } else {
        publicAPI.setMinificationFilter(Filter.NEAREST);
        publicAPI.setMagnificationFilter(Filter.NEAREST);
      }
      if (model.renderable.getRepeat()) {
        publicAPI.setWrapR(Wrap.REPEAT);
        publicAPI.setWrapS(Wrap.REPEAT);
        publicAPI.setWrapT(Wrap.REPEAT);
      }
      // clear image if input data is set
      if (model.renderable.getInputData()) {
        model.renderable.setImage(null);
      }
      // create the texture if it is not done already
      if (
        !model.handle ||
        model.renderable.getMTime() > model.textureBuildTime.getMTime()
      ) {
        // if we have an Image
        if (model.renderable.getImage() !== null) {
          if (model.renderable.getInterpolate()) {
            model.generateMipmap = true;
            publicAPI.setMinificationFilter(Filter.LINEAR_MIPMAP_LINEAR);
          }
          // Have an Image which may not be complete
          if (model.renderable.getImage() && model.renderable.getImageLoaded()) {
            publicAPI.create2DFromImage(model.renderable.getImage());
            publicAPI.activate();
            publicAPI.sendParameters();
            model.textureBuildTime.modified();
          }
        }
        // if we have Inputdata
        const input = model.renderable.getInputData(0);
        if (input && input.getPointData().getScalars()) {
          const ext = input.getExtent();
          const inScalars = input.getPointData().getScalars();
  
          // do we have a cube map? Six inputs
          const data = [];
          for (let i = 0; i < model.renderable.getNumberOfInputPorts(); ++i) {
            const indata = model.renderable.getInputData(i);
            const scalars = indata
              ? indata.getPointData().getScalars().getData()
              : null;
            if (scalars) {
              data.push(scalars);
            }
          }
          if (
            model.renderable.getInterpolate() &&
            inScalars.getNumberOfComponents() === 4
          ) {
            model.generateMipmap = true;
            publicAPI.setMinificationFilter(Filter.LINEAR_MIPMAP_LINEAR);
          }
          if (data.length % 6 === 0) {
            publicAPI.createCubeFromRaw(
              ext[1] - ext[0] + 1,
              ext[3] - ext[2] + 1,
              inScalars.getNumberOfComponents(),
              inScalars.getDataType(),
              data
            );
          } else {
            publicAPI.create2DFromRaw(
              ext[1] - ext[0] + 1,
              ext[3] - ext[2] + 1,
              inScalars.getNumberOfComponents(),
              inScalars.getDataType(),
              inScalars.getData()
            );
          }
          publicAPI.activate();
          publicAPI.sendParameters();
          model.textureBuildTime.modified();
        }
      }
      if (model.handle) {
        publicAPI.activate();
      }
    };
  
    //----------------------------------------------------------------------------
    publicAPI.destroyTexture = () => {
      // deactivate it first
      publicAPI.deactivate();
  
      if (model.context && model.handle) {
        model.context.deleteTexture(model.handle);
      }
      model.handle = 0;
      model.numberOfDimensions = 0;
      model.target = 0;
      model.components = 0;
      model.width = 0;
      model.height = 0;
      model.depth = 0;
      publicAPI.resetFormatAndType();
    };
  
    //----------------------------------------------------------------------------
    publicAPI.createTexture = () => {
      // reuse the existing handle if we have one
      if (!model.handle) {
        model.handle = model.context.createTexture();
  
        if (model.target) {
          model.context.bindTexture(model.target, model.handle);
  
          // See: http://www.openmodel.context..org/wiki/Common_Mistakes#Creating_a_complete_texture
          // turn off mip map filter or set the base and max level correctly. here
          // both are done.
          model.context.texParameteri(
            model.target,
            model.context.TEXTURE_MIN_FILTER,
            publicAPI.getOpenGLFilterMode(model.minificationFilter)
          );
          model.context.texParameteri(
            model.target,
            model.context.TEXTURE_MAG_FILTER,
            publicAPI.getOpenGLFilterMode(model.magnificationFilter)
          );
  
          model.context.texParameteri(
            model.target,
            model.context.TEXTURE_WRAP_S,
            publicAPI.getOpenGLWrapMode(model.wrapS)
          );
          model.context.texParameteri(
            model.target,
            model.context.TEXTURE_WRAP_T,
            publicAPI.getOpenGLWrapMode(model.wrapT)
          );
          if (model.openGLRenderWindow.getWebgl2()) {
            model.context.texParameteri(
              model.target,
              model.context.TEXTURE_WRAP_R,
              publicAPI.getOpenGLWrapMode(model.wrapR)
            );
          }
  
          model.context.bindTexture(model.target, null);
        }
      }
    };
  
    //---------------------------------------------------------------------------
    publicAPI.getTextureUnit = () => {
      if (model.openGLRenderWindow) {
        return model.openGLRenderWindow.getTextureUnitForTexture(publicAPI);
      }
      return -1;
    };
  
    //---------------------------------------------------------------------------
    publicAPI.activate = () => {
      // activate a free texture unit for this texture
      model.openGLRenderWindow.activateTexture(publicAPI);
      publicAPI.bind();
    };
  
    //---------------------------------------------------------------------------
    publicAPI.deactivate = () => {
      if (model.openGLRenderWindow) {
        model.openGLRenderWindow.deactivateTexture(publicAPI);
      }
    };
  
    //---------------------------------------------------------------------------
    publicAPI.releaseGraphicsResources = (rwin) => {
      if (rwin && model.handle) {
        rwin.activateTexture(publicAPI);
        rwin.deactivateTexture(publicAPI);
        model.context.deleteTexture(model.handle);
        model.handle = 0;
        model.numberOfDimensions = 0;
        model.target = 0;
        model.internalFormat = 0;
        model.format = 0;
        model.openGLDataType = 0;
        model.components = 0;
        model.width = 0;
        model.height = 0;
        model.depth = 0;
      }
      if (model.shaderProgram) {
        model.shaderProgram.releaseGraphicsResources(rwin);
        model.shaderProgram = null;
      }
    };
  
    //----------------------------------------------------------------------------
    publicAPI.bind = () => {
      model.context.bindTexture(model.target, model.handle);
      if (
        model.autoParameters &&
        publicAPI.getMTime() > model.sendParametersTime.getMTime()
      ) {
        publicAPI.sendParameters();
      }
    };
  
    //----------------------------------------------------------------------------
    publicAPI.isBound = () => {
      let result = false;
      if (model.context && model.handle) {
        let target = 0;
        switch (model.target) {
          case model.context.TEXTURE_2D:
            target = model.context.TEXTURE_BINDING_2D;
            break;
          default:
            vtkWarningMacro('impossible case');
            break;
        }
        const oid = model.context.getIntegerv(target);
        result = oid === model.handle;
      }
      return result;
    };
  
    //----------------------------------------------------------------------------
    publicAPI.sendParameters = () => {
      model.context.texParameteri(
        model.target,
        model.context.TEXTURE_WRAP_S,
        publicAPI.getOpenGLWrapMode(model.wrapS)
      );
      model.context.texParameteri(
        model.target,
        model.context.TEXTURE_WRAP_T,
        publicAPI.getOpenGLWrapMode(model.wrapT)
      );
      if (model.openGLRenderWindow.getWebgl2()) {
        model.context.texParameteri(
          model.target,
          model.context.TEXTURE_WRAP_R,
          publicAPI.getOpenGLWrapMode(model.wrapR)
        );
      }
  
      model.context.texParameteri(
        model.target,
        model.context.TEXTURE_MIN_FILTER,
        publicAPI.getOpenGLFilterMode(model.minificationFilter)
      );
  
      model.context.texParameteri(
        model.target,
        model.context.TEXTURE_MAG_FILTER,
        publicAPI.getOpenGLFilterMode(model.magnificationFilter)
      );
  
      if (model.openGLRenderWindow.getWebgl2()) {
        model.context.texParameteri(
          model.target,
          model.context.TEXTURE_BASE_LEVEL,
          model.baseLevel
        );
  
        model.context.texParameteri(
          model.target,
          model.context.TEXTURE_MAX_LEVEL,
          model.maxLevel
        );
      }
  
      // model.context.texParameterf(model.target, model.context.TEXTURE_MIN_LOD, model.minLOD);
      // model.context.texParameterf(model.target, model.context.TEXTURE_MAX_LOD, model.maxLOD);
  
      model.sendParametersTime.modified();
    };
  
    //----------------------------------------------------------------------------
    publicAPI.getInternalFormat = (vtktype, numComps) => {
      if (!model.internalFormat) {
        model.internalFormat = publicAPI.getDefaultInternalFormat(
          vtktype,
          numComps
        );
      }
  
      if (!model.internalFormat) {
        vtkDebugMacro(
          `Unable to find suitable internal format for T=${vtktype} NC= ${numComps}`
        );
      }
  
      return model.internalFormat;
    };
  
    //----------------------------------------------------------------------------
    publicAPI.getDefaultInternalFormat = (vtktype, numComps) => {
      let result = 0;
  
      // try default next
      result = model.openGLRenderWindow.getDefaultTextureInternalFormat(
        vtktype,
        numComps,
        false
      );
      if (result) {
        return result;
      }
  
      // try floating point
      result = this.openGLRenderWindow.getDefaultTextureInternalFormat(
        vtktype,
        numComps,
        true
      );
  
      if (!result) {
        vtkDebugMacro('Unsupported internal texture type!');
        vtkDebugMacro(
          `Unable to find suitable internal format for T=${vtktype} NC= ${numComps}`
        );
      }
  
      return result;
    };
  
    //----------------------------------------------------------------------------
    publicAPI.setInternalFormat = (iFormat) => {
      if (iFormat !== model.internalFormat) {
        model.internalFormat = iFormat;
        publicAPI.modified();
      }
    };
  
    //----------------------------------------------------------------------------
    publicAPI.getFormat = (vtktype, numComps) => {
      model.format = publicAPI.getDefaultFormat(vtktype, numComps);
      return model.format;
    };
  
    //----------------------------------------------------------------------------
    publicAPI.getDefaultFormat = (vtktype, numComps) => {
      if (model.openGLRenderWindow.getWebgl2()) {
        switch (numComps) {
          case 1:
            return model.context.RED;
          case 2:
            return model.context.RG;
          case 3:
            return model.context.RGB;
          case 4:
            return model.context.RGBA;
          default:
            return model.context.RGB;
        }
      } else {
        // webgl1
        switch (numComps) {
          case 1:
            return model.context.LUMINANCE;
          case 2:
            return model.context.LUMINANCE_ALPHA;
          case 3:
            return model.context.RGB;
          case 4:
            return model.context.RGBA;
          default:
            return model.context.RGB;
        }
      }
    };
  
    //----------------------------------------------------------------------------
    publicAPI.resetFormatAndType = () => {
      model.format = 0;
      model.internalFormat = 0;
      model.openGLDataType = 0;
    };
  
    //----------------------------------------------------------------------------
    publicAPI.getDefaultDataType = (vtkScalarType) => {
      // DON'T DEAL with VTK_CHAR as this is platform dependent.
      if (model.openGLRenderWindow.getWebgl2()) {
        switch (vtkScalarType) {
          // case VtkDataTypes.SIGNED_CHAR:
          //   return model.context.BYTE;
          case VtkDataTypes.UNSIGNED_CHAR:
            return model.context.UNSIGNED_BYTE;
          // case VtkDataTypes.SHORT:
          //   return model.context.SHORT;
          // case VtkDataTypes.UNSIGNED_SHORT:
          //   return model.context.UNSIGNED_SHORT;
          // case VtkDataTypes.INT:
          //   return model.context.INT;
          // case VtkDataTypes.UNSIGNED_INT:
          //   return model.context.UNSIGNED_INT;
          case VtkDataTypes.FLOAT:
          case VtkDataTypes.VOID: // used for depth component textures.
          default:
            return model.context.FLOAT;
        }
      }
  
      switch (vtkScalarType) {
        // case VtkDataTypes.SIGNED_CHAR:
        //   return model.context.BYTE;
        case VtkDataTypes.UNSIGNED_CHAR:
          return model.context.UNSIGNED_BYTE;
        // case VtkDataTypes.SHORT:
        //   return model.context.SHORT;
        // case VtkDataTypes.UNSIGNED_SHORT:
        //   return model.context.UNSIGNED_SHORT;
        // case VtkDataTypes.INT:
        //   return model.context.INT;
        // case VtkDataTypes.UNSIGNED_INT:
        //   return model.context.UNSIGNED_INT;
        case VtkDataTypes.FLOAT:
        case VtkDataTypes.VOID: // used for depth component textures.
        default:
          if (
            model.context.getExtension('OES_texture_float') &&
            model.context.getExtension('OES_texture_float_linear')
          ) {
            return model.context.FLOAT;
          }
          return model.context.UNSIGNED_BYTE;
      }
    };
  
    //----------------------------------------------------------------------------
    publicAPI.getOpenGLDataType = (vtkScalarType) => {
      model.openGLDataType = publicAPI.getDefaultDataType(vtkScalarType);
      return model.openGLDataType;
    };
  
    publicAPI.getShiftAndScale = () => {
      let shift = 0.0;
      let scale = 1.0;
  
      // for all float type internal formats
      switch (model.openGLDataType) {
        case model.context.BYTE:
          scale = 127.5;
          shift = scale - 128.0;
          break;
        case model.context.UNSIGNED_BYTE:
          scale = 255.0;
          shift = 0.0;
          break;
        case model.context.SHORT:
          scale = 32767.5;
          shift = scale - 32768.0;
          break;
        case model.context.UNSIGNED_SHORT:
          scale = 65536.0;
          shift = 0.0;
          break;
        case model.context.INT:
          scale = 2147483647.5;
          shift = scale - 2147483648.0;
          break;
        case model.context.UNSIGNED_INT:
          scale = 4294967295.0;
          shift = 0.0;
          break;
        case model.context.FLOAT:
        default:
          break;
      }
      return { shift, scale };
    };
  
    //----------------------------------------------------------------------------
    publicAPI.getOpenGLFilterMode = (emode) => {
      switch (emode) {
        case Filter.NEAREST:
          return model.context.NEAREST;
        case Filter.LINEAR:
          return model.context.LINEAR;
        case Filter.NEAREST_MIPMAP_NEAREST:
          return model.context.NEAREST_MIPMAP_NEAREST;
        case Filter.NEAREST_MIPMAP_LINEAR:
          return model.context.NEAREST_MIPMAP_LINEAR;
        case Filter.LINEAR_MIPMAP_NEAREST:
          return model.context.LINEAR_MIPMAP_NEAREST;
        case Filter.LINEAR_MIPMAP_LINEAR:
          return model.context.LINEAR_MIPMAP_LINEAR;
        default:
          return model.context.NEAREST;
      }
    };
  
    //----------------------------------------------------------------------------
    publicAPI.getOpenGLWrapMode = (vtktype) => {
      switch (vtktype) {
        case Wrap.CLAMP_TO_EDGE:
          return model.context.CLAMP_TO_EDGE;
        case Wrap.REPEAT:
          return model.context.REPEAT;
        case Wrap.MIRRORED_REPEAT:
          return model.context.MIRRORED_REPEAT;
        default:
          return model.context.CLAMP_TO_EDGE;
      }
    };
  
    //----------------------------------------------------------------------------
    function updateArrayDataType(dataType, data) {
      const pixData = [];
      // if the opengl data type is float
      // then the data array must be float
      if (
        dataType !== VtkDataTypes.FLOAT &&
        model.openGLDataType === model.context.FLOAT
      ) {
        const pixCount = model.width * model.height * model.components;
        for (let idx = 0; idx < data.length; idx++) {
          const newArray = new Float32Array(pixCount);
          for (let i = 0; i < pixCount; i++) {
            newArray[i] = data[idx][i];
          }
          pixData.push(newArray);
        }
      }
  
      // if the opengl data type is ubyte
      // then the data array must be u8, we currently simply truncate the data
      if (
        dataType !== VtkDataTypes.UNSIGNED_CHAR &&
        model.openGLDataType === model.context.UNSIGNED_BYTE
      ) {
        const pixCount = model.width * model.height * model.components;
        for (let idx = 0; idx < data.length; idx++) {
          const newArray = new Uint8Array(pixCount);
          for (let i = 0; i < pixCount; i++) {
            newArray[i] = data[idx][i];
          }
          pixData.push(newArray);
        }
      }
  
      // The output has to be filled
      if (pixData.length === 0) {
        for (let i = 0; i < data.length; i++) {
          pixData.push(data[i]);
        }
      }
  
      return pixData;
    }
  
    //----------------------------------------------------------------------------
    function scaleTextureToHighestPowerOfTwo(data) {
      if (model.openGLRenderWindow.getWebgl2()) {
        // No need if webGL2
        return data;
      }
      const pixData = [];
      const width = model.width;
      const height = model.height;
      const numComps = model.components;
      if (
        data &&
        (!vtkMath.isPowerOfTwo(width) || !vtkMath.isPowerOfTwo(height))
      ) {
        // Scale up the texture to the next highest power of two dimensions.
        const newWidth = vtkMath.nearestPowerOfTwo(width);
        const newHeight = vtkMath.nearestPowerOfTwo(height);
        const pixCount = newWidth * newHeight * model.components;
        for (let idx = 0; idx < data.length; idx++) {
          if (data[idx] !== null) {
            let newArray = null;
            switch (model.openGLDataType) {
              case model.context.FLOAT:
                newArray = new Float32Array(pixCount);
                break;
              default:
              case model.context.UNSIGNED_BYTE:
                newArray = new Uint8Array(pixCount);
                break;
            }
            const jFactor = height / newHeight;
            const iFactor = width / newWidth;
            for (let j = 0; j < newHeight; j++) {
              const joff = j * newWidth * numComps;
              const jidx = j * jFactor;
              let jlow = Math.floor(jidx);
              let jhi = Math.ceil(jidx);
              if (jhi >= height) {
                jhi = height - 1;
              }
              const jmix = jidx - jlow;
              const jmix1 = 1.0 - jmix;
              jlow = jlow * width * numComps;
              jhi = jhi * width * numComps;
              for (let i = 0; i < newWidth; i++) {
                const ioff = i * numComps;
                const iidx = i * iFactor;
                let ilow = Math.floor(iidx);
                let ihi = Math.ceil(iidx);
                if (ihi >= width) {
                  ihi = width - 1;
                }
                const imix = iidx - ilow;
                ilow *= numComps;
                ihi *= numComps;
                for (let c = 0; c < numComps; c++) {
                  newArray[joff + ioff + c] =
                    data[idx][jlow + ilow + c] * jmix1 * (1.0 - imix) +
                    data[idx][jlow + ihi + c] * jmix1 * imix +
                    data[idx][jhi + ilow + c] * jmix * (1.0 - imix) +
                    data[idx][jhi + ihi + c] * jmix * imix;
                }
              }
            }
            pixData.push(newArray);
            model.width = newWidth;
            model.height = newHeight;
          } else {
            pixData.push(null);
          }
        }
      }
  
      // The output has to be filled
      if (pixData.length === 0) {
        for (let i = 0; i < data.length; i++) {
          pixData.push(data[i]);
        }
      }
  
      return pixData;
    }
  
    //----------------------------------------------------------------------------
    publicAPI.create2DFromRaw = (width, height, numComps, dataType, data) => {
      // Now determine the texture parameters using the arguments.
      publicAPI.getOpenGLDataType(dataType);
      publicAPI.getInternalFormat(dataType, numComps);
      publicAPI.getFormat(dataType, numComps);
  
      if (!model.internalFormat || !model.format || !model.openGLDataType) {
        vtkErrorMacro('Failed to determine texture parameters.');
        return false;
      }
  
      model.target = model.context.TEXTURE_2D;
      model.components = numComps;
      model.width = width;
      model.height = height;
      model.depth = 1;
      model.numberOfDimensions = 2;
      model.openGLRenderWindow.activateTexture(publicAPI);
      publicAPI.createTexture();
      publicAPI.bind();
  
      // Create an array of texture with one texture
      const dataArray = [data];
      const pixData = updateArrayDataType(dataType, dataArray);
      const scaledData = scaleTextureToHighestPowerOfTwo(pixData);
  
      // Source texture data from the PBO.
      // model.context.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
      model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1);
  
      model.context.texImage2D(
        model.target,
        0,
        model.internalFormat,
        model.width,
        model.height,
        0,
        model.format,
        model.openGLDataType,
        scaledData[0]
      );
  
      if (model.generateMipmap) {
        model.context.generateMipmap(model.target);
      }
  
      publicAPI.deactivate();
      return true;
    };
  
    //----------------------------------------------------------------------------
    publicAPI.createCubeFromRaw = (width, height, numComps, dataType, data) => {
      // Now determine the texture parameters using the arguments.
      publicAPI.getOpenGLDataType(dataType);
      publicAPI.getInternalFormat(dataType, numComps);
      publicAPI.getFormat(dataType, numComps);
  
      if (!model.internalFormat || !model.format || !model.openGLDataType) {
        vtkErrorMacro('Failed to determine texture parameters.');
        return false;
      }
  
      model.target = model.context.TEXTURE_CUBE_MAP;
      model.components = numComps;
      model.width = width;
      model.height = height;
      model.depth = 1;
      model.numberOfDimensions = 2;
      model.openGLRenderWindow.activateTexture(publicAPI);
      model.maxLevel = data.length / 6 - 1;
      publicAPI.createTexture();
      publicAPI.bind();
  
      const pixData = updateArrayDataType(dataType, data);
      const scaledData = scaleTextureToHighestPowerOfTwo(pixData);
  
      // invert the data because opengl is messed up with cube maps
      // and uses the old renderman standard with Y going down
      // even though it is completely at odds with OpenGL standards
      const invertedData = [];
      let widthLevel = model.width;
      let heightLevel = model.height;
      for (let i = 0; i < scaledData.length; i++) {
        if (i % 6 === 0 && i !== 0) {
          widthLevel /= 2;
          heightLevel /= 2;
        }
        invertedData[i] = new window[dataType](
          heightLevel * widthLevel * model.components
        );
        for (let y = 0; y < heightLevel; ++y) {
          const row1 = y * widthLevel * model.components;
          const row2 = (heightLevel - y - 1) * widthLevel * model.components;
          invertedData[i].set(
            scaledData[i].slice(row2, row2 + widthLevel * model.components),
            row1
          );
        }
      }
  
      // Source texture data from the PBO.
      model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1);
  
      // We get the 6 images
      for (let i = 0; i < 6; i++) {
        // For each mipmap level
        let j = 0;
        let w = model.width;
        let h = model.height;
        while (w >= 1 && h >= 1) {
          // In webgl 1, all levels need to be defined. So if the latest level size is
          // 8x8, we have to add 3 more null textures (4x4, 2x2, 1x1)
          // In webgl 2, the attribute maxLevel will be use.
          let tempData = null;
          if (j <= model.maxLevel) {
            tempData = invertedData[6 * j + i];
          }
          model.context.texImage2D(
            model.context.TEXTURE_CUBE_MAP_POSITIVE_X + i,
            j,
            model.internalFormat,
            w,
            h,
            0,
            model.format,
            model.openGLDataType,
            tempData
          );
          j++;
          w /= 2;
          h /= 2;
        }
      }
  
      // generateMipmap must not be called here because we manually upload all levels
      // if it is called, all levels will be overwritten
  
      publicAPI.deactivate();
      return true;
    };
  
    //----------------------------------------------------------------------------
    publicAPI.createDepthFromRaw = (width, height, dataType, data) => {
      // Now determine the texture parameters using the arguments.
      publicAPI.getOpenGLDataType(dataType);
      model.format = model.context.DEPTH_COMPONENT;
      if (model.openGLRenderWindow.getWebgl2()) {
        if (dataType === VtkDataTypes.FLOAT) {
          model.internalFormat = model.context.DEPTH_COMPONENT32F;
        } else {
          model.internalFormat = model.context.DEPTH_COMPONENT16;
        }
      } else {
        model.internalFormat = model.context.DEPTH_COMPONENT;
      }
  
      if (!model.internalFormat || !model.format || !model.openGLDataType) {
        vtkErrorMacro('Failed to determine texture parameters.');
        return false;
      }
  
      model.target = model.context.TEXTURE_2D;
      model.components = 1;
      model.width = width;
      model.height = height;
      model.depth = 1;
      model.numberOfDimensions = 2;
      model.openGLRenderWindow.activateTexture(publicAPI);
      publicAPI.createTexture();
      publicAPI.bind();
  
      // Source texture data from the PBO.
      // model.context.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
      model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1);
  
      model.context.texImage2D(
        model.target,
        0,
        model.internalFormat,
        model.width,
        model.height,
        0,
        model.format,
        model.openGLDataType,
        data
      );
  
      if (model.generateMipmap) {
        model.context.generateMipmap(model.target);
      }
  
      publicAPI.deactivate();
      return true;
    };
  
    //----------------------------------------------------------------------------
    publicAPI.create2DFromImage = (image) => {
      // Now determine the texture parameters using the arguments.
      publicAPI.getOpenGLDataType(VtkDataTypes.UNSIGNED_CHAR);
      publicAPI.getInternalFormat(VtkDataTypes.UNSIGNED_CHAR, 4);
      publicAPI.getFormat(VtkDataTypes.UNSIGNED_CHAR, 4);
  
      if (!model.internalFormat || !model.format || !model.openGLDataType) {
        vtkErrorMacro('Failed to determine texture parameters.');
        return false;
      }
  
      model.target = model.context.TEXTURE_2D;
      model.components = 4;
      model.width = image.width;
      model.height = image.height;
      model.depth = 1;
      model.numberOfDimensions = 2;
      model.openGLRenderWindow.activateTexture(publicAPI);
      publicAPI.createTexture();
      publicAPI.bind();
  
      // Source texture data from the PBO.
      // model.context.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
      model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1);
  
      // Scale up the texture to the next highest power of two dimensions (if needed) and flip y.
      const needNearestPowerOfTwo =
        !vtkMath.isPowerOfTwo(image.width) || !vtkMath.isPowerOfTwo(image.height);
      const canvas = document.createElement('canvas');
      canvas.width = needNearestPowerOfTwo
        ? vtkMath.nearestPowerOfTwo(image.width)
        : image.width;
      canvas.height = needNearestPowerOfTwo
        ? vtkMath.nearestPowerOfTwo(image.height)
        : image.height;
      const ctx = canvas.getContext('2d');
      ctx.translate(0, canvas.height);
      ctx.scale(1, -1);
      ctx.drawImage(
        image,
        0,
        0,
        image.width,
        image.height,
        0,
        0,
        canvas.width,
        canvas.height
      );
      // In Chrome 69 on Windows and Ubuntu, there is a bug that prevents some
      // canvases from working properly with webGL textures.  By getting any
      // image data from the canvas, this works around the bug.  See
      // https://bugs.chromium.org/p/chromium/issues/detail?id=896307
      if (navigator.userAgent.indexOf('Chrome/69') >= 0) {
        ctx.getImageData(0, 0, 1, 1);
      }
      const safeImage = canvas;
  
      model.context.texImage2D(
        model.target,
        0,
        model.internalFormat,
        model.format,
        model.openGLDataType,
        safeImage
      );
  
      if (model.generateMipmap) {
        model.context.generateMipmap(model.target);
      }
  
      publicAPI.deactivate();
      return true;
    };
  
    //----------------------------------------------------------------------------
    publicAPI.create3DFromRaw = (
      width,
      height,
      depth,
      numComps,
      dataType,
      data
    ) => {
      // Now determine the texture parameters using the arguments.
      publicAPI.getOpenGLDataType(dataType);
      publicAPI.getInternalFormat(dataType, numComps);
      publicAPI.getFormat(dataType, numComps);
  
      if (!model.internalFormat || !model.format || !model.openGLDataType) {
        vtkErrorMacro('Failed to determine texture parameters.');
        return false;
      }
  
      model.target = model.context.TEXTURE_3D;
      model.components = numComps;
      model.width = width;
      model.height = height;
      model.depth = depth;
      model.numberOfDimensions = 3;
      model.openGLRenderWindow.activateTexture(publicAPI);
      publicAPI.createTexture();
      publicAPI.bind();
  
      // Source texture data from the PBO.
      // model.context.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
      // model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1);
  
      model.context.texImage3D(
        model.target,
        0,
        model.internalFormat,
        model.width,
        model.height,
        model.depth,
        0,
        model.format,
        model.openGLDataType,
        data
      );
  
      if (model.generateMipmap) {
        model.context.generateMipmap(model.target);
      }
  
      publicAPI.deactivate();
      return true;
    };
  
    function computeScaleOffsets(numComps, numPixelsIn, data) {
      // compute min and max values per component
      const min = [];
      const max = [];
      for (let c = 0; c < numComps; ++c) {
        min[c] = data[c];
        max[c] = data[c];
      }
      let count = 0;
      for (let i = 0; i < numPixelsIn; ++i) {
        for (let c = 0; c < numComps; ++c) {
          if (data[count] < min[c]) {
            min[c] = data[count];
          }
          if (data[count] > max[c]) {
            max[c] = data[count];
          }
          count++;
        }
      }
      const offset = [];
      const scale = [];
      for (let c = 0; c < numComps; ++c) {
        if (min[c] === max[c]) {
          max[c] = min[c] + 1.0;
        }
        offset[c] = min[c];
        scale[c] = max[c] - min[c];
      }
      return { scale, offset };
    }
  
    //----------------------------------------------------------------------------
    // This method simulates a 3D texture using 2D
    publicAPI.create3DFilterableFromRaw = (
      width,
      height,
      depth,
      numComps,
      dataType,
      data
    ) => {
      const numPixelsIn = width * height * depth;
  
      // initialize offset/scale
      const offset = [];
      const scale = [];
      for (let c = 0; c < numComps; ++c) {
        offset[c] = 0.0;
        scale[c] = 1.0;
      }
  
      // store the information, we will need it later
      // offset and scale are the offset and scale required to get
      // the texture value back to data values ala
      // data = texture * scale + offset
      // and texture = (data - offset)/scale
      model.volumeInfo = { scale, offset, width, height, depth };
  
      // WebGL2 path, we have 3d textures etc
      if (model.openGLRenderWindow.getWebgl2()) {
        if (dataType === VtkDataTypes.FLOAT) {
          return publicAPI.create3DFromRaw(
            width,
            height,
            depth,
            numComps,
            dataType,
            data
          );
        }
        if (dataType === VtkDataTypes.UNSIGNED_CHAR) {
          for (let c = 0; c < numComps; ++c) {
            model.volumeInfo.scale[c] = 255.0;
          }
          return publicAPI.create3DFromRaw(
            width,
            height,
            depth,
            numComps,
            dataType,
            data
          );
        }
        // otherwise convert to float
        const newArray = new Float32Array(numPixelsIn * numComps);
        // compute min and max values
        const res = computeScaleOffsets(numComps, numPixelsIn, data);
        model.volumeInfo.offset = res.offset;
        model.volumeInfo.scale = res.scale;
        let count = 0;
        for (let i = 0; i < numPixelsIn; ++i) {
          for (let nc = 0; nc < numComps; ++nc) {
            newArray[count] =
              (data[count] - model.volumeInfo.offset[nc]) /
              model.volumeInfo.scale[nc];
            count++;
          }
        }
        return publicAPI.create3DFromRaw(
          width,
          height,
          depth,
          numComps,
          VtkDataTypes.FLOAT,
          newArray
        );
      }
  
      // not webgl2, deal with webgl1, no 3d textures
      // and maybe no float textures
  
      // compute min and max values
      const res = computeScaleOffsets(numComps, numPixelsIn, data);
  
      let volCopyData = (outArray, outIdx, inValue, smin, smax) => {
        outArray[outIdx] = inValue;
      };
      let dataTypeToUse = VtkDataTypes.UNSIGNED_CHAR;
      // unsigned char gets used as is
      if (dataType === VtkDataTypes.UNSIGNED_CHAR) {
        for (let c = 0; c < numComps; ++c) {
          res.offset[c] = 0.0;
          res.scale[c] = 255.0;
        }
      } else if (
        model.context.getExtension('OES_texture_float') &&
        model.context.getExtension('OES_texture_float_linear')
      ) {
        // use float textures scaled to 0.0 to 1.0
        dataTypeToUse = VtkDataTypes.FLOAT;
        volCopyData = (outArray, outIdx, inValue, soffset, sscale) => {
          outArray[outIdx] = (inValue - soffset) / sscale;
        };
      } else {
        // worst case, scale data to uchar
        dataTypeToUse = VtkDataTypes.UNSIGNED_CHAR;
        volCopyData = (outArray, outIdx, inValue, soffset, sscale) => {
          outArray[outIdx] = (255.0 * (inValue - soffset)) / sscale;
        };
      }
  
      // Now determine the texture parameters using the arguments.
      publicAPI.getOpenGLDataType(dataTypeToUse);
      publicAPI.getInternalFormat(dataTypeToUse, numComps);
      publicAPI.getFormat(dataTypeToUse, numComps);
  
      if (!model.internalFormat || !model.format || !model.openGLDataType) {
        vtkErrorMacro('Failed to determine texture parameters.');
        return false;
      }
  
      // have to pack this 3D texture into pot 2D texture
      model.target = model.context.TEXTURE_2D;
      model.components = numComps;
      model.depth = 1;
      model.numberOfDimensions = 2;
  
      // MAX_TEXTURE_SIZE gives the max dimensions that can be supported by the GPU,
      // but it doesn't mean it will fit in memory. If we have to use a float data type
      // or 4 components, there are good chances that the texture size will blow up
      // and could not fit in the GPU memory. Use a smaller texture size in that case,
      // which will force a downsampling of the dataset.
      // That problem does not occur when using webGL2 since we can pack the data in
      // denser textures based on our data type.
      // TODO: try to fit in the biggest supported texture, catch the gl error if it
      // does not fix (OUT_OF_MEMORY), then attempt with smaller texture
      let maxTexDim = model.context.getParameter(model.context.MAX_TEXTURE_SIZE);
      if (
        maxTexDim > 4096 &&
        (dataTypeToUse === VtkDataTypes.FLOAT || numComps >= 3)
      ) {
        maxTexDim = 4096;
      }
  
      // compute estimate for XY subsample
      let xstride = 1;
      let ystride = 1;
      if (numPixelsIn > maxTexDim * maxTexDim) {
        xstride = Math.ceil(Math.sqrt(numPixelsIn / (maxTexDim * maxTexDim)));
        ystride = xstride;
      }
      let targetWidth = Math.sqrt(numPixelsIn) / xstride;
      targetWidth = vtkMath.nearestPowerOfTwo(targetWidth);
      // determine X reps
      const xreps = Math.floor((targetWidth * xstride) / width);
      const yreps = Math.ceil(depth / xreps);
      const targetHeight = vtkMath.nearestPowerOfTwo((height * yreps) / ystride);
  
      model.width = targetWidth;
      model.height = targetHeight;
      model.openGLRenderWindow.activateTexture(publicAPI);
      publicAPI.createTexture();
      publicAPI.bind();
  
      // store the information, we will need it later
      model.volumeInfo.xreps = xreps;
      model.volumeInfo.yreps = yreps;
      model.volumeInfo.xstride = xstride;
      model.volumeInfo.ystride = ystride;
      model.volumeInfo.offset = res.offset;
      model.volumeInfo.scale = res.scale;
  
      // OK stuff the data into the 2d TEXTURE
  
      // first allocate the new texture
      let newArray;
      const pixCount = targetWidth * targetHeight * numComps;
      if (dataTypeToUse === VtkDataTypes.FLOAT) {
        newArray = new Float32Array(pixCount);
      } else {
        newArray = new Uint8Array(pixCount);
      }
  
      // then stuff the data into it, nothing fancy right now
      // for stride
      let outIdx = 0;
  
      const tileWidth = Math.floor(width / xstride);
      const tileHeight = Math.floor(height / ystride);
  
      for (let yRep = 0; yRep < yreps; yRep++) {
        const xrepsThisRow = Math.min(xreps, depth - yRep * xreps);
        const outXContIncr =
          numComps * (model.width - xrepsThisRow * Math.floor(width / xstride));
        for (let tileY = 0; tileY < tileHeight; tileY++) {
          for (let xRep = 0; xRep < xrepsThisRow; xRep++) {
            const inOffset =
              numComps *
              ((yRep * xreps + xRep) * width * height + ystride * tileY * width);
  
            for (let tileX = 0; tileX < tileWidth; tileX++) {
              // copy value
              for (let nc = 0; nc < numComps; nc++) {
                volCopyData(
                  newArray,
                  outIdx,
                  data[inOffset + xstride * tileX * numComps + nc],
                  res.offset[nc],
                  res.scale[nc]
                );
                outIdx++;
              }
            }
          }
          outIdx += outXContIncr;
        }
      }
  
      // Source texture data from the PBO.
      // model.context.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
      model.context.pixelStorei(model.context.UNPACK_ALIGNMENT, 1);
  
      model.context.texImage2D(
        model.target,
        0,
        model.internalFormat,
        model.width,
        model.height,
        0,
        model.format,
        model.openGLDataType,
        newArray
      );
  
      publicAPI.deactivate();
      return true;
    };
  
    publicAPI.setOpenGLRenderWindow = (rw) => {
      if (model.openGLRenderWindow === rw) {
        return;
      }
      publicAPI.releaseGraphicsResources();
      model.openGLRenderWindow = rw;
      model.context = null;
      if (rw) {
        model.context = model.openGLRenderWindow.getContext();
      }
    };
  
    //----------------------------------------------------------------------------
    publicAPI.getMaximumTextureSize = (ctx) => {
      if (ctx && ctx.isCurrent()) {
        return ctx.getIntegerv(ctx.MAX_TEXTURE_SIZE);
      }
  
      return -1;
    };
  }
  
  // ----------------------------------------------------------------------------
  // Object factory
  // ----------------------------------------------------------------------------
  
  const DEFAULT_VALUES = {
    openGLRenderWindow: null,
    context: null,
    handle: 0,
    sendParametersTime: null,
    textureBuildTime: null,
    numberOfDimensions: 0,
    target: 0,
    format: 0,
    openGLDataType: 0,
    components: 0,
    width: 0,
    height: 0,
    depth: 0,
    autoParameters: true,
    wrapS: Wrap.CLAMP_TO_EDGE,
    wrapT: Wrap.CLAMP_TO_EDGE,
    wrapR: Wrap.CLAMP_TO_EDGE,
    minificationFilter: Filter.NEAREST,
    magnificationFilter: Filter.NEAREST,
    minLOD: -1000.0,
    maxLOD: 1000.0,
    baseLevel: 0,
    maxLevel: 1000,
    generateMipmap: false,
  };
  
  // ----------------------------------------------------------------------------
  
  export function extend(publicAPI, model, initialValues = {}) {
    Object.assign(model, DEFAULT_VALUES, initialValues);
  
    // Inheritance
    vtkViewNode.extend(publicAPI, model, initialValues);
  
    model.sendParametersTime = {};
    macro.obj(model.sendParametersTime, { mtime: 0 });
  
    model.textureBuildTime = {};
    macro.obj(model.textureBuildTime, { mtime: 0 });
  
    // Build VTK API
    macro.set(publicAPI, model, ['format', 'openGLDataType']);
  
    macro.setGet(publicAPI, model, [
      'keyMatrixTime',
      'minificationFilter',
      'magnificationFilter',
      'wrapS',
      'wrapT',
      'wrapR',
      'generateMipmap',
    ]);
  
    macro.get(publicAPI, model, [
      'width',
      'height',
      'volumeInfo',
      'components',
      'handle',
      'target',
    ]);
  
    // Object methods
    vtkOpenGLTexture(publicAPI, model);
  }
  
  // ----------------------------------------------------------------------------
  
  export const newInstance = macro.newInstance(extend, 'vtkOpenGLTexture');
  
  // ----------------------------------------------------------------------------
  
  export default { newInstance, extend, ...Constants };
  