feat: Add shader inputs (uniform buffer) debug trace (#1918)

This commit is contained in:
Ib Green 2024-01-12 15:59:43 -05:00 committed by GitHub
parent ab4f510bf9
commit f6848a93b0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 87 additions and 65 deletions

View File

@ -1,8 +1,8 @@
//
//
import type {ShaderUniformType, NumberArray} from '@luma.gl/core';
import {Device, Framebuffer, makeRandomNumberGenerator, UniformStore, glsl} from '@luma.gl/core';
import {Device, Framebuffer, makeRandomNumberGenerator, glsl} from '@luma.gl/core';
import type {AnimationProps, ModelProps} from '@luma.gl/engine';
import {AnimationLoopTemplate, CubeGeometry, Timeline, Model} from '@luma.gl/engine';
import {AnimationLoopTemplate, CubeGeometry, Timeline, Model, _ShaderInputs} from '@luma.gl/engine';
import {readPixelsToArray} from '@luma.gl/webgl';
import {picking, dirlight} from '@luma.gl/shadertools';
import {Matrix4, radians} from '@math.gl/core';
@ -39,19 +39,6 @@ uniform appUniforms {
out vec3 color;
void main(void) {
// vec3 normal = vec3(uModel * vec4(normals, 1.0));
// // Set up data for modules
// color = instanceColors;
// project_setNormal(normal);
// // vec4 pickColor = vec4(0., instancePickingColors, 1.0);
// picking_setPickingColor(vec3(0., instancePickingColors));
// // Vertex position (z coordinate undulates with time), and model rotates around center
// float delta = length(instanceOffsets);
// vec4 offset = vec4(instanceOffsets, sin((uTime + delta) * 0.1) * 16.0, 0);
// gl_Position = uProjection * uView * (uModel * vec4(positions * 1., 1.0) + offset);
// Set up data for modules
color = instanceColors;
vec3 normal = vec3(app.modelMatrix * vec4(normals, 1.0));
@ -72,7 +59,6 @@ const fs = glsl`\
precision highp float;
in vec3 color;
out vec4 fragColor;
void main(void) {
@ -86,7 +72,6 @@ const SIDE = 256;
// Make a cube with 65K instances and attributes to control offset and color of each instance
class InstancedCube extends Model {
// uniformBuffer: Buffer;
constructor(device: Device, props?: Partial<ModelProps>) {
@ -139,8 +124,8 @@ class InstancedCube extends Model {
bufferLayout: [
{name: 'instanceOffsets', format: 'float32x2'},
{name: 'instanceColors', format: 'unorm8x4'},
{name: 'instancePickingColors', format: 'unorm8x2'},
// TODO - normalizing picking colors breaks picking
{name: 'instancePickingColors', format: 'unorm8x2'}
// TODO - normalizing picking colors breaks picking
// {name: 'instancePickingColors', format: 'unorm8x2'},
],
attributes: {
@ -182,10 +167,10 @@ export default class AppAnimationLoopTemplate extends AnimationLoopTemplate {
timelineChannels: Record<string, number>;
pickingFramebuffer: Framebuffer;
uniformStore = new UniformStore<{
app: AppUniforms,
dirlight: typeof dirlight.uniforms,
picking: typeof picking.uniforms
shaderInputs = new _ShaderInputs<{
app: AppUniforms;
dirlight: typeof dirlight.props;
picking: typeof picking.props;
}>({
app,
dirlight,
@ -210,13 +195,10 @@ export default class AppAnimationLoopTemplate extends AnimationLoopTemplate {
colorAttachments: ['rgba8unorm'],
depthStencilAttachment: 'depth24plus'
});
this.cube = new InstancedCube(device, {
bindings: {
app: this.uniformStore.getManagedUniformBuffer(device, 'app'),
dirlight: this.uniformStore.getManagedUniformBuffer(device, 'dirlight'),
picking: this.uniformStore.getManagedUniformBuffer(device, 'picking'),
}
// @ts-ignore
shaderInputs: this.shaderInputs
});
}
@ -225,11 +207,16 @@ export default class AppAnimationLoopTemplate extends AnimationLoopTemplate {
const {_mousePosition} = animationProps;
const {timeChannel, eyeXChannel, eyeYChannel, eyeZChannel} = this.timelineChannels;
this.uniformStore.setUniforms({
this.shaderInputs.setProps({
app: {
time: this.timeline.getTime(timeChannel),
// Basic projection matrix
projectionMatrix: new Matrix4().perspective({fovy: radians(60), aspect, near: 1, far: 2048.0}),
projectionMatrix: new Matrix4().perspective({
fovy: radians(60),
aspect,
near: 1,
far: 2048.0
}),
// Move the eye around the plane
viewMatrix: new Matrix4().lookAt({
center: [0, 0, 0],
@ -244,9 +231,7 @@ export default class AppAnimationLoopTemplate extends AnimationLoopTemplate {
}
});
if (_mousePosition) {
this.pickInstance(device, _mousePosition, this.cube, this.pickingFramebuffer);
}
this.pickInstance(device, _mousePosition, this.cube, this.pickingFramebuffer);
// Draw the cubes
const renderPass = device.beginRenderPass({
@ -265,25 +250,34 @@ export default class AppAnimationLoopTemplate extends AnimationLoopTemplate {
pickInstance(
device: Device,
mousePosition: number[],
mousePosition: number[] | null | undefined,
model: Model,
framebuffer: Framebuffer
) {
if (!mousePosition) {
this.shaderInputs.setProps({picking: {highlightedObjectColor: null}});
return;
}
// use the center pixel location in device pixel range
const devicePixels = device.canvasContext.cssToDevicePixels(mousePosition);
const devicePixels = device.canvasContext!.cssToDevicePixels(mousePosition);
const pickX = devicePixels.x + Math.floor(devicePixels.width / 2);
const pickY = devicePixels.y + Math.floor(devicePixels.height / 2);
// Render picking colors
framebuffer.resize(device.canvasContext.getPixelSize());
framebuffer.resize(device.canvasContext!.getPixelSize());
this.uniformStore.setUniforms({picking: {isActive: true}});
this.shaderInputs.setProps({picking: {isActive: true}});
const pickingPass = device.beginRenderPass({framebuffer, clearColor: [0, 0, 0, 0], clearDepth: 1});
const pickingPass = device.beginRenderPass({
framebuffer,
clearColor: [0, 0, 0, 0],
clearDepth: 1
});
model.draw(pickingPass);
pickingPass.end();
// Read back
// Read back
const color255 = readPixelsToArray(framebuffer, {
sourceX: pickX,
sourceY: pickY,
@ -292,10 +286,12 @@ export default class AppAnimationLoopTemplate extends AnimationLoopTemplate {
});
// console.log(color255);
const highlightedObjectColor = new Float32Array(color255).map((x) => x / 255);
const isHighlightActive = highlightedObjectColor[0] + highlightedObjectColor[1] + highlightedObjectColor[2] > 0;
this.uniformStore.setUniforms({picking: {isActive: false, isHighlightActive, highlightedObjectColor}});
}
}
const highlightedObjectColor = new Float32Array(color255).map(x => x / 255);
const isHighlightActive =
highlightedObjectColor[0] + highlightedObjectColor[1] + highlightedObjectColor[2] > 0;
this.shaderInputs.setProps({
picking: {isActive: false, isHighlightActive, highlightedObjectColor}
});
}
}

View File

@ -163,10 +163,6 @@ export {requestAnimationFrame, cancelAnimationFrame} from './utils/request-anima
*/
export const glsl = (x: TemplateStringsArray) => `${x}`;
// DEBUG
export {getDebugTableForShaderLayout} from './lib/debug/debug-shader-layout';
// INTERNAL
export type {

View File

@ -14,7 +14,7 @@ declare global {
function initializeLuma(): string {
// Version detection using babel plugin
// @ts-expect-error
const VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'untranspiled source';
const VERSION = typeof __VERSION__ !== 'undefined' ? __VERSION__ : 'running from source';
const STARTUP_MESSAGE = 'set luma.log.level=1 (or higher) to trace rendering';
// Assign luma.log.level in console to control logging: \
@ -27,7 +27,7 @@ function initializeLuma(): string {
if (!globalThis.luma) {
if (isBrowser()) {
log.log(1, `luma.gl ${VERSION} - ${STARTUP_MESSAGE}`)();
log.log(1, `${VERSION} - ${STARTUP_MESSAGE}`)();
}
globalThis.luma = globalThis.luma || {

View File

@ -22,6 +22,3 @@ import './lib/uniforms/uniform-buffer-layout.spec';
// compiler logs
import './lib/compiler-log/format-compiler-log.spec';
// debug
import './lib/debug/get-debug-table-from-shader-layout.spec';

View File

@ -1,4 +1,8 @@
import type {ShaderLayout} from '../../adapter/types/shader-layout';
// luma.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors
import type {ShaderLayout} from '@luma.gl/core';
/**
* Extracts a table suitable for `console.table()` from a shader layout to assist in debugging.

View File

@ -6,13 +6,14 @@ import type {BufferLayout, VertexArray, TransformFeedback} from '@luma.gl/core';
import type {AttributeInfo, Binding, UniformValue, PrimitiveTopology} from '@luma.gl/core';
import {Device, Buffer, RenderPipeline, RenderPass, UniformStore} from '@luma.gl/core';
import {log, uid, deepEqual, splitUniformsAndBindings} from '@luma.gl/core';
import {getAttributeInfosFromLayouts, getDebugTableForShaderLayout} from '@luma.gl/core';
import {getAttributeInfosFromLayouts} from '@luma.gl/core';
import type {ShaderModule, PlatformInfo} from '@luma.gl/shadertools';
import {ShaderAssembler} from '@luma.gl/shadertools';
import {ShaderInputs} from '../shader-inputs';
import type {Geometry} from '../geometry/geometry';
import {GPUGeometry, makeGPUGeometry} from '../geometry/gpu-geometry';
import {PipelineFactory} from '../lib/pipeline-factory';
import {getDebugTableForShaderLayout} from '../debug/debug-shader-layout';
const LOG_DRAW_PRIORITY = 2;
const LOG_DRAW_TIMEOUT = 10000;
@ -304,7 +305,7 @@ export class Model {
_setGeometryAttributes(gpuGeometry: GPUGeometry): void {
// TODO - delete previous geometry?
this.vertexCount = gpuGeometry.vertexCount;
this.setAttributes(gpuGeometry.attributes);
this.setAttributes(gpuGeometry.attributes, 'ignore-unknown');
this.setIndexBuffer(gpuGeometry.indices);
}
@ -438,7 +439,7 @@ export class Model {
* Sets attributes (buffers)
* @note Overrides any attributes previously set with the same name
*/
setAttributes(buffers: Record<string, Buffer>): void {
setAttributes(buffers: Record<string, Buffer>, _option?: 'ignore-unknown'): void {
if (buffers.indices) {
log.warn(
`Model:${this.id} setAttributes() - indexBuffer should be set using setIndexBuffer()`
@ -463,7 +464,7 @@ export class Model {
set = true;
}
}
if (!set) {
if (!set && _option !== 'ignore-unknown') {
log.warn(
`Model(${this.id}): Ignoring buffer "${buffer.id}" for unknown attribute "${bufferName}"`
)();
@ -552,6 +553,13 @@ export class Model {
// log.table(logLevel, uniformTable)();
log.table(LOG_DRAW_PRIORITY, shaderLayoutTable)();
const uniformTable = this.shaderInputs.getDebugTable();
// Add any global uniforms
for (const [name, value] of Object.entries(this.uniforms)) {
uniformTable[name] = {value};
}
log.table(LOG_DRAW_PRIORITY, uniformTable)();
log.groupEnd(LOG_DRAW_PRIORITY)();
this._logOpen = false;
}

View File

@ -134,4 +134,17 @@ export class ShaderInputs<
}
return bindings;
}
getDebugTable(): Record<string, Record<string, unknown>> {
const table: Record<string,Record<string, unknown>> = {};
for (const [moduleName, module] of Object.entries(this.moduleUniforms)) {
for (const [key, value] of Object.entries(module)) {
table[`${moduleName}.${key}`] = {
type: this.modules[moduleName].uniformTypes?.[key],
value: String(value)
};
}
}
return table;
}
}

View File

@ -2,8 +2,8 @@
// Copyright (c) vis.gl contributors
import test from 'tape-promise/tape';
import {getDebugTableForShaderLayout, ShaderLayout} from '@luma.gl/core';
import type {ShaderLayout} from '@luma.gl/core';
import {getDebugTableForShaderLayout} from '../../src/debug/debug-shader-layout';
const SHADER_LAYOUT: ShaderLayout = {
attributes: [

View File

@ -20,3 +20,7 @@ import './scenegraph/model-node.spec';
import './shader-inputs.spec';
import './transform/buffer-transform.spec';
import './transform/texture-transform.spec';
// debug
import './debug/get-debug-table-from-shader-layout.spec';

View File

@ -38,9 +38,13 @@ function initializeExtensions(gl: WebGLRenderingContext): void {
const contextState = getContextData(gl);
// `getSupportedExtensions` can return null when context is lost.
const EXTENSIONS = gl.getSupportedExtensions() || [];
// Generates warnings in Chrome
const IGNORE_EXTENSIONS = ['WEBGL_polygon_mode'];
for (const extensionName of EXTENSIONS) {
const extension = gl.getExtension(extensionName);
contextState._extensions[extensionName] = extension;
if (!IGNORE_EXTENSIONS.includes(extensionName)) {
const extension = gl.getExtension(extensionName);
contextState._extensions[extensionName] = extension;
}
}
}