Skip to content

Commit

Permalink
Image block layer: optimize slice flattening
Browse files Browse the repository at this point in the history
Make two optimization to slice flattening:

- If the text column stride is smaller than the row stride, load the
  texture into GPU memory that way and transpose it during rendering.
- Only fall back to numjs.flatten when elements are discontiguous in
  memory. Optimize the fully-contiguous and row-contiguous cases to use
  direct TypedArray views and copying.

These two changes reduce slice flattening in 3-view ortho from 60% to
40% of profile. This also eliminates any copying in the fully-contiguous
case, i.e., for XY views into naturally ordered N5 stacks.

Closes catmaid#1959.
  • Loading branch information
aschampion committed Jan 7, 2020
1 parent 0e882f2 commit 4ee358f
Showing 1 changed file with 68 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -200,9 +200,6 @@

let slice = this._sliceBlock(block, blockZ);

// The array is still column major, so transpose to row-major for tex.
slice = slice.transpose(1, 0);

this._sliceToTexture(slice, this._tilesBuffer[i][j].texture);
this._tilesBuffer[i][j].coord = coord;
this._tilesBuffer[i][j].loaded = true;
Expand Down Expand Up @@ -305,6 +302,13 @@
let baseTex = pixiTex.baseTexture;
let texture = baseTex._glTextures[renderer.CONTEXT_UID];
let newTex = false;

// Since we assume array coordinates are [x][y], we want f-order since
// OpenGL wants x most rapidly changing.
let transpose = slice.selection.stride[0] > slice.selection.stride[1];
if (!transpose) {
slice = slice.transpose(1, 0);
}
let width = slice.shape[1];
let height = slice.shape[0];

Expand All @@ -329,6 +333,12 @@
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

let typedArr = _flattenNdarraySliceToView(slice);
let arrayBuff = new jsArrayType(typedArr,
typedArr.byteOffset, typedArr.byteLength/jsArrayType.BYTES_PER_ELEMENT);
pixiTex._transpose = transpose;

if (newTex) {
gl.texImage2D(
gl.TEXTURE_2D,
Expand All @@ -339,7 +349,7 @@
0, // Border
texture.format,
texture.type,
new jsArrayType(slice.flatten().selection.data.buffer));
arrayBuff);
} else {
gl.texSubImage2D(
gl.TEXTURE_2D,
Expand All @@ -349,7 +359,7 @@
texture.height,
texture.format,
texture.type,
new jsArrayType(slice.flatten().selection.data.buffer));
arrayBuff);
}
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, glScaleMode);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, glScaleMode);
Expand Down Expand Up @@ -378,6 +388,15 @@
if (tile.texture.baseTexture.scaleMode !== this._pixiInterpolationMode) {
this._setTextureInterpolationMode(tile.texture, this._pixiInterpolationMode);
}
if (tile.texture._transpose && !tile._transpose) {
tile.scale.x = -1.0;
tile.rotation = -Math.PI / 2.0;
tile._transpose = true;
} else if (!tile.texture._transpose && tile._transpose) {
tile.scale.x = 1.0;
tile.rotation = 0.0;
tile._transpose = false;
}
tile.visible = true;
} else if (force) {
tile.visible = false;
Expand Down Expand Up @@ -408,4 +427,48 @@

CATMAID.PixiImageBlockLayer = PixiImageBlockLayer;

/** Convert a 2-d c-order ndarray into a flattened TypedArray. */
function _flattenNdarraySliceToView(slice) {
let sourceArray = slice.selection.data;
if (slice.selection.stride[1] === 1) {
if (slice.selection.stride[0] === slice.shape[1]) {
// In this case the data is already contiguous in memory in the correct order.
let sourceOffset = slice.selection.offset;
return sourceArray.subarray(sourceOffset, sourceOffset + slice.shape[0] * slice.shape[1]);
}

// In this case the rows are contiguous in memory, but the colums are
// strided non-contiguously.
let typedArr = new sourceArray.constructor(slice.shape[0] * slice.shape[1]);
let targetOffset = 0;
let sourceOffset = slice.selection.offset;
for (let i = 0; i < slice.shape[0]; ++i) {
typedArr.set(sourceArray.subarray(sourceOffset, sourceOffset + slice.shape[1]), targetOffset);
targetOffset += slice.shape[1];
sourceOffset += slice.selection.stride[0];
}

return typedArr;
} else {

// In this case no elements are contiguous so much be copied individually.
return slice.flatten().selection.data;
// Manual implementation that is somehow slower than ndarray's nested
// Array push and joining:
// let typedArr = new sourceArray.constructor(slice.shape[0] * slice.shape[1]);
// let targetOffset = 0;
// let sourceIOffset = slice.selection.offset;
// for (let i = 0; i < slice.shape[0]; ++i) {
// let sourceJOffset = sourceIOffset;
// for (let j = 0; j < slice.shape[1]; ++j) {
// typedArr[targetOffset++] = sourceArray[sourceJOffset];
// sourceJOffset += slice.selection.stride[1];
// }
// sourceIOffset += slice.selection.stride[0];
// }

// return typedArr;
}
}

})(CATMAID);

0 comments on commit 4ee358f

Please sign in to comment.