Skip to content
Snippets Groups Projects
Select Git revision
  • 33fc40ddb70f3394b0138a7f2c43ebccdbdc1068
  • master default protected
  • e-infra2
  • ci-megalinter-speedup
  • egi-fixes
  • e-infra
  • envri-hub-new-aai
  • egi-b2drop-no-collapse
  • lfs
  • gpu_staging
  • resurrect-testing-ownloud
  • experiments/collab
  • update_claim_group_keys
  • envri-hub
  • enable_rtc
  • eosc-ui
  • future/jupyterhub-5.x
  • versioning
  • eosc-templating
  • staging1-raw-image
  • token-exchange
21 results

extra

Blame
  • gpu.ts 15.64 KiB
    /**
     * Copyright (c) 2017-2019 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>
     */
    
    import { PositionData, DensityData, DensityTextureData } from '../common'
    import { Box3D } from '../../geometry'
    import { GaussianDensityGPUProps } from '../gaussian-density'
    import { OrderedSet } from '../../../mol-data/int'
    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 { createTexture, Texture } 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';
    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'
    
    export const GaussianDensitySchema = {
        drawCount: ValueSpec('number'),
        instanceCount: ValueSpec('number'),
    
        aRadius: AttributeSpec('float32', 1, 0),
        aPosition: AttributeSpec('float32', 3, 0),
        aGroup: AttributeSpec('float32', 1, 0),
    
        uCurrentSlice: UniformSpec('f'),
        uCurrentX: UniformSpec('f'),
        uCurrentY: UniformSpec('f'),
        uBboxMin: UniformSpec('v3', true),
        uBboxSize: UniformSpec('v3', true),
        uGridDim: UniformSpec('v3', true),
        uGridTexDim: UniformSpec('v3', true),
        uGridTexScale: UniformSpec('v2', true),
        uAlpha: UniformSpec('f', true),
        uResolution: UniformSpec('f', true),
        tMinDistanceTex: TextureSpec('texture', 'rgba', 'float', 'nearest'),
    
        dGridTexType: DefineSpec('string', ['2d', '3d']),
        dCalcType: DefineSpec('string', ['density', 'minDistance', 'groupId']),
    }
    
    export const GaussianDensityShaderCode = ShaderCode(
        gaussian_density_vert, gaussian_density_frag,
        { standardDerivatives: false, fragDepth: false }
    )
    
    /** name for shared framebuffer used for gpu gaussian surface operations */
    const FramebufferName = 'gaussian-density'
    
    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)
        // webgl.waitForGpuCommandsCompleteSync()
        // console.timeEnd('GaussianDensityTexture2d')
        const { field, idField } = fieldFromTexture2d(webgl, texture, gridDim, gridTexDim)
    
        return { field, idField, transform: getTransform(scale, bbox) }
    }
    
    export function GaussianDensityTexture(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): DensityTextureData {
        return webgl.isWebGL2 ?
            GaussianDensityTexture3d(webgl, position, box, radius, props, oldTexture) :
            GaussianDensityTexture2d(webgl, position, box, radius, props, oldTexture)
    }
    
    export function GaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): DensityTextureData {
        return finalizeGaussianDensityTexture(calcGaussianDensityTexture2d(webgl, position, box, radius, props, oldTexture))
    }
    
    export function GaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): DensityTextureData {
        return finalizeGaussianDensityTexture(calcGaussianDensityTexture3d(webgl, position, box, radius, props, oldTexture))
    }
    
    function finalizeGaussianDensityTexture({ texture, scale, bbox, gridDim, gridTexDim, gridTexScale }: GaussianDensityTextureData): DensityTextureData {
        return { transform: getTransform(scale, bbox), texture, bbox, gridDim, gridTexDim, gridTexScale }
    }
    
    function getTransform(scale: Vec3, bbox: Box3D) {
        const transform = Mat4.identity()
        Mat4.fromScaling(transform, scale)
        Mat4.setTranslation(transform, bbox.min)
        return transform
    }
    
    //
    
    type GaussianDensityTextureData = {
        texture: Texture,
        scale: Vec3,
        bbox: Box3D,
        gridDim: Vec3,
        gridTexDim: Vec3
        gridTexScale: Vec2
    }
    
    function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): GaussianDensityTextureData {
        const { smoothness } = props
    
        const { drawCount, positions, radii, groups, scale, expandedBox, dim } = 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 gridTexDim = Vec3.create(texDimX, texDimY, 0)
        const gridTexScale = Vec2.create(texDimX / powerOfTwoSize, texDimY / powerOfTwoSize)
    
        const minDistanceTexture = createTexture(webgl, 'image-float32', 'rgba', 'float', 'nearest')
        minDistanceTexture.define(powerOfTwoSize, powerOfTwoSize)
    
        const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistanceTexture, expandedBox, dim, gridTexDim, gridTexScale, smoothness, props.resolution)
    
        //
    
        const { gl, framebufferCache, state } = webgl
        const { uCurrentSlice, uCurrentX, uCurrentY } = renderable.values
    
        const framebuffer = framebufferCache.get(FramebufferName).value
        framebuffer.bind()
        setRenderingDefaults(webgl)
    
        if (!texture) {
            texture = createTexture(webgl, 'image-float32', 'rgba', 'float', 'nearest')
            texture.define(powerOfTwoSize, powerOfTwoSize)
        } else if (texture.width !== powerOfTwoSize || texture.height !== powerOfTwoSize) {
            texture.define(powerOfTwoSize, powerOfTwoSize)
        }
    
        // console.log(renderable)
    
        function render(fbTex: Texture, clear: boolean) {
            state.currentRenderItemId = -1
            fbTex.attachFramebuffer(framebuffer, 0)
            if (clear) gl.clear(gl.COLOR_BUFFER_BIT)
            let currCol = 0
            let currY = 0
            let currX = 0
            for (let i = 0; i < dz; ++i) {
                if (currCol >= texCols) {
                    currCol -= texCols
                    currY += dy
                    currX = 0
                    ValueCell.update(uCurrentY, currY)
                }
                // console.log({ i, currX, currY })
                ValueCell.update(uCurrentX, currX)
                ValueCell.update(uCurrentSlice, i)
                gl.viewport(currX, currY, dx, dy)
                renderable.render()
                ++currCol
                currX += dx
            }
            gl.finish()
        }
    
        setupDensityRendering(webgl, renderable)
        render(texture, true)
    
        setupMinDistanceRendering(webgl, renderable)
        render(minDistanceTexture, true)
    
        setupGroupIdRendering(webgl, renderable)
        render(texture, false)
    
        // printTexture(webgl, texture, 1)
    
        return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale }
    }
    
    function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): GaussianDensityTextureData {
        const { smoothness } = props
    
        const { drawCount, positions, radii, groups, scale, expandedBox, dim } = prepareGaussianDensityData(position, box, radius, props)
        const [ dx, dy, dz ] = dim
        const minDistanceTexture = createTexture(webgl, 'volume-float32', 'rgba', 'float', 'nearest')
        minDistanceTexture.define(dx, dy, dz)
    
        const gridTexScale = Vec2.create(1, 1)
    
        const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistanceTexture, expandedBox, dim, dim, gridTexScale, smoothness, props.resolution)
    
        //
    
        const { gl, framebufferCache } = webgl
        const { uCurrentSlice } = renderable.values
    
        const framebuffer = framebufferCache.get(FramebufferName).value
        framebuffer.bind()
        setRenderingDefaults(webgl)
        gl.viewport(0, 0, dx, dy)
    
        if (!texture) texture = createTexture(webgl, 'volume-float32', 'rgba', 'float', 'nearest')
        texture.define(dx, dy, dz)
    
        function render(fbTex: Texture) {
            for (let i = 0; i < dz; ++i) {
                ValueCell.update(uCurrentSlice, i)
                fbTex.attachFramebuffer(framebuffer, 0, i)
                renderable.render()
            }
        }
    
        setupMinDistanceRendering(webgl, renderable)
        render(minDistanceTexture)
    
        setupDensityRendering(webgl, renderable)
        render(texture)
    
        setupGroupIdRendering(webgl, renderable)
        render(texture)
    
        return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridTexScale }
    }
    
    //
    
    function prepareGaussianDensityData(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps) {
        const { resolution, radiusOffset } = props
        const scaleFactor = 1 / resolution
    
        const { indices, x, y, z, id } = position
        const n = OrderedSet.size(indices)
    
        const positions = new Float32Array(n * 3)
        const radii = new Float32Array(n)
        const groups = new Float32Array(n)
    
        let maxRadius = 0
    
        for (let i = 0; i < n; ++i) {
            const j = OrderedSet.getAt(indices, i);
    
            positions[i * 3] = x[j]
            positions[i * 3 + 1] = y[j]
            positions[i * 3 + 2] = z[j]
            const r = radius(j) + radiusOffset
            if (maxRadius < r) maxRadius = r
            radii[i] = r
            groups[i] = id ? id[i] : i
        }
    
        const pad = maxRadius * 2 + resolution * 4
        const expandedBox = Box3D.expand(Box3D(), box, Vec3.create(pad, pad, pad));
        const scaledBox = Box3D.scale(Box3D(), expandedBox, scaleFactor)
        const dim = Box3D.size(Vec3(), scaledBox)
        Vec3.ceil(dim, dim)
    
        const scale = Vec3.create(resolution, resolution, resolution)
    
        return { drawCount: n, positions, radii, groups, scale, expandedBox, dim }
    }
    
    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)
    
        const values: Values<typeof GaussianDensitySchema> = {
            drawCount: ValueCell.create(drawCount),
            instanceCount: ValueCell.create(1),
    
            aRadius: ValueCell.create(radii),
            aPosition: ValueCell.create(positions),
            aGroup: ValueCell.create(groups),
    
            uCurrentSlice: ValueCell.create(0),
            uCurrentX: ValueCell.create(0),
            uCurrentY: ValueCell.create(0),
            uBboxMin: ValueCell.create(box.min),
            uBboxSize: ValueCell.create(extent),
            uGridDim: ValueCell.create(gridDim),
            uGridTexDim: ValueCell.create(gridTexDim),
            uGridTexScale: ValueCell.create(gridTexScale),
            uAlpha: ValueCell.create(smoothness),
            uResolution: ValueCell.create(resolution),
            tMinDistanceTex: ValueCell.create(minDistanceTexture),
    
            dGridTexType: ValueCell.create(minDistanceTexture.depth > 0 ? '3d' : '2d'),
            dCalcType: ValueCell.create('minDistance'),
        }
    
        const schema = { ...GaussianDensitySchema }
        const shaderCode = GaussianDensityShaderCode
        const renderItem =  createComputeRenderItem(webgl, 'points', shaderCode, schema, values)
    
        return createComputeRenderable(renderItem, values)
    }
    
    function setRenderingDefaults(ctx: WebGLContext) {
        const { gl, state } = ctx
        state.disable(gl.CULL_FACE)
        state.enable(gl.BLEND)
        state.disable(gl.DEPTH_TEST)
        state.disable(gl.SCISSOR_TEST)
        state.depthMask(false)
        state.clearColor(0, 0, 0, 0)
    }
    
    function setupMinDistanceRendering(webgl: WebGLContext, renderable: ComputeRenderable<any>) {
        const { gl, state } = webgl
        ValueCell.update(renderable.values.dCalcType, 'minDistance')
        renderable.update()
        state.colorMask(false, false, false, true)
        state.blendFunc(gl.ONE, gl.ONE)
        // the shader writes 1 - dist so we set blending to MAX
        if (!webgl.extensions.blendMinMax) {
            throw new Error('GPU gaussian surface calculation requires EXT_blend_minmax')
        }
        state.blendEquation(webgl.extensions.blendMinMax.MAX)
    }
    
    function setupDensityRendering(webgl: WebGLContext, renderable: ComputeRenderable<any>) {
        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>) {
        const { gl, state } = webgl
        ValueCell.update(renderable.values.dCalcType, 'groupId')
        renderable.update()
        // overwrite color, don't change alpha
        state.colorMask(true, true, true, false)
        state.blendFunc(gl.ONE, gl.ZERO)
        state.blendEquation(gl.FUNC_ADD)
    }
    
    function getTexture2dSize(gridDim: Vec3) {
        const area = gridDim[0] * gridDim[1] * gridDim[2]
        const squareDim = Math.sqrt(area)
        const powerOfTwoSize = Math.pow(2, Math.ceil(Math.log(squareDim) / Math.log(2)))
    
        let texDimX = 0
        let texDimY = gridDim[1]
        let texRows = 1
        let texCols = gridDim[2]
        if (powerOfTwoSize < gridDim[0] * gridDim[2]) {
            texCols = Math.floor(powerOfTwoSize / gridDim[0])
            texRows = Math.ceil(gridDim[2] / texCols)
            texDimX = texCols * gridDim[0]
            texDimY *= texRows
        } else {
            texDimX = gridDim[0] * gridDim[2]
        }
        return { texDimX, texDimY, texRows, texCols, powerOfTwoSize: texDimY < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2 }
    }
    
    export function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec3, texDim: Vec3) {
        // console.time('fieldFromTexture2d')
        const { framebufferCache } = ctx
        const [ dx, dy, dz ] = dim
        // const { width, height } = texture
        const [ width, height ] = texDim
        const fboTexCols = Math.floor(width / dx)
    
        const space = Tensor.Space(dim, [2, 1, 0], Float32Array)
        const data = space.create()
        const field = Tensor.create(space, data)
        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 framebuffer = framebufferCache.get(FramebufferName).value
        framebuffer.bind()
        texture.attachFramebuffer(framebuffer, 0)
        ctx.readPixels(0, 0, width, height, image)
    
        // printImageData(createImageData(image, width, height), 1/3)
    
        let j = 0
        let tmpCol = 0
        let tmpRow = 0
        for (let iz = 0; iz < dz; ++iz) {
            if (tmpCol >= fboTexCols ) {
                tmpCol = 0
                tmpRow += dy
            }
            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)
                    j++
                }
            }
            tmpCol++
        }
    
        // console.timeEnd('fieldFromTexture2d')
    
        return { field, idField }
    }