renderer.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. /**
  2. * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { Viewport } from '../mol-canvas3d/camera/util';
  7. import { ICamera } from '../mol-canvas3d/camera';
  8. import Scene from './scene';
  9. import { WebGLContext } from './webgl/context';
  10. import { Mat4, Vec3, Vec4, Vec2, Quat } from '../mol-math/linear-algebra';
  11. import { Renderable } from './renderable';
  12. import { Color } from '../mol-util/color';
  13. import { ValueCell, deepEqual } from '../mol-util';
  14. import { RenderableValues, GlobalUniformValues, BaseValues } from './renderable/schema';
  15. import { GraphicsRenderVariant } from './webgl/render-item';
  16. import { ParamDefinition as PD } from '../mol-util/param-definition';
  17. import { Clipping } from '../mol-theme/clipping';
  18. import { stringToWords } from '../mol-util/string';
  19. import { Transparency } from '../mol-theme/transparency';
  20. import { degToRad } from '../mol-math/misc';
  21. import { Texture } from './webgl/texture';
  22. export interface RendererStats {
  23. programCount: number
  24. shaderCount: number
  25. attributeCount: number
  26. elementsCount: number
  27. framebufferCount: number
  28. renderbufferCount: number
  29. textureCount: number
  30. vertexArrayCount: number
  31. drawCount: number
  32. instanceCount: number
  33. instancedDrawCount: number
  34. }
  35. interface Renderer {
  36. readonly stats: RendererStats
  37. readonly props: Readonly<RendererProps>
  38. clear: (transparentBackground: boolean) => void
  39. render: (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean, drawingBufferScale: number, depthTexture: Texture | null) => void
  40. setProps: (props: Partial<RendererProps>) => void
  41. setViewport: (x: number, y: number, width: number, height: number) => void
  42. dispose: () => void
  43. }
  44. export const RendererParams = {
  45. backgroundColor: PD.Color(Color(0x000000), { description: 'Background color of the 3D canvas' }),
  46. // the following are general 'material' parameters
  47. pickingAlphaThreshold: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }, { description: 'The minimum opacity value needed for an object to be pickable.' }),
  48. transparencyVariant: PD.Select('single', PD.arrayToOptions<Transparency.Variant>(['single', 'multi'])),
  49. interiorDarkening: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
  50. interiorColorFlag: PD.Boolean(true, { label: 'Use Interior Color' }),
  51. interiorColor: PD.Color(Color.fromNormalizedRgb(0.3, 0.3, 0.3)),
  52. highlightColor: PD.Color(Color.fromNormalizedRgb(1.0, 0.4, 0.6)),
  53. selectColor: PD.Color(Color.fromNormalizedRgb(0.2, 1.0, 0.1)),
  54. style: PD.MappedStatic('matte', {
  55. custom: PD.Group({
  56. lightIntensity: PD.Numeric(0.6, { min: 0.0, max: 1.0, step: 0.01 }),
  57. ambientIntensity: PD.Numeric(0.4, { min: 0.0, max: 1.0, step: 0.01 }),
  58. metalness: PD.Numeric(0.0, { min: 0.0, max: 1.0, step: 0.01 }),
  59. roughness: PD.Numeric(1.0, { min: 0.0, max: 1.0, step: 0.01 }),
  60. reflectivity: PD.Numeric(0.5, { min: 0.0, max: 1.0, step: 0.01 }),
  61. }, { isExpanded: true }),
  62. flat: PD.Group({}),
  63. matte: PD.Group({}),
  64. glossy: PD.Group({}),
  65. metallic: PD.Group({}),
  66. plastic: PD.Group({}),
  67. }, { label: 'Lighting', description: 'Style in which the 3D scene is rendered/lighted' }),
  68. clip: PD.Group({
  69. variant: PD.Select('instance', PD.arrayToOptions<Clipping.Variant>(['instance', 'pixel'])),
  70. objects: PD.ObjectList({
  71. type: PD.Select('plane', PD.objectToOptions(Clipping.Type, t => stringToWords(t))),
  72. position: PD.Vec3(Vec3()),
  73. rotation: PD.Group({
  74. axis: PD.Vec3(Vec3.create(1, 0, 0)),
  75. angle: PD.Numeric(0, { min: -180, max: 180, step: 0.1 }, { description: 'Angle in Degrees' }),
  76. }, { isExpanded: true }),
  77. scale: PD.Vec3(Vec3.create(1, 1, 1)),
  78. }, o => stringToWords(o.type))
  79. })
  80. };
  81. export type RendererProps = PD.Values<typeof RendererParams>
  82. function getStyle(props: RendererProps['style']) {
  83. switch (props.name) {
  84. case 'custom':
  85. return props.params;
  86. case 'flat':
  87. return {
  88. lightIntensity: 0, ambientIntensity: 1,
  89. metalness: 0, roughness: 0.4, reflectivity: 0.5
  90. };
  91. case 'matte':
  92. return {
  93. lightIntensity: 0.6, ambientIntensity: 0.4,
  94. metalness: 0, roughness: 1, reflectivity: 0.5
  95. };
  96. case 'glossy':
  97. return {
  98. lightIntensity: 0.6, ambientIntensity: 0.4,
  99. metalness: 0, roughness: 0.4, reflectivity: 0.5
  100. };
  101. case 'metallic':
  102. return {
  103. lightIntensity: 0.6, ambientIntensity: 0.4,
  104. metalness: 0.4, roughness: 0.6, reflectivity: 0.5
  105. };
  106. case 'plastic':
  107. return {
  108. lightIntensity: 0.6, ambientIntensity: 0.4,
  109. metalness: 0, roughness: 0.2, reflectivity: 0.5
  110. };
  111. }
  112. }
  113. type Clip = {
  114. variant: Clipping.Variant
  115. objects: {
  116. count: number
  117. type: number[]
  118. position: number[]
  119. rotation: number[]
  120. scale: number[]
  121. }
  122. }
  123. const tmpQuat = Quat();
  124. function getClip(props: RendererProps['clip'], clip?: Clip): Clip {
  125. const { type, position, rotation, scale } = clip?.objects || {
  126. type: (new Array(5)).fill(1),
  127. position: (new Array(5 * 3)).fill(0),
  128. rotation: (new Array(5 * 4)).fill(0),
  129. scale: (new Array(5 * 3)).fill(1),
  130. };
  131. for (let i = 0, il = props.objects.length; i < il; ++i) {
  132. const p = props.objects[i];
  133. type[i] = Clipping.Type[p.type];
  134. Vec3.toArray(p.position, position, i * 3);
  135. Quat.toArray(Quat.setAxisAngle(tmpQuat, p.rotation.axis, degToRad(p.rotation.angle)), rotation, i * 4);
  136. Vec3.toArray(p.scale, scale, i * 3);
  137. }
  138. return {
  139. variant: props.variant,
  140. objects: { count: props.objects.length, type, position, rotation, scale }
  141. };
  142. }
  143. namespace Renderer {
  144. export function create(ctx: WebGLContext, props: Partial<RendererProps> = {}): Renderer {
  145. const { gl, state, stats, extensions: { fragDepth } } = ctx;
  146. const p = PD.merge(RendererParams, PD.getDefaultValues(RendererParams), props);
  147. const style = getStyle(p.style);
  148. const clip = getClip(p.clip);
  149. const viewport = Viewport();
  150. const drawingBufferSize = Vec2.create(gl.drawingBufferWidth, gl.drawingBufferHeight);
  151. const bgColor = Color.toVec3Normalized(Vec3(), p.backgroundColor);
  152. const view = Mat4();
  153. const invView = Mat4();
  154. const modelView = Mat4();
  155. const invModelView = Mat4();
  156. const invProjection = Mat4();
  157. const modelViewProjection = Mat4();
  158. const invModelViewProjection = Mat4();
  159. const cameraDir = Vec3();
  160. const viewOffset = Vec2();
  161. const globalUniforms: GlobalUniformValues = {
  162. uModel: ValueCell.create(Mat4.identity()),
  163. uView: ValueCell.create(view),
  164. uInvView: ValueCell.create(invView),
  165. uModelView: ValueCell.create(modelView),
  166. uInvModelView: ValueCell.create(invModelView),
  167. uInvProjection: ValueCell.create(invProjection),
  168. uProjection: ValueCell.create(Mat4()),
  169. uModelViewProjection: ValueCell.create(modelViewProjection),
  170. uInvModelViewProjection: ValueCell.create(invModelViewProjection),
  171. uIsOrtho: ValueCell.create(1),
  172. uViewOffset: ValueCell.create(viewOffset),
  173. uPixelRatio: ValueCell.create(ctx.pixelRatio),
  174. uViewportHeight: ValueCell.create(viewport.height),
  175. uViewport: ValueCell.create(Viewport.toVec4(Vec4(), viewport)),
  176. uDrawingBufferSize: ValueCell.create(drawingBufferSize),
  177. uCameraPosition: ValueCell.create(Vec3()),
  178. uCameraDir: ValueCell.create(cameraDir),
  179. uNear: ValueCell.create(1),
  180. uFar: ValueCell.create(10000),
  181. uFogNear: ValueCell.create(1),
  182. uFogFar: ValueCell.create(10000),
  183. uFogColor: ValueCell.create(bgColor),
  184. uTransparentBackground: ValueCell.create(false),
  185. uClipObjectType: ValueCell.create(clip.objects.type),
  186. uClipObjectPosition: ValueCell.create(clip.objects.position),
  187. uClipObjectRotation: ValueCell.create(clip.objects.rotation),
  188. uClipObjectScale: ValueCell.create(clip.objects.scale),
  189. // the following are general 'material' uniforms
  190. uLightIntensity: ValueCell.create(style.lightIntensity),
  191. uAmbientIntensity: ValueCell.create(style.ambientIntensity),
  192. uMetalness: ValueCell.create(style.metalness),
  193. uRoughness: ValueCell.create(style.roughness),
  194. uReflectivity: ValueCell.create(style.reflectivity),
  195. uPickingAlphaThreshold: ValueCell.create(p.pickingAlphaThreshold),
  196. uInteriorDarkening: ValueCell.create(p.interiorDarkening),
  197. uInteriorColorFlag: ValueCell.create(p.interiorColorFlag),
  198. uInteriorColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.interiorColor)),
  199. uHighlightColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.highlightColor)),
  200. uSelectColor: ValueCell.create(Color.toVec3Normalized(Vec3(), p.selectColor)),
  201. };
  202. const globalUniformList = Object.entries(globalUniforms);
  203. let globalUniformsNeedUpdate = true;
  204. const renderObject = (r: Renderable<RenderableValues & BaseValues>, variant: GraphicsRenderVariant, depthTexture: Texture | null) => {
  205. if (!r.state.visible || (!r.state.pickable && variant[0] === 'p')) {
  206. return;
  207. }
  208. let definesNeedUpdate = false;
  209. if (r.values.dClipObjectCount.ref.value !== clip.objects.count) {
  210. ValueCell.update(r.values.dClipObjectCount, clip.objects.count);
  211. definesNeedUpdate = true;
  212. }
  213. if (r.values.dClipVariant.ref.value !== clip.variant) {
  214. ValueCell.update(r.values.dClipVariant, clip.variant);
  215. definesNeedUpdate = true;
  216. }
  217. if (r.values.dTransparencyVariant.ref.value !== p.transparencyVariant) {
  218. ValueCell.update(r.values.dTransparencyVariant, p.transparencyVariant);
  219. definesNeedUpdate = true;
  220. }
  221. if (definesNeedUpdate) r.update();
  222. const program = r.getProgram(variant);
  223. if (state.currentProgramId !== program.id) {
  224. // console.log('new program')
  225. globalUniformsNeedUpdate = true;
  226. program.use();
  227. }
  228. if (globalUniformsNeedUpdate) {
  229. // console.log('globalUniformsNeedUpdate')
  230. program.setUniforms(globalUniformList);
  231. globalUniformsNeedUpdate = false;
  232. }
  233. if (depthTexture) program.bindTextures([['tDepth', depthTexture]]);
  234. if (r.values.dRenderMode) { // indicates direct-volume
  235. // always cull front
  236. state.enable(gl.CULL_FACE);
  237. state.frontFace(gl.CW);
  238. state.cullFace(gl.BACK);
  239. // depth test done manually in shader against `depthTexture`
  240. // still need to enable when fragDepth can be used to write depth
  241. // (unclear why depthMask is insufficient)
  242. if (r.values.dRenderMode.ref.value === 'volume' || !fragDepth) {
  243. state.disable(gl.DEPTH_TEST);
  244. state.depthMask(false);
  245. } else {
  246. state.enable(gl.DEPTH_TEST);
  247. state.depthMask(r.state.writeDepth);
  248. }
  249. } else {
  250. state.enable(gl.DEPTH_TEST);
  251. if (r.values.dDoubleSided) {
  252. if (r.values.dDoubleSided.ref.value || r.values.hasReflection.ref.value) {
  253. state.disable(gl.CULL_FACE);
  254. } else {
  255. state.enable(gl.CULL_FACE);
  256. }
  257. } else {
  258. // webgl default
  259. state.disable(gl.CULL_FACE);
  260. }
  261. if (r.values.dFlipSided) {
  262. if (r.values.dFlipSided.ref.value) {
  263. state.frontFace(gl.CW);
  264. state.cullFace(gl.FRONT);
  265. } else {
  266. state.frontFace(gl.CCW);
  267. state.cullFace(gl.BACK);
  268. }
  269. } else {
  270. // webgl default
  271. state.frontFace(gl.CCW);
  272. state.cullFace(gl.BACK);
  273. }
  274. state.depthMask(r.state.writeDepth);
  275. }
  276. r.render(variant);
  277. };
  278. const render = (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean, drawingBufferScale: number, depthTexture: Texture | null) => {
  279. ValueCell.update(globalUniforms.uModel, group.view);
  280. ValueCell.update(globalUniforms.uView, camera.view);
  281. ValueCell.update(globalUniforms.uInvView, Mat4.invert(invView, camera.view));
  282. ValueCell.update(globalUniforms.uModelView, Mat4.mul(modelView, group.view, camera.view));
  283. ValueCell.update(globalUniforms.uInvModelView, Mat4.invert(invModelView, modelView));
  284. ValueCell.update(globalUniforms.uProjection, camera.projection);
  285. ValueCell.update(globalUniforms.uInvProjection, Mat4.invert(invProjection, camera.projection));
  286. ValueCell.update(globalUniforms.uModelViewProjection, Mat4.mul(modelViewProjection, modelView, camera.projection));
  287. ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection));
  288. ValueCell.updateIfChanged(globalUniforms.uIsOrtho, camera.state.mode === 'orthographic' ? 1 : 0);
  289. ValueCell.update(globalUniforms.uViewOffset, camera.viewOffset.enabled ? Vec2.set(viewOffset, camera.viewOffset.offsetX * 16, camera.viewOffset.offsetY * 16) : Vec2.set(viewOffset, 0, 0));
  290. ValueCell.update(globalUniforms.uCameraPosition, camera.state.position);
  291. ValueCell.update(globalUniforms.uCameraDir, Vec3.normalize(cameraDir, Vec3.sub(cameraDir, camera.state.target, camera.state.position)));
  292. ValueCell.updateIfChanged(globalUniforms.uFar, camera.far);
  293. ValueCell.updateIfChanged(globalUniforms.uNear, camera.near);
  294. ValueCell.updateIfChanged(globalUniforms.uFogFar, camera.fogFar);
  295. ValueCell.updateIfChanged(globalUniforms.uFogNear, camera.fogNear);
  296. ValueCell.updateIfChanged(globalUniforms.uTransparentBackground, transparentBackground);
  297. if (gl.drawingBufferWidth * drawingBufferScale !== drawingBufferSize[0] ||
  298. gl.drawingBufferHeight * drawingBufferScale !== drawingBufferSize[1]
  299. ) {
  300. ValueCell.update(globalUniforms.uDrawingBufferSize, Vec2.set(drawingBufferSize,
  301. gl.drawingBufferWidth * drawingBufferScale,
  302. gl.drawingBufferHeight * drawingBufferScale
  303. ));
  304. }
  305. globalUniformsNeedUpdate = true;
  306. state.currentRenderItemId = -1;
  307. const { renderables } = group;
  308. state.enable(gl.SCISSOR_TEST);
  309. state.disable(gl.BLEND);
  310. state.colorMask(true, true, true, true);
  311. state.enable(gl.DEPTH_TEST);
  312. const { x, y, width, height } = viewport;
  313. gl.viewport(x, y, width, height);
  314. gl.scissor(x, y, width, height);
  315. if (clear) {
  316. state.depthMask(true);
  317. if (variant === 'color') {
  318. state.clearColor(bgColor[0], bgColor[1], bgColor[2], transparentBackground ? 0 : 1);
  319. } else {
  320. state.clearColor(1, 1, 1, 1);
  321. }
  322. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  323. }
  324. if (variant === 'color') {
  325. for (let i = 0, il = renderables.length; i < il; ++i) {
  326. const r = renderables[i];
  327. if (r.state.opaque) {
  328. renderObject(r, variant, depthTexture);
  329. }
  330. }
  331. state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
  332. state.enable(gl.BLEND);
  333. for (let i = 0, il = renderables.length; i < il; ++i) {
  334. const r = renderables[i];
  335. if (!r.state.opaque && r.state.writeDepth) {
  336. renderObject(r, variant, depthTexture);
  337. }
  338. }
  339. for (let i = 0, il = renderables.length; i < il; ++i) {
  340. const r = renderables[i];
  341. if (!r.state.opaque && !r.state.writeDepth) {
  342. renderObject(r, variant, depthTexture);
  343. }
  344. }
  345. } else { // picking & depth
  346. for (let i = 0, il = renderables.length; i < il; ++i) {
  347. if (!renderables[i].state.colorOnly) {
  348. renderObject(renderables[i], variant, depthTexture);
  349. }
  350. }
  351. }
  352. gl.flush();
  353. };
  354. return {
  355. clear: (transparentBackground: boolean) => {
  356. ctx.unbindFramebuffer();
  357. state.enable(gl.SCISSOR_TEST);
  358. state.depthMask(true);
  359. state.colorMask(true, true, true, true);
  360. state.clearColor(bgColor[0], bgColor[1], bgColor[2], transparentBackground ? 0 : 1);
  361. gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  362. },
  363. render,
  364. setProps: (props: Partial<RendererProps>) => {
  365. if (props.backgroundColor !== undefined && props.backgroundColor !== p.backgroundColor) {
  366. p.backgroundColor = props.backgroundColor;
  367. Color.toVec3Normalized(bgColor, p.backgroundColor);
  368. ValueCell.update(globalUniforms.uFogColor, Vec3.copy(globalUniforms.uFogColor.ref.value, bgColor));
  369. }
  370. if (props.pickingAlphaThreshold !== undefined && props.pickingAlphaThreshold !== p.pickingAlphaThreshold) {
  371. p.pickingAlphaThreshold = props.pickingAlphaThreshold;
  372. ValueCell.update(globalUniforms.uPickingAlphaThreshold, p.pickingAlphaThreshold);
  373. }
  374. if (props.transparencyVariant !== undefined && props.transparencyVariant !== p.transparencyVariant) {
  375. p.transparencyVariant = props.transparencyVariant;
  376. }
  377. if (props.interiorDarkening !== undefined && props.interiorDarkening !== p.interiorDarkening) {
  378. p.interiorDarkening = props.interiorDarkening;
  379. ValueCell.update(globalUniforms.uInteriorDarkening, p.interiorDarkening);
  380. }
  381. if (props.interiorColorFlag !== undefined && props.interiorColorFlag !== p.interiorColorFlag) {
  382. p.interiorColorFlag = props.interiorColorFlag;
  383. ValueCell.update(globalUniforms.uInteriorColorFlag, p.interiorColorFlag);
  384. }
  385. if (props.interiorColor !== undefined && props.interiorColor !== p.interiorColor) {
  386. p.interiorColor = props.interiorColor;
  387. ValueCell.update(globalUniforms.uInteriorColor, Color.toVec3Normalized(globalUniforms.uInteriorColor.ref.value, p.interiorColor));
  388. }
  389. if (props.highlightColor !== undefined && props.highlightColor !== p.highlightColor) {
  390. p.highlightColor = props.highlightColor;
  391. ValueCell.update(globalUniforms.uHighlightColor, Color.toVec3Normalized(globalUniforms.uHighlightColor.ref.value, p.highlightColor));
  392. }
  393. if (props.selectColor !== undefined && props.selectColor !== p.selectColor) {
  394. p.selectColor = props.selectColor;
  395. ValueCell.update(globalUniforms.uSelectColor, Color.toVec3Normalized(globalUniforms.uSelectColor.ref.value, p.selectColor));
  396. }
  397. if (props.style !== undefined) {
  398. p.style = props.style;
  399. Object.assign(style, getStyle(props.style));
  400. ValueCell.updateIfChanged(globalUniforms.uLightIntensity, style.lightIntensity);
  401. ValueCell.updateIfChanged(globalUniforms.uAmbientIntensity, style.ambientIntensity);
  402. ValueCell.updateIfChanged(globalUniforms.uMetalness, style.metalness);
  403. ValueCell.updateIfChanged(globalUniforms.uRoughness, style.roughness);
  404. ValueCell.updateIfChanged(globalUniforms.uReflectivity, style.reflectivity);
  405. }
  406. if (props.clip !== undefined && !deepEqual(props.clip, p.clip)) {
  407. p.clip = props.clip;
  408. Object.assign(clip, getClip(props.clip, clip));
  409. ValueCell.update(globalUniforms.uClipObjectPosition, clip.objects.position);
  410. ValueCell.update(globalUniforms.uClipObjectRotation, clip.objects.rotation);
  411. ValueCell.update(globalUniforms.uClipObjectScale, clip.objects.scale);
  412. ValueCell.update(globalUniforms.uClipObjectType, clip.objects.type);
  413. }
  414. },
  415. setViewport: (x: number, y: number, width: number, height: number) => {
  416. gl.viewport(x, y, width, height);
  417. gl.scissor(x, y, width, height);
  418. if (x !== viewport.x || y !== viewport.y || width !== viewport.width || height !== viewport.height) {
  419. Viewport.set(viewport, x, y, width, height);
  420. ValueCell.update(globalUniforms.uViewportHeight, height);
  421. ValueCell.update(globalUniforms.uViewport, Vec4.set(globalUniforms.uViewport.ref.value, x, y, width, height));
  422. }
  423. },
  424. get props() {
  425. return p;
  426. },
  427. get stats(): RendererStats {
  428. return {
  429. programCount: ctx.stats.resourceCounts.program,
  430. shaderCount: ctx.stats.resourceCounts.shader,
  431. attributeCount: ctx.stats.resourceCounts.attribute,
  432. elementsCount: ctx.stats.resourceCounts.elements,
  433. framebufferCount: ctx.stats.resourceCounts.framebuffer,
  434. renderbufferCount: ctx.stats.resourceCounts.renderbuffer,
  435. textureCount: ctx.stats.resourceCounts.texture,
  436. vertexArrayCount: ctx.stats.resourceCounts.vertexArray,
  437. drawCount: stats.drawCount,
  438. instanceCount: stats.instanceCount,
  439. instancedDrawCount: stats.instancedDrawCount,
  440. };
  441. },
  442. dispose: () => {
  443. // TODO
  444. }
  445. };
  446. }
  447. }
  448. export default Renderer;