diff --git a/src/mol-geo/geometry/color-data.ts b/src/mol-geo/geometry/color-data.ts index 19acb4e3396212cbafad013d967fd5ac5eeb1a2c..ca57e2f49accec4851001bb73129cd75ad1e71dc 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 2c49bd1b233b05d234831506791289b26de0ecd6..65d8e59816a2c018f250227b051f72e44c95fa18 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 875bef81b528dd5989f394dbfd4e0cfdf53870bb..a1c74f1273bb7f2b854357062a0bb1390ca45802 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 fb4b94b851027ba336703e18d8502f2b34c6338b..9fdc122f25e83585c983a3c0552bb5af6012d111 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 0ad781f2bd77edd0ee34462da0aa2199c9546ec4..46f11f633197e941f93173e9aba398bb5a100562 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 58e11502cef2e2a24ffa86e2bdf4d7f05a094754..0323d397ded64e55b7fb0c7ffd4880f93252bd4d 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 65e2d17c2d85aa0b9509739f0da84bc842bc2656..3b19b6130f9b6dff272c9dd7617c5ae8cb3ad98b 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 32bf2ecbd4dd47e7cdb6351e71f8da4a595c47f7..5c768690c85d276809d48647916c7166a511ead2 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 7d763614ea33e22cf2c1c5ec32b8d5b85fb64ce2..70c2b8dfbdffea438eaf0d8a4ce7f513a1018900 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 375a1dd75037b3b65c9a87700de41b038acd5c1d..3f8c5af5aaf37e3393d778e071f1f5e7e7406792 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 40edd4e72b5fe857f0f945a9ff2bface3bc78707..886204c7aba358c3824bd7e8badcdc3cd8cee817 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)