mirror of
https://github.com/molstar/molstar.git
synced 2026-06-04 13:30:24 +08:00
Support float and half-float data type
- direct-volume rendering - GPU isosurface extraction
This commit is contained in:
@@ -5,7 +5,7 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- Volume UI improvements
|
||||
- Volume UI improvements
|
||||
- Render all volume entries instead of selecting them one-by-one
|
||||
- Toggle visibility of all volumes
|
||||
- More accessible iso value control
|
||||
@@ -13,6 +13,7 @@ Note that since we don't clearly distinguish between a public and private interf
|
||||
- MolViewSpec extension:
|
||||
- Add validation for discriminated union params
|
||||
- Primitives: remove triangle_colors, line_colors, have implicit grouping instead; rename many parameters
|
||||
- Support float and half-float data type for direct-volume rendering and GPU isosurface extraction
|
||||
|
||||
## [v4.10.0] - 2024-12-15
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ export interface DirectVolume {
|
||||
readonly cartnToUnit: ValueCell<Mat4>
|
||||
readonly packedGroup: ValueCell<boolean>
|
||||
readonly axisOrder: ValueCell<Vec3>
|
||||
readonly dataType: ValueCell<'byte' | 'float' | 'halfFloat'>
|
||||
|
||||
/** Bounding sphere of the volume */
|
||||
readonly boundingSphere: Sphere3D
|
||||
@@ -57,10 +58,10 @@ export interface DirectVolume {
|
||||
}
|
||||
|
||||
export namespace DirectVolume {
|
||||
export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, axisOrder: Vec3, directVolume?: DirectVolume): DirectVolume {
|
||||
export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, axisOrder: Vec3, dataType: 'byte' | 'float' | 'halfFloat', directVolume?: DirectVolume): DirectVolume {
|
||||
return directVolume ?
|
||||
update(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, axisOrder, directVolume) :
|
||||
fromData(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, axisOrder);
|
||||
update(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, axisOrder, dataType, directVolume) :
|
||||
fromData(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, axisOrder, dataType);
|
||||
}
|
||||
|
||||
function hashCode(directVolume: DirectVolume) {
|
||||
@@ -71,7 +72,7 @@ export namespace DirectVolume {
|
||||
]);
|
||||
}
|
||||
|
||||
function fromData(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, axisOrder: Vec3): DirectVolume {
|
||||
function fromData(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, axisOrder: Vec3, dataType: 'byte' | 'float' | 'halfFloat'): DirectVolume {
|
||||
const boundingSphere = Sphere3D();
|
||||
let currentHash = -1;
|
||||
|
||||
@@ -103,6 +104,7 @@ export namespace DirectVolume {
|
||||
},
|
||||
packedGroup: ValueCell.create(packedGroup),
|
||||
axisOrder: ValueCell.create(axisOrder),
|
||||
dataType: ValueCell.create(dataType),
|
||||
setBoundingSphere(sphere: Sphere3D) {
|
||||
Sphere3D.copy(boundingSphere, sphere);
|
||||
currentHash = hashCode(directVolume);
|
||||
@@ -111,7 +113,7 @@ export namespace DirectVolume {
|
||||
return directVolume;
|
||||
}
|
||||
|
||||
function update(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, axisOrder: Vec3, directVolume: DirectVolume): DirectVolume {
|
||||
function update(bbox: Box3D, gridDimension: Vec3, transform: Mat4, unitToCartn: Mat4, cellDim: Vec3, texture: Texture, stats: Grid['stats'], packedGroup: boolean, axisOrder: Vec3, dataType: 'byte' | 'float' | 'halfFloat', directVolume: DirectVolume): DirectVolume {
|
||||
const width = texture.getWidth();
|
||||
const height = texture.getHeight();
|
||||
const depth = texture.getDepth();
|
||||
@@ -129,6 +131,7 @@ export namespace DirectVolume {
|
||||
ValueCell.update(directVolume.cartnToUnit, Mat4.invert(Mat4(), unitToCartn));
|
||||
ValueCell.updateIfChanged(directVolume.packedGroup, packedGroup);
|
||||
ValueCell.updateIfChanged(directVolume.axisOrder, Vec3.fromArray(directVolume.axisOrder.ref.value, axisOrder, 0));
|
||||
ValueCell.updateIfChanged(directVolume.dataType, dataType);
|
||||
return directVolume;
|
||||
}
|
||||
|
||||
@@ -142,7 +145,8 @@ export namespace DirectVolume {
|
||||
const stats = Grid.One.stats;
|
||||
const packedGroup = false;
|
||||
const axisOrder = Vec3.create(0, 1, 2);
|
||||
return create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, axisOrder, directVolume);
|
||||
const dataType = 'byte';
|
||||
return create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, stats, packedGroup, axisOrder, dataType, directVolume);
|
||||
}
|
||||
|
||||
export const Params = {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -7,7 +7,7 @@
|
||||
import { ComputeRenderable, createComputeRenderable } from '../../renderable';
|
||||
import { WebGLContext } from '../../webgl/context';
|
||||
import { createComputeRenderItem } from '../../webgl/render-item';
|
||||
import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
|
||||
import { Values, TextureSpec, UniformSpec, DefineSpec } from '../../renderable/schema';
|
||||
import { Texture } from '../../../mol-gl/webgl/texture';
|
||||
import { ShaderCode } from '../../../mol-gl/shader-code';
|
||||
import { ValueCell } from '../../../mol-util';
|
||||
@@ -17,12 +17,14 @@ import { getTriCount } from './tables';
|
||||
import { quad_vert } from '../../../mol-gl/shader/quad.vert';
|
||||
import { activeVoxels_frag } from '../../../mol-gl/shader/marching-cubes/active-voxels.frag';
|
||||
import { isTimingMode } from '../../../mol-util/debug';
|
||||
import { isWebGL2 } from '../../webgl/compat';
|
||||
|
||||
const ActiveVoxelsSchema = {
|
||||
...QuadSchema,
|
||||
|
||||
tTriCount: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
|
||||
tVolumeData: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
|
||||
dValueChannel: DefineSpec('string', ['red', 'alpha']),
|
||||
uIsoValue: UniformSpec('f'),
|
||||
|
||||
uGridDim: UniformSpec('v3'),
|
||||
@@ -34,12 +36,17 @@ type ActiveVoxelsValues = Values<typeof ActiveVoxelsSchema>
|
||||
|
||||
const ActiveVoxelsName = 'active-voxels';
|
||||
|
||||
function valueChannel(ctx: WebGLContext, volumeData: Texture) {
|
||||
return isWebGL2(ctx.gl) && volumeData.format === ctx.gl.RED ? 'red' : 'alpha';
|
||||
}
|
||||
|
||||
function getActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, scale: Vec2): ComputeRenderable<ActiveVoxelsValues> {
|
||||
if (ctx.namedComputeRenderables[ActiveVoxelsName]) {
|
||||
const v = ctx.namedComputeRenderables[ActiveVoxelsName].values as ActiveVoxelsValues;
|
||||
|
||||
ValueCell.update(v.uQuadScale, scale);
|
||||
ValueCell.update(v.tVolumeData, volumeData);
|
||||
ValueCell.update(v.dValueChannel, valueChannel(ctx, volumeData));
|
||||
ValueCell.updateIfChanged(v.uIsoValue, isoValue);
|
||||
ValueCell.update(v.uGridDim, gridDim);
|
||||
ValueCell.update(v.uGridTexDim, gridTexDim);
|
||||
@@ -59,6 +66,7 @@ function createActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gr
|
||||
|
||||
uQuadScale: ValueCell.create(scale),
|
||||
tVolumeData: ValueCell.create(volumeData),
|
||||
dValueChannel: ValueCell.create(valueChannel(ctx, volumeData)),
|
||||
uIsoValue: ValueCell.create(isoValue),
|
||||
uGridDim: ValueCell.create(gridDim),
|
||||
uGridTexDim: ValueCell.create(gridTexDim),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2019-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2019-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -28,6 +28,7 @@ const IsosurfaceSchema = {
|
||||
tActiveVoxelsPyramid: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
tActiveVoxelsBase: TextureSpec('texture', 'rgba', 'float', 'nearest'),
|
||||
tVolumeData: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
|
||||
dValueChannel: DefineSpec('string', ['red', 'alpha']),
|
||||
uIsoValue: UniformSpec('f'),
|
||||
|
||||
uSize: UniformSpec('f'),
|
||||
@@ -48,6 +49,10 @@ type IsosurfaceValues = Values<typeof IsosurfaceSchema>
|
||||
|
||||
const IsosurfaceName = 'isosurface';
|
||||
|
||||
function valueChannel(ctx: WebGLContext, volumeData: Texture) {
|
||||
return isWebGL2(ctx.gl) && volumeData.format === ctx.gl.RED ? 'red' : 'alpha';
|
||||
}
|
||||
|
||||
function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, invert: boolean, packedGroup: boolean, axisOrder: Vec3, constantGroup: boolean): ComputeRenderable<IsosurfaceValues> {
|
||||
if (ctx.namedComputeRenderables[IsosurfaceName]) {
|
||||
const v = ctx.namedComputeRenderables[IsosurfaceName].values as IsosurfaceValues;
|
||||
@@ -55,6 +60,7 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
|
||||
ValueCell.update(v.tActiveVoxelsPyramid, activeVoxelsPyramid);
|
||||
ValueCell.update(v.tActiveVoxelsBase, activeVoxelsBase);
|
||||
ValueCell.update(v.tVolumeData, volumeData);
|
||||
ValueCell.update(v.dValueChannel, valueChannel(ctx, volumeData));
|
||||
|
||||
ValueCell.updateIfChanged(v.uIsoValue, isoValue);
|
||||
ValueCell.updateIfChanged(v.uSize, Math.pow(2, levels));
|
||||
@@ -87,6 +93,7 @@ function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Text
|
||||
tActiveVoxelsPyramid: ValueCell.create(activeVoxelsPyramid),
|
||||
tActiveVoxelsBase: ValueCell.create(activeVoxelsBase),
|
||||
tVolumeData: ValueCell.create(volumeData),
|
||||
dValueChannel: ValueCell.create(valueChannel(ctx, volumeData)),
|
||||
|
||||
uIsoValue: ValueCell.create(isoValue),
|
||||
uSize: ValueCell.create(Math.pow(2, levels)),
|
||||
|
||||
@@ -38,9 +38,14 @@ vec4 texture3dFrom2dNearest(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim)
|
||||
return texture2D(tex, coord);
|
||||
}
|
||||
|
||||
vec4 voxel(vec3 pos) {
|
||||
float voxelValue(vec3 pos) {
|
||||
pos = min(max(vec3(0.0), pos), uGridDim - vec3(1.0));
|
||||
return texture3dFrom2dNearest(tVolumeData, pos / uGridDim, uGridDim, uGridTexDim.xy);
|
||||
vec4 v = texture3dFrom2dNearest(tVolumeData, pos / uGridDim, uGridDim, uGridTexDim.xy);
|
||||
#ifdef dValueChannel_red
|
||||
return v.r;
|
||||
#else
|
||||
return v.a;
|
||||
#endif
|
||||
}
|
||||
|
||||
void main(void) {
|
||||
@@ -48,14 +53,14 @@ void main(void) {
|
||||
vec3 posXYZ = index3dFrom2d(uv);
|
||||
|
||||
// get MC case as the sum of corners that are below the given iso level
|
||||
float c = step(voxel(posXYZ).a, uIsoValue)
|
||||
+ 2. * step(voxel(posXYZ + c1).a, uIsoValue)
|
||||
+ 4. * step(voxel(posXYZ + c2).a, uIsoValue)
|
||||
+ 8. * step(voxel(posXYZ + c3).a, uIsoValue)
|
||||
+ 16. * step(voxel(posXYZ + c4).a, uIsoValue)
|
||||
+ 32. * step(voxel(posXYZ + c5).a, uIsoValue)
|
||||
+ 64. * step(voxel(posXYZ + c6).a, uIsoValue)
|
||||
+ 128. * step(voxel(posXYZ + c7).a, uIsoValue);
|
||||
float c = step(voxelValue(posXYZ), uIsoValue)
|
||||
+ 2. * step(voxelValue(posXYZ + c1), uIsoValue)
|
||||
+ 4. * step(voxelValue(posXYZ + c2), uIsoValue)
|
||||
+ 8. * step(voxelValue(posXYZ + c3), uIsoValue)
|
||||
+ 16. * step(voxelValue(posXYZ + c4), uIsoValue)
|
||||
+ 32. * step(voxelValue(posXYZ + c5), uIsoValue)
|
||||
+ 64. * step(voxelValue(posXYZ + c6), uIsoValue)
|
||||
+ 128. * step(voxelValue(posXYZ + c7), uIsoValue);
|
||||
c *= step(c, 254.);
|
||||
|
||||
// handle out of bounds positions
|
||||
|
||||
@@ -59,9 +59,14 @@ vec4 voxel(vec3 pos) {
|
||||
return texture3dFrom2dNearest(tVolumeData, pos / uGridDim, uGridDim, uGridTexDim.xy);
|
||||
}
|
||||
|
||||
vec4 voxelPadded(vec3 pos) {
|
||||
float voxelValuePadded(vec3 pos) {
|
||||
pos = min(max(vec3(0.0), pos), uGridDim - vec3(vec2(2.0), 1.0)); // remove xy padding
|
||||
return texture3dFrom2dNearest(tVolumeData, pos / uGridDim, uGridDim, uGridTexDim.xy);
|
||||
vec4 v = texture3dFrom2dNearest(tVolumeData, pos / uGridDim, uGridDim, uGridTexDim.xy);
|
||||
#ifdef dValueChannel_red
|
||||
return v.r;
|
||||
#else
|
||||
return v.a;
|
||||
#endif
|
||||
}
|
||||
|
||||
int idot2(const in ivec2 a, const in ivec2 b) {
|
||||
@@ -261,8 +266,13 @@ void main(void) {
|
||||
vec4 d0 = voxel(b0);
|
||||
vec4 d1 = voxel(b1);
|
||||
|
||||
float v0 = d0.a;
|
||||
float v1 = d1.a;
|
||||
#ifdef dValueChannel_red
|
||||
float v0 = d0.r;
|
||||
float v1 = d1.r;
|
||||
#else
|
||||
float v0 = d0.a;
|
||||
float v1 = d1.a;
|
||||
#endif
|
||||
|
||||
float t = (uIsoValue - v0) / (v0 - v1);
|
||||
gl_FragData[0].xyz = (uGridTransform * vec4(b0 + t * (b0 - b1), 1.0)).xyz;
|
||||
@@ -286,14 +296,14 @@ void main(void) {
|
||||
|
||||
// normals from gradients
|
||||
vec3 n0 = -normalize(vec3(
|
||||
voxelPadded(b0 - c1).a - voxelPadded(b0 + c1).a,
|
||||
voxelPadded(b0 - c3).a - voxelPadded(b0 + c3).a,
|
||||
voxelPadded(b0 - c4).a - voxelPadded(b0 + c4).a
|
||||
voxelValuePadded(b0 - c1) - voxelValuePadded(b0 + c1),
|
||||
voxelValuePadded(b0 - c3) - voxelValuePadded(b0 + c3),
|
||||
voxelValuePadded(b0 - c4) - voxelValuePadded(b0 + c4)
|
||||
));
|
||||
vec3 n1 = -normalize(vec3(
|
||||
voxelPadded(b1 - c1).a - voxelPadded(b1 + c1).a,
|
||||
voxelPadded(b1 - c3).a - voxelPadded(b1 + c3).a,
|
||||
voxelPadded(b1 - c4).a - voxelPadded(b1 + c4).a
|
||||
voxelValuePadded(b1 - c1) - voxelValuePadded(b1 + c1),
|
||||
voxelValuePadded(b1 - c3) - voxelValuePadded(b1 + c3),
|
||||
voxelValuePadded(b1 - c4) - voxelValuePadded(b1 + c4)
|
||||
));
|
||||
gl_FragData[2].xyz = -vec3(
|
||||
n0.x + t * (n0.x - n1.x),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
* @author Gianluca Tomasello <giagitom@gmail.com>
|
||||
@@ -59,14 +59,14 @@ export function getTarget(gl: GLRenderingContext, kind: TextureKind): number {
|
||||
export function getFormat(gl: GLRenderingContext, format: TextureFormat, type: TextureType): number {
|
||||
switch (format) {
|
||||
case 'alpha':
|
||||
if (isWebGL2(gl) && type === 'float') return gl.RED;
|
||||
if (isWebGL2(gl) && (type === 'float' || type === 'fp16')) return gl.RED;
|
||||
else if (isWebGL2(gl) && type === 'int') return gl.RED_INTEGER;
|
||||
else return gl.ALPHA;
|
||||
case 'rgb':
|
||||
if (isWebGL2(gl) && type === 'int') return gl.RGB_INTEGER;
|
||||
return gl.RGB;
|
||||
case 'rg':
|
||||
if (isWebGL2(gl) && type === 'float') return gl.RG;
|
||||
if (isWebGL2(gl) && (type === 'float' || type === 'fp16')) return gl.RG;
|
||||
else if (isWebGL2(gl) && type === 'int') return gl.RG_INTEGER;
|
||||
else throw new Error('texture format "rg" requires webgl2 and type "float" or int"');
|
||||
case 'rgba':
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -9,6 +9,7 @@ import { CifWriter } from '../mol-io/writer/cif';
|
||||
import { CifExportContext } from './structure/export/mmcif';
|
||||
import { QuerySymbolRuntime } from '../mol-script/runtime/query/compiler';
|
||||
import { UUID } from '../mol-util';
|
||||
import { arrayRemoveInPlace } from '../mol-util/array';
|
||||
|
||||
export { CustomPropertyDescriptor, CustomProperties };
|
||||
|
||||
@@ -61,6 +62,14 @@ class CustomProperties {
|
||||
this._set.add(desc);
|
||||
}
|
||||
|
||||
remove(desc: CustomPropertyDescriptor<any>) {
|
||||
if (!this._set.has(desc)) return;
|
||||
|
||||
arrayRemoveInPlace(this._list, desc);
|
||||
this._set.delete(desc);
|
||||
this.assets(desc);
|
||||
}
|
||||
|
||||
reference(desc: CustomPropertyDescriptor<any>, add: boolean) {
|
||||
let refs = this._refs.get(desc) || 0;
|
||||
refs += add ? 1 : -1;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -31,7 +31,7 @@ async function createGaussianDensityVolume(ctx: VisualContext, structure: Struct
|
||||
const unitToCartn = Mat4.mul(Mat4(), transform, Mat4.fromScaling(Mat4(), gridDim));
|
||||
const cellDim = Mat4.getScaling(Vec3(), transform);
|
||||
const axisOrder = Vec3.create(0, 1, 2);
|
||||
const vol = DirectVolume.create(bbox, gridDim, transform, unitToCartn, cellDim, texture, stats, true, axisOrder, directVolume);
|
||||
const vol = DirectVolume.create(bbox, gridDim, transform, unitToCartn, cellDim, texture, stats, true, axisOrder, 'byte', directVolume);
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, densityTextureData.maxRadius);
|
||||
vol.setBoundingSphere(sphere);
|
||||
@@ -89,7 +89,7 @@ async function createUnitsGaussianDensityVolume(ctx: VisualContext, unit: Unit,
|
||||
const unitToCartn = Mat4.mul(Mat4(), transform, Mat4.fromScaling(Mat4(), gridDim));
|
||||
const cellDim = Mat4.getScaling(Vec3(), transform);
|
||||
const axisOrder = Vec3.create(0, 1, 2);
|
||||
const vol = DirectVolume.create(bbox, gridDim, transform, unitToCartn, cellDim, texture, stats, true, axisOrder, directVolume);
|
||||
const vol = DirectVolume.create(bbox, gridDim, transform, unitToCartn, cellDim, texture, stats, true, axisOrder, 'byte', directVolume);
|
||||
|
||||
const sphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, densityTextureData.maxRadius);
|
||||
vol.setBoundingSphere(sphere);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -22,6 +22,7 @@ import { Interval } from '../../mol-data/int';
|
||||
import { Loci, EmptyLoci } from '../../mol-model/loci';
|
||||
import { PickingId } from '../../mol-geo/geometry/picking';
|
||||
import { createVolumeTexture2d, createVolumeTexture3d, eachVolumeLoci, getVolumeTexture2dLayout } from './util';
|
||||
import { Texture } from '../../mol-gl/webgl/texture';
|
||||
|
||||
function getBoundingBox(gridDimension: Vec3, transform: Mat4) {
|
||||
const bbox = Box3D();
|
||||
@@ -32,24 +33,35 @@ function getBoundingBox(gridDimension: Vec3, transform: Mat4) {
|
||||
|
||||
// 2d volume texture
|
||||
|
||||
export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, volume: Volume, directVolume?: DirectVolume) {
|
||||
export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, volume: Volume, props: PD.Values<DirectVolumeParams>, directVolume?: DirectVolume) {
|
||||
const gridDimension = volume.grid.cells.space.dimensions as Vec3;
|
||||
const { width, height } = getVolumeTexture2dLayout(gridDimension);
|
||||
if (Math.max(width, height) > webgl.maxTextureSize / 2) {
|
||||
throw new Error('volume too large for direct-volume rendering');
|
||||
}
|
||||
|
||||
const textureImage = createVolumeTexture2d(volume, 'normals');
|
||||
const dataType = props.dataType === 'halfFloat' && !webgl.extensions.textureHalfFloat ? 'float' : props.dataType;
|
||||
|
||||
const textureImage = createVolumeTexture2d(volume, 'normals', 0, dataType);
|
||||
// debugTexture(createImageData(textureImage.array, textureImage.width, textureImage.height), 1/3)
|
||||
const transform = Grid.getGridToCartesianTransform(volume.grid);
|
||||
const bbox = getBoundingBox(gridDimension, transform);
|
||||
|
||||
const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
|
||||
let texture: Texture;
|
||||
if (directVolume && directVolume.dataType.ref.value === dataType) {
|
||||
texture = directVolume.gridTexture.ref.value;
|
||||
} else {
|
||||
texture = dataType === 'byte'
|
||||
? webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear')
|
||||
: dataType === 'halfFloat'
|
||||
? webgl.resources.texture('image-float16', 'rgba', 'fp16', 'linear')
|
||||
: webgl.resources.texture('image-float32', 'rgba', 'float', 'linear');
|
||||
}
|
||||
texture.load(textureImage);
|
||||
|
||||
const { unitToCartn, cellDim } = getUnitToCartn(volume.grid);
|
||||
const axisOrder = volume.grid.cells.space.axisOrderSlowToFast as Vec3;
|
||||
return DirectVolume.create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, volume.grid.stats, false, axisOrder, directVolume);
|
||||
return DirectVolume.create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, volume.grid.stats, false, axisOrder, dataType, directVolume);
|
||||
}
|
||||
|
||||
// 3d volume texture
|
||||
@@ -76,33 +88,44 @@ function getUnitToCartn(grid: Grid) {
|
||||
};
|
||||
}
|
||||
|
||||
export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, volume: Volume, directVolume?: DirectVolume) {
|
||||
export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, volume: Volume, props: PD.Values<DirectVolumeParams>, directVolume?: DirectVolume) {
|
||||
const gridDimension = volume.grid.cells.space.dimensions as Vec3;
|
||||
if (Math.max(...gridDimension) > webgl.max3dTextureSize / 2) {
|
||||
throw new Error('volume too large for direct-volume rendering');
|
||||
}
|
||||
|
||||
const textureVolume = createVolumeTexture3d(volume);
|
||||
const dataType = props.dataType === 'halfFloat' && !webgl.extensions.textureHalfFloat ? 'float' : props.dataType;
|
||||
|
||||
const textureVolume = createVolumeTexture3d(volume, dataType);
|
||||
const transform = Grid.getGridToCartesianTransform(volume.grid);
|
||||
const bbox = getBoundingBox(gridDimension, transform);
|
||||
|
||||
const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear');
|
||||
let texture: Texture;
|
||||
if (directVolume && directVolume.dataType.ref.value === dataType) {
|
||||
texture = directVolume.gridTexture.ref.value;
|
||||
} else {
|
||||
texture = dataType === 'byte'
|
||||
? webgl.resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear')
|
||||
: dataType === 'halfFloat'
|
||||
? webgl.resources.texture('volume-float16', 'rgba', 'fp16', 'linear')
|
||||
: webgl.resources.texture('volume-float32', 'rgba', 'float', 'linear');
|
||||
}
|
||||
texture.load(textureVolume);
|
||||
|
||||
const { unitToCartn, cellDim } = getUnitToCartn(volume.grid);
|
||||
const axisOrder = volume.grid.cells.space.axisOrderSlowToFast as Vec3;
|
||||
return DirectVolume.create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, volume.grid.stats, false, axisOrder, directVolume);
|
||||
return DirectVolume.create(bbox, gridDimension, transform, unitToCartn, cellDim, texture, volume.grid.stats, false, axisOrder, dataType, directVolume);
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
export async function createDirectVolume(ctx: VisualContext, volume: Volume, key: number, theme: Theme, props: PD.Values<DirectVolumeParams>, directVolume?: DirectVolume) {
|
||||
const { runtime, webgl } = ctx;
|
||||
if (webgl === undefined) throw new Error('DirectVolumeVisual requires `webgl` in props');
|
||||
if (webgl === undefined) throw new Error('DirectVolumeVisual requires `webgl` in VisualContext');
|
||||
|
||||
return webgl.isWebGL2 ?
|
||||
createDirectVolume3d(runtime, webgl, volume, directVolume) :
|
||||
createDirectVolume2d(runtime, webgl, volume, directVolume);
|
||||
createDirectVolume3d(runtime, webgl, volume, props, directVolume) :
|
||||
createDirectVolume2d(runtime, webgl, volume, props, directVolume);
|
||||
}
|
||||
|
||||
function getLoci(volume: Volume, props: PD.Values<DirectVolumeParams>) {
|
||||
@@ -126,6 +149,7 @@ export function eachDirectVolume(loci: Loci, volume: Volume, key: number, props:
|
||||
export const DirectVolumeParams = {
|
||||
...DirectVolume.Params,
|
||||
quality: { ...DirectVolume.Params.quality, isEssential: false },
|
||||
dataType: PD.Select('byte', PD.arrayToOptions(['byte', 'float', 'halfFloat'] as const)),
|
||||
};
|
||||
export type DirectVolumeParams = typeof DirectVolumeParams
|
||||
export function getDirectVolumeParams(ctx: ThemeRegistryContext, volume: Volume) {
|
||||
@@ -143,6 +167,7 @@ export function DirectVolumeVisual(materialId: number): VolumeVisual<DirectVolum
|
||||
getLoci: getDirectVolumeLoci,
|
||||
eachLocation: eachDirectVolume,
|
||||
setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<DirectVolumeParams>, currentProps: PD.Values<DirectVolumeParams>) => {
|
||||
state.createGeometry = newProps.dataType !== currentProps.dataType;
|
||||
},
|
||||
geometryUtils: DirectVolume.Utils,
|
||||
dispose: (geometry: DirectVolume) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2018-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author David Sehnal <david.sehnal@gmail.com>
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
@@ -32,11 +32,19 @@ import { BaseGeometry } from '../../mol-geo/geometry/base';
|
||||
import { ValueCell } from '../../mol-util/value-cell';
|
||||
|
||||
export const VolumeIsosurfaceParams = {
|
||||
isoValue: Volume.IsoValueParam
|
||||
isoValue: Volume.IsoValueParam,
|
||||
};
|
||||
export type VolumeIsosurfaceParams = typeof VolumeIsosurfaceParams
|
||||
export type VolumeIsosurfaceProps = PD.Values<VolumeIsosurfaceParams>
|
||||
|
||||
export const VolumeIsosurfaceTextureParams = {
|
||||
isoValue: Volume.IsoValueParam,
|
||||
tryUseGpu: PD.Boolean(true),
|
||||
gpuDataType: PD.Select('byte', PD.arrayToOptions(['byte', 'float', 'halfFloat'] as const), { hideIf: p => !p.tryUseGpu }),
|
||||
};
|
||||
export type VolumeIsosurfaceGpuParams = typeof VolumeIsosurfaceTextureParams
|
||||
export type VolumeIsosurfaceGpuProps = PD.Values<VolumeIsosurfaceGpuParams>
|
||||
|
||||
function gpuSupport(webgl: WebGLContext) {
|
||||
return webgl.extensions.colorBufferFloat && webgl.extensions.textureFloat && webgl.extensions.drawBuffers;
|
||||
}
|
||||
@@ -117,8 +125,8 @@ export const IsosurfaceMeshParams = {
|
||||
...Mesh.Params,
|
||||
...TextureMesh.Params,
|
||||
...VolumeIsosurfaceParams,
|
||||
...VolumeIsosurfaceTextureParams,
|
||||
quality: { ...Mesh.Params.quality, isEssential: false },
|
||||
tryUseGpu: PD.Boolean(true),
|
||||
};
|
||||
export type IsosurfaceMeshParams = typeof IsosurfaceMeshParams
|
||||
|
||||
@@ -144,9 +152,7 @@ export function IsosurfaceMeshVisual(materialId: number): VolumeVisual<Isosurfac
|
||||
namespace VolumeIsosurfaceTexture {
|
||||
const name = 'volume-isosurface-texture';
|
||||
export const descriptor = CustomPropertyDescriptor({ name });
|
||||
export function get(volume: Volume, webgl: WebGLContext) {
|
||||
const { resources } = webgl;
|
||||
|
||||
export function get(volume: Volume, webgl: WebGLContext, props: VolumeIsosurfaceGpuProps) {
|
||||
const transform = Grid.getGridToCartesianTransform(volume.grid);
|
||||
const gridDimension = Vec3.clone(volume.grid.cells.space.dimensions as Vec3);
|
||||
const { width, height, powerOfTwoSize: texDim } = getVolumeTexture2dLayout(gridDimension, Padding);
|
||||
@@ -158,12 +164,23 @@ namespace VolumeIsosurfaceTexture {
|
||||
throw new Error('volume too large for gpu isosurface extraction');
|
||||
}
|
||||
|
||||
const dataType = props.gpuDataType === 'halfFloat' && !webgl.extensions.textureHalfFloat ? 'float' : props.gpuDataType;
|
||||
|
||||
if (volume._propertyData[name]?.dataType !== dataType) {
|
||||
volume.customProperties.remove(descriptor);
|
||||
delete volume._propertyData[name];
|
||||
}
|
||||
|
||||
if (!volume._propertyData[name]) {
|
||||
volume._propertyData[name] = resources.texture('image-uint8', 'alpha', 'ubyte', 'linear');
|
||||
const texture = volume._propertyData[name] as Texture;
|
||||
const texture = dataType === 'byte'
|
||||
? webgl.resources.texture('image-uint8', 'alpha', 'ubyte', 'linear')
|
||||
: dataType === 'halfFloat'
|
||||
? webgl.resources.texture('image-float16', 'alpha', 'fp16', 'linear')
|
||||
: webgl.resources.texture('image-float32', 'alpha', 'float', 'linear');
|
||||
volume._propertyData[name] = { texture, dataType };
|
||||
texture.define(texDim, texDim);
|
||||
// load volume into sub-section of texture
|
||||
texture.load(createVolumeTexture2d(volume, 'data', Padding), true);
|
||||
texture.load(createVolumeTexture2d(volume, 'data', Padding, dataType), true);
|
||||
volume.customProperties.add(descriptor);
|
||||
volume.customProperties.assets(descriptor, [{ dispose: () => texture.destroy() }]);
|
||||
}
|
||||
@@ -172,7 +189,7 @@ namespace VolumeIsosurfaceTexture {
|
||||
gridDimension[1] += Padding;
|
||||
|
||||
return {
|
||||
texture: volume._propertyData[name] as Texture,
|
||||
texture: volume._propertyData[name].texture as Texture,
|
||||
transform,
|
||||
gridDimension,
|
||||
gridTexDim,
|
||||
@@ -181,7 +198,7 @@ namespace VolumeIsosurfaceTexture {
|
||||
}
|
||||
}
|
||||
|
||||
async function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Volume, key: number, theme: Theme, props: VolumeIsosurfaceProps, textureMesh?: TextureMesh) {
|
||||
async function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Volume, key: number, theme: Theme, props: VolumeIsosurfaceGpuProps, textureMesh?: TextureMesh) {
|
||||
if (!ctx.webgl) throw new Error('webgl context required to create volume isosurface texture-mesh');
|
||||
|
||||
if (volume.grid.cells.data.length <= 1) {
|
||||
@@ -193,7 +210,7 @@ async function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Vol
|
||||
const value = Volume.IsoValue.toAbsolute(props.isoValue, volume.grid.stats).absoluteValue;
|
||||
const isoLevel = ((value - min) / diff);
|
||||
|
||||
const { texture, gridDimension, gridTexDim, gridTexScale, transform } = VolumeIsosurfaceTexture.get(volume, ctx.webgl);
|
||||
const { texture, gridDimension, gridTexDim, gridTexScale, transform } = VolumeIsosurfaceTexture.get(volume, ctx.webgl, props);
|
||||
|
||||
const axisOrder = volume.grid.cells.space.axisOrderSlowToFast as Vec3;
|
||||
const buffer = textureMesh?.doubleBuffer.get();
|
||||
@@ -216,6 +233,7 @@ export function IsosurfaceTextureMeshVisual(materialId: number): VolumeVisual<Is
|
||||
eachLocation: eachIsosurface,
|
||||
setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<IsosurfaceMeshParams>, currentProps: PD.Values<IsosurfaceMeshParams>) => {
|
||||
if (!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats)) state.createGeometry = true;
|
||||
if (newProps.gpuDataType !== currentProps.gpuDataType) state.createGeometry = true;
|
||||
},
|
||||
geometryUtils: TextureMesh.Utils,
|
||||
mustRecreate: (volumeKey: VolumeKey, props: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2020-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
* Copyright (c) 2020-2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*/
|
||||
@@ -12,6 +12,8 @@ import { Vec3 } from '../../mol-math/linear-algebra/3d/vec3';
|
||||
import { packIntToRGBArray } from '../../mol-util/number-packing';
|
||||
import { SetUtils } from '../../mol-util/set';
|
||||
import { Box3D } from '../../mol-math/geometry';
|
||||
import { toHalfFloat } from '../../mol-util/number-conversion';
|
||||
import { clamp } from '../../mol-math/interpolate';
|
||||
|
||||
// avoiding namespace lookup improved performance in Chrome (Aug 2020)
|
||||
const v3set = Vec3.set;
|
||||
@@ -97,14 +99,18 @@ export function getVolumeTexture2dLayout(dim: Vec3, padding = 0) {
|
||||
return { width, height, columns, rows, powerOfTwoSize: height < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2 };
|
||||
}
|
||||
|
||||
export function createVolumeTexture2d(volume: Volume, variant: 'normals' | 'groups' | 'data', padding = 0) {
|
||||
export function createVolumeTexture2d(volume: Volume, variant: 'normals' | 'groups' | 'data', padding = 0, type: 'byte' | 'float' | 'halfFloat' = 'byte') {
|
||||
const { cells: { space, data }, stats: { max, min } } = volume.grid;
|
||||
const dim = space.dimensions as Vec3;
|
||||
const { dataOffset: o } = space;
|
||||
const { width, height } = getVolumeTexture2dLayout(dim, padding);
|
||||
|
||||
const itemSize = variant === 'data' ? 1 : 4;
|
||||
const array = new Uint8Array(width * height * itemSize);
|
||||
const array = type === 'byte'
|
||||
? new Uint8Array(width * height * itemSize)
|
||||
: type === 'halfFloat'
|
||||
? new Uint16Array(width * height * itemSize)
|
||||
: new Float32Array(width * height * itemSize);
|
||||
const textureImage = { array, width, height };
|
||||
|
||||
const diff = max - min;
|
||||
@@ -128,11 +134,29 @@ export function createVolumeTexture2d(volume: Volume, variant: 'normals' | 'grou
|
||||
const index = itemSize * ((row * ynp * width) + (y * width) + px);
|
||||
const offset = o(x, y, z);
|
||||
|
||||
let value: number;
|
||||
if (type === 'byte') {
|
||||
value = Math.round(((data[offset] - min) / diff) * 255);
|
||||
} else if (type === 'halfFloat') {
|
||||
value = toHalfFloat((data[offset] - min) / diff);
|
||||
} else {
|
||||
value = (data[offset] - min) / diff;
|
||||
}
|
||||
|
||||
if (variant === 'data') {
|
||||
array[index] = Math.round(((data[offset] - min) / diff) * 255);
|
||||
array[index] = value;
|
||||
} else {
|
||||
if (variant === 'groups') {
|
||||
packIntToRGBArray(offset, array, index);
|
||||
if (type === 'halfFloat') {
|
||||
let group = clamp(Math.round(offset), 0, 16777216 - 1) + 1;
|
||||
array[index + 2] = toHalfFloat(group % 256);
|
||||
group = Math.floor(group / 256);
|
||||
array[index + 1] = toHalfFloat(group % 256);
|
||||
group = Math.floor(group / 256);
|
||||
array[index] = toHalfFloat(group % 256);
|
||||
} else {
|
||||
packIntToRGBArray(offset, array, index);
|
||||
}
|
||||
} else {
|
||||
v3set(n0,
|
||||
data[o(Math.max(0, x - 1), y, z)],
|
||||
@@ -146,10 +170,19 @@ export function createVolumeTexture2d(volume: Volume, variant: 'normals' | 'grou
|
||||
);
|
||||
v3normalize(n0, v3sub(n0, n0, n1));
|
||||
v3addScalar(n0, v3scale(n0, n0, 0.5), 0.5);
|
||||
v3toArray(v3scale(n0, n0, 255), array, index);
|
||||
|
||||
if (type === 'byte') {
|
||||
v3toArray(v3scale(n0, n0, 255), array, index);
|
||||
} else if (type === 'halfFloat') {
|
||||
array[index] = toHalfFloat(n0[0]);
|
||||
array[index + 1] = toHalfFloat(n0[1]);
|
||||
array[index + 2] = toHalfFloat(n0[2]);
|
||||
} else {
|
||||
v3toArray(n0, array, index);
|
||||
}
|
||||
}
|
||||
|
||||
array[index + 3] = Math.round(((data[offset] - min) / diff) * 255);
|
||||
array[index + 3] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -158,12 +191,16 @@ export function createVolumeTexture2d(volume: Volume, variant: 'normals' | 'grou
|
||||
return textureImage;
|
||||
}
|
||||
|
||||
export function createVolumeTexture3d(volume: Volume) {
|
||||
export function createVolumeTexture3d(volume: Volume, type: 'byte' | 'float' | 'halfFloat' = 'byte') {
|
||||
const { cells: { space, data }, stats: { max, min } } = volume.grid;
|
||||
const [width, height, depth] = space.dimensions as Vec3;
|
||||
const { dataOffset: o } = space;
|
||||
|
||||
const array = new Uint8Array(width * height * depth * 4);
|
||||
const array = type === 'byte'
|
||||
? new Uint8Array(width * height * depth * 4)
|
||||
: type === 'halfFloat'
|
||||
? new Uint16Array(width * height * depth * 4)
|
||||
: new Float32Array(width * height * depth * 4);
|
||||
const textureVolume = { array, width, height, depth };
|
||||
const diff = max - min;
|
||||
|
||||
@@ -192,9 +229,19 @@ export function createVolumeTexture3d(volume: Volume) {
|
||||
);
|
||||
v3normalize(n0, v3sub(n0, n0, n1));
|
||||
v3addScalar(n0, v3scale(n0, n0, 0.5), 0.5);
|
||||
v3toArray(v3scale(n0, n0, 255), array, i);
|
||||
|
||||
array[i + 3] = Math.round(((data[offset] - min) / diff) * 255);
|
||||
if (type === 'byte') {
|
||||
v3toArray(v3scale(n0, n0, 255), array, i);
|
||||
array[i + 3] = Math.round(((data[offset] - min) / diff) * 255);
|
||||
} else if (type === 'halfFloat') {
|
||||
array[i] = toHalfFloat(n0[0]);
|
||||
array[i + 1] = toHalfFloat(n0[1]);
|
||||
array[i + 2] = toHalfFloat(n0[2]);
|
||||
array[i + 3] = toHalfFloat((data[offset] - min) / diff);
|
||||
} else {
|
||||
v3toArray(n0, array, i);
|
||||
array[i + 3] = (data[offset] - min) / diff;
|
||||
}
|
||||
i += 4;
|
||||
}
|
||||
}
|
||||
|
||||
124
src/mol-util/number-conversion.ts
Normal file
124
src/mol-util/number-conversion.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* Copyright (c) 2024 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
||||
*
|
||||
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
||||
*
|
||||
* This code has been modified from https://github.com/mrdoob/three.js/,
|
||||
* copyright (c) 2010-2024 three.js authors. MIT License
|
||||
*/
|
||||
|
||||
// Fast Half Float Conversions, http://www.fox-toolkit.org/ftp/fasthalffloatconversion.pdf
|
||||
|
||||
import { clamp } from '../mol-math/interpolate';
|
||||
|
||||
const Tables = generateTables();
|
||||
|
||||
function generateTables() {
|
||||
// float32 to float16 helpers
|
||||
const buffer = new ArrayBuffer(4);
|
||||
const floatView = new Float32Array(buffer);
|
||||
const uint32View = new Uint32Array(buffer);
|
||||
|
||||
const baseTable = new Uint32Array(512);
|
||||
const shiftTable = new Uint32Array(512);
|
||||
|
||||
for (let i = 0; i < 256; ++i) {
|
||||
const e = i - 127;
|
||||
if (e < -27) { // very small number (0, -0)
|
||||
baseTable[i] = 0x0000;
|
||||
baseTable[i | 0x100] = 0x8000;
|
||||
shiftTable[i] = 24;
|
||||
shiftTable[i | 0x100] = 24;
|
||||
} else if (e < -14) { // small number (denorm)
|
||||
baseTable[i] = 0x0400 >> (-e - 14);
|
||||
baseTable[i | 0x100] = (0x0400 >> (-e - 14)) | 0x8000;
|
||||
shiftTable[i] = - e - 1;
|
||||
shiftTable[i | 0x100] = -e - 1;
|
||||
} else if (e <= 15) { // normal number
|
||||
baseTable[i] = (e + 15) << 10;
|
||||
baseTable[i | 0x100] = ((e + 15) << 10) | 0x8000;
|
||||
shiftTable[i] = 13;
|
||||
shiftTable[i | 0x100] = 13;
|
||||
} else if (e < 128) { // large number (Infinity, -Infinity)
|
||||
baseTable[i] = 0x7c00;
|
||||
baseTable[i | 0x100] = 0xfc00;
|
||||
shiftTable[i] = 24;
|
||||
shiftTable[i | 0x100] = 24;
|
||||
} else { // stay (NaN, Infinity, -Infinity)
|
||||
baseTable[i] = 0x7c00;
|
||||
baseTable[i | 0x100] = 0xfc00;
|
||||
shiftTable[i] = 13;
|
||||
shiftTable[i | 0x100] = 13;
|
||||
}
|
||||
}
|
||||
|
||||
// float16 to float32 helpers
|
||||
const mantissaTable = new Uint32Array(2048);
|
||||
const exponentTable = new Uint32Array(64);
|
||||
const offsetTable = new Uint32Array(64);
|
||||
|
||||
for (let i = 1; i < 1024; ++i) {
|
||||
let m = i << 13; // zero pad mantissa bits
|
||||
let e = 0; // zero exponent
|
||||
|
||||
// normalized
|
||||
while ((m & 0x00800000) === 0) {
|
||||
m <<= 1;
|
||||
e -= 0x00800000; // decrement exponent
|
||||
}
|
||||
|
||||
m &= ~ 0x00800000; // clear leading 1 bit
|
||||
e += 0x38800000; // adjust bias
|
||||
|
||||
mantissaTable[i] = m | e;
|
||||
}
|
||||
|
||||
for (let i = 1024; i < 2048; ++i) {
|
||||
mantissaTable[i] = 0x38000000 + ((i - 1024) << 13);
|
||||
}
|
||||
|
||||
for (let i = 1; i < 31; ++i) {
|
||||
exponentTable[i] = i << 23;
|
||||
}
|
||||
|
||||
exponentTable[31] = 0x47800000;
|
||||
exponentTable[32] = 0x80000000;
|
||||
|
||||
for (let i = 33; i < 63; ++i) {
|
||||
exponentTable[i] = 0x80000000 + ((i - 32) << 23);
|
||||
}
|
||||
|
||||
exponentTable[63] = 0xc7800000;
|
||||
|
||||
for (let i = 1; i < 64; ++i) {
|
||||
if (i !== 32) {
|
||||
offsetTable[i] = 1024;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
floatView,
|
||||
uint32View,
|
||||
baseTable,
|
||||
shiftTable,
|
||||
mantissaTable,
|
||||
exponentTable,
|
||||
offsetTable
|
||||
};
|
||||
}
|
||||
|
||||
/** float32 to float16 */
|
||||
export function toHalfFloat(val: number) {
|
||||
val = clamp(val, -65504, 65504);
|
||||
Tables.floatView[0] = val;
|
||||
const f = Tables.uint32View[0];
|
||||
const e = (f >> 23) & 0x1ff;
|
||||
return Tables.baseTable[e] + ((f & 0x007fffff) >> Tables.shiftTable[e]);
|
||||
}
|
||||
|
||||
/** float16 to float32 */
|
||||
export function fromHalfFloat(val: number) {
|
||||
const m = val >> 10;
|
||||
Tables.uint32View[0] = Tables.mantissaTable[Tables.offsetTable[m] + (val & 0x3ff)] + Tables.exponentTable[m];
|
||||
return Tables.floatView[0];
|
||||
}
|
||||
Reference in New Issue
Block a user