UNPKG

molstar

Version:

A comprehensive macromolecular library.

443 lines (442 loc) 22.6 kB
"use strict"; /** * Copyright (c) 2018-2025 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author Gianluca Tomasello <giagitom@gmail.com> */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ComputeRenderVariants = exports.GraphicsRenderVariants = void 0; exports.getDrawMode = getDrawMode; exports.createGraphicsRenderItem = createGraphicsRenderItem; exports.createComputeRenderItem = createComputeRenderItem; exports.createRenderItem = createRenderItem; const buffer_1 = require("./buffer"); const texture_1 = require("./texture"); const context_1 = require("./context"); const schema_1 = require("../renderable/schema"); const id_factory_1 = require("../../mol-util/id-factory"); const mol_util_1 = require("../../mol-util"); const framebuffer_1 = require("./framebuffer"); const debug_1 = require("../../mol-util/debug"); const array_1 = require("../../mol-util/array"); const object_1 = require("../../mol-util/object"); const uniform_1 = require("./uniform"); // Handle Firefox's preference [webgl.max-vert-ids-per-draw] which defaults to 30_000_000 // since FF119, see https://bugzilla.mozilla.org/show_bug.cgi?id=1849433 const MaxDrawCount = 30000000; const getNextRenderItemId = (0, id_factory_1.idFactory)(); function getDrawMode(ctx, drawMode) { const { gl } = ctx; switch (drawMode) { case 'points': return gl.POINTS; case 'lines': return gl.LINES; case 'line-strip': return gl.LINE_STRIP; case 'line-loop': return gl.LINE_LOOP; case 'triangles': return gl.TRIANGLES; case 'triangle-strip': return gl.TRIANGLE_STRIP; case 'triangle-fan': return gl.TRIANGLE_FAN; } } // const GraphicsRenderVariant = { color: '', pick: '', depth: '', marking: '', emissive: '', tracing: '' }; exports.GraphicsRenderVariants = Object.keys(GraphicsRenderVariant); const ComputeRenderVariant = { compute: '' }; exports.ComputeRenderVariants = Object.keys(ComputeRenderVariant); function createProgramVariant(ctx, variant, defineValues, shaderCode, schema) { defineValues = { ...defineValues, dRenderVariant: mol_util_1.ValueCell.create(variant) }; if (schema.dRenderVariant === undefined) { Object.defineProperty(schema, 'dRenderVariant', { value: (0, schema_1.DefineSpec)('string') }); } return ctx.resources.program(defineValues, shaderCode, schema); } // function createValueChanges() { return { attributes: false, defines: false, elements: false, textures: false, }; } function resetValueChanges(valueChanges) { valueChanges.attributes = false; valueChanges.defines = false; valueChanges.elements = false; valueChanges.textures = false; } function getRenderVariant(variant, transparency) { if (variant === 'color') { switch (transparency) { case 'blended': return 'colorBlended'; case 'wboit': return 'colorWboit'; case 'dpoit': return 'colorDpoit'; } } return variant; } function createGraphicsRenderItem(ctx, drawMode, shaderCode, schema, values, materialId, transparency) { return createRenderItem(ctx, drawMode, shaderCode, schema, values, materialId, exports.GraphicsRenderVariants, transparency); } function createComputeRenderItem(ctx, drawMode, shaderCode, schema, values, materialId = -1) { return createRenderItem(ctx, drawMode, shaderCode, schema, values, materialId, exports.ComputeRenderVariants, undefined); } /** * Creates a render item * * - assumes that `values.drawCount` and `values.instanceCount` exist */ function createRenderItem(ctx, drawMode, shaderCode, schema, values, materialId, renderVariants, transparency) { const id = getNextRenderItemId(); const { stats, state, resources } = ctx; const { instancedArrays, vertexArrayObject, multiDrawInstancedBaseVertexBaseInstance, drawInstancedBaseVertexBaseInstance } = ctx.extensions; // filter out unsupported variants renderVariants = renderVariants.filter(v => { if (v === 'tracing') return !!ctx.extensions.drawBuffers; return true; }); // emulate gl_VertexID when needed if (values.uVertexCount && !ctx.extensions.noNonInstancedActiveAttribs) { const vertexCount = values.uVertexCount.ref.value; values.aVertex = mol_util_1.ValueCell.create((0, array_1.fillSerial)(new Float32Array(vertexCount))); schema.aVertex = (0, schema_1.AttributeSpec)('float32', 1, 0); } const { attributeValues, defineValues, textureValues, materialTextureValues, uniformValues, materialUniformValues, bufferedUniformValues } = (0, schema_1.splitValues)(schema, values); const uniformValueEntries = Object.entries(uniformValues); const materialUniformValueEntries = Object.entries(materialUniformValues); const backBufferUniformValueEntries = Object.entries(bufferedUniformValues); const frontBufferUniformValueEntries = Object.entries((0, uniform_1.cloneUniformValues)(bufferedUniformValues)); const defineValueEntries = Object.entries(defineValues); const versions = (0, schema_1.getValueVersions)(values); const glDrawMode = getDrawMode(ctx, drawMode); const programs = {}; for (const rv of renderVariants) { programs[rv] = createProgramVariant(ctx, getRenderVariant(rv, transparency), defineValues, shaderCode, schema); } const textures = (0, texture_1.createTextures)(ctx, schema, textureValues); const materialTextures = (0, texture_1.createTextures)(ctx, schema, materialTextureValues); const attributeBuffers = (0, buffer_1.createAttributeBuffers)(ctx, schema, attributeValues); const instanceBuffers = []; for (let i = 0, il = attributeBuffers.length; i < il; ++i) { const ab = attributeBuffers[i]; if (ab[1].divisor === 1) instanceBuffers.push(ab); } let elementsBuffer; const elements = values.elements; if (elements && elements.ref.value) { elementsBuffer = resources.elements(elements.ref.value); } const vertexArrays = {}; for (const k of renderVariants) { vertexArrays[k] = vertexArrayObject ? resources.vertexArray(programs[k], attributeBuffers, elementsBuffer) : null; } let drawCount = values.drawCount.ref.value; let instanceCount = values.instanceCount.ref.value; stats.drawCount += drawCount; stats.instanceCount += instanceCount; stats.instancedDrawCount += instanceCount * drawCount; const valueChanges = createValueChanges(); let destroyed = false; let currentProgramId = -1; return { id, materialId, getProgram: (variant) => programs[variant], setTransparency: (value) => { if (value === transparency) return; transparency = value; for (const rv of renderVariants) { programs[rv].destroy(); programs[rv] = createProgramVariant(ctx, getRenderVariant(rv, transparency), defineValues, shaderCode, schema); } }, render: (variant, sharedTexturesCount, mdbDataList) => { if (drawCount === 0 || instanceCount === 0) return; const program = programs[variant]; if (program.id === currentProgramId && state.currentRenderItemId === id) { program.setUniforms(uniformValueEntries); program.bindTextures(textures, sharedTexturesCount); } else { const vertexArray = vertexArrays[variant]; if (program.id !== state.currentProgramId || program.id !== currentProgramId || materialId === -1 || materialId !== state.currentMaterialId) { // console.log('program.id changed or materialId changed/-1', materialId) if (program.id !== state.currentProgramId) program.use(); program.setUniforms(materialUniformValueEntries); program.bindTextures(materialTextures, sharedTexturesCount + textures.length); state.currentMaterialId = materialId; currentProgramId = program.id; } program.setUniforms(uniformValueEntries); program.setUniforms(frontBufferUniformValueEntries); program.bindTextures(textures, sharedTexturesCount); if (vertexArray) { vertexArray.bind(); // need to bind elements buffer explicitly since it is not always recorded in the VAO if (elementsBuffer) elementsBuffer.bind(); } else { if (elementsBuffer) elementsBuffer.bind(); program.bindAttributes(attributeBuffers); } state.currentRenderItemId = id; } if (debug_1.isDebugMode) { try { (0, framebuffer_1.checkFramebufferStatus)(ctx.gl); } catch (e) { throw new Error(`Framebuffer error rendering item id ${id}: '${e}'`); } } if (mdbDataList) { for (const mdbData of mdbDataList) { if (mdbData.count === 0) continue; program.setUniforms(mdbData.uniforms); // console.log(mdbData.uniforms) if (multiDrawInstancedBaseVertexBaseInstance) { if (elementsBuffer) { multiDrawInstancedBaseVertexBaseInstance.multiDrawElementsInstancedBaseVertexBaseInstance(glDrawMode, mdbData.counts, 0, elementsBuffer._dataType, mdbData.offsets, 0, mdbData.instanceCounts, 0, mdbData.baseVertices, 0, mdbData.baseInstances, 0, mdbData.count); } else { multiDrawInstancedBaseVertexBaseInstance.multiDrawArraysInstancedBaseInstance(glDrawMode, mdbData.firsts, 0, mdbData.counts, 0, mdbData.instanceCounts, 0, mdbData.baseInstances, 0, mdbData.count); } } else if (drawInstancedBaseVertexBaseInstance) { if (elementsBuffer) { for (let i = 0; i < mdbData.count; ++i) { if (mdbData.counts[i] > 0) { program.uniform('uDrawId', i); drawInstancedBaseVertexBaseInstance.drawElementsInstancedBaseVertexBaseInstance(glDrawMode, mdbData.counts[i], elementsBuffer._dataType, mdbData.offsets[i], mdbData.instanceCounts[i], mdbData.baseVertices[i], mdbData.baseInstances[i]); } } } else { for (let i = 0; i < mdbData.count; ++i) { if (mdbData.counts[i] > 0) { program.uniform('uDrawId', i); drawInstancedBaseVertexBaseInstance.drawArraysInstancedBaseInstance(glDrawMode, mdbData.firsts[i], mdbData.counts[i], mdbData.instanceCounts[i], mdbData.baseInstances[i]); } } } } else { if (elementsBuffer) { for (let i = 0; i < mdbData.count; ++i) { if (mdbData.counts[i] > 0) { program.uniform('uDrawId', i); program.offsetAttributes(instanceBuffers, mdbData.baseInstances[i]); instancedArrays.drawElementsInstanced(glDrawMode, mdbData.counts[i], elementsBuffer._dataType, mdbData.offsets[i], mdbData.instanceCounts[i]); } } } else { for (let i = 0; i < mdbData.count; ++i) { if (mdbData.counts[i] > 0) { program.uniform('uDrawId', i); program.offsetAttributes(instanceBuffers, mdbData.baseInstances[i]); instancedArrays.drawArraysInstanced(glDrawMode, 0, mdbData.counts[i], mdbData.instanceCounts[i]); } } } } if (debug_1.isTimingMode) { if (multiDrawInstancedBaseVertexBaseInstance) { stats.calls.multiDrawInstancedBase += 1; } else if (drawInstancedBaseVertexBaseInstance) { stats.calls.drawInstancedBase += mdbData.count; } else { stats.calls.drawInstanced += mdbData.count; } for (let i = 0; i < mdbData.count; ++i) { stats.calls.counts += mdbData.instanceCounts[i]; } } } } else { let offset = 0; while (true) { const count = Math.min(drawCount - offset, MaxDrawCount); if (elementsBuffer) { instancedArrays.drawElementsInstanced(glDrawMode, count, elementsBuffer._dataType, offset * elementsBuffer._bpe, instanceCount); } else { instancedArrays.drawArraysInstanced(glDrawMode, offset, count, instanceCount); } offset += count; if (offset >= drawCount) break; } if (debug_1.isTimingMode) { stats.calls.drawInstanced += 1; stats.calls.counts += instanceCount; } } if (debug_1.isDebugMode) { try { (0, context_1.checkError)(ctx.gl); } catch (e) { throw new Error(`Draw error rendering item id ${id}: '${e}'`); } } }, update: () => { resetValueChanges(valueChanges); if (values.aVertex) { const vertexCount = values.uVertexCount.ref.value; if (values.aVertex.ref.value.length < vertexCount) { mol_util_1.ValueCell.update(values.aVertex, (0, array_1.fillSerial)(new Float32Array(vertexCount))); } } for (let i = 0, il = defineValueEntries.length; i < il; ++i) { const [k, value] = defineValueEntries[i]; if (value.ref.version !== versions[k]) { // console.log('define version changed', k); valueChanges.defines = true; versions[k] = value.ref.version; } } if (valueChanges.defines) { // console.log('some defines changed, need to rebuild programs'); for (const rv of renderVariants) { programs[rv].destroy(); programs[rv] = createProgramVariant(ctx, getRenderVariant(rv, transparency), defineValues, shaderCode, schema); } } if (values.drawCount.ref.version !== versions.drawCount) { // console.log('drawCount version changed'); stats.drawCount += values.drawCount.ref.value - drawCount; stats.instancedDrawCount += instanceCount * values.drawCount.ref.value - instanceCount * drawCount; drawCount = values.drawCount.ref.value; versions.drawCount = values.drawCount.ref.version; } if (values.instanceCount.ref.version !== versions.instanceCount) { // console.log('instanceCount version changed'); stats.instanceCount += values.instanceCount.ref.value - instanceCount; stats.instancedDrawCount += values.instanceCount.ref.value * drawCount - instanceCount * drawCount; instanceCount = values.instanceCount.ref.value; versions.instanceCount = values.instanceCount.ref.version; } for (let i = 0, il = attributeBuffers.length; i < il; ++i) { const [k, buffer] = attributeBuffers[i]; const value = attributeValues[k]; if (value.ref.version !== versions[k]) { if (buffer.length >= value.ref.value.length) { // console.log('attribute array large enough to update', buffer.id, k, value.ref.id, value.ref.version); buffer.updateSubData(value.ref.value, 0, buffer.length); } else { // console.log('attribute array too small, need to create new attribute', buffer.id, k, value.ref.id, value.ref.version); buffer.destroy(); const { itemSize, divisor } = schema[k]; attributeBuffers[i][1] = resources.attribute(value.ref.value, itemSize, divisor); valueChanges.attributes = true; } versions[k] = value.ref.version; } } if (elementsBuffer && values.elements.ref.version !== versions.elements) { if (elementsBuffer.length >= values.elements.ref.value.length) { // console.log('elements array large enough to update', values.elements.ref.id, values.elements.ref.version); elementsBuffer.updateSubData(values.elements.ref.value, 0, elementsBuffer.length); } else { // console.log('elements array to small, need to create new elements', values.elements.ref.id, values.elements.ref.version); elementsBuffer.destroy(); elementsBuffer = resources.elements(values.elements.ref.value); valueChanges.elements = true; } versions.elements = values.elements.ref.version; } if (valueChanges.attributes || valueChanges.defines || valueChanges.elements) { // console.log('program/defines or buffers changed, update vaos'); for (const k of renderVariants) { const vertexArray = vertexArrays[k]; if (vertexArray) vertexArray.destroy(); vertexArrays[k] = vertexArrayObject ? resources.vertexArray(programs[k], attributeBuffers, elementsBuffer) : null; } } for (let i = 0, il = textures.length; i < il; ++i) { const [k, texture] = textures[i]; const value = textureValues[k]; if (value.ref.version !== versions[k]) { // update of textures with kind 'texture' is done externally if (schema[k].kind !== 'texture') { // console.log('texture version changed, uploading image', k); texture.load(value.ref.value); valueChanges.textures = true; } else { textures[i][1] = value.ref.value; } versions[k] = value.ref.version; } } for (let i = 0, il = materialTextures.length; i < il; ++i) { const [k, texture] = materialTextures[i]; const value = materialTextureValues[k]; if (value.ref.version !== versions[k]) { // update of textures with kind 'texture' is done externally if (schema[k].kind !== 'texture') { // console.log('materialTexture version changed, uploading image', k); texture.load(value.ref.value); valueChanges.textures = true; } else { materialTextures[i][1] = value.ref.value; } versions[k] = value.ref.version; } } for (let i = 0, il = backBufferUniformValueEntries.length; i < il; ++i) { const [k, uniform] = backBufferUniformValueEntries[i]; if (uniform.ref.version !== versions[k]) { // console.log('back-buffer uniform version changed, updating front-buffer', k); mol_util_1.ValueCell.update(frontBufferUniformValueEntries[i][1], (0, object_1.deepClone)(uniform.ref.value)); versions[k] = uniform.ref.version; } } }, destroy: () => { if (!destroyed) { for (const k of renderVariants) { programs[k].destroy(); const vertexArray = vertexArrays[k]; if (vertexArray) vertexArray.destroy(); } textures.forEach(([k, texture]) => { // lifetime of textures with kind 'texture' is defined externally if (schema[k].kind !== 'texture') texture.destroy(); }); materialTextures.forEach(([k, texture]) => { // lifetime of textures with kind 'texture' is defined externally if (schema[k].kind !== 'texture') texture.destroy(); }); attributeBuffers.forEach(([_, buffer]) => buffer.destroy()); if (elementsBuffer) elementsBuffer.destroy(); stats.drawCount -= drawCount; stats.instanceCount -= instanceCount; stats.instancedDrawCount -= instanceCount * drawCount; destroyed = true; } } }; }