context.ts 8.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 { createProgramCache, ProgramCache } from './program'
  7. import { createShaderCache, ShaderCache } from './shader'
  8. import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, isWebGL2, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth } from './compat';
  9. export function getGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes): GLRenderingContext | null {
  10. function getContext(contextId: 'webgl' | 'experimental-webgl' | 'webgl2') {
  11. try {
  12. return canvas.getContext(contextId, contextAttributes) as GLRenderingContext | null
  13. } catch (e) {
  14. return null
  15. }
  16. }
  17. return getContext('webgl2') || getContext('webgl') || getContext('experimental-webgl')
  18. }
  19. function getPixelRatio() {
  20. return (typeof window !== 'undefined') ? window.devicePixelRatio : 1
  21. }
  22. function unbindResources (gl: GLRenderingContext) {
  23. // bind null to all texture units
  24. const maxTextureImageUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS)
  25. for (let i = 0; i < maxTextureImageUnits; ++i) {
  26. gl.activeTexture(gl.TEXTURE0 + i)
  27. gl.bindTexture(gl.TEXTURE_2D, null)
  28. gl.bindTexture(gl.TEXTURE_CUBE_MAP, null)
  29. }
  30. // assign the smallest possible buffer to all attributes
  31. const buf = gl.createBuffer();
  32. gl.bindBuffer(gl.ARRAY_BUFFER, buf);
  33. const maxVertexAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
  34. for (let i = 0; i < maxVertexAttribs; ++i) {
  35. gl.vertexAttribPointer(i, 1, gl.FLOAT, false, 0, 0);
  36. }
  37. // bind null to all buffers
  38. gl.bindBuffer(gl.ARRAY_BUFFER, null)
  39. gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null)
  40. gl.bindRenderbuffer(gl.RENDERBUFFER, null)
  41. unbindFramebuffer(gl)
  42. }
  43. function unbindFramebuffer(gl: GLRenderingContext) {
  44. gl.bindFramebuffer(gl.FRAMEBUFFER, null)
  45. }
  46. const tmpPixel = new Uint8Array(1 * 4);
  47. async function waitForGpuCommandsComplete(gl: GLRenderingContext) {
  48. if (isWebGL2(gl)) {
  49. const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
  50. if (sync) {
  51. // TODO too slow in Firefox
  52. // await new Promise(resolve => {
  53. // const check = async () => {
  54. // if (gl.getSyncParameter(sync, gl.SYNC_STATUS) === gl.SIGNALED) {
  55. // gl.deleteSync(sync)
  56. // resolve();
  57. // } else {
  58. // setTimeout(check, 50)
  59. // }
  60. // };
  61. // setTimeout(check, 10)
  62. // })
  63. gl.deleteSync(sync)
  64. gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel)
  65. } else {
  66. console.warn('unable to get webgl sync object')
  67. gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel)
  68. }
  69. } else {
  70. console.info('webgl sync object not supported in webgl 1')
  71. gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel)
  72. }
  73. }
  74. export function createImageData(buffer: ArrayLike<number>, width: number, height: number) {
  75. const w = width * 4
  76. const h = height
  77. const data = new Uint8ClampedArray(width * height * 4)
  78. for (let i = 0, maxI = h / 2; i < maxI; ++i) {
  79. for (let j = 0, maxJ = w; j < maxJ; ++j) {
  80. const index1 = i * w + j;
  81. const index2 = (h-i-1) * w + j;
  82. data[index1] = buffer[index2];
  83. data[index2] = buffer[index1];
  84. }
  85. }
  86. return new ImageData(data, width, height);
  87. }
  88. //
  89. type Extensions = {
  90. instancedArrays: COMPAT_instanced_arrays
  91. standardDerivatives: COMPAT_standard_derivatives
  92. blendMinMax: COMPAT_blend_minmax
  93. textureFloat: COMPAT_texture_float
  94. textureFloatLinear: COMPAT_texture_float_linear
  95. elementIndexUint: COMPAT_element_index_uint | null
  96. vertexArrayObject: COMPAT_vertex_array_object | null
  97. fragDepth: COMPAT_frag_depth | null
  98. }
  99. /** A WebGL context object, including the rendering context, resource caches and counts */
  100. export interface Context {
  101. readonly gl: GLRenderingContext
  102. readonly isWebGL2: boolean
  103. readonly extensions: Extensions
  104. readonly pixelRatio: number
  105. readonly shaderCache: ShaderCache
  106. readonly programCache: ProgramCache
  107. bufferCount: number
  108. framebufferCount: number
  109. renderbufferCount: number
  110. textureCount: number
  111. vaoCount: number
  112. drawCount: number
  113. instanceCount: number
  114. instancedDrawCount: number
  115. readonly maxTextureSize: number
  116. readonly maxDrawBuffers: number
  117. unbindFramebuffer: () => void
  118. readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => void
  119. waitForGpuCommandsComplete: () => Promise<void>
  120. destroy: () => void
  121. }
  122. export function createContext(gl: GLRenderingContext): Context {
  123. const instancedArrays = getInstancedArrays(gl)
  124. if (instancedArrays === null) {
  125. throw new Error('Could not find support for "instanced_arrays"')
  126. }
  127. const standardDerivatives = getStandardDerivatives(gl)
  128. if (standardDerivatives === null) {
  129. throw new Error('Could not find support for "standard_derivatives"')
  130. }
  131. const blendMinMax = getBlendMinMax(gl)
  132. if (blendMinMax === null) {
  133. throw new Error('Could not find support for "blend_minmax"')
  134. }
  135. const textureFloat = getTextureFloat(gl)
  136. if (textureFloat === null) {
  137. throw new Error('Could not find support for "texture_float"')
  138. }
  139. const textureFloatLinear = getTextureFloatLinear(gl)
  140. if (textureFloatLinear === null) {
  141. throw new Error('Could not find support for "texture_float_linear"')
  142. }
  143. const elementIndexUint = getElementIndexUint(gl)
  144. if (elementIndexUint === null) {
  145. console.warn('Could not find support for "element_index_uint"')
  146. }
  147. const vertexArrayObject = getVertexArrayObject(gl)
  148. if (vertexArrayObject === null) {
  149. console.log('Could not find support for "vertex_array_object"')
  150. }
  151. const fragDepth = getFragDepth(gl)
  152. if (fragDepth === null) {
  153. console.log('Could not find support for "frag_depth"')
  154. }
  155. const shaderCache = createShaderCache()
  156. const programCache = createProgramCache()
  157. const parameters = {
  158. maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
  159. maxDrawBuffers: isWebGL2(gl) ? gl.getParameter(gl.MAX_DRAW_BUFFERS) : 0,
  160. maxVertexTextureImageUnits: gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS),
  161. }
  162. if (parameters.maxVertexTextureImageUnits < 4) {
  163. throw new Error('Need "MAX_VERTEX_TEXTURE_IMAGE_UNITS" >= 4')
  164. }
  165. return {
  166. gl,
  167. isWebGL2: isWebGL2(gl),
  168. extensions: {
  169. instancedArrays,
  170. standardDerivatives,
  171. blendMinMax,
  172. textureFloat,
  173. textureFloatLinear,
  174. elementIndexUint,
  175. vertexArrayObject,
  176. fragDepth
  177. },
  178. pixelRatio: getPixelRatio(),
  179. shaderCache,
  180. programCache,
  181. bufferCount: 0,
  182. framebufferCount: 0,
  183. renderbufferCount: 0,
  184. textureCount: 0,
  185. vaoCount: 0,
  186. drawCount: 0,
  187. instanceCount: 0,
  188. instancedDrawCount: 0,
  189. get maxTextureSize () { return parameters.maxTextureSize },
  190. get maxDrawBuffers () { return parameters.maxDrawBuffers },
  191. unbindFramebuffer: () => unbindFramebuffer(gl),
  192. readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => {
  193. gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer)
  194. // TODO check is very expensive
  195. // if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE) {
  196. // gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer)
  197. // } else {
  198. // console.error('Reading pixels failed. Framebuffer not complete.')
  199. // }
  200. },
  201. waitForGpuCommandsComplete: () => waitForGpuCommandsComplete(gl),
  202. destroy: () => {
  203. unbindResources(gl)
  204. programCache.dispose()
  205. shaderCache.dispose()
  206. // TODO destroy buffers and textures
  207. }
  208. }
  209. }