diff --git a/src/extensions/alpha-orbitals/gpu/compute.ts b/src/extensions/alpha-orbitals/gpu/compute.ts
index cb844f5ebd34fc9fe0b78aa60b64bade6718beb7..fa94690e9fc069164f76be928664d35d5871bf23 100644
--- a/src/extensions/alpha-orbitals/gpu/compute.ts
+++ b/src/extensions/alpha-orbitals/gpu/compute.ts
@@ -34,7 +34,8 @@ const AlphaOrbitalsSchema = {
     uMaxCoeffs: UniformSpec('i'),
     uLittleEndian: UniformSpec('b')
 };
-const AlphaOrbitalsShaderCode = ShaderCode('postprocessing', quad_vert, shader_frag);
+const AlphaOrbitalsName = 'alpha-orbitals';
+const AlphaOrbitalsShaderCode = ShaderCode(AlphaOrbitalsName, quad_vert, shader_frag);
 type AlphaOrbitalsRenderable = ComputeRenderable<Values<typeof AlphaOrbitalsSchema>>
 
 function createTextureData({
@@ -112,7 +113,7 @@ function createTextureData({
     return { nCenters: centerCount, nAlpha: baseCount, nCoeff: coeffCount, maxCoeffs, centers, info, alpha, coeff };
 }
 
-function getPostprocessingRenderable(ctx: WebGLContext, params: CollocationParams): AlphaOrbitalsRenderable {
+function createAlphaOrbitalsRenderable(ctx: WebGLContext, params: CollocationParams): AlphaOrbitalsRenderable {
     const data = createTextureData(params);
 
     const [nx, ny, nz] = params.grid.dimensions;
@@ -136,36 +137,65 @@ function getPostprocessingRenderable(ctx: WebGLContext, params: CollocationParam
     };
 
     const schema = { ...AlphaOrbitalsSchema };
+    if (!ctx.isWebGL2) {
+        // workaround for webgl1 limitation that loop counters need to be `const`
+        (schema.uNCenters as any) = DefineSpec('number');
+        (schema.uMaxCoeffs as any) = DefineSpec('number');
+    }
+
     const renderItem = createComputeRenderItem(ctx, 'triangles', AlphaOrbitalsShaderCode, schema, values);
 
     return createComputeRenderable(renderItem, values);
 }
 
-function normalizeParams(webgl: WebGLContext) {
-    if (!webgl.isWebGL2) {
-        // workaround for webgl1 limitation that loop counters need to be `const`
-        (AlphaOrbitalsSchema.uNCenters as any) = DefineSpec('number');
-        (AlphaOrbitalsSchema.uMaxCoeffs as any) = DefineSpec('number');
+function getAlphaOrbitalsRenderable(ctx: WebGLContext, params: CollocationParams): AlphaOrbitalsRenderable {
+    if (ctx.namedComputeRenderables[AlphaOrbitalsName]) {
+        const v = ctx.namedComputeRenderables[AlphaOrbitalsName].values;
+
+        const data = createTextureData(params);
+
+        const [nx, ny, nz] = params.grid.dimensions;
+        const width = Math.ceil(Math.sqrt(nx * ny * nz));
+
+        ValueCell.update(v.uDimensions, params.grid.dimensions);
+        ValueCell.update(v.uMin, params.grid.box.min);
+        ValueCell.update(v.uDelta, params.grid.delta);
+        ValueCell.updateIfChanged(v.uWidth, width);
+        ValueCell.updateIfChanged(v.uNCenters, data.nCenters);
+        ValueCell.updateIfChanged(v.uNAlpha, data.nAlpha);
+        ValueCell.updateIfChanged(v.uNCoeff, data.nCoeff);
+        ValueCell.updateIfChanged(v.uMaxCoeffs, data.maxCoeffs);
+        ValueCell.update(v.tCenters, { width: data.nCenters, height: 1, array: data.centers });
+        ValueCell.update(v.tInfo, { width: data.nCenters, height: 1, array: data.info });
+        ValueCell.update(v.tCoeff, { width: data.nCoeff, height: 1, array: data.coeff });
+        ValueCell.update(v.tAlpha, { width: data.nAlpha, height: 1, array: data.alpha });
+        ValueCell.updateIfChanged(v.uLittleEndian, isLittleEndian());
+
+        ctx.namedComputeRenderables[AlphaOrbitalsName].update();
+    } else {
+        ctx.namedComputeRenderables[AlphaOrbitalsName] = createAlphaOrbitalsRenderable(ctx, params);
     }
+    return ctx.namedComputeRenderables[AlphaOrbitalsName];
 }
 
 export function gpuComputeAlphaOrbitalsGridValues(webgl: WebGLContext, params: CollocationParams) {
     const [nx, ny, nz] = params.grid.dimensions;
-    normalizeParams(webgl);
-
-    const renderable = getPostprocessingRenderable(webgl, params);
+    const renderable = getAlphaOrbitalsRenderable(webgl, params);
     const width = renderable.values.uWidth.ref.value;
 
-    if (!webgl.computeTargets['alpha-oribtals']) {
-        webgl.computeTargets['alpha-oribtals'] = webgl.createRenderTarget(width, width, false, 'uint8', 'nearest');
-    } else {
-        webgl.computeTargets['alpha-oribtals'].setSize(width, width);
+    if (!webgl.namedFramebuffers[AlphaOrbitalsName]) {
+        webgl.namedFramebuffers[AlphaOrbitalsName] = webgl.resources.framebuffer();
     }
+    const framebuffer = webgl.namedFramebuffers[AlphaOrbitalsName];
 
-    const target = webgl.computeTargets['alpha-oribtals'];
+    if (!webgl.namedTextures[AlphaOrbitalsName]) {
+        webgl.namedTextures[AlphaOrbitalsName] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
+    }
+    webgl.namedTextures[AlphaOrbitalsName].define(width, width);
+    webgl.namedTextures[AlphaOrbitalsName].attachFramebuffer(framebuffer, 'color0');
 
     const { gl, state } = webgl;
-    target.bind();
+    framebuffer.bind();
     gl.viewport(0, 0, width, width);
     gl.scissor(0, 0, width, width);
     state.disable(gl.SCISSOR_TEST);
@@ -176,10 +206,7 @@ export function gpuComputeAlphaOrbitalsGridValues(webgl: WebGLContext, params: C
 
     const array = new Uint8Array(width * width * 4);
     webgl.readPixels(0, 0, width, width, array);
-    const floats = new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
-    renderable.dispose();
-
-    return floats;
+    return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz);
 }
 
 export function canComputeAlphaOrbitalsOnGPU(webgl?: WebGLContext) {
diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts
index b2250f3a61cff551dccfba8db2bc90decadc90df..e9303b859574b1cb6826032105360fe21aab5036 100644
--- a/src/mol-gl/webgl/context.ts
+++ b/src/mol-gl/webgl/context.ts
@@ -5,7 +5,7 @@
  */
 
 import { GLRenderingContext, isWebGL2 } from './compat';
-import { checkFramebufferStatus } from './framebuffer';
+import { checkFramebufferStatus, Framebuffer } from './framebuffer';
 import { Scheduler } from '../../mol-task';
 import { isDebugMode } from '../../mol-util/debug';
 import { createExtensions, WebGLExtensions } from './extensions';
@@ -15,7 +15,8 @@ import { WebGLResources, createResources } from './resources';
 import { RenderTarget, createRenderTarget } from './render-target';
 import { BehaviorSubject } from 'rxjs';
 import { now } from '../../mol-util/now';
-import { TextureFilter } from './texture';
+import { Texture, TextureFilter } from './texture';
+import { ComputeRenderable } from '../renderable';
 
 export function getGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes): GLRenderingContext | null {
     function getContext(contextId: 'webgl' | 'experimental-webgl' | 'webgl2') {
@@ -191,8 +192,12 @@ export interface WebGLContext {
     setContextLost: () => void
     handleContextRestored: () => void
 
-    // A cache for compute targets, managed by the code that uses it
-    readonly computeTargets: { [name: string]: RenderTarget }
+    /** Cache for compute renderables, managed by consumers */
+    readonly namedComputeRenderables: { [name: string]: ComputeRenderable<any> }
+    /** Cache for frambuffers, managed by consumers */
+    readonly namedFramebuffers: { [name: string]: Framebuffer }
+    /** Cache for textures, managed by consumers */
+    readonly namedTextures: { [name: string]: Texture }
 
     createRenderTarget: (width: number, height: number, depth?: boolean, type?: 'uint8' | 'float32', filter?: TextureFilter) => RenderTarget
     unbindFramebuffer: () => void
@@ -264,8 +269,6 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
 
     const renderTargets = new Set<RenderTarget>();
 
-    const computeTargets = Object.create(null);
-
     return {
         gl,
         isWebGL2: isWebGL2(gl),
@@ -283,7 +286,9 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
         get maxRenderbufferSize () { return parameters.maxRenderbufferSize; },
         get maxDrawBuffers () { return parameters.maxDrawBuffers; },
 
-        computeTargets,
+        namedComputeRenderables: Object.create(null),
+        namedFramebuffers: Object.create(null),
+        namedTextures: Object.create(null),
 
         get isContextLost () {
             return isContextLost || gl.isContextLost();
diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts
index ce0976ed28e1066707d3fa85173a213379d6ccc6..3ccd3093cec7727c13e42966478edd75f2ba64a7 100644
--- a/src/mol-gl/webgl/texture.ts
+++ b/src/mol-gl/webgl/texture.ts
@@ -197,6 +197,8 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
     let destroyed = false;
 
     function define(_width: number, _height: number, _depth?: number) {
+        if (width === _width && height === _height && depth === (_depth || 0)) return;
+
         width = _width, height = _height, depth = _depth || 0;
         gl.bindTexture(target, texture);
         if (target === gl.TEXTURE_2D) {
diff --git a/src/mol-math/geometry/gaussian-density/gpu.ts b/src/mol-math/geometry/gaussian-density/gpu.ts
index b5d5352e1f6450f121a1462c9d62d87b7a1e30b0..09618276513924f174cf5f76b0c239d00f5c475e 100644
--- a/src/mol-math/geometry/gaussian-density/gpu.ts
+++ b/src/mol-math/geometry/gaussian-density/gpu.ts
@@ -13,7 +13,7 @@ import { Vec3, Tensor, Mat4, Vec2 } from '../../linear-algebra';
 import { ValueCell } from '../../../mol-util';
 import { createComputeRenderable, ComputeRenderable } from '../../../mol-gl/renderable';
 import { WebGLContext } from '../../../mol-gl/webgl/context';
-import { Texture } from '../../../mol-gl/webgl/texture';
+import { Texture, TextureFilter, TextureFormat, TextureKind, TextureType } from '../../../mol-gl/webgl/texture';
 import { decodeFloatRGB } from '../../../mol-util/float-packing';
 import { ShaderCode } from '../../../mol-gl/shader-code';
 import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item';
@@ -48,21 +48,32 @@ export const GaussianDensitySchema = {
 };
 type GaussianDensityValues = Values<typeof GaussianDensitySchema>
 type GaussianDensityRenderable = ComputeRenderable<GaussianDensityValues>
-
+const GaussianDensityName = 'gaussian-density';
 const GaussianDensityShaderCode = ShaderCode(
-    'gaussian-density', gaussian_density_vert, gaussian_density_frag
+    GaussianDensityName, gaussian_density_vert, gaussian_density_frag
 );
 
-let _tmpTexture: Texture | undefined = undefined;
+function getFramebuffer(webgl: WebGLContext): Framebuffer {
+    if (!webgl.namedFramebuffers[GaussianDensityName]) {
+        webgl.namedFramebuffers[GaussianDensityName] = webgl.resources.framebuffer();
+    }
+    return webgl.namedFramebuffers[GaussianDensityName];
+}
+
+function getTexture(name: string, webgl: WebGLContext, kind: TextureKind, format: TextureFormat, type: TextureType, filter: TextureFilter): Texture {
+    const _name = `${GaussianDensityName}-${name}`;
+    if (!webgl.namedTextures[_name]) {
+        webgl.namedTextures[_name] = webgl.resources.texture(kind, format, type, filter);
+    }
+    return webgl.namedTextures[_name];
+}
 
 export function GaussianDensityGPU(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, webgl: WebGLContext): DensityData {
     // always use texture2d when the gaussian density needs to be downloaded from the GPU,
     // it's faster than texture3d
     // console.time('GaussianDensityTexture2d')
-    if (!_tmpTexture) {
-        _tmpTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
-    }
-    const { scale, bbox, texture, gridDim, gridTexDim } = calcGaussianDensityTexture2d(webgl, position, box, radius, props, _tmpTexture);
+    const tmpTexture = getTexture('tmp', webgl, 'image-uint8', 'rgba', 'ubyte', 'linear');
+    const { scale, bbox, texture, gridDim, gridTexDim } = calcGaussianDensityTexture2d(webgl, position, box, radius, props, tmpTexture);
     // webgl.waitForGpuCommandsCompleteSync()
     // console.timeEnd('GaussianDensityTexture2d')
     const { field, idField } = fieldFromTexture2d(webgl, texture, gridDim, gridTexDim);
@@ -106,9 +117,6 @@ type GaussianDensityTextureData = {
     gridTexScale: Vec2
 }
 
-let _tmpFramebuffer: Framebuffer | undefined = undefined;
-let _minDistanceTexture2d: Texture | undefined = undefined;
-
 function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): GaussianDensityTextureData {
     // console.log('2d');
     const { smoothness, resolution } = props;
@@ -121,31 +129,22 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
     const gridTexScale = Vec2.create(texDimX / texDimX, texDimY / texDimY);
     const radiusFactor = maxRadius * 2;
 
-    if (!_minDistanceTexture2d) {
-        _minDistanceTexture2d = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
-        _minDistanceTexture2d.define(texDimX, texDimY);
-    } else if (_minDistanceTexture2d.getWidth() !== texDimX || _minDistanceTexture2d.getHeight() !== texDimY) {
-        _minDistanceTexture2d.define(texDimX, texDimY);
-    }
+    const minDistTex = getTexture('min-dist-2d', webgl, 'image-uint8', 'rgba', 'ubyte', 'nearest');
+    minDistTex.define(texDimX, texDimY);
 
-    const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, _minDistanceTexture2d, expandedBox, dim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor);
+    const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistTex, expandedBox, dim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor);
 
     //
 
     const { gl, resources, state } = webgl;
     const { uCurrentSlice, uCurrentX, uCurrentY } = renderable.values;
 
-    if (!_tmpFramebuffer) _tmpFramebuffer = resources.framebuffer();
-    const framebuffer = _tmpFramebuffer;
+    const framebuffer = getFramebuffer(webgl);
     framebuffer.bind();
     setRenderingDefaults(webgl);
 
-    if (!texture) {
-        texture = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
-        texture.define(texDimX, texDimY);
-    } else if (texture.getWidth() !== texDimX || texture.getHeight() !== texDimY) {
-        texture.define(texDimX, texDimY);
-    }
+    if (!texture) texture = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
+    texture.define(texDimX, texDimY);
 
     // console.log(renderable)
 
@@ -178,7 +177,7 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
     render(texture, true);
 
     setupMinDistanceRendering(webgl, renderable);
-    render(_minDistanceTexture2d, true);
+    render(minDistTex, true);
 
     setupGroupIdRendering(webgl, renderable);
     render(texture, false);
@@ -188,8 +187,6 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
     return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale };
 }
 
-let _minDistanceTexture3d: Texture | undefined = undefined;
-
 function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): GaussianDensityTextureData {
     // console.log('3d');
     const { gl, resources, state } = webgl;
@@ -198,33 +195,25 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat
     const { drawCount, positions, radii, groups, scale, expandedBox, dim, maxRadius } = prepareGaussianDensityData(position, box, radius, props);
     const [ dx, dy, dz ] = dim;
 
-    if (!_minDistanceTexture3d) {
-        _minDistanceTexture3d = resources.texture('volume-uint8', 'rgba', 'ubyte', 'nearest');
-        _minDistanceTexture3d.define(dx, dy, dz);
-    } else if (_minDistanceTexture3d.getWidth() !== dx || _minDistanceTexture3d.getHeight() !== dy || _minDistanceTexture3d.getDepth() !== dz) {
-        _minDistanceTexture3d.define(dx, dy, dz);
-    }
+    const minDistTex = getTexture('min-dist-3d', webgl, 'volume-uint8', 'rgba', 'ubyte', 'nearest');
+    minDistTex.define(dx, dy, dz);
 
     const gridTexScale = Vec2.create(1, 1);
     const radiusFactor = maxRadius * 2;
 
-    const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, _minDistanceTexture3d, expandedBox, dim, dim, gridTexScale, smoothness, resolution, radiusFactor);
+    const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistTex, expandedBox, dim, dim, gridTexScale, smoothness, resolution, radiusFactor);
 
     //
 
     const { uCurrentSlice } = renderable.values;
 
-    const framebuffer = resources.framebuffer();
+    const framebuffer = getFramebuffer(webgl);
     framebuffer.bind();
     setRenderingDefaults(webgl);
     gl.viewport(0, 0, dx, dy);
 
-    if (!texture) {
-        texture = resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear');
-        texture.define(dx, dy, dz);
-    } else if (texture.getWidth() !== dx || texture.getHeight() !== dy || texture.getDepth() !== dz) {
-        texture.define(dx, dy, dz);
-    }
+    if (!texture) texture = resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear');
+    texture.define(dx, dy, dz);
 
     function render(fbTex: Texture, clear: boolean) {
         state.currentRenderItemId = -1;
@@ -241,7 +230,7 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat
     render(texture, true);
 
     setupMinDistanceRendering(webgl, renderable);
-    render(_minDistanceTexture3d, true);
+    render(minDistTex, true);
 
     setupGroupIdRendering(webgl, renderable);
     render(texture, false);
@@ -287,13 +276,11 @@ function prepareGaussianDensityData(position: PositionData, box: Box3D, radius:
     return { drawCount: n, positions, radii, groups, scale, expandedBox, dim, maxRadius };
 }
 
-let _GaussianDensityRenderable: GaussianDensityRenderable | undefined = undefined;
-
 function getGaussianDensityRenderable(webgl: WebGLContext, drawCount: number, positions: Float32Array, radii: Float32Array, groups: Float32Array, minDistanceTexture: Texture, box: Box3D, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, smoothness: number, resolution: number, radiusFactor: number) {
     // console.log('radiusFactor', radiusFactor);
-    if (_GaussianDensityRenderable) {
+    if (webgl.namedComputeRenderables[GaussianDensityName]) {
         const extent = Vec3.sub(Vec3(), box.max, box.min);
-        const v = _GaussianDensityRenderable.values;
+        const v = webgl.namedComputeRenderables[GaussianDensityName].values;
 
         ValueCell.updateIfChanged(v.drawCount, drawCount);
         ValueCell.updateIfChanged(v.instanceCount, 1);
@@ -318,14 +305,14 @@ function getGaussianDensityRenderable(webgl: WebGLContext, drawCount: number, po
         ValueCell.updateIfChanged(v.dGridTexType, minDistanceTexture.getDepth() > 0 ? '3d' : '2d');
         ValueCell.updateIfChanged(v.dCalcType, 'density');
 
-        _GaussianDensityRenderable.update();
+        webgl.namedComputeRenderables[GaussianDensityName].update();
     } else {
-        _GaussianDensityRenderable = _getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistanceTexture, box, gridDim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor);
+        webgl.namedComputeRenderables[GaussianDensityName] = createGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistanceTexture, box, gridDim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor);
     }
-    return _GaussianDensityRenderable;
+    return webgl.namedComputeRenderables[GaussianDensityName];
 }
 
-function _getGaussianDensityRenderable(webgl: WebGLContext, drawCount: number, positions: Float32Array, radii: Float32Array, groups: Float32Array, minDistanceTexture: Texture, box: Box3D, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, smoothness: number, resolution: number, radiusFactor: number) {
+function createGaussianDensityRenderable(webgl: WebGLContext, drawCount: number, positions: Float32Array, radii: Float32Array, groups: Float32Array, minDistanceTexture: Texture, box: Box3D, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, smoothness: number, resolution: number, radiusFactor: number) {
     const extent = Vec3.sub(Vec3(), box.max, box.min);
 
     const values: GaussianDensityValues = {
@@ -423,7 +410,6 @@ function getTexture2dSize(gridDim: Vec3) {
 
 export function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec3, texDim: Vec3) {
     // console.time('fieldFromTexture2d')
-    const { resources } = ctx;
     const [ dx, dy, dz ] = dim;
     const [ width, height ] = texDim;
     const fboTexCols = Math.floor(width / dx);
@@ -436,7 +422,7 @@ export function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec
 
     const image = new Uint8Array(width * height * 4);
 
-    const framebuffer = resources.framebuffer();
+    const framebuffer = getFramebuffer(ctx);
     framebuffer.bind();
     texture.attachFramebuffer(framebuffer, 0);
     ctx.readPixels(0, 0, width, height, image);