texture.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /**
  2. * Copyright (c) 2018-2019 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 } from './compat';
  13. import { ValueOf } from 'mol-util/type-helpers';
  14. const getNextTextureId = idFactory()
  15. export type TextureKindValue = {
  16. 'image-uint8': TextureImage<Uint8Array>
  17. 'image-float32': TextureImage<Float32Array>
  18. 'volume-uint8': TextureVolume<Uint8Array>
  19. 'volume-float32': TextureVolume<Float32Array>
  20. 'texture': Texture
  21. }
  22. export type TextureValueType = ValueOf<TextureKindValue>
  23. export type TextureKind = keyof TextureKindValue
  24. export type TextureType = 'ubyte' | 'float'
  25. export type TextureFormat = 'alpha' | 'rgb' | 'rgba'
  26. /** Numbers are shortcuts for color attachment */
  27. export type TextureAttachment = 'depth' | 'stencil' | 'color0' | 'color1' | 'color2' | 'color3' | 'color4' | 'color5' | 'color6' | 'color7' | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
  28. export type TextureFilter = 'nearest' | 'linear'
  29. export function getTarget(ctx: WebGLContext, kind: TextureKind): number {
  30. const { gl } = ctx
  31. switch (kind) {
  32. case 'image-uint8': return gl.TEXTURE_2D
  33. case 'image-float32': return gl.TEXTURE_2D
  34. }
  35. if (isWebGL2(gl)) {
  36. switch (kind) {
  37. case 'volume-uint8': return gl.TEXTURE_3D
  38. case 'volume-float32': return gl.TEXTURE_3D
  39. }
  40. }
  41. throw new Error(`unknown texture kind '${kind}'`)
  42. }
  43. export function getFormat(ctx: WebGLContext, format: TextureFormat): number {
  44. const { gl } = ctx
  45. switch (format) {
  46. case 'alpha': return gl.ALPHA
  47. case 'rgb': return gl.RGB
  48. case 'rgba': return gl.RGBA
  49. }
  50. }
  51. export function getInternalFormat(ctx: WebGLContext, format: TextureFormat, type: TextureType): number {
  52. const { gl, isWebGL2 } = ctx
  53. if (isWebGL2) {
  54. switch (format) {
  55. case 'alpha':
  56. switch (type) {
  57. case 'ubyte': return gl.ALPHA
  58. case 'float': throw new Error('invalid format/type combination alpha/float')
  59. }
  60. case 'rgb':
  61. switch (type) {
  62. case 'ubyte': return gl.RGB
  63. case 'float': return (gl as WebGL2RenderingContext).RGB32F
  64. }
  65. case 'rgba':
  66. switch (type) {
  67. case 'ubyte': return gl.RGBA
  68. case 'float': return (gl as WebGL2RenderingContext).RGBA32F
  69. }
  70. }
  71. }
  72. return getFormat(ctx, format)
  73. }
  74. export function getType(ctx: WebGLContext, type: TextureType): number {
  75. const { gl } = ctx
  76. switch (type) {
  77. case 'ubyte': return gl.UNSIGNED_BYTE
  78. case 'float': return gl.FLOAT
  79. }
  80. }
  81. export function getFilter(ctx: WebGLContext, type: TextureFilter): number {
  82. const { gl } = ctx
  83. switch (type) {
  84. case 'nearest': return gl.NEAREST
  85. case 'linear': return gl.LINEAR
  86. }
  87. }
  88. export function getAttachment(ctx: WebGLContext, attachment: TextureAttachment): number {
  89. const { gl } = ctx
  90. switch (attachment) {
  91. case 'depth': return gl.DEPTH_ATTACHMENT
  92. case 'stencil': return gl.STENCIL_ATTACHMENT
  93. case 'color0': case 0: return gl.COLOR_ATTACHMENT0
  94. }
  95. if (isWebGL2(gl)) {
  96. switch (attachment) {
  97. case 'color1': case 1: return gl.COLOR_ATTACHMENT1
  98. case 'color2': case 2: return gl.COLOR_ATTACHMENT2
  99. case 'color3': case 3: return gl.COLOR_ATTACHMENT3
  100. case 'color4': case 4: return gl.COLOR_ATTACHMENT4
  101. case 'color5': case 5: return gl.COLOR_ATTACHMENT5
  102. case 'color6': case 6: return gl.COLOR_ATTACHMENT6
  103. case 'color7': case 7: return gl.COLOR_ATTACHMENT7
  104. }
  105. }
  106. throw new Error('unknown texture attachment')
  107. }
  108. export interface Texture {
  109. readonly id: number
  110. readonly target: number
  111. readonly format: number
  112. readonly internalFormat: number
  113. readonly type: number
  114. readonly width: number
  115. readonly height: number
  116. readonly depth: number
  117. define: (width: number, height: number, depth?: number) => void
  118. load: (image: TextureImage<any> | TextureVolume<any>) => void
  119. bind: (id: TextureId) => void
  120. unbind: (id: TextureId) => void
  121. /** Use `layer` to attach a z-slice of a 3D texture */
  122. attachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment, layer?: number) => void
  123. detachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment) => void
  124. destroy: () => void
  125. }
  126. export type TextureId = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15
  127. export type TextureValues = { [k: string]: ValueCell<TextureValueType> }
  128. export type Textures = [string, Texture][]
  129. export function createTexture(ctx: WebGLContext, kind: TextureKind, _format: TextureFormat, _type: TextureType, _filter: TextureFilter): Texture {
  130. const id = getNextTextureId()
  131. const { gl, stats } = ctx
  132. const texture = gl.createTexture()
  133. if (texture === null) {
  134. throw new Error('Could not create WebGL texture')
  135. }
  136. const target = getTarget(ctx, kind)
  137. const filter = getFilter(ctx, _filter)
  138. const format = getFormat(ctx, _format)
  139. const internalFormat = getInternalFormat(ctx, _format, _type)
  140. const type = getType(ctx, _type)
  141. gl.bindTexture(target, texture)
  142. gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, filter)
  143. gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, filter)
  144. // clamp-to-edge needed for non-power-of-two textures in webgl
  145. gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  146. gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  147. gl.bindTexture(target, null)
  148. let width = 0, height = 0, depth = 0
  149. let destroyed = false
  150. stats.textureCount += 1
  151. return {
  152. id,
  153. target,
  154. format,
  155. internalFormat,
  156. type,
  157. get width () { return width },
  158. get height () { return height },
  159. get depth () { return depth },
  160. define: (_width: number, _height: number, _depth?: number) => {
  161. width = _width, height = _height, depth = _depth || 0
  162. gl.bindTexture(target, texture)
  163. if (target === gl.TEXTURE_2D) {
  164. gl.texImage2D(target, 0, internalFormat, width, height, 0, format, type, null)
  165. } else if (target === (gl as WebGL2RenderingContext).TEXTURE_3D && depth !== undefined) {
  166. (gl as WebGL2RenderingContext).texImage3D(target, 0, internalFormat, width, height, depth, 0, format, type, null)
  167. } else {
  168. throw new Error('unknown texture target')
  169. }
  170. },
  171. load: (data: TextureImage<any> | TextureVolume<any>) => {
  172. gl.bindTexture(target, texture)
  173. // unpack alignment of 1 since we use textures only for data
  174. gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
  175. gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
  176. gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
  177. if (target === gl.TEXTURE_2D) {
  178. const { array, width: _width, height: _height } = data as TextureImage<any>
  179. width = _width, height = _height;
  180. gl.texImage2D(target, 0, internalFormat, width, height, 0, format, type, array)
  181. } else if (target === (gl as WebGL2RenderingContext).TEXTURE_3D) {
  182. const { array, width: _width, height: _height, depth: _depth } = data as TextureVolume<any>
  183. width = _width, height = _height, depth = _depth;
  184. (gl as WebGL2RenderingContext).texImage3D(target, 0, internalFormat, width, height, depth, 0, format, type, array)
  185. } else {
  186. throw new Error('unknown texture target')
  187. }
  188. gl.bindTexture(target, null)
  189. },
  190. bind: (id: TextureId) => {
  191. gl.activeTexture(gl.TEXTURE0 + id)
  192. gl.bindTexture(target, texture)
  193. },
  194. unbind: (id: TextureId) => {
  195. gl.activeTexture(gl.TEXTURE0 + id)
  196. gl.bindTexture(target, null)
  197. },
  198. attachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment, layer?: number) => {
  199. framebuffer.bind()
  200. if (target === (gl as WebGL2RenderingContext).TEXTURE_3D) {
  201. if (layer === undefined) throw new Error('need `layer` to attach 3D texture');
  202. (gl as WebGL2RenderingContext).framebufferTextureLayer(gl.FRAMEBUFFER, getAttachment(ctx, attachment), texture, 0, layer)
  203. } else {
  204. gl.framebufferTexture2D(gl.FRAMEBUFFER, getAttachment(ctx, attachment), gl.TEXTURE_2D, texture, 0)
  205. }
  206. },
  207. detachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment) => {
  208. framebuffer.bind()
  209. if (target === (gl as WebGL2RenderingContext).TEXTURE_3D) {
  210. (gl as WebGL2RenderingContext).framebufferTextureLayer(gl.FRAMEBUFFER, getAttachment(ctx, attachment), null, 0, 0)
  211. } else {
  212. gl.framebufferTexture2D(gl.FRAMEBUFFER, getAttachment(ctx, attachment), gl.TEXTURE_2D, null, 0)
  213. }
  214. },
  215. destroy: () => {
  216. if (destroyed) return
  217. gl.deleteTexture(texture)
  218. destroyed = true
  219. stats.textureCount -= 1
  220. }
  221. }
  222. }
  223. export function createTextures(ctx: WebGLContext, schema: RenderableSchema, values: TextureValues) {
  224. const textures: Textures = []
  225. Object.keys(schema).forEach((k, i) => {
  226. const spec = schema[k]
  227. if (spec.type === 'texture') {
  228. if (spec.kind === 'texture') {
  229. textures[textures.length] = [k, values[k].ref.value as Texture]
  230. } else {
  231. const texture = createTexture(ctx, spec.kind, spec.format, spec.dataType, spec.filter)
  232. texture.load(values[k].ref.value as TextureImage<any> | TextureVolume<any>)
  233. textures[textures.length] = [k, texture]
  234. }
  235. }
  236. })
  237. return textures
  238. }