From 68e9fbbb14281663b2ed2a985aac08d127fe4dcb Mon Sep 17 00:00:00 2001 From: Alexander Rose <alex.rose@rcsb.org> Date: Sat, 30 Mar 2019 18:39:52 -0700 Subject: [PATCH] support reading of pixels as float32 --- src/mol-geo/geometry/color-data.ts | 6 +-- src/mol-geo/geometry/marker-data.ts | 2 +- src/mol-geo/geometry/overpaint-data.ts | 2 +- src/mol-geo/geometry/size-data.ts | 6 +-- src/mol-geo/geometry/text/font-atlas.ts | 2 +- src/mol-geo/geometry/text/text.ts | 2 +- src/mol-geo/geometry/transparency-data.ts | 2 +- src/mol-gl/renderable/util.ts | 4 +- src/mol-gl/webgl/compat.ts | 23 +++++++--- src/mol-gl/webgl/context.ts | 54 ++++++++++++++++++----- src/mol-gl/webgl/texture.ts | 5 +++ 11 files changed, 78 insertions(+), 30 deletions(-) diff --git a/src/mol-geo/geometry/color-data.ts b/src/mol-geo/geometry/color-data.ts index 19acb4e33..ca57e2f49 100644 --- a/src/mol-geo/geometry/color-data.ts +++ b/src/mol-geo/geometry/color-data.ts @@ -74,7 +74,7 @@ export function createTextureColor(colors: TextureImage<Uint8Array>, type: Color /** Creates color texture with color for each instance/unit */ export function createInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { const { instanceCount } = locationIt - const colors = createTextureImage(Math.max(1, instanceCount), 3, colorData && colorData.tColor.ref.value.array) + const colors = createTextureImage(Math.max(1, instanceCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array) locationIt.reset() while (locationIt.hasNext) { const { location, isSecondary, instanceIndex } = locationIt.move() @@ -87,7 +87,7 @@ export function createInstanceColor(locationIt: LocationIterator, color: Locatio /** Creates color texture with color for each group (i.e. shared across instances/units) */ export function createGroupColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { const { groupCount } = locationIt - const colors = createTextureImage(Math.max(1, groupCount), 3, colorData && colorData.tColor.ref.value.array) + const colors = createTextureImage(Math.max(1, groupCount), 3, Uint8Array, colorData && colorData.tColor.ref.value.array) locationIt.reset() while (locationIt.hasNext && !locationIt.isNextNewInstance) { const { location, isSecondary, groupIndex } = locationIt.move() @@ -100,7 +100,7 @@ export function createGroupColor(locationIt: LocationIterator, color: LocationCo export function createGroupInstanceColor(locationIt: LocationIterator, color: LocationColor, colorData?: ColorData): ColorData { const { groupCount, instanceCount } = locationIt const count = instanceCount * groupCount - const colors = createTextureImage(Math.max(1, count), 3, colorData && colorData.tColor.ref.value.array) + const colors = createTextureImage(Math.max(1, count), 3, Uint8Array, colorData && colorData.tColor.ref.value.array) locationIt.reset() while (locationIt.hasNext) { const { location, isSecondary, index } = locationIt.move() diff --git a/src/mol-geo/geometry/marker-data.ts b/src/mol-geo/geometry/marker-data.ts index 2c49bd1b2..65d8e5981 100644 --- a/src/mol-geo/geometry/marker-data.ts +++ b/src/mol-geo/geometry/marker-data.ts @@ -65,7 +65,7 @@ export function applyMarkerAction(array: Uint8Array, start: number, end: number, } export function createMarkers(count: number, markerData?: MarkerData): MarkerData { - const markers = createTextureImage(Math.max(1, count), 1, markerData && markerData.tMarker.ref.value.array) + const markers = createTextureImage(Math.max(1, count), 1, Uint8Array, markerData && markerData.tMarker.ref.value.array) if (markerData) { ValueCell.update(markerData.tMarker, markers) ValueCell.update(markerData.uMarkerTexDim, Vec2.create(markers.width, markers.height)) diff --git a/src/mol-geo/geometry/overpaint-data.ts b/src/mol-geo/geometry/overpaint-data.ts index 875bef81b..a1c74f127 100644 --- a/src/mol-geo/geometry/overpaint-data.ts +++ b/src/mol-geo/geometry/overpaint-data.ts @@ -28,7 +28,7 @@ export function clearOverpaint(array: Uint8Array, start: number, end: number) { } export function createOverpaint(count: number, overpaintData?: OverpaintData): OverpaintData { - const overpaint = createTextureImage(Math.max(1, count), 4, overpaintData && overpaintData.tOverpaint.ref.value.array) + const overpaint = createTextureImage(Math.max(1, count), 4, Uint8Array, overpaintData && overpaintData.tOverpaint.ref.value.array) if (overpaintData) { ValueCell.update(overpaintData.tOverpaint, overpaint) ValueCell.update(overpaintData.uOverpaintTexDim, Vec2.create(overpaint.width, overpaint.height)) diff --git a/src/mol-geo/geometry/size-data.ts b/src/mol-geo/geometry/size-data.ts index fb4b94b85..9fdc122f2 100644 --- a/src/mol-geo/geometry/size-data.ts +++ b/src/mol-geo/geometry/size-data.ts @@ -101,7 +101,7 @@ export function createTextureSize(sizes: TextureImage<Uint8Array>, type: SizeTyp /** Creates size texture with size for each instance/unit */ export function createInstanceSize(locationIt: LocationIterator, sizeFn: LocationSize, sizeData?: SizeData): SizeData { const { instanceCount} = locationIt - const sizes = createTextureImage(Math.max(1, instanceCount), 1, sizeData && sizeData.tSize.ref.value.array) + const sizes = createTextureImage(Math.max(1, instanceCount), 1, Uint8Array, sizeData && sizeData.tSize.ref.value.array) locationIt.reset() while (locationIt.hasNext && !locationIt.isNextNewInstance) { const v = locationIt.move() @@ -114,7 +114,7 @@ export function createInstanceSize(locationIt: LocationIterator, sizeFn: Locatio /** Creates size texture with size for each group (i.e. shared across instances/units) */ export function createGroupSize(locationIt: LocationIterator, sizeFn: LocationSize, sizeData?: SizeData): SizeData { const { groupCount } = locationIt - const sizes = createTextureImage(Math.max(1, groupCount), 1, sizeData && sizeData.tSize.ref.value.array) + const sizes = createTextureImage(Math.max(1, groupCount), 1, Uint8Array, sizeData && sizeData.tSize.ref.value.array) locationIt.reset() while (locationIt.hasNext && !locationIt.isNextNewInstance) { const v = locationIt.move() @@ -127,7 +127,7 @@ export function createGroupSize(locationIt: LocationIterator, sizeFn: LocationSi export function createGroupInstanceSize(locationIt: LocationIterator, sizeFn: LocationSize, sizeData?: SizeData): SizeData { const { groupCount, instanceCount } = locationIt const count = instanceCount * groupCount - const sizes = createTextureImage(Math.max(1, count), 1, sizeData && sizeData.tSize.ref.value.array) + const sizes = createTextureImage(Math.max(1, count), 1, Uint8Array, sizeData && sizeData.tSize.ref.value.array) locationIt.reset() while (locationIt.hasNext && !locationIt.isNextNewInstance) { const v = locationIt.move() diff --git a/src/mol-geo/geometry/text/font-atlas.ts b/src/mol-geo/geometry/text/font-atlas.ts index 0ad781f2b..46f11f633 100644 --- a/src/mol-geo/geometry/text/font-atlas.ts +++ b/src/mol-geo/geometry/text/font-atlas.ts @@ -81,7 +81,7 @@ export class FontAtlas { this.maxWidth = Math.round(this.lineHeight * 0.75) // create texture (for ~350 characters) - this.texture = createTextureImage(350 * this.lineHeight * this.maxWidth, 1) + this.texture = createTextureImage(350 * this.lineHeight * this.maxWidth, 1, Uint8Array) // prepare scratch canvas this.scratchCanvas = document.createElement('canvas') diff --git a/src/mol-geo/geometry/text/text.ts b/src/mol-geo/geometry/text/text.ts index 58e11502c..0323d397d 100644 --- a/src/mol-geo/geometry/text/text.ts +++ b/src/mol-geo/geometry/text/text.ts @@ -58,7 +58,7 @@ export interface Text { export namespace Text { export function createEmpty(text?: Text): Text { - const ft = text ? text.fontTexture.ref.value : createTextureImage(0, 1) + const ft = text ? text.fontTexture.ref.value : createTextureImage(0, 1, Uint8Array) const cb = text ? text.centerBuffer.ref.value : new Float32Array(0) const mb = text ? text.mappingBuffer.ref.value : new Float32Array(0) const db = text ? text.depthBuffer.ref.value : new Float32Array(0) diff --git a/src/mol-geo/geometry/transparency-data.ts b/src/mol-geo/geometry/transparency-data.ts index 65e2d17c2..3b19b6130 100644 --- a/src/mol-geo/geometry/transparency-data.ts +++ b/src/mol-geo/geometry/transparency-data.ts @@ -28,7 +28,7 @@ export function clearTransparency(array: Uint8Array, start: number, end: number) } export function createTransparency(count: number, variant: Transparency.Variant, transparencyData?: TransparencyData): TransparencyData { - const transparency = createTextureImage(Math.max(1, count), 1, transparencyData && transparencyData.tTransparency.ref.value.array) + const transparency = createTextureImage(Math.max(1, count), 1, Uint8Array, transparencyData && transparencyData.tTransparency.ref.value.array) if (transparencyData) { ValueCell.update(transparencyData.tTransparency, transparency) ValueCell.update(transparencyData.uTransparencyTexDim, Vec2.create(transparency.width, transparency.height)) diff --git a/src/mol-gl/renderable/util.ts b/src/mol-gl/renderable/util.ts index 32bf2ecbd..5c768690c 100644 --- a/src/mol-gl/renderable/util.ts +++ b/src/mol-gl/renderable/util.ts @@ -29,9 +29,9 @@ export interface TextureVolume<T extends Uint8Array | Float32Array> { readonly depth: number } -export function createTextureImage(n: number, itemSize: number, array?: Uint8Array): TextureImage<Uint8Array> { +export function createTextureImage<T extends Uint8Array | Float32Array>(n: number, itemSize: number, arrayCtor: new (length: number) => T, array?: T): TextureImage<T> { const { length, width, height } = calculateTextureInfo(n, itemSize) - array = array && array.length >= length ? array : new Uint8Array(length) + array = array && array.length >= length ? array : new arrayCtor(length) return { array, width, height } } diff --git a/src/mol-gl/webgl/compat.ts b/src/mol-gl/webgl/compat.ts index 7d763614e..70c2b8dfb 100644 --- a/src/mol-gl/webgl/compat.ts +++ b/src/mol-gl/webgl/compat.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -50,9 +50,7 @@ export function getStandardDerivatives(gl: GLRenderingContext): COMPAT_standard_ return { FRAGMENT_SHADER_DERIVATIVE_HINT: gl.FRAGMENT_SHADER_DERIVATIVE_HINT } } else { const ext = gl.getExtension('OES_standard_derivatives') - if (ext === null) { - throw new Error('Could not get "OES_standard_derivatives" extension') - } + if (ext === null) return null return { FRAGMENT_SHADER_DERIVATIVE_HINT: ext.FRAGMENT_SHADER_DERIVATIVE_HINT_OES } } } @@ -79,7 +77,7 @@ export function getVertexArrayObject(gl: GLRenderingContext): COMPAT_vertex_arra bindVertexArray: gl.bindVertexArray.bind(gl), createVertexArray: gl.createVertexArray.bind(gl), deleteVertexArray: gl.deleteVertexArray.bind(gl), - isVertexArray: gl.isVertexArray.bind(gl) as (value: any) => value is WebGLVertexArrayObject // TODO change when webgl2 types are fixed + isVertexArray: gl.isVertexArray.bind(gl) } } else { const ext = gl.getExtension('OES_vertex_array_object') @@ -128,4 +126,19 @@ export interface COMPAT_frag_depth { export function getFragDepth(gl: GLRenderingContext): COMPAT_frag_depth | null { return isWebGL2(gl) ? {} : gl.getExtension('EXT_frag_depth') +} + +export interface COMPAT_color_buffer_float { + readonly RGBA32F: number; +} + +export function getColorBufferFloat(gl: GLRenderingContext): COMPAT_color_buffer_float | null { + if (isWebGL2(gl)) { + if (gl.getExtension('EXT_color_buffer_float') === null) return null + return { RGBA32F: gl.RGBA32F } + } else { + const ext = gl.getExtension('WEBGL_color_buffer_float') + if (ext === null) return null + return { RGBA32F: ext.RGBA32F_EXT } + } } \ No newline at end of file diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index 375a1dd75..3f8c5af5a 100644 --- a/src/mol-gl/webgl/context.ts +++ b/src/mol-gl/webgl/context.ts @@ -6,9 +6,10 @@ import { createProgramCache, ProgramCache } from './program' import { createShaderCache, ShaderCache } from './shader' -import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, isWebGL2, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth } from './compat'; +import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, isWebGL2, 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 } from './compat'; import { createFramebufferCache, FramebufferCache } from './framebuffer'; import { Scheduler } from 'mol-task'; +import { isProductionMode } from 'mol-util/debug'; export function getGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes): GLRenderingContext | null { function getContext(contextId: 'webgl' | 'experimental-webgl' | 'webgl2') { @@ -25,6 +26,19 @@ function getPixelRatio() { return (typeof window !== 'undefined') ? window.devicePixelRatio : 1 } +function getErrorDescription(gl: GLRenderingContext, error: number) { + switch (error) { + case gl.NO_ERROR: return 'no error' + case gl.INVALID_ENUM: return 'invalid enum' + case gl.INVALID_VALUE: return 'invalid value' + case gl.INVALID_OPERATION: return 'invalid operation' + case gl.INVALID_FRAMEBUFFER_OPERATION: return 'invalid framebuffer operation' + case gl.OUT_OF_MEMORY: return 'out of memory' + case gl.CONTEXT_LOST_WEBGL: return 'context lost' + } + return 'unknown error' +} + function unbindResources (gl: GLRenderingContext) { // bind null to all texture units const maxTextureImageUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) @@ -96,6 +110,22 @@ function waitForGpuCommandsCompleteSync(gl: GLRenderingContext): void { gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel) } +function readPixels(gl: GLRenderingContext, x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array) { + if (!isProductionMode && gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) { + console.error('Reading pixels failed. Framebuffer not complete.') + return + } + if (buffer instanceof Uint8Array) { + gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer) + } else { + gl.readPixels(x, y, width, height, gl.RGBA, gl.FLOAT, buffer) + } + if (!isProductionMode) { + const error = gl.getError() + if (error) console.log(`Error reading pixels: '${getErrorDescription(gl, error)}'`) + } +} + export function createImageData(buffer: ArrayLike<number>, width: number, height: number) { const w = width * 4 const h = height @@ -122,6 +152,7 @@ export type WebGLExtensions = { elementIndexUint: COMPAT_element_index_uint | null vertexArrayObject: COMPAT_vertex_array_object | null fragDepth: COMPAT_frag_depth | null + colorBufferFloat: COMPAT_color_buffer_float | null } export type WebGLStats = { @@ -159,7 +190,7 @@ export interface WebGLContext { readonly maxDrawBuffers: number unbindFramebuffer: () => void - readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => 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> waitForGpuCommandsComplete: () => Promise<void> waitForGpuCommandsCompleteSync: () => void @@ -199,6 +230,10 @@ export function createContext(gl: GLRenderingContext): WebGLContext { if (fragDepth === null) { console.log('Could not find support for "frag_depth"') } + const colorBufferFloat = getColorBufferFloat(gl) + if (colorBufferFloat === null) { + console.log('Could not find support for "color_buffer_float"') + } const state: WebGLState = { currentProgramId: -1, @@ -225,7 +260,8 @@ export function createContext(gl: GLRenderingContext): WebGLContext { textureFloatLinear, elementIndexUint, vertexArrayObject, - fragDepth + fragDepth, + colorBufferFloat } const shaderCache: ShaderCache = createShaderCache(gl) @@ -275,7 +311,7 @@ export function createContext(gl: GLRenderingContext): WebGLContext { }) } else { readPixelsAsync = async (x: number, y: number, width: number, height: number, buffer: Uint8Array) => { - gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer) + readPixels(gl, x, y, width, height, buffer) } } @@ -299,14 +335,8 @@ export function createContext(gl: GLRenderingContext): WebGLContext { get maxDrawBuffers () { return parameters.maxDrawBuffers }, unbindFramebuffer: () => unbindFramebuffer(gl), - readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => { - gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer) - // TODO check is very expensive - // if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE) { - // gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer) - // } else { - // console.error('Reading pixels failed. Framebuffer not complete.') - // } + readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array | Float32Array) => { + readPixels(gl, x, y, width, height, buffer) }, readPixelsAsync, waitForGpuCommandsComplete: () => waitForGpuCommandsComplete(gl), diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts index 40edd4e72..886204c7a 100644 --- a/src/mol-gl/webgl/texture.ts +++ b/src/mol-gl/webgl/texture.ts @@ -149,6 +149,11 @@ export function createTexture(ctx: WebGLContext, kind: TextureKind, _format: Tex throw new Error('Could not create WebGL texture') } + // check texture kind and type compatability + if ((kind.endsWith('float32') && _type !== 'float') || kind.endsWith('uint8') && _type !== 'ubyte') { + throw new Error(`texture kind '${kind}' and type '${_type}' are incompatible`) + } + const target = getTarget(ctx, kind) const filter = getFilter(ctx, _filter) const format = getFormat(ctx, _format) -- GitLab