From 3d21f1ecc6f989fe53215d93ef2262c2ab86aadb Mon Sep 17 00:00:00 2001 From: Aron Kovacs <s.aron.kovacs@gmail.com> Date: Fri, 13 Nov 2020 15:01:57 +0100 Subject: [PATCH] patch and background transparency --- src/mol-canvas3d/passes/draw.ts | 2 +- src/mol-geo/geometry/transparency-data.ts | 12 +++ src/mol-gl/renderable.ts | 2 +- src/mol-gl/renderable/direct-volume.ts | 1 + src/mol-gl/renderable/schema.ts | 1 + src/mol-gl/renderer.ts | 90 ++++++++++------------- src/mol-gl/shader-code.ts | 14 ++-- src/mol-gl/shader/evaluate-wboit.frag.ts | 3 +- src/mol-gl/webgl/context.ts | 3 + src/mol-gl/webgl/program.ts | 4 +- src/mol-repr/visual.ts | 5 +- 11 files changed, 70 insertions(+), 67 deletions(-) diff --git a/src/mol-canvas3d/passes/draw.ts b/src/mol-canvas3d/passes/draw.ts index 1c707adc7..3c72b628b 100644 --- a/src/mol-canvas3d/passes/draw.ts +++ b/src/mol-canvas3d/passes/draw.ts @@ -126,7 +126,7 @@ export class DrawPass { // console.log('toDrawingBuffer', toDrawingBuffer); - for (let i = 0; i < this.webgl.gl.getParameter(this.webgl.gl.MAX_TEXTURE_IMAGE_UNITS); i++) { + for (let i = 0; i < this.webgl.maxTextureImageUnits; i++) { this.webgl.gl.activeTexture(this.webgl.gl.TEXTURE0 + i); this.webgl.gl.bindTexture(this.webgl.gl.TEXTURE_2D, null); } diff --git a/src/mol-geo/geometry/transparency-data.ts b/src/mol-geo/geometry/transparency-data.ts index 22879551b..85e798135 100644 --- a/src/mol-geo/geometry/transparency-data.ts +++ b/src/mol-geo/geometry/transparency-data.ts @@ -13,6 +13,7 @@ export type TransparencyData = { uTransparencyTexDim: ValueCell<Vec2> dTransparency: ValueCell<boolean>, dTransparencyVariant: ValueCell<string>, + transparencyAverage: ValueCell<number>, } export function applyTransparencyValue(array: Uint8Array, start: number, end: number, value: number) { @@ -22,6 +23,14 @@ export function applyTransparencyValue(array: Uint8Array, start: number, end: nu return true; } +export function getTransparencyAverage(array: Uint8Array, count: number): number { + let sum = 0; + for (let i = 0; i < count; ++i) { + sum += array[i]; + } + return sum / (255 * count); +} + export function clearTransparency(array: Uint8Array, start: number, end: number) { array.fill(0, start, end); } @@ -32,6 +41,7 @@ export function createTransparency(count: number, transparencyData?: Transparenc ValueCell.update(transparencyData.tTransparency, transparency); ValueCell.update(transparencyData.uTransparencyTexDim, Vec2.create(transparency.width, transparency.height)); ValueCell.updateIfChanged(transparencyData.dTransparency, count > 0); + ValueCell.updateIfChanged(transparencyData.transparencyAverage, getTransparencyAverage(transparency.array, count)); return transparencyData; } else { return { @@ -39,6 +49,7 @@ export function createTransparency(count: number, transparencyData?: Transparenc uTransparencyTexDim: ValueCell.create(Vec2.create(transparency.width, transparency.height)), dTransparency: ValueCell.create(count > 0), dTransparencyVariant: ValueCell.create('single'), + transparencyAverage: ValueCell.create(0), }; } } @@ -55,6 +66,7 @@ export function createEmptyTransparency(transparencyData?: TransparencyData): Tr uTransparencyTexDim: ValueCell.create(Vec2.create(1, 1)), dTransparency: ValueCell.create(false), dTransparencyVariant: ValueCell.create('single'), + transparencyAverage: ValueCell.create(0), }; } } \ No newline at end of file diff --git a/src/mol-gl/renderable.ts b/src/mol-gl/renderable.ts index ad28d9262..d9ffb4561 100644 --- a/src/mol-gl/renderable.ts +++ b/src/mol-gl/renderable.ts @@ -20,7 +20,7 @@ export type RenderableState = { pickable: boolean colorOnly: boolean opaque: boolean - writeDepth: boolean, + writeDepth: boolean } export interface Renderable<T extends RenderableValues> { diff --git a/src/mol-gl/renderable/direct-volume.ts b/src/mol-gl/renderable/direct-volume.ts index 79b954ce9..a0a5fde42 100644 --- a/src/mol-gl/renderable/direct-volume.ts +++ b/src/mol-gl/renderable/direct-volume.ts @@ -28,6 +28,7 @@ export const DirectVolumeSchema = { tTransparency: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'), dTransparency: DefineSpec('boolean'), dTransparencyVariant: DefineSpec('string', ['single', 'multi']), + transparencyAverage: ValueSpec('number'), dClipObjectCount: DefineSpec('number'), dClipVariant: DefineSpec('string', ['instance', 'pixel']), diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index fa0927a85..40f0c0854 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/src/mol-gl/renderable/schema.ts @@ -214,6 +214,7 @@ export const TransparencySchema = { tTransparency: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'), dTransparency: DefineSpec('boolean'), dTransparencyVariant: DefineSpec('string', ['single', 'multi']), + transparencyAverage: ValueSpec('number'), } as const; export type TransparencySchema = typeof TransparencySchema export type TransparencyValues = Values<TransparencySchema> diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index 0cd846303..1fc80a22a 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -169,7 +169,7 @@ namespace Renderer { const style = getStyle(p.style); const clip = getClip(p.clip); - const { drawBuffers, textureFloat, colorBufferFloat, depthTexture } = ctx.extensions; + const { drawBuffers, textureFloat, colorBufferFloat, depthTexture, fragDepth } = ctx.extensions; const viewport = Viewport(); const drawingBufferSize = Vec2.create(gl.drawingBufferWidth, gl.drawingBufferHeight); @@ -177,7 +177,7 @@ namespace Renderer { const sharedTexturesList: Textures = []; - const enableWboit = textureFloat !== null && colorBufferFloat !== null && depthTexture !== null; + const enableWboit = textureFloat && colorBufferFloat && depthTexture && drawBuffers; const wboitATexture = enableWboit ? resources.texture('image-float32', 'rgba', 'float', 'nearest') : null; wboitATexture?.define(viewport.width, viewport.height); @@ -186,25 +186,16 @@ namespace Renderer { const evaluateWboitRenderable = enableWboit ? getEvaluateWboitRenderable(ctx, wboitATexture!, wboitBTexture!) : null; - const wboitFramebuffers = [resources.framebuffer()]; + const wboitFramebuffer = resources.framebuffer(); if (enableWboit) { - if (drawBuffers) { - wboitFramebuffers.push(resources.framebuffer()); - - wboitFramebuffers[0].bind(); - drawBuffers.drawBuffers([ - drawBuffers.COLOR_ATTACHMENT0, - drawBuffers.COLOR_ATTACHMENT1, - ]); - - wboitATexture!.attachFramebuffer(wboitFramebuffers[0], 'color0'); - wboitBTexture!.attachFramebuffer(wboitFramebuffers[0], 'color1'); - } else { - wboitFramebuffers.push(resources.framebuffer(), resources.framebuffer()); - - wboitATexture!.attachFramebuffer(wboitFramebuffers[0], 'color0'); - wboitBTexture!.attachFramebuffer(wboitFramebuffers[1], 'color0'); - } + wboitFramebuffer.bind(); + drawBuffers!.drawBuffers([ + drawBuffers!.COLOR_ATTACHMENT0, + drawBuffers!.COLOR_ATTACHMENT1, + ]); + + wboitATexture!.attachFramebuffer(wboitFramebuffer, 'color0'); + wboitBTexture!.attachFramebuffer(wboitFramebuffer, 'color1'); } const view = Mat4(); @@ -312,6 +303,18 @@ namespace Renderer { // culling done in fragment shader state.disable(gl.CULL_FACE); state.frontFace(gl.CCW); + + if (!enableWboit) { + // depth test done manually in shader against `depthTexture` + // still need to enable when fragDepth can be used to write depth + if (r.values.dRenderMode.ref.value === 'volume' || !fragDepth) { + state.disable(gl.DEPTH_TEST); + state.depthMask(false); + } else { + state.enable(gl.DEPTH_TEST); + state.depthMask(r.state.writeDepth); + } + } } else { if (r.values.dDoubleSided) { if (r.values.dDoubleSided.ref.value || r.values.hasReflection.ref.value) { @@ -337,6 +340,8 @@ namespace Renderer { state.frontFace(gl.CCW); state.cullFace(gl.BACK); } + + if (!enableWboit) state.depthMask(r.state.writeDepth); } r.render(variant, sharedTexturesList); @@ -347,7 +352,6 @@ namespace Renderer { if (depthTexture) { localSharedTexturesList = [...localSharedTexturesList, ['tDepth', depthTexture]]; } - // console.log('depthTexture', depthTexture); ValueCell.update(globalUniforms.uModel, group.view); ValueCell.update(globalUniforms.uView, camera.view); @@ -404,7 +408,6 @@ namespace Renderer { gl.scissor(x, y, width, height); if (clear) { - state.depthMask(true); if (variant === 'color') { state.clearColor(bgColor[0], bgColor[1], bgColor[2], transparentBackground ? 0 : 1); } else { @@ -420,12 +423,13 @@ namespace Renderer { state.depthMask(true); for (let i = 0, il = renderables.length; i < il; ++i) { - // TODO: when available in r.state check if fullyOpaque const r = renderables[i]; - renderObject(r, variant, localSharedTexturesList); + if (r.values.uAlpha.ref.value === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values?.dRenderMode?.ref.value !== 'volume') { + renderObject(r, variant, localSharedTexturesList); + } } } else { - wboitFramebuffers[0].bind(); + wboitFramebuffer.bind(); state.clearColor(0, 0, 0, 1); gl.clear(gl.COLOR_BUFFER_BIT); @@ -439,9 +443,10 @@ namespace Renderer { state.enable(gl.BLEND); for (let i = 0, il = renderables.length; i < il; ++i) { - // TODO: when available in r.state check if not fullyOpaque const r = renderables[i]; - renderObject(r, variant, localSharedTexturesList); + if (r.values.uAlpha.ref.value < 1 || r.values.transparencyAverage.ref.value !== 0) { + renderObject(r, variant, localSharedTexturesList); + } } if (renderTarget) { @@ -450,7 +455,7 @@ namespace Renderer { gl.bindFramebuffer(gl.FRAMEBUFFER, null); } - state.blendFuncSeparate(gl.ONE_MINUS_SRC_ALPHA, gl.SRC_ALPHA, gl.ZERO, gl.ONE); + state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE); state.enable(gl.BLEND); // unclear why needed but otherwise there is a @@ -572,33 +577,12 @@ namespace Renderer { ValueCell.update(globalUniforms.uViewportHeight, height); ValueCell.update(globalUniforms.uViewport, Vec4.set(globalUniforms.uViewport.ref.value, x, y, width, height)); + // TODO: this gets called every frame... in any case, we will need to move + // the wboit resources (framebuffer, textures, renderable) eventually + // to the draw pass to be more efficient when having >100 viewports + // rendered to a single canvas every frame. wboitATexture?.define(viewport.width, viewport.height); wboitBTexture?.define(viewport.width, viewport.height); - - // TODO there should not be any need to re-create the framebuffers, - // re-sizing the textures should do it - if (drawBuffers) { - wboitFramebuffers[0].destroy(); - wboitFramebuffers.length = 0; - wboitFramebuffers.push(resources.framebuffer()); - - wboitFramebuffers[0].bind(); - drawBuffers?.drawBuffers([ - drawBuffers.COLOR_ATTACHMENT0, - drawBuffers.COLOR_ATTACHMENT1, - ]); - - wboitATexture?.attachFramebuffer(wboitFramebuffers[0], 'color0'); - wboitBTexture?.attachFramebuffer(wboitFramebuffers[0], 'color1'); - } else { - wboitFramebuffers[0].destroy(); - wboitFramebuffers[1].destroy(); - wboitFramebuffers.length = 0; - wboitFramebuffers.push(resources.framebuffer(), resources.framebuffer()); - - wboitATexture?.attachFramebuffer(wboitFramebuffers[0], 'color0'); - wboitBTexture?.attachFramebuffer(wboitFramebuffers[1], 'color0'); - } } }, diff --git a/src/mol-gl/shader-code.ts b/src/mol-gl/shader-code.ts index 1322623f0..c43abeac4 100644 --- a/src/mol-gl/shader-code.ts +++ b/src/mol-gl/shader-code.ts @@ -119,31 +119,31 @@ export function ShaderCode(name: string, vert: string, frag: string, extensions: import points_vert from './shader/points.vert'; import points_frag from './shader/points.frag'; -export const PointsShaderCode = ShaderCode('points', points_vert, points_frag); +export const PointsShaderCode = ShaderCode('points', points_vert, points_frag, { drawBuffers: 'optional' }); import spheres_vert from './shader/spheres.vert'; import spheres_frag from './shader/spheres.frag'; -export const SpheresShaderCode = ShaderCode('spheres', spheres_vert, spheres_frag, { fragDepth: 'required' }); +export const SpheresShaderCode = ShaderCode('spheres', spheres_vert, spheres_frag, { fragDepth: 'required', drawBuffers: 'optional' }); import text_vert from './shader/text.vert'; import text_frag from './shader/text.frag'; -export const TextShaderCode = ShaderCode('text', text_vert, text_frag, { standardDerivatives: 'required' }); +export const TextShaderCode = ShaderCode('text', text_vert, text_frag, { standardDerivatives: 'required', drawBuffers: 'optional' }); import lines_vert from './shader/lines.vert'; import lines_frag from './shader/lines.frag'; -export const LinesShaderCode = ShaderCode('lines', lines_vert, lines_frag); +export const LinesShaderCode = ShaderCode('lines', lines_vert, lines_frag, { drawBuffers: 'optional' }); import mesh_vert from './shader/mesh.vert'; import mesh_frag from './shader/mesh.frag'; -export const MeshShaderCode = ShaderCode('mesh', mesh_vert, mesh_frag, { standardDerivatives: 'optional' }); +export const MeshShaderCode = ShaderCode('mesh', mesh_vert, mesh_frag, { standardDerivatives: 'optional', drawBuffers: 'optional' }); import direct_volume_vert from './shader/direct-volume.vert'; import direct_volume_frag from './shader/direct-volume.frag'; -export const DirectVolumeShaderCode = ShaderCode('direct-volume', direct_volume_vert, direct_volume_frag, { fragDepth: 'optional' }); +export const DirectVolumeShaderCode = ShaderCode('direct-volume', direct_volume_vert, direct_volume_frag, { fragDepth: 'optional', drawBuffers: 'optional' }); import image_vert from './shader/image.vert'; import image_frag from './shader/image.frag'; -export const ImageShaderCode = ShaderCode('image', image_vert, image_frag); +export const ImageShaderCode = ShaderCode('image', image_vert, image_frag, { drawBuffers: 'optional' }); // diff --git a/src/mol-gl/shader/evaluate-wboit.frag.ts b/src/mol-gl/shader/evaluate-wboit.frag.ts index e09ce3407..3f0ad43f0 100644 --- a/src/mol-gl/shader/evaluate-wboit.frag.ts +++ b/src/mol-gl/shader/evaluate-wboit.frag.ts @@ -8,7 +8,8 @@ void main() { ivec2 coords = ivec2(gl_FragCoord.xy); vec4 accum = texelFetch(tWboitA, coords, 0); - float r = accum.a; + float r = 1.0 - accum.a; + accum.a = texelFetch(tWboitB, coords, 0).r; gl_FragColor = vec4(accum.rgb / clamp(accum.a, 0.0001, 50000.0), r); } diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index e9303b859..091a96ec7 100644 --- a/src/mol-gl/webgl/context.ts +++ b/src/mol-gl/webgl/context.ts @@ -186,6 +186,7 @@ export interface WebGLContext { readonly maxTextureSize: number readonly maxRenderbufferSize: number readonly maxDrawBuffers: number + readonly maxTextureImageUnits: number readonly isContextLost: boolean readonly contextRestored: BehaviorSubject<now.Timestamp> @@ -220,6 +221,7 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE) as number, maxRenderbufferSize: gl.getParameter(gl.MAX_RENDERBUFFER_SIZE) as number, maxDrawBuffers: isWebGL2(gl) ? gl.getParameter(gl.MAX_DRAW_BUFFERS) as number : 0, + maxTextureImageUnits: gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) as number, maxVertexTextureImageUnits: gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) as number, }; @@ -285,6 +287,7 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal get maxTextureSize () { return parameters.maxTextureSize; }, get maxRenderbufferSize () { return parameters.maxRenderbufferSize; }, get maxDrawBuffers () { return parameters.maxDrawBuffers; }, + get maxTextureImageUnits () { return parameters.maxTextureImageUnits; }, namedComputeRenderables: Object.create(null), namedFramebuffers: Object.create(null), diff --git a/src/mol-gl/webgl/program.ts b/src/mol-gl/webgl/program.ts index 8d3443d42..0fdfc6c2b 100644 --- a/src/mol-gl/webgl/program.ts +++ b/src/mol-gl/webgl/program.ts @@ -166,8 +166,8 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi uniformSetters = getUniformSetters(schema); if (isDebugMode) { - // checkActiveAttributes(gl, program, schema); - // checkActiveUniforms(gl, program, schema); + checkActiveAttributes(gl, program, schema); + checkActiveUniforms(gl, program, schema); } } init(); diff --git a/src/mol-repr/visual.ts b/src/mol-repr/visual.ts index b163b9f3b..7b1651ac0 100644 --- a/src/mol-repr/visual.ts +++ b/src/mol-repr/visual.ts @@ -20,7 +20,7 @@ import { Overpaint } from '../mol-theme/overpaint'; import { createOverpaint, clearOverpaint, applyOverpaintColor } from '../mol-geo/geometry/overpaint-data'; import { Interval } from '../mol-data/int'; import { Transparency } from '../mol-theme/transparency'; -import { createTransparency, clearTransparency, applyTransparencyValue } from '../mol-geo/geometry/transparency-data'; +import { createTransparency, clearTransparency, applyTransparencyValue, getTransparencyAverage } from '../mol-geo/geometry/transparency-data'; import { Clipping } from '../mol-theme/clipping'; import { createClipping, applyClippingGroups, clearClipping } from '../mol-geo/geometry/clipping-data'; @@ -114,7 +114,7 @@ namespace Visual { export function setTransparency(renderObject: GraphicsRenderObject | undefined, transparency: Transparency, lociApply: LociApply, clear: boolean) { if (!renderObject) return; - const { tTransparency, uGroupCount, instanceCount } = renderObject.values; + const { tTransparency, transparencyAverage, uGroupCount, instanceCount } = renderObject.values; const count = uGroupCount.ref.value * instanceCount.ref.value; // ensure texture has right size and variant @@ -134,6 +134,7 @@ namespace Visual { lociApply(loci, apply, false); } ValueCell.update(tTransparency, tTransparency.ref.value); + ValueCell.updateIfChanged(transparencyAverage, getTransparencyAverage(array, count)); } export function setClipping(renderObject: GraphicsRenderObject | undefined, clipping: Clipping, lociApply: LociApply, clear: boolean) { -- GitLab