diff --git a/src/mol-canvas3d/passes/multi-sample.ts b/src/mol-canvas3d/passes/multi-sample.ts
index 6db628cd96cc1cec14066676dabc18eea85913d6..99fb46edf897921356bc4e73fe4e47fb10154219 100644
--- a/src/mol-canvas3d/passes/multi-sample.ts
+++ b/src/mol-canvas3d/passes/multi-sample.ts
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -69,11 +69,13 @@ export class MultiSamplePass {
     private compose: ComposeRenderable
 
     constructor(private webgl: WebGLContext, private drawPass: DrawPass, private postprocessing: PostprocessingPass) {
-        const { colorBufferFloat, textureFloat } = webgl.extensions;
+        const { colorBufferFloat, textureFloat, colorBufferHalfFloat, textureHalfFloat } = webgl.extensions;
         const width = drawPass.colorTarget.getWidth();
         const height = drawPass.colorTarget.getHeight();
         this.colorTarget = webgl.createRenderTarget(width, height, false);
-        this.composeTarget = webgl.createRenderTarget(width, height, false, colorBufferFloat && textureFloat ? 'float32' : 'uint8');
+        const type = colorBufferHalfFloat && textureHalfFloat ? 'fp16' :
+            colorBufferFloat && textureFloat ? 'float32' : 'uint8';
+        this.composeTarget = webgl.createRenderTarget(width, height, false, type);
         this.holdTarget = webgl.createRenderTarget(width, height, false);
         this.compose = getComposeRenderable(webgl, drawPass.colorTarget.texture);
     }
diff --git a/src/mol-gl/webgl/compat.ts b/src/mol-gl/webgl/compat.ts
index 2d0cf14252be40a3e292fa39edbaaa79909f65b0..9f3b17d61af7fd119e5dfb3c0062e3efff1c1348 100644
--- a/src/mol-gl/webgl/compat.ts
+++ b/src/mol-gl/webgl/compat.ts
@@ -1,6 +1,6 @@
 import { isDebugMode } from '../../mol-util/debug';
 /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -111,6 +111,27 @@ export function getTextureFloatLinear(gl: GLRenderingContext): COMPAT_texture_fl
     return gl.getExtension('OES_texture_float_linear');
 }
 
+export interface COMPAT_texture_half_float {
+    readonly HALF_FLOAT: number
+}
+
+export function getTextureHalfFloat(gl: GLRenderingContext): COMPAT_texture_half_float | null {
+    if (isWebGL2(gl)) {
+        return { HALF_FLOAT: gl.HALF_FLOAT };
+    } else {
+        const ext = gl.getExtension('OES_texture_half_float');
+        if (ext === null) return null;
+        return { HALF_FLOAT: ext.HALF_FLOAT_OES };
+    }
+}
+
+export interface COMPAT_texture_half_float_linear {
+}
+
+export function getTextureHalfFloatLinear(gl: GLRenderingContext): COMPAT_texture_half_float_linear | null {
+    return gl.getExtension('OES_texture_half_float_linear');
+}
+
 export interface COMPAT_blend_minmax {
     readonly MIN: number
     readonly MAX: number
@@ -146,13 +167,33 @@ export function getColorBufferFloat(gl: GLRenderingContext): COMPAT_color_buffer
         const ext = gl.getExtension('WEBGL_color_buffer_float');
         if (ext === null) {
             // test as support may not be advertised by browsers
-            return testColorBufferFloat() ? { RGBA32F: 0x8814 } : null;
+            return testColorBuffer(gl.FLOAT, 'OES_texture_float') ? { RGBA32F: 0x8814 } : null;
         }
         gl.getExtension('EXT_float_blend');
         return { RGBA32F: ext.RGBA32F_EXT };
     }
 }
 
+export interface COMPAT_color_buffer_half_float {
+    readonly RGBA16F: number;
+}
+
+export function getColorBufferHalfFloat(gl: GLRenderingContext): COMPAT_color_buffer_half_float | null {
+    if (isWebGL2(gl)) {
+        if (gl.getExtension('EXT_color_buffer_half_float') === null) return null;
+        gl.getExtension('EXT_float_blend');
+        return { RGBA16F: gl.RGBA16F };
+    } else {
+        const ext = gl.getExtension('EXT_color_buffer_half_float');
+        if (ext === null) {
+            // test as support may not be advertised by browsers
+            return testColorBuffer(0x8D61, 'OES_texture_half_float') ? { RGBA16F: 0x881A } : null;
+        }
+        gl.getExtension('EXT_float_blend');
+        return { RGBA16F: ext.RGBA16F_EXT };
+    }
+}
+
 export interface COMPAT_draw_buffers {
     drawBuffers(buffers: number[]): void;
     readonly COLOR_ATTACHMENT0: number;
@@ -299,7 +340,7 @@ const TextureTestTexCoords = new Float32Array([
     -1.0, -1.0, 1.0, -1.0, -1.0,  1.0, -1.0,  1.0, 1.0, -1.0, 1.0,  1.0
 ]);
 
-export function testColorBufferFloat() {
+export function testColorBuffer(type: number, ext: string) {
     // adapted from https://stackoverflow.com/questions/28827511/
 
     // Get A WebGL context
@@ -311,8 +352,7 @@ export function testColorBufferFloat() {
     const gl = getGLContext(canvas);
     if (gl === null) throw new Error('Unable to get WebGL context');
 
-    const type = gl.FLOAT;
-    gl.getExtension('OES_texture_float');
+    gl.getExtension(ext);
 
     // setup shaders
     const vertShader = getShader(gl, { type: 'vert', source: TextureTestVertShader });
diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts
index 8823b75f35a3171fa9620eedf80cdac9e20503ee..fa6616a87501b7428308ee5f31aaf92309c61297 100644
--- a/src/mol-gl/webgl/context.ts
+++ b/src/mol-gl/webgl/context.ts
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -202,7 +202,7 @@ export interface WebGLContext {
     /** Cache for textures, managed by consumers */
     readonly namedTextures: { [name: string]: Texture }
 
-    createRenderTarget: (width: number, height: number, depth?: boolean, type?: 'uint8' | 'float32', filter?: TextureFilter) => RenderTarget
+    createRenderTarget: (width: number, height: number, depth?: boolean, type?: 'uint8' | 'float32' | 'fp16', filter?: TextureFilter) => RenderTarget
     unbindFramebuffer: () => void
     readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array) => void
     readPixelsAsync: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => Promise<void>
@@ -317,7 +317,7 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
             contextRestored.next(now());
         },
 
-        createRenderTarget: (width: number, height: number, depth?: boolean, type?: 'uint8' | 'float32', filter?: TextureFilter) => {
+        createRenderTarget: (width: number, height: number, depth?: boolean, type?: 'uint8' | 'float32' | 'fp16', filter?: TextureFilter) => {
             const renderTarget = createRenderTarget(gl, resources, width, height, depth, type, filter);
             renderTargets.add(renderTarget);
             return {
diff --git a/src/mol-gl/webgl/extensions.ts b/src/mol-gl/webgl/extensions.ts
index 712dddf18f56e222eb4e8c94724c8973fc2ea222..61abe6d232328751e8a31f224da87d8fae47bbd5 100644
--- a/src/mol-gl/webgl/extensions.ts
+++ b/src/mol-gl/webgl/extensions.ts
@@ -1,10 +1,10 @@
 /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth, COMPAT_color_buffer_float, getColorBufferFloat, COMPAT_draw_buffers, getDrawBuffers, getShaderTextureLod, COMPAT_shader_texture_lod, getDepthTexture, COMPAT_depth_texture, COMPAT_sRGB, getSRGB } from './compat';
+import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth, COMPAT_color_buffer_float, getColorBufferFloat, COMPAT_draw_buffers, getDrawBuffers, getShaderTextureLod, COMPAT_shader_texture_lod, getDepthTexture, COMPAT_depth_texture, COMPAT_sRGB, getSRGB, getTextureHalfFloat, getTextureHalfFloatLinear, COMPAT_texture_half_float, COMPAT_texture_half_float_linear, COMPAT_color_buffer_half_float, getColorBufferHalfFloat } from './compat';
 import { isDebugMode } from '../../mol-util/debug';
 
 export type WebGLExtensions = {
@@ -14,11 +14,14 @@ export type WebGLExtensions = {
     standardDerivatives: COMPAT_standard_derivatives | null
     textureFloat: COMPAT_texture_float | null
     textureFloatLinear: COMPAT_texture_float_linear | null
+    textureHalfFloat: COMPAT_texture_half_float | null
+    textureHalfFloatLinear: COMPAT_texture_half_float_linear | null
     depthTexture: COMPAT_depth_texture | null
     blendMinMax: COMPAT_blend_minmax | null
     vertexArrayObject: COMPAT_vertex_array_object | null
     fragDepth: COMPAT_frag_depth | null
     colorBufferFloat: COMPAT_color_buffer_float | null
+    colorBufferHalfFloat: COMPAT_color_buffer_half_float | null
     drawBuffers: COMPAT_draw_buffers | null
     shaderTextureLod: COMPAT_shader_texture_lod | null
     sRGB: COMPAT_sRGB | null
@@ -50,6 +53,16 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
         // - can't be a required extension because it is not supported by `headless-gl`
         console.log('Could not find support for "texture_float_linear"');
     }
+    const textureHalfFloat = getTextureHalfFloat(gl);
+    if (isDebugMode && textureHalfFloat === null) {
+        console.log('Could not find support for "texture_half_float"');
+    }
+    const textureHalfFloatLinear = getTextureHalfFloatLinear(gl);
+    if (isDebugMode && textureHalfFloatLinear === null) {
+        // TODO handle non-support downstream (no gpu gaussian calc, no gpu mc???)
+        // - can't be a required extension because it is not supported by `headless-gl`
+        console.log('Could not find support for "texture_half_float_linear"');
+    }
     const depthTexture = getDepthTexture(gl);
     if (isDebugMode && depthTexture === null) {
         console.log('Could not find support for "depth_texture"');
@@ -72,6 +85,10 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
     if (isDebugMode && colorBufferFloat === null) {
         console.log('Could not find support for "color_buffer_float"');
     }
+    const colorBufferHalfFloat = getColorBufferHalfFloat(gl);
+    if (isDebugMode && colorBufferHalfFloat === null) {
+        console.log('Could not find support for "color_buffer_half_float"');
+    }
     const drawBuffers = getDrawBuffers(gl);
     if (isDebugMode && drawBuffers === null) {
         console.log('Could not find support for "draw_buffers"');
@@ -90,6 +107,8 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
         standardDerivatives,
         textureFloat,
         textureFloatLinear,
+        textureHalfFloat,
+        textureHalfFloatLinear,
         elementIndexUint,
         depthTexture,
 
@@ -97,6 +116,7 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions {
         vertexArrayObject,
         fragDepth,
         colorBufferFloat,
+        colorBufferHalfFloat,
         drawBuffers,
         shaderTextureLod,
         sRGB,
diff --git a/src/mol-gl/webgl/render-target.ts b/src/mol-gl/webgl/render-target.ts
index a2c9e751240726132b13d74f09ae2a978ef85756..d400557d815b5706ff66fc3d4f8b0a85995b5326 100644
--- a/src/mol-gl/webgl/render-target.ts
+++ b/src/mol-gl/webgl/render-target.ts
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -26,12 +26,14 @@ export interface RenderTarget {
     destroy: () => void
 }
 
-export function createRenderTarget(gl: GLRenderingContext, resources: WebGLResources, _width: number, _height: number, depth = true, type: 'uint8' | 'float32' = 'uint8', filter: TextureFilter = 'nearest'): RenderTarget {
+export function createRenderTarget(gl: GLRenderingContext, resources: WebGLResources, _width: number, _height: number, depth = true, type: 'uint8' | 'float32' | 'fp16' = 'uint8', filter: TextureFilter = 'nearest'): RenderTarget {
 
     const framebuffer = resources.framebuffer();
-    const targetTexture = type === 'float32'
-        ? resources.texture('image-float32', 'rgba', 'float', filter)
-        : resources.texture('image-uint8', 'rgba', 'ubyte', filter);
+    const targetTexture = type === 'fp16'
+        ? resources.texture('image-float16', 'rgba', 'fp16', filter)
+        : type === 'float32'
+            ? resources.texture('image-float32', 'rgba', 'float', filter)
+            : resources.texture('image-uint8', 'rgba', 'ubyte', filter);
     // make a depth renderbuffer of the same size as the targetTexture
     const depthRenderbuffer = depth
         ? resources.renderbuffer('depth16', 'depth', _width, _height)
diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts
index 11eb2404657c148591954952e9145eaafc8824e0..0cd5edd669ac9a8577879ca5c9eb06359dce5d7e 100644
--- a/src/mol-gl/webgl/texture.ts
+++ b/src/mol-gl/webgl/texture.ts
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -19,14 +19,16 @@ const getNextTextureId = idFactory();
 export type TextureKindValue = {
     'image-uint8': TextureImage<Uint8Array>
     'image-float32': TextureImage<Float32Array>
+    'image-float16': TextureImage<Float32Array>
     'image-depth': TextureImage<Uint8Array> // TODO should be Uint32Array
     'volume-uint8': TextureVolume<Uint8Array>
     'volume-float32': TextureVolume<Float32Array>
+    'volume-float16': TextureVolume<Float32Array>
     'texture': Texture
 }
 export type TextureValueType = ValueOf<TextureKindValue>
 export type TextureKind = keyof TextureKindValue
-export type TextureType = 'ubyte' | 'ushort' | 'float'
+export type TextureType = 'ubyte' | 'ushort' | 'float' | 'fp16'
 export type TextureFormat = 'alpha' | 'rgb' | 'rgba' | 'depth'
 /** Numbers are shortcuts for color attachment */
 export type TextureAttachment = 'depth' | 'stencil' | 'color0' | 'color1' | 'color2' | 'color3' | 'color4' | 'color5' | 'color6' | 'color7' | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
@@ -36,12 +38,14 @@ export function getTarget(gl: GLRenderingContext, kind: TextureKind): number {
     switch (kind) {
         case 'image-uint8': return gl.TEXTURE_2D;
         case 'image-float32': return gl.TEXTURE_2D;
+        case 'image-float16': return gl.TEXTURE_2D;
         case 'image-depth': return gl.TEXTURE_2D;
     }
     if (isWebGL2(gl)) {
         switch (kind) {
             case 'volume-uint8': return gl.TEXTURE_3D;
             case 'volume-float32': return gl.TEXTURE_3D;
+            case 'volume-float16': return gl.TEXTURE_3D;
         }
     }
     throw new Error(`unknown texture kind '${kind}'`);
@@ -65,16 +69,19 @@ export function getInternalFormat(gl: GLRenderingContext, format: TextureFormat,
                 switch (type) {
                     case 'ubyte': return gl.ALPHA;
                     case 'float': return gl.R32F;
+                    case 'fp16': return gl.R16F;
                 }
             case 'rgb':
                 switch (type) {
                     case 'ubyte': return gl.RGB;
                     case 'float': return gl.RGB32F;
+                    case 'fp16': return gl.RGB16F;
                 }
             case 'rgba':
                 switch (type) {
                     case 'ubyte': return gl.RGBA;
                     case 'float': return gl.RGBA32F;
+                    case 'fp16': return gl.RGBA16F;
                 }
             case 'depth':
                 return gl.DEPTH_COMPONENT16;
@@ -83,11 +90,14 @@ export function getInternalFormat(gl: GLRenderingContext, format: TextureFormat,
     return getFormat(gl, format, type);
 }
 
-export function getType(gl: GLRenderingContext, type: TextureType): number {
+export function getType(gl: GLRenderingContext, extensions: WebGLExtensions, type: TextureType): number {
     switch (type) {
         case 'ubyte': return gl.UNSIGNED_BYTE;
         case 'ushort': return gl.UNSIGNED_SHORT;
         case 'float': return gl.FLOAT;
+        case 'fp16':
+            if (extensions.textureHalfFloat) return extensions.textureHalfFloat.HALF_FLOAT;
+            else throw new Error('extension "texture_half_float" unavailable');
     }
 }
 
@@ -169,6 +179,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
     // check texture kind and type compatability
     if (
         (kind.endsWith('float32') && _type !== 'float') ||
+        (kind.endsWith('float16') && _type !== 'fp16') ||
         (kind.endsWith('uint8') && _type !== 'ubyte') ||
         (kind.endsWith('depth') && _type !== 'ushort')
     ) {
@@ -179,7 +190,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension
     const filter = getFilter(gl, _filter);
     const format = getFormat(gl, _format, _type);
     const internalFormat = getInternalFormat(gl, _format, _type);
-    const type = getType(gl, _type);
+    const type = getType(gl, extensions, _type);
 
     function init() {
         gl.bindTexture(target, texture);
diff --git a/src/mol-math/geometry/gaussian-density/gpu.ts b/src/mol-math/geometry/gaussian-density/gpu.ts
index 3f54064405eecdd9e36f855d4ee36660c414c6a5..afc03720fea586dc5a03d4fbd02e82074af4ad08 100644
--- a/src/mol-math/geometry/gaussian-density/gpu.ts
+++ b/src/mol-math/geometry/gaussian-density/gpu.ts
@@ -120,7 +120,7 @@ type _GaussianDensityTextureData = {
 
 function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, powerOfTwo: boolean, props: GaussianDensityGPUProps, texture?: Texture): _GaussianDensityTextureData {
     // console.log('2d');
-    const { gl, resources, state, extensions: { colorBufferFloat, textureFloat } } = webgl;
+    const { gl, resources, state, extensions: { colorBufferFloat, textureFloat, colorBufferHalfFloat, textureHalfFloat } } = webgl;
     const { smoothness, resolution } = props;
 
     const { drawCount, positions, radii, groups, scale, expandedBox, dim, maxRadius } = prepareGaussianDensityData(position, box, radius, props);
@@ -147,9 +147,11 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
     framebuffer.bind();
     setRenderingDefaults(webgl);
 
-    if (!texture) texture = colorBufferFloat && textureFloat
-        ? resources.texture('image-float32', 'rgba', 'float', 'linear')
-        : resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
+    if (!texture) texture = colorBufferHalfFloat && textureHalfFloat
+        ? resources.texture('image-float16', 'rgba', 'fp16', 'linear')
+        : colorBufferFloat && textureFloat
+            ? resources.texture('image-float32', 'rgba', 'float', 'linear')
+            : resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
     texture.define(width, height);
 
     // console.log(renderable)
@@ -201,7 +203,7 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
 
 function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): _GaussianDensityTextureData {
     // console.log('3d');
-    const { gl, resources, state, extensions: { colorBufferFloat, textureFloat } } = webgl;
+    const { gl, resources, state, extensions: { colorBufferFloat, textureFloat, colorBufferHalfFloat, textureHalfFloat } } = webgl;
     const { smoothness, resolution } = props;
 
     const { drawCount, positions, radii, groups, scale, expandedBox, dim, maxRadius } = prepareGaussianDensityData(position, box, radius, props);
@@ -225,9 +227,11 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat
     gl.viewport(0, 0, dx, dy);
     gl.scissor(0, 0, dx, dy);
 
-    if (!texture) texture = colorBufferFloat && textureFloat
-        ? resources.texture('volume-float32', 'rgba', 'float', 'linear')
-        : resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear');
+    if (!texture) texture = colorBufferHalfFloat && textureHalfFloat
+        ? resources.texture('volume-float16', 'rgba', 'fp16', 'linear')
+        : colorBufferFloat && textureFloat
+            ? resources.texture('volume-float32', 'rgba', 'float', 'linear')
+            : resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear');
     texture.define(dx, dy, dz);
 
     function render(fbTex: Texture, clear: boolean) {