diff --git a/src/extensions/alpha-orbitals/gpu/compute.ts b/src/extensions/alpha-orbitals/gpu/compute.ts index cb844f5ebd34fc9fe0b78aa60b64bade6718beb7..fa94690e9fc069164f76be928664d35d5871bf23 100644 --- a/src/extensions/alpha-orbitals/gpu/compute.ts +++ b/src/extensions/alpha-orbitals/gpu/compute.ts @@ -34,7 +34,8 @@ const AlphaOrbitalsSchema = { uMaxCoeffs: UniformSpec('i'), uLittleEndian: UniformSpec('b') }; -const AlphaOrbitalsShaderCode = ShaderCode('postprocessing', quad_vert, shader_frag); +const AlphaOrbitalsName = 'alpha-orbitals'; +const AlphaOrbitalsShaderCode = ShaderCode(AlphaOrbitalsName, quad_vert, shader_frag); type AlphaOrbitalsRenderable = ComputeRenderable<Values<typeof AlphaOrbitalsSchema>> function createTextureData({ @@ -112,7 +113,7 @@ function createTextureData({ return { nCenters: centerCount, nAlpha: baseCount, nCoeff: coeffCount, maxCoeffs, centers, info, alpha, coeff }; } -function getPostprocessingRenderable(ctx: WebGLContext, params: CollocationParams): AlphaOrbitalsRenderable { +function createAlphaOrbitalsRenderable(ctx: WebGLContext, params: CollocationParams): AlphaOrbitalsRenderable { const data = createTextureData(params); const [nx, ny, nz] = params.grid.dimensions; @@ -136,36 +137,65 @@ function getPostprocessingRenderable(ctx: WebGLContext, params: CollocationParam }; const schema = { ...AlphaOrbitalsSchema }; + if (!ctx.isWebGL2) { + // workaround for webgl1 limitation that loop counters need to be `const` + (schema.uNCenters as any) = DefineSpec('number'); + (schema.uMaxCoeffs as any) = DefineSpec('number'); + } + const renderItem = createComputeRenderItem(ctx, 'triangles', AlphaOrbitalsShaderCode, schema, values); return createComputeRenderable(renderItem, values); } -function normalizeParams(webgl: WebGLContext) { - if (!webgl.isWebGL2) { - // workaround for webgl1 limitation that loop counters need to be `const` - (AlphaOrbitalsSchema.uNCenters as any) = DefineSpec('number'); - (AlphaOrbitalsSchema.uMaxCoeffs as any) = DefineSpec('number'); +function getAlphaOrbitalsRenderable(ctx: WebGLContext, params: CollocationParams): AlphaOrbitalsRenderable { + if (ctx.namedComputeRenderables[AlphaOrbitalsName]) { + const v = ctx.namedComputeRenderables[AlphaOrbitalsName].values; + + const data = createTextureData(params); + + const [nx, ny, nz] = params.grid.dimensions; + const width = Math.ceil(Math.sqrt(nx * ny * nz)); + + ValueCell.update(v.uDimensions, params.grid.dimensions); + ValueCell.update(v.uMin, params.grid.box.min); + ValueCell.update(v.uDelta, params.grid.delta); + ValueCell.updateIfChanged(v.uWidth, width); + ValueCell.updateIfChanged(v.uNCenters, data.nCenters); + ValueCell.updateIfChanged(v.uNAlpha, data.nAlpha); + ValueCell.updateIfChanged(v.uNCoeff, data.nCoeff); + ValueCell.updateIfChanged(v.uMaxCoeffs, data.maxCoeffs); + ValueCell.update(v.tCenters, { width: data.nCenters, height: 1, array: data.centers }); + ValueCell.update(v.tInfo, { width: data.nCenters, height: 1, array: data.info }); + ValueCell.update(v.tCoeff, { width: data.nCoeff, height: 1, array: data.coeff }); + ValueCell.update(v.tAlpha, { width: data.nAlpha, height: 1, array: data.alpha }); + ValueCell.updateIfChanged(v.uLittleEndian, isLittleEndian()); + + ctx.namedComputeRenderables[AlphaOrbitalsName].update(); + } else { + ctx.namedComputeRenderables[AlphaOrbitalsName] = createAlphaOrbitalsRenderable(ctx, params); } + return ctx.namedComputeRenderables[AlphaOrbitalsName]; } export function gpuComputeAlphaOrbitalsGridValues(webgl: WebGLContext, params: CollocationParams) { const [nx, ny, nz] = params.grid.dimensions; - normalizeParams(webgl); - - const renderable = getPostprocessingRenderable(webgl, params); + const renderable = getAlphaOrbitalsRenderable(webgl, params); const width = renderable.values.uWidth.ref.value; - if (!webgl.computeTargets['alpha-oribtals']) { - webgl.computeTargets['alpha-oribtals'] = webgl.createRenderTarget(width, width, false, 'uint8', 'nearest'); - } else { - webgl.computeTargets['alpha-oribtals'].setSize(width, width); + if (!webgl.namedFramebuffers[AlphaOrbitalsName]) { + webgl.namedFramebuffers[AlphaOrbitalsName] = webgl.resources.framebuffer(); } + const framebuffer = webgl.namedFramebuffers[AlphaOrbitalsName]; - const target = webgl.computeTargets['alpha-oribtals']; + if (!webgl.namedTextures[AlphaOrbitalsName]) { + webgl.namedTextures[AlphaOrbitalsName] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest'); + } + webgl.namedTextures[AlphaOrbitalsName].define(width, width); + webgl.namedTextures[AlphaOrbitalsName].attachFramebuffer(framebuffer, 'color0'); const { gl, state } = webgl; - target.bind(); + framebuffer.bind(); gl.viewport(0, 0, width, width); gl.scissor(0, 0, width, width); state.disable(gl.SCISSOR_TEST); @@ -176,10 +206,7 @@ export function gpuComputeAlphaOrbitalsGridValues(webgl: WebGLContext, params: C const array = new Uint8Array(width * width * 4); webgl.readPixels(0, 0, width, width, array); - const floats = new Float32Array(array.buffer, array.byteOffset, nx * ny * nz); - renderable.dispose(); - - return floats; + return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz); } export function canComputeAlphaOrbitalsOnGPU(webgl?: WebGLContext) { diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index b2250f3a61cff551dccfba8db2bc90decadc90df..e9303b859574b1cb6826032105360fe21aab5036 100644 --- a/src/mol-gl/webgl/context.ts +++ b/src/mol-gl/webgl/context.ts @@ -5,7 +5,7 @@ */ import { GLRenderingContext, isWebGL2 } from './compat'; -import { checkFramebufferStatus } from './framebuffer'; +import { checkFramebufferStatus, Framebuffer } from './framebuffer'; import { Scheduler } from '../../mol-task'; import { isDebugMode } from '../../mol-util/debug'; import { createExtensions, WebGLExtensions } from './extensions'; @@ -15,7 +15,8 @@ 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'; +import { Texture, TextureFilter } from './texture'; +import { ComputeRenderable } from '../renderable'; export function getGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes): GLRenderingContext | null { function getContext(contextId: 'webgl' | 'experimental-webgl' | 'webgl2') { @@ -191,8 +192,12 @@ export interface WebGLContext { setContextLost: () => void handleContextRestored: () => void - // A cache for compute targets, managed by the code that uses it - readonly computeTargets: { [name: string]: RenderTarget } + /** Cache for compute renderables, managed by consumers */ + readonly namedComputeRenderables: { [name: string]: ComputeRenderable<any> } + /** Cache for frambuffers, managed by consumers */ + readonly namedFramebuffers: { [name: string]: Framebuffer } + /** Cache for textures, managed by consumers */ + readonly namedTextures: { [name: string]: Texture } createRenderTarget: (width: number, height: number, depth?: boolean, type?: 'uint8' | 'float32', filter?: TextureFilter) => RenderTarget unbindFramebuffer: () => void @@ -264,8 +269,6 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal const renderTargets = new Set<RenderTarget>(); - const computeTargets = Object.create(null); - return { gl, isWebGL2: isWebGL2(gl), @@ -283,7 +286,9 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal get maxRenderbufferSize () { return parameters.maxRenderbufferSize; }, get maxDrawBuffers () { return parameters.maxDrawBuffers; }, - computeTargets, + namedComputeRenderables: Object.create(null), + namedFramebuffers: Object.create(null), + namedTextures: Object.create(null), get isContextLost () { return isContextLost || gl.isContextLost(); diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts index ce0976ed28e1066707d3fa85173a213379d6ccc6..3ccd3093cec7727c13e42966478edd75f2ba64a7 100644 --- a/src/mol-gl/webgl/texture.ts +++ b/src/mol-gl/webgl/texture.ts @@ -197,6 +197,8 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension let destroyed = false; function define(_width: number, _height: number, _depth?: number) { + if (width === _width && height === _height && depth === (_depth || 0)) return; + width = _width, height = _height, depth = _depth || 0; gl.bindTexture(target, texture); if (target === gl.TEXTURE_2D) { diff --git a/src/mol-math/geometry/gaussian-density/gpu.ts b/src/mol-math/geometry/gaussian-density/gpu.ts index b5d5352e1f6450f121a1462c9d62d87b7a1e30b0..09618276513924f174cf5f76b0c239d00f5c475e 100644 --- a/src/mol-math/geometry/gaussian-density/gpu.ts +++ b/src/mol-math/geometry/gaussian-density/gpu.ts @@ -13,7 +13,7 @@ import { Vec3, Tensor, Mat4, Vec2 } from '../../linear-algebra'; import { ValueCell } from '../../../mol-util'; import { createComputeRenderable, ComputeRenderable } from '../../../mol-gl/renderable'; import { WebGLContext } from '../../../mol-gl/webgl/context'; -import { Texture } from '../../../mol-gl/webgl/texture'; +import { Texture, TextureFilter, TextureFormat, TextureKind, TextureType } from '../../../mol-gl/webgl/texture'; import { decodeFloatRGB } from '../../../mol-util/float-packing'; import { ShaderCode } from '../../../mol-gl/shader-code'; import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item'; @@ -48,21 +48,32 @@ export const GaussianDensitySchema = { }; type GaussianDensityValues = Values<typeof GaussianDensitySchema> type GaussianDensityRenderable = ComputeRenderable<GaussianDensityValues> - +const GaussianDensityName = 'gaussian-density'; const GaussianDensityShaderCode = ShaderCode( - 'gaussian-density', gaussian_density_vert, gaussian_density_frag + GaussianDensityName, gaussian_density_vert, gaussian_density_frag ); -let _tmpTexture: Texture | undefined = undefined; +function getFramebuffer(webgl: WebGLContext): Framebuffer { + if (!webgl.namedFramebuffers[GaussianDensityName]) { + webgl.namedFramebuffers[GaussianDensityName] = webgl.resources.framebuffer(); + } + return webgl.namedFramebuffers[GaussianDensityName]; +} + +function getTexture(name: string, webgl: WebGLContext, kind: TextureKind, format: TextureFormat, type: TextureType, filter: TextureFilter): Texture { + const _name = `${GaussianDensityName}-${name}`; + if (!webgl.namedTextures[_name]) { + webgl.namedTextures[_name] = webgl.resources.texture(kind, format, type, filter); + } + return webgl.namedTextures[_name]; +} export function GaussianDensityGPU(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, webgl: WebGLContext): DensityData { // always use texture2d when the gaussian density needs to be downloaded from the GPU, // it's faster than texture3d // console.time('GaussianDensityTexture2d') - if (!_tmpTexture) { - _tmpTexture = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear'); - } - const { scale, bbox, texture, gridDim, gridTexDim } = calcGaussianDensityTexture2d(webgl, position, box, radius, props, _tmpTexture); + const tmpTexture = getTexture('tmp', webgl, 'image-uint8', 'rgba', 'ubyte', 'linear'); + const { scale, bbox, texture, gridDim, gridTexDim } = calcGaussianDensityTexture2d(webgl, position, box, radius, props, tmpTexture); // webgl.waitForGpuCommandsCompleteSync() // console.timeEnd('GaussianDensityTexture2d') const { field, idField } = fieldFromTexture2d(webgl, texture, gridDim, gridTexDim); @@ -106,9 +117,6 @@ type GaussianDensityTextureData = { gridTexScale: Vec2 } -let _tmpFramebuffer: Framebuffer | undefined = undefined; -let _minDistanceTexture2d: Texture | undefined = undefined; - function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): GaussianDensityTextureData { // console.log('2d'); const { smoothness, resolution } = props; @@ -121,31 +129,22 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat const gridTexScale = Vec2.create(texDimX / texDimX, texDimY / texDimY); const radiusFactor = maxRadius * 2; - if (!_minDistanceTexture2d) { - _minDistanceTexture2d = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest'); - _minDistanceTexture2d.define(texDimX, texDimY); - } else if (_minDistanceTexture2d.getWidth() !== texDimX || _minDistanceTexture2d.getHeight() !== texDimY) { - _minDistanceTexture2d.define(texDimX, texDimY); - } + const minDistTex = getTexture('min-dist-2d', webgl, 'image-uint8', 'rgba', 'ubyte', 'nearest'); + minDistTex.define(texDimX, texDimY); - const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, _minDistanceTexture2d, expandedBox, dim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor); + const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistTex, expandedBox, dim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor); // const { gl, resources, state } = webgl; const { uCurrentSlice, uCurrentX, uCurrentY } = renderable.values; - if (!_tmpFramebuffer) _tmpFramebuffer = resources.framebuffer(); - const framebuffer = _tmpFramebuffer; + const framebuffer = getFramebuffer(webgl); framebuffer.bind(); setRenderingDefaults(webgl); - if (!texture) { - texture = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear'); - texture.define(texDimX, texDimY); - } else if (texture.getWidth() !== texDimX || texture.getHeight() !== texDimY) { - texture.define(texDimX, texDimY); - } + if (!texture) texture = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear'); + texture.define(texDimX, texDimY); // console.log(renderable) @@ -178,7 +177,7 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat render(texture, true); setupMinDistanceRendering(webgl, renderable); - render(_minDistanceTexture2d, true); + render(minDistTex, true); setupGroupIdRendering(webgl, renderable); render(texture, false); @@ -188,8 +187,6 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale }; } -let _minDistanceTexture3d: Texture | undefined = undefined; - function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): GaussianDensityTextureData { // console.log('3d'); const { gl, resources, state } = webgl; @@ -198,33 +195,25 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat const { drawCount, positions, radii, groups, scale, expandedBox, dim, maxRadius } = prepareGaussianDensityData(position, box, radius, props); const [ dx, dy, dz ] = dim; - if (!_minDistanceTexture3d) { - _minDistanceTexture3d = resources.texture('volume-uint8', 'rgba', 'ubyte', 'nearest'); - _minDistanceTexture3d.define(dx, dy, dz); - } else if (_minDistanceTexture3d.getWidth() !== dx || _minDistanceTexture3d.getHeight() !== dy || _minDistanceTexture3d.getDepth() !== dz) { - _minDistanceTexture3d.define(dx, dy, dz); - } + const minDistTex = getTexture('min-dist-3d', webgl, 'volume-uint8', 'rgba', 'ubyte', 'nearest'); + minDistTex.define(dx, dy, dz); const gridTexScale = Vec2.create(1, 1); const radiusFactor = maxRadius * 2; - const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, _minDistanceTexture3d, expandedBox, dim, dim, gridTexScale, smoothness, resolution, radiusFactor); + const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistTex, expandedBox, dim, dim, gridTexScale, smoothness, resolution, radiusFactor); // const { uCurrentSlice } = renderable.values; - const framebuffer = resources.framebuffer(); + const framebuffer = getFramebuffer(webgl); framebuffer.bind(); setRenderingDefaults(webgl); gl.viewport(0, 0, dx, dy); - if (!texture) { - texture = resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear'); - texture.define(dx, dy, dz); - } else if (texture.getWidth() !== dx || texture.getHeight() !== dy || texture.getDepth() !== dz) { - texture.define(dx, dy, dz); - } + if (!texture) texture = resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear'); + texture.define(dx, dy, dz); function render(fbTex: Texture, clear: boolean) { state.currentRenderItemId = -1; @@ -241,7 +230,7 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat render(texture, true); setupMinDistanceRendering(webgl, renderable); - render(_minDistanceTexture3d, true); + render(minDistTex, true); setupGroupIdRendering(webgl, renderable); render(texture, false); @@ -287,13 +276,11 @@ function prepareGaussianDensityData(position: PositionData, box: Box3D, radius: return { drawCount: n, positions, radii, groups, scale, expandedBox, dim, maxRadius }; } -let _GaussianDensityRenderable: GaussianDensityRenderable | undefined = undefined; - function getGaussianDensityRenderable(webgl: WebGLContext, drawCount: number, positions: Float32Array, radii: Float32Array, groups: Float32Array, minDistanceTexture: Texture, box: Box3D, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, smoothness: number, resolution: number, radiusFactor: number) { // console.log('radiusFactor', radiusFactor); - if (_GaussianDensityRenderable) { + if (webgl.namedComputeRenderables[GaussianDensityName]) { const extent = Vec3.sub(Vec3(), box.max, box.min); - const v = _GaussianDensityRenderable.values; + const v = webgl.namedComputeRenderables[GaussianDensityName].values; ValueCell.updateIfChanged(v.drawCount, drawCount); ValueCell.updateIfChanged(v.instanceCount, 1); @@ -318,14 +305,14 @@ function getGaussianDensityRenderable(webgl: WebGLContext, drawCount: number, po ValueCell.updateIfChanged(v.dGridTexType, minDistanceTexture.getDepth() > 0 ? '3d' : '2d'); ValueCell.updateIfChanged(v.dCalcType, 'density'); - _GaussianDensityRenderable.update(); + webgl.namedComputeRenderables[GaussianDensityName].update(); } else { - _GaussianDensityRenderable = _getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistanceTexture, box, gridDim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor); + webgl.namedComputeRenderables[GaussianDensityName] = createGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistanceTexture, box, gridDim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor); } - return _GaussianDensityRenderable; + return webgl.namedComputeRenderables[GaussianDensityName]; } -function _getGaussianDensityRenderable(webgl: WebGLContext, drawCount: number, positions: Float32Array, radii: Float32Array, groups: Float32Array, minDistanceTexture: Texture, box: Box3D, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, smoothness: number, resolution: number, radiusFactor: number) { +function createGaussianDensityRenderable(webgl: WebGLContext, drawCount: number, positions: Float32Array, radii: Float32Array, groups: Float32Array, minDistanceTexture: Texture, box: Box3D, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, smoothness: number, resolution: number, radiusFactor: number) { const extent = Vec3.sub(Vec3(), box.max, box.min); const values: GaussianDensityValues = { @@ -423,7 +410,6 @@ function getTexture2dSize(gridDim: Vec3) { export function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec3, texDim: Vec3) { // console.time('fieldFromTexture2d') - const { resources } = ctx; const [ dx, dy, dz ] = dim; const [ width, height ] = texDim; const fboTexCols = Math.floor(width / dx); @@ -436,7 +422,7 @@ export function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec const image = new Uint8Array(width * height * 4); - const framebuffer = resources.framebuffer(); + const framebuffer = getFramebuffer(ctx); framebuffer.bind(); texture.attachFramebuffer(framebuffer, 0); ctx.readPixels(0, 0, width, height, image);