Browse Source

marker-data improvements

- add uniform marker type
- check if a loci is superset of a visual
- special case to improve reversing the previous mark
Alexander Rose 3 years ago
parent
commit
bad6d030f1

+ 11 - 1
src/mol-geo/geometry/marker-data.ts

@@ -9,8 +9,10 @@ import { Vec2 } from '../../mol-math/linear-algebra';
 import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
 
 export type MarkerData = {
+    uMarker: ValueCell<number>,
     tMarker: ValueCell<TextureImage<Uint8Array>>
     uMarkerTexDim: ValueCell<Vec2>
+    dMarkerType: ValueCell<string>,
     markerAverage: ValueCell<number>
     markerStatus: ValueCell<number>
 }
@@ -19,7 +21,7 @@ export function getMarkersAverage(array: Uint8Array, count: number): number {
     if (count === 0) return 0;
     let sum = 0;
     for (let i = 0; i < count; ++i) {
-        if (array[i]) sum += 1;
+        sum += +!!array[i];
     }
     return sum / count;
 }
@@ -29,17 +31,21 @@ export function createMarkers(count: number, markerData?: MarkerData): MarkerDat
     const average = getMarkersAverage(markers.array, count);
     const status = average === 0 ? 0 : -1;
     if (markerData) {
+        ValueCell.updateIfChanged(markerData.uMarker, 0);
         ValueCell.update(markerData.tMarker, markers);
         ValueCell.update(markerData.uMarkerTexDim, Vec2.create(markers.width, markers.height));
+        ValueCell.updateIfChanged(markerData.dMarkerType, status === -1 ? 'groupInstance' : 'uniform');
         ValueCell.updateIfChanged(markerData.markerAverage, average);
         ValueCell.updateIfChanged(markerData.markerStatus, status);
         return markerData;
     } else {
         return {
+            uMarker: ValueCell.create(0),
             tMarker: ValueCell.create(markers),
             uMarkerTexDim: ValueCell.create(Vec2.create(markers.width, markers.height)),
             markerAverage: ValueCell.create(average),
             markerStatus: ValueCell.create(status),
+            dMarkerType: ValueCell.create('uniform'),
         };
     }
 }
@@ -47,17 +53,21 @@ export function createMarkers(count: number, markerData?: MarkerData): MarkerDat
 const emptyMarkerTexture = { array: new Uint8Array(1), width: 1, height: 1 };
 export function createEmptyMarkers(markerData?: MarkerData): MarkerData {
     if (markerData) {
+        ValueCell.updateIfChanged(markerData.uMarker, 0);
         ValueCell.update(markerData.tMarker, emptyMarkerTexture);
         ValueCell.update(markerData.uMarkerTexDim, Vec2.create(1, 1));
+        ValueCell.updateIfChanged(markerData.dMarkerType, 'uniform');
         ValueCell.updateIfChanged(markerData.markerAverage, 0);
         ValueCell.updateIfChanged(markerData.markerStatus, 0);
         return markerData;
     } else {
         return {
+            uMarker: ValueCell.create(0),
             tMarker: ValueCell.create(emptyMarkerTexture),
             uMarkerTexDim: ValueCell.create(Vec2.create(1, 1)),
             markerAverage: ValueCell.create(0),
             markerStatus: ValueCell.create(0),
+            dMarkerType: ValueCell.create('uniform'),
         };
     }
 }

+ 2 - 0
src/mol-gl/renderable/schema.ts

@@ -210,8 +210,10 @@ export type SizeSchema = typeof SizeSchema
 export type SizeValues = Values<SizeSchema>
 
 export const MarkerSchema = {
+    uMarker: UniformSpec('f', 'material'),
     uMarkerTexDim: UniformSpec('v2'),
     tMarker: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
+    dMarkerType: DefineSpec('string', ['uniform', 'groupInstance']),
     markerAverage: ValueSpec('number'),
     markerStatus: ValueSpec('number'),
 } as const;

+ 0 - 1
src/mol-gl/shader/chunks/apply-marker-color.glsl.ts

@@ -1,5 +1,4 @@
 export const apply_marker_color = `
-float marker = floor(vMarker * 255.0 + 0.5); // rounding required to work on some cards on win
 if (marker > 0.1) {
     if (intMod(marker, 2.0) > 0.1) {
         gl_FragColor.rgb = mix(gl_FragColor.rgb, uHighlightColor, uHighlightStrength);

+ 3 - 1
src/mol-gl/shader/chunks/assign-marker-varying.glsl.ts

@@ -1,3 +1,5 @@
 export const assign_marker_varying = `
-vMarker = readFromTexture(tMarker, aInstance * float(uGroupCount) + group, uMarkerTexDim).a;
+#if defined(dMarkerType_groupInstance)
+    vMarker = readFromTexture(tMarker, aInstance * float(uGroupCount) + group, uMarkerTexDim).a;
+#endif
 `;

+ 12 - 3
src/mol-gl/shader/chunks/assign-material-color.glsl.ts

@@ -1,4 +1,13 @@
 export const assign_material_color = `
+#if defined(dRenderVariant_color) || defined(dRenderVariant_marking)
+    #if defined(dMarkerType_uniform)
+        float marker = uMarker;
+    #elif defined(dMarkerType_groupInstance)
+        float marker = vMarker;
+    #endif
+    marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
+#endif
+
 #if defined(dRenderVariant_color)
     #if defined(dUsePalette)
         vec4 material = vec4(texture2D(tPalette, vec2(vPaletteV, 0.5)).rgb, uAlpha);
@@ -21,7 +30,7 @@ export const assign_material_color = `
         vec4 material = packDepthToRGBA(gl_FragCoord.z);
     #endif
 #elif defined(dRenderVariant_markingDepth)
-    if (vMarker > 0.0)
+    if (marker > 0.0)
         discard;
     #ifdef enabledFragDepth
         vec4 material = packDepthToRGBA(gl_FragDepthEXT);
@@ -29,13 +38,13 @@ export const assign_material_color = `
         vec4 material = packDepthToRGBA(gl_FragCoord.z);
     #endif
 #elif defined(dRenderVariant_markingMask)
-    if (vMarker == 0.0)
+    if (marker == 0.0)
         discard;
     float depthTest = 1.0;
     if (uMarkingDepthTest) {
         depthTest = (fragmentDepth >= getDepth(gl_FragCoord.xy / uDrawingBufferSize)) ? 1.0 : 0.0;
     }
-    bool isHighlight = intMod(floor(vMarker * 255.0 + 0.5), 2.0) > 0.1;
+    bool isHighlight = intMod(marker, 2.0) > 0.1;
     vec4 material = vec4(0.0, depthTest, isHighlight ? 1.0 : 0.0, 1.0);
 #endif
 

+ 9 - 4
src/mol-gl/shader/chunks/common-frag-params.glsl.ts

@@ -23,10 +23,15 @@ uniform vec3 uHighlightColor;
 uniform vec3 uSelectColor;
 uniform float uHighlightStrength;
 uniform float uSelectStrength;
-#if __VERSION__ == 100
-    varying float vMarker;
-#else
-    flat in float vMarker;
+
+#if defined(dMarkerType_uniform)
+    uniform float uMarker;
+#elif defined(dMarkerType_groupInstance)
+    #if __VERSION__ == 100
+        varying float vMarker;
+    #else
+        flat in float vMarker;
+    #endif
 #endif
 
 varying vec3 vModelPosition;

+ 10 - 6
src/mol-gl/shader/chunks/common-vert-params.glsl.ts

@@ -26,12 +26,16 @@ uniform vec4 uInvariantBoundingSphere;
     #endif
 #endif
 
-uniform vec2 uMarkerTexDim;
-uniform sampler2D tMarker;
-#if __VERSION__ == 100
-    varying float vMarker;
-#else
-    flat out float vMarker;
+#if defined(dMarkerType_uniform)
+    uniform float uMarker;
+#elif defined(dMarkerType_groupInstance)
+    uniform vec2 uMarkerTexDim;
+    uniform sampler2D tMarker;
+    #if __VERSION__ == 100
+        varying float vMarker;
+    #else
+        flat out float vMarker;
+    #endif
 #endif
 
 varying vec3 vModelPosition;

+ 24 - 10
src/mol-gl/shader/direct-volume.frag.ts

@@ -55,8 +55,13 @@ uniform vec3 uHighlightColor;
 uniform vec3 uSelectColor;
 uniform float uHighlightStrength;
 uniform float uSelectStrength;
-uniform vec2 uMarkerTexDim;
-uniform sampler2D tMarker;
+
+#if defined(dMarkerType_uniform)
+    uniform float uMarker;
+#elif defined(dMarkerType_groupInstance)
+    uniform vec2 uMarkerTexDim;
+    uniform sampler2D tMarker;
+#endif
 
 uniform float uFogNear;
 uniform float uFogFar;
@@ -329,7 +334,12 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
                         #include apply_light_color
                     #endif
 
-                    float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
+                    #if defined(dMarkerType_uniform)
+                        float marker = uMarker;
+                    #elif defined(dMarkerType_groupInstance)
+                        float marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
+                        marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
+                    #endif
                     #include apply_interior_color
                     #include apply_marker_color
 
@@ -392,14 +402,18 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) {
 
                 gl_FragColor.a = material.a * uAlpha * uTransferScale;
 
-                #ifdef dPackedGroup
-                    float group = decodeFloatRGB(textureGroup(floor(unitPos * uGridDim + 0.5) / uGridDim).rgb);
-                #else
-                    vec3 g = floor(unitPos * uGridDim + 0.5);
-                    float group = g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y;
+                #if defined(dMarkerType_uniform)
+                    float marker = uMarker;
+                #elif defined(dMarkerType_groupInstance)
+                    #ifdef dPackedGroup
+                        float group = decodeFloatRGB(textureGroup(floor(unitPos * uGridDim + 0.5) / uGridDim).rgb);
+                    #else
+                        vec3 g = floor(unitPos * uGridDim + 0.5);
+                        float group = g.z + g.y * uGridDim.z + g.x * uGridDim.z * uGridDim.y;
+                    #endif
+                    float marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
+                    marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
                 #endif
-
-                float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
                 #include apply_marker_color
 
                 preFogAlphaBlended = (1.0 - preFogAlphaBlended) * gl_FragColor.a + preFogAlphaBlended;

+ 17 - 7
src/mol-gl/shader/image.frag.ts

@@ -116,20 +116,25 @@ void main() {
             discard;
         gl_FragColor = packDepthToRGBA(gl_FragCoord.z);
     #elif defined(dRenderVariant_marking)
-        float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb);
-        float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
+        #if defined(dMarkerType_uniform)
+            float marker = uMarker;
+        #elif defined(dMarkerType_groupInstance)
+            float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb);
+            float marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
+            marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
+        #endif
         #if defined(dRenderVariant_markingDepth)
-            if (vMarker > 0.0 || imageData.a < 0.05)
+            if (marker > 0.0 || imageData.a < 0.05)
                 discard;
             gl_FragColor = packDepthToRGBA(gl_FragCoord.z);
         #elif defined(dRenderVariant_markingMask)
-            if (vMarker == 0.0 || imageData.a < 0.05)
+            if (marker == 0.0 || imageData.a < 0.05)
                 discard;
             float depthTest = 1.0;
             if (uMarkingDepthTest) {
                 depthTest = (fragmentDepth >= getDepth(gl_FragCoord.xy / uDrawingBufferSize)) ? 1.0 : 0.0;
             }
-            bool isHighlight = intMod(floor(vMarker * 255.0 + 0.5), 2.0) > 0.1;
+            bool isHighlight = intMod(marker, 2.0) > 0.1;
             gl_FragColor = vec4(0.0, depthTest, isHighlight ? 1.0 : 0.0, 1.0);
         #endif
     #elif defined(dRenderVariant_color)
@@ -138,8 +143,13 @@ void main() {
         gl_FragColor = imageData;
         gl_FragColor.a *= uAlpha;
 
-        float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb);
-        float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
+        #if defined(dMarkerType_uniform)
+            float marker = uMarker;
+        #elif defined(dMarkerType_groupInstance)
+            float group = decodeFloatRGB(texture2D(tGroupTex, vUv).rgb);
+            float marker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a;
+            marker = floor(marker * 255.0 + 0.5); // rounding required to work on some cards on win
+        #endif
 
         #include apply_marker_color
         #include apply_fog

+ 2 - 1
src/mol-repr/structure/complex-visual.ts

@@ -67,6 +67,7 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
     const { defaultProps, createGeometry, createLocationIterator, getLoci, eachLocation, setUpdateState, mustRecreate, processValues, dispose } = builder;
     const { updateValues, updateBoundingSphere, updateRenderableState, createPositionIterator } = builder.geometryUtils;
     const updateState = VisualUpdateState.create();
+    const previousMark: Visual.PreviousMark = { loci: EmptyLoci, action: MarkerAction.None, status: -1 };
 
     let renderObject: GraphicsRenderObject<G['kind']> | undefined;
 
@@ -235,7 +236,7 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
             return renderObject ? getLoci(pickingId, currentStructure, renderObject.id) : EmptyLoci;
         },
         mark(loci: Loci, action: MarkerAction) {
-            return Visual.mark(renderObject, loci, action, lociApply);
+            return Visual.mark(renderObject, loci, action, lociApply, previousMark);
         },
         setVisibility(visible: boolean) {
             Visual.setVisibility(renderObject, visible);

+ 2 - 1
src/mol-repr/structure/units-visual.ts

@@ -71,6 +71,7 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
     const { defaultProps, createGeometry, createLocationIterator, getLoci, eachLocation, setUpdateState, mustRecreate, processValues, dispose } = builder;
     const { createEmpty: createEmptyGeometry, updateValues, updateBoundingSphere, updateRenderableState, createPositionIterator } = builder.geometryUtils;
     const updateState = VisualUpdateState.create();
+    const previousMark: Visual.PreviousMark = { loci: EmptyLoci, action: MarkerAction.None, status: -1 };
 
     let renderObject: GraphicsRenderObject<G['kind']> | undefined;
 
@@ -289,7 +290,7 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
             return renderObject ? getLoci(pickingId, currentStructureGroup, renderObject.id) : EmptyLoci;
         },
         mark(loci: Loci, action: MarkerAction) {
-            return Visual.mark(renderObject, loci, action, lociApply);
+            return Visual.mark(renderObject, loci, action, lociApply, previousMark);
         },
         setVisibility(visible: boolean) {
             Visual.setVisibility(renderObject, visible);

+ 44 - 13
src/mol-repr/visual.ts

@@ -7,8 +7,8 @@
 import { RuntimeContext } from '../mol-task';
 import { GraphicsRenderObject } from '../mol-gl/render-object';
 import { PickingId } from '../mol-geo/geometry/picking';
-import { Loci, isEmptyLoci, isEveryLoci } from '../mol-model/loci';
-import { MarkerAction, applyMarkerAction, getMarkerInfo } from '../mol-util/marker-action';
+import { Loci, isEmptyLoci, isEveryLoci, EveryLoci } from '../mol-model/loci';
+import { MarkerAction, applyMarkerAction, getMarkerInfo, setMarkerValue, getPartialMarkerAverage, MarkerActions, MarkerInfo } from '../mol-util/marker-action';
 import { ParamDefinition as PD } from '../mol-util/param-definition';
 import { WebGLContext } from '../mol-gl/webgl/context';
 import { Theme } from '../mol-theme/theme';
@@ -68,32 +68,63 @@ namespace Visual {
         if (renderObject) renderObject.state.colorOnly = colorOnly;
     }
 
-    export function mark(renderObject: GraphicsRenderObject | undefined, loci: Loci, action: MarkerAction, lociApply: LociApply) {
-        if (!renderObject) return false;
+    export type PreviousMark = { loci: Loci, action: MarkerAction, status: MarkerInfo['status'] }
 
-        const { tMarker, markerAverage, markerStatus, uGroupCount, instanceCount } = renderObject.values;
+    export function mark(renderObject: GraphicsRenderObject | undefined, loci: Loci, action: MarkerAction, lociApply: LociApply, previous?: PreviousMark) {
+        if (!renderObject || isEmptyLoci(loci)) return false;
+
+        const { tMarker, dMarkerType, uMarker, markerAverage, markerStatus, uGroupCount, instanceCount } = renderObject.values;
         const count = uGroupCount.ref.value * instanceCount.ref.value;
         const { array } = tMarker.ref.value;
 
+        if (!isEveryLoci(loci)) {
+            let intervalSize = 0;
+            lociApply(loci, interval => {
+                intervalSize += Interval.size(interval);
+                return true;
+            }, true);
+            if (intervalSize === count) loci = EveryLoci;
+        }
+
         let changed = false;
         let average = -1;
-        let status = -1;
+        let status: MarkerInfo['status'] = -1;
         if (isEveryLoci(loci)) {
-            changed = applyMarkerAction(array, Interval.ofLength(count), action);
-            if (changed) {
-                const info = getMarkerInfo(action, markerStatus.ref.value);
-                average = info.average;
-                status = info.status;
+            const info = getMarkerInfo(action, markerStatus.ref.value);
+            if (info.status !== -1) {
+                changed = markerStatus.ref.value !== info.status;
+                if (changed) setMarkerValue(array, info.status, count);
+            } else {
+                changed = applyMarkerAction(array, Interval.ofLength(count), action);
             }
-        } else if (!isEmptyLoci(loci)) {
+            average = info.average;
+            status = info.status;
+        } else {
             changed = lociApply(loci, interval => applyMarkerAction(array, interval, action), true);
+            if (changed) {
+                average = getPartialMarkerAverage(action, markerStatus.ref.value);
+                if (previous && previous.status !== -1 && average === -1 &&
+                    MarkerActions.isReverse(previous.action, action) &&
+                    Loci.areEqual(loci, previous.loci)
+                ) {
+                    status = previous.status;
+                    average = status === 0 ? 0 : 1;
+                }
+            }
         }
         if (changed) {
             if (average === -1) {
                 average = getMarkersAverage(array, count);
                 if (average === 0) status = 0;
             }
-            ValueCell.update(tMarker, tMarker.ref.value);
+            if (previous) {
+                previous.action = action;
+                previous.loci = loci;
+                previous.status = markerStatus.ref.value as MarkerInfo['status'];
+            }
+            ValueCell.updateIfChanged(uMarker, status);
+            if (status === -1) ValueCell.update(tMarker, tMarker.ref.value);
+            ValueCell.updateIfChanged(dMarkerType, status === -1 ? 'groupInstance' : 'uniform');
             ValueCell.updateIfChanged(markerAverage, average);
             ValueCell.updateIfChanged(markerStatus, status);
         }

+ 68 - 0
src/mol-util/marker-action.ts

@@ -35,6 +35,20 @@ export namespace MarkerActions {
         MarkerAction.Select | MarkerAction.Deselect | MarkerAction.Toggle |
         MarkerAction.Clear
     ) as MarkerActions;
+
+    export function isReverse(a: MarkerAction, b: MarkerAction) {
+        return (
+            (a === MarkerAction.Highlight && b === MarkerAction.RemoveHighlight) ||
+            (a === MarkerAction.RemoveHighlight && b === MarkerAction.Highlight) ||
+            (a === MarkerAction.Select && b === MarkerAction.Deselect) ||
+            (a === MarkerAction.Deselect && b === MarkerAction.Select) ||
+            (a === MarkerAction.Toggle && b === MarkerAction.Toggle)
+        );
+    }
+}
+
+export function setMarkerValue(array: Uint8Array, status: 0 | 1 | 2 | 3, count: number) {
+    array.fill(status, 0, count);
 }
 
 export function applyMarkerActionAtPosition(array: Uint8Array, i: number, action: MarkerAction) {
@@ -204,4 +218,58 @@ export function getMarkerInfo(action: MarkerAction, currentStatus: number): Mark
             break;
     }
     return { average, status };
+}
+
+/**
+ * Assumes the action is applied to a partial set that is
+ * neither the empty set nor the full set.
+ */
+export function getPartialMarkerAverage(action: MarkerAction, currentStatus: number): MarkerInfo['average'] {
+    switch (action) {
+        case MarkerAction.Highlight:
+            return 1;
+        case MarkerAction.RemoveHighlight:
+            if (currentStatus === 0) {
+                return 0;
+            } else if (currentStatus === 1) {
+                return -1;
+            } else if (currentStatus === 2 || currentStatus === 3) {
+                return 1;
+            }
+            return -1;
+        case MarkerAction.Select:
+            return 1;
+        case MarkerAction.Deselect:
+            if (currentStatus === 1 || currentStatus === 3) {
+                return 1;
+            } else if (currentStatus === 0) {
+                return 0;
+            } else if (currentStatus === 2) {
+                return -1;
+            }
+            return -1;
+        case MarkerAction.Toggle:
+            if (currentStatus === 1) {
+                return 1;
+            } else if (currentStatus === 2) {
+                return 1;
+            } else if (currentStatus === 3) {
+                return 1;
+            } else if (currentStatus === 0) {
+                return 1;
+            }
+            return -1;
+        case MarkerAction.Clear:
+            if (currentStatus === 1) {
+                return 1;
+            } else if (currentStatus === 2) {
+                return 1;
+            } else if (currentStatus === 3) {
+                return 1;
+            } else if (currentStatus === 0) {
+                return 0;
+            }
+            return -1;
+    }
+    return -1;
 }