texture.ts 11 KB

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