Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use less ram when initializing data textures #6711

Merged
merged 18 commits into from
Jan 4, 2023
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .circleci/not-on-master.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
set -Eeuo pipefail

if [ "${CIRCLE_BRANCH}" == "master" ]; then
# if [ "${CIRCLE_BRANCH}" == "master" ]; then
philippotto marked this conversation as resolved.
Show resolved Hide resolved
echo "Skipping this step on master..."
else
exec "$@"
fi
# else
# exec "$@"
# fi
1 change: 1 addition & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Bulk task creation now needs the taskTypeId, the task type summary will no longer be accepted. [#6640](https://github.com/scalableminds/webknossos/pull/6640)
- Error handling and reporting is more robust now. [#6700](https://github.com/scalableminds/webknossos/pull/6700)
- The Quick-Select settings are opened (and closed) automatically when labeling with the preview mode. That way, bulk labelings with preview mode don't require constantly opening the settings manually. [#6706](https://github.com/scalableminds/webknossos/pull/6706)
- Improved performance of opening a dataset or annotation. [#6711](https://github.com/scalableminds/webknossos/pull/6711)

### Fixed
- Fixed import of N5 datasets. [#6668](https://github.com/scalableminds/webknossos/pull/6668)
Expand Down
99 changes: 27 additions & 72 deletions frontend/javascripts/libs/UpdatableTexture.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,26 @@
import * as THREE from "three";
import { document } from "libs/window";
import _ from "lodash";
import { TypedArray } from "oxalis/constants";

const lazyGetCanvas = _.memoize(() => {
const canvas = document.createElement("canvas");
canvas.width = 1;
canvas.height = 1;
return canvas;
});

const getImageData = _.memoize(
(
width: number,
height: number,
isInt: boolean,
): { width: number; height: number; data: TypedArray } => {
const canvas = lazyGetCanvas();
const ctx = canvas.getContext("2d");
if (ctx == null) {
throw new Error("Could not get context for texture.");
}

// Integer textures cannot be used with four channels, which is why
// we are creating an image-like object here which can be used
// with ThreeJS if isDataTexture = true is given.
if (isInt) {
return { width, height, data: new Uint32Array(4 * width * height) };
}

const imageData = ctx.createImageData(width, height);

// Explicitly "release" canvas. Necessary for iOS.
// See https://pqina.nl/blog/total-canvas-memory-use-exceeds-the-maximum-limit/
canvas.width = 1;
canvas.height = 1;
ctx.clearRect(0, 0, 1, 1);

return imageData;
},
(width: number, height: number, isInt: boolean) => `${width}_${height}_${isInt}`,
);
/* The UpdatableTexture class exposes a way to partially update a texture.
* Since we use this class for data which is usually only available in chunks,
* the default ThreeJS way of initializing the texture with an appropriately
* sized array buffer is inefficient (both allocation of array and upload to GPU).
* Therefore, we only allocate a dummy typed array of size 1 which mismatches
* the actual texture size. To avoid (benign) WebGL errors in the console,
* the WebGL function texSubImage2D is overridden to do nothing so that ThreeJS
* effectively doesn't try to upload the dummy data.
* This is a hacky workaround and can hopefully be removed, when/if this issue
* is done in ThreeJS: https://github.com/mrdoob/three.js/issues/25133
* In WebGL 1, we used to simply resize the texture after initialization, but
* this is not possible anymore, since ThreeJS uses texStorage2D now (which is
* also recommended).
*/
let originalTexSubImage2D: WebGL2RenderingContext["texSubImage2D"] | null = null;

class UpdatableTexture extends THREE.Texture {
isUpdatableTexture: boolean = true;
// Needs to be set to true for integer textures:
isDataTexture: boolean = false;
renderer!: THREE.WebGLRenderer;
gl: any;
gl!: WebGL2RenderingContext;
utils!: THREE.WebGLUtils;
width: number | undefined;
height: number | undefined;
Expand All @@ -65,7 +38,7 @@ class UpdatableTexture extends THREE.Texture {
anisotropy?: number,
encoding?: THREE.TextureEncoding,
) {
const imageData = getImageData(width, height, type === THREE.UnsignedIntType);
const imageData = { width, height, data: new Uint32Array(1) };

super(
// @ts-ignore
Expand All @@ -91,38 +64,14 @@ class UpdatableTexture extends THREE.Texture {

setRenderer(renderer: THREE.WebGLRenderer) {
this.renderer = renderer;
this.gl = this.renderer.getContext();
this.gl = this.renderer.getContext() as WebGL2RenderingContext;
this.utils = new THREE.WebGLUtils(
this.gl,
this.renderer.extensions,
this.renderer.capabilities,
);
}

setSize(width: number, height: number) {
if (width === this.width && height === this.height) return;
if (!this.isInitialized()) return;
this.width = width;
this.height = height;
const activeTexture = this.gl.getParameter(this.gl.TEXTURE_BINDING_2D);
const textureProperties = this.renderer.properties.get(this);
this.gl.bindTexture(this.gl.TEXTURE_2D, textureProperties.__webglTexture);
if (!this.isInitialized()) this.width = undefined;

this.gl.texImage2D(
this.gl.TEXTURE_2D,
0,
this.utils.convert(this.format),
width,
height,
0,
this.utils.convert(this.format),
this.utils.convert(this.type),
null,
);
this.gl.bindTexture(this.gl.TEXTURE_2D, activeTexture);
}

isInitialized() {
return this.renderer.properties.get(this).__webglTexture != null;
}
Expand All @@ -134,21 +83,27 @@ class UpdatableTexture extends THREE.Texture {
width: number,
height: number,
) {
if (originalTexSubImage2D == null) {
// See explanation at declaration of originalTexSubImage2D.
originalTexSubImage2D = this.gl.texSubImage2D.bind(this.gl);
this.gl.texSubImage2D = _.noop;
}
daniel-wer marked this conversation as resolved.
Show resolved Hide resolved
if (!this.isInitialized()) {
this.renderer.initTexture(this);
}
const activeTexture = this.gl.getParameter(this.gl.TEXTURE_BINDING_2D);
const textureProperties = this.renderer.properties.get(this);
this.gl.bindTexture(this.gl.TEXTURE_2D, textureProperties.__webglTexture);
this.gl.texSubImage2D(

originalTexSubImage2D(
this.gl.TEXTURE_2D,
0,
x,
y,
width,
height,
this.utils.convert(this.format),
this.utils.convert(this.type),
this.utils.convert(this.format) as number,
this.utils.convert(this.type) as number,
src,
);
this.gl.bindTexture(this.gl.TEXTURE_2D, activeTexture);
Expand Down
13 changes: 13 additions & 0 deletions frontend/javascripts/oxalis/controller/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,18 @@ function getRenderer(): THREE.WebGLRenderer {
return renderer;
}

function testContextLoss() {
const renderer = getRenderer();
const ext = renderer.getContext().getExtension("WEBGL_lose_context");
if (ext == null) {
return;
}
ext.loseContext();
setTimeout(() => ext.restoreContext(), 2500);
}

// @ts-ignore
window.testContextLoss = testContextLoss;

export { getRenderer };
export default {};
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ export function createUpdatableTexture(
THREE.NearestFilter,
);
newTexture.setRenderer(renderer);
newTexture.setSize(width, width);
return newTexture;
}
export default {};
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,7 @@ export class CuckooTable {
getRenderer(),
THREE.RGBAIntegerFormat,
);
// This is needed so that the initialization of the texture
// can be done with an Image-like object ({ width, height, data })
// which is needed for integer textures, since createImageData results
// cannot be used for the combination THREE.UnsignedIntType + THREE.RGBAIntegerFormat.
// See the definition of texImage2D here: https://registry.khronos.org/webgl/specs/latest/2.0/
// Note that there are two overloads of texImage2D.
this._texture.isDataTexture = true;

// The internal format has to be set manually, since ThreeJS does not
// derive this value by itself.
// See https://webgl2fundamentals.org/webgl/lessons/webgl-data-textures.html
Expand Down Expand Up @@ -73,7 +67,6 @@ export class CuckooTable {
getRenderer(),
THREE.RGBAIntegerFormat,
);
cachedNullTexture.isDataTexture = true;
cachedNullTexture.internalFormat = "RGBA32UI";

return cachedNullTexture;
Expand Down