render-item.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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 { createAttributeBuffers, createElementsBuffer, ElementsBuffer, createAttributeBuffer, AttributeKind } from './buffer';
  7. import { createTextures, Texture } from './texture';
  8. import { WebGLContext } from './context';
  9. import { ShaderCode } from '../shader-code';
  10. import { Program } from './program';
  11. import { RenderableSchema, RenderableValues, AttributeSpec, getValueVersions, splitValues, Values } from '../renderable/schema';
  12. import { idFactory } from 'mol-util/id-factory';
  13. import { deleteVertexArray, createVertexArray } from './vertex-array';
  14. import { ValueCell } from 'mol-util';
  15. import { ReferenceItem } from 'mol-util/reference-cache';
  16. import { TextureImage, TextureVolume } from 'mol-gl/renderable/util';
  17. const getNextRenderItemId = idFactory()
  18. export type DrawMode = 'points' | 'lines' | 'line-strip' | 'line-loop' | 'triangles' | 'triangle-strip' | 'triangle-fan'
  19. export function getDrawMode(ctx: WebGLContext, drawMode: DrawMode) {
  20. const { gl } = ctx
  21. switch (drawMode) {
  22. case 'points': return gl.POINTS
  23. case 'lines': return gl.LINES
  24. case 'line-strip': return gl.LINE_STRIP
  25. case 'line-loop': return gl.LINE_LOOP
  26. case 'triangles': return gl.TRIANGLES
  27. case 'triangle-strip': return gl.TRIANGLE_STRIP
  28. case 'triangle-fan': return gl.TRIANGLE_FAN
  29. }
  30. }
  31. export interface RenderItem<T extends string> {
  32. readonly id: number
  33. readonly materialId: number
  34. getProgram: (variant: T) => Program
  35. render: (variant: T) => void
  36. update: () => Readonly<ValueChanges>
  37. destroy: () => void
  38. }
  39. //
  40. const GraphicsRenderVariantDefines = {
  41. 'draw': {},
  42. 'pickObject': { dColorType: ValueCell.create('objectPicking') },
  43. 'pickInstance': { dColorType: ValueCell.create('instancePicking') },
  44. 'pickGroup': { dColorType: ValueCell.create('groupPicking') }
  45. }
  46. export type GraphicsRenderVariant = keyof typeof GraphicsRenderVariantDefines
  47. const ComputeRenderVariantDefines = {
  48. 'compute': {},
  49. }
  50. export type ComputeRenderVariant = keyof typeof ComputeRenderVariantDefines
  51. type RenderVariantDefines = typeof GraphicsRenderVariantDefines | typeof ComputeRenderVariantDefines
  52. //
  53. type ProgramVariants = { [k: string]: ReferenceItem<Program> }
  54. type VertexArrayVariants = { [k: string]: WebGLVertexArrayObjectOES | null }
  55. interface ValueChanges {
  56. attributes: boolean
  57. defines: boolean
  58. elements: boolean
  59. textures: boolean
  60. }
  61. function createValueChanges() {
  62. return {
  63. attributes: false,
  64. defines: false,
  65. elements: false,
  66. textures: false,
  67. }
  68. }
  69. function resetValueChanges(valueChanges: ValueChanges) {
  70. valueChanges.attributes = false
  71. valueChanges.defines = false
  72. valueChanges.elements = false
  73. valueChanges.textures = false
  74. }
  75. //
  76. export type GraphicsRenderItem = RenderItem<keyof typeof GraphicsRenderVariantDefines & string>
  77. export function createGraphicsRenderItem(ctx: WebGLContext, drawMode: DrawMode, shaderCode: ShaderCode, schema: RenderableSchema, values: RenderableValues, materialId: number) {
  78. return createRenderItem(ctx, drawMode, shaderCode, schema, values, materialId, GraphicsRenderVariantDefines)
  79. }
  80. export type ComputeRenderItem = RenderItem<keyof typeof ComputeRenderVariantDefines & string>
  81. export function createComputeRenderItem(ctx: WebGLContext, drawMode: DrawMode, shaderCode: ShaderCode, schema: RenderableSchema, values: RenderableValues) {
  82. return createRenderItem(ctx, drawMode, shaderCode, schema, values, -1, ComputeRenderVariantDefines)
  83. }
  84. /**
  85. * Creates a render item
  86. *
  87. * - assumes that `values.drawCount` and `values.instanceCount` exist
  88. */
  89. export function createRenderItem<T extends RenderVariantDefines, S extends keyof T & string>(ctx: WebGLContext, drawMode: DrawMode, shaderCode: ShaderCode, schema: RenderableSchema, values: RenderableValues, materialId: number, renderVariantDefines: T): RenderItem<S> {
  90. const id = getNextRenderItemId()
  91. const { stats, state, programCache } = ctx
  92. const { instancedArrays, vertexArrayObject } = ctx.extensions
  93. const { attributeValues, defineValues, textureValues, uniformValues, materialUniformValues } = splitValues(schema, values)
  94. const uniformValueEntries = Object.entries(uniformValues)
  95. const materialUniformValueEntries = Object.entries(materialUniformValues)
  96. const defineValueEntries = Object.entries(defineValues)
  97. const versions = getValueVersions(values)
  98. const glDrawMode = getDrawMode(ctx, drawMode)
  99. const programs: ProgramVariants = {}
  100. Object.keys(renderVariantDefines).forEach(k => {
  101. const variantDefineValues: Values<RenderableSchema> = (renderVariantDefines as any)[k]
  102. programs[k] = programCache.get({
  103. defineValues: { ...defineValues, ...variantDefineValues },
  104. shaderCode,
  105. schema
  106. })
  107. })
  108. const textures = createTextures(ctx, schema, textureValues)
  109. const attributeBuffers = createAttributeBuffers(ctx, schema, attributeValues)
  110. let elementsBuffer: ElementsBuffer | undefined
  111. const elements = values.elements
  112. if (elements && elements.ref.value) {
  113. elementsBuffer = createElementsBuffer(ctx, elements.ref.value)
  114. }
  115. const vertexArrays: VertexArrayVariants = {}
  116. Object.keys(renderVariantDefines).forEach(k => {
  117. vertexArrays[k] = createVertexArray(ctx, programs[k].value, attributeBuffers, elementsBuffer)
  118. })
  119. let drawCount = values.drawCount.ref.value
  120. let instanceCount = values.instanceCount.ref.value
  121. stats.drawCount += drawCount
  122. stats.instanceCount += instanceCount
  123. stats.instancedDrawCount += instanceCount * drawCount
  124. const valueChanges = createValueChanges()
  125. let destroyed = false
  126. let currentProgramId = -1
  127. return {
  128. id,
  129. materialId,
  130. getProgram: (variant: S) => programs[variant].value,
  131. render: (variant: S) => {
  132. if (drawCount === 0 || instanceCount === 0) return
  133. const program = programs[variant].value
  134. const vertexArray = vertexArrays[variant]
  135. program.setUniforms(uniformValueEntries)
  136. if (program.id !== currentProgramId ||
  137. materialId === -1 || materialId !== state.currentMaterialId
  138. ) {
  139. // console.log('program.id changed or materialId changed/-1', materialId)
  140. program.setUniforms(materialUniformValueEntries)
  141. currentProgramId = program.id
  142. state.currentMaterialId = materialId
  143. }
  144. program.bindTextures(textures)
  145. if (vertexArrayObject && vertexArray) {
  146. vertexArrayObject.bindVertexArray(vertexArray)
  147. // need to bind elements buffer explicitly since it is not always recorded in the VAO
  148. if (elementsBuffer) elementsBuffer.bind()
  149. } else {
  150. if (elementsBuffer) elementsBuffer.bind()
  151. program.bindAttributes(attributeBuffers)
  152. }
  153. if (elementsBuffer) {
  154. instancedArrays.drawElementsInstanced(glDrawMode, drawCount, elementsBuffer._dataType, 0, instanceCount);
  155. } else {
  156. instancedArrays.drawArraysInstanced(glDrawMode, 0, drawCount, instanceCount)
  157. }
  158. },
  159. update: () => {
  160. resetValueChanges(valueChanges)
  161. for (let i = 0, il = defineValueEntries.length; i < il; ++i) {
  162. const [k, value] = defineValueEntries[i]
  163. if (value.ref.version !== versions[k]) {
  164. // console.log('define version changed', k)
  165. valueChanges.defines = true
  166. versions[k] = value.ref.version
  167. }
  168. }
  169. if (valueChanges.defines) {
  170. // console.log('some defines changed, need to rebuild programs')
  171. Object.keys(renderVariantDefines).forEach(k => {
  172. const variantDefineValues: Values<RenderableSchema> = (renderVariantDefines as any)[k]
  173. programs[k].free()
  174. programs[k] = programCache.get({
  175. defineValues: { ...defineValues, ...variantDefineValues },
  176. shaderCode,
  177. schema
  178. })
  179. })
  180. }
  181. if (values.drawCount.ref.version !== versions.drawCount) {
  182. // console.log('drawCount version changed')
  183. stats.drawCount += values.drawCount.ref.value - drawCount
  184. stats.instancedDrawCount += instanceCount * values.drawCount.ref.value - instanceCount * drawCount
  185. drawCount = values.drawCount.ref.value
  186. versions.drawCount = values.drawCount.ref.version
  187. }
  188. if (values.instanceCount.ref.version !== versions.instanceCount) {
  189. // console.log('instanceCount version changed')
  190. stats.instanceCount += values.instanceCount.ref.value - instanceCount
  191. stats.instancedDrawCount += values.instanceCount.ref.value * drawCount - instanceCount * drawCount
  192. instanceCount = values.instanceCount.ref.value
  193. versions.instanceCount = values.instanceCount.ref.version
  194. }
  195. for (let i = 0, il = attributeBuffers.length; i < il; ++i) {
  196. const [k, buffer] = attributeBuffers[i]
  197. const value = attributeValues[k]
  198. if (value.ref.version !== versions[k]) {
  199. if (buffer.length >= value.ref.value.length) {
  200. // console.log('attribute array large enough to update', k, value.ref.id, value.ref.version)
  201. buffer.updateData(value.ref.value)
  202. } else {
  203. // console.log('attribute array to small, need to create new attribute', k, value.ref.id, value.ref.version)
  204. buffer.destroy()
  205. const { itemSize, divisor } = schema[k] as AttributeSpec<AttributeKind>
  206. attributeBuffers[i][1] = createAttributeBuffer(ctx, value.ref.value, itemSize, divisor)
  207. valueChanges.attributes = true
  208. }
  209. versions[k] = value.ref.version
  210. }
  211. }
  212. if (elementsBuffer && values.elements.ref.version !== versions.elements) {
  213. if (elementsBuffer.length >= values.elements.ref.value.length) {
  214. // console.log('elements array large enough to update', values.elements.ref.id, values.elements.ref.version)
  215. elementsBuffer.updateData(values.elements.ref.value)
  216. } else {
  217. // console.log('elements array to small, need to create new elements', values.elements.ref.id, values.elements.ref.version)
  218. elementsBuffer.destroy()
  219. elementsBuffer = createElementsBuffer(ctx, values.elements.ref.value)
  220. valueChanges.elements = true
  221. }
  222. versions.elements = values.elements.ref.version
  223. }
  224. if (valueChanges.attributes || valueChanges.defines || valueChanges.elements) {
  225. // console.log('program/defines or buffers changed, update vaos')
  226. const { vertexArrayObject } = ctx.extensions
  227. if (vertexArrayObject) {
  228. Object.keys(renderVariantDefines).forEach(k => {
  229. vertexArrayObject.bindVertexArray(vertexArrays[k])
  230. if (elementsBuffer && (valueChanges.defines || valueChanges.elements)) {
  231. elementsBuffer.bind()
  232. }
  233. if (valueChanges.attributes || valueChanges.defines) {
  234. programs[k].value.bindAttributes(attributeBuffers)
  235. }
  236. vertexArrayObject.bindVertexArray(null)
  237. })
  238. }
  239. }
  240. for (let i = 0, il = textures.length; i < il; ++i) {
  241. const [k, texture] = textures[i]
  242. const value = textureValues[k]
  243. if (value.ref.version !== versions[k]) {
  244. // update of textures with kind 'texture' is done externally
  245. if (schema[k].kind !== 'texture') {
  246. // console.log('texture version changed, uploading image', k)
  247. texture.load(value.ref.value as TextureImage<any> | TextureVolume<any>)
  248. versions[k] = value.ref.version
  249. valueChanges.textures = true
  250. } else {
  251. textures[i][1] = value.ref.value as Texture
  252. }
  253. }
  254. }
  255. return valueChanges
  256. },
  257. destroy: () => {
  258. if (!destroyed) {
  259. Object.keys(renderVariantDefines).forEach(k => {
  260. programs[k].free()
  261. deleteVertexArray(ctx, vertexArrays[k])
  262. })
  263. textures.forEach(([k, texture]) => {
  264. // lifetime of textures with kind 'texture' is defined externally
  265. if (schema[k].kind !== 'texture') {
  266. texture.destroy()
  267. }
  268. })
  269. attributeBuffers.forEach(([_, buffer]) => buffer.destroy())
  270. if (elementsBuffer) elementsBuffer.destroy()
  271. destroyed = true
  272. }
  273. }
  274. }
  275. }