texture.ts 9.4 KB

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