gpu.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. /**
  2. * Copyright (c) 2017-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. * @author Michael Krone <michael.krone@uni-tuebingen.de>
  6. */
  7. import { PositionData } from '../common';
  8. import { Box3D } from '../../geometry';
  9. import { GaussianDensityProps, GaussianDensityData, GaussianDensityTextureData } from '../gaussian-density';
  10. import { OrderedSet } from '../../../mol-data/int';
  11. import { Vec3, Tensor, Mat4, Vec2 } from '../../linear-algebra';
  12. import { ValueCell } from '../../../mol-util';
  13. import { createComputeRenderable, ComputeRenderable } from '../../../mol-gl/renderable';
  14. import { WebGLContext } from '../../../mol-gl/webgl/context';
  15. import { Texture, TextureFilter, TextureFormat, TextureKind, TextureType } from '../../../mol-gl/webgl/texture';
  16. import { decodeFloatRGB } from '../../../mol-util/float-packing';
  17. import { ShaderCode } from '../../../mol-gl/shader-code';
  18. import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item';
  19. import { ValueSpec, AttributeSpec, UniformSpec, TextureSpec, DefineSpec, Values } from '../../../mol-gl/renderable/schema';
  20. import { gaussianDensity_vert } from '../../../mol-gl/shader/gaussian-density.vert';
  21. import { gaussianDensity_frag } from '../../../mol-gl/shader/gaussian-density.frag';
  22. import { Framebuffer } from '../../../mol-gl/webgl/framebuffer';
  23. const GaussianDensitySchema = {
  24. drawCount: ValueSpec('number'),
  25. instanceCount: ValueSpec('number'),
  26. aRadius: AttributeSpec('float32', 1, 0),
  27. aPosition: AttributeSpec('float32', 3, 0),
  28. aGroup: AttributeSpec('float32', 1, 0),
  29. uCurrentSlice: UniformSpec('f'),
  30. uCurrentX: UniformSpec('f'),
  31. uCurrentY: UniformSpec('f'),
  32. uBboxMin: UniformSpec('v3', 'material'),
  33. uBboxSize: UniformSpec('v3', 'material'),
  34. uGridDim: UniformSpec('v3', 'material'),
  35. uGridTexDim: UniformSpec('v3', 'material'),
  36. uGridTexScale: UniformSpec('v2', 'material'),
  37. uAlpha: UniformSpec('f', 'material'),
  38. uResolution: UniformSpec('f', 'material'),
  39. uRadiusFactorInv: UniformSpec('f', 'material'),
  40. tMinDistanceTex: TextureSpec('texture', 'rgba', 'float', 'nearest'),
  41. dGridTexType: DefineSpec('string', ['2d', '3d']),
  42. dCalcType: DefineSpec('string', ['density', 'minDistance', 'groupId']),
  43. };
  44. type GaussianDensityValues = Values<typeof GaussianDensitySchema>
  45. type GaussianDensityRenderable = ComputeRenderable<GaussianDensityValues>
  46. const GaussianDensityName = 'gaussian-density';
  47. function getFramebuffer(webgl: WebGLContext): Framebuffer {
  48. if (!webgl.namedFramebuffers[GaussianDensityName]) {
  49. webgl.namedFramebuffers[GaussianDensityName] = webgl.resources.framebuffer();
  50. }
  51. return webgl.namedFramebuffers[GaussianDensityName];
  52. }
  53. function getTexture(name: string, webgl: WebGLContext, kind: TextureKind, format: TextureFormat, type: TextureType, filter: TextureFilter): Texture {
  54. const _name = `${GaussianDensityName}-${name}`;
  55. if (!webgl.namedTextures[_name]) {
  56. webgl.namedTextures[_name] = webgl.resources.texture(kind, format, type, filter);
  57. }
  58. return webgl.namedTextures[_name];
  59. }
  60. export function GaussianDensityGPU(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, webgl: WebGLContext): GaussianDensityData {
  61. // always use texture2d when the gaussian density needs to be downloaded from the GPU,
  62. // it's faster than texture3d
  63. // console.time('GaussianDensityTexture2d')
  64. const tmpTexture = getTexture('tmp', webgl, 'image-uint8', 'rgba', 'ubyte', 'linear');
  65. const { scale, bbox, texture, gridDim, gridTexDim, radiusFactor, resolution } = calcGaussianDensityTexture2d(webgl, position, box, radius, false, props, tmpTexture);
  66. // webgl.waitForGpuCommandsCompleteSync()
  67. // console.timeEnd('GaussianDensityTexture2d')
  68. const { field, idField } = fieldFromTexture2d(webgl, texture, gridDim, gridTexDim);
  69. return { field, idField, transform: getTransform(scale, bbox), radiusFactor, resolution };
  70. }
  71. export function GaussianDensityTexture(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, oldTexture?: Texture): GaussianDensityTextureData {
  72. return webgl.isWebGL2 ?
  73. GaussianDensityTexture3d(webgl, position, box, radius, props, oldTexture) :
  74. GaussianDensityTexture2d(webgl, position, box, radius, false, props, oldTexture);
  75. }
  76. export function GaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, powerOfTwo: boolean, props: GaussianDensityProps, oldTexture?: Texture): GaussianDensityTextureData {
  77. return finalizeGaussianDensityTexture(calcGaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, props, oldTexture));
  78. }
  79. export function GaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, oldTexture?: Texture): GaussianDensityTextureData {
  80. return finalizeGaussianDensityTexture(calcGaussianDensityTexture3d(webgl, position, box, radius, props, oldTexture));
  81. }
  82. function finalizeGaussianDensityTexture({ texture, scale, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor, resolution }: _GaussianDensityTextureData): GaussianDensityTextureData {
  83. return { transform: getTransform(scale, bbox), texture, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor, resolution };
  84. }
  85. function getTransform(scale: Vec3, bbox: Box3D) {
  86. const transform = Mat4.identity();
  87. Mat4.fromScaling(transform, scale);
  88. Mat4.setTranslation(transform, bbox.min);
  89. return transform;
  90. }
  91. //
  92. type _GaussianDensityTextureData = {
  93. texture: Texture,
  94. scale: Vec3,
  95. bbox: Box3D,
  96. gridDim: Vec3,
  97. gridTexDim: Vec3
  98. gridTexScale: Vec2
  99. radiusFactor: number
  100. resolution: number
  101. }
  102. function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, powerOfTwo: boolean, props: GaussianDensityProps, texture?: Texture): _GaussianDensityTextureData {
  103. // console.log('2d');
  104. const { gl, resources, state, extensions: { colorBufferFloat, textureFloat, colorBufferHalfFloat, textureHalfFloat, blendMinMax } } = webgl;
  105. const { smoothness, resolution } = props;
  106. const { drawCount, positions, radii, groups, scale, expandedBox, dim, maxRadius } = prepareGaussianDensityData(position, box, radius, props);
  107. const [dx, dy, dz] = dim;
  108. const { texDimX, texDimY, texCols, powerOfTwoSize } = getTexture2dSize(dim);
  109. // console.log({ texDimX, texDimY, texCols, powerOfTwoSize, dim });
  110. const gridTexDim = Vec3.create(texDimX, texDimY, 0);
  111. const gridTexScale = Vec2.create(texDimX / powerOfTwoSize, texDimY / powerOfTwoSize);
  112. const radiusFactor = maxRadius * 2;
  113. const width = powerOfTwo ? powerOfTwoSize : texDimX;
  114. const height = powerOfTwo ? powerOfTwoSize : texDimY;
  115. const minDistTex = getTexture('min-dist-2d', webgl, 'image-uint8', 'rgba', 'ubyte', 'nearest');
  116. minDistTex.define(width, height);
  117. const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistTex, expandedBox, dim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor);
  118. //
  119. const { uCurrentSlice, uCurrentX, uCurrentY } = renderable.values;
  120. const framebuffer = getFramebuffer(webgl);
  121. framebuffer.bind();
  122. setRenderingDefaults(webgl);
  123. if (!texture) texture = colorBufferHalfFloat && textureHalfFloat
  124. ? resources.texture('image-float16', 'rgba', 'fp16', 'linear')
  125. : colorBufferFloat && textureFloat
  126. ? resources.texture('image-float32', 'rgba', 'float', 'linear')
  127. : resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
  128. texture.define(width, height);
  129. // console.log(renderable)
  130. function render(fbTex: Texture, clear: boolean) {
  131. state.currentRenderItemId = -1;
  132. fbTex.attachFramebuffer(framebuffer, 0);
  133. if (clear) {
  134. gl.viewport(0, 0, width, height);
  135. gl.scissor(0, 0, width, height);
  136. gl.clear(gl.COLOR_BUFFER_BIT);
  137. }
  138. ValueCell.update(uCurrentY, 0);
  139. let currCol = 0;
  140. let currY = 0;
  141. let currX = 0;
  142. for (let i = 0; i < dz; ++i) {
  143. if (currCol >= texCols) {
  144. currCol -= texCols;
  145. currY += dy;
  146. currX = 0;
  147. ValueCell.update(uCurrentY, currY);
  148. }
  149. // console.log({ i, currX, currY });
  150. ValueCell.update(uCurrentX, currX);
  151. ValueCell.update(uCurrentSlice, i);
  152. gl.viewport(currX, currY, dx, dy);
  153. gl.scissor(currX, currY, dx, dy);
  154. renderable.render();
  155. ++currCol;
  156. currX += dx;
  157. }
  158. gl.flush();
  159. }
  160. setupDensityRendering(webgl, renderable);
  161. render(texture, true);
  162. if (blendMinMax) {
  163. setupMinDistanceRendering(webgl, renderable);
  164. render(minDistTex, true);
  165. setupGroupIdRendering(webgl, renderable);
  166. render(texture, false);
  167. }
  168. // printTexture(webgl, minDistTex, 0.75);
  169. return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale, radiusFactor, resolution };
  170. }
  171. function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, texture?: Texture): _GaussianDensityTextureData {
  172. // console.log('3d');
  173. const { gl, resources, state, extensions: { colorBufferFloat, textureFloat, colorBufferHalfFloat, textureHalfFloat } } = webgl;
  174. const { smoothness, resolution } = props;
  175. const { drawCount, positions, radii, groups, scale, expandedBox, dim, maxRadius } = prepareGaussianDensityData(position, box, radius, props);
  176. const [dx, dy, dz] = dim;
  177. const minDistTex = getTexture('min-dist-3d', webgl, 'volume-uint8', 'rgba', 'ubyte', 'nearest');
  178. minDistTex.define(dx, dy, dz);
  179. const gridTexScale = Vec2.create(1, 1);
  180. const radiusFactor = maxRadius * 2;
  181. const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistTex, expandedBox, dim, dim, gridTexScale, smoothness, resolution, radiusFactor);
  182. //
  183. const { uCurrentSlice } = renderable.values;
  184. const framebuffer = getFramebuffer(webgl);
  185. framebuffer.bind();
  186. setRenderingDefaults(webgl);
  187. gl.viewport(0, 0, dx, dy);
  188. gl.scissor(0, 0, dx, dy);
  189. if (!texture) texture = colorBufferHalfFloat && textureHalfFloat
  190. ? resources.texture('volume-float16', 'rgba', 'fp16', 'linear')
  191. : colorBufferFloat && textureFloat
  192. ? resources.texture('volume-float32', 'rgba', 'float', 'linear')
  193. : resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear');
  194. texture.define(dx, dy, dz);
  195. function render(fbTex: Texture, clear: boolean) {
  196. state.currentRenderItemId = -1;
  197. for (let i = 0; i < dz; ++i) {
  198. ValueCell.update(uCurrentSlice, i);
  199. fbTex.attachFramebuffer(framebuffer, 0, i);
  200. if (clear) gl.clear(gl.COLOR_BUFFER_BIT);
  201. renderable.render();
  202. }
  203. gl.flush();
  204. }
  205. setupDensityRendering(webgl, renderable);
  206. render(texture, true);
  207. setupMinDistanceRendering(webgl, renderable);
  208. render(minDistTex, true);
  209. setupGroupIdRendering(webgl, renderable);
  210. render(texture, false);
  211. return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridTexScale, radiusFactor, resolution };
  212. }
  213. //
  214. function prepareGaussianDensityData(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps) {
  215. const { resolution, radiusOffset } = props;
  216. const scaleFactor = 1 / resolution;
  217. const { indices, x, y, z, id } = position;
  218. const n = OrderedSet.size(indices);
  219. const positions = new Float32Array(n * 3);
  220. const radii = new Float32Array(n);
  221. const groups = new Float32Array(n);
  222. let maxRadius = 0;
  223. for (let i = 0; i < n; ++i) {
  224. const j = OrderedSet.getAt(indices, i);
  225. positions[i * 3] = x[j];
  226. positions[i * 3 + 1] = y[j];
  227. positions[i * 3 + 2] = z[j];
  228. const r = radius(j) + radiusOffset;
  229. if (maxRadius < r) maxRadius = r;
  230. radii[i] = r;
  231. groups[i] = id ? id[i] : i;
  232. }
  233. const pad = maxRadius * 2 + resolution * 4;
  234. const expandedBox = Box3D.expand(Box3D(), box, Vec3.create(pad, pad, pad));
  235. const scaledBox = Box3D.scale(Box3D(), expandedBox, scaleFactor);
  236. const dim = Box3D.size(Vec3(), scaledBox);
  237. Vec3.ceil(dim, dim);
  238. const scale = Vec3.create(resolution, resolution, resolution);
  239. return { drawCount: n, positions, radii, groups, scale, expandedBox, dim, maxRadius };
  240. }
  241. function getGaussianDensityRenderable(webgl: WebGLContext, drawCount: number, positions: Float32Array, radii: Float32Array, groups: Float32Array, minDistanceTexture: Texture, box: Box3D, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, smoothness: number, resolution: number, radiusFactor: number) {
  242. // console.log('radiusFactor', radiusFactor);
  243. if (webgl.namedComputeRenderables[GaussianDensityName]) {
  244. const extent = Vec3.sub(Vec3(), box.max, box.min);
  245. const v = webgl.namedComputeRenderables[GaussianDensityName].values;
  246. ValueCell.updateIfChanged(v.drawCount, drawCount);
  247. ValueCell.updateIfChanged(v.instanceCount, 1);
  248. ValueCell.update(v.aRadius, radii);
  249. ValueCell.update(v.aPosition, positions);
  250. ValueCell.update(v.aGroup, groups);
  251. ValueCell.updateIfChanged(v.uCurrentSlice, 0);
  252. ValueCell.updateIfChanged(v.uCurrentX, 0);
  253. ValueCell.updateIfChanged(v.uCurrentY, 0);
  254. ValueCell.update(v.uBboxMin, box.min);
  255. ValueCell.update(v.uBboxSize, extent);
  256. ValueCell.update(v.uGridDim, gridDim);
  257. ValueCell.update(v.uGridTexDim, gridTexDim);
  258. ValueCell.update(v.uGridTexScale, gridTexScale);
  259. ValueCell.updateIfChanged(v.uAlpha, smoothness);
  260. ValueCell.updateIfChanged(v.uResolution, resolution);
  261. ValueCell.updateIfChanged(v.uRadiusFactorInv, 1 / radiusFactor);
  262. ValueCell.update(v.tMinDistanceTex, minDistanceTexture);
  263. ValueCell.updateIfChanged(v.dGridTexType, minDistanceTexture.getDepth() > 0 ? '3d' : '2d');
  264. ValueCell.updateIfChanged(v.dCalcType, 'density');
  265. webgl.namedComputeRenderables[GaussianDensityName].update();
  266. } else {
  267. webgl.namedComputeRenderables[GaussianDensityName] = createGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistanceTexture, box, gridDim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor);
  268. }
  269. return webgl.namedComputeRenderables[GaussianDensityName];
  270. }
  271. function createGaussianDensityRenderable(webgl: WebGLContext, drawCount: number, positions: Float32Array, radii: Float32Array, groups: Float32Array, minDistanceTexture: Texture, box: Box3D, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, smoothness: number, resolution: number, radiusFactor: number) {
  272. const extent = Vec3.sub(Vec3(), box.max, box.min);
  273. const values: GaussianDensityValues = {
  274. drawCount: ValueCell.create(drawCount),
  275. instanceCount: ValueCell.create(1),
  276. aRadius: ValueCell.create(radii),
  277. aPosition: ValueCell.create(positions),
  278. aGroup: ValueCell.create(groups),
  279. uCurrentSlice: ValueCell.create(0),
  280. uCurrentX: ValueCell.create(0),
  281. uCurrentY: ValueCell.create(0),
  282. uBboxMin: ValueCell.create(box.min),
  283. uBboxSize: ValueCell.create(extent),
  284. uGridDim: ValueCell.create(gridDim),
  285. uGridTexDim: ValueCell.create(gridTexDim),
  286. uGridTexScale: ValueCell.create(gridTexScale),
  287. uAlpha: ValueCell.create(smoothness),
  288. uResolution: ValueCell.create(resolution),
  289. uRadiusFactorInv: ValueCell.create(1 / radiusFactor),
  290. tMinDistanceTex: ValueCell.create(minDistanceTexture),
  291. dGridTexType: ValueCell.create(minDistanceTexture.getDepth() > 0 ? '3d' : '2d'),
  292. dCalcType: ValueCell.create('density'),
  293. };
  294. const schema = { ...GaussianDensitySchema };
  295. const shaderCode = ShaderCode(GaussianDensityName, gaussianDensity_vert, gaussianDensity_frag);
  296. const renderItem = createComputeRenderItem(webgl, 'points', shaderCode, schema, values);
  297. return createComputeRenderable(renderItem, values);
  298. }
  299. function setRenderingDefaults(ctx: WebGLContext) {
  300. const { gl, state } = ctx;
  301. state.disable(gl.CULL_FACE);
  302. state.enable(gl.BLEND);
  303. state.disable(gl.DEPTH_TEST);
  304. state.enable(gl.SCISSOR_TEST);
  305. state.depthMask(false);
  306. state.clearColor(0, 0, 0, 0);
  307. }
  308. function setupMinDistanceRendering(webgl: WebGLContext, renderable: GaussianDensityRenderable) {
  309. const { gl, state } = webgl;
  310. ValueCell.update(renderable.values.dCalcType, 'minDistance');
  311. renderable.update();
  312. state.colorMask(false, false, false, true);
  313. state.blendFunc(gl.ONE, gl.ONE);
  314. // the shader writes 1 - dist so we set blending to MAX
  315. if (!webgl.extensions.blendMinMax) {
  316. throw new Error('GPU gaussian surface calculation requires EXT_blend_minmax');
  317. }
  318. state.blendEquation(webgl.extensions.blendMinMax.MAX);
  319. }
  320. function setupDensityRendering(webgl: WebGLContext, renderable: GaussianDensityRenderable) {
  321. const { gl, state } = webgl;
  322. ValueCell.update(renderable.values.dCalcType, 'density');
  323. renderable.update();
  324. state.colorMask(false, false, false, true);
  325. state.blendFunc(gl.ONE, gl.ONE);
  326. state.blendEquation(gl.FUNC_ADD);
  327. }
  328. function setupGroupIdRendering(webgl: WebGLContext, renderable: GaussianDensityRenderable) {
  329. const { gl, state } = webgl;
  330. ValueCell.update(renderable.values.dCalcType, 'groupId');
  331. renderable.update();
  332. // overwrite color, don't change alpha
  333. state.colorMask(true, true, true, false);
  334. state.blendFunc(gl.ONE, gl.ZERO);
  335. state.blendEquation(gl.FUNC_ADD);
  336. }
  337. function getTexture2dSize(gridDim: Vec3) {
  338. const area = gridDim[0] * gridDim[1] * gridDim[2];
  339. const squareDim = Math.sqrt(area);
  340. const powerOfTwoSize = Math.pow(2, Math.ceil(Math.log(squareDim) / Math.log(2)));
  341. let texDimX = 0;
  342. let texDimY = gridDim[1];
  343. let texRows = 1;
  344. let texCols = gridDim[2];
  345. if (powerOfTwoSize < gridDim[0] * gridDim[2]) {
  346. texCols = Math.floor(powerOfTwoSize / gridDim[0]);
  347. texRows = Math.ceil(gridDim[2] / texCols);
  348. texDimX = texCols * gridDim[0];
  349. texDimY *= texRows;
  350. } else {
  351. texDimX = gridDim[0] * gridDim[2];
  352. }
  353. // console.log(texDimX, texDimY, texDimY < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2);
  354. return { texDimX, texDimY, texRows, texCols, powerOfTwoSize: texDimY < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2 };
  355. }
  356. function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec3, texDim: Vec3) {
  357. // console.time('fieldFromTexture2d')
  358. const [dx, dy, dz] = dim;
  359. const [width, height] = texDim;
  360. const fboTexCols = Math.floor(width / dx);
  361. const space = Tensor.Space(dim, [2, 1, 0], Float32Array);
  362. const data = space.create();
  363. const field = Tensor.create(space, data);
  364. const idData = space.create();
  365. const idField = Tensor.create(space, idData);
  366. const image = new Uint8Array(width * height * 4);
  367. const framebuffer = getFramebuffer(ctx);
  368. framebuffer.bind();
  369. texture.attachFramebuffer(framebuffer, 0);
  370. ctx.readPixels(0, 0, width, height, image);
  371. // printImageData(createImageData(image, width, height), 1/3)
  372. let j = 0;
  373. let tmpCol = 0;
  374. let tmpRow = 0;
  375. for (let iz = 0; iz < dz; ++iz) {
  376. if (tmpCol >= fboTexCols) {
  377. tmpCol = 0;
  378. tmpRow += dy;
  379. }
  380. for (let iy = 0; iy < dy; ++iy) {
  381. for (let ix = 0; ix < dx; ++ix) {
  382. const idx = 4 * (tmpCol * dx + (iy + tmpRow) * width + ix);
  383. data[j] = image[idx + 3] / 255;
  384. idData[j] = decodeFloatRGB(image[idx], image[idx + 1], image[idx + 2]);
  385. j++;
  386. }
  387. }
  388. tmpCol++;
  389. }
  390. // console.timeEnd('fieldFromTexture2d')
  391. return { field, idField };
  392. }