Texture

WebGL Texture

Create custom texture

In order to provide specific properties for rendering, you will have to add
properties into the viewSpecificProperties attribute of your mapper.

Source

Constants.js
export const Wrap = {
CLAMP_TO_EDGE: 0,
REPEAT: 1,
MIRRORED_REPEAT: 2,
};

export const Filter = {
NEAREST: 0,
LINEAR: 1,
NEAREST_MIPMAP_NEAREST: 2,
NEAREST_MIPMAP_LINEAR: 3,
LINEAR_MIPMAP_NEAREST: 4,
LINEAR_MIPMAP_LINEAR: 5,
};

export default {
Wrap,
Filter,
};
index.js
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) {
return 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.context.InternalFormat) {
model.internalFormat = iFormat;
publicAPI.modified();
}
};

//----------------------------------------------------------------------------
publicAPI.getFormat = (vtktype, numComps) => {
if (!model.format) {
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) => {
if (!model.openGLDataType) {
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 attribtue 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;
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;

for (let yRep = 0; yRep < yreps; yRep++) {
const xrepsThisRow = Math.min(xreps, depth - yRep * xreps);
const outXContIncr =
model.width - xrepsThisRow * Math.floor(width / xstride);
for (let inY = 0; inY < height; inY += ystride) {
for (let xRep = 0; xRep < xrepsThisRow; xRep++) {
const inOffset =
numComps * ((yRep * xreps + xRep) * width * height + inY * width);
for (let inX = 0; inX < width; inX += xstride) {
// copy value
for (let nc = 0; nc < numComps; nc++) {
volCopyData(
newArray,
outIdx,
data[inOffset + inX * numComps + nc],
res.offset[nc],
res.scale[nc]
);
outIdx++;
}
}
}
outIdx += outXContIncr * numComps;
}
}

// 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 Object.assign({ newInstance, extend }, Constants);