diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 0b3774e13bf3db05887c1d6f958d7831ac65892e..85a3a4e2337ad1c1fee9474ed0880748e4511270 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -195,7 +195,7 @@ namespace Canvas3D { const drawPass = new DrawPass(webgl, renderer, scene, camera, debugHelper, handleHelper, { cameraHelper: p.camera.helper }); - const pickPass = new PickPass(webgl, renderer, scene, camera, handleHelper, 0.5); + const pickPass = new PickPass(webgl, renderer, scene, camera, handleHelper, 0.25, drawPass); const postprocessing = new PostprocessingPass(webgl, camera, drawPass, p.postprocessing); const multiSample = new MultiSamplePass(webgl, camera, drawPass, postprocessing, p.multiSample); diff --git a/src/mol-canvas3d/passes/draw.ts b/src/mol-canvas3d/passes/draw.ts index 8940f2e7725675b06baf32a160f27b8fdc908fb9..fc123fb77a79f5bb6c5b70c900cb61a6bae34f38 100644 --- a/src/mol-canvas3d/passes/draw.ts +++ b/src/mol-canvas3d/passes/draw.ts @@ -58,16 +58,16 @@ export const DefaultDrawPassProps = PD.getDefaultValues(DrawPassParams); export type DrawPassProps = PD.Values<typeof DrawPassParams> export class DrawPass { - colorTarget: RenderTarget - depthTexture: Texture - packedDepth: boolean + readonly colorTarget: RenderTarget + readonly depthTexture: Texture + readonly depthTexturePrimitives: Texture + readonly packedDepth: boolean - cameraHelper: CameraHelper + readonly cameraHelper: CameraHelper private depthTarget: RenderTarget private depthTargetPrimitives: RenderTarget | null private depthTargetVolumes: RenderTarget | null - private depthTexturePrimitives: Texture private depthTextureVolumes: Texture private depthMerge: DepthMergeRenderable diff --git a/src/mol-canvas3d/passes/pick.ts b/src/mol-canvas3d/passes/pick.ts index 425c07544ce563ce360a4107ad36c86ea709b31c..51538baf255b8be05fa624903f7a5eaec0737cf2 100644 --- a/src/mol-canvas3d/passes/pick.ts +++ b/src/mol-canvas3d/passes/pick.ts @@ -12,6 +12,7 @@ import { PickingId } from '../../mol-geo/geometry/picking'; import { decodeFloatRGB } from '../../mol-util/float-packing'; import { Camera } from '../camera'; import { HandleHelper } from '../helper/handle-helper'; +import { DrawPass } from './draw'; const NullId = Math.pow(2, 24) - 2; @@ -30,14 +31,14 @@ export class PickPass { private pickWidth: number private pickHeight: number - constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private handleHelper: HandleHelper, private pickBaseScale: number) { + constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, private handleHelper: HandleHelper, private pickBaseScale: number, private drawPass: DrawPass) { const { gl } = webgl; const width = gl.drawingBufferWidth; const height = gl.drawingBufferHeight; this.pickScale = pickBaseScale / webgl.pixelRatio; - this.pickWidth = Math.round(width * this.pickScale); - this.pickHeight = Math.round(height * this.pickScale); + this.pickWidth = Math.ceil(width * this.pickScale); + this.pickHeight = Math.ceil(height * this.pickScale); this.objectPickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight); this.instancePickTarget = webgl.createRenderTarget(this.pickWidth, this.pickHeight); @@ -57,8 +58,8 @@ export class PickPass { setSize(width: number, height: number) { this.pickScale = this.pickBaseScale / this.webgl.pixelRatio; - this.pickWidth = Math.round(width * this.pickScale); - this.pickHeight = Math.round(height * this.pickScale); + this.pickWidth = Math.ceil(width * this.pickScale); + this.pickHeight = Math.ceil(height * this.pickScale); this.objectPickTarget.setSize(this.pickWidth, this.pickHeight); this.instancePickTarget.setSize(this.pickWidth, this.pickHeight); @@ -69,16 +70,22 @@ export class PickPass { render() { const { renderer, scene, camera, handleHelper: { scene: handleScene } } = this; + const depth = this.drawPass.depthTexturePrimitives; renderer.setViewport(0, 0, this.pickWidth, this.pickHeight); this.objectPickTarget.bind(); - renderer.render(scene, camera, 'pickObject', true, false, null); + renderer.render(scene.primitives, camera, 'pickObject', true, false, null); + renderer.render(scene.volumes, camera, 'pickObject', false, false, depth); renderer.render(handleScene, camera, 'pickObject', false, false, null); + this.instancePickTarget.bind(); - renderer.render(scene, camera, 'pickInstance', true, false, null); + renderer.render(scene.primitives, camera, 'pickInstance', true, false, null); + renderer.render(scene.volumes, camera, 'pickInstance', false, false, depth); renderer.render(handleScene, camera, 'pickInstance', false, false, null); + this.groupPickTarget.bind(); - renderer.render(scene, camera, 'pickGroup', true, false, null); + renderer.render(scene.primitives, camera, 'pickGroup', true, false, null); + renderer.render(scene.volumes, camera, 'pickGroup', false, false, depth); renderer.render(handleScene, camera, 'pickGroup', false, false, null); this.pickDirty = false; @@ -116,18 +123,21 @@ export class PickPass { y *= webgl.pixelRatio; y = gl.drawingBufferHeight - y; // flip y - const xp = Math.round(x * pickScale); - const yp = Math.round(y * pickScale); + const xp = Math.floor(x * pickScale); + const yp = Math.floor(y * pickScale); const objectId = this.getId(xp, yp, this.objectBuffer); + // console.log('objectId', objectId); if (objectId === -1 || objectId === NullId) return; const instanceId = this.getId(xp, yp, this.instanceBuffer); + // console.log('instanceId', instanceId); if (instanceId === -1 || instanceId === NullId) return; const groupId = this.getId(xp, yp, this.groupBuffer); + // console.log('groupId', groupId); if (groupId === -1 || groupId === NullId) return; - + // console.log({ objectId, instanceId, groupId }); return { objectId, instanceId, groupId }; } } \ No newline at end of file diff --git a/src/mol-geo/geometry/direct-volume/direct-volume.ts b/src/mol-geo/geometry/direct-volume/direct-volume.ts index 198eb48124aaea070b6cbd609700159ef0fdc31e..cd3e99c97a6d7dbe57b65ec79c37d2a5d70eddf7 100644 --- a/src/mol-geo/geometry/direct-volume/direct-volume.ts +++ b/src/mol-geo/geometry/direct-volume/direct-volume.ts @@ -133,9 +133,9 @@ export namespace DirectVolume { return {} as DirectVolume; // TODO } - export function createRenderModeParam(volume?: Volume) { - const isoValueParam = volume - ? Volume.createIsoValueParam(Volume.IsoValue.relative(2), volume.grid.stats) + export function createRenderModeParam(stats?: Grid['stats']) { + const isoValueParam = stats + ? Volume.createIsoValueParam(Volume.IsoValue.relative(2), stats) : Volume.IsoValueParam; return PD.MappedStatic('volume', { diff --git a/src/mol-geo/geometry/image/image.ts b/src/mol-geo/geometry/image/image.ts index 0283ee669e504cb72953f33e3dd37b50a3246929..0f34aeff787b93d7e900291fecd77e1de00592df 100644 --- a/src/mol-geo/geometry/image/image.ts +++ b/src/mol-geo/geometry/image/image.ts @@ -53,17 +53,17 @@ export { Image }; interface Image { readonly kind: 'image', - readonly imageTexture: ValueCell<TextureImage<Float32Array>>, + readonly imageTexture: ValueCell<TextureImage<Uint8Array>>, readonly imageTextureDim: ValueCell<Vec2>, readonly cornerBuffer: ValueCell<Float32Array>, - readonly groupTexture: ValueCell<TextureImage<Float32Array>>, + readonly groupTexture: ValueCell<TextureImage<Uint8Array>>, /** Bounding sphere of the image */ boundingSphere: Sphere3D } namespace Image { - export function create(imageTexture: TextureImage<Float32Array>, corners: Float32Array, groupTexture: TextureImage<Float32Array>, image?: Image): Image { + export function create(imageTexture: TextureImage<Uint8Array>, corners: Float32Array, groupTexture: TextureImage<Uint8Array>, image?: Image): Image { return image ? update(imageTexture, corners, groupTexture, image) : fromData(imageTexture, corners, groupTexture); @@ -75,7 +75,7 @@ namespace Image { ]); } - function fromData(imageTexture: TextureImage<Float32Array>, corners: Float32Array, groupTexture: TextureImage<Float32Array>): Image { + function fromData(imageTexture: TextureImage<Uint8Array>, corners: Float32Array, groupTexture: TextureImage<Uint8Array>): Image { const boundingSphere = Sphere3D(); let currentHash = -1; @@ -101,7 +101,7 @@ namespace Image { return image; } - function update(imageTexture: TextureImage<Float32Array>, corners: Float32Array, groupTexture: TextureImage<Float32Array>, image: Image): Image { + function update(imageTexture: TextureImage<Uint8Array>, corners: Float32Array, groupTexture: TextureImage<Uint8Array>, image: Image): Image { const width = imageTexture.width; const height = imageTexture.height; diff --git a/src/mol-gl/renderable/image.ts b/src/mol-gl/renderable/image.ts index 90dcb2730384731353682bd989aa6843cff37563..0ba595cca0e1d49c1cc73c7cba7964664b8cc6a2 100644 --- a/src/mol-gl/renderable/image.ts +++ b/src/mol-gl/renderable/image.ts @@ -21,8 +21,8 @@ export const ImageSchema = { elements: ElementsSpec('uint32'), uImageTexDim: UniformSpec('v2'), - tImageTex: TextureSpec('image-float32', 'rgba', 'float', 'nearest'), - tGroupTex: TextureSpec('image-float32', 'alpha', 'float', 'nearest'), + tImageTex: TextureSpec('image-uint8', 'rgba', 'ubyte', 'nearest'), + tGroupTex: TextureSpec('image-uint8', 'rgba', 'ubyte', 'nearest'), dInterpolation: DefineSpec('string', InterpolationTypeNames), }; diff --git a/src/mol-gl/shader/direct-volume.frag.ts b/src/mol-gl/shader/direct-volume.frag.ts index d5569c154dfdf7b13d7b8a75394f4b5a0944c22d..4d392f4d1b5e8b055a70648e0a26c4d49468872c 100644 --- a/src/mol-gl/shader/direct-volume.frag.ts +++ b/src/mol-gl/shader/direct-volume.frag.ts @@ -267,8 +267,7 @@ vec4 raymarch(vec3 startLoc, vec3 step) { #include apply_marker_color #include apply_fog - src.rgb = gl_FragColor.rgb; - src.a = gl_FragColor.a; + src = gl_FragColor; src.rgb *= src.a; dst = (1.0 - dst.a) * src + dst; // standard blending @@ -279,19 +278,14 @@ vec4 raymarch(vec3 startLoc, vec3 step) { #endif } prevValue = value; - #endif - - #if defined(dRenderMode_volume) + #elif defined(dRenderMode_volume) isoPos = toUnit(pos); vec4 mvPosition = uModelView * uTransform * vec4(isoPos * uGridDim, 1.0); if (calcDepth(mvPosition.xyz) > getDepth(gl_FragCoord.xy / uViewport.zw)) break; - // bool flipped = value > uIsoValue.y; // negative isosurfaces - // interior = value < uIsoValue.x && flipped; vec3 vViewPosition = mvPosition.xyz; vec4 material = transferFunction(value); - src.a = material.a * uAlpha; if (material.a >= 0.01) { #ifdef dPackedGroup @@ -304,14 +298,16 @@ vec4 raymarch(vec3 startLoc, vec3 step) { #endif mat3 normalMatrix = transpose3(inverse3(mat3(uModelView * uUnitToCartn))); vec3 normal = -normalize(normalMatrix * normalize(gradient)); - // normal = normal * (float(flipped) * 2.0 - 1.0); - // normal = normal * -(float(interior) * 2.0 - 1.0); #include apply_light_color - src.rgb = gl_FragColor.rgb; } else { - src.rgb = material.rgb; + gl_FragColor.rgb = material.rgb; } + gl_FragColor.a = material.a * uAlpha; + #include apply_fog + + src = gl_FragColor; + src.rgb *= src.a; dst = (1.0 - dst.a) * src + dst; // standard blending #endif @@ -322,13 +318,20 @@ vec4 raymarch(vec3 startLoc, vec3 step) { pos += step; } + + #if defined(dRenderMode_isosurface) && defined(enabledFragDepth) + // ensure depth is written everywhere + if (!hit) + gl_FragDepthEXT = 1.0; + #endif + return dst; } // TODO calculate normalMatrix on CPU // TODO fix near/far clipping // TODO support clip objects -// TODO support float texture for higher precision values +// TODO support float texture for higher precision values??? void main () { // TODO handle on CPU in renderloop @@ -349,7 +352,5 @@ void main () { vec3 step = rayDir * stepScale; gl_FragColor = raymarch(origPos, step); - if (length(gl_FragColor.rgb) < 0.00001) - discard; } `; \ No newline at end of file diff --git a/src/mol-gl/shader/gaussian-density.frag.ts b/src/mol-gl/shader/gaussian-density.frag.ts index 6f9d31b866d18c26049a9036f22ea4b5a923f6fb..b6b05b91e23080791f11e49a6519aa165780bd4f 100644 --- a/src/mol-gl/shader/gaussian-density.frag.ts +++ b/src/mol-gl/shader/gaussian-density.frag.ts @@ -1,5 +1,5 @@ /** - * 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> * @author Michael Krone <michael.krone@uni-tuebingen.de> @@ -11,9 +11,14 @@ precision highp float; varying vec3 vPosition; varying float vRadiusSqInv; #if defined(dCalcType_groupId) - precision highp sampler2D; - uniform sampler2D tMinDistanceTex; - uniform vec3 uGridTexDim; + #if defined(dGridTexType_2d) + precision highp sampler2D; + uniform sampler2D tMinDistanceTex; + uniform vec3 uGridTexDim; + #elif defined(dGridTexType_3d) + precision highp sampler3D; + uniform sampler3D tMinDistanceTex; + #endif varying float vGroup; #endif @@ -26,6 +31,7 @@ uniform float uCurrentX; uniform float uCurrentY; uniform float uAlpha; uniform float uResolution; +uniform float uRadiusFactor; void main() { vec2 v = gl_FragCoord.xy - vec2(uCurrentX, uCurrentY) - 0.5; @@ -34,12 +40,16 @@ void main() { #if defined(dCalcType_density) float density = exp(-uAlpha * ((dist * dist) * vRadiusSqInv)); - gl_FragColor.a = density; + gl_FragColor.a = density / uRadiusFactor; #elif defined(dCalcType_minDistance) - gl_FragColor.a = 10000.0 - dist; + gl_FragColor.a = 1.0 - dist / uRadiusFactor; #elif defined(dCalcType_groupId) - float minDistance = 10000.0 - texture2D(tMinDistanceTex, (gl_FragCoord.xy) / (uGridTexDim.xy / uGridTexScale)).a; - if (dist > minDistance + uResolution * 0.05) + #if defined(dGridTexType_2d) + float minDistance = 1.0 - texture2D(tMinDistanceTex, (gl_FragCoord.xy) / (uGridTexDim.xy / uGridTexScale)).a; + #elif defined(dGridTexType_3d) + float minDistance = 1.0 - texelFetch(tMinDistanceTex, ivec3(gl_FragCoord.xy, uCurrentSlice), 0).a; + #endif + if (dist / uRadiusFactor > minDistance + uResolution * 0.05) discard; gl_FragColor.rgb = encodeFloatRGB(vGroup); #endif diff --git a/src/mol-gl/shader/image.frag.ts b/src/mol-gl/shader/image.frag.ts index 11e7acbd4dfa38e5c60e0a29c2df55e4486e6618..3b2e076d4d530c30b7ad7e16a8b4100e4f85ae4c 100644 --- a/src/mol-gl/shader/image.frag.ts +++ b/src/mol-gl/shader/image.frag.ts @@ -104,8 +104,7 @@ void main() { #elif defined(dRenderVariant_pickInstance) gl_FragColor = vec4(encodeFloatRGB(vInstance), 1.0); #elif defined(dRenderVariant_pickGroup) - float group = texture2D(tGroupTex, vUv).a; - gl_FragColor = vec4(encodeFloatRGB(group), 1.0); + gl_FragColor = vec4(texture2D(tGroupTex, vUv).rgb, 1.0); #endif #elif defined(dRenderVariant_depth) if (imageData.a < 0.05) @@ -119,7 +118,7 @@ void main() { gl_FragColor = imageData; gl_FragColor.a *= uAlpha; - float group = texture2D(tGroupTex, vUv).a; + float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb); float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a; #include apply_marker_color #include apply_fog diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts index f0f8205a37ec7f1f9e11a40dead7812942784eef..ce0976ed28e1066707d3fa85173a213379d6ccc6 100644 --- a/src/mol-gl/webgl/texture.ts +++ b/src/mol-gl/webgl/texture.ts @@ -219,6 +219,7 @@ export function createTexture(gl: GLRenderingContext, extensions: WebGLExtension width = data.width, height = data.height; gl.texImage2D(target, 0, internalFormat, width, height, 0, format, type, data.array); } else if (isWebGL2(gl) && isTexture3d(data, target, gl)) { + gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false); width = data.width, height = data.height, depth = data.depth; gl.texImage3D(target, 0, internalFormat, width, height, depth, 0, format, type, data.array); } else { diff --git a/src/mol-math/geometry/gaussian-density/gpu.ts b/src/mol-math/geometry/gaussian-density/gpu.ts index 61204ab8160a841379e3dfd07907a60d22376f4c..c91bca94147082cc4bd85835af0ea311498e535f 100644 --- a/src/mol-math/geometry/gaussian-density/gpu.ts +++ b/src/mol-math/geometry/gaussian-density/gpu.ts @@ -20,6 +20,7 @@ import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item'; import { ValueSpec, AttributeSpec, UniformSpec, TextureSpec, DefineSpec, Values } from '../../../mol-gl/renderable/schema'; import gaussian_density_vert from '../../../mol-gl/shader/gaussian-density.vert'; import gaussian_density_frag from '../../../mol-gl/shader/gaussian-density.frag'; +import { Framebuffer } from '../../../mol-gl/webgl/framebuffer'; export const GaussianDensitySchema = { drawCount: ValueSpec('number'), @@ -39,22 +40,30 @@ export const GaussianDensitySchema = { uGridTexScale: UniformSpec('v2', true), uAlpha: UniformSpec('f', true), uResolution: UniformSpec('f', true), + uRadiusFactor: UniformSpec('f', true), tMinDistanceTex: TextureSpec('texture', 'rgba', 'float', 'nearest'), dGridTexType: DefineSpec('string', ['2d', '3d']), dCalcType: DefineSpec('string', ['density', 'minDistance', 'groupId']), }; +type GaussianDensityValues = Values<typeof GaussianDensitySchema> +type GaussianDensityRenderable = ComputeRenderable<GaussianDensityValues> export const GaussianDensityShaderCode = ShaderCode( 'gaussian-density', gaussian_density_vert, gaussian_density_frag, { standardDerivatives: false, fragDepth: false } ); +let _tmpTexture: Texture | undefined = undefined; + 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') - const { scale, bbox, texture, gridDim, gridTexDim } = calcGaussianDensityTexture2d(webgl, position, box, radius, props); + if (!_tmpTexture) { + _tmpTexture = webgl.resources.texture('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); @@ -98,35 +107,45 @@ 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 { - const { smoothness } = props; + // console.log('2d'); + const { smoothness, resolution } = props; - const { drawCount, positions, radii, groups, scale, expandedBox, dim } = prepareGaussianDensityData(position, box, radius, props); + const { drawCount, positions, radii, groups, scale, expandedBox, dim, maxRadius } = prepareGaussianDensityData(position, box, radius, props); const [ dx, dy, dz ] = dim; - const { texDimX, texDimY, texCols, powerOfTwoSize } = getTexture2dSize(dim); - // console.log({ texDimX, texDimY, texCols, powerOfTwoSize, dim }) + const { texDimX, texDimY, texCols } = getTexture2dSize(dim); + // console.log({ texDimX, texDimY, texCols, texSize, dim }); const gridTexDim = Vec3.create(texDimX, texDimY, 0); - const gridTexScale = Vec2.create(texDimX / powerOfTwoSize, texDimY / powerOfTwoSize); - - const minDistanceTexture = webgl.resources.texture('image-float32', 'rgba', 'float', 'nearest'); - minDistanceTexture.define(powerOfTwoSize, powerOfTwoSize); + 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 renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistanceTexture, expandedBox, dim, gridTexDim, gridTexScale, smoothness, props.resolution); + const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, _minDistanceTexture2d, expandedBox, dim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor); // const { gl, resources, state } = webgl; const { uCurrentSlice, uCurrentX, uCurrentY } = renderable.values; - const framebuffer = resources.framebuffer(); + if (!_tmpFramebuffer) _tmpFramebuffer = resources.framebuffer(); + const framebuffer = _tmpFramebuffer; framebuffer.bind(); setRenderingDefaults(webgl); if (!texture) { - texture = resources.texture('image-float32', 'rgba', 'float', 'nearest'); - texture.define(powerOfTwoSize, powerOfTwoSize); - } else if (texture.getWidth() !== powerOfTwoSize || texture.getHeight() !== powerOfTwoSize) { - texture.define(powerOfTwoSize, powerOfTwoSize); + texture = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear'); + texture.define(texDimX, texDimY); + } else if (texture.getWidth() !== texDimX || texture.getHeight() !== texDimY) { + texture.define(texDimX, texDimY); } // console.log(renderable) @@ -160,28 +179,37 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat render(texture, true); setupMinDistanceRendering(webgl, renderable); - render(minDistanceTexture, true); + render(_minDistanceTexture2d, true); setupGroupIdRendering(webgl, renderable); render(texture, false); - // printTexture(webgl, texture, 1) + // printTexture(webgl, texture, 1); 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 { - const { gl, resources } = webgl; - const { smoothness } = props; + // console.log('3d'); + const { gl, resources, state } = webgl; + const { smoothness, resolution } = props; - const { drawCount, positions, radii, groups, scale, expandedBox, dim } = prepareGaussianDensityData(position, box, radius, props); + const { drawCount, positions, radii, groups, scale, expandedBox, dim, maxRadius } = prepareGaussianDensityData(position, box, radius, props); const [ dx, dy, dz ] = dim; - const minDistanceTexture = resources.texture('volume-float32', 'rgba', 'float', 'nearest'); - minDistanceTexture.define(dx, dy, dz); + + 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 gridTexScale = Vec2.create(1, 1); + const radiusFactor = maxRadius * 2; - const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistanceTexture, expandedBox, dim, dim, gridTexScale, smoothness, props.resolution); + const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, _minDistanceTexture3d, expandedBox, dim, dim, gridTexScale, smoothness, resolution, radiusFactor); // @@ -192,25 +220,32 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat setRenderingDefaults(webgl); gl.viewport(0, 0, dx, dy); - if (!texture) texture = resources.texture('volume-float32', 'rgba', 'float', 'nearest'); - texture.define(dx, dy, dz); + 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); + } - function render(fbTex: Texture) { + function render(fbTex: Texture, clear: boolean) { + state.currentRenderItemId = -1; for (let i = 0; i < dz; ++i) { ValueCell.update(uCurrentSlice, i); fbTex.attachFramebuffer(framebuffer, 0, i); + if (clear) gl.clear(gl.COLOR_BUFFER_BIT); renderable.render(); } + gl.finish(); } - setupMinDistanceRendering(webgl, renderable); - render(minDistanceTexture); - setupDensityRendering(webgl, renderable); - render(texture); + render(texture, true); + + setupMinDistanceRendering(webgl, renderable); + render(_minDistanceTexture3d, true); setupGroupIdRendering(webgl, renderable); - render(texture); + render(texture, false); return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridTexScale }; } @@ -250,13 +285,51 @@ function prepareGaussianDensityData(position: PositionData, box: Box3D, radius: const scale = Vec3.create(resolution, resolution, resolution); - return { drawCount: n, positions, radii, groups, scale, expandedBox, dim }; + 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) { + const extent = Vec3.sub(Vec3(), box.max, box.min); + const v = _GaussianDensityRenderable.values; + + ValueCell.updateIfChanged(v.drawCount, drawCount); + ValueCell.updateIfChanged(v.instanceCount, 1); + + ValueCell.updateIfChanged(v.aRadius, radii); + ValueCell.updateIfChanged(v.aPosition, positions); + ValueCell.updateIfChanged(v.aGroup, groups); + + ValueCell.updateIfChanged(v.uCurrentSlice, 0); + ValueCell.updateIfChanged(v.uCurrentX, 0); + ValueCell.updateIfChanged(v.uCurrentY, 0); + ValueCell.updateIfChanged(v.uBboxMin, box.min); + ValueCell.updateIfChanged(v.uBboxSize, extent); + ValueCell.updateIfChanged(v.uGridDim, gridDim); + ValueCell.updateIfChanged(v.uGridTexDim, gridTexDim); + ValueCell.updateIfChanged(v.uGridTexScale, gridTexScale); + ValueCell.updateIfChanged(v.uAlpha, smoothness); + ValueCell.updateIfChanged(v.uResolution, resolution); + ValueCell.updateIfChanged(v.uRadiusFactor, radiusFactor); + ValueCell.updateIfChanged(v.tMinDistanceTex, minDistanceTexture); + + ValueCell.updateIfChanged(v.dGridTexType, minDistanceTexture.getDepth() > 0 ? '3d' : '2d'); + ValueCell.updateIfChanged(v.dCalcType, 'density'); + + _GaussianDensityRenderable.update(); + } else { + _GaussianDensityRenderable = _getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistanceTexture, box, gridDim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor); + } + return _GaussianDensityRenderable; } -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) { - const extent = Vec3.sub(Vec3.zero(), box.max, box.min); +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) { + const extent = Vec3.sub(Vec3(), box.max, box.min); - const values: Values<typeof GaussianDensitySchema> = { + const values: GaussianDensityValues = { drawCount: ValueCell.create(drawCount), instanceCount: ValueCell.create(1), @@ -274,10 +347,11 @@ function getGaussianDensityRenderable(webgl: WebGLContext, drawCount: number, po uGridTexScale: ValueCell.create(gridTexScale), uAlpha: ValueCell.create(smoothness), uResolution: ValueCell.create(resolution), + uRadiusFactor: ValueCell.create(radiusFactor), tMinDistanceTex: ValueCell.create(minDistanceTexture), dGridTexType: ValueCell.create(minDistanceTexture.getDepth() > 0 ? '3d' : '2d'), - dCalcType: ValueCell.create('minDistance'), + dCalcType: ValueCell.create('density'), }; const schema = { ...GaussianDensitySchema }; @@ -297,7 +371,7 @@ function setRenderingDefaults(ctx: WebGLContext) { state.clearColor(0, 0, 0, 0); } -function setupMinDistanceRendering(webgl: WebGLContext, renderable: ComputeRenderable<any>) { +function setupMinDistanceRendering(webgl: WebGLContext, renderable: GaussianDensityRenderable) { const { gl, state } = webgl; ValueCell.update(renderable.values.dCalcType, 'minDistance'); renderable.update(); @@ -310,18 +384,16 @@ function setupMinDistanceRendering(webgl: WebGLContext, renderable: ComputeRende state.blendEquation(webgl.extensions.blendMinMax.MAX); } -function setupDensityRendering(webgl: WebGLContext, renderable: ComputeRenderable<any>) { +function setupDensityRendering(webgl: WebGLContext, renderable: GaussianDensityRenderable) { const { gl, state } = webgl; ValueCell.update(renderable.values.dCalcType, 'density'); renderable.update(); state.colorMask(false, false, false, true); state.blendFunc(gl.ONE, gl.ONE); - // state.colorMask(true, true, true, true) - // state.blendFuncSeparate(gl.ONE, gl.ZERO, gl.ONE, gl.ONE) state.blendEquation(gl.FUNC_ADD); } -function setupGroupIdRendering(webgl: WebGLContext, renderable: ComputeRenderable<any>) { +function setupGroupIdRendering(webgl: WebGLContext, renderable: GaussianDensityRenderable) { const { gl, state } = webgl; ValueCell.update(renderable.values.dCalcType, 'groupId'); renderable.update(); @@ -348,14 +420,13 @@ function getTexture2dSize(gridDim: Vec3) { } else { texDimX = gridDim[0] * gridDim[2]; } - return { texDimX, texDimY, texRows, texCols, powerOfTwoSize: texDimY < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2 }; + return { texDimX, texDimY, texRows, texCols, powerOfTwoSize: texDimY }; } 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 } = texture const [ width, height ] = texDim; const fboTexCols = Math.floor(width / dx); @@ -365,8 +436,7 @@ export function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec const idData = space.create(); const idField = Tensor.create(space, idData); - // const image = new Uint8Array(width * height * 4) - const image = new Float32Array(width * height * 4); + const image = new Uint8Array(width * height * 4); const framebuffer = resources.framebuffer(); framebuffer.bind(); @@ -386,8 +456,8 @@ export function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec for (let iy = 0; iy < dy; ++iy) { for (let ix = 0; ix < dx; ++ix) { const idx = 4 * (tmpCol * dx + (iy + tmpRow) * width + ix); - data[j] = image[idx + 3]; // / 255 - idData[j] = decodeFloatRGB(image[idx] * 255, image[idx + 1] * 255, image[idx + 2] * 255); + data[j] = image[idx + 3] / 255; + idData[j] = decodeFloatRGB(image[idx], image[idx + 1], image[idx + 2]); j++; } } diff --git a/src/mol-repr/structure/registry.ts b/src/mol-repr/structure/registry.ts index 03e0c3af3f70b439b4c659f9c2dbc315a96e2781..f49e9b37fa1e13b2d26f24f6a1b358e0f144fd2a 100644 --- a/src/mol-repr/structure/registry.ts +++ b/src/mol-repr/structure/registry.ts @@ -1,5 +1,5 @@ /** - * 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> */ @@ -20,6 +20,7 @@ import { PointRepresentationProvider } from './representation/point'; import { PuttyRepresentationProvider } from './representation/putty'; import { SpacefillRepresentationProvider } from './representation/spacefill'; import { LineRepresentationProvider } from './representation/line'; +import { GaussianVolumeRepresentationProvider } from './representation/gaussian-volume'; export class StructureRepresentationRegistry extends RepresentationRegistry<Structure, StructureRepresentationState> { constructor() { @@ -38,7 +39,7 @@ export namespace StructureRepresentationRegistry { 'carbohydrate': CarbohydrateRepresentationProvider, 'ellipsoid': EllipsoidRepresentationProvider, 'gaussian-surface': GaussianSurfaceRepresentationProvider, - // 'gaussian-volume': GaussianVolumeRepresentationProvider, // TODO disabled for now, needs more work + 'gaussian-volume': GaussianVolumeRepresentationProvider, // TODO disabled for now, needs more work 'label': LabelRepresentationProvider, 'line': LineRepresentationProvider, 'molecular-surface': MolecularSurfaceRepresentationProvider, diff --git a/src/mol-repr/structure/representation/gaussian-volume.ts b/src/mol-repr/structure/representation/gaussian-volume.ts index 3c98dea41b55e7fda48ee00a0f9a7ea85654862e..eb2439e584ec3c65ebec9a603caf08ee96fd54b6 100644 --- a/src/mol-repr/structure/representation/gaussian-volume.ts +++ b/src/mol-repr/structure/representation/gaussian-volume.ts @@ -10,6 +10,7 @@ import { StructureRepresentation, StructureRepresentationProvider, ComplexRepres import { Representation, RepresentationParamsGetter, RepresentationContext } from '../../../mol-repr/representation'; import { ThemeRegistryContext } from '../../../mol-theme/theme'; import { Structure } from '../../../mol-model/structure'; +import { DirectVolume } from '../../../mol-geo/geometry/direct-volume/direct-volume'; const GaussianVolumeVisuals = { 'gaussian-volume': (ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, GaussianDensityVolumeParams>) => ComplexRepresentation('Gaussian volume', ctx, getParams, GaussianDensityVolumeVisual) @@ -20,7 +21,12 @@ export const GaussianVolumeParams = { }; export type GaussianVolumeParams = typeof GaussianVolumeParams export function getGaussianVolumeParams(ctx: ThemeRegistryContext, structure: Structure) { - return PD.clone(GaussianVolumeParams); + const p = PD.clone(GaussianVolumeParams); + p.renderMode = DirectVolume.createRenderModeParam({ + // TODO find a better way to set + min: 0, max: 1, mean: 0.04, sigma: 0.01 + }); + return p; } export type GaussianVolumeRepresentation = StructureRepresentation<GaussianVolumeParams> diff --git a/src/mol-repr/structure/visual/gaussian-density-volume.ts b/src/mol-repr/structure/visual/gaussian-density-volume.ts index cdb7e5e66f129a86a4dc75d6a2838466a6f06446..3cdfdfb0602bc1b06098f40ac0216c0d2ebd9d21 100644 --- a/src/mol-repr/structure/visual/gaussian-density-volume.ts +++ b/src/mol-repr/structure/visual/gaussian-density-volume.ts @@ -14,6 +14,7 @@ import { ComplexDirectVolumeParams, ComplexVisual, ComplexDirectVolumeVisual } f import { VisualUpdateState } from '../../util'; import { Mat4, Vec3 } from '../../../mol-math/linear-algebra'; import { eachSerialElement, ElementIterator, getSerialElementLoci } from './util/element'; +import { Sphere3D } from '../../../mol-math/geometry'; async function createGaussianDensityVolume(ctx: VisualContext, structure: Structure, theme: Theme, props: GaussianDensityTextureProps, directVolume?: DirectVolume): Promise<DirectVolume> { const { runtime, webgl } = ctx; @@ -23,12 +24,17 @@ async function createGaussianDensityVolume(ctx: VisualContext, structure: Struct const oldTexture = directVolume ? directVolume.gridTexture.ref.value : undefined; const densityTextureData = await computeStructureGaussianDensityTexture(structure, p, webgl, oldTexture).runInContext(runtime); const { transform, texture, bbox, gridDim } = densityTextureData; - const stats = { min: 0, max: 1, mean: 0.5, sigma: 0.1 }; + const stats = { min: 0, max: 1, mean: 0.04, sigma: 0.01 }; const unitToCartn = Mat4.mul(Mat4(), transform, Mat4.fromScaling(Mat4(), gridDim)); const cellDim = Vec3.create(1, 1, 1); - return DirectVolume.create(bbox, gridDim, transform, unitToCartn, cellDim, texture, stats, true, directVolume); + const vol = DirectVolume.create(bbox, gridDim, transform, unitToCartn, cellDim, texture, stats, true, directVolume); + + const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset); + vol.setBoundingSphere(sphere); + + return vol; } export const GaussianDensityVolumeParams = { diff --git a/src/mol-repr/structure/visual/gaussian-surface-mesh.ts b/src/mol-repr/structure/visual/gaussian-surface-mesh.ts index 1eb19601781f6bcad2f75c3bce700abda96259d1..eb8f5bfab59f3a7d880bc2d11c9149f24be28910 100644 --- a/src/mol-repr/structure/visual/gaussian-surface-mesh.ts +++ b/src/mol-repr/structure/visual/gaussian-surface-mesh.ts @@ -7,7 +7,6 @@ import { ParamDefinition as PD } from '../../../mol-util/param-definition'; import { UnitsMeshParams, UnitsTextureMeshParams, UnitsVisual, UnitsMeshVisual, UnitsTextureMeshVisual } from '../units-visual'; import { GaussianDensityParams, computeUnitGaussianDensity, GaussianDensityTextureProps, computeUnitGaussianDensityTexture2d, GaussianDensityProps, computeStructureGaussianDensity, getUnitExtraRadius } from './util/gaussian'; -import { WebGLContext } from '../../../mol-gl/webgl/context'; import { VisualContext } from '../../visual'; import { Unit, Structure } from '../../../mol-model/structure'; import { Theme } from '../../../mol-theme/theme'; @@ -29,10 +28,6 @@ export const GaussianSurfaceMeshParams = { }; export type GaussianSurfaceMeshParams = typeof GaussianSurfaceMeshParams -export function getGaussianSurfaceVisual(webgl?: WebGLContext) { - return webgl && webgl.extensions.drawBuffers ? GaussianSurfaceTextureMeshVisual : GaussianSurfaceMeshVisual; -} - // async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> { diff --git a/src/mol-repr/volume/direct-volume.ts b/src/mol-repr/volume/direct-volume.ts index 552d5b94924bbb20a9b23e956885dce8ae34a00e..cd4626c705f6f19405e6bb3449bc64549893849c 100644 --- a/src/mol-repr/volume/direct-volume.ts +++ b/src/mol-repr/volume/direct-volume.ts @@ -239,7 +239,7 @@ export const DirectVolumeParams = { export type DirectVolumeParams = typeof DirectVolumeParams export function getDirectVolumeParams(ctx: ThemeRegistryContext, volume: Volume) { const p = PD.clone(DirectVolumeParams); - p.renderMode = DirectVolume.createRenderModeParam(volume); + p.renderMode = DirectVolume.createRenderModeParam(volume.grid.stats); return p; } export type DirectVolumeProps = PD.Values<DirectVolumeParams> diff --git a/src/mol-repr/volume/slice.ts b/src/mol-repr/volume/slice.ts index f1b3273f46ae05f8be1da54699d010652f2d6960..21309f24650691dde8f275e64ad48cf69c2e4a10 100644 --- a/src/mol-repr/volume/slice.ts +++ b/src/mol-repr/volume/slice.ts @@ -22,6 +22,7 @@ import { equalEps } from '../../mol-math/linear-algebra/3d/common'; import { RenderableState } from '../../mol-gl/renderable'; import { Color } from '../../mol-util/color'; import { ColorTheme } from '../../mol-theme/color'; +import { encodeFloatRGBtoArray } from '../../mol-util/float-packing'; export async function createImage(ctx: VisualContext, volume: Volume, theme: Theme, props: PD.Values<SliceParams>, image?: Image) { const { dimension: { name: dim }, isoValue } = props; @@ -47,8 +48,8 @@ export async function createImage(ctx: VisualContext, volume: Volume, theme: The [0, 0, z, 0, y, z, x, 0, z, x, y, z] ); - const imageArray = new Float32Array(width * height * 4); - const groupArray = getGroupArray(volume.grid, props); + const imageArray = new Uint8Array(width * height * 4); + const groupArray = getPackedGroupArray(volume.grid, props); let i = 0; for (let iy = y0; iy < ny; ++iy) { @@ -57,10 +58,10 @@ export async function createImage(ctx: VisualContext, volume: Volume, theme: The const val = space.get(data, ix, iy, iz); const normVal = (val - min) / (max - min); - imageArray[i] = r * normVal * 2; - imageArray[i + 1] = g * normVal * 2; - imageArray[i + 2] = b * normVal * 2; - imageArray[i + 3] = val >= isoVal ? 1 : 0; + imageArray[i] = r * normVal * 2 * 255; + imageArray[i + 1] = g * normVal * 2 * 255; + imageArray[i + 2] = b * normVal * 2 * 255; + imageArray[i + 3] = val >= isoVal ? 255 : 0; i += 4; } @@ -106,10 +107,27 @@ function getSliceInfo(grid: Grid, props: PD.Values<SliceParams>) { }; } +function getPackedGroupArray(grid: Grid, props: PD.Values<SliceParams>) { + const { space } = grid.cells; + const { width, height, x0, y0, z0, nx, ny, nz } = getSliceInfo(grid, props); + const groupArray = new Uint8Array(width * height * 4); + + let j = 0; + for (let iy = y0; iy < ny; ++iy) { + for (let ix = x0; ix < nx; ++ix) { + for (let iz = z0; iz < nz; ++iz) { + encodeFloatRGBtoArray(space.dataOffset(ix, iy, iz), groupArray, j); + j += 4; + } + } + } + return groupArray; +} + function getGroupArray(grid: Grid, props: PD.Values<SliceParams>) { const { space } = grid.cells; const { width, height, x0, y0, z0, nx, ny, nz } = getSliceInfo(grid, props); - const groupArray = new Float32Array(width * height); + const groupArray = new Uint32Array(width * height); let j = 0; for (let iy = y0; iy < ny; ++iy) {