render-item.ts 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  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 { createAttributeBuffers, createElementsBuffer, ElementsBuffer, createAttributeBuffer, ArrayKind } from './buffer';
  7. import { createTextures } from './texture';
  8. import { Context } from './context';
  9. import { ShaderCode, addShaderDefines } from '../shader-code';
  10. import { Program } from './program';
  11. import { RenderableSchema, RenderableValues, AttributeSpec, getValueVersions, splitValues } from '../renderable/schema';
  12. import { idFactory } from 'mol-util/id-factory';
  13. import { deleteVertexArray, createVertexArray } from './vertex-array';
  14. const getNextRenderItemId = idFactory()
  15. export type DrawMode = 'points' | 'lines' | 'line-strip' | 'line-loop' | 'triangles' | 'triangle-strip' | 'triangle-fan'
  16. export function getDrawMode(ctx: Context, drawMode: DrawMode) {
  17. const { gl } = ctx
  18. switch (drawMode) {
  19. case 'points': return gl.POINTS
  20. case 'lines': return gl.LINES
  21. case 'line-strip': return gl.LINE_STRIP
  22. case 'line-loop': return gl.LINE_LOOP
  23. case 'triangles': return gl.TRIANGLES
  24. case 'triangle-strip': return gl.TRIANGLE_STRIP
  25. case 'triangle-fan': return gl.TRIANGLE_FAN
  26. }
  27. }
  28. export interface RenderItem {
  29. readonly id: number
  30. readonly programId: number
  31. readonly program: Program
  32. update: () => void
  33. draw: () => void
  34. destroy: () => void
  35. }
  36. export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: ShaderCode, schema: RenderableSchema, values: RenderableValues): RenderItem {
  37. const id = getNextRenderItemId()
  38. const { programCache } = ctx
  39. const { angleInstancedArrays, oesVertexArrayObject } = ctx.extensions
  40. const { attributeValues, defineValues, textureValues, uniformValues } = splitValues(schema, values)
  41. const versions = getValueVersions(values)
  42. const glDrawMode = getDrawMode(ctx, drawMode)
  43. let drawProgram = programCache.get(ctx, {
  44. shaderCode: addShaderDefines(defineValues, shaderCode),
  45. schema
  46. })
  47. const textures = createTextures(ctx, schema, textureValues)
  48. const attributeBuffers = createAttributeBuffers(ctx, schema, attributeValues)
  49. let elementsBuffer: ElementsBuffer | undefined
  50. const elements = values.elements
  51. if (elements && elements.ref.value) {
  52. elementsBuffer = createElementsBuffer(ctx, elements.ref.value)
  53. }
  54. let vertexArray: WebGLVertexArrayObjectOES | undefined = createVertexArray(ctx, drawProgram.value, attributeBuffers, elementsBuffer)
  55. let drawCount = values.drawCount.ref.value
  56. let instanceCount = values.instanceCount.ref.value
  57. let destroyed = false
  58. function render(program: Program) {
  59. program.setUniforms(uniformValues)
  60. if (oesVertexArrayObject && vertexArray) {
  61. oesVertexArrayObject.bindVertexArrayOES(vertexArray)
  62. } else {
  63. program.bindAttributes(attributeBuffers)
  64. if (elementsBuffer) elementsBuffer.bind()
  65. }
  66. program.bindTextures(textures)
  67. if (elementsBuffer) {
  68. angleInstancedArrays.drawElementsInstancedANGLE(glDrawMode, drawCount, elementsBuffer._dataType, 0, instanceCount);
  69. } else {
  70. angleInstancedArrays.drawArraysInstancedANGLE(glDrawMode, 0, drawCount, instanceCount)
  71. }
  72. }
  73. return {
  74. id,
  75. get programId () { return drawProgram.value.id },
  76. get program () { return drawProgram.value },
  77. draw: () => {
  78. render(drawProgram.value)
  79. },
  80. update: () => {
  81. let defineChange = false
  82. Object.keys(defineValues).forEach(k => {
  83. const value = defineValues[k]
  84. if (value.ref.version !== versions[k]) {
  85. console.log('define version changed', k)
  86. defineChange = true
  87. versions[k] = value.ref.version
  88. }
  89. })
  90. if (defineChange) {
  91. console.log('some defines changed, need to rebuild program')
  92. drawProgram.free()
  93. drawProgram = programCache.get(ctx, {
  94. shaderCode: addShaderDefines(defineValues, shaderCode),
  95. schema
  96. })
  97. }
  98. if (values.drawCount.ref.version !== versions.drawCount) {
  99. console.log('drawCount version changed')
  100. drawCount = values.drawCount.ref.value
  101. versions.drawCount = values.drawCount.ref.version
  102. }
  103. if (values.instanceCount.ref.version !== versions.instanceCount) {
  104. console.log('instanceCount version changed')
  105. instanceCount = values.instanceCount.ref.value
  106. versions.instanceCount = values.instanceCount.ref.version
  107. }
  108. let bufferChange = false
  109. Object.keys(attributeValues).forEach(k => {
  110. const value = attributeValues[k]
  111. if (value.ref.version !== versions[k]) {
  112. const buffer = attributeBuffers[k]
  113. if (buffer.length >= value.ref.value.length) {
  114. console.log('attribute array large enough to update', k)
  115. attributeBuffers[k].updateData(value.ref.value)
  116. } else {
  117. console.log('attribute array to small, need to create new attribute', k)
  118. attributeBuffers[k].destroy()
  119. const spec = schema[k] as AttributeSpec<ArrayKind>
  120. attributeBuffers[k] = createAttributeBuffer(ctx, value.ref.value, spec.itemSize, spec.divisor)
  121. bufferChange = true
  122. }
  123. versions[k] = value.ref.version
  124. }
  125. })
  126. if (elementsBuffer && values.elements.ref.version !== versions.elements) {
  127. if (elementsBuffer.length >= values.elements.ref.value.length) {
  128. console.log('elements array large enough to update')
  129. elementsBuffer.updateData(values.elements.ref.value)
  130. } else {
  131. console.log('elements array to small, need to create new elements')
  132. elementsBuffer.destroy()
  133. elementsBuffer = createElementsBuffer(ctx, values.elements.ref.value)
  134. bufferChange = true
  135. }
  136. versions.elements = values.elements.ref.version
  137. }
  138. if (defineChange || bufferChange) {
  139. console.log('program/defines or buffers changed, rebuild vao')
  140. deleteVertexArray(ctx, vertexArray)
  141. vertexArray = createVertexArray(ctx, drawProgram.value, attributeBuffers, elementsBuffer)
  142. }
  143. Object.keys(textureValues).forEach(k => {
  144. const value = textureValues[k]
  145. if (value.ref.version !== versions[k]) {
  146. console.log('texture version changed, uploading image', k)
  147. textures[k].load(value.ref.value)
  148. versions[k] = value.ref.version
  149. }
  150. })
  151. },
  152. destroy: () => {
  153. if (!destroyed) {
  154. drawProgram.free()
  155. Object.keys(textures).forEach(k => textures[k].destroy())
  156. Object.keys(attributeBuffers).forEach(k => attributeBuffers[k].destroy())
  157. if (elementsBuffer) elementsBuffer.destroy()
  158. deleteVertexArray(ctx, vertexArray)
  159. destroyed = true
  160. }
  161. }
  162. }
  163. }