Преглед на файлове

support reading of pixels as float32

Alexander Rose преди 6 години
родител
ревизия
68e9fbbb14

+ 3 - 3
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()

+ 1 - 1
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))

+ 1 - 1
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))

+ 3 - 3
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()

+ 1 - 1
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')

+ 1 - 1
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)

+ 1 - 1
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))

+ 2 - 2
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 }
 }
 

+ 18 - 5
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 }
+    }
 }

+ 42 - 12
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),

+ 5 - 0
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)