texture.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. /**
  2. * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { WebGLContext } from './context';
  7. import { TextureImage, TextureVolume } from '../renderable/util';
  8. import { ValueCell } from '../../mol-util';
  9. import { RenderableSchema } from '../renderable/schema';
  10. import { idFactory } from '../../mol-util/id-factory';
  11. import { Framebuffer } from './framebuffer';
  12. import { isWebGL2, GLRenderingContext } from './compat';
  13. import { ValueOf } from '../../mol-util/type-helpers';
  14. import { WebGLExtensions } from './extensions';
  15. const getNextTextureId = idFactory();
  16. export type TextureKindValue = {
  17. 'image-uint8': TextureImage<Uint8Array>
  18. 'image-float32': TextureImage<Float32Array>
  19. 'image-depth': TextureImage<Uint8Array> // TODO should be Uint32Array
  20. 'volume-uint8': TextureVolume<Uint8Array>
  21. 'volume-float32': TextureVolume<Float32Array>
  22. 'texture': Texture
  23. }
  24. export type TextureValueType = ValueOf<TextureKindValue>
  25. export type TextureKind = keyof TextureKindValue
  26. export type TextureType = 'ubyte' | 'ushort' | 'float'
  27. export type TextureFormat = 'alpha' | 'rgb' | 'rgba' | 'depth'
  28. /** Numbers are shortcuts for color attachment */
  29. export type TextureAttachment = 'depth' | 'stencil' | 'color0' | 'color1' | 'color2' | 'color3' | 'color4' | 'color5' | 'color6' | 'color7' | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
  30. export type TextureFilter = 'nearest' | 'linear'
  31. export function getTarget(gl: GLRenderingContext, kind: TextureKind): number {
  32. switch (kind) {
  33. case 'image-uint8': return gl.TEXTURE_2D;
  34. case 'image-float32': return gl.TEXTURE_2D;
  35. case 'image-depth': return gl.TEXTURE_2D;
  36. }
  37. if (isWebGL2(gl)) {
  38. switch (kind) {
  39. case 'volume-uint8': return gl.TEXTURE_3D;
  40. case 'volume-float32': return gl.TEXTURE_3D;
  41. }
  42. }
  43. throw new Error(`unknown texture kind '${kind}'`);
  44. }
  45. export function getFormat(gl: GLRenderingContext, format: TextureFormat, type: TextureType): number {
  46. switch (format) {
  47. case 'alpha':
  48. if (isWebGL2(gl) && type === 'float') return gl.RED;
  49. else return gl.ALPHA;
  50. case 'rgb': return gl.RGB;
  51. case 'rgba': return gl.RGBA;
  52. case 'depth': return gl.DEPTH_COMPONENT;
  53. }
  54. }
  55. export function getInternalFormat(gl: GLRenderingContext, format: TextureFormat, type: TextureType): number {
  56. if (isWebGL2(gl)) {
  57. switch (format) {
  58. case 'alpha':
  59. switch (type) {
  60. case 'ubyte': return gl.ALPHA;
  61. case 'float': return gl.R32F;
  62. }
  63. case 'rgb':
  64. switch (type) {
  65. case 'ubyte': return gl.RGB;
  66. case 'float': return gl.RGB32F;
  67. }
  68. case 'rgba':
  69. switch (type) {
  70. case 'ubyte': return gl.RGBA;
  71. case 'float': return gl.RGBA32F;
  72. }
  73. case 'depth':
  74. return gl.DEPTH_COMPONENT16;
  75. }
  76. }
  77. return getFormat(gl, format, type);
  78. }
  79. export function getType(gl: GLRenderingContext, type: TextureType): number {
  80. switch (type) {
  81. case 'ubyte': return gl.UNSIGNED_BYTE;
  82. case 'ushort': return gl.UNSIGNED_SHORT;
  83. case 'float': return gl.FLOAT;
  84. }
  85. }
  86. export function getFilter(gl: GLRenderingContext, type: TextureFilter): number {
  87. switch (type) {
  88. case 'nearest': return gl.NEAREST;
  89. case 'linear': return gl.LINEAR;
  90. }
  91. }
  92. export function getAttachment(gl: GLRenderingContext, extensions: WebGLExtensions, attachment: TextureAttachment): number {
  93. switch (attachment) {
  94. case 'depth': return gl.DEPTH_ATTACHMENT;
  95. case 'stencil': return gl.STENCIL_ATTACHMENT;
  96. case 'color0': case 0: return gl.COLOR_ATTACHMENT0;
  97. }
  98. if (extensions.drawBuffers) {
  99. switch (attachment) {
  100. case 'color1': case 1: return extensions.drawBuffers.COLOR_ATTACHMENT1;
  101. case 'color2': case 2: return extensions.drawBuffers.COLOR_ATTACHMENT2;
  102. case 'color3': case 3: return extensions.drawBuffers.COLOR_ATTACHMENT3;
  103. case 'color4': case 4: return extensions.drawBuffers.COLOR_ATTACHMENT4;
  104. case 'color5': case 5: return extensions.drawBuffers.COLOR_ATTACHMENT5;
  105. case 'color6': case 6: return extensions.drawBuffers.COLOR_ATTACHMENT6;
  106. case 'color7': case 7: return extensions.drawBuffers.COLOR_ATTACHMENT7;
  107. }
  108. }
  109. throw new Error('unknown texture attachment');
  110. }
  111. function isImage(x: TextureImage<any> | TextureVolume<any> | HTMLImageElement): x is HTMLImageElement {
  112. return typeof HTMLImageElement !== undefined && (x instanceof HTMLImageElement);
  113. }
  114. function isTexture2d(x: TextureImage<any> | TextureVolume<any>, target: number, gl: GLRenderingContext): x is TextureImage<any> {
  115. return target === gl.TEXTURE_2D;
  116. }
  117. function isTexture3d(x: TextureImage<any> | TextureVolume<any>, target: number, gl: WebGL2RenderingContext): x is TextureImage<any> {
  118. return target === gl.TEXTURE_3D;
  119. }
  120. export interface Texture {
  121. readonly id: number
  122. readonly target: number
  123. readonly format: number
  124. readonly internalFormat: number
  125. readonly type: number
  126. getWidth: () => number
  127. getHeight: () => number
  128. getDepth: () => number
  129. define: (width: number, height: number, depth?: number) => void
  130. load: (image: TextureImage<any> | TextureVolume<any> | HTMLImageElement) => void
  131. bind: (id: TextureId) => void
  132. unbind: (id: TextureId) => void
  133. /** Use `layer` to attach a z-slice of a 3D texture */
  134. attachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment, layer?: number) => void
  135. detachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment) => void
  136. reset: () => void
  137. destroy: () => void
  138. }
  139. export type TextureId = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15
  140. export type TextureValues = { [k: string]: ValueCell<TextureValueType> }
  141. export type Textures = [string, Texture][]
  142. function getTexture(gl: GLRenderingContext) {
  143. const texture = gl.createTexture();
  144. if (texture === null) {
  145. throw new Error('Could not create WebGL texture');
  146. }
  147. return texture;
  148. }
  149. export function createTexture(gl: GLRenderingContext, extensions: WebGLExtensions, kind: TextureKind, _format: TextureFormat, _type: TextureType, _filter: TextureFilter): Texture {
  150. const id = getNextTextureId();
  151. let texture = getTexture(gl);
  152. // check texture kind and type compatability
  153. if (
  154. (kind.endsWith('float32') && _type !== 'float') ||
  155. (kind.endsWith('uint8') && _type !== 'ubyte') ||
  156. (kind.endsWith('depth') && _type !== 'ushort')
  157. ) {
  158. throw new Error(`texture kind '${kind}' and type '${_type}' are incompatible`);
  159. }
  160. const target = getTarget(gl, kind);
  161. const filter = getFilter(gl, _filter);
  162. const format = getFormat(gl, _format, _type);
  163. const internalFormat = getInternalFormat(gl, _format, _type);
  164. const type = getType(gl, _type);
  165. function init() {
  166. gl.bindTexture(target, texture);
  167. gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, filter);
  168. gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, filter);
  169. // clamp-to-edge needed for non-power-of-two textures in webgl
  170. gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  171. gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  172. gl.bindTexture(target, null);
  173. }
  174. init();
  175. let width = 0, height = 0, depth = 0;
  176. let loadedData: undefined | TextureImage<any> | TextureVolume<any> | HTMLImageElement;
  177. let destroyed = false;
  178. function define(_width: number, _height: number, _depth?: number) {
  179. if (width === _width && height === _height && depth === (_depth || 0)) return;
  180. width = _width, height = _height, depth = _depth || 0;
  181. gl.bindTexture(target, texture);
  182. if (target === gl.TEXTURE_2D) {
  183. gl.texImage2D(target, 0, internalFormat, width, height, 0, format, type, null);
  184. } else if (isWebGL2(gl) && target === gl.TEXTURE_3D && depth !== undefined) {
  185. gl.texImage3D(target, 0, internalFormat, width, height, depth, 0, format, type, null);
  186. } else {
  187. throw new Error('unknown texture target');
  188. }
  189. }
  190. function load(data: TextureImage<any> | TextureVolume<any> | HTMLImageElement) {
  191. gl.bindTexture(target, texture);
  192. // unpack alignment of 1 since we use textures only for data
  193. gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
  194. gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
  195. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
  196. if (isImage(data)) {
  197. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
  198. gl.bindTexture(gl.TEXTURE_2D, texture);
  199. gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, format, type, data);
  200. } else if (isTexture2d(data, target, gl)) {
  201. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, !!data.flipY);
  202. width = data.width, height = data.height;
  203. gl.texImage2D(target, 0, internalFormat, width, height, 0, format, type, data.array);
  204. } else if (isWebGL2(gl) && isTexture3d(data, target, gl)) {
  205. gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
  206. width = data.width, height = data.height, depth = data.depth;
  207. gl.texImage3D(target, 0, internalFormat, width, height, depth, 0, format, type, data.array);
  208. } else {
  209. throw new Error('unknown texture target');
  210. }
  211. gl.bindTexture(target, null);
  212. loadedData = data;
  213. }
  214. function attachFramebuffer(framebuffer: Framebuffer, attachment: TextureAttachment, layer?: number) {
  215. framebuffer.bind();
  216. if (target === gl.TEXTURE_2D) {
  217. gl.framebufferTexture2D(gl.FRAMEBUFFER, getAttachment(gl, extensions, attachment), gl.TEXTURE_2D, texture, 0);
  218. } else if (isWebGL2(gl) && target === gl.TEXTURE_3D) {
  219. if (layer === undefined) throw new Error('need `layer` to attach 3D texture');
  220. gl.framebufferTextureLayer(gl.FRAMEBUFFER, getAttachment(gl, extensions, attachment), texture, 0, layer);
  221. } else {
  222. throw new Error('unknown texture target');
  223. }
  224. }
  225. return {
  226. id,
  227. target,
  228. format,
  229. internalFormat,
  230. type,
  231. getWidth: () => width,
  232. getHeight: () => height,
  233. getDepth: () => depth,
  234. define,
  235. load,
  236. bind: (id: TextureId) => {
  237. gl.activeTexture(gl.TEXTURE0 + id);
  238. gl.bindTexture(target, texture);
  239. },
  240. unbind: (id: TextureId) => {
  241. gl.activeTexture(gl.TEXTURE0 + id);
  242. gl.bindTexture(target, null);
  243. },
  244. attachFramebuffer,
  245. detachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment) => {
  246. framebuffer.bind();
  247. if (target === gl.TEXTURE_2D) {
  248. gl.framebufferTexture2D(gl.FRAMEBUFFER, getAttachment(gl, extensions, attachment), gl.TEXTURE_2D, null, 0);
  249. } else if (isWebGL2(gl) && target === gl.TEXTURE_3D) {
  250. gl.framebufferTextureLayer(gl.FRAMEBUFFER, getAttachment(gl, extensions, attachment), null, 0, 0);
  251. } else {
  252. throw new Error('unknown texture target');
  253. }
  254. },
  255. reset: () => {
  256. texture = getTexture(gl);
  257. init();
  258. if (loadedData) {
  259. load(loadedData);
  260. } else {
  261. define(width, height, depth);
  262. }
  263. },
  264. destroy: () => {
  265. if (destroyed) return;
  266. gl.deleteTexture(texture);
  267. destroyed = true;
  268. }
  269. };
  270. }
  271. export function createTextures(ctx: WebGLContext, schema: RenderableSchema, values: TextureValues) {
  272. const { resources } = ctx;
  273. const textures: Textures = [];
  274. Object.keys(schema).forEach(k => {
  275. const spec = schema[k];
  276. if (spec.type === 'texture') {
  277. const value = values[k];
  278. if (value) {
  279. if (spec.kind === 'texture') {
  280. textures[textures.length] = [k, value.ref.value as Texture];
  281. } else {
  282. const texture = resources.texture(spec.kind, spec.format, spec.dataType, spec.filter);
  283. texture.load(value.ref.value as TextureImage<any> | TextureVolume<any>);
  284. textures[textures.length] = [k, texture];
  285. }
  286. }
  287. }
  288. });
  289. return textures;
  290. }
  291. /**
  292. * Loads an image from a url to a textures and triggers update asynchronously.
  293. * This will not work on node.js with a polyfill for HTMLImageElement.
  294. */
  295. export function loadImageTexture(src: string, cell: ValueCell<Texture>, texture: Texture) {
  296. const img = new Image();
  297. img.onload = function() {
  298. texture.load(img);
  299. ValueCell.update(cell, texture);
  300. };
  301. img.src = src;
  302. }
  303. //
  304. export function createNullTexture(gl: GLRenderingContext, kind: TextureKind): Texture {
  305. const target = getTarget(gl, kind);
  306. return {
  307. id: getNextTextureId(),
  308. target,
  309. format: 0,
  310. internalFormat: 0,
  311. type: 0,
  312. getWidth: () => 0,
  313. getHeight: () => 0,
  314. getDepth: () => 0,
  315. define: () => {},
  316. load: () => {},
  317. bind: (id: TextureId) => {
  318. gl.activeTexture(gl.TEXTURE0 + id);
  319. gl.bindTexture(target, null);
  320. },
  321. unbind: (id: TextureId) => {
  322. gl.activeTexture(gl.TEXTURE0 + id);
  323. gl.bindTexture(target, null);
  324. },
  325. attachFramebuffer: () => {},
  326. detachFramebuffer: () => {},
  327. reset: () => {},
  328. destroy: () => {},
  329. };
  330. }