context.ts 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  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 } 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. }
  98. /** A WebGL context object, including the rendering context, resource caches and counts */
  99. export interface Context {
  100. readonly gl: GLRenderingContext
  101. readonly isWebGL2: boolean
  102. readonly extensions: Extensions
  103. readonly pixelRatio: number
  104. readonly shaderCache: ShaderCache
  105. readonly programCache: ProgramCache
  106. bufferCount: number
  107. framebufferCount: number
  108. renderbufferCount: number
  109. textureCount: number
  110. vaoCount: number
  111. drawCount: number
  112. instanceCount: number
  113. instancedDrawCount: number
  114. readonly maxTextureSize: number
  115. readonly maxDrawBuffers: number
  116. unbindFramebuffer: () => void
  117. readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => void
  118. waitForGpuCommandsComplete: () => Promise<void>
  119. destroy: () => void
  120. }
  121. export function createContext(gl: GLRenderingContext): Context {
  122. const instancedArrays = getInstancedArrays(gl)
  123. if (instancedArrays === null) {
  124. throw new Error('Could not find support for "instanced_arrays"')
  125. }
  126. const standardDerivatives = getStandardDerivatives(gl)
  127. if (standardDerivatives === null) {
  128. throw new Error('Could not find support for "standard_derivatives"')
  129. }
  130. const blendMinMax = getBlendMinMax(gl)
  131. if (blendMinMax === null) {
  132. throw new Error('Could not find support for "blend_minmax"')
  133. }
  134. const textureFloat = getTextureFloat(gl)
  135. if (textureFloat === null) {
  136. throw new Error('Could not find support for "texture_float"')
  137. }
  138. const textureFloatLinear = getTextureFloatLinear(gl)
  139. if (textureFloatLinear === null) {
  140. throw new Error('Could not find support for "texture_float_linear"')
  141. }
  142. const elementIndexUint = getElementIndexUint(gl)
  143. if (elementIndexUint === null) {
  144. console.warn('Could not find support for "element_index_uint"')
  145. }
  146. const vertexArrayObject = getVertexArrayObject(gl)
  147. if (vertexArrayObject === null) {
  148. console.log('Could not find support for "vertex_array_object"')
  149. }
  150. const shaderCache = createShaderCache()
  151. const programCache = createProgramCache()
  152. const parameters = {
  153. maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
  154. maxDrawBuffers: isWebGL2(gl) ? gl.getParameter(gl.MAX_DRAW_BUFFERS) : 0,
  155. }
  156. return {
  157. gl,
  158. isWebGL2: isWebGL2(gl),
  159. extensions: {
  160. instancedArrays,
  161. standardDerivatives,
  162. blendMinMax,
  163. textureFloat,
  164. textureFloatLinear,
  165. elementIndexUint,
  166. vertexArrayObject
  167. },
  168. pixelRatio: getPixelRatio(),
  169. shaderCache,
  170. programCache,
  171. bufferCount: 0,
  172. framebufferCount: 0,
  173. renderbufferCount: 0,
  174. textureCount: 0,
  175. vaoCount: 0,
  176. drawCount: 0,
  177. instanceCount: 0,
  178. instancedDrawCount: 0,
  179. get maxTextureSize () { return parameters.maxTextureSize },
  180. get maxDrawBuffers () { return parameters.maxDrawBuffers },
  181. unbindFramebuffer: () => unbindFramebuffer(gl),
  182. readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => {
  183. gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer)
  184. // TODO check is very expensive
  185. // if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE) {
  186. // gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer)
  187. // } else {
  188. // console.error('Reading pixels failed. Framebuffer not complete.')
  189. // }
  190. },
  191. waitForGpuCommandsComplete: () => waitForGpuCommandsComplete(gl),
  192. destroy: () => {
  193. unbindResources(gl)
  194. programCache.dispose()
  195. shaderCache.dispose()
  196. // TODO destroy buffers and textures
  197. }
  198. }
  199. }