diff --git a/Sources/Common/Core/CellArray/index.js b/Sources/Common/Core/CellArray/index.js index 97424f50e33..503d36a15cc 100644 --- a/Sources/Common/Core/CellArray/index.js +++ b/Sources/Common/Core/CellArray/index.js @@ -106,7 +106,16 @@ function vtkCellArray(publicAPI, model) { cellPointIds = cell; } const cellId = publicAPI.getNumberOfCells(); - publicAPI.insertNextTuples([cellPointIds.length, ...cellPointIds]); + // Build the [count, ...pointIds] record without the spread operator, which + // would walk the iterator protocol and allocate an extra intermediate for + // every inserted cell. + const numberOfPoints = cellPointIds.length; + const cellRecord = new Array(numberOfPoints + 1); + cellRecord[0] = numberOfPoints; + for (let i = 0; i < numberOfPoints; ++i) { + cellRecord[i + 1] = cellPointIds[i]; + } + publicAPI.insertNextTuples(cellRecord); // By computing the number of cells earlier, we made sure that numberOfCells is defined ++model.numberOfCells; if (model.cellSizes != null) { diff --git a/Sources/Common/DataModel/CellLinks/index.js b/Sources/Common/DataModel/CellLinks/index.js index 7156c385011..0f20b7ba8f0 100644 --- a/Sources/Common/DataModel/CellLinks/index.js +++ b/Sources/Common/DataModel/CellLinks/index.js @@ -105,12 +105,13 @@ function vtkCellLinks(publicAPI, model) { * will be built. */ publicAPI.allocate = (numLinks, ext = 1000) => { - model.array = Array(numLinks) - .fill() - .map(() => ({ - ncells: 0, - cells: null, - })); + // Populate in a single pass. Array(n).fill().map() walks the array three + // times (allocate sparse, fill, map) before producing the link objects. + const array = new Array(numLinks); + for (let i = 0; i < numLinks; ++i) { + array[i] = { ncells: 0, cells: null }; + } + model.array = array; model.extend = ext; model.maxId = -1; }; diff --git a/Sources/Rendering/OpenGL/CellArrayBufferObject/index.js b/Sources/Rendering/OpenGL/CellArrayBufferObject/index.js index 678e42ae441..50000ad8219 100644 --- a/Sources/Rendering/OpenGL/CellArrayBufferObject/index.js +++ b/Sources/Rendering/OpenGL/CellArrayBufferObject/index.js @@ -2,6 +2,7 @@ import { vec3 } from 'gl-matrix'; import macro from 'vtk.js/Sources/macros'; import vtkBufferObject from 'vtk.js/Sources/Rendering/OpenGL/BufferObject'; +import vtkCellArray from 'vtk.js/Sources/Common/Core/CellArray'; import { ObjectType } from 'vtk.js/Sources/Rendering/OpenGL/BufferObject/Constants'; import { Representation } from 'vtk.js/Sources/Rendering/Core/Property/Constants'; import { @@ -26,6 +27,156 @@ function shouldApplyCoordShiftAndScale(coordShift, coordScale) { ); } +function canUseIndexedVBO(options) { + // Indexed (shared point) VBOs avoid expanding every cell vertex. They only + // work for point based attributes. Per cell attributes are baked per vertex + // in the flattened path, and cell accurate hardware selection needs the + // flattened layout too (WebGL2 has no gl_PrimitiveID), so callers ask for it + // explicitly via options.forceFlatten. + return ( + !options.forceFlatten && + !options.haveCellScalars && + !options.haveCellNormals && + !options.useTCoordsPerCell + ); +} + +// Walk one cell, emitting each output vertex via emit(pointId, cellId). The +// indexed and flattened VBO paths share these so the primitive assembly logic +// lives in exactly one place; they differ only in what emit() does (push an +// index vs. expand a full vertex). +const CELL_BUILDERS = { + // easy, every input point becomes an output point + anythingToPoints(numPoints, cellPts, offset, cellId, emit) { + for (let i = 0; i < numPoints; ++i) { + emit(cellPts[offset + i], cellId); + } + }, + linesToWireframe(numPoints, cellPts, offset, cellId, emit) { + // for lines we add a bunch of segments + for (let i = 0; i < numPoints - 1; ++i) { + emit(cellPts[offset + i], cellId); + emit(cellPts[offset + i + 1], cellId); + } + }, + polysToWireframe(numPoints, cellPts, offset, cellId, emit) { + // for polys we add a bunch of segments and close it + if (numPoints > 2) { + for (let i = 0; i < numPoints; ++i) { + emit(cellPts[offset + i], cellId); + emit(cellPts[offset + ((i + 1) % numPoints)], cellId); + } + } + }, + stripsToWireframe(numPoints, cellPts, offset, cellId, emit) { + if (numPoints > 2) { + // for strips we add a bunch of segments and close it + for (let i = 0; i < numPoints - 1; ++i) { + emit(cellPts[offset + i], cellId); + emit(cellPts[offset + i + 1], cellId); + } + for (let i = 0; i < numPoints - 2; i++) { + emit(cellPts[offset + i], cellId); + emit(cellPts[offset + i + 2], cellId); + } + } + }, + polysToSurface(numPoints, cellPts, offset, cellId, emit) { + for (let i = 0; i < numPoints - 2; i++) { + emit(cellPts[offset], cellId); + emit(cellPts[offset + i + 1], cellId); + emit(cellPts[offset + i + 2], cellId); + } + }, + stripsToSurface(numPoints, cellPts, offset, cellId, emit) { + for (let i = 0; i < numPoints - 2; i++) { + emit(cellPts[offset + i], cellId); + emit(cellPts[offset + i + 1 + (i % 2)], cellId); + emit(cellPts[offset + i + 1 + ((i + 1) % 2)], cellId); + } + }, +}; + +// Number of output vertices a single cell produces, matching CELL_BUILDERS. +const CELL_COUNTERS = { + anythingToPoints(numPoints) { + return numPoints; + }, + linesToWireframe(numPoints) { + return numPoints > 1 ? (numPoints - 1) * 2 : 0; + }, + polysToWireframe(numPoints) { + return numPoints > 2 ? numPoints * 2 : 0; + }, + stripsToWireframe(numPoints) { + return numPoints > 2 ? numPoints * 4 - 6 : 0; + }, + polysToSurface(numPoints) { + return numPoints > 2 ? (numPoints - 2) * 3 : 0; + }, + stripsToSurface(numPoints) { + return numPoints > 2 ? (numPoints - 2) * 3 : 0; + }, +}; + +// Pick the build/count pair for an input cell type + output representation. +function getCellHandlers(inRep, outRep) { + if (outRep === Representation.POINTS || inRep === 'verts') { + return { + build: CELL_BUILDERS.anythingToPoints, + count: CELL_COUNTERS.anythingToPoints, + }; + } + if (outRep === Representation.WIREFRAME || inRep === 'lines') { + return { + build: CELL_BUILDERS[`${inRep}ToWireframe`], + count: CELL_COUNTERS[`${inRep}ToWireframe`], + }; + } + return { + build: CELL_BUILDERS[`${inRep}ToSurface`], + count: CELL_COUNTERS[`${inRep}ToSurface`], + }; +} + +// Total number of output vertices across every cell in the array. +function countOutputVertices(cellData, count) { + let total = 0; + for (let index = 0; index < cellData.length; index += cellData[index] + 1) { + total += count(cellData[index]); + } + return total; +} + +// Walk every cell in the array, calling build() with a running cell id. +// Returns the next cell id (cellOffset + number of cells walked). +function forEachCell(cellData, cellOffset, build, emit) { + let cellId = cellOffset; + for (let index = 0; index < cellData.length; index += cellData[index] + 1) { + build(cellData[index], cellData, index + 1, cellId, emit); + cellId++; + } + return cellId; +} + +function buildIndexArray(cellArray, inRep, outRep, options) { + const cellData = cellArray.getData(); + const { build, count } = getCellHandlers(inRep, outRep); + + const indexCount = countOutputVertices(cellData, count); + const indices = + options.points.getNumberOfPoints() <= 0xffff + ? new Uint16Array(indexCount) + : new Uint32Array(indexCount); + + let cursor = 0; + const cellCount = forEachCell(cellData, 0, build, (pointId) => { + indices[cursor++] = pointId; + }); + + return { indices, cellCount }; +} + // ---------------------------------------------------------------------------- // vtkOpenGLCellArrayBufferObject methods // ---------------------------------------------------------------------------- @@ -45,6 +196,7 @@ function vtkOpenGLCellArrayBufferObject(publicAPI, model) { ) => { if (!cellArray.getData() || !cellArray.getData().length) { model.elementCount = 0; + model.indexed = false; return 0; } @@ -112,123 +264,123 @@ function vtkOpenGLCellArrayBufferObject(publicAPI, model) { } model.stride = 4 * model.blockSize; + // The indexed path avoids expanding point attributes once for every cell + // vertex. It is limited to point based attributes; per cell attributes still + // need the flattened path below. let pointIdx = 0; let normalIdx = 0; let tcoordIdx = 0; let colorIdx = 0; let custIdx = 0; - let cellCount = 0; - let addAPoint; - const cellBuilders = { - // easy, every input point becomes an output point - anythingToPoints(numPoints, cellPts, offset, cellId) { - for (let i = 0; i < numPoints; ++i) { - addAPoint(cellPts[offset + i], cellId); - } - }, - linesToWireframe(numPoints, cellPts, offset, cellIdx) { - // for lines we add a bunch of segments - for (let i = 0; i < numPoints - 1; ++i) { - addAPoint(cellPts[offset + i], cellIdx); - addAPoint(cellPts[offset + i + 1], cellIdx); + if (canUseIndexedVBO(options)) { + const { indices: indexArray, cellCount } = buildIndexArray( + cellArray, + inRep, + outRep, + options + ); + if (!model.indexBO) { + model.indexBO = vtkBufferObject.newInstance(); + } + model.indexBO.setOpenGLRenderWindow(model._openGLRenderWindow); + model.indexBO.upload(indexArray, ObjectType.ELEMENT_ARRAY_BUFFER); + if (indexArray instanceof Uint16Array) { + model.indexElementType = model.context.UNSIGNED_SHORT; + } else { + model.indexElementType = model.context.UNSIGNED_INT; + } + + const numberOfPoints = options.points.getNumberOfPoints(); + const packedVBO = new Float32Array(numberOfPoints * model.blockSize); + let vboidx = 0; + + const { useShiftAndScale, coordShift, coordScale } = + computeCoordShiftAndScale(options.points); + + if (useShiftAndScale) { + publicAPI.setCoordShiftAndScale(coordShift, coordScale); + } else if (model.coordShiftAndScaleEnabled === true) { + publicAPI.setCoordShiftAndScale(null, null); + } + + for (let pointId = 0; pointId < numberOfPoints; pointId++) { + pointIdx = pointId * 3; + if (!model.coordShiftAndScaleEnabled) { + packedVBO[vboidx++] = pointData[pointIdx++]; + packedVBO[vboidx++] = pointData[pointIdx++]; + packedVBO[vboidx++] = pointData[pointIdx++]; + } else { + packedVBO[vboidx++] = + (pointData[pointIdx++] - model.coordShift[0]) * model.coordScale[0]; + packedVBO[vboidx++] = + (pointData[pointIdx++] - model.coordShift[1]) * model.coordScale[1]; + packedVBO[vboidx++] = + (pointData[pointIdx++] - model.coordShift[2]) * model.coordScale[2]; } - }, - polysToWireframe(numPoints, cellPts, offset, cellIdx) { - // for polys we add a bunch of segments and close it - if (numPoints > 2) { - for (let i = 0; i < numPoints; ++i) { - addAPoint(cellPts[offset + i], cellIdx); - addAPoint(cellPts[offset + ((i + 1) % numPoints)], cellIdx); - } + + if (normalData !== null) { + normalIdx = pointId * 3; + packedVBO[vboidx++] = normalData[normalIdx++]; + packedVBO[vboidx++] = normalData[normalIdx++]; + packedVBO[vboidx++] = normalData[normalIdx++]; } - }, - stripsToWireframe(numPoints, cellPts, offset, cellIdx) { - if (numPoints > 2) { - // for strips we add a bunch of segments and close it - for (let i = 0; i < numPoints - 1; ++i) { - addAPoint(cellPts[offset + i], cellIdx); - addAPoint(cellPts[offset + i + 1], cellIdx); + + model.customData.forEach((attr) => { + custIdx = pointId * attr.components; + for (let j = 0; j < attr.components; ++j) { + packedVBO[vboidx++] = attr.data[custIdx++]; } - for (let i = 0; i < numPoints - 2; i++) { - addAPoint(cellPts[offset + i], cellIdx); - addAPoint(cellPts[offset + i + 2], cellIdx); + }); + + if (tcoordData !== null) { + tcoordIdx = pointId * textureComponents; + for (let j = 0; j < textureComponents; ++j) { + packedVBO[vboidx++] = tcoordData[tcoordIdx++]; } } - }, - polysToSurface(npts, cellPts, offset, cellIdx) { - for (let i = 0; i < npts - 2; i++) { - addAPoint(cellPts[offset + 0], cellIdx); - addAPoint(cellPts[offset + i + 1], cellIdx); - addAPoint(cellPts[offset + i + 2], cellIdx); - } - }, - stripsToSurface(npts, cellPts, offset, cellIdx) { - for (let i = 0; i < npts - 2; i++) { - addAPoint(cellPts[offset + i], cellIdx); - addAPoint(cellPts[offset + i + 1 + (i % 2)], cellIdx); - addAPoint(cellPts[offset + i + 1 + ((i + 1) % 2)], cellIdx); - } - }, - }; + } - const cellCounters = { - // easy, every input point becomes an output point - anythingToPoints(numPoints, cellPts) { - return numPoints; - }, - linesToWireframe(numPoints, cellPts) { - if (numPoints > 1) { - return (numPoints - 1) * 2; - } - return 0; - }, - polysToWireframe(numPoints, cellPts) { - if (numPoints > 2) { - return numPoints * 2; - } - return 0; - }, - stripsToWireframe(numPoints, cellPts) { - if (numPoints > 2) { - return numPoints * 4 - 6; - } - return 0; - }, - polysToSurface(npts, cellPts) { - if (npts > 2) { - return (npts - 2) * 3; - } - return 0; - }, - stripsToSurface(npts, cellPts, offset) { - if (npts > 2) { - return (npts - 2) * 3; + publicAPI.upload(packedVBO, ObjectType.ARRAY_BUFFER); + + if (model.colorBO) { + const packedUCVBO = new Uint8Array(numberOfPoints * 4); + let ucidx = 0; + for (let pointId = 0; pointId < numberOfPoints; pointId++) { + colorIdx = pointId * colorComponents; + packedUCVBO[ucidx++] = colorData[colorIdx++]; + packedUCVBO[ucidx++] = colorData[colorIdx++]; + packedUCVBO[ucidx++] = colorData[colorIdx++]; + if (colorComponents === 4) { + packedUCVBO[ucidx++] = colorData[colorIdx++]; + } else { + packedUCVBO[ucidx++] = 255; + } } - return 0; - }, - }; + model.colorBOStride = 4; + model.colorBO.upload(packedUCVBO, ObjectType.ARRAY_BUFFER); + } - let func = null; - let countFunc = null; - if (outRep === Representation.POINTS || inRep === 'verts') { - func = cellBuilders.anythingToPoints; - countFunc = cellCounters.anythingToPoints; - } else if (outRep === Representation.WIREFRAME || inRep === 'lines') { - func = cellBuilders[`${inRep}ToWireframe`]; - countFunc = cellCounters[`${inRep}ToWireframe`]; - } else { - func = cellBuilders[`${inRep}ToSurface`]; - countFunc = cellCounters[`${inRep}ToSurface`]; + model.elementCount = indexArray.length; + model.indexed = true; + return cellCount; } - const array = cellArray.getData(); - const size = array.length; - let caboCount = 0; - for (let index = 0; index < size; ) { - caboCount += countFunc(array[index], array); - index += array[index] + 1; + if (model.indexBO) { + model.indexBO.releaseGraphicsResources(); } + model.indexed = false; + model.indexElementType = null; + + let addAPoint; + + const { build: cellBuilder, count: cellCounter } = getCellHandlers( + inRep, + outRep + ); + + const array = cellArray.getData(); + const caboCount = countOutputVertices(array, cellCounter); let packedUCVBO = null; const packedVBO = new Float32Array(caboCount * model.blockSize); @@ -271,7 +423,7 @@ function vtkOpenGLCellArrayBufferObject(publicAPI, model) { // Keep track of original point and cell ids, for selection if (selectionMaps) { selectionMaps.points[pointCount] = pointId; - selectionMaps.cells[pointCount] = cellCount + options.cellOffset; + selectionMaps.cells[pointCount] = cellId; } ++pointCount; @@ -294,7 +446,7 @@ function vtkOpenGLCellArrayBufferObject(publicAPI, model) { if (normalData !== null) { if (options.haveCellNormals) { - normalIdx = (cellCount + options.cellOffset) * 3; + normalIdx = cellId * 3; } else { normalIdx = pointId * 3; } @@ -323,7 +475,7 @@ function vtkOpenGLCellArrayBufferObject(publicAPI, model) { if (colorData !== null) { if (options.haveCellScalars) { - colorIdx = (cellCount + options.cellOffset) * colorComponents; + colorIdx = cellId * colorComponents; } else { colorIdx = pointId * colorComponents; } @@ -335,18 +487,16 @@ function vtkOpenGLCellArrayBufferObject(publicAPI, model) { } }; - // Browse the cell array: the index is at the beginning of a cell - // The value of 'array' at the position 'index' is the number of points in the cell - for (let index = 0; index < size; index += array[index] + 1, cellCount++) { - func(array[index], array, index + 1, cellCount + options.cellOffset); - } + // Browse the cell array, expanding every cell vertex into the packed VBO. + forEachCell(array, options.cellOffset, cellBuilder, addAPoint); + model.elementCount = caboCount; publicAPI.upload(packedVBO, ObjectType.ARRAY_BUFFER); if (model.colorBO) { model.colorBOStride = 4; model.colorBO.upload(packedUCVBO, ObjectType.ARRAY_BUFFER); } - return cellCount; + return vtkCellArray.getNumberOfCells(cellArray.getData()); }; publicAPI.setCoordShiftAndScale = (coordShift, coordScale) => { @@ -396,6 +546,25 @@ function vtkOpenGLCellArrayBufferObject(publicAPI, model) { model.inverseShiftAndScaleMatrix = null; } }; + + const parentReleaseGraphicsResources = publicAPI.releaseGraphicsResources; + publicAPI.releaseGraphicsResources = () => { + parentReleaseGraphicsResources(); + if (model.indexBO) { + model.indexBO.releaseGraphicsResources(); + } + if (model.colorBO) { + model.colorBO.releaseGraphicsResources(); + } + model.indexed = false; + }; + + const parentGetAllocatedGPUMemoryInBytes = + publicAPI.getAllocatedGPUMemoryInBytes; + publicAPI.getAllocatedGPUMemoryInBytes = () => + parentGetAllocatedGPUMemoryInBytes() + + (model.indexBO ? model.indexBO.getAllocatedGPUMemoryInBytes() : 0) + + (model.colorBO ? model.colorBO.getAllocatedGPUMemoryInBytes() : 0); } // ---------------------------------------------------------------------------- @@ -404,6 +573,9 @@ function vtkOpenGLCellArrayBufferObject(publicAPI, model) { const DEFAULT_VALUES = { elementCount: 0, + indexed: false, + indexBO: null, + indexElementType: null, stride: 0, colorBOStride: 0, vertexOffset: 0, @@ -440,6 +612,9 @@ export function extend(publicAPI, model, initialValues = {}) { 'colorOffset', 'colorComponents', 'customData', + 'indexed', + 'indexBO', + 'indexElementType', ]); macro.get(publicAPI, model, [ diff --git a/Sources/Rendering/OpenGL/Convolution2DPass/index.js b/Sources/Rendering/OpenGL/Convolution2DPass/index.js index 4ed2175ad30..0545c4ca855 100644 --- a/Sources/Rendering/OpenGL/Convolution2DPass/index.js +++ b/Sources/Rendering/OpenGL/Convolution2DPass/index.js @@ -253,6 +253,9 @@ function vtkConvolution2DPass(publicAPI, model) { points, tcoords, cellOffset: 0, + // This pass draws with gl.drawArrays, so it needs the flattened + // (non indexed) vertex layout where elementCount matches the vertices. + forceFlatten: true, }); model.VBOBuildTime.modified(); diff --git a/Sources/Rendering/OpenGL/Framebuffer/index.js b/Sources/Rendering/OpenGL/Framebuffer/index.js index 045eaea3466..34294f06c36 100644 --- a/Sources/Rendering/OpenGL/Framebuffer/index.js +++ b/Sources/Rendering/OpenGL/Framebuffer/index.js @@ -38,7 +38,7 @@ function vtkFramebuffer(publicAPI, model) { }; publicAPI.saveCurrentBuffers = (modeIn) => { - // noop on webgl 1 + // noop on webgl }; publicAPI.restorePreviousBindingsAndBuffers = (modeIn) => { @@ -64,7 +64,7 @@ function vtkFramebuffer(publicAPI, model) { }; publicAPI.restorePreviousBuffers = (modeIn) => { - // currently a noop on webgl1 + // currently a noop }; publicAPI.bind = (modeArg = null) => { @@ -104,14 +104,7 @@ function vtkFramebuffer(publicAPI, model) { let glAttachment = gl.COLOR_ATTACHMENT0; if (attachment > 0) { - if (model._openGLRenderWindow.getWebgl2()) { - glAttachment += attachment; - } else { - macro.vtkErrorMacro( - 'Using multiple framebuffer attachments requires WebGL 2' - ); - return; - } + glAttachment += attachment; } model.colorBuffers[attachment] = texture; gl.framebufferTexture2D( @@ -135,14 +128,7 @@ function vtkFramebuffer(publicAPI, model) { let glAttachment = gl.COLOR_ATTACHMENT0; if (attachment > 0) { - if (model._openGLRenderWindow.getWebgl2()) { - glAttachment += attachment; - } else { - macro.vtkErrorMacro( - 'Using multiple framebuffer attachments requires WebGL 2' - ); - return; - } + glAttachment += attachment; } gl.framebufferTexture2D( @@ -164,20 +150,14 @@ function vtkFramebuffer(publicAPI, model) { return; } - if (model._openGLRenderWindow.getWebgl2()) { - const gl = model.context; - gl.framebufferTexture2D( - gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - texture.getHandle(), - 0 - ); - } else { - macro.vtkErrorMacro( - 'Attaching depth buffer textures to fbo requires WebGL 2' - ); - } + const gl = model.context; + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + texture.getHandle(), + 0 + ); }; publicAPI.removeDepthBuffer = () => { @@ -188,20 +168,14 @@ function vtkFramebuffer(publicAPI, model) { return; } - if (model._openGLRenderWindow.getWebgl2()) { - const gl = model.context; - gl.framebufferTexture2D( - gl.FRAMEBUFFER, - gl.DEPTH_ATTACHMENT, - gl.TEXTURE_2D, - null, - 0 - ); - } else { - macro.vtkErrorMacro( - 'Attaching depth buffer textures to framebuffers requires WebGL 2' - ); - } + const gl = model.context; + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.DEPTH_ATTACHMENT, + gl.TEXTURE_2D, + null, + 0 + ); }; publicAPI.getGLFramebuffer = () => model.glFramebuffer; diff --git a/Sources/Rendering/OpenGL/Glyph3DMapper/index.js b/Sources/Rendering/OpenGL/Glyph3DMapper/index.js index 82082c6ea94..49456d56706 100644 --- a/Sources/Rendering/OpenGL/Glyph3DMapper/index.js +++ b/Sources/Rendering/OpenGL/Glyph3DMapper/index.js @@ -1,9 +1,8 @@ -import { mat3, mat4 } from 'gl-matrix'; +import { mat4 } from 'gl-matrix'; import * as macro from 'vtk.js/Sources/macros'; import vtkBufferObject from 'vtk.js/Sources/Rendering/OpenGL/BufferObject'; -import vtkHardwareSelector from 'vtk.js/Sources/Rendering/OpenGL/HardwareSelector'; import vtkProperty from 'vtk.js/Sources/Rendering/Core/Property'; import vtkOpenGLPolyDataMapper from 'vtk.js/Sources/Rendering/OpenGL/PolyDataMapper'; import vtkShaderProgram from 'vtk.js/Sources/Rendering/OpenGL/ShaderProgram'; @@ -18,7 +17,6 @@ import { primTypes } from '../Helper'; const { vtkErrorMacro } = macro; const { Representation } = vtkProperty; const { ObjectType } = vtkBufferObject; -const { PassTypes } = vtkHardwareSelector; const StartEvent = { type: 'StartEvent' }; const EndEvent = { type: 'EndEvent' }; @@ -60,16 +58,6 @@ function vtkOpenGLGlyph3DMapper(publicAPI, model) { // apply faceCulling const gl = model.context; - if (model._openGLRenderWindow.getWebgl2()) { - model.hardwareSupport = true; - model.extension = null; - } else if (!model.extension) { - model.extension = model.context.getExtension('ANGLE_instanced_arrays'); - model.hardwareSupport = !!model.extension; - } - // to test without extension support uncomment the next two lines - // model.extension = null; - // model.hardwareSupport = !!model.extension; const backfaceCulling = actor.getProperty().getBackfaceCulling(); const frontfaceCulling = actor.getProperty().getFrontfaceCulling(); @@ -88,142 +76,78 @@ function vtkOpenGLGlyph3DMapper(publicAPI, model) { publicAPI.renderPieceFinish(ren, actor); }; - publicAPI.multiply4x4WithOffset = (out, a, b, off) => { - const a00 = a[0]; - const a01 = a[1]; - const a02 = a[2]; - const a03 = a[3]; - const a10 = a[4]; - const a11 = a[5]; - const a12 = a[6]; - const a13 = a[7]; - const a20 = a[8]; - const a21 = a[9]; - const a22 = a[10]; - const a23 = a[11]; - const a30 = a[12]; - const a31 = a[13]; - const a32 = a[14]; - const a33 = a[15]; - - // Cache only the current line of the second matrix - let b0 = b[off]; - let b1 = b[off + 1]; - let b2 = b[off + 2]; - let b3 = b[off + 3]; - out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; - out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; - out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; - out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; - - b0 = b[off + 4]; - b1 = b[off + 5]; - b2 = b[off + 6]; - b3 = b[off + 7]; - out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; - out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; - out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; - out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; - - b0 = b[off + 8]; - b1 = b[off + 9]; - b2 = b[off + 10]; - b3 = b[off + 11]; - out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; - out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; - out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; - out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; - - b0 = b[off + 12]; - b1 = b[off + 13]; - b2 = b[off + 14]; - b3 = b[off + 15]; - out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; - out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; - out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; - out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; - }; - publicAPI.replaceShaderNormal = (shaders, ren, actor) => { - if (model.hardwareSupport) { - const lastLightComplexity = model.lastBoundBO.getReferenceByName( - 'lastLightComplexity' - ); + const lastLightComplexity = model.lastBoundBO.getReferenceByName( + 'lastLightComplexity' + ); - if (lastLightComplexity > 0) { - let VSSource = shaders.Vertex; - - if (model.lastBoundBO.getCABO().getNormalOffset()) { - VSSource = vtkShaderProgram.substitute( - VSSource, - '//VTK::Normal::Dec', - [ - 'attribute vec3 normalMC;', - 'attribute mat3 gNormal;', - 'uniform mat3 normalMatrix;', - 'varying vec3 normalVCVSOutput;', - ] - ).result; - VSSource = vtkShaderProgram.substitute( - VSSource, - '//VTK::Normal::Impl', - ['normalVCVSOutput = normalMatrix * gNormal * normalMC;'] - ).result; - } - shaders.Vertex = VSSource; + if (lastLightComplexity > 0) { + let VSSource = shaders.Vertex; + + if (model.lastBoundBO.getCABO().getNormalOffset()) { + VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Normal::Dec', [ + 'attribute vec3 normalMC;', + 'attribute mat3 gNormal;', + 'uniform mat3 normalMatrix;', + 'varying vec3 normalVCVSOutput;', + ]).result; + VSSource = vtkShaderProgram.substitute( + VSSource, + '//VTK::Normal::Impl', + ['normalVCVSOutput = normalMatrix * gNormal * normalMC;'] + ).result; } + shaders.Vertex = VSSource; } superClass.replaceShaderNormal(shaders, ren, actor); }; publicAPI.replaceShaderClip = (shaders, ren, actor) => { - if (model.hardwareSupport) { - let VSSource = shaders.Vertex; - let FSSource = shaders.Fragment; - - if (model.renderable.getNumberOfClippingPlanes()) { - const numClipPlanes = model.renderable.getNumberOfClippingPlanes(); - VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Clip::Dec', [ - 'uniform int numClipPlanes;', - `uniform vec4 clipPlanes[${numClipPlanes}];`, - `varying float clipDistancesVSOutput[${numClipPlanes}];`, - ]).result; + let VSSource = shaders.Vertex; + let FSSource = shaders.Fragment; + + if (model.renderable.getNumberOfClippingPlanes()) { + const numClipPlanes = model.renderable.getNumberOfClippingPlanes(); + VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Clip::Dec', [ + 'uniform int numClipPlanes;', + `uniform vec4 clipPlanes[${numClipPlanes}];`, + `varying float clipDistancesVSOutput[${numClipPlanes}];`, + ]).result; - VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Clip::Impl', [ - `for (int planeNum = 0; planeNum < ${numClipPlanes}; planeNum++)`, - ' {', - ' if (planeNum >= numClipPlanes)', - ' {', - ' break;', - ' }', - ' vec4 gVertex = gMatrix * vertexMC;', - ' clipDistancesVSOutput[planeNum] = dot(clipPlanes[planeNum], gVertex);', - ' }', - ]).result; - FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Clip::Dec', [ - 'uniform int numClipPlanes;', - `varying float clipDistancesVSOutput[${numClipPlanes}];`, - ]).result; + VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Clip::Impl', [ + `for (int planeNum = 0; planeNum < ${numClipPlanes}; planeNum++)`, + ' {', + ' if (planeNum >= numClipPlanes)', + ' {', + ' break;', + ' }', + ' vec4 gVertex = gMatrix * vertexMC;', + ' clipDistancesVSOutput[planeNum] = dot(clipPlanes[planeNum], gVertex);', + ' }', + ]).result; + FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Clip::Dec', [ + 'uniform int numClipPlanes;', + `varying float clipDistancesVSOutput[${numClipPlanes}];`, + ]).result; - FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Clip::Impl', [ - `for (int planeNum = 0; planeNum < ${numClipPlanes}; planeNum++)`, - ' {', - ' if (planeNum >= numClipPlanes)', - ' {', - ' break;', - ' }', - ' if (clipDistancesVSOutput[planeNum] < 0.0) discard;', - ' }', - ]).result; - } - shaders.Vertex = VSSource; - shaders.Fragment = FSSource; + FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Clip::Impl', [ + `for (int planeNum = 0; planeNum < ${numClipPlanes}; planeNum++)`, + ' {', + ' if (planeNum >= numClipPlanes)', + ' {', + ' break;', + ' }', + ' if (clipDistancesVSOutput[planeNum] < 0.0) discard;', + ' }', + ]).result; } + shaders.Vertex = VSSource; + shaders.Fragment = FSSource; superClass.replaceShaderClip(shaders, ren, actor); }; publicAPI.replaceShaderColor = (shaders, ren, actor) => { - if (model.hardwareSupport && model.renderable.getColorArray()) { + if (model.renderable.getColorArray()) { let VSSource = shaders.Vertex; let GSSource = shaders.Geometry; let FSSource = shaders.Fragment; @@ -314,158 +238,68 @@ function vtkOpenGLGlyph3DMapper(publicAPI, model) { }; publicAPI.replaceShaderPositionVC = (shaders, ren, actor) => { - if (model.hardwareSupport) { - let VSSource = shaders.Vertex; - - // do we need the vertex in the shader in View Coordinates - const lastLightComplexity = model.lastBoundBO.getReferenceByName( - 'lastLightComplexity' - ); - if (lastLightComplexity > 0) { - VSSource = vtkShaderProgram.substitute( - VSSource, - '//VTK::PositionVC::Impl', - [ - 'vec4 gVertexMC = gMatrix * vertexMC;', - 'vertexVCVSOutput = MCVCMatrix * gVertexMC;', - ' gl_Position = MCPCMatrix * gVertexMC;', - ] - ).result; - VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Camera::Dec', [ - 'attribute mat4 gMatrix;', - 'uniform mat4 MCPCMatrix;', - 'uniform mat4 MCVCMatrix;', - ]).result; - } else { - VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Camera::Dec', [ - 'attribute mat4 gMatrix;', - 'uniform mat4 MCPCMatrix;', - ]).result; - VSSource = vtkShaderProgram.substitute( - VSSource, - '//VTK::PositionVC::Impl', - [ - 'vec4 gVertexMC = gMatrix * vertexMC;', - ' gl_Position = MCPCMatrix * gVertexMC;', - ] - ).result; - } - shaders.Vertex = VSSource; - } - superClass.replaceShaderPositionVC(shaders, ren, actor); - }; + let VSSource = shaders.Vertex; - publicAPI.replaceShaderPicking = (shaders, ren, actor) => { - if (model.hardwareSupport) { - let FSSource = shaders.Fragment; - let VSSource = shaders.Vertex; - VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Picking::Dec', [ - 'attribute vec3 mapperIndexVS;', - 'varying vec3 mapperIndexVSOutput;', - ]).result; + // do we need the vertex in the shader in View Coordinates + const lastLightComplexity = model.lastBoundBO.getReferenceByName( + 'lastLightComplexity' + ); + if (lastLightComplexity > 0) { VSSource = vtkShaderProgram.substitute( VSSource, - '//VTK::Picking::Impl', - ' mapperIndexVSOutput = mapperIndexVS;' + '//VTK::PositionVC::Impl', + [ + 'vec4 gVertexMC = gMatrix * vertexMC;', + 'vertexVCVSOutput = MCVCMatrix * gVertexMC;', + ' gl_Position = MCPCMatrix * gVertexMC;', + ] ).result; - shaders.Vertex = VSSource; - FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Picking::Dec', [ - 'varying vec3 mapperIndexVSOutput;', - 'uniform vec3 mapperIndex;', - 'uniform int picking;', + VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Camera::Dec', [ + 'attribute mat4 gMatrix;', + 'uniform mat4 MCPCMatrix;', + 'uniform mat4 MCVCMatrix;', ]).result; - FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Picking::Impl', [ - ' vec4 pickColor = picking == 2 ? vec4(mapperIndexVSOutput,1.0) : vec4(mapperIndex,1.0);', - ' gl_FragData[0] = picking != 0 ? pickColor : gl_FragData[0];', - ]).result; - shaders.Fragment = FSSource; } else { - superClass.replaceShaderPicking(shaders, ren, actor); + VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Camera::Dec', [ + 'attribute mat4 gMatrix;', + 'uniform mat4 MCPCMatrix;', + ]).result; + VSSource = vtkShaderProgram.substitute( + VSSource, + '//VTK::PositionVC::Impl', + [ + 'vec4 gVertexMC = gMatrix * vertexMC;', + ' gl_Position = MCPCMatrix * gVertexMC;', + ] + ).result; } + shaders.Vertex = VSSource; + superClass.replaceShaderPositionVC(shaders, ren, actor); }; - publicAPI.updateGlyphShaderParameters = ( - normalMatrixUsed, - mcvcMatrixUsed, - cellBO, - carray, - garray, - narray, - p, - selector - ) => { - const program = cellBO.getProgram(); - - if (normalMatrixUsed) { - const a = model.normalMatrix; - const b = narray; - const ofs = p * 9; - const out = model.tmpMat3; - - const a00 = a[0]; - const a01 = a[1]; - const a02 = a[2]; - const a10 = a[3]; - const a11 = a[4]; - const a12 = a[5]; - const a20 = a[6]; - const a21 = a[7]; - const a22 = a[8]; - - const b00 = b[ofs]; - const b01 = b[ofs + 1]; - const b02 = b[ofs + 2]; - const b10 = b[ofs + 3]; - const b11 = b[ofs + 4]; - const b12 = b[ofs + 5]; - const b20 = b[ofs + 6]; - const b21 = b[ofs + 7]; - const b22 = b[ofs + 8]; - - out[0] = b00 * a00 + b01 * a10 + b02 * a20; - out[1] = b00 * a01 + b01 * a11 + b02 * a21; - out[2] = b00 * a02 + b01 * a12 + b02 * a22; - - out[3] = b10 * a00 + b11 * a10 + b12 * a20; - out[4] = b10 * a01 + b11 * a11 + b12 * a21; - out[5] = b10 * a02 + b11 * a12 + b12 * a22; - - out[6] = b20 * a00 + b21 * a10 + b22 * a20; - out[7] = b20 * a01 + b21 * a11 + b22 * a21; - out[8] = b20 * a02 + b21 * a12 + b22 * a22; - - program.setUniformMatrix3x3('normalMatrix', model.tmpMat3); - } - publicAPI.multiply4x4WithOffset( - model.tmpMat4, - model.mcpcMatrix, - garray, - p * 16 - ); - program.setUniformMatrix('MCPCMatrix', model.tmpMat4); - if (mcvcMatrixUsed) { - publicAPI.multiply4x4WithOffset( - model.tmpMat4, - model.mcvcMatrix, - garray, - p * 16 - ); - program.setUniformMatrix('MCVCMatrix', model.tmpMat4); - } - - // set color - if (carray) { - const cdata = carray.getData(); - model.tmpColor[0] = cdata[p * 4] / 255.0; - model.tmpColor[1] = cdata[p * 4 + 1] / 255.0; - model.tmpColor[2] = cdata[p * 4 + 2] / 255.0; - program.setUniform3fArray('ambientColorUniform', model.tmpColor); - program.setUniform3fArray('diffuseColorUniform', model.tmpColor); - } - - if (selector) { - program.setUniform3fArray('mapperIndex', selector.getPropColorValue()); - } + publicAPI.replaceShaderPicking = (shaders, ren, actor) => { + let FSSource = shaders.Fragment; + let VSSource = shaders.Vertex; + VSSource = vtkShaderProgram.substitute(VSSource, '//VTK::Picking::Dec', [ + 'attribute vec3 mapperIndexVS;', + 'varying vec3 mapperIndexVSOutput;', + ]).result; + VSSource = vtkShaderProgram.substitute( + VSSource, + '//VTK::Picking::Impl', + ' mapperIndexVSOutput = mapperIndexVS;' + ).result; + shaders.Vertex = VSSource; + FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Picking::Dec', [ + 'varying vec3 mapperIndexVSOutput;', + 'uniform vec3 mapperIndex;', + 'uniform int picking;', + ]).result; + FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Picking::Impl', [ + ' vec4 pickColor = picking == 2 ? vec4(mapperIndexVSOutput,1.0) : vec4(mapperIndex,1.0);', + ' gl_FragData[0] = picking != 0 ? pickColor : gl_FragData[0];', + ]).result; + shaders.Fragment = FSSource; }; publicAPI.renderPieceDraw = (ren, actor) => { @@ -477,35 +311,9 @@ function vtkOpenGLGlyph3DMapper(publicAPI, model) { actor.getProperty().getEdgeVisibility() && representation === Representation.SURFACE; - // [WMVP]C == {world, model, view, projection} coordinates - // E.g., WCPC == world to projection coordinate transformation - const keyMats = model.openGLCamera.getKeyMatrices(ren); - const actMats = model.openGLActor.getKeyMatrices(); - - // precompute the actor+camera mats once - mat3.multiply( - model.normalMatrix, - keyMats.normalMatrix, - actMats.normalMatrix - ); - mat4.multiply(model.mcpcMatrix, keyMats.wcpc, actMats.mcwc); - mat4.multiply(model.mcvcMatrix, keyMats.wcvc, actMats.mcwc); - const garray = model.renderable.getMatrixArray(); - const narray = model.renderable.getNormalArray(); - const carray = model.renderable.getColorArray(); const numPts = garray.length / 16; - let compositePass = false; - if (model._openGLRenderer.getSelector()) { - if ( - model._openGLRenderer.getSelector().getCurrentPass() === - PassTypes.COMPOSITE_INDEX_PASS - ) { - compositePass = true; - } - } - // for every primitive type for (let i = model.primTypes.Start; i < model.primTypes.End; i++) { // if there are entries @@ -518,41 +326,20 @@ function vtkOpenGLGlyph3DMapper(publicAPI, model) { i === model.primTypes.TriStripsEdges); model.lastBoundBO = model.primitives[i]; model.primitives[i].updateShaders(ren, actor, publicAPI); - const program = model.primitives[i].getProgram(); const mode = model.primitives[i].getOpenGLMode(representation); - const normalMatrixUsed = program.isUniformUsed('normalMatrix'); - const mcvcMatrixUsed = program.isUniformUsed('MCVCMatrix'); - if (model.hardwareSupport) { - if (model.extension) { - model.extension.drawArraysInstancedANGLE( - mode, - 0, - cabo.getElementCount(), - numPts - ); - } else { - gl.drawArraysInstanced(mode, 0, cabo.getElementCount(), numPts); - } + if (cabo.getIndexed()) { + cabo.getIndexBO().bind(); + gl.drawElementsInstanced( + mode, + cabo.getElementCount(), + cabo.getIndexElementType(), + 0, + numPts + ); } else { - // draw the array multiple times with different cam matrix - for (let p = 0; p < numPts; ++p) { - if (compositePass) { - model._openGLRenderer.getSelector().renderCompositeIndex(p); - } - publicAPI.updateGlyphShaderParameters( - normalMatrixUsed, - mcvcMatrixUsed, - model.primitives[i], - carray, - garray, - narray, - p, - compositePass ? model._openGLRenderer.getSelector() : null - ); - gl.drawArrays(mode, 0, cabo.getElementCount()); - } + gl.drawArraysInstanced(mode, 0, cabo.getElementCount(), numPts); } } } @@ -690,66 +477,60 @@ function vtkOpenGLGlyph3DMapper(publicAPI, model) { const { useShiftAndScale, coordShift, coordScale } = computeCoordShiftAndScale(pts); - if (model.hardwareSupport) { - // update the buffer objects if needed - const narray = model.renderable.getNormalArray(); - const carray = model.renderable.getColorArray(); - if (!model.matrixBuffer) { - model.matrixBuffer = vtkBufferObject.newInstance(); - model.matrixBuffer.setOpenGLRenderWindow(model._openGLRenderWindow); - model.normalBuffer = vtkBufferObject.newInstance(); - model.normalBuffer.setOpenGLRenderWindow(model._openGLRenderWindow); - model.colorBuffer = vtkBufferObject.newInstance(); - model.colorBuffer.setOpenGLRenderWindow(model._openGLRenderWindow); - model.pickBuffer = vtkBufferObject.newInstance(); - model.pickBuffer.setOpenGLRenderWindow(model._openGLRenderWindow); - } + // update the buffer objects if needed + const narray = model.renderable.getNormalArray(); + const carray = model.renderable.getColorArray(); + if (!model.matrixBuffer) { + model.matrixBuffer = vtkBufferObject.newInstance(); + model.matrixBuffer.setOpenGLRenderWindow(model._openGLRenderWindow); + model.normalBuffer = vtkBufferObject.newInstance(); + model.normalBuffer.setOpenGLRenderWindow(model._openGLRenderWindow); + model.colorBuffer = vtkBufferObject.newInstance(); + model.colorBuffer.setOpenGLRenderWindow(model._openGLRenderWindow); + model.pickBuffer = vtkBufferObject.newInstance(); + model.pickBuffer.setOpenGLRenderWindow(model._openGLRenderWindow); + } - if (useShiftAndScale) { - const buf = garray.buffer; - const shiftScaleMat = computeInverseShiftAndScaleMatrix( - coordShift, - coordScale - ); - mat4.invert(shiftScaleMat, shiftScaleMat); - for ( - let ptIdx = 0; - ptIdx < garray.byteLength; - ptIdx += MAT4_BYTE_SIZE - ) { - const mat = new Float32Array(buf, ptIdx, MAT4_ELEMENT_COUNT); - mat4.multiply(mat, shiftScaleMat, mat); - } + if (useShiftAndScale) { + const buf = garray.buffer; + const shiftScaleMat = computeInverseShiftAndScaleMatrix( + coordShift, + coordScale + ); + mat4.invert(shiftScaleMat, shiftScaleMat); + for (let ptIdx = 0; ptIdx < garray.byteLength; ptIdx += MAT4_BYTE_SIZE) { + const mat = new Float32Array(buf, ptIdx, MAT4_ELEMENT_COUNT); + mat4.multiply(mat, shiftScaleMat, mat); } + } - if ( - model.renderable.getBuildTime().getMTime() > - model.glyphBOBuildTime.getMTime() - ) { - model.matrixBuffer.upload(garray, ObjectType.ARRAY_BUFFER); - model.normalBuffer.upload(narray, ObjectType.ARRAY_BUFFER); - if (carray) { - model.colorBuffer.upload(carray.getData(), ObjectType.ARRAY_BUFFER); - } else { - model.colorBuffer.releaseGraphicsResources(); - } - const numPts = garray.length / 16; - const parray = new Uint8Array(4 * numPts); - for (let i = 0; i < numPts; ++i) { - let value = i + 1; - const offset = i * 4; - parray[offset] = value % 256; - value -= parray[offset]; - value /= 256; - parray[offset + 1] = value % 256; - value -= parray[offset + 1]; - value /= 256; - parray[offset + 2] = value % 256; - parray[offset + 3] = 255; - } - model.pickBuffer.upload(parray, ObjectType.ARRAY_BUFFER); - model.glyphBOBuildTime.modified(); + if ( + model.renderable.getBuildTime().getMTime() > + model.glyphBOBuildTime.getMTime() + ) { + model.matrixBuffer.upload(garray, ObjectType.ARRAY_BUFFER); + model.normalBuffer.upload(narray, ObjectType.ARRAY_BUFFER); + if (carray) { + model.colorBuffer.upload(carray.getData(), ObjectType.ARRAY_BUFFER); + } else { + model.colorBuffer.releaseGraphicsResources(); + } + const numPts = garray.length / 16; + const parray = new Uint8Array(4 * numPts); + for (let i = 0; i < numPts; ++i) { + let value = i + 1; + const offset = i * 4; + parray[offset] = value % 256; + value -= parray[offset]; + value /= 256; + parray[offset + 1] = value % 256; + value -= parray[offset + 1]; + value /= 256; + parray[offset + 2] = value % 256; + parray[offset + 3] = 255; } + model.pickBuffer.upload(parray, ObjectType.ARRAY_BUFFER); + model.glyphBOBuildTime.modified(); } superClass.buildBufferObjects(ren, actor); @@ -771,11 +552,7 @@ function vtkOpenGLGlyph3DMapper(publicAPI, model) { // Object factory // ---------------------------------------------------------------------------- -const DEFAULT_VALUES = { - normalMatrix: null, - mcpcMatrix: null, - mcwcMatrix: null, -}; +const DEFAULT_VALUES = {}; // ---------------------------------------------------------------------------- @@ -785,12 +562,6 @@ export function extend(publicAPI, model, initialValues = {}) { // Inheritance vtkOpenGLPolyDataMapper.extend(publicAPI, model, initialValues); - model.tmpMat3 = mat3.identity(new Float64Array(9)); - model.normalMatrix = mat3.identity(new Float64Array(9)); - model.mcpcMatrix = mat4.identity(new Float64Array(16)); - model.mcvcMatrix = mat4.identity(new Float64Array(16)); - model.tmpColor = []; - model.glyphBOBuildTime = {}; macro.obj(model.glyphBOBuildTime, { mtime: 0 }); diff --git a/Sources/Rendering/OpenGL/Helper/index.js b/Sources/Rendering/OpenGL/Helper/index.js index 78941e6ef80..d2c336f9452 100644 --- a/Sources/Rendering/OpenGL/Helper/index.js +++ b/Sources/Rendering/OpenGL/Helper/index.js @@ -51,21 +51,46 @@ function vtkOpenGLHelper(publicAPI, model) { const drawingLines = mode === gl.LINES; if (drawingLines && wideLines) { publicAPI.updateShaders(ren, actor, oglMapper); - gl.drawArraysInstanced( - mode, - 0, - model.CABO.getElementCount(), - 2 * Math.ceil(actor.getProperty().getLineWidth()) - ); + if (model.CABO.getIndexed()) { + model.CABO.getIndexBO().bind(); + gl.drawElementsInstanced( + mode, + model.CABO.getElementCount(), + model.CABO.getIndexElementType(), + 0, + 2 * Math.ceil(actor.getProperty().getLineWidth()) + ); + } else { + gl.drawArraysInstanced( + mode, + 0, + model.CABO.getElementCount(), + 2 * Math.ceil(actor.getProperty().getLineWidth()) + ); + } } else { gl.lineWidth(actor.getProperty().getLineWidth()); publicAPI.updateShaders(ren, actor, oglMapper); - gl.drawArrays(mode, 0, model.CABO.getElementCount()); + if (model.CABO.getIndexed()) { + model.CABO.getIndexBO().bind(); + gl.drawElements( + mode, + model.CABO.getElementCount(), + model.CABO.getIndexElementType(), + 0 + ); + } else { + gl.drawArrays(mode, 0, model.CABO.getElementCount()); + } // reset the line width gl.lineWidth(1); } - const stride = - (mode === gl.POINTS ? 1 : 0) || (mode === gl.LINES ? 2 : 3); + let stride = 3; + if (mode === gl.POINTS) { + stride = 1; + } else if (mode === gl.LINES) { + stride = 2; + } if (model.pointPicking) { gl.depthMask(depthMask); } diff --git a/Sources/Rendering/OpenGL/ImageCPRMapper/index.js b/Sources/Rendering/OpenGL/ImageCPRMapper/index.js index bc5247ef5c9..a20f83f34c4 100644 --- a/Sources/Rendering/OpenGL/ImageCPRMapper/index.js +++ b/Sources/Rendering/OpenGL/ImageCPRMapper/index.js @@ -599,6 +599,9 @@ function vtkOpenGLImageCPRMapper(publicAPI, model) { model.tris.getCABO().createVBO(cells, 'polys', Representation.SURFACE, { points, customAttributes, + // This mapper draws with gl.drawArrays, so it needs the flattened + // (non indexed) vertex layout where elementCount matches the vertices. + forceFlatten: true, }); model.VBOBuildTime.modified(); } diff --git a/Sources/Rendering/OpenGL/ImageMapper/index.js b/Sources/Rendering/OpenGL/ImageMapper/index.js index 6c11570e221..2dbbfa0dcbc 100644 --- a/Sources/Rendering/OpenGL/ImageMapper/index.js +++ b/Sources/Rendering/OpenGL/ImageMapper/index.js @@ -1410,6 +1410,9 @@ function vtkOpenGLImageMapper(publicAPI, model) { points, tcoords, cellOffset: 0, + // This mapper draws with gl.drawArrays, so it needs the flattened + // (non indexed) vertex layout where elementCount matches the vertices. + forceFlatten: true, }); model.VBOBuildTime.modified(); model.VBOBuildString = toString; diff --git a/Sources/Rendering/OpenGL/ImageResliceMapper/index.js b/Sources/Rendering/OpenGL/ImageResliceMapper/index.js index c6fc9831f1e..f375c2a67b2 100644 --- a/Sources/Rendering/OpenGL/ImageResliceMapper/index.js +++ b/Sources/Rendering/OpenGL/ImageResliceMapper/index.js @@ -636,6 +636,9 @@ function vtkOpenGLImageResliceMapper(publicAPI, model) { const options = { points, cellOffset: 0, + // This mapper draws with gl.drawArrays, so it needs the flattened + // (non indexed) vertex layout where elementCount matches the vertices. + forceFlatten: true, }; if (model.renderable.getSlabThickness() > 0.0) { const n = model.resliceGeom.getPointData().getNormals(); diff --git a/Sources/Rendering/OpenGL/OrderIndependentTranslucentPass/index.js b/Sources/Rendering/OpenGL/OrderIndependentTranslucentPass/index.js index f27f7629e75..1a39c79fb90 100644 --- a/Sources/Rendering/OpenGL/OrderIndependentTranslucentPass/index.js +++ b/Sources/Rendering/OpenGL/OrderIndependentTranslucentPass/index.js @@ -83,6 +83,9 @@ function vtkOpenGLOrderIndependentTranslucentPass(publicAPI, model) { points, tcoords, cellOffset: 0, + // This pass draws with gl.drawArrays, so it needs the flattened + // (non indexed) vertex layout where elementCount matches the vertices. + forceFlatten: true, }); model.VBOBuildTime.modified(); @@ -201,13 +204,11 @@ function vtkOpenGLOrderIndependentTranslucentPass(publicAPI, model) { const size = viewNode.getSize(); const gl = viewNode.getContext(); - // if we lack the webgl2 and half floatsupport just do - // basic alpha blending + // If required float color buffer support is missing, fall back to basic alpha blending. model._supported = false; if ( renNode.getSelector() || !gl || - !viewNode.getWebgl2() || (!gl.getExtension('EXT_color_buffer_half_float') && !gl.getExtension('EXT_color_buffer_float')) ) { @@ -242,9 +243,7 @@ function vtkOpenGLOrderIndependentTranslucentPass(publicAPI, model) { gl.colorMask(false, false, false, false); - // rerender the opaque pass to set the depth buffer - // TODO remove when webgl1 is deprecated and instead - // have the forward pass use a texture backed zbuffer + // Re-render the opaque pass to set the depth buffer. if (forwardPass.getOpaqueActorCount() > 0) { // Don't use zBufferPass as it will also render the depth of translucent actors forwardPass.setCurrentOperation('opaqueZBufferPass'); diff --git a/Sources/Rendering/OpenGL/PolyDataMapper/index.js b/Sources/Rendering/OpenGL/PolyDataMapper/index.js index aa898eb0438..a36d022d213 100755 --- a/Sources/Rendering/OpenGL/PolyDataMapper/index.js +++ b/Sources/Rendering/OpenGL/PolyDataMapper/index.js @@ -1612,6 +1612,12 @@ function vtkOpenGLPolyDataMapper(publicAPI, model) { if (model.selectionWebGLIdsToVTKIds?.points?.length) { const length = model.selectionWebGLIdsToVTKIds.points.length; selector.setMaximumPointId(length - 1); + } else if (model.currentInput && model.currentInput.getPoints()) { + // Indexed layout: gl_VertexID is the vtk point id directly, so the max + // id is simply the number of points. + selector.setMaximumPointId( + model.currentInput.getPoints().getNumberOfPoints() - 1 + ); } if (model.selectionWebGLIdsToVTKIds?.cells?.length) { @@ -1692,9 +1698,12 @@ function vtkOpenGLPolyDataMapper(publicAPI, model) { representation, publicAPI ); - model.vertexIDOffset += model.primitives[i] - .getCABO() - .getElementCount(); + // For indexed VBOs gl_VertexID is already the point id (shared across + // primitives), so the offset stays 0. The flattened layout assigns + // each primitive a contiguous range of expanded vertex ids. + if (!cabo.getIndexed()) { + model.vertexIDOffset += cabo.getElementCount(); + } } } } @@ -1763,6 +1772,25 @@ function vtkOpenGLPolyDataMapper(publicAPI, model) { publicAPI.updateMaximumPointCellIds(); }; + // The VBO layout depends on whether a cell id selection pass is active: cell + // accurate hardware selection needs the flattened per vertex layout (see + // buildBufferObjects), while everything else renders with the indexed layout. + // The selector's field association can change between renders without bumping + // any mtime, so the coarse mtime check in getNeedToRebuildBufferObjects would + // miss the transition and leave a stale (indexed) VBO in place, making cell + // picking return point ids. Detect that transition explicitly. + function computeForceFlatten() { + const selector = model._openGLRenderer + ? model._openGLRenderer.getSelector() + : null; + return ( + !!selector && + model.renderable.getPopulateSelectionSettings() && + selector.getFieldAssociation() === + FieldAssociations.FIELD_ASSOCIATION_CELLS + ); + } + publicAPI.getNeedToRebuildBufferObjects = (ren, actor) => { // first do a coarse check // Note that the actor's mtime includes it's properties mtime @@ -1771,7 +1799,8 @@ function vtkOpenGLPolyDataMapper(publicAPI, model) { vmtime < publicAPI.getMTime() || vmtime < model.renderable.getMTime() || vmtime < actor.getMTime() || - vmtime < model.currentInput.getMTime() + vmtime < model.currentInput.getMTime() || + computeForceFlatten() !== model.lastForceFlatten ) { return true; } @@ -1865,6 +1894,17 @@ function vtkOpenGLPolyDataMapper(publicAPI, model) { const customAttributesArrays = customAttributes.map((arrayName) => poly.getPointData().getArrayByName(arrayName) ); + + // Rendering uses indexed (shared point) VBOs by default. Cell accurate + // hardware selection still needs the flattened per vertex layout because + // WebGL2 has no gl_PrimitiveID to recover the cell from a shared vertex. + // Point selection works with the indexed layout (gl_VertexID is the point + // id), so we only flatten while a cell id selection pass is active. + const forceFlatten = computeForceFlatten(); + // Remember the layout we built so getNeedToRebuildBufferObjects can detect + // a field association change (which carries no mtime) and rebuild. + model.lastForceFlatten = forceFlatten; + const toString = `${poly.getMTime()}` + `A${representation}` + @@ -1875,7 +1915,8 @@ function vtkOpenGLPolyDataMapper(publicAPI, model) { `F${tcoords ? tcoords.getMTime() : 1}` + `G${customAttributesArrays .map((attributeArray) => attributeArray.getMTime()) - .join(',')}`; + .join(',')}` + + `H${forceFlatten}`; if (model.VBOBuildString !== toString) { // Build the VBOs const points = poly.getPoints(); @@ -1890,13 +1931,19 @@ function vtkOpenGLPolyDataMapper(publicAPI, model) { haveCellScalars: model.haveCellScalars, haveCellNormals: model.haveCellNormals, customAttributes: customAttributesArrays, + forceFlatten, }; - if (model.renderable.getPopulateSelectionSettings()) { + // Per vertex id maps are only meaningful for the flattened layout. When + // rendering indexed, point picking reads gl_VertexID directly, so no map + // is built and selectionWebGLIdsToVTKIds is left null. + if (forceFlatten) { model.selectionWebGLIdsToVTKIds = { points: null, cells: null, }; + } else { + model.selectionWebGLIdsToVTKIds = null; } const primitives = [ @@ -1996,6 +2043,7 @@ const DEFAULT_VALUES = { selectionStateChanged: null, selectionWebGLIdsToVTKIds: null, pointPicking: false, + lastForceFlatten: false, }; // ---------------------------------------------------------------------------- diff --git a/Sources/Rendering/OpenGL/RadialDistortionPass/index.js b/Sources/Rendering/OpenGL/RadialDistortionPass/index.js index 7d60f50cd6b..0782ee08d5c 100644 --- a/Sources/Rendering/OpenGL/RadialDistortionPass/index.js +++ b/Sources/Rendering/OpenGL/RadialDistortionPass/index.js @@ -220,6 +220,9 @@ function vtkRadialDistortionPass(publicAPI, model) { points, tcoords, cellOffset: 0, + // This pass draws with gl.drawArrays, so it needs the flattened + // (non indexed) vertex layout where elementCount matches the vertices. + forceFlatten: true, }); model.VBOBuildTime.modified(); diff --git a/Sources/Rendering/OpenGL/RenderWindow/index.d.ts b/Sources/Rendering/OpenGL/RenderWindow/index.d.ts index 09a34851448..2da8b6e0fe3 100644 --- a/Sources/Rendering/OpenGL/RenderWindow/index.d.ts +++ b/Sources/Rendering/OpenGL/RenderWindow/index.d.ts @@ -29,8 +29,6 @@ export interface IOpenGLRenderWindowInitialValues { containerSize?: Size; renderPasses?: any[]; notifyStartCaptureImage?: boolean; - webgl2?: boolean; - defaultToWebgl2?: boolean; activeFramebuffer?: any; imageFormat?: 'image/png'; useOffScreen?: boolean; @@ -249,7 +247,7 @@ export interface vtkOpenGLRenderWindow extends vtkViewNode { */ get3DContext( options: WebGLContextAttributes - ): Nullable; + ): Nullable; /** * diff --git a/Sources/Rendering/OpenGL/RenderWindow/index.js b/Sources/Rendering/OpenGL/RenderWindow/index.js index ffa5fef592e..562f1021f7d 100644 --- a/Sources/Rendering/OpenGL/RenderWindow/index.js +++ b/Sources/Rendering/OpenGL/RenderWindow/index.js @@ -33,7 +33,6 @@ const parentMethodsToProxy = [ 'getContext', 'getDefaultTextureByteSize', 'getDefaultTextureInternalFormat', - 'getDefaultToWebgl2', 'getGLInformations', 'getGraphicsMemoryInfo', 'getGraphicsResourceForObject', @@ -42,7 +41,6 @@ const parentMethodsToProxy = [ 'getShaderCache', 'getTextureUnitForTexture', 'getTextureUnitManager', - 'getWebgl2', 'makeCurrent', 'releaseGraphicsResources', 'registerGraphicsResourceUser', @@ -50,7 +48,6 @@ const parentMethodsToProxy = [ 'restoreContext', 'setActiveFramebuffer', 'setContext', - 'setDefaultToWebgl2', 'setGraphicsResourceForObject', ]; @@ -307,25 +304,15 @@ function vtkOpenGLRenderWindow(publicAPI, model) { powerPreference: 'high-performance', } ) => { - let result = null; - const webgl2Supported = typeof WebGL2RenderingContext !== 'undefined'; - model.webgl2 = false; - if (model.defaultToWebgl2 && webgl2Supported) { - result = model.canvas.getContext('webgl2', options); - if (result) { - model.webgl2 = true; - vtkDebugMacro('using webgl2'); - } + const result = webgl2Supported + ? model.canvas.getContext('webgl2', options) + : null; + if (result) { + vtkDebugMacro('using webgl2'); } if (!result) { - vtkDebugMacro('using webgl1'); - result = - model.canvas.getContext('webgl', options) || - model.canvas.getContext('experimental-webgl', options); - } - if (!result) { - vtkErrorMacro('no webgl context'); + vtkErrorMacro('no webgl2 context'); } return new Proxy(result, getCachingContextHandler()); @@ -382,25 +369,20 @@ function vtkOpenGLRenderWindow(publicAPI, model) { oglNorm16Ext = null, useHalfFloat = false ) => { - if (model.webgl2) { - switch (vtkType) { - case VtkDataTypes.CHAR: - case VtkDataTypes.SIGNED_CHAR: - case VtkDataTypes.UNSIGNED_CHAR: - return 1; - case oglNorm16Ext: - case useHalfFloat: - case VtkDataTypes.UNSIGNED_SHORT: - case VtkDataTypes.SHORT: - case VtkDataTypes.VOID: // Used for unsigned int depth - return 2; - default: // For all other cases, assume float - return 4; - } + switch (vtkType) { + case VtkDataTypes.CHAR: + case VtkDataTypes.SIGNED_CHAR: + case VtkDataTypes.UNSIGNED_CHAR: + return 1; + case oglNorm16Ext: + case useHalfFloat: + case VtkDataTypes.UNSIGNED_SHORT: + case VtkDataTypes.SHORT: + case VtkDataTypes.VOID: // Used for unsigned int depth + return 2; + default: // For all other cases, assume float + return 4; } - - // webgl1 type support is limited to 1 byte - return 1; }; publicAPI.getDefaultTextureInternalFormat = ( @@ -409,78 +391,61 @@ function vtkOpenGLRenderWindow(publicAPI, model) { oglNorm16Ext = null, useHalfFloat = false ) => { - if (model.webgl2) { - switch (vtktype) { - case VtkDataTypes.UNSIGNED_CHAR: - switch (numComps) { - case 1: - return model.context.R8; - case 2: - return model.context.RG8; - case 3: - return model.context.RGB8; - case 4: - default: - return model.context.RGBA8; - } - case oglNorm16Ext && !useHalfFloat && VtkDataTypes.UNSIGNED_SHORT: - switch (numComps) { - case 1: - return oglNorm16Ext.R16_EXT; - case 2: - return oglNorm16Ext.RG16_EXT; - case 3: - return oglNorm16Ext.RGB16_EXT; - case 4: - default: - return oglNorm16Ext.RGBA16_EXT; - } - // prioritize norm16 over float - case oglNorm16Ext && !useHalfFloat && VtkDataTypes.SHORT: - switch (numComps) { - case 1: - return oglNorm16Ext.R16_SNORM_EXT; - case 2: - return oglNorm16Ext.RG16_SNORM_EXT; - case 3: - return oglNorm16Ext.RGB16_SNORM_EXT; - case 4: - default: - return oglNorm16Ext.RGBA16_SNORM_EXT; - } - case VtkDataTypes.UNSIGNED_SHORT: - case VtkDataTypes.SHORT: - case VtkDataTypes.FLOAT: - default: - // useHalfFloat tells us if the texture can be accurately - // rendered with 16 bits or not. - switch (numComps) { - case 1: - return useHalfFloat ? model.context.R16F : model.context.R32F; - case 2: - return useHalfFloat ? model.context.RG16F : model.context.RG32F; - case 3: - return useHalfFloat ? model.context.RGB16F : model.context.RGB32F; - case 4: - default: - return useHalfFloat - ? model.context.RGBA16F - : model.context.RGBA32F; - } - } - } - - // webgl1 only supports four types - switch (numComps) { - case 1: - return model.context.LUMINANCE; - case 2: - return model.context.LUMINANCE_ALPHA; - case 3: - return model.context.RGB; - case 4: + switch (vtktype) { + case VtkDataTypes.UNSIGNED_CHAR: + switch (numComps) { + case 1: + return model.context.R8; + case 2: + return model.context.RG8; + case 3: + return model.context.RGB8; + case 4: + default: + return model.context.RGBA8; + } + case oglNorm16Ext && !useHalfFloat && VtkDataTypes.UNSIGNED_SHORT: + switch (numComps) { + case 1: + return oglNorm16Ext.R16_EXT; + case 2: + return oglNorm16Ext.RG16_EXT; + case 3: + return oglNorm16Ext.RGB16_EXT; + case 4: + default: + return oglNorm16Ext.RGBA16_EXT; + } + // prioritize norm16 over float + case oglNorm16Ext && !useHalfFloat && VtkDataTypes.SHORT: + switch (numComps) { + case 1: + return oglNorm16Ext.R16_SNORM_EXT; + case 2: + return oglNorm16Ext.RG16_SNORM_EXT; + case 3: + return oglNorm16Ext.RGB16_SNORM_EXT; + case 4: + default: + return oglNorm16Ext.RGBA16_SNORM_EXT; + } + case VtkDataTypes.UNSIGNED_SHORT: + case VtkDataTypes.SHORT: + case VtkDataTypes.FLOAT: default: - return model.context.RGBA; + // useHalfFloat tells us if the texture can be accurately + // rendered with 16 bits or not. + switch (numComps) { + case 1: + return useHalfFloat ? model.context.R16F : model.context.R32F; + case 2: + return useHalfFloat ? model.context.RG16F : model.context.RG32F; + case 3: + return useHalfFloat ? model.context.RGB16F : model.context.RGB32F; + case 4: + default: + return useHalfFloat ? model.context.RGBA16F : model.context.RGBA32F; + } } }; @@ -1060,7 +1025,7 @@ function vtkOpenGLRenderWindow(publicAPI, model) { glDebugRendererInfo && gl.getParameter(glDebugRendererInfo.UNMASKED_VENDOR_WEBGL), ], - ['WebGL Version', 'WEBGL_VERSION', model.webgl2 ? 2 : 1], + ['WebGL Version', 'WEBGL_VERSION', 2], ]; const result = {}; @@ -1323,8 +1288,6 @@ const DEFAULT_VALUES = { containerSize: null, renderPasses: [], notifyStartCaptureImage: false, - webgl2: false, - defaultToWebgl2: true, // attempt webgl2 on by default activeFramebuffer: null, imageFormat: 'image/png', useOffScreen: false, @@ -1375,7 +1338,6 @@ export function extend(publicAPI, model, initialValues = {}) { macro.get(publicAPI, model, [ 'shaderCache', 'textureUnitManager', - 'webgl2', 'useBackgroundImage', 'activeFramebuffer', 'rootOpenGLRenderWindow', @@ -1388,7 +1350,6 @@ export function extend(publicAPI, model, initialValues = {}) { 'canvas', 'renderPasses', 'notifyStartCaptureImage', - 'defaultToWebgl2', 'cursor', 'useOffScreen', ]); diff --git a/Sources/Rendering/OpenGL/ReplacementShaderMapper/index.js b/Sources/Rendering/OpenGL/ReplacementShaderMapper/index.js index d7726700a09..17fcda34e43 100644 --- a/Sources/Rendering/OpenGL/ReplacementShaderMapper/index.js +++ b/Sources/Rendering/OpenGL/ReplacementShaderMapper/index.js @@ -46,29 +46,27 @@ function implementReplaceShaderCoincidentOffset( ).result; } } - if (model._openGLRenderWindow.getWebgl2()) { - if (cp.factor !== 0.0) { - FSSource = vtkShaderProgram.substitute( - FSSource, + if (cp.factor !== 0.0) { + FSSource = vtkShaderProgram.substitute( + FSSource, + '//VTK::UniformFlow::Impl', + [ + 'float cscale = length(vec2(dFdx(gl_FragCoord.z),dFdy(gl_FragCoord.z)));', '//VTK::UniformFlow::Impl', - [ - 'float cscale = length(vec2(dFdx(gl_FragCoord.z),dFdy(gl_FragCoord.z)));', - '//VTK::UniformFlow::Impl', - ], - false - ).result; - FSSource = vtkShaderProgram.substitute( - FSSource, - '//VTK::Depth::Impl', - 'gl_FragDepth = gl_FragCoord.z + cfactor*cscale + 0.000016*coffset;' - ).result; - } else { - FSSource = vtkShaderProgram.substitute( - FSSource, - '//VTK::Depth::Impl', - 'gl_FragDepth = gl_FragCoord.z + 0.000016*coffset;' - ).result; - } + ], + false + ).result; + FSSource = vtkShaderProgram.substitute( + FSSource, + '//VTK::Depth::Impl', + 'gl_FragDepth = gl_FragCoord.z + cfactor*cscale + 0.000016*coffset;' + ).result; + } else { + FSSource = vtkShaderProgram.substitute( + FSSource, + '//VTK::Depth::Impl', + 'gl_FragDepth = gl_FragCoord.z + 0.000016*coffset;' + ).result; } shaders.Fragment = FSSource; } diff --git a/Sources/Rendering/OpenGL/ShaderCache/index.js b/Sources/Rendering/OpenGL/ShaderCache/index.js index 6513aed447b..a0724f248f9 100644 --- a/Sources/Rendering/OpenGL/ShaderCache/index.js +++ b/Sources/Rendering/OpenGL/ShaderCache/index.js @@ -35,36 +35,16 @@ function vtkShaderCache(publicAPI, model) { ).result; } - const gl2 = model._openGLRenderWindow.getWebgl2(); - - let fragDepthString = '\n'; - - let version = '#version 100\n'; - if (gl2) { - version = - '#version 300 es\n' + - '#define attribute in\n' + - '#define textureCube texture\n' + - '#define texture2D texture\n' + - '#define textureCubeLod textureLod\n' + - '#define texture2DLod textureLod\n'; - } else { - model.context.getExtension('OES_standard_derivatives'); - if (model.context.getExtension('EXT_frag_depth')) { - fragDepthString = '#extension GL_EXT_frag_depth : enable\n'; - } - if (model.context.getExtension('EXT_shader_texture_lod')) { - fragDepthString += - '#extension GL_EXT_shader_texture_lod : enable\n' + - '#define textureCubeLod textureCubeLodEXT\n' + - '#define texture2DLod texture2DLodEXT'; - } - } + const version = + '#version 300 es\n' + + '#define attribute in\n' + + '#define textureCube texture\n' + + '#define texture2D texture\n' + + '#define textureCubeLod textureLod\n' + + '#define texture2DLod textureLod\n'; nFSSource = vtkShaderProgram.substitute(nFSSource, '//VTK::System::Dec', [ `${version}\n`, - gl2 ? '' : '#extension GL_OES_standard_derivatives : enable\n', - fragDepthString, '#ifdef GL_FRAGMENT_PRECISION_HIGH', 'precision highp float;', 'precision highp int;', @@ -89,35 +69,25 @@ function vtkShaderCache(publicAPI, model) { ] ).result; - if (gl2) { - nVSSource = vtkShaderProgram.substitute( - nVSSource, - 'varying', - 'out' - ).result; - nFSSource = vtkShaderProgram.substitute( - nFSSource, - 'varying', - 'in' - ).result; + nVSSource = vtkShaderProgram.substitute(nVSSource, 'varying', 'out').result; + nFSSource = vtkShaderProgram.substitute(nFSSource, 'varying', 'in').result; - let shaderOutputs = ''; - let outputCount = 0; - while (nFSSource.includes(`gl_FragData[${outputCount}]`)) { - nFSSource = vtkShaderProgram.substitute( - nFSSource, - `gl_FragData\\[${outputCount}\\]`, - `fragOutput${outputCount}` - ).result; - shaderOutputs += `layout(location = ${outputCount}) out vec4 fragOutput${outputCount};\n`; - outputCount++; - } + let shaderOutputs = ''; + let outputCount = 0; + while (nFSSource.includes(`gl_FragData[${outputCount}]`)) { nFSSource = vtkShaderProgram.substitute( nFSSource, - '//VTK::Output::Dec', - shaderOutputs + `gl_FragData\\[${outputCount}\\]`, + `fragOutput${outputCount}` ).result; + shaderOutputs += `layout(location = ${outputCount}) out vec4 fragOutput${outputCount};\n`; + outputCount++; } + nFSSource = vtkShaderProgram.substitute( + nFSSource, + '//VTK::Output::Dec', + shaderOutputs + ).result; // nFSSource = ShaderProgram.substitute(nFSSource, 'gl_FragData\\[0\\]', // 'gl_FragColor').result; diff --git a/Sources/Rendering/OpenGL/Skybox/index.js b/Sources/Rendering/OpenGL/Skybox/index.js index 8025860fab1..e96eb53f7e9 100644 --- a/Sources/Rendering/OpenGL/Skybox/index.js +++ b/Sources/Rendering/OpenGL/Skybox/index.js @@ -121,6 +121,9 @@ function vtkOpenGLSkybox(publicAPI, model) { model.tris.getCABO().createVBO(cells, 'polys', Representation.SURFACE, { points, cellOffset: 0, + // This mapper draws with gl.drawArrays, so it needs the flattened + // (non indexed) vertex layout where elementCount matches the vertices. + forceFlatten: true, }); } diff --git a/Sources/Rendering/OpenGL/SphereMapper/index.js b/Sources/Rendering/OpenGL/SphereMapper/index.js index e0392e565ea..eb5b3d5b720 100644 --- a/Sources/Rendering/OpenGL/SphereMapper/index.js +++ b/Sources/Rendering/OpenGL/SphereMapper/index.js @@ -70,13 +70,7 @@ function vtkOpenGLSphereMapper(publicAPI, model) { replacement ).result; - let fragString = ''; - if (model.context.getExtension('EXT_frag_depth')) { - fragString = 'gl_FragDepthEXT = (pos.z / pos.w + 1.0) / 2.0;\n'; - } - if (model._openGLRenderWindow.getWebgl2()) { - fragString = 'gl_FragDepth = (pos.z / pos.w + 1.0) / 2.0;\n'; - } + const fragString = 'gl_FragDepth = (pos.z / pos.w + 1.0) / 2.0;\n'; FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Depth::Impl', [ // compute the eye position and unit direction ' vec3 EyePos;\n', diff --git a/Sources/Rendering/OpenGL/StickMapper/index.js b/Sources/Rendering/OpenGL/StickMapper/index.js index c8fa6886448..7ea8336962e 100644 --- a/Sources/Rendering/OpenGL/StickMapper/index.js +++ b/Sources/Rendering/OpenGL/StickMapper/index.js @@ -70,13 +70,7 @@ function vtkOpenGLStickMapper(publicAPI, model) { replacement ).result; - let fragString = ''; - if (model.context.getExtension('EXT_frag_depth')) { - fragString = ' gl_FragDepthEXT = (pos.z / pos.w + 1.0) / 2.0;\n'; - } - if (model._openGLRenderWindow.getWebgl2()) { - fragString = 'gl_FragDepth = (pos.z / pos.w + 1.0) / 2.0;\n'; - } + const fragString = 'gl_FragDepth = (pos.z / pos.w + 1.0) / 2.0;\n'; // see https://www.cl.cam.ac.uk/teaching/1999/AGraphHCI/SMAG/node2.html FSSource = vtkShaderProgram.substitute(FSSource, '//VTK::Depth::Impl', [ // compute the eye position and unit direction diff --git a/Sources/Rendering/OpenGL/SurfaceLIC/LineIntegralConvolution2D/pingpong.js b/Sources/Rendering/OpenGL/SurfaceLIC/LineIntegralConvolution2D/pingpong.js index 2bfef328aac..f0ba4e74340 100644 --- a/Sources/Rendering/OpenGL/SurfaceLIC/LineIntegralConvolution2D/pingpong.js +++ b/Sources/Rendering/OpenGL/SurfaceLIC/LineIntegralConvolution2D/pingpong.js @@ -47,6 +47,9 @@ function getQuadPoly(openGLRenderWindow) { points, cellOffset: 0, tcoords: tArray, + // This quad is drawn with gl.drawArrays, so it needs the flattened + // (non indexed) vertex layout where elementCount matches the vertices. + forceFlatten: true, }); return quad; } diff --git a/Sources/Rendering/OpenGL/SurfaceLIC/SurfaceLICInterface/index.js b/Sources/Rendering/OpenGL/SurfaceLIC/SurfaceLICInterface/index.js index fa78ed12957..cbdbb3f7135 100644 --- a/Sources/Rendering/OpenGL/SurfaceLIC/SurfaceLICInterface/index.js +++ b/Sources/Rendering/OpenGL/SurfaceLIC/SurfaceLICInterface/index.js @@ -66,6 +66,9 @@ function getQuadPoly(openGLRenderWindow) { points, cellOffset: 0, tcoords: tArray, + // This quad is drawn with gl.drawArrays, so it needs the flattened + // (non indexed) vertex layout where elementCount matches the vertices. + forceFlatten: true, }); return quad; } diff --git a/Sources/Rendering/OpenGL/SurfaceLIC/SurfaceLICMapper/index.js b/Sources/Rendering/OpenGL/SurfaceLIC/SurfaceLICMapper/index.js index 0372781eef7..c9e7b588a76 100644 --- a/Sources/Rendering/OpenGL/SurfaceLIC/SurfaceLICMapper/index.js +++ b/Sources/Rendering/OpenGL/SurfaceLIC/SurfaceLICMapper/index.js @@ -143,13 +143,6 @@ function vtkOpenGLSurfaceLICMapper(publicAPI, model) { publicAPI.renderPiece = (ren, actor) => { let canDrawLIC = true; - // Check for gl compatibility - const gl2 = model._openGLRenderWindow.getWebgl2(); - if (!gl2) { - vtkErrorMacro('SurfaceLICMapper Requires WebGL 2'); - canDrawLIC = false; - } - // Check for required extensions if ( !model.context.getExtension('EXT_color_buffer_float') || diff --git a/Sources/Rendering/OpenGL/Texture/index.d.ts b/Sources/Rendering/OpenGL/Texture/index.d.ts index 3157e6418b4..d590edc152b 100644 --- a/Sources/Rendering/OpenGL/Texture/index.d.ts +++ b/Sources/Rendering/OpenGL/Texture/index.d.ts @@ -307,7 +307,7 @@ export interface vtkOpenGLTexture extends vtkViewNode { /** * Creates a 3D texture from raw data. * - * updatedExtents is currently incompatible with webgl1, since there's no extent scaling. + * `updatedExtents` support assumes WebGL2 3D texture uploads. * * @param width The width of the texture. * @param height The height of the texture. @@ -339,7 +339,7 @@ export interface vtkOpenGLTexture extends vtkViewNode { /** * Creates a 3D filterable texture from raw data, with a preference for size over accuracy if necessary. * - * updatedExtents is currently incompatible with webgl1, since there's no extent scaling. + * `updatedExtents` support assumes WebGL2 3D texture uploads. * * @param width The width of the texture. * @param height The height of the texture. @@ -379,7 +379,7 @@ export interface vtkOpenGLTexture extends vtkViewNode { /** * Creates a 3D filterable texture from a data array, with a preference for size over accuracy if necessary. * - * updatedExtents is currently incompatible with webgl1, since there's no extent scaling. + * `updatedExtents` support assumes WebGL2 3D texture uploads. * * @param width The width of the texture. * @param height The height of the texture. diff --git a/Sources/Rendering/OpenGL/Texture/index.js b/Sources/Rendering/OpenGL/Texture/index.js index 9a4f22aa7ce..4210941f415 100644 --- a/Sources/Rendering/OpenGL/Texture/index.js +++ b/Sources/Rendering/OpenGL/Texture/index.js @@ -253,13 +253,11 @@ function vtkOpenGLTexture(publicAPI, model) { 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_WRAP_R, + publicAPI.getOpenGLWrapMode(model.wrapR) + ); model.context.bindTexture(model.target, null); } @@ -355,13 +353,11 @@ function vtkOpenGLTexture(publicAPI, model) { 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_WRAP_R, + publicAPI.getOpenGLWrapMode(model.wrapR) + ); model.context.texParameteri( model.target, @@ -375,19 +371,16 @@ function vtkOpenGLTexture(publicAPI, model) { 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.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); @@ -471,33 +464,17 @@ function vtkOpenGLTexture(publicAPI, model) { //---------------------------------------------------------------------------- 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; - } + 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; } }; @@ -514,43 +491,21 @@ function vtkOpenGLTexture(publicAPI, model) { publicAPI.getDefaultDataType = (vtkScalarType) => { const useHalfFloat = publicAPI.useHalfFloat(); // 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; - // prefer norm16 since that is accurate compared to - // half float which is not - case getNorm16Ext() && !useHalfFloat && VtkDataTypes.SHORT: - return model.context.SHORT; - case getNorm16Ext() && !useHalfFloat && VtkDataTypes.UNSIGNED_SHORT: - return model.context.UNSIGNED_SHORT; - // use half float type - case useHalfFloat && VtkDataTypes.SHORT: - return model.context.HALF_FLOAT; - case useHalfFloat && VtkDataTypes.UNSIGNED_SHORT: - return model.context.HALF_FLOAT; - // 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; + // prefer norm16 since that is accurate compared to + // half float which is not + case getNorm16Ext() && !useHalfFloat && VtkDataTypes.SHORT: + return model.context.SHORT; + case getNorm16Ext() && !useHalfFloat && VtkDataTypes.UNSIGNED_SHORT: + return model.context.UNSIGNED_SHORT; + // use half float type + case useHalfFloat && VtkDataTypes.SHORT: + case useHalfFloat && VtkDataTypes.UNSIGNED_SHORT: + return model.context.HALF_FLOAT; // case VtkDataTypes.INT: // return model.context.INT; // case VtkDataTypes.UNSIGNED_INT: @@ -558,24 +513,7 @@ function vtkOpenGLTexture(publicAPI, model) { 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; - } - { - const halfFloat = model.context.getExtension( - 'OES_texture_half_float' - ); - if ( - halfFloat && - model.context.getExtension('OES_texture_half_float_linear') - ) { - return halfFloat.HALF_FLOAT_OES; - } - } - return model.context.UNSIGNED_BYTE; + return model.context.FLOAT; } }; @@ -821,14 +759,7 @@ function vtkOpenGLTexture(publicAPI, model) { // if the opengl data type is half float // then the data array must be u16 - let halfFloat = false; - if (model._openGLRenderWindow.getWebgl2()) { - halfFloat = model.openGLDataType === model.context.HALF_FLOAT; - } else { - const halfFloatExt = model.context.getExtension('OES_texture_half_float'); - halfFloat = - halfFloatExt && model.openGLDataType === halfFloatExt.HALF_FLOAT_OES; - } + const halfFloat = model.openGLDataType === model.context.HALF_FLOAT; if (halfFloat) { for (let idx = 0; idx < data.length; idx++) { @@ -866,10 +797,9 @@ function vtkOpenGLTexture(publicAPI, model) { //---------------------------------------------------------------------------- function scaleTextureToHighestPowerOfTwo(data) { - if (model._openGLRenderWindow.getWebgl2()) { - // No need if webGL2 - return data; - } + // No scaling needed in WebGL2. + return data; + /* const pixData = []; const width = model.width; const height = model.height; @@ -964,6 +894,7 @@ function vtkOpenGLTexture(publicAPI, model) { } return pixData; + */ } //---------------------------------------------------------------------------- @@ -973,23 +904,19 @@ function vtkOpenGLTexture(publicAPI, model) { // Cannot use texStorage if the texture is supposed to be resizable. return false; } - if (model._openGLRenderWindow.getWebgl2()) { - const webGLInfo = model._openGLRenderWindow.getGLInformations(); - if ( - webGLInfo.RENDERER.value.match(/WebKit/gi) && - navigator.platform.match(/Mac/gi) && - getNorm16Ext() && - (dataType === VtkDataTypes.UNSIGNED_SHORT || - dataType === VtkDataTypes.SHORT) - ) { - // Cannot use texStorage with EXT_texture_norm16 textures on Mac M1 GPU. - // No errors reported but the texture is unusable. - return false; - } - // Use texStorage for WebGL2 - return true; + const webGLInfo = model._openGLRenderWindow.getGLInformations(); + if ( + webGLInfo.RENDERER.value.match(/WebKit/gi) && + navigator.platform.match(/Mac/gi) && + getNorm16Ext() && + (dataType === VtkDataTypes.UNSIGNED_SHORT || + dataType === VtkDataTypes.SHORT) + ) { + // Cannot use texStorage with EXT_texture_norm16 textures on Mac M1 GPU. + // No errors reported but the texture is unusable. + return false; } - return false; + return true; } return false; } @@ -1234,14 +1161,10 @@ function vtkOpenGLTexture(publicAPI, model) { // 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; - } + if (dataType === VtkDataTypes.FLOAT) { + model.internalFormat = model.context.DEPTH_COMPONENT32F; } else { - model.internalFormat = model.context.DEPTH_COMPONENT; + model.internalFormat = model.context.DEPTH_COMPONENT16; } if (!model.internalFormat || !model.format || !model.openGLDataType) { @@ -1336,10 +1259,7 @@ function vtkOpenGLTexture(publicAPI, model) { publicAPI.createTexture(); publicAPI.bind(); - const needNearestPowerOfTwo = - !model._openGLRenderWindow.getWebgl2() && - (!vtkMath.isPowerOfTwo(image.width) || - !vtkMath.isPowerOfTwo(image.height)); + const needNearestPowerOfTwo = false; let textureSource = image; let targetWidth = image.width; @@ -1347,9 +1267,7 @@ function vtkOpenGLTexture(publicAPI, model) { let flipY = true; - // For WebGL1, we need to scale the image to the nearest power of two - // dimensions if the image is not already a power of two. For WebGL2, we can - // use the image as is. Note: Chrome has a perf issue where the path + // Note: Chrome has a perf issue where the path // HTMLImageElement -> Canvas -> texSubImage2D is faster than // HTMLImageElement -> texSubImage2D directly. See // https://issues.chromium.org/issues/41311312#comment7 @@ -1553,21 +1471,13 @@ function vtkOpenGLTexture(publicAPI, model) { const isExactHalfFloat = hasExactHalfFloat(offset, scale) || preferSizeOverAccuracy; - let useHalfFloat = false; - if (model._openGLRenderWindow.getWebgl2()) { - // If OES_texture_float_linear is not available, and using a half float would still be exact, force half floats - // This is because half floats are always texture filterable in webgl2, while full *32F floats are not (unless the extension is present) - const forceHalfFloat = - model.openGLDataType === model.context.FLOAT && - model.context.getExtension('OES_texture_float_linear') === null && - isExactHalfFloat; - useHalfFloat = - forceHalfFloat || model.openGLDataType === model.context.HALF_FLOAT; - } else { - const halfFloatExt = model.context.getExtension('OES_texture_half_float'); - useHalfFloat = - halfFloatExt && model.openGLDataType === halfFloatExt.HALF_FLOAT_OES; - } + // If OES_texture_float_linear is not available, and using a half float would still be exact, force half floats. + const forceHalfFloat = + model.openGLDataType === model.context.FLOAT && + model.context.getExtension('OES_texture_float_linear') === null && + isExactHalfFloat; + const useHalfFloat = + forceHalfFloat || model.openGLDataType === model.context.HALF_FLOAT; model.canUseHalfFloat = useHalfFloat && isExactHalfFloat; } @@ -1947,200 +1857,15 @@ function vtkOpenGLTexture(publicAPI, model) { // Original is read only, copy is read/write // Use the copy as volumeInfo.scale and volumeInfo.offset - // WebGL2 path, we have 3d textures etc - if (model._openGLRenderWindow.getWebgl2()) { - return publicAPI.create3DFromRaw({ - width, - height, - depth, - numComps, - dataType, - data, - updatedExtents, - }); - } - - const numPixelsIn = width * height * depth; - const scaleOffsetsCopy = structuredClone(scaleOffsets); - - // not webgl2, deal with webgl1, no 3d textures - // and maybe no float textures - - 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) { - scaleOffsetsCopy.offset[c] = 0.0; - scaleOffsetsCopy.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 = scaleOffsetsCopy.offset; - model.volumeInfo.scale = scaleOffsetsCopy.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], - scaleOffsetsCopy.offset[nc], - scaleOffsetsCopy.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); - - if (useTexStorage(dataTypeToUse)) { - model.context.texStorage2D( - model.target, - 1, - model.internalFormat, - model.width, - model.height - ); - if (newArray != null) { - model.context.texSubImage2D( - model.target, - 0, - 0, - 0, - model.width, - model.height, - model.format, - model.openGLDataType, - newArray - ); - } - } else { - model.context.texImage2D( - model.target, - 0, - model.internalFormat, - model.width, - model.height, - 0, - model.format, - model.openGLDataType, - newArray - ); - } - - publicAPI.deactivate(); - return true; + return publicAPI.create3DFromRaw({ + width, + height, + depth, + numComps, + dataType, + data, + updatedExtents, + }); }; publicAPI.setOpenGLRenderWindow = (rw) => { diff --git a/Sources/Rendering/OpenGL/VertexArrayObject/api.md b/Sources/Rendering/OpenGL/VertexArrayObject/api.md index 40f979f568a..372a46de1bf 100644 --- a/Sources/Rendering/OpenGL/VertexArrayObject/api.md +++ b/Sources/Rendering/OpenGL/VertexArrayObject/api.md @@ -1,13 +1,11 @@ -VertexArrayObject - encapsulate VAOs or emulate them +VertexArrayObject - encapsulate WebGL2 vertex array objects -The VertexArrayObject class uses, or emulates, vertex array objects. -These are extremely useful for setup/tear down of vertex attributes, and can -offer significant performance benefits when the hardware supports them. -It should be noted that this object is very lightweight, and it assumes the -objects being used are correctly set up. Even without support for VAOs this -class caches the array locations, types, etc and avoids repeated look ups. It -it bound to a single ShaderProgram object. +The VertexArrayObject class wraps native WebGL2 vertex array objects. +These are extremely useful for setup/tear down of vertex attributes, and +offer significant performance benefits. It should be noted that this object +is very lightweight, and it assumes the objects being used are correctly set +up. It is bound to a single ShaderProgram object. ### bind(); @@ -48,10 +46,4 @@ every primitive. ### removeAttributeArray(name); -Remove ab attribute array from the VAO - -### setForceEmulation(val); - -Force this VAO to emulate a vertex array object even if -the system supports VAOs. This can be useful in cases where -the vertex array object does not handle all extensions. +Remove an attribute array from the VAO diff --git a/Sources/Rendering/OpenGL/VertexArrayObject/index.js b/Sources/Rendering/OpenGL/VertexArrayObject/index.js index 9795b237497..107fa8a05ad 100644 --- a/Sources/Rendering/OpenGL/VertexArrayObject/index.js +++ b/Sources/Rendering/OpenGL/VertexArrayObject/index.js @@ -9,139 +9,29 @@ function vtkOpenGLVertexArrayObject(publicAPI, model) { // Set our className model.classHierarchy.push('vtkOpenGLVertexArrayObject'); - // Public API methods - publicAPI.exposedMethod = () => { - // This is a publicly exposed method of this object - }; - publicAPI.initialize = () => { - model.instancingExtension = null; - if (!model._openGLRenderWindow.getWebgl2()) { - model.instancingExtension = model.context.getExtension( - 'ANGLE_instanced_arrays' - ); - } - if ( - !model.forceEmulation && - model._openGLRenderWindow && - model._openGLRenderWindow.getWebgl2() - ) { - model.extension = null; - model.supported = true; - model.handleVAO = model.context.createVertexArray(); - } else { - model.extension = model.context.getExtension('OES_vertex_array_object'); - // Start setting up VAO - if (!model.forceEmulation && model.extension) { - model.supported = true; - model.handleVAO = model.extension.createVertexArrayOES(); - } else { - model.supported = false; - } - } + model.handleVAO = model.context.createVertexArray(); }; - publicAPI.isReady = () => - // We either probed and allocated a VAO, or are falling back as the current - // hardware does not support VAOs. - model.handleVAO !== 0 || model.supported === false; + publicAPI.isReady = () => model.handleVAO !== 0; publicAPI.bind = () => { - // Either simply bind the VAO, or emulate behavior by binding all attributes. if (!publicAPI.isReady()) { publicAPI.initialize(); } - if (publicAPI.isReady() && model.supported) { - if (model.extension) { - model.extension.bindVertexArrayOES(model.handleVAO); - } else { - model.context.bindVertexArray(model.handleVAO); - } - } else if (publicAPI.isReady()) { - const gl = model.context; - for (let ibuff = 0; ibuff < model.buffers.length; ++ibuff) { - const buff = model.buffers[ibuff]; - model.context.bindBuffer(gl.ARRAY_BUFFER, buff.buffer); - for (let iatt = 0; iatt < buff.attributes.length; ++iatt) { - const attrIt = buff.attributes[iatt]; - const matrixCount = attrIt.isMatrix ? attrIt.size : 1; - for (let i = 0; i < matrixCount; ++i) { - gl.enableVertexAttribArray(attrIt.index + i); - gl.vertexAttribPointer( - attrIt.index + i, - attrIt.size, - attrIt.type, - attrIt.normalize, - attrIt.stride, - attrIt.offset + (attrIt.stride * i) / attrIt.size - ); - if (attrIt.divisor > 0) { - if (model.instancingExtension) { - model.instancingExtension.vertexAttribDivisorANGLE( - attrIt.index + i, - 1 - ); - } else { - gl.vertexAttribDivisor(attrIt.index + i, 1); - } - } - } - } - } - } + model.context.bindVertexArray(model.handleVAO); }; publicAPI.release = () => { - // Either simply release the VAO, or emulate behavior by releasing all attributes. - if (publicAPI.isReady() && model.supported) { - if (model.extension) { - model.extension.bindVertexArrayOES(null); - } else { - model.context.bindVertexArray(null); - } - } else if (publicAPI.isReady()) { - const gl = model.context; - for (let ibuff = 0; ibuff < model.buffers.length; ++ibuff) { - const buff = model.buffers[ibuff]; - model.context.bindBuffer(gl.ARRAY_BUFFER, buff.buffer); - for (let iatt = 0; iatt < buff.attributes.length; ++iatt) { - const attrIt = buff.attributes[iatt]; - const matrixCount = attrIt.isMatrix ? attrIt.size : 1; - for (let i = 0; i < matrixCount; ++i) { - gl.enableVertexAttribArray(attrIt.index + i); - gl.vertexAttribPointer( - attrIt.index + i, - attrIt.size, - attrIt.type, - attrIt.normalize, - attrIt.stride, - attrIt.offset + (attrIt.stride * i) / attrIt.size - ); - if (attrIt.divisor > 0) { - if (model.instancingExtension) { - model.instancingExtension.vertexAttribDivisorANGLE( - attrIt.index + i, - 0 - ); - } else { - gl.vertexAttribDivisor(attrIt.index + i, 0); - } - } - gl.disableVertexAttribArray(attrIt.index + i); - } - } - } + if (model.context) { + model.context.bindVertexArray(null); } }; publicAPI.shaderProgramChanged = () => { publicAPI.release(); - if (model.handleVAO) { - if (model.extension) { - model.extension.deleteVertexArrayOES(model.handleVAO); - } else { - model.context.deleteVertexArray(model.handleVAO); - } + if (model.handleVAO && model.context) { + model.context.deleteVertexArray(model.handleVAO); } model.handleVAO = 0; model.handleProgram = 0; @@ -149,15 +39,10 @@ function vtkOpenGLVertexArrayObject(publicAPI, model) { publicAPI.releaseGraphicsResources = () => { publicAPI.shaderProgramChanged(); - if (model.handleVAO) { - if (model.extension) { - model.extension.deleteVertexArrayOES(model.handleVAO); - } else { - model.context.deleteVertexArray(model.handleVAO); - } + if (model.handleVAO && model.context) { + model.context.deleteVertexArray(model.handleVAO); } model.handleVAO = 0; - model.supported = true; model.handleProgram = 0; }; @@ -222,69 +107,26 @@ function vtkOpenGLVertexArrayObject(publicAPI, model) { const gl = model.context; - const attribs = {}; - attribs.name = name; - attribs.index = gl.getAttribLocation(model.handleProgram, name); - attribs.offset = offset; - attribs.stride = stride; - attribs.type = elementType; - attribs.size = elementTupleSize; - attribs.normalize = normalize; - attribs.isMatrix = isMatrix; - attribs.divisor = divisor; - - if (attribs.Index === -1) { + const index = gl.getAttribLocation(model.handleProgram, name); + if (index === -1) { return false; } - // Always make the call as even the first use wants the attrib pointer setting - // up when we are emulating. buffer.bind(); - gl.enableVertexAttribArray(attribs.index); + gl.enableVertexAttribArray(index); gl.vertexAttribPointer( - attribs.index, - attribs.size, - attribs.type, - attribs.normalize, - attribs.stride, - attribs.offset + index, + elementTupleSize, + elementType, + normalize, + stride, + offset ); if (divisor > 0) { - if (model.instancingExtension) { - model.instancingExtension.vertexAttribDivisorANGLE(attribs.index, 1); - } else { - gl.vertexAttribDivisor(attribs.index, 1); - } + gl.vertexAttribDivisor(index, 1); } - attribs.buffer = buffer.getHandle(); - - // If vertex array objects are not supported then build up our list. - if (!model.supported) { - // find the buffer - let buffFound = false; - for (let ibuff = 0; ibuff < model.buffers.length; ++ibuff) { - const buff = model.buffers[ibuff]; - if (buff.buffer === attribs.buffer) { - buffFound = true; - let found = false; - for (let iatt = 0; iatt < buff.attributes.length; ++iatt) { - const attrIt = buff.attributes[iatt]; - if (attrIt.name === name) { - found = true; - buff.attributes[iatt] = attribs; - } - } - if (!found) { - buff.attributes.push(attribs); - } - } - } - if (!buffFound) { - model.buffers.push({ buffer: attribs.buffer, attributes: [attribs] }); - } - } return true; }; @@ -332,11 +174,7 @@ function vtkOpenGLVertexArrayObject(publicAPI, model) { offset + (stride * i) / elementTupleSize ); if (divisor > 0) { - if (model.instancingExtension) { - model.instancingExtension.vertexAttribDivisorANGLE(index + i, 1); - } else { - gl.vertexAttribDivisor(index + i, 1); - } + gl.vertexAttribDivisor(index + i, 1); } } @@ -347,24 +185,8 @@ function vtkOpenGLVertexArrayObject(publicAPI, model) { if (!publicAPI.isReady() || model.handleProgram === 0) { return false; } - - // If we don't have real VAOs find the entry and remove it too. - if (!model.supported) { - for (let ibuff = 0; ibuff < model.buffers.length; ++ibuff) { - const buff = model.buffers[ibuff]; - for (let iatt = 0; iatt < buff.attributes.length; ++iatt) { - const attrIt = buff.attributes[iatt]; - if (attrIt.name === name) { - buff.attributes.splice(iatt, 1); - if (!buff.attributes.length) { - model.buffers.splice(ibuff, 1); - } - return true; - } - } - } - } - + // With core vertex array objects, the attribute state lives inside the VAO + // and is overwritten on the next bind, so there is nothing to do here. return true; }; @@ -386,11 +208,8 @@ function vtkOpenGLVertexArrayObject(publicAPI, model) { // ---------------------------------------------------------------------------- const DEFAULT_VALUES = { - forceEmulation: false, handleVAO: 0, handleProgram: 0, - supported: true, - buffers: null, context: null, // _openGLRenderWindow: null, }; @@ -400,18 +219,9 @@ const DEFAULT_VALUES = { export function extend(publicAPI, model, initialValues = {}) { Object.assign(model, DEFAULT_VALUES, initialValues); - // Internal objects initialization - model.buffers = []; - // Object methods macro.obj(publicAPI, model); - // Create get-only macros - macro.get(publicAPI, model, ['supported']); - - // Create get-set macros - macro.setGet(publicAPI, model, ['forceEmulation']); - // For more macro methods, see "Sources/macros.js" // Object specific methods diff --git a/Sources/Rendering/OpenGL/VolumeMapper/index.js b/Sources/Rendering/OpenGL/VolumeMapper/index.js index f40cf31b51c..03060bf1580 100644 --- a/Sources/Rendering/OpenGL/VolumeMapper/index.js +++ b/Sources/Rendering/OpenGL/VolumeMapper/index.js @@ -1603,9 +1603,8 @@ function vtkOpenGLVolumeMapper(publicAPI, model) { // visible artifacts. High values of opacity quickly terminate without // artifacts. if ( - model._openGLRenderWindow.getWebgl2() || - (model.context.getExtension('OES_texture_float') && - model.context.getExtension('OES_texture_float_linear')) + model.context.getExtension('OES_texture_float') && + model.context.getExtension('OES_texture_float_linear') ) { newOpacityTexture.create2DFromRaw({ width: oWidth, @@ -1871,6 +1870,9 @@ function vtkOpenGLVolumeMapper(publicAPI, model) { model.tris.getCABO().createVBO(cells, 'polys', Representation.SURFACE, { points, cellOffset: 0, + // This mapper draws with gl.drawArrays, so it needs the flattened + // (non indexed) vertex layout where elementCount matches the vertices. + forceFlatten: true, }); } diff --git a/Sources/Rendering/OpenGL/glsl/vtkVolumeFS.glsl b/Sources/Rendering/OpenGL/glsl/vtkVolumeFS.glsl index 26383ac8f07..a18a5ecd90f 100644 --- a/Sources/Rendering/OpenGL/glsl/vtkVolumeFS.glsl +++ b/Sources/Rendering/OpenGL/glsl/vtkVolumeFS.glsl @@ -179,10 +179,6 @@ bool isLabelOutlineBitSet(int segmentIndex) { return ((labelOutlineBitmasks[arrayIndex] & (1u << bitIndex)) != 0u); } -// if you want to see the raw tiled -// data in webgl1 uncomment the following line -// #define debugtile - // camera values uniform float camThick; uniform float camNear;