molstar
Version:
A comprehensive macromolecular library.
443 lines (442 loc) • 22.6 kB
JavaScript
/**
* 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;
}
}
};
}
;