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) {