diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 1751c4c77ac72572360ff4af1cbe6c041509f532..0b3774e13bf3db05887c1d6f958d7831ac65892e 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -248,9 +248,9 @@ namespace Canvas3D { controls.update(currentTime); Viewport.set(camera.viewport, 0, 0, width, height); const cameraChanged = camera.update(); - multiSample.update(force || cameraChanged, currentTime); + const multiSampleChanged = multiSample.update(force || cameraChanged); - if (force || cameraChanged || multiSample.enabled) { + if (force || cameraChanged || multiSampleChanged) { renderer.setViewport(0, 0, width, height); if (multiSample.enabled) { multiSample.render(true, p.transparentBackground); diff --git a/src/mol-canvas3d/helper/camera-helper.ts b/src/mol-canvas3d/helper/camera-helper.ts index a51ad3aa8d6d355375b34d8b9ddc26a6184d6bf7..7b5d405210fe1b070a8810ddace253a0cb3eda27 100644 --- a/src/mol-canvas3d/helper/camera-helper.ts +++ b/src/mol-canvas3d/helper/camera-helper.ts @@ -89,7 +89,7 @@ export class CameraHelper { update(camera: Camera) { if (!this.renderObject) return; - updateCamera(this.camera, camera.viewport); + updateCamera(this.camera, camera.viewport, camera.viewOffset); Mat4.extractRotation(this.scene.view, camera.view); const r = this.renderObject.values.boundingSphere.ref.value.radius; @@ -101,23 +101,32 @@ export class CameraHelper { } } -function updateCamera(camera: Camera, viewport: Viewport) { +function updateCamera(camera: Camera, viewport: Viewport, viewOffset: Camera.ViewOffset) { const { near, far } = camera; - const fullLeft = -(viewport.width - viewport.x) / 2; - const fullRight = (viewport.width - viewport.x) / 2; - const fullTop = (viewport.height - viewport.y) / 2; - const fullBottom = -(viewport.height - viewport.y) / 2; + const fullLeft = -viewport.width / 2; + const fullRight = viewport.width / 2; + const fullTop = viewport.height / 2; + const fullBottom = -viewport.height / 2; const dx = (fullRight - fullLeft) / 2; const dy = (fullTop - fullBottom) / 2; const cx = (fullRight + fullLeft) / 2; const cy = (fullTop + fullBottom) / 2; - const left = cx - dx; - const right = cx + dx; - const top = cy + dy; - const bottom = cy - dy; + let left = cx - dx; + let right = cx + dx; + let top = cy + dy; + let bottom = cy - dy; + + if (viewOffset.enabled) { + const scaleW = (fullRight - fullLeft) / viewOffset.width; + const scaleH = (fullTop - fullBottom) / viewOffset.height; + left += scaleW * viewOffset.offsetX; + right = left + scaleW * viewOffset.width; + top -= scaleH * viewOffset.offsetY; + bottom = top - scaleH * viewOffset.height; + } Mat4.ortho(camera.projection, left, right, top, bottom, near, far); } diff --git a/src/mol-canvas3d/passes/image.ts b/src/mol-canvas3d/passes/image.ts index 516b488a59a6227e061cb724471f741a13649261..41d9b1432e6917f20ed9b63cfd38dd1646588663 100644 --- a/src/mol-canvas3d/passes/image.ts +++ b/src/mol-canvas3d/passes/image.ts @@ -16,6 +16,7 @@ import { MultiSamplePass, MultiSampleParams } from './multi-sample'; import { Camera } from '../camera'; import { Viewport } from '../camera/util'; import { HandleHelper } from '../helper/handle-helper'; +import { PixelData } from '../../mol-util/image'; export const ImageParams = { transparentBackground: PD.Boolean(false), @@ -41,7 +42,7 @@ export class ImagePass { get width() { return this._width; } get height() { return this._height; } - constructor(webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, handleHelper: HandleHelper, props: Partial<ImageProps>) { + constructor(private webgl: WebGLContext, private renderer: Renderer, scene: Scene, private camera: Camera, debugHelper: BoundingSphereHelper, handleHelper: HandleHelper, props: Partial<ImageProps>) { const p = { ...PD.getDefaultValues(ImageParams), ...props }; this._transparentBackground = p.transparentBackground; @@ -104,7 +105,10 @@ export class ImagePass { getImageData(width: number, height: number) { this.setSize(width, height); this.render(); - const pd = this.colorTarget.getPixelData(); - return new ImageData(new Uint8ClampedArray(pd.array), pd.width, pd.height); + this.colorTarget.bind(); + const array = new Uint8Array(width * height * 4); + this.webgl.readPixels(0, 0, width, height, array); + PixelData.flipY({ array, width, height }); + return new ImageData(new Uint8ClampedArray(array), width, height); } } \ No newline at end of file diff --git a/src/mol-canvas3d/passes/multi-sample.ts b/src/mol-canvas3d/passes/multi-sample.ts index 35e9874d6a6fe165d856a3c35be88c044a0781d2..f4000bf861bcc3d98d0ea13839519e9782beb7ae 100644 --- a/src/mol-canvas3d/passes/multi-sample.ts +++ b/src/mol-canvas3d/passes/multi-sample.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ @@ -60,37 +60,24 @@ export class MultiSamplePass { private holdTarget: RenderTarget private compose: ComposeRenderable - private sampleIndex = -1 - private currentTime = 0 - private lastRenderTime = 0 + private sampleIndex = -2 constructor(private webgl: WebGLContext, private camera: Camera, private drawPass: DrawPass, private postprocessing: PostprocessingPass, props: Partial<MultiSampleProps>) { - const { gl } = webgl; - this.colorTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight); - this.composeTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight); - this.holdTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight); + const { gl, extensions } = webgl; + this.colorTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight, false); + this.composeTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight, false, extensions.colorBufferFloat ? 'float32' : 'uint8'); + this.holdTarget = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight, false); this.compose = getComposeRenderable(webgl, drawPass.colorTarget.texture); this.props = { ...PD.getDefaultValues(MultiSampleParams), ...props }; } get enabled() { - if (this.props.mode === 'temporal') { - if (this.currentTime - this.lastRenderTime > 200) { - return this.sampleIndex !== -1; - } else { - this.sampleIndex = 0; - return false; - } - } else if (this.props.mode === 'on') { - return true; - } else { - return false; - } + return this.props.mode !== 'off'; } - update(changed: boolean, currentTime: number) { - if (changed) this.lastRenderTime = currentTime; - this.currentTime = currentTime; + update(changed: boolean) { + if (changed) this.sampleIndex = -1; + return this.props.mode === 'temporal' ? this.sampleIndex !== -2 : false; } setSize(width: number, height: number) { @@ -140,6 +127,7 @@ export class MultiSamplePass { const offset = offsetList[i]; Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height); camera.update(); + this.drawPass.cameraHelper.update(camera); // the theory is that equal weights for each sample lead to an accumulation of rounding // errors. The following equation varies the sampleWeight per sample so that it is uniformly @@ -196,15 +184,17 @@ export class MultiSamplePass { // each sample with camera jitter and accumulates the results. const offsetList = JitterVectors[ Math.max(0, Math.min(this.props.sampleLevel, 5)) ]; - if (this.sampleIndex === -1) return; + if (this.sampleIndex === -2) return; if (this.sampleIndex >= offsetList.length) { - this.sampleIndex = -1; + this.sampleIndex = -2; return; } - const i = this.sampleIndex; + const width = drawPass.colorTarget.getWidth(); + const height = drawPass.colorTarget.getHeight(); + const sampleWeight = 1.0 / offsetList.length; - if (i === 0) { + if (this.sampleIndex === -1) { drawPass.render(false, transparentBackground); if (postprocessing.enabled) postprocessing.render(false); ValueCell.update(compose.values.uWeight, 1.0); @@ -214,59 +204,57 @@ export class MultiSamplePass { holdTarget.bind(); state.disable(gl.BLEND); compose.render(); - } - - const sampleWeight = 1.0 / offsetList.length; - - camera.viewOffset.enabled = true; - ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture); - ValueCell.update(compose.values.uWeight, sampleWeight); - compose.update(); - - const width = drawPass.colorTarget.getWidth(); - const height = drawPass.colorTarget.getHeight(); - - // render the scene multiple times, each slightly jitter offset - // from the last and accumulate the results. - const numSamplesPerFrame = Math.pow(2, this.props.sampleLevel); - for (let i = 0; i < numSamplesPerFrame; ++i) { - const offset = offsetList[this.sampleIndex]; - Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height); - camera.update(); - - // render scene and optionally postprocess - drawPass.render(false, transparentBackground); - if (postprocessing.enabled) postprocessing.render(false); + this.sampleIndex += 1; + } else { + camera.viewOffset.enabled = true; + ValueCell.update(compose.values.tColor, postprocessing.enabled ? postprocessing.target.texture : drawPass.colorTarget.texture); + ValueCell.update(compose.values.uWeight, sampleWeight); + compose.update(); - // compose rendered scene with compose target - composeTarget.bind(); - state.enable(gl.BLEND); - state.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD); - state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE); - state.disable(gl.DEPTH_TEST); - state.disable(gl.SCISSOR_TEST); - state.depthMask(false); - if (this.sampleIndex === 0) { - state.clearColor(0, 0, 0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); + // render the scene multiple times, each slightly jitter offset + // from the last and accumulate the results. + const numSamplesPerFrame = Math.pow(2, Math.max(0, this.props.sampleLevel - 2)); + for (let i = 0; i < numSamplesPerFrame; ++i) { + const offset = offsetList[this.sampleIndex]; + Camera.setViewOffset(camera.viewOffset, width, height, offset[0], offset[1], width, height); + camera.update(); + this.drawPass.cameraHelper.update(camera); + + // render scene and optionally postprocess + drawPass.render(false, transparentBackground); + if (postprocessing.enabled) postprocessing.render(false); + + // compose rendered scene with compose target + composeTarget.bind(); + state.enable(gl.BLEND); + state.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD); + state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE); + state.disable(gl.DEPTH_TEST); + state.disable(gl.SCISSOR_TEST); + state.depthMask(false); + if (this.sampleIndex === 0) { + state.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + } + compose.render(); + + this.sampleIndex += 1; + if (this.sampleIndex >= offsetList.length ) break; } - compose.render(); + } - this.sampleIndex += 1; - if (this.sampleIndex >= offsetList.length ) break; + if (toDrawingBuffer) { + webgl.unbindFramebuffer(); + } else { + this.colorTarget.bind(); } + gl.viewport(0, 0, width, height); const accumulationWeight = this.sampleIndex * sampleWeight; if (accumulationWeight > 0) { ValueCell.update(compose.values.uWeight, 1.0); ValueCell.update(compose.values.tColor, composeTarget.texture); compose.update(); - if (toDrawingBuffer) { - webgl.unbindFramebuffer(); - } else { - this.colorTarget.bind(); - } - gl.viewport(0, 0, width, height); state.disable(gl.BLEND); compose.render(); } @@ -274,12 +262,6 @@ export class MultiSamplePass { ValueCell.update(compose.values.uWeight, 1.0 - accumulationWeight); ValueCell.update(compose.values.tColor, holdTarget.texture); compose.update(); - if (toDrawingBuffer) { - webgl.unbindFramebuffer(); - } else { - this.colorTarget.bind(); - } - gl.viewport(0, 0, width, height); if (accumulationWeight === 0) state.disable(gl.BLEND); else state.enable(gl.BLEND); compose.render(); @@ -287,7 +269,7 @@ export class MultiSamplePass { camera.viewOffset.enabled = false; camera.update(); - if (this.sampleIndex >= offsetList.length) this.sampleIndex = -1; + if (this.sampleIndex >= offsetList.length) this.sampleIndex = -2; } } diff --git a/src/mol-canvas3d/passes/postprocessing.ts b/src/mol-canvas3d/passes/postprocessing.ts index fb4a34378ac2a1db78c2964ae720a52c9ddcf122..6d45c98f65257892b2bd186522803b2d52b6faf1 100644 --- a/src/mol-canvas3d/passes/postprocessing.ts +++ b/src/mol-canvas3d/passes/postprocessing.ts @@ -105,7 +105,7 @@ export class PostprocessingPass { constructor(private webgl: WebGLContext, private camera: Camera, drawPass: DrawPass, props: Partial<PostprocessingProps>) { const { gl } = webgl; - this.target = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight); + this.target = webgl.createRenderTarget(gl.drawingBufferWidth, gl.drawingBufferHeight, false); this.props = { ...PD.getDefaultValues(PostprocessingParams), ...props }; const { colorTarget, depthTexture, packedDepth } = drawPass; this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture, packedDepth, this.props); diff --git a/src/mol-gl/webgl/compat.ts b/src/mol-gl/webgl/compat.ts index 8bae6250a270285c0e92a46779c8622cc5148bf4..2d0cf14252be40a3e292fa39edbaaa79909f65b0 100644 --- a/src/mol-gl/webgl/compat.ts +++ b/src/mol-gl/webgl/compat.ts @@ -1,9 +1,14 @@ +import { isDebugMode } from '../../mol-util/debug'; /** - * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ +import { getErrorDescription, getGLContext } from './context'; +import { getProgram } from './program'; +import { getShader } from './shader'; + export type GLRenderingContext = WebGLRenderingContext | WebGL2RenderingContext export function isWebGL(gl: any): gl is WebGLRenderingContext { @@ -139,7 +144,10 @@ export function getColorBufferFloat(gl: GLRenderingContext): COMPAT_color_buffer return { RGBA32F: gl.RGBA32F }; } else { const ext = gl.getExtension('WEBGL_color_buffer_float'); - if (ext === null) return null; + if (ext === null) { + // test as support may not be advertised by browsers + return testColorBufferFloat() ? { RGBA32F: 0x8814 } : null; + } gl.getExtension('EXT_float_blend'); return { RGBA32F: ext.RGBA32F_EXT }; } @@ -267,4 +275,131 @@ export function getSRGB(gl: GLRenderingContext): COMPAT_sRGB | null { SRGB: ext.SRGB_EXT }; } -} \ No newline at end of file +} + +// + +const TextureTestVertShader = ` +attribute vec4 aPosition; + +void main() { + gl_Position = aPosition; +}`; + +const TextureTestFragShader = ` +precision mediump float; +uniform vec4 uColor; +uniform sampler2D uTexture; + +void main() { + gl_FragColor = texture2D(uTexture, vec2(0.5, 0.5)) * uColor; +}`; + +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() { + // adapted from https://stackoverflow.com/questions/28827511/ + + // Get A WebGL context + const canvas = document.createElement('canvas'); + canvas.width = 16; + canvas.height = 16; + canvas.style.width = `${16}px`; + canvas.style.height = `${16}px`; + const gl = getGLContext(canvas); + if (gl === null) throw new Error('Unable to get WebGL context'); + + const type = gl.FLOAT; + gl.getExtension('OES_texture_float'); + + // setup shaders + const vertShader = getShader(gl, { type: 'vert', source: TextureTestVertShader }); + const fragShader = getShader(gl, { type: 'frag', source: TextureTestFragShader }); + if (!vertShader || !fragShader) return false; + + // setup program + const program = getProgram(gl); + gl.attachShader(program, vertShader); + gl.attachShader(program, fragShader); + gl.linkProgram(program); + gl.useProgram(program); + + // look up where the vertex data needs to go. + const positionLocation = gl.getAttribLocation(program, 'aPosition'); + const colorLoc = gl.getUniformLocation(program, 'uColor'); + if (!colorLoc) { + if (isDebugMode) { + console.log(`error getting 'uColor' uniform location`); + } + return false; + } + + // provide texture coordinates for the rectangle. + const positionBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); + gl.bufferData(gl.ARRAY_BUFFER, TextureTestTexCoords, gl.STATIC_DRAW); + gl.enableVertexAttribArray(positionLocation); + gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0); + + const whiteTex = gl.createTexture(); + const whiteData = new Uint8Array([255, 255, 255, 255]); + gl.bindTexture(gl.TEXTURE_2D, whiteTex); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, whiteData); + + const tex = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, type, null); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST); + + const fb = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, fb); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0); + const status = gl.checkFramebufferStatus(gl.FRAMEBUFFER); + if (status !== gl.FRAMEBUFFER_COMPLETE) { + if (isDebugMode) { + console.log(`error creating framebuffer for '${type}'`); + } + return false; + } + + // Draw the rectangle. + gl.bindTexture(gl.TEXTURE_2D, whiteTex); + gl.uniform4fv(colorLoc, [0, 10, 20, 1]); + gl.drawArrays(gl.TRIANGLES, 0, 6); + + gl.bindTexture(gl.TEXTURE_2D, tex); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + gl.clearColor(1, 0, 0, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.uniform4fv(colorLoc, [0, 1 / 10, 1 / 20, 1]); + gl.drawArrays(gl.TRIANGLES, 0, 6); + + // Check if rendered correctly + const pixel = new Uint8Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel); + if (pixel[0] !== 0 || pixel[1] < 248 || pixel[2] < 248 || pixel[3] < 254) { + if (isDebugMode) { + console.log(`not able to actually render to '${type}' texture`); + } + return false; + } + + // Check reading from float texture + if (type === gl.FLOAT) { + gl.bindFramebuffer(gl.FRAMEBUFFER, fb); + const floatPixel = new Float32Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.FLOAT, floatPixel); + const error = gl.getError(); + if (error) { + if (isDebugMode) { + console.log(`error reading float pixels: '${getErrorDescription(gl, error)}'`); + } + return false; + } + } + + return true; +} diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index cfc6a8720cf8b4b535af5ce9969df7fce997fd42..a2a8382e6771f705e143e72ca9efa4d739c0f0fd 100644 --- a/src/mol-gl/webgl/context.ts +++ b/src/mol-gl/webgl/context.ts @@ -15,6 +15,7 @@ import { WebGLResources, createResources } from './resources'; import { RenderTarget, createRenderTarget } from './render-target'; import { BehaviorSubject } from 'rxjs'; import { now } from '../../mol-util/now'; +import { TextureFilter } from './texture'; export function getGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes): GLRenderingContext | null { function getContext(contextId: 'webgl' | 'experimental-webgl' | 'webgl2') { @@ -31,7 +32,7 @@ function getPixelRatio() { return (typeof window !== 'undefined') ? window.devicePixelRatio : 1; } -function getErrorDescription(gl: GLRenderingContext, error: number) { +export function getErrorDescription(gl: GLRenderingContext, error: number) { switch (error) { case gl.NO_ERROR: return 'no error'; case gl.INVALID_ENUM: return 'invalid enum'; @@ -194,7 +195,7 @@ export interface WebGLContext { setContextLost: () => void handleContextRestored: () => void - createRenderTarget: (width: number, height: number) => RenderTarget + createRenderTarget: (width: number, height: number, depth?: boolean, type?: 'uint8' | 'float32', 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> @@ -302,8 +303,8 @@ export function createContext(gl: GLRenderingContext): WebGLContext { contextRestored.next(now()); }, - createRenderTarget: (width: number, height: number) => { - const renderTarget = createRenderTarget(gl, resources, width, height); + createRenderTarget: (width: number, height: number, depth?: boolean, type?: 'uint8' | 'float32', filter?: TextureFilter) => { + const renderTarget = createRenderTarget(gl, resources, width, height, depth, type, filter); renderTargets.add(renderTarget); return { ...renderTarget, diff --git a/src/mol-gl/webgl/extensions.ts b/src/mol-gl/webgl/extensions.ts index 2de4bceb07cbfe04047ba727db141d2db4e4db20..df056ddb8c64d5fc52a97b765c0d07e7f8cc3c19 100644 --- a/src/mol-gl/webgl/extensions.ts +++ b/src/mol-gl/webgl/extensions.ts @@ -9,10 +9,10 @@ import { isDebugMode } from '../../mol-util/debug'; export type WebGLExtensions = { instancedArrays: COMPAT_instanced_arrays - textureFloat: COMPAT_texture_float elementIndexUint: COMPAT_element_index_uint standardDerivatives: COMPAT_standard_derivatives | null + textureFloat: COMPAT_texture_float | null textureFloatLinear: COMPAT_texture_float_linear | null depthTexture: COMPAT_depth_texture | null blendMinMax: COMPAT_blend_minmax | null @@ -29,10 +29,6 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions { if (instancedArrays === null) { throw new Error('Could not find support for "instanced_arrays"'); } - const textureFloat = getTextureFloat(gl); - if (textureFloat === null) { - throw new Error('Could not find support for "texture_float"'); - } const elementIndexUint = getElementIndexUint(gl); if (elementIndexUint === null) { throw new Error('Could not find support for "element_index_uint"'); @@ -44,6 +40,11 @@ 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 "standard_derivatives"'); } + const textureFloat = getTextureFloat(gl); + if (isDebugMode && textureFloat === null) { + // TODO make sure non-support is handled downstream + console.log('Could not find support for "texture_float"'); + } const textureFloatLinear = getTextureFloatLinear(gl); if (isDebugMode && textureFloatLinear === null) { // TODO handle non-support downstream (no gpu gaussian calc, no gpu mc???) diff --git a/src/mol-gl/webgl/program.ts b/src/mol-gl/webgl/program.ts index 1fc4086b6f9510acde2b074e35df336d2cbb0aa9..065fed789c6e70607e8eef1cb5ae61bca8ccc638 100644 --- a/src/mol-gl/webgl/program.ts +++ b/src/mol-gl/webgl/program.ts @@ -131,7 +131,7 @@ export interface ProgramProps { schema: RenderableSchema } -function getProgram(gl: GLRenderingContext) { +export function getProgram(gl: GLRenderingContext) { const program = gl.createProgram(); if (program === null) { throw new Error('Could not create WebGL program'); diff --git a/src/mol-gl/webgl/render-target.ts b/src/mol-gl/webgl/render-target.ts index 7860728ad012909d78caa9471a1c20e345db10b7..10131809159d87f243d0d5e2edb3eab08544d106 100644 --- a/src/mol-gl/webgl/render-target.ts +++ b/src/mol-gl/webgl/render-target.ts @@ -4,13 +4,9 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import { readPixels } from './context'; import { idFactory } from '../../mol-util/id-factory'; -import { Texture } from './texture'; +import { Texture, TextureFilter } from './texture'; import { Framebuffer } from './framebuffer'; -import { TextureImage } from '../renderable/util'; -import { Mutable } from '../../mol-util/type-helpers'; -import { PixelData } from '../../mol-util/image'; import { WebGLResources } from './resources'; import { GLRenderingContext } from './compat'; @@ -18,7 +14,6 @@ const getNextRenderTargetId = idFactory(); export interface RenderTarget { readonly id: number - readonly image: TextureImage<any> readonly texture: Texture readonly framebuffer: Framebuffer @@ -27,48 +22,32 @@ export interface RenderTarget { /** binds framebuffer and sets viewport to rendertarget's width and height */ bind: () => void setSize: (width: number, height: number) => void - readBuffer: (x: number, y: number, width: number, height: number, dst: Uint8Array) => void - getBuffer: () => Uint8Array - getPixelData: () => PixelData reset: () => void destroy: () => void } -export function createRenderTarget(gl: GLRenderingContext, resources: WebGLResources, _width: number, _height: number): RenderTarget { - const image: Mutable<TextureImage<Uint8Array>> = { - array: new Uint8Array(_width * _height * 4), - width: _width, - height: _height - }; +export function createRenderTarget(gl: GLRenderingContext, resources: WebGLResources, _width: number, _height: number, depth = true, type: 'uint8' | 'float32' = 'uint8', filter: TextureFilter = 'nearest'): RenderTarget { const framebuffer = resources.framebuffer(); - const targetTexture = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear'); + const targetTexture = 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 = resources.renderbuffer('depth16', 'depth', _width, _height); + const depthRenderbuffer = depth + ? resources.renderbuffer('depth16', 'depth', _width, _height) + : null; function init() { - targetTexture.load(image); + targetTexture.define(_width, _height); targetTexture.attachFramebuffer(framebuffer, 'color0'); - depthRenderbuffer.attachFramebuffer(framebuffer); + if (depthRenderbuffer) depthRenderbuffer.attachFramebuffer(framebuffer); } init(); let destroyed = false; - function readBuffer(x: number, y: number, width: number, height: number, dst: Uint8Array) { - framebuffer.bind(); - gl.viewport(0, 0, _width, _height); - readPixels(gl, x, y, width, height, dst); - } - - function getBuffer() { - readBuffer(0, 0, _width, _height, image.array); - return image.array; - } - return { id: getNextRenderTargetId(), - image, texture: targetTexture, framebuffer, @@ -81,15 +60,9 @@ export function createRenderTarget(gl: GLRenderingContext, resources: WebGLResou setSize: (width: number, height: number) => { _width = width; _height = height; - image.array = new Uint8Array(_width * _height * 4); - image.width = _width; - image.height = _height; - targetTexture.load(image); - depthRenderbuffer.setSize(_width, _height); + targetTexture.define(_width, _height); + if (depthRenderbuffer) depthRenderbuffer.setSize(_width, _height); }, - readBuffer, - getBuffer, - getPixelData: () => PixelData.flipY(PixelData.create(getBuffer(), _width, _height)), reset: () => { init(); }, @@ -97,8 +70,8 @@ export function createRenderTarget(gl: GLRenderingContext, resources: WebGLResou if (destroyed) return; targetTexture.destroy(); framebuffer.destroy(); - depthRenderbuffer.destroy(); + if (depthRenderbuffer) depthRenderbuffer.destroy(); destroyed = true; } }; -} \ No newline at end of file +} diff --git a/src/mol-gl/webgl/shader.ts b/src/mol-gl/webgl/shader.ts index 87f719b9d62c40bd2570448d908a258d5b34c29d..e52bc3a37fd4c03ea691c28a45bf7d5962057709 100644 --- a/src/mol-gl/webgl/shader.ts +++ b/src/mol-gl/webgl/shader.ts @@ -27,7 +27,7 @@ export interface Shader { destroy: () => void } -function getShader(gl: GLRenderingContext, props: ShaderProps) { +export function getShader(gl: GLRenderingContext, props: ShaderProps) { const { type, source } = props; const shader = gl.createShader(type === 'vert' ? gl.VERTEX_SHADER : gl.FRAGMENT_SHADER); if (shader === null) {