diff --git a/README.md b/README.md index 3be1e9192..beb387434 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ You can directly download the files and start using the ES6 modules: #### ES modules ```javascript -import { GPUCurtains } from 'path/to/dist/gpu-curtains.mjs' +import { GPUCurtains } from 'path/to/dist/esm/index.mjs' window.addEventListener('load', async () => { // set our main GPUCurtains instance diff --git a/dist/esm/core/DOM/DOMElement.mjs b/dist/esm/core/DOM/DOMElement.mjs new file mode 100644 index 000000000..9c8555d25 --- /dev/null +++ b/dist/esm/core/DOM/DOMElement.mjs @@ -0,0 +1,107 @@ +import { resizeManager } from '../../utils/ResizeManager.mjs'; +import { throwError } from '../../utils/utils.mjs'; + +class DOMElement { + /** + * DOMElement constructor + * @param parameters - {@link DOMElementParams | parameters} used to create our DOMElement + */ + constructor({ + element = document.body, + priority = 1, + onSizeChanged = (boundingRect = null) => { + }, + onPositionChanged = (boundingRect = null) => { + } + } = {}) { + if (typeof element === "string") { + this.element = document.querySelector(element); + if (!this.element) { + const notFoundEl = typeof element === "string" ? `'${element}' selector` : `${element} HTMLElement`; + throwError(`DOMElement: corresponding ${notFoundEl} not found.`); + } + } else { + this.element = element; + } + this.priority = priority; + this.isResizing = false; + this.onSizeChanged = onSizeChanged; + this.onPositionChanged = onPositionChanged; + this.resizeManager = resizeManager; + this.resizeManager.observe({ + element: this.element, + priority: this.priority, + callback: () => { + this.setSize(); + } + }); + this.setSize(); + } + /** + * Check whether 2 bounding rectangles are equals + * @param rect1 - first bounding rectangle + * @param rect2 - second bounding rectangle + * @returns - whether the rectangles are equals or not + */ + compareBoundingRect(rect1, rect2) { + return !["x", "y", "left", "top", "right", "bottom", "width", "height"].some((k) => rect1[k] !== rect2[k]); + } + /** + * Get our element bounding rectangle + */ + get boundingRect() { + return this._boundingRect; + } + /** + * Set our element bounding rectangle + * @param boundingRect - new bounding rectangle + */ + set boundingRect(boundingRect) { + const isSameRect = !!this.boundingRect && this.compareBoundingRect(boundingRect, this.boundingRect); + this._boundingRect = { + top: boundingRect.top, + right: boundingRect.right, + bottom: boundingRect.bottom, + left: boundingRect.left, + width: boundingRect.width, + height: boundingRect.height, + x: boundingRect.x, + y: boundingRect.y + }; + if (!isSameRect) { + this.onSizeChanged(this.boundingRect); + } + } + /** + * Update our element bounding rectangle because the scroll position has changed + * @param delta - scroll delta values along X and Y axis + */ + updateScrollPosition(delta = { x: 0, y: 0 }) { + if (this.isResizing) + return; + this._boundingRect.top += delta.y; + this._boundingRect.left += delta.x; + if (delta.x || delta.y) { + this.onPositionChanged(this.boundingRect); + } + } + /** + * Set our element bounding rectangle, either by a value or a getBoundingClientRect call + * @param boundingRect - new bounding rectangle + */ + setSize(boundingRect = null) { + if (!this.element) + return; + this.boundingRect = boundingRect ?? this.element.getBoundingClientRect(); + this.isResizing = false; + } + /** + * Destroy our DOMElement - remove from resize observer and clear throttle timeout + */ + destroy() { + this.resizeManager.unobserve(this.element); + } +} + +export { DOMElement }; +//# sourceMappingURL=DOMElement.mjs.map diff --git a/dist/esm/core/DOM/DOMElement.mjs.map b/dist/esm/core/DOM/DOMElement.mjs.map new file mode 100644 index 000000000..3c4598158 --- /dev/null +++ b/dist/esm/core/DOM/DOMElement.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"DOMElement.mjs","sources":["../../../../src/core/DOM/DOMElement.ts"],"sourcesContent":["import { resizeManager, ResizeManager, ResizeManagerEntry } from '../../utils/ResizeManager'\nimport { throwError } from '../../utils/utils'\n\n/**\n * Defines a rectangular coordinates object\n */\nexport interface RectCoords {\n /** top position */\n top: number\n /** right position */\n right: number\n /** bottom position */\n bottom: number\n /** left position */\n left: number\n}\n\n/**\n * Defines a size object\n */\nexport interface RectSize {\n /** width of the rectangle */\n width: number\n /** height of the rectangle */\n height: number\n}\n\n/**\n * Defines a rectangular bounding box object\n */\nexport interface RectBBox extends RectSize {\n /** top position of the bounding box */\n top: number\n /** left position of the bounding box */\n left: number\n}\n\n/**\n * Defines a DOM position object\n */\nexport interface DOMPosition {\n /** X position */\n x: number\n /** Y position */\n y: number\n}\n\n/**\n * Defines a complete DOM Element bounding rect object, similar to a {@link DOMRect}\n */\nexport interface DOMElementBoundingRect extends RectCoords, RectBBox, DOMPosition {}\n\n/**\n * Parameters used to create a {@link DOMElement}\n */\nexport interface DOMElementParams {\n /** {@link HTMLElement} or string representing an {@link HTMLElement} selector of the element the resize observer should track */\n element?: string | Element\n /** Order in which the {@link resizeManager} callback is executed */\n priority?: ResizeManagerEntry['priority']\n /** Callback to tun when the {@link DOMElement#element | element} size changed */\n onSizeChanged?: (boundingRect: DOMElementBoundingRect | null) => void | null\n /** Callback to tun when the {@link DOMElement#element | element} position changed */\n onPositionChanged?: (boundingRect: DOMElementBoundingRect | null) => void | null\n}\n\n/**\n * Used to track a DOM Element size and position by using a resize observer provided by {@link ResizeManager}.
\n * Execute callbacks when the bounding rectangle of the DOM Element changes, which means when its size and/or position change.\n */\nexport class DOMElement {\n /** The HTML element to track */\n element: HTMLElement\n /** Priority at which this element {@link onSizeChanged} function must be called */\n priority: ResizeManagerEntry['priority']\n /** Flag indicating whether the timeout is still running and we should avoid a new computation */\n isResizing: boolean\n /** Callback to run whenever the {@link element} size changed */\n onSizeChanged: (boundingRect: DOMElementBoundingRect | null) => void | null\n /** Callback to run whenever the {@link element} position changed */\n onPositionChanged: (boundingRect: DOMElementBoundingRect | null) => void | null\n /** The {@link ResizeManager} used, basically a wrapper around a {@link ResizeObserver} */\n resizeManager: ResizeManager\n /** Current {@link element} bounding rectangle */\n _boundingRect: DOMElementBoundingRect\n\n /**\n * DOMElement constructor\n * @param parameters - {@link DOMElementParams | parameters} used to create our DOMElement\n */\n constructor(\n {\n element = document.body,\n priority = 1,\n onSizeChanged = (boundingRect = null) => {\n /* allow empty callback */\n },\n onPositionChanged = (boundingRect = null) => {\n /* allow empty callback */\n },\n } = {} as DOMElementParams\n ) {\n if (typeof element === 'string') {\n this.element = document.querySelector(element)\n\n if (!this.element) {\n const notFoundEl = typeof element === 'string' ? `'${element}' selector` : `${element} HTMLElement`\n throwError(`DOMElement: corresponding ${notFoundEl} not found.`)\n }\n } else {\n this.element = element as HTMLElement\n }\n\n this.priority = priority\n\n this.isResizing = false\n\n this.onSizeChanged = onSizeChanged\n this.onPositionChanged = onPositionChanged\n\n this.resizeManager = resizeManager\n\n this.resizeManager.observe({\n element: this.element,\n priority: this.priority,\n callback: () => {\n this.setSize()\n },\n })\n\n // set size right away on init\n this.setSize()\n }\n\n /**\n * Check whether 2 bounding rectangles are equals\n * @param rect1 - first bounding rectangle\n * @param rect2 - second bounding rectangle\n * @returns - whether the rectangles are equals or not\n */\n compareBoundingRect(rect1: DOMRect | DOMElementBoundingRect, rect2: DOMRect | DOMElementBoundingRect): boolean {\n return !['x', 'y', 'left', 'top', 'right', 'bottom', 'width', 'height'].some((k) => rect1[k] !== rect2[k])\n }\n\n /**\n * Get our element bounding rectangle\n */\n get boundingRect(): DOMElementBoundingRect {\n return this._boundingRect\n }\n\n /**\n * Set our element bounding rectangle\n * @param boundingRect - new bounding rectangle\n */\n set boundingRect(boundingRect: DOMElementBoundingRect) {\n const isSameRect = !!this.boundingRect && this.compareBoundingRect(boundingRect, this.boundingRect)\n\n this._boundingRect = {\n top: boundingRect.top,\n right: boundingRect.right,\n bottom: boundingRect.bottom,\n left: boundingRect.left,\n width: boundingRect.width,\n height: boundingRect.height,\n x: boundingRect.x,\n y: boundingRect.y,\n }\n\n if (!isSameRect) {\n this.onSizeChanged(this.boundingRect)\n }\n }\n\n /**\n * Update our element bounding rectangle because the scroll position has changed\n * @param delta - scroll delta values along X and Y axis\n */\n updateScrollPosition(delta: DOMPosition = { x: 0, y: 0 }) {\n if (this.isResizing) return\n\n this._boundingRect.top += delta.y\n this._boundingRect.left += delta.x\n\n if (delta.x || delta.y) {\n this.onPositionChanged(this.boundingRect)\n }\n }\n\n /**\n * Set our element bounding rectangle, either by a value or a getBoundingClientRect call\n * @param boundingRect - new bounding rectangle\n */\n setSize(boundingRect: DOMElementBoundingRect | null = null) {\n if (!this.element) return\n\n this.boundingRect = boundingRect ?? this.element.getBoundingClientRect()\n\n this.isResizing = false\n }\n\n /**\n * Destroy our DOMElement - remove from resize observer and clear throttle timeout\n */\n destroy() {\n this.resizeManager.unobserve(this.element)\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/DOM/DOMFrustum.mjs b/dist/esm/core/DOM/DOMFrustum.mjs new file mode 100644 index 000000000..8f82616e4 --- /dev/null +++ b/dist/esm/core/DOM/DOMFrustum.mjs @@ -0,0 +1,113 @@ +import { Box3 } from '../../math/Box3.mjs'; +import { Mat4 } from '../../math/Mat4.mjs'; + +const defaultDOMFrustumMargins = { + top: 0, + right: 0, + bottom: 0, + left: 0 +}; +class DOMFrustum { + /** + * DOMFrustum constructor + * @param {DOMFrustumParams} parameters - {@link DOMFrustumParams | parameters} used to create our {@link DOMFrustum} + */ + constructor({ + boundingBox = new Box3(), + modelViewProjectionMatrix = new Mat4(), + containerBoundingRect = { + top: 0, + right: 0, + bottom: 0, + left: 0, + width: 0, + height: 0, + x: 0, + y: 0 + }, + DOMFrustumMargins = defaultDOMFrustumMargins, + onReEnterView = () => { + }, + onLeaveView = () => { + } + }) { + this.boundingBox = boundingBox; + this.modelViewProjectionMatrix = modelViewProjectionMatrix; + this.containerBoundingRect = containerBoundingRect; + this.DOMFrustumMargins = { ...defaultDOMFrustumMargins, ...DOMFrustumMargins }; + this.projectedBoundingRect = { + top: 0, + right: 0, + bottom: 0, + left: 0, + width: 0, + height: 0, + x: 0, + y: 0 + }; + this.onReEnterView = onReEnterView; + this.onLeaveView = onLeaveView; + this.isIntersecting = false; + this.shouldUpdate = false; + } + /** + * Set our {@link containerBoundingRect} (called on resize) + * @param boundingRect - new bounding rectangle + */ + setContainerBoundingRect(boundingRect) { + this.containerBoundingRect = boundingRect; + } + /** + * Get our DOM frustum bounding rectangle, i.e. our {@link containerBoundingRect} with the {@link DOMFrustumMargins} applied + * @readonly + */ + get DOMFrustumBoundingRect() { + return { + top: this.projectedBoundingRect.top - this.DOMFrustumMargins.top, + right: this.projectedBoundingRect.right + this.DOMFrustumMargins.right, + bottom: this.projectedBoundingRect.bottom + this.DOMFrustumMargins.bottom, + left: this.projectedBoundingRect.left - this.DOMFrustumMargins.left + }; + } + /** + * Applies all {@link modelViewProjectionMatrix} transformations to our {@link boundingBox} and then check against intersections + */ + computeProjectedToDocumentCoords() { + const projectedBox = this.boundingBox.applyMat4(this.modelViewProjectionMatrix); + projectedBox.min.x = (projectedBox.min.x + 1) * 0.5; + projectedBox.max.x = (projectedBox.max.x + 1) * 0.5; + projectedBox.min.y = 1 - (projectedBox.min.y + 1) * 0.5; + projectedBox.max.y = 1 - (projectedBox.max.y + 1) * 0.5; + const { width, height, top, left } = this.containerBoundingRect; + this.projectedBoundingRect = { + left: projectedBox.min.x * width + left, + x: projectedBox.min.x * width + left, + top: projectedBox.max.y * height + top, + y: projectedBox.max.y * height + top, + right: projectedBox.max.x * width + left, + bottom: projectedBox.min.y * height + top, + width: projectedBox.max.x * width + left - (projectedBox.min.x * width + left), + height: projectedBox.min.y * height + top - (projectedBox.max.y * height + top) + }; + this.intersectsContainer(); + } + /** + * Check whether our {@link projectedBoundingRect} intersects with our {@link DOMFrustumBoundingRect} + */ + intersectsContainer() { + if (Math.round(this.DOMFrustumBoundingRect.right) <= this.containerBoundingRect.left || Math.round(this.DOMFrustumBoundingRect.left) >= this.containerBoundingRect.left + this.containerBoundingRect.width || Math.round(this.DOMFrustumBoundingRect.bottom) <= this.containerBoundingRect.top || Math.round(this.DOMFrustumBoundingRect.top) >= this.containerBoundingRect.top + this.containerBoundingRect.height) { + if (this.isIntersecting) { + this.onLeaveView(); + } + this.isIntersecting = false; + } else { + if (!this.isIntersecting) { + this.onReEnterView(); + } + this.isIntersecting = true; + } + } +} + +export { DOMFrustum }; +//# sourceMappingURL=DOMFrustum.mjs.map diff --git a/dist/esm/core/DOM/DOMFrustum.mjs.map b/dist/esm/core/DOM/DOMFrustum.mjs.map new file mode 100644 index 000000000..6637acece --- /dev/null +++ b/dist/esm/core/DOM/DOMFrustum.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"DOMFrustum.mjs","sources":["../../../../src/core/DOM/DOMFrustum.ts"],"sourcesContent":["import { Box3 } from '../../math/Box3'\nimport { Mat4 } from '../../math/Mat4'\nimport { DOMElementBoundingRect, RectCoords } from './DOMElement'\n\n/**\n * An object defining all possible {@link DOMFrustum} class instancing parameters\n */\nexport interface DOMFrustumParams {\n /** our 3D Object bounding box, i.e. size in world space before any transform. Usually defined by a {@link core/geometries/Geometry.Geometry | Geometry} */\n boundingBox?: Box3\n /** {@link core/objects3D/ProjectedObject3D.ProjectedObject3D#modelViewProjectionMatrix | model view projection matrix} to use for frustum calculations */\n modelViewProjectionMatrix?: Mat4\n /** the {@link DOMElementBoundingRect | bounding rectangle} to check against */\n containerBoundingRect?: DOMElementBoundingRect\n /** additional margins to add to {@link containerBoundingRect} */\n DOMFrustumMargins?: RectCoords\n /** callback to run when the {@link DOMFrustum#projectedBoundingRect | projectedBoundingRect} reenters the view frustum */\n onReEnterView?: () => void\n /** callback to run when the {@link DOMFrustum#projectedBoundingRect | projectedBoundingRect} leaves the view frustum */\n onLeaveView?: () => void\n}\n\n/** @constant {RectCoords} - default {@link DOMFrustum#DOMFrustumMargins | DOMFrustumMargins} */\nconst defaultDOMFrustumMargins: RectCoords = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n}\n\n/**\n * Used to check if a {@link core/objects3D/ProjectedObject3D.ProjectedObject3D | ProjectedObject3D} is currently contained inside a DOM bounding rectangle.\n *\n * Uses a {@link core/objects3D/ProjectedObject3D.ProjectedObject3D#modelViewProjectionMatrix | model view projection matrix} that contains both useful {@link core/objects3D/ProjectedObject3D.ProjectedObject3D#transforms | Object3D transforms} and {@link core/camera/Camera.Camera | Camera} projection information.\n * The DOM bounding rectangle to check against usually is the {@link core/renderers/GPURenderer.GPURenderer | renderer}'s {@link core/DOM/DOMElement.DOMElement | DOMElement} bounding rectangle, unless frustum margins are specified.\n */\nexport class DOMFrustum {\n /** Our 3D Object bounding box, i.e. size in world space before any transform. Usually defined by a {@link core/geometries/Geometry.Geometry | Geometry} */\n boundingBox: Box3\n /** A model view projection matrix defining transformations, usually from a {@link core/objects3D/ProjectedObject3D.ProjectedObject3D | ProjectedObject3D}, to use for frustum calculations */\n modelViewProjectionMatrix: Mat4\n\n /** The DOM bounding rectangle to check against, usually the renderer DOM Element bounding rectangle */\n containerBoundingRect: DOMElementBoundingRect\n /** Additional margins to add to {@link containerBoundingRect} */\n DOMFrustumMargins: RectCoords\n /** A DOM Element bounding rectangle representing the result of our {@link boundingBox} with the {@link modelViewProjectionMatrix} applied */\n projectedBoundingRect: DOMElementBoundingRect\n\n /** Callback to run when the {@link projectedBoundingRect} reenters the view frustum */\n onReEnterView: () => void\n /** Callback to run when the {@link projectedBoundingRect} leaves the view frustum */\n onLeaveView: () => void\n\n /** Flag to indicate whether the given {@link projectedBoundingRect} is intersecting our view frustum */\n isIntersecting: boolean\n /** Flag to indicate whether we should update our {@link projectedBoundingRect} */\n shouldUpdate: boolean\n\n /**\n * DOMFrustum constructor\n * @param {DOMFrustumParams} parameters - {@link DOMFrustumParams | parameters} used to create our {@link DOMFrustum}\n */\n constructor({\n boundingBox = new Box3(),\n modelViewProjectionMatrix = new Mat4(),\n containerBoundingRect = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n width: 0,\n height: 0,\n x: 0,\n y: 0,\n },\n DOMFrustumMargins = defaultDOMFrustumMargins,\n onReEnterView = () => {\n /* allow empty callbacks */\n },\n onLeaveView = () => {\n /* allow empty callbacks */\n },\n }: DOMFrustumParams) {\n this.boundingBox = boundingBox\n this.modelViewProjectionMatrix = modelViewProjectionMatrix\n this.containerBoundingRect = containerBoundingRect\n this.DOMFrustumMargins = { ...defaultDOMFrustumMargins, ...DOMFrustumMargins }\n\n this.projectedBoundingRect = {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n width: 0,\n height: 0,\n x: 0,\n y: 0,\n }\n\n this.onReEnterView = onReEnterView\n this.onLeaveView = onLeaveView\n\n this.isIntersecting = false\n this.shouldUpdate = false\n }\n\n /**\n * Set our {@link containerBoundingRect} (called on resize)\n * @param boundingRect - new bounding rectangle\n */\n setContainerBoundingRect(boundingRect: DOMElementBoundingRect) {\n this.containerBoundingRect = boundingRect\n }\n\n /**\n * Get our DOM frustum bounding rectangle, i.e. our {@link containerBoundingRect} with the {@link DOMFrustumMargins} applied\n * @readonly\n */\n get DOMFrustumBoundingRect(): RectCoords {\n return {\n top: this.projectedBoundingRect.top - this.DOMFrustumMargins.top,\n right: this.projectedBoundingRect.right + this.DOMFrustumMargins.right,\n bottom: this.projectedBoundingRect.bottom + this.DOMFrustumMargins.bottom,\n left: this.projectedBoundingRect.left - this.DOMFrustumMargins.left,\n }\n }\n\n /**\n * Applies all {@link modelViewProjectionMatrix} transformations to our {@link boundingBox} and then check against intersections\n */\n computeProjectedToDocumentCoords() {\n const projectedBox = this.boundingBox.applyMat4(this.modelViewProjectionMatrix)\n\n // normalize [-1, 1] coords to [0, 1]\n projectedBox.min.x = (projectedBox.min.x + 1) * 0.5\n projectedBox.max.x = (projectedBox.max.x + 1) * 0.5\n\n projectedBox.min.y = 1 - (projectedBox.min.y + 1) * 0.5\n projectedBox.max.y = 1 - (projectedBox.max.y + 1) * 0.5\n\n const { width, height, top, left } = this.containerBoundingRect\n\n this.projectedBoundingRect = {\n left: projectedBox.min.x * width + left,\n x: projectedBox.min.x * width + left,\n top: projectedBox.max.y * height + top,\n y: projectedBox.max.y * height + top,\n right: projectedBox.max.x * width + left,\n bottom: projectedBox.min.y * height + top,\n width: projectedBox.max.x * width + left - (projectedBox.min.x * width + left),\n height: projectedBox.min.y * height + top - (projectedBox.max.y * height + top),\n }\n\n this.intersectsContainer()\n }\n\n /**\n * Check whether our {@link projectedBoundingRect} intersects with our {@link DOMFrustumBoundingRect}\n */\n intersectsContainer() {\n if (\n Math.round(this.DOMFrustumBoundingRect.right) <= this.containerBoundingRect.left ||\n Math.round(this.DOMFrustumBoundingRect.left) >=\n this.containerBoundingRect.left + this.containerBoundingRect.width ||\n Math.round(this.DOMFrustumBoundingRect.bottom) <= this.containerBoundingRect.top ||\n Math.round(this.DOMFrustumBoundingRect.top) >= this.containerBoundingRect.top + this.containerBoundingRect.height\n ) {\n if (this.isIntersecting) {\n this.onLeaveView()\n }\n\n this.isIntersecting = false\n } else {\n if (!this.isIntersecting) {\n this.onReEnterView()\n }\n\n this.isIntersecting = true\n }\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/bindGroups/BindGroup.mjs b/dist/esm/core/bindGroups/BindGroup.mjs new file mode 100644 index 000000000..8e4b7a103 --- /dev/null +++ b/dist/esm/core/bindGroups/BindGroup.mjs @@ -0,0 +1,355 @@ +import { isRenderer } from '../renderers/utils.mjs'; +import { generateUUID, toKebabCase } from '../../utils/utils.mjs'; +import { WritableBufferBinding } from '../bindings/WritableBufferBinding.mjs'; +import { BufferBinding } from '../bindings/BufferBinding.mjs'; + +class BindGroup { + /** + * BindGroup constructor + * @param renderer - a {@link Renderer} class object or a {@link GPUCurtains} class object + * @param parameters - {@link BindGroupParams | parameters} used to create our {@link BindGroup} + */ + constructor(renderer, { label = "BindGroup", index = 0, bindings = [], uniforms, storages } = {}) { + this.type = "BindGroup"; + renderer = renderer && renderer.renderer || renderer; + isRenderer(renderer, this.type); + this.renderer = renderer; + this.options = { + label, + index, + bindings, + ...uniforms && { uniforms }, + ...storages && { storages } + }; + this.index = index; + this.uuid = generateUUID(); + this.bindings = []; + bindings.length && this.addBindings(bindings); + if (this.options.uniforms || this.options.storages) + this.setInputBindings(); + this.resetEntries(); + this.bindGroupLayout = null; + this.bindGroup = null; + this.needsPipelineFlush = false; + this.renderer.addBindGroup(this); + } + /** + * Sets our {@link BindGroup#index | bind group index} + * @param index - {@link BindGroup#index | bind group index} to set + */ + setIndex(index) { + this.index = index; + } + /** + * Adds an array of already created {@link bindings} (buffers, texture, etc.) to the {@link bindings} array + * @param bindings - {@link bindings} to add + */ + addBindings(bindings = []) { + this.bindings = [...this.bindings, ...bindings]; + } + /** + * Adds an already created {@link bindings} (buffers, texture, etc.) to the {@link bindings} array + * @param binding - binding to add + */ + addBinding(binding) { + this.bindings.push(binding); + } + /** + * Creates Bindings based on a list of inputs + * @param bindingType - {@link core/bindings/Binding.Binding#bindingType | binding type} + * @param inputs - {@link ReadOnlyInputBindings | inputs (uniform or storage)} that will be used to create the binding + * @returns - a {@link bindings} array + */ + createInputBindings(bindingType = "uniform", inputs = {}) { + return [ + ...Object.keys(inputs).map((inputKey) => { + const binding = inputs[inputKey]; + const bindingParams = { + label: toKebabCase(binding.label || inputKey), + name: inputKey, + bindingType, + useStruct: true, + // by default + visibility: binding.access === "read_write" ? "compute" : binding.visibility, + access: binding.access ?? "read", + // read by default + struct: binding.struct, + ...binding.shouldCopyResult !== void 0 && { shouldCopyResult: binding.shouldCopyResult } + }; + const BufferBindingConstructor = bindingParams.access === "read_write" ? WritableBufferBinding : BufferBinding; + return binding.useStruct !== false ? new BufferBindingConstructor(bindingParams) : Object.keys(binding.struct).map((bindingKey) => { + bindingParams.label = toKebabCase(binding.label ? binding.label + bindingKey : inputKey + bindingKey); + bindingParams.name = inputKey + bindingKey; + bindingParams.useStruct = false; + bindingParams.struct = { [bindingKey]: binding.struct[bindingKey] }; + return new BufferBindingConstructor(bindingParams); + }); + }) + ].flat(); + } + /** + * Create and adds {@link bindings} based on inputs provided upon creation + */ + setInputBindings() { + this.addBindings([ + ...this.createInputBindings("uniform", this.options.uniforms), + ...this.createInputBindings("storage", this.options.storages) + ]); + } + /** + * Get whether the GPU bind group is ready to be created + * It can be created if it has {@link bindings} and has not been created yet + * @readonly + */ + get shouldCreateBindGroup() { + return !this.bindGroup && !!this.bindings.length; + } + /** + * Reset our {@link BindGroup} {@link entries} + */ + resetEntries() { + this.entries = { + bindGroupLayout: [], + bindGroup: [] + }; + } + /** + * Create the GPU buffers, {@link bindings}, {@link entries}, {@link bindGroupLayout} and {@link bindGroup} + */ + createBindGroup() { + this.fillEntries(); + this.setBindGroupLayout(); + this.setBindGroup(); + } + /** + * Reset the {@link BindGroup#entries.bindGroup | bindGroup entries}, recreates them and then recreate the {@link BindGroup#bindGroup | GPU bind group} + */ + resetBindGroup() { + this.entries.bindGroup = []; + this.bindings.forEach((binding) => { + this.entries.bindGroup.push({ + binding: this.entries.bindGroup.length, + resource: binding.resource + }); + }); + this.setBindGroup(); + } + /** + * Reset the {@link BindGroup#entries.bindGroupLayout | bindGroupLayout entries}, recreates them and then recreate the {@link BindGroup#bindGroupLayout | GPU bind group layout} + */ + resetBindGroupLayout() { + this.entries.bindGroupLayout = []; + this.bindings.forEach((binding) => { + this.entries.bindGroupLayout.push({ + binding: this.entries.bindGroupLayout.length, + ...binding.resourceLayout, + visibility: binding.visibility + }); + }); + this.setBindGroupLayout(); + } + /** + * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been lost to prepare everything for restoration + */ + loseContext() { + this.resetEntries(); + this.bufferBindings.forEach((binding) => { + binding.buffer = null; + if ("resultBuffer" in binding) { + binding.resultBuffer = null; + } + }); + this.bindGroup = null; + this.bindGroupLayout = null; + this.needsPipelineFlush = true; + } + /** + * Get all {@link BindGroup#bindings | bind group bindings} that handle a {@link GPUBuffer} + */ + get bufferBindings() { + return this.bindings.filter( + (binding) => binding instanceof BufferBinding || binding instanceof WritableBufferBinding + ); + } + /** + * Creates binding GPUBuffer with correct params + * @param binding - the binding element + */ + createBindingBuffer(binding) { + binding.buffer = this.renderer.createBuffer({ + label: this.options.label + ": " + binding.bindingType + " buffer from: " + binding.label, + size: binding.arrayBuffer.byteLength, + usage: binding.bindingType === "uniform" ? GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC | GPUBufferUsage.VERTEX : GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC | GPUBufferUsage.VERTEX + }); + if ("resultBuffer" in binding) { + binding.resultBuffer = this.renderer.createBuffer({ + label: this.options.label + ": Result buffer from: " + binding.label, + size: binding.arrayBuffer.byteLength, + usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST + }); + } + } + /** + * Fill in our entries bindGroupLayout and bindGroup arrays with the correct binding resources. + * For buffer struct, create a GPUBuffer first if needed + */ + fillEntries() { + this.bindings.forEach((binding) => { + if (!binding.visibility) { + binding.visibility = GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE; + } + if ("buffer" in binding && !binding.buffer) { + this.createBindingBuffer(binding); + } + this.entries.bindGroupLayout.push({ + binding: this.entries.bindGroupLayout.length, + ...binding.resourceLayout, + visibility: binding.visibility + }); + this.entries.bindGroup.push({ + binding: this.entries.bindGroup.length, + resource: binding.resource + }); + }); + } + /** + * Get a bind group binding by name/key + * @param bindingName - the binding name or key + * @returns - the found binding, or null if not found + */ + getBindingByName(bindingName = "") { + return this.bindings.find((binding) => binding.name === bindingName); + } + /** + * Create a GPUBindGroupLayout and set our {@link bindGroupLayout} + */ + setBindGroupLayout() { + this.bindGroupLayout = this.renderer.createBindGroupLayout({ + label: this.options.label + " layout", + entries: this.entries.bindGroupLayout + }); + } + /** + * Create a GPUBindGroup and set our {@link bindGroup} + */ + setBindGroup() { + this.bindGroup = this.renderer.createBindGroup({ + label: this.options.label, + layout: this.bindGroupLayout, + entries: this.entries.bindGroup + }); + } + /** + * Check whether we should update (write) our {@link GPUBuffer} or not. + */ + updateBufferBindings() { + this.bufferBindings.forEach((binding, index) => { + binding.update(); + if (binding.shouldUpdate) { + if (!binding.useStruct && binding.bufferElements.length > 1) { + this.renderer.queueWriteBuffer(binding.buffer, 0, binding.bufferElements[index].view); + } else { + this.renderer.queueWriteBuffer(binding.buffer, 0, binding.arrayBuffer); + } + } + binding.shouldUpdate = false; + }); + } + /** + * Update the {@link BindGroup}, which means update its {@link BindGroup#bufferBindings | buffer bindings} and {@link BindGroup#resetBindGroup | reset it} if needed. + * Called at each render from the parentMesh {@link core/materials/Material.Material | material} + */ + update() { + this.updateBufferBindings(); + const needBindGroupReset = this.bindings.some((binding) => binding.shouldResetBindGroup); + const needBindGroupLayoutReset = this.bindings.some((binding) => binding.shouldResetBindGroupLayout); + if (needBindGroupReset || needBindGroupLayoutReset) { + this.renderer.onAfterCommandEncoderSubmission.add( + () => { + this.bindings.forEach((binding) => { + binding.shouldResetBindGroup = false; + binding.shouldResetBindGroupLayout = false; + }); + }, + { once: true } + ); + } + if (needBindGroupLayoutReset) { + this.resetBindGroupLayout(); + this.needsPipelineFlush = true; + } + if (needBindGroupReset) { + this.resetBindGroup(); + } + } + /** + * Clones a {@link BindGroup} from a list of {@link bindings} + * Useful to create a new bind group with already created buffers, but swapped + * @param parameters - parameters to use for cloning + * @param parameters.bindings - our input {@link bindings} + * @param [parameters.keepLayout=false] - whether we should keep original {@link bindGroupLayout} or not + * @returns - the cloned {@link BindGroup} + */ + clone({ + bindings = [], + keepLayout = false + } = {}) { + const params = { ...this.options }; + params.label += " (copy)"; + const bindGroupCopy = new this.constructor(this.renderer, { + label: params.label + }); + bindGroupCopy.setIndex(this.index); + bindGroupCopy.options = params; + const bindingsRef = bindings.length ? bindings : this.bindings; + bindingsRef.forEach((binding, index) => { + bindGroupCopy.addBinding(binding); + if ("buffer" in binding && !binding.buffer) { + bindGroupCopy.createBindingBuffer(binding); + } + if (!keepLayout) { + bindGroupCopy.entries.bindGroupLayout.push({ + binding: bindGroupCopy.entries.bindGroupLayout.length, + ...binding.resourceLayout, + visibility: binding.visibility + }); + } + bindGroupCopy.entries.bindGroup.push({ + binding: bindGroupCopy.entries.bindGroup.length, + resource: binding.resource + }); + }); + if (keepLayout) { + bindGroupCopy.entries.bindGroupLayout = [...this.entries.bindGroupLayout]; + } + bindGroupCopy.setBindGroupLayout(); + bindGroupCopy.setBindGroup(); + return bindGroupCopy; + } + /** + * Destroy our {@link BindGroup} + * Most important is to destroy the GPUBuffers to free the memory + */ + destroy() { + this.renderer.removeBindGroup(this); + this.bufferBindings.forEach((binding) => { + if ("buffer" in binding) { + this.renderer.removeBuffer(binding.buffer); + binding.buffer?.destroy(); + binding.buffer = null; + } + if ("resultBuffer" in binding) { + this.renderer.removeBuffer(binding.resultBuffer); + binding.resultBuffer?.destroy(); + binding.resultBuffer = null; + } + }); + this.bindings = []; + this.bindGroupLayout = null; + this.bindGroup = null; + this.resetEntries(); + } +} + +export { BindGroup }; +//# sourceMappingURL=BindGroup.mjs.map diff --git a/dist/esm/core/bindGroups/BindGroup.mjs.map b/dist/esm/core/bindGroups/BindGroup.mjs.map new file mode 100644 index 000000000..80e253258 --- /dev/null +++ b/dist/esm/core/bindGroups/BindGroup.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"BindGroup.mjs","sources":["../../../../src/core/bindGroups/BindGroup.ts"],"sourcesContent":["import { isRenderer, Renderer } from '../renderers/utils'\r\nimport { generateUUID, toKebabCase } from '../../utils/utils'\r\nimport { WritableBufferBinding, WritableBufferBindingParams } from '../bindings/WritableBufferBinding'\r\nimport { BufferBinding } from '../bindings/BufferBinding'\r\nimport {\r\n AllowedBindGroups,\r\n BindGroupBindingElement,\r\n BindGroupBufferBindingElement,\r\n BindGroupEntries,\r\n BindGroupParams,\r\n ReadOnlyInputBindings,\r\n} from '../../types/BindGroups'\r\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\r\nimport { TextureBindGroupParams } from './TextureBindGroup'\r\nimport { BindingType } from '../bindings/Binding'\r\n\r\n/**\r\n * Used to handle all inputs data sent to the GPU.
\r\n * In WebGPU, data (buffers, textures or samplers, called bindings) are organised by bind groups, containing those bindings.\r\n *\r\n * ## Bindings\r\n *\r\n * A {@link BindGroup} is responsible for creating each {@link BufferBinding} {@link GPUBuffer} and then the {@link GPUBindGroup} and {@link GPUBindGroupLayout} that are used to create {@link GPUComputePipeline} or {@link GPURenderPipeline}.
\r\n * Those are generally automatically created by the {@link core/materials/Material.Material | Material} using this {@link BindGroup}. If you need to manually create them, you will have to call its {@link BindGroup#createBindGroup | `createBindGroup()` method}\r\n *\r\n * ### Samplers and textures\r\n *\r\n * A {@link BindGroup} is best suited to handle {@link GPUBuffer} only bindings. If you need to handle {@link GPUSampler}, a {@link GPUTexture} or a {@link GPUExternalTexture}, you should use a {@link core/bindGroups/TextureBindGroup.TextureBindGroup | TextureBindGroup} instead.\r\n *\r\n * ### Updating a GPUBindGroup or GPUBindGroupLayout\r\n *\r\n * Each time one of the {@link https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createBindGroup#resource | binding resource} changes, its {@link BindGroup#bindGroup | bindGroup} will be recreated (usually, when a {@link GPUTexture} is uploaded).
\r\n * Each time one of the {@link https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createBindGroupLayout#resource_layout_objects | binding resource layout} changes, its {@link BindGroup#bindGroupLayout | bindGroupLayout} and {@link BindGroup#bindGroup | bindGroup} will be recreated, and the {@link GPUComputePipeline} or {@link GPURenderPipeline} will be recreated as well.\r\n *\r\n * @example\r\n * ```javascript\r\n * // set our main GPUCurtains instance\r\n * const gpuCurtains = new GPUCurtains({\r\n * container: '#canvas' // selector of our WebGPU canvas container\r\n * })\r\n *\r\n * // set the GPU device\r\n * // note this is asynchronous\r\n * await gpuCurtains.setDevice()\r\n *\r\n * const bindGroup = new BindGroup(gpuCurtains, {\r\n * label: 'My bind group',\r\n * uniforms: {\r\n * params: {\r\n * struct: {\r\n * opacity: {\r\n * type: 'f32',\r\n * value: 1,\r\n * },\r\n * mousePosition: {\r\n * type: 'vec2f',\r\n * value: new Vec2(),\r\n * },\r\n * },\r\n * },\r\n * },\r\n * })\r\n *\r\n * // create the GPU buffer, bindGroupLayout and bindGroup\r\n * bindGroup.createBindGroup()\r\n * ```\r\n */\r\nexport class BindGroup {\r\n /** The type of the {@link BindGroup} */\r\n type: string\r\n /** The universal unique id of the {@link BindGroup} */\r\n uuid: string\r\n /** The {@link Renderer} used */\r\n renderer: Renderer\r\n /** Options used to create this {@link BindGroup} */\r\n options: TextureBindGroupParams\r\n /** Index of this {@link BindGroup}, used to link struct in the shaders */\r\n index: number\r\n\r\n /** List of {@link BindGroupBindingElement | bindings} (buffers, texture, etc.) handled by this {@link BindGroup} */\r\n bindings: BindGroupBindingElement[]\r\n\r\n /** Our {@link BindGroup} {@link BindGroupEntries | entries} objects */\r\n entries: BindGroupEntries\r\n\r\n /** Our {@link BindGroup}{@link GPUBindGroupLayout} */\r\n bindGroupLayout: null | GPUBindGroupLayout\r\n /** Our {@link BindGroup} {@link GPUBindGroup} */\r\n bindGroup: null | GPUBindGroup\r\n\r\n /** Flag indicating whether we need to flush and recreate the pipeline using this {@link BindGroup} s*/\r\n needsPipelineFlush: boolean\r\n\r\n /**\r\n * BindGroup constructor\r\n * @param renderer - a {@link Renderer} class object or a {@link GPUCurtains} class object\r\n * @param parameters - {@link BindGroupParams | parameters} used to create our {@link BindGroup}\r\n */\r\n constructor(\r\n renderer: Renderer | GPUCurtains,\r\n { label = 'BindGroup', index = 0, bindings = [], uniforms, storages }: BindGroupParams = {}\r\n ) {\r\n this.type = 'BindGroup'\r\n\r\n // we could pass our curtains object OR our curtains renderer object\r\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\r\n\r\n isRenderer(renderer, this.type)\r\n\r\n this.renderer = renderer\r\n this.options = {\r\n label,\r\n index,\r\n bindings,\r\n ...(uniforms && { uniforms }),\r\n ...(storages && { storages }),\r\n }\r\n\r\n this.index = index\r\n this.uuid = generateUUID()\r\n\r\n this.bindings = []\r\n bindings.length && this.addBindings(bindings)\r\n if (this.options.uniforms || this.options.storages) this.setInputBindings()\r\n\r\n this.resetEntries()\r\n\r\n this.bindGroupLayout = null\r\n this.bindGroup = null\r\n\r\n // if we ever update our bind group layout\r\n // we will have to recreate the whole pipeline again\r\n this.needsPipelineFlush = false\r\n\r\n this.renderer.addBindGroup(this)\r\n }\r\n\r\n /**\r\n * Sets our {@link BindGroup#index | bind group index}\r\n * @param index - {@link BindGroup#index | bind group index} to set\r\n */\r\n setIndex(index: number) {\r\n this.index = index\r\n }\r\n\r\n /**\r\n * Adds an array of already created {@link bindings} (buffers, texture, etc.) to the {@link bindings} array\r\n * @param bindings - {@link bindings} to add\r\n */\r\n addBindings(bindings: BindGroupBindingElement[] = []) {\r\n this.bindings = [...this.bindings, ...bindings]\r\n }\r\n\r\n /**\r\n * Adds an already created {@link bindings} (buffers, texture, etc.) to the {@link bindings} array\r\n * @param binding - binding to add\r\n */\r\n addBinding(binding: BindGroupBindingElement) {\r\n this.bindings.push(binding)\r\n }\r\n\r\n /**\r\n * Creates Bindings based on a list of inputs\r\n * @param bindingType - {@link core/bindings/Binding.Binding#bindingType | binding type}\r\n * @param inputs - {@link ReadOnlyInputBindings | inputs (uniform or storage)} that will be used to create the binding\r\n * @returns - a {@link bindings} array\r\n */\r\n createInputBindings(\r\n bindingType: BindingType = 'uniform',\r\n inputs: ReadOnlyInputBindings = {}\r\n ): BindGroupBindingElement[] {\r\n return [\r\n ...Object.keys(inputs).map((inputKey) => {\r\n const binding = inputs[inputKey] as WritableBufferBindingParams\r\n\r\n const bindingParams: WritableBufferBindingParams = {\r\n label: toKebabCase(binding.label || inputKey),\r\n name: inputKey,\r\n bindingType,\r\n useStruct: true, // by default\r\n visibility: binding.access === 'read_write' ? 'compute' : binding.visibility,\r\n access: binding.access ?? 'read', // read by default\r\n struct: binding.struct,\r\n ...(binding.shouldCopyResult !== undefined && { shouldCopyResult: binding.shouldCopyResult }),\r\n }\r\n\r\n const BufferBindingConstructor = bindingParams.access === 'read_write' ? WritableBufferBinding : BufferBinding\r\n\r\n return binding.useStruct !== false\r\n ? new BufferBindingConstructor(bindingParams)\r\n : Object.keys(binding.struct).map((bindingKey) => {\r\n bindingParams.label = toKebabCase(binding.label ? binding.label + bindingKey : inputKey + bindingKey)\r\n bindingParams.name = inputKey + bindingKey\r\n bindingParams.useStruct = false\r\n bindingParams.struct = { [bindingKey]: binding.struct[bindingKey] }\r\n\r\n return new BufferBindingConstructor(bindingParams)\r\n })\r\n }),\r\n ].flat()\r\n }\r\n\r\n /**\r\n * Create and adds {@link bindings} based on inputs provided upon creation\r\n */\r\n setInputBindings() {\r\n this.addBindings([\r\n ...this.createInputBindings('uniform', this.options.uniforms),\r\n ...this.createInputBindings('storage', this.options.storages),\r\n ])\r\n }\r\n\r\n /**\r\n * Get whether the GPU bind group is ready to be created\r\n * It can be created if it has {@link bindings} and has not been created yet\r\n * @readonly\r\n */\r\n get shouldCreateBindGroup(): boolean {\r\n return !this.bindGroup && !!this.bindings.length\r\n }\r\n\r\n /**\r\n * Reset our {@link BindGroup} {@link entries}\r\n */\r\n resetEntries() {\r\n this.entries = {\r\n bindGroupLayout: [],\r\n bindGroup: [],\r\n }\r\n }\r\n\r\n /**\r\n * Create the GPU buffers, {@link bindings}, {@link entries}, {@link bindGroupLayout} and {@link bindGroup}\r\n */\r\n createBindGroup() {\r\n this.fillEntries()\r\n this.setBindGroupLayout()\r\n this.setBindGroup()\r\n }\r\n\r\n /**\r\n * Reset the {@link BindGroup#entries.bindGroup | bindGroup entries}, recreates them and then recreate the {@link BindGroup#bindGroup | GPU bind group}\r\n */\r\n resetBindGroup() {\r\n this.entries.bindGroup = []\r\n this.bindings.forEach((binding) => {\r\n this.entries.bindGroup.push({\r\n binding: this.entries.bindGroup.length,\r\n resource: binding.resource,\r\n })\r\n })\r\n\r\n this.setBindGroup()\r\n }\r\n\r\n /**\r\n * Reset the {@link BindGroup#entries.bindGroupLayout | bindGroupLayout entries}, recreates them and then recreate the {@link BindGroup#bindGroupLayout | GPU bind group layout}\r\n */\r\n resetBindGroupLayout() {\r\n this.entries.bindGroupLayout = []\r\n this.bindings.forEach((binding) => {\r\n this.entries.bindGroupLayout.push({\r\n binding: this.entries.bindGroupLayout.length,\r\n ...binding.resourceLayout,\r\n visibility: binding.visibility,\r\n })\r\n })\r\n\r\n this.setBindGroupLayout()\r\n }\r\n\r\n /**\r\n * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been lost to prepare everything for restoration\r\n */\r\n loseContext() {\r\n this.resetEntries()\r\n\r\n this.bufferBindings.forEach((binding) => {\r\n binding.buffer = null\r\n\r\n if ('resultBuffer' in binding) {\r\n binding.resultBuffer = null\r\n }\r\n })\r\n\r\n this.bindGroup = null\r\n this.bindGroupLayout = null\r\n this.needsPipelineFlush = true\r\n }\r\n\r\n /**\r\n * Get all {@link BindGroup#bindings | bind group bindings} that handle a {@link GPUBuffer}\r\n */\r\n get bufferBindings(): BindGroupBufferBindingElement[] {\r\n return this.bindings.filter(\r\n (binding) => binding instanceof BufferBinding || binding instanceof WritableBufferBinding\r\n ) as BindGroupBufferBindingElement[]\r\n }\r\n\r\n /**\r\n * Creates binding GPUBuffer with correct params\r\n * @param binding - the binding element\r\n */\r\n createBindingBuffer(binding: BindGroupBufferBindingElement) {\r\n // TODO user defined usage?\r\n // [Kangz](https://github.com/Kangz) said:\r\n // \"In general though COPY_SRC/DST is free (at least in Dawn / Chrome because we add it all the time for our own purpose).\"\r\n binding.buffer = this.renderer.createBuffer({\r\n label: this.options.label + ': ' + binding.bindingType + ' buffer from: ' + binding.label,\r\n size: binding.arrayBuffer.byteLength,\r\n usage:\r\n binding.bindingType === 'uniform'\r\n ? GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC | GPUBufferUsage.VERTEX\r\n : GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC | GPUBufferUsage.VERTEX,\r\n })\r\n\r\n if ('resultBuffer' in binding) {\r\n binding.resultBuffer = this.renderer.createBuffer({\r\n label: this.options.label + ': Result buffer from: ' + binding.label,\r\n size: binding.arrayBuffer.byteLength,\r\n usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Fill in our entries bindGroupLayout and bindGroup arrays with the correct binding resources.\r\n * For buffer struct, create a GPUBuffer first if needed\r\n */\r\n fillEntries() {\r\n this.bindings.forEach((binding) => {\r\n // if no visibility specified, just set it to the maximum default capabilities\r\n if (!binding.visibility) {\r\n binding.visibility = GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE\r\n }\r\n\r\n // if it's a buffer binding, create the GPUBuffer\r\n if ('buffer' in binding && !binding.buffer) {\r\n this.createBindingBuffer(binding)\r\n }\r\n\r\n // now that everything is ready, fill our entries\r\n this.entries.bindGroupLayout.push({\r\n binding: this.entries.bindGroupLayout.length,\r\n ...binding.resourceLayout,\r\n visibility: binding.visibility,\r\n })\r\n\r\n this.entries.bindGroup.push({\r\n binding: this.entries.bindGroup.length,\r\n resource: binding.resource,\r\n })\r\n })\r\n }\r\n\r\n /**\r\n * Get a bind group binding by name/key\r\n * @param bindingName - the binding name or key\r\n * @returns - the found binding, or null if not found\r\n */\r\n getBindingByName(bindingName = ''): BindGroupBindingElement | null {\r\n return this.bindings.find((binding) => binding.name === bindingName)\r\n }\r\n\r\n /**\r\n * Create a GPUBindGroupLayout and set our {@link bindGroupLayout}\r\n */\r\n setBindGroupLayout() {\r\n this.bindGroupLayout = this.renderer.createBindGroupLayout({\r\n label: this.options.label + ' layout',\r\n entries: this.entries.bindGroupLayout,\r\n })\r\n }\r\n\r\n /**\r\n * Create a GPUBindGroup and set our {@link bindGroup}\r\n */\r\n setBindGroup() {\r\n this.bindGroup = this.renderer.createBindGroup({\r\n label: this.options.label,\r\n layout: this.bindGroupLayout,\r\n entries: this.entries.bindGroup,\r\n })\r\n }\r\n\r\n /**\r\n * Check whether we should update (write) our {@link GPUBuffer} or not.\r\n */\r\n updateBufferBindings() {\r\n this.bufferBindings.forEach((binding, index) => {\r\n // update binding elements\r\n binding.update()\r\n\r\n // now write to the GPUBuffer if needed\r\n if (binding.shouldUpdate) {\r\n // bufferOffset is always equals to 0 in our case\r\n if (!binding.useStruct && binding.bufferElements.length > 1) {\r\n // we're in a non struct buffer binding with multiple entries\r\n // that should not happen but that way we're covered\r\n this.renderer.queueWriteBuffer(binding.buffer, 0, binding.bufferElements[index].view)\r\n } else {\r\n this.renderer.queueWriteBuffer(binding.buffer, 0, binding.arrayBuffer)\r\n }\r\n }\r\n\r\n // reset update flag\r\n binding.shouldUpdate = false\r\n })\r\n }\r\n\r\n /**\r\n * Update the {@link BindGroup}, which means update its {@link BindGroup#bufferBindings | buffer bindings} and {@link BindGroup#resetBindGroup | reset it} if needed.\r\n * Called at each render from the parentMesh {@link core/materials/Material.Material | material}\r\n */\r\n update() {\r\n this.updateBufferBindings()\r\n\r\n const needBindGroupReset = this.bindings.some((binding) => binding.shouldResetBindGroup)\r\n const needBindGroupLayoutReset = this.bindings.some((binding) => binding.shouldResetBindGroupLayout)\r\n\r\n // since other bind groups might be using that binding\r\n // wait for the end of the render loop to reset the bindings flags\r\n if (needBindGroupReset || needBindGroupLayoutReset) {\r\n this.renderer.onAfterCommandEncoderSubmission.add(\r\n () => {\r\n this.bindings.forEach((binding) => {\r\n binding.shouldResetBindGroup = false\r\n binding.shouldResetBindGroupLayout = false\r\n })\r\n },\r\n { once: true }\r\n )\r\n }\r\n\r\n if (needBindGroupLayoutReset) {\r\n this.resetBindGroupLayout()\r\n // bind group layout has changed, we have to recreate the pipeline\r\n this.needsPipelineFlush = true\r\n }\r\n\r\n if (needBindGroupReset) {\r\n this.resetBindGroup()\r\n }\r\n }\r\n\r\n /**\r\n * Clones a {@link BindGroup} from a list of {@link bindings}\r\n * Useful to create a new bind group with already created buffers, but swapped\r\n * @param parameters - parameters to use for cloning\r\n * @param parameters.bindings - our input {@link bindings}\r\n * @param [parameters.keepLayout=false] - whether we should keep original {@link bindGroupLayout} or not\r\n * @returns - the cloned {@link BindGroup}\r\n */\r\n clone({\r\n bindings = [],\r\n keepLayout = false,\r\n }: {\r\n bindings?: BindGroupBindingElement[]\r\n keepLayout?: boolean\r\n } = {}): AllowedBindGroups {\r\n const params = { ...this.options }\r\n params.label += ' (copy)'\r\n\r\n const bindGroupCopy = new (this.constructor as typeof BindGroup)(this.renderer, {\r\n label: params.label,\r\n })\r\n\r\n bindGroupCopy.setIndex(this.index)\r\n bindGroupCopy.options = params\r\n\r\n const bindingsRef = bindings.length ? bindings : this.bindings\r\n\r\n bindingsRef.forEach((binding, index) => {\r\n bindGroupCopy.addBinding(binding)\r\n\r\n // if it's a buffer binding without a GPUBuffer, create it now\r\n if ('buffer' in binding && !binding.buffer) {\r\n bindGroupCopy.createBindingBuffer(binding)\r\n }\r\n\r\n // if we should create a new bind group layout, fill it\r\n if (!keepLayout) {\r\n bindGroupCopy.entries.bindGroupLayout.push({\r\n binding: bindGroupCopy.entries.bindGroupLayout.length,\r\n ...binding.resourceLayout,\r\n visibility: binding.visibility,\r\n })\r\n }\r\n\r\n bindGroupCopy.entries.bindGroup.push({\r\n binding: bindGroupCopy.entries.bindGroup.length,\r\n resource: binding.resource,\r\n } as GPUBindGroupEntry)\r\n })\r\n\r\n // if we should copy the given bind group layout\r\n if (keepLayout) {\r\n bindGroupCopy.entries.bindGroupLayout = [...this.entries.bindGroupLayout]\r\n }\r\n\r\n bindGroupCopy.setBindGroupLayout()\r\n bindGroupCopy.setBindGroup()\r\n\r\n return bindGroupCopy\r\n }\r\n\r\n /**\r\n * Destroy our {@link BindGroup}\r\n * Most important is to destroy the GPUBuffers to free the memory\r\n */\r\n destroy() {\r\n this.renderer.removeBindGroup(this)\r\n\r\n this.bufferBindings.forEach((binding) => {\r\n if ('buffer' in binding) {\r\n this.renderer.removeBuffer(binding.buffer)\r\n binding.buffer?.destroy()\r\n binding.buffer = null\r\n }\r\n\r\n if ('resultBuffer' in binding) {\r\n this.renderer.removeBuffer(binding.resultBuffer)\r\n binding.resultBuffer?.destroy()\r\n binding.resultBuffer = null\r\n }\r\n })\r\n\r\n this.bindings = []\r\n this.bindGroupLayout = null\r\n this.bindGroup = null\r\n this.resetEntries()\r\n }\r\n}\r\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/bindGroups/TextureBindGroup.mjs b/dist/esm/core/bindGroups/TextureBindGroup.mjs new file mode 100644 index 000000000..a85a0e251 --- /dev/null +++ b/dist/esm/core/bindGroups/TextureBindGroup.mjs @@ -0,0 +1,103 @@ +import { BindGroup } from './BindGroup.mjs'; +import { isRenderer } from '../renderers/utils.mjs'; +import { Texture } from '../textures/Texture.mjs'; + +class TextureBindGroup extends BindGroup { + /** + * TextureBindGroup constructor + * @param renderer - a {@link Renderer} class object or a {@link GPUCurtains} class object + * @param parameters - {@link TextureBindGroupParams | parameters} used to create our {@link TextureBindGroup} + */ + constructor(renderer, { label, index = 0, bindings = [], uniforms, storages, textures = [], samplers = [] } = {}) { + const type = "TextureBindGroup"; + renderer = renderer && renderer.renderer || renderer; + isRenderer(renderer, type); + super(renderer, { label, index, bindings, uniforms, storages }); + this.options = { + ...this.options, + // will be filled after + textures: [], + samplers: [] + }; + if (textures.length) { + textures.forEach((texture) => this.addTexture(texture)); + } + if (samplers.length) { + samplers.forEach((sampler) => this.addSampler(sampler)); + } + this.type = type; + } + /** + * Adds a texture to the textures array and the struct + * @param texture - texture to add + */ + addTexture(texture) { + this.textures.push(texture); + this.addBindings([...texture.bindings]); + } + /** + * Get the current textures array + * @readonly + */ + get textures() { + return this.options.textures; + } + /** + * Adds a sampler to the samplers array and the struct + * @param sampler + */ + addSampler(sampler) { + this.samplers.push(sampler); + this.addBindings([sampler.binding]); + } + /** + * Get the current samplers array + * @readonly + */ + get samplers() { + return this.options.samplers; + } + /** + * Get whether the GPU bind group is ready to be created + * It can be created if it has {@link BindGroup#bindings} and has not been created yet and all GPU textures and samplers are created + * @readonly + */ + get shouldCreateBindGroup() { + return !this.bindGroup && !!this.bindings.length && !this.textures.find((texture) => !(texture.texture || texture.externalTexture)) && !this.samplers.find((sampler) => !sampler.sampler); + } + /** + * Update the {@link TextureBindGroup#textures | bind group textures}: + * - Check if they need to copy their source texture + * - Upload video texture if needed + */ + updateTextures() { + this.textures.forEach((texture) => { + if (texture instanceof Texture) { + if (texture.options.fromTexture && texture.options.fromTexture.sourceUploaded && !texture.sourceUploaded) { + texture.copy(texture.options.fromTexture); + } + if (texture.shouldUpdate && texture.options.sourceType && texture.options.sourceType === "externalVideo") { + texture.uploadVideoTexture(); + } + } + }); + } + /** + * Update the {@link TextureBindGroup}, which means update its {@link TextureBindGroup#textures | textures}, then update its {@link TextureBindGroup#bufferBindings | buffer bindings} and finally {@link TextureBindGroup#resetBindGroup | reset it} if needed + */ + update() { + this.updateTextures(); + super.update(); + } + /** + * Destroy our {@link TextureBindGroup} + */ + destroy() { + super.destroy(); + this.options.textures = []; + this.options.samplers = []; + } +} + +export { TextureBindGroup }; +//# sourceMappingURL=TextureBindGroup.mjs.map diff --git a/dist/esm/core/bindGroups/TextureBindGroup.mjs.map b/dist/esm/core/bindGroups/TextureBindGroup.mjs.map new file mode 100644 index 000000000..450d5de5b --- /dev/null +++ b/dist/esm/core/bindGroups/TextureBindGroup.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"TextureBindGroup.mjs","sources":["../../../../src/core/bindGroups/TextureBindGroup.ts"],"sourcesContent":["import { BindGroup } from './BindGroup'\nimport { isRenderer, Renderer } from '../renderers/utils'\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\nimport { Texture } from '../textures/Texture'\nimport { Sampler } from '../samplers/Sampler'\nimport { BindGroupParams } from '../../types/BindGroups'\nimport { MaterialTexture } from '../../types/Materials'\n\n/**\n * An object defining all possible {@link TextureBindGroup} class instancing parameters\n */\nexport interface TextureBindGroupParams extends BindGroupParams {\n /** array of {@link MaterialTexture | textures} to add to a {@link TextureBindGroup} */\n textures?: MaterialTexture[]\n /** array of {@link Sampler} to add to a {@link TextureBindGroup} */\n samplers?: Sampler[]\n}\n\n/**\n * Used to regroup all {@link types/BindGroups.BindGroupBindingElement | bindings} related to textures (texture, texture matrices buffers and samplers) into one single specific {@link BindGroup}.\n *\n * Also responsible for uploading video textures if needed.\n *\n * @example\n * ```javascript\n * // set our main GPUCurtains instance\n * const gpuCurtains = new GPUCurtains({\n * container: '#canvas' // selector of our WebGPU canvas container\n * })\n *\n * // set the GPU device\n * // note this is asynchronous\n * await gpuCurtains.setDevice()\n *\n * // create a render texture\n * const renderTexture = new RenderTexture(gpuCurtains, {\n * label: 'Input texture',\n * name: 'inputTexture',\n * })\n *\n * // create a texture bind group using that render texture\n * const textureBindGroup = new TextureBindGroup(gpuCurtains, {\n * label: 'My texture bind group',\n * textures: [renderTexture],\n * uniforms: {\n * params: {\n * struct: {\n * opacity: {\n * type: 'f32',\n * value: 1,\n * },\n * mousePosition: {\n * type: 'vec2f',\n * value: new Vec2(),\n * },\n * },\n * },\n * },\n * })\n *\n * // create the GPU buffer, bindGroupLayout and bindGroup\n * textureBindGroup.createBindGroup()\n * ```\n */\nexport class TextureBindGroup extends BindGroup {\n /**\n * TextureBindGroup constructor\n * @param renderer - a {@link Renderer} class object or a {@link GPUCurtains} class object\n * @param parameters - {@link TextureBindGroupParams | parameters} used to create our {@link TextureBindGroup}\n */\n constructor(\n renderer: Renderer | GPUCurtains,\n { label, index = 0, bindings = [], uniforms, storages, textures = [], samplers = [] }: TextureBindGroupParams = {}\n ) {\n const type = 'TextureBindGroup'\n\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\n\n isRenderer(renderer, type)\n\n super(renderer, { label, index, bindings, uniforms, storages })\n\n this.options = {\n ...this.options,\n // will be filled after\n textures: [],\n samplers: [],\n }\n\n // add initial textures if any\n if (textures.length) {\n textures.forEach((texture) => this.addTexture(texture))\n }\n\n // add initial samplers if any\n if (samplers.length) {\n samplers.forEach((sampler) => this.addSampler(sampler))\n }\n\n this.type = type\n }\n\n /**\n * Adds a texture to the textures array and the struct\n * @param texture - texture to add\n */\n addTexture(texture: MaterialTexture) {\n this.textures.push(texture)\n this.addBindings([...texture.bindings])\n }\n\n /**\n * Get the current textures array\n * @readonly\n */\n get textures(): MaterialTexture[] {\n return this.options.textures\n }\n\n /**\n * Adds a sampler to the samplers array and the struct\n * @param sampler\n */\n addSampler(sampler: Sampler) {\n this.samplers.push(sampler)\n this.addBindings([sampler.binding])\n }\n\n /**\n * Get the current samplers array\n * @readonly\n */\n get samplers(): Sampler[] {\n return this.options.samplers\n }\n\n /**\n * Get whether the GPU bind group is ready to be created\n * It can be created if it has {@link BindGroup#bindings} and has not been created yet and all GPU textures and samplers are created\n * @readonly\n */\n get shouldCreateBindGroup(): boolean {\n return (\n !this.bindGroup &&\n !!this.bindings.length &&\n !this.textures.find((texture) => !(texture.texture || (texture as Texture).externalTexture)) &&\n !this.samplers.find((sampler) => !sampler.sampler)\n )\n }\n\n /**\n * Update the {@link TextureBindGroup#textures | bind group textures}:\n * - Check if they need to copy their source texture\n * - Upload video texture if needed\n */\n updateTextures() {\n this.textures.forEach((texture) => {\n // copy textures that need it on first init, but only when original texture is ready\n if (texture instanceof Texture) {\n if (texture.options.fromTexture && texture.options.fromTexture.sourceUploaded && !texture.sourceUploaded) {\n texture.copy(texture.options.fromTexture)\n }\n\n if (texture.shouldUpdate && texture.options.sourceType && texture.options.sourceType === 'externalVideo') {\n texture.uploadVideoTexture()\n }\n }\n })\n }\n\n /**\n * Update the {@link TextureBindGroup}, which means update its {@link TextureBindGroup#textures | textures}, then update its {@link TextureBindGroup#bufferBindings | buffer bindings} and finally {@link TextureBindGroup#resetBindGroup | reset it} if needed\n */\n update() {\n this.updateTextures()\n super.update()\n }\n\n /**\n * Destroy our {@link TextureBindGroup}\n */\n destroy() {\n super.destroy()\n this.options.textures = []\n this.options.samplers = []\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/bindings/Binding.mjs b/dist/esm/core/bindings/Binding.mjs new file mode 100644 index 000000000..cec2014cf --- /dev/null +++ b/dist/esm/core/bindings/Binding.mjs @@ -0,0 +1,36 @@ +import { toCamelCase } from '../../utils/utils.mjs'; + +class Binding { + /** + * Binding constructor + * @param parameters - {@link BindingParams | parameters} used to create our {@link Binding} + */ + constructor({ label = "Uniform", name = "uniform", bindingType = "uniform", visibility }) { + this.label = label; + this.name = toCamelCase(name); + this.bindingType = bindingType; + this.visibility = visibility ? (() => { + switch (visibility) { + case "vertex": + return GPUShaderStage.VERTEX; + case "fragment": + return GPUShaderStage.FRAGMENT; + case "compute": + return GPUShaderStage.COMPUTE; + default: + return GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE; + } + })() : GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE; + this.options = { + label, + name, + bindingType, + visibility + }; + this.shouldResetBindGroup = false; + this.shouldResetBindGroupLayout = false; + } +} + +export { Binding }; +//# sourceMappingURL=Binding.mjs.map diff --git a/dist/esm/core/bindings/Binding.mjs.map b/dist/esm/core/bindings/Binding.mjs.map new file mode 100644 index 000000000..c5f59e326 --- /dev/null +++ b/dist/esm/core/bindings/Binding.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"Binding.mjs","sources":["../../../../src/core/bindings/Binding.ts"],"sourcesContent":["import { toCamelCase } from '../../utils/utils'\nimport { MaterialShadersType } from '../../types/Materials'\nimport { TextureBinding } from './TextureBinding'\nimport { SamplerBinding } from './SamplerBinding'\n\n/** Defines all kind of texture binding types */\nexport type TextureBindingType = 'texture' | 'externalTexture' | 'storage' | 'depth'\n/** Defines all kind of binding types */\nexport type BindingType = 'uniform' | 'storage' | TextureBindingType | 'sampler'\n\n// see https://www.w3.org/TR/WGSL/#memory-access-mode\n/** Defines buffer binding memory access types (read only or read/write) */\nexport type BufferBindingMemoryAccessType = 'read' | 'read_write'\n/** Defines texture binding memory access types (read only, write only or read/write) */\nexport type BindingMemoryAccessType = BufferBindingMemoryAccessType | 'write'\n\n/**\n * Defines all kind of {@link Binding} that are related to textures or samplers\n */\nexport type TextureSamplerBindings = TextureBinding | SamplerBinding\n\n/**\n * An object defining all possible {@link Binding} class instancing parameters\n */\nexport interface BindingParams {\n /** {@link Binding} label */\n label?: string\n /** {@link Binding} name/key */\n name?: string\n /** {@link BindingType | binding type} to use with this {@link Binding} */\n bindingType?: BindingType\n /** {@link Binding} variables shaders visibility */\n visibility?: MaterialShadersType | null\n}\n\n/**\n * Used as a shell to build actual bindings upon, like {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}, {@link core/bindings/WritableBufferBinding.WritableBufferBinding | WritableBufferBinding}, {@link TextureBinding} and {@link SamplerBinding}.\n *\n * Ultimately the goal of a {@link Binding} element is to provide correct resources for {@link GPUBindGroupLayoutEntry} and {@link GPUBindGroupEntry}\n *\n * ## WGSL\n *\n * Each {@link Binding} creates its own WGSL code snippet variable declaration, using structured types or not.\n */\nexport class Binding {\n /** The label of the {@link Binding} */\n label: string\n /** The name/key of the {@link Binding} */\n name: string\n /** The binding type of the {@link Binding} */\n bindingType: BindingType\n /** The visibility of the {@link Binding} in the shaders */\n visibility: GPUShaderStageFlags\n /** Options used to create this {@link Binding} */\n options: BindingParams\n\n /** Flag indicating whether we should recreate the parentMesh {@link core/bindGroups/BindGroup.BindGroup#bindGroup | bind group}, usually when a resource has changed */\n shouldResetBindGroup: boolean\n /** Flag indicating whether we should recreate the parentMesh {@link core/bindGroups/BindGroup.BindGroup#bindGroupLayout | GPU bind group layout}, usually when a resource layout has changed */\n shouldResetBindGroupLayout: boolean\n\n /**\n * Binding constructor\n * @param parameters - {@link BindingParams | parameters} used to create our {@link Binding}\n */\n constructor({ label = 'Uniform', name = 'uniform', bindingType = 'uniform', visibility }: BindingParams) {\n this.label = label\n this.name = toCamelCase(name)\n this.bindingType = bindingType\n\n this.visibility = visibility\n ? (() => {\n switch (visibility) {\n case 'vertex':\n return GPUShaderStage.VERTEX\n case 'fragment':\n return GPUShaderStage.FRAGMENT\n case 'compute':\n return GPUShaderStage.COMPUTE\n default:\n return GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE\n }\n })()\n : GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE\n\n this.options = {\n label,\n name,\n bindingType,\n visibility,\n }\n\n this.shouldResetBindGroup = false\n this.shouldResetBindGroupLayout = false\n }\n}\n"],"names":[],"mappings":";;AA4CO,MAAM,OAAQ,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBnB,WAAA,CAAY,EAAE,KAAQ,GAAA,SAAA,EAAW,OAAO,SAAW,EAAA,WAAA,GAAc,SAAW,EAAA,UAAA,EAA6B,EAAA;AACvG,IAAA,IAAA,CAAK,KAAQ,GAAA,KAAA,CAAA;AACb,IAAK,IAAA,CAAA,IAAA,GAAO,YAAY,IAAI,CAAA,CAAA;AAC5B,IAAA,IAAA,CAAK,WAAc,GAAA,WAAA,CAAA;AAEnB,IAAK,IAAA,CAAA,UAAA,GAAa,cACb,MAAM;AACL,MAAA,QAAQ,UAAY;AAAA,QAClB,KAAK,QAAA;AACH,UAAA,OAAO,cAAe,CAAA,MAAA,CAAA;AAAA,QACxB,KAAK,UAAA;AACH,UAAA,OAAO,cAAe,CAAA,QAAA,CAAA;AAAA,QACxB,KAAK,SAAA;AACH,UAAA,OAAO,cAAe,CAAA,OAAA,CAAA;AAAA,QACxB;AACE,UAAA,OAAO,cAAe,CAAA,MAAA,GAAS,cAAe,CAAA,QAAA,GAAW,cAAe,CAAA,OAAA,CAAA;AAAA,OAC5E;AAAA,QAEF,GAAA,cAAA,CAAe,MAAS,GAAA,cAAA,CAAe,WAAW,cAAe,CAAA,OAAA,CAAA;AAErE,IAAA,IAAA,CAAK,OAAU,GAAA;AAAA,MACb,KAAA;AAAA,MACA,IAAA;AAAA,MACA,WAAA;AAAA,MACA,UAAA;AAAA,KACF,CAAA;AAEA,IAAA,IAAA,CAAK,oBAAuB,GAAA,KAAA,CAAA;AAC5B,IAAA,IAAA,CAAK,0BAA6B,GAAA,KAAA,CAAA;AAAA,GACpC;AACF;;;;"} \ No newline at end of file diff --git a/dist/esm/core/bindings/BufferBinding.mjs b/dist/esm/core/bindings/BufferBinding.mjs new file mode 100644 index 000000000..2a79bece6 --- /dev/null +++ b/dist/esm/core/bindings/BufferBinding.mjs @@ -0,0 +1,283 @@ +import { Binding } from './Binding.mjs'; +import { getBindGroupLayoutBindingType, getBufferLayout, getBindingWGSLVarType } from './utils.mjs'; +import { toCamelCase, throwWarning, toKebabCase } from '../../utils/utils.mjs'; +import { Vec2 } from '../../math/Vec2.mjs'; +import { Vec3 } from '../../math/Vec3.mjs'; +import { BufferElement } from './bufferElements/BufferElement.mjs'; +import { BufferArrayElement } from './bufferElements/BufferArrayElement.mjs'; +import { BufferInterleavedArrayElement } from './bufferElements/BufferInterleavedArrayElement.mjs'; + +class BufferBinding extends Binding { + /** + * BufferBinding constructor + * @param parameters - {@link BufferBindingParams | parameters} used to create our BufferBindings + */ + constructor({ + label = "Uniform", + name = "uniform", + bindingType, + visibility, + useStruct = true, + access = "read", + struct = {} + }) { + bindingType = bindingType ?? "uniform"; + super({ label, name, bindingType, visibility }); + this.options = { + ...this.options, + useStruct, + access, + struct + }; + this.arrayBufferSize = 0; + this.shouldUpdate = false; + this.useStruct = useStruct; + this.bufferElements = []; + this.inputs = {}; + this.buffer = null; + this.setBindings(struct); + this.setBufferAttributes(); + this.setWGSLFragment(); + } + /** + * Get {@link GPUBindGroupLayoutEntry#buffer | bind group layout entry resource} + * @readonly + */ + get resourceLayout() { + return { + buffer: { + type: getBindGroupLayoutBindingType(this) + } + }; + } + /** + * Get {@link GPUBindGroupEntry#resource | bind group resource} + * @readonly + */ + get resource() { + return { buffer: this.buffer }; + } + /** + * Format bindings struct and set our {@link inputs} + * @param bindings - bindings inputs + */ + setBindings(bindings) { + Object.keys(bindings).forEach((bindingKey) => { + const binding = {}; + for (const key in bindings[bindingKey]) { + if (key !== "value") { + binding[key] = bindings[bindingKey][key]; + } + } + binding.name = bindings[bindingKey].name ?? bindingKey; + Object.defineProperty(binding, "value", { + get() { + return binding._value; + }, + set(v) { + binding._value = v; + binding.shouldUpdate = true; + } + }); + binding.value = bindings[bindingKey].value; + if (binding.value instanceof Vec2 || binding.value instanceof Vec3) { + binding.value.onChange(() => binding.shouldUpdate = true); + } + this.inputs[bindingKey] = binding; + }); + } + /** + * Set our buffer attributes: + * Takes all the {@link inputs} and adds them to the {@link bufferElements} array with the correct start and end offsets (padded), then fill our {@link arrayBuffer} typed array accordingly. + */ + setBufferAttributes() { + const arrayBindings = Object.keys(this.inputs).filter( + (bindingKey) => this.inputs[bindingKey].type.indexOf("array") !== -1 + ); + let orderedBindings = Object.keys(this.inputs).sort((bindingKeyA, bindingKeyB) => { + const isBindingAArray = Math.min(0, this.inputs[bindingKeyA].type.indexOf("array")); + const isBindingBArray = Math.min(0, this.inputs[bindingKeyB].type.indexOf("array")); + return isBindingAArray - isBindingBArray; + }); + if (arrayBindings.length > 1) { + orderedBindings = orderedBindings.filter((bindingKey) => !arrayBindings.includes(bindingKey)); + } + orderedBindings.forEach((bindingKey) => { + const binding = this.inputs[bindingKey]; + const bufferElementOptions = { + name: toCamelCase(binding.name ?? bindingKey), + key: bindingKey, + type: binding.type + }; + const isArray = binding.type.indexOf("array") !== -1 && (Array.isArray(binding.value) || ArrayBuffer.isView(binding.value)); + this.bufferElements.push( + isArray ? new BufferArrayElement({ + ...bufferElementOptions, + arrayLength: binding.value.length + }) : new BufferElement(bufferElementOptions) + ); + }); + this.bufferElements.forEach((bufferElement, index) => { + const startOffset = index === 0 ? 0 : this.bufferElements[index - 1].endOffset + 1; + bufferElement.setAlignment(startOffset); + }); + if (arrayBindings.length > 1) { + const arraySizes = arrayBindings.map((bindingKey) => { + const binding = this.inputs[bindingKey]; + const bufferLayout = getBufferLayout(binding.type.replace("array", "").replace("<", "").replace(">", "")); + return binding.value.length / bufferLayout.numElements; + }); + const equalSize = arraySizes.every((size, i, array) => size === array[0]); + if (equalSize) { + const interleavedBufferElements = arrayBindings.map((bindingKey) => { + const binding = this.inputs[bindingKey]; + return new BufferInterleavedArrayElement({ + name: toCamelCase(binding.name ?? bindingKey), + key: bindingKey, + type: binding.type, + arrayLength: binding.value.length + }); + }); + const tempBufferElements = arrayBindings.map((bindingKey) => { + const binding = this.inputs[bindingKey]; + return new BufferElement({ + name: toCamelCase(binding.name ?? bindingKey), + key: bindingKey, + type: binding.type.replace("array", "").replace("<", "").replace(">", "") + }); + }); + tempBufferElements.forEach((bufferElement, index) => { + if (index === 0) { + if (this.bufferElements.length) { + bufferElement.setAlignmentFromPosition({ + row: this.bufferElements[this.bufferElements.length - 1].alignment.end.row + 1, + byte: 0 + }); + } else { + bufferElement.setAlignment(0); + } + } else { + bufferElement.setAlignment(tempBufferElements[index - 1].endOffset + 1); + } + }); + const totalStride = tempBufferElements[tempBufferElements.length - 1].endOffset + 1 - tempBufferElements[0].startOffset; + interleavedBufferElements.forEach((bufferElement, index) => { + bufferElement.setAlignment(tempBufferElements[index].startOffset, totalStride); + }); + this.bufferElements = [...this.bufferElements, ...interleavedBufferElements]; + } else { + throwWarning( + `BufferBinding: "${this.label}" contains multiple array inputs that should use an interleaved array, but their sizes do not match. These inputs cannot be added to the BufferBinding: "${arrayBindings.join( + ", " + )}"` + ); + } + } + this.arrayBufferSize = this.bufferElements.length ? this.bufferElements[this.bufferElements.length - 1].paddedByteCount : 0; + this.arrayBuffer = new ArrayBuffer(this.arrayBufferSize); + this.arrayView = new DataView(this.arrayBuffer, 0, this.arrayBuffer.byteLength); + this.bufferElements.forEach((bufferElement) => { + bufferElement.setView(this.arrayBuffer, this.arrayView); + }); + this.shouldUpdate = this.arrayBufferSize > 0; + } + /** + * Set the WGSL code snippet to append to the shaders code. It consists of variable (and Struct structures if needed) declarations. + */ + setWGSLFragment() { + const kebabCaseLabel = toKebabCase(this.label); + if (this.useStruct) { + const bufferElements = this.bufferElements.filter( + (bufferElement) => !(bufferElement instanceof BufferInterleavedArrayElement) + ); + const interleavedBufferElements = this.bufferElements.filter( + (bufferElement) => bufferElement instanceof BufferInterleavedArrayElement + ); + if (interleavedBufferElements.length) { + const arrayLength = this.bindingType === "uniform" ? `, ${interleavedBufferElements[0].numElements}` : ""; + if (bufferElements.length) { + this.wgslStructFragment = `struct ${kebabCaseLabel}Element { + ${interleavedBufferElements.map((binding) => binding.name + ": " + binding.type.replace("array", "").replace("<", "").replace(">", "")).join(",\n ")} +}; + +`; + const interleavedBufferStructDeclaration = `${this.name}Element: array<${kebabCaseLabel}Element${arrayLength}>,`; + this.wgslStructFragment += `struct ${kebabCaseLabel} { + ${bufferElements.map((bufferElement) => bufferElement.name + ": " + bufferElement.type).join(",\n ")} + ${interleavedBufferStructDeclaration} +};`; + const varType = getBindingWGSLVarType(this); + this.wgslGroupFragment = [`${varType} ${this.name}: ${kebabCaseLabel};`]; + } else { + this.wgslStructFragment = `struct ${kebabCaseLabel} { + ${this.bufferElements.map((binding) => binding.name + ": " + binding.type.replace("array", "").replace("<", "").replace(">", "")).join(",\n ")} +};`; + const varType = getBindingWGSLVarType(this); + this.wgslGroupFragment = [`${varType} ${this.name}: array<${kebabCaseLabel}${arrayLength}>;`]; + } + } else { + this.wgslStructFragment = `struct ${kebabCaseLabel} { + ${this.bufferElements.map((binding) => { + const bindingType = this.bindingType === "uniform" && "numElements" in binding ? `array<${binding.type.replace("array", "").replace("<", "").replace(">", "")}, ${binding.numElements}>` : binding.type; + return binding.name + ": " + bindingType; + }).join(",\n ")} +};`; + const varType = getBindingWGSLVarType(this); + this.wgslGroupFragment = [`${varType} ${this.name}: ${kebabCaseLabel};`]; + } + } else { + this.wgslStructFragment = ""; + this.wgslGroupFragment = this.bufferElements.map((binding) => { + const varType = getBindingWGSLVarType(this); + return `${varType} ${binding.name}: ${binding.type};`; + }); + } + } + /** + * Set a binding shouldUpdate flag to true to update our {@link arrayBuffer} array during next render. + * @param bindingName - the binding name/key to update + */ + shouldUpdateBinding(bindingName = "") { + const bindingKey = Object.keys(this.inputs).find((bindingKey2) => this.inputs[bindingKey2].name === bindingName); + if (bindingKey) + this.inputs[bindingKey].shouldUpdate = true; + } + /** + * Executed at the beginning of a Material render call. + * If any of the {@link inputs} has changed, run its onBeforeUpdate callback then updates our {@link arrayBuffer} array. + * Also sets the {@link shouldUpdate} property to true so the {@link core/bindGroups/BindGroup.BindGroup | BindGroup} knows it will need to update the {@link GPUBuffer}. + */ + update() { + Object.keys(this.inputs).forEach((bindingKey) => { + const binding = this.inputs[bindingKey]; + const bufferElement = this.bufferElements.find((bufferEl) => bufferEl.key === bindingKey); + if (binding.shouldUpdate && bufferElement) { + binding.onBeforeUpdate && binding.onBeforeUpdate(); + bufferElement.update(binding.value); + this.shouldUpdate = true; + binding.shouldUpdate = false; + } + }); + } + /** + * Extract the data corresponding to a specific {@link BufferElement} from a {@link Float32Array} holding the {@link BufferBinding#buffer | GPU buffer} data of this {@link BufferBinding} + * @param parameters - parameters used to extract the data + * @param parameters.result - {@link Float32Array} holding {@link GPUBuffer} data + * @param parameters.bufferElementName - name of the {@link BufferElement} to use to extract the data + * @returns - extracted data from the {@link Float32Array} + */ + extractBufferElementDataFromBufferResult({ + result, + bufferElementName + }) { + const bufferElement = this.bufferElements.find((bufferElement2) => bufferElement2.name === bufferElementName); + if (bufferElement) { + return bufferElement.extractDataFromBufferResult(result); + } else { + return result; + } + } +} + +export { BufferBinding }; +//# sourceMappingURL=BufferBinding.mjs.map diff --git a/dist/esm/core/bindings/BufferBinding.mjs.map b/dist/esm/core/bindings/BufferBinding.mjs.map new file mode 100644 index 000000000..dd96a4362 --- /dev/null +++ b/dist/esm/core/bindings/BufferBinding.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"BufferBinding.mjs","sources":["../../../../src/core/bindings/BufferBinding.ts"],"sourcesContent":["import { Binding, BindingParams, BufferBindingMemoryAccessType } from './Binding'\nimport { getBindGroupLayoutBindingType, getBindingWGSLVarType, getBufferLayout, TypedArray } from './utils'\nimport { throwWarning, toCamelCase, toKebabCase } from '../../utils/utils'\nimport { Vec2 } from '../../math/Vec2'\nimport { Vec3 } from '../../math/Vec3'\nimport { Input, InputBase, InputValue } from '../../types/BindGroups'\nimport { BufferElement } from './bufferElements/BufferElement'\nimport { BufferArrayElement } from './bufferElements/BufferArrayElement'\nimport { BufferInterleavedArrayElement } from './bufferElements/BufferInterleavedArrayElement'\n\n/**\n * Defines a {@link BufferBinding} input object that can set a value and run a callback function when this happens\n */\nexport interface BufferBindingInput extends InputBase {\n /** Original {@link InputValue | input value} */\n _value: InputValue\n\n /** Get the {@link InputValue | input value} */\n get value(): InputValue\n\n /** Set the {@link InputValue | input value} */\n set value(value: InputValue)\n\n /** Whether the {@link InputValue | input value} has changed and we should update the {@link BufferBinding#arrayBuffer | buffer binding array} */\n shouldUpdate: boolean\n}\n\n/**\n * Base parameters used to create a {@link BufferBinding}\n */\nexport interface BufferBindingBaseParams {\n /** Whether this {@link BufferBinding} should use structured WGSL variables */\n useStruct?: boolean\n /** {@link BufferBinding} memory access types (read only or read/write) */\n access?: BufferBindingMemoryAccessType\n /** Object containing one or multiple {@link Input | inputs} describing the structure of the {@link BufferBinding} */\n struct?: Record\n}\n\n/**\n * Parameters used to create a {@link BufferBinding}\n */\nexport interface BufferBindingParams extends BindingParams, BufferBindingBaseParams {}\n\n/** All allowed {@link BufferElement | buffer elements} */\nexport type AllowedBufferElement = BufferElement | BufferArrayElement | BufferInterleavedArrayElement\n\n/**\n * Used to format {@link BufferBindingParams#struct | uniforms or storages struct inputs} and create a single typed array that will hold all those inputs values. The array needs to be correctly padded depending on every value type, so it can be safely used as a GPUBuffer input.
\n * It will also create WGSL Structs and variables according to the BufferBindings inputs parameters.
\n * The WGSL structs and variables declaration may vary based on the input types, especially if there's one or more arrays involved (i.e. `array`, `array` etc.).\n *\n * @example\n * ```javascript\n * // create a GPU buffer binding\n * const bufferBinding = new BufferBinding({\n * name: 'params', // name of the WGSL object\n * bindingType: 'uniform', // should be 'storage' for large arrays\n * struct: {\n * opacity: {\n * type: 'f32',\n * value: 1,\n * },\n * mousePosition: {\n * type: 'vec2f',\n * value: new Vec2(),\n * },\n * },\n * })\n * ```\n */\nexport class BufferBinding extends Binding {\n /** Flag to indicate whether this {@link BufferBinding} {@link bufferElements | buffer elements} should be packed in a single structured object or if each one of them should be a separate binding. */\n useStruct: boolean\n /** All the {@link BufferBinding} data inputs */\n inputs: Record\n\n /** Flag to indicate whether one of the {@link inputs} value has changed and we need to update the GPUBuffer linked to the {@link arrayBuffer} array */\n shouldUpdate: boolean\n\n /** An array describing how each corresponding {@link inputs} should be inserted into our {@link arrayView} array */\n bufferElements: AllowedBufferElement[]\n\n /** Total size of our {@link arrayBuffer} array in bytes */\n arrayBufferSize: number\n /** Array buffer that will be sent to the {@link GPUBuffer} */\n arrayBuffer: ArrayBuffer\n /** Data view of our {@link arrayBuffer | array buffer} */\n arrayView: DataView\n\n /** The GPUBuffer */\n buffer: GPUBuffer | null\n\n /** A string to append to our shaders code describing the WGSL structure representing this {@link BufferBinding} */\n wgslStructFragment: string\n /** An array of strings to append to our shaders code declaring all the WGSL variables representing this {@link BufferBinding} */\n wgslGroupFragment: string[]\n /** Options used to create this {@link BufferBinding} */\n options: BufferBindingParams\n\n /**\n * BufferBinding constructor\n * @param parameters - {@link BufferBindingParams | parameters} used to create our BufferBindings\n */\n constructor({\n label = 'Uniform',\n name = 'uniform',\n bindingType,\n visibility,\n useStruct = true,\n access = 'read',\n struct = {},\n }: BufferBindingParams) {\n bindingType = bindingType ?? 'uniform'\n\n super({ label, name, bindingType, visibility })\n\n this.options = {\n ...this.options,\n useStruct,\n access,\n struct: struct,\n }\n\n this.arrayBufferSize = 0\n\n this.shouldUpdate = false\n this.useStruct = useStruct\n\n this.bufferElements = []\n this.inputs = {}\n this.buffer = null\n\n this.setBindings(struct)\n this.setBufferAttributes()\n this.setWGSLFragment()\n }\n\n /**\n * Get {@link GPUBindGroupLayoutEntry#buffer | bind group layout entry resource}\n * @readonly\n */\n get resourceLayout(): {\n /** {@link GPUBindGroupLayout | bind group layout} resource */\n buffer: GPUBufferBindingLayout\n } {\n return {\n buffer: {\n type: getBindGroupLayoutBindingType(this),\n },\n }\n }\n\n /**\n * Get {@link GPUBindGroupEntry#resource | bind group resource}\n * @readonly\n */\n get resource(): {\n /** {@link GPUBindGroup | bind group} resource */\n buffer: GPUBuffer | null\n } {\n return { buffer: this.buffer }\n }\n\n /**\n * Format bindings struct and set our {@link inputs}\n * @param bindings - bindings inputs\n */\n setBindings(bindings: Record) {\n Object.keys(bindings).forEach((bindingKey) => {\n const binding = {} as BufferBindingInput\n\n for (const key in bindings[bindingKey]) {\n if (key !== 'value') {\n binding[key] = bindings[bindingKey][key]\n }\n }\n\n // force the binding to have a name\n binding.name = bindings[bindingKey].name ?? bindingKey\n\n // define a \"value\" getter/setter so we can now when to update the buffer binding\n Object.defineProperty(binding, 'value', {\n get() {\n return binding._value\n },\n set(v) {\n binding._value = v\n binding.shouldUpdate = true\n },\n })\n\n binding.value = bindings[bindingKey].value\n\n if (binding.value instanceof Vec2 || binding.value instanceof Vec3) {\n binding.value.onChange(() => (binding.shouldUpdate = true))\n }\n\n this.inputs[bindingKey] = binding\n })\n }\n\n /**\n * Set our buffer attributes:\n * Takes all the {@link inputs} and adds them to the {@link bufferElements} array with the correct start and end offsets (padded), then fill our {@link arrayBuffer} typed array accordingly.\n */\n setBufferAttributes() {\n // early on, check if there's at least one array binding\n // If there's one and only one, put it at the end of the binding elements array, treat it as a single entry of the type, but loop on it by array.length / size to fill the alignment\n // If there's more than one, create buffer interleaved elements.\n\n // if length === 0, OK\n // if length === 1, put it at the end of our struct\n // if length > 1, create a buffer interleaved elements\n const arrayBindings = Object.keys(this.inputs).filter(\n (bindingKey) => this.inputs[bindingKey].type.indexOf('array') !== -1\n )\n\n // put the array struct at the end\n let orderedBindings = Object.keys(this.inputs).sort((bindingKeyA, bindingKeyB) => {\n // 0 if it's an array, -1 else\n const isBindingAArray = Math.min(0, this.inputs[bindingKeyA].type.indexOf('array'))\n const isBindingBArray = Math.min(0, this.inputs[bindingKeyB].type.indexOf('array'))\n\n return isBindingAArray - isBindingBArray\n })\n\n if (arrayBindings.length > 1) {\n // remove interleaved arrays from the ordered struct key array\n orderedBindings = orderedBindings.filter((bindingKey) => !arrayBindings.includes(bindingKey))\n }\n\n // handle buffer (non interleaved) elements\n orderedBindings.forEach((bindingKey) => {\n const binding = this.inputs[bindingKey]\n\n const bufferElementOptions = {\n name: toCamelCase(binding.name ?? bindingKey),\n key: bindingKey,\n type: binding.type,\n }\n\n const isArray =\n binding.type.indexOf('array') !== -1 && (Array.isArray(binding.value) || ArrayBuffer.isView(binding.value))\n\n this.bufferElements.push(\n isArray\n ? new BufferArrayElement({\n ...bufferElementOptions,\n arrayLength: (binding.value as number[]).length,\n })\n : new BufferElement(bufferElementOptions)\n )\n })\n\n // set their alignments\n this.bufferElements.forEach((bufferElement, index) => {\n const startOffset = index === 0 ? 0 : this.bufferElements[index - 1].endOffset + 1\n\n bufferElement.setAlignment(startOffset)\n })\n\n // now create our interleaved buffer elements\n if (arrayBindings.length > 1) {\n // first get the sizes of the arrays\n const arraySizes = arrayBindings.map((bindingKey) => {\n const binding = this.inputs[bindingKey]\n const bufferLayout = getBufferLayout(binding.type.replace('array', '').replace('<', '').replace('>', ''))\n\n return (binding.value as number[] | TypedArray).length / bufferLayout.numElements\n })\n\n // are they all of the same size?\n const equalSize = arraySizes.every((size, i, array) => size === array[0])\n\n if (equalSize) {\n // this will hold our interleaved buffer elements\n const interleavedBufferElements = arrayBindings.map((bindingKey) => {\n const binding = this.inputs[bindingKey]\n return new BufferInterleavedArrayElement({\n name: toCamelCase(binding.name ?? bindingKey),\n key: bindingKey,\n type: binding.type,\n arrayLength: (binding.value as number[]).length,\n })\n })\n\n // now create temp buffer elements that we'll use to fill the interleaved buffer elements alignments\n const tempBufferElements = arrayBindings.map((bindingKey) => {\n const binding = this.inputs[bindingKey]\n return new BufferElement({\n name: toCamelCase(binding.name ?? bindingKey),\n key: bindingKey,\n type: binding.type.replace('array', '').replace('<', '').replace('>', ''),\n })\n })\n\n // set temp buffer alignments as if it was regular buffer elements\n tempBufferElements.forEach((bufferElement, index) => {\n if (index === 0) {\n if (this.bufferElements.length) {\n // if there are already buffer elements\n // get last one end row, and start at the next row\n bufferElement.setAlignmentFromPosition({\n row: this.bufferElements[this.bufferElements.length - 1].alignment.end.row + 1,\n byte: 0,\n })\n } else {\n bufferElement.setAlignment(0)\n }\n } else {\n bufferElement.setAlignment(tempBufferElements[index - 1].endOffset + 1)\n }\n })\n\n // now use last temp buffer end offset as our interleaved arrayStride\n const totalStride =\n tempBufferElements[tempBufferElements.length - 1].endOffset + 1 - tempBufferElements[0].startOffset\n\n // finally, set interleaved buffer elements alignment\n interleavedBufferElements.forEach((bufferElement, index) => {\n bufferElement.setAlignment(tempBufferElements[index].startOffset, totalStride)\n })\n\n // add to our buffer elements array\n this.bufferElements = [...this.bufferElements, ...interleavedBufferElements]\n } else {\n throwWarning(\n `BufferBinding: \"${\n this.label\n }\" contains multiple array inputs that should use an interleaved array, but their sizes do not match. These inputs cannot be added to the BufferBinding: \"${arrayBindings.join(\n ', '\n )}\"`\n )\n }\n }\n\n this.arrayBufferSize = this.bufferElements.length\n ? this.bufferElements[this.bufferElements.length - 1].paddedByteCount\n : 0\n\n this.arrayBuffer = new ArrayBuffer(this.arrayBufferSize)\n this.arrayView = new DataView(this.arrayBuffer, 0, this.arrayBuffer.byteLength)\n\n this.bufferElements.forEach((bufferElement) => {\n bufferElement.setView(this.arrayBuffer, this.arrayView)\n })\n\n this.shouldUpdate = this.arrayBufferSize > 0\n }\n\n /**\n * Set the WGSL code snippet to append to the shaders code. It consists of variable (and Struct structures if needed) declarations.\n */\n setWGSLFragment() {\n const kebabCaseLabel = toKebabCase(this.label)\n\n if (this.useStruct) {\n const bufferElements = this.bufferElements.filter(\n (bufferElement) => !(bufferElement instanceof BufferInterleavedArrayElement)\n )\n const interleavedBufferElements = this.bufferElements.filter(\n (bufferElement) => bufferElement instanceof BufferInterleavedArrayElement\n ) as BufferInterleavedArrayElement[]\n\n if (interleavedBufferElements.length) {\n const arrayLength = this.bindingType === 'uniform' ? `, ${interleavedBufferElements[0].numElements}` : ''\n\n if (bufferElements.length) {\n this.wgslStructFragment = `struct ${kebabCaseLabel}Element {\\n\\t${interleavedBufferElements\n .map((binding) => binding.name + ': ' + binding.type.replace('array', '').replace('<', '').replace('>', ''))\n .join(',\\n\\t')}\n};\\n\\n`\n\n const interleavedBufferStructDeclaration = `${this.name}Element: array<${kebabCaseLabel}Element${arrayLength}>,`\n\n this.wgslStructFragment += `struct ${kebabCaseLabel} {\\n\\t${bufferElements\n .map((bufferElement) => bufferElement.name + ': ' + bufferElement.type)\n .join(',\\n\\t')}\n\\t${interleavedBufferStructDeclaration}\n};`\n\n const varType = getBindingWGSLVarType(this)\n this.wgslGroupFragment = [`${varType} ${this.name}: ${kebabCaseLabel};`]\n } else {\n this.wgslStructFragment = `struct ${kebabCaseLabel} {\\n\\t${this.bufferElements\n .map((binding) => binding.name + ': ' + binding.type.replace('array', '').replace('<', '').replace('>', ''))\n .join(',\\n\\t')}\n};`\n\n const varType = getBindingWGSLVarType(this)\n this.wgslGroupFragment = [`${varType} ${this.name}: array<${kebabCaseLabel}${arrayLength}>;`]\n }\n } else {\n this.wgslStructFragment = `struct ${kebabCaseLabel} {\\n\\t${this.bufferElements\n .map((binding) => {\n // now add array length if needed\n const bindingType =\n this.bindingType === 'uniform' && 'numElements' in binding\n ? `array<${binding.type.replace('array', '').replace('<', '').replace('>', '')}, ${\n binding.numElements\n }>`\n : binding.type\n return binding.name + ': ' + bindingType\n })\n .join(',\\n\\t')}\n};`\n\n const varType = getBindingWGSLVarType(this)\n this.wgslGroupFragment = [`${varType} ${this.name}: ${kebabCaseLabel};`]\n }\n } else {\n this.wgslStructFragment = ''\n this.wgslGroupFragment = this.bufferElements.map((binding) => {\n const varType = getBindingWGSLVarType(this)\n return `${varType} ${binding.name}: ${binding.type};`\n })\n }\n }\n\n /**\n * Set a binding shouldUpdate flag to true to update our {@link arrayBuffer} array during next render.\n * @param bindingName - the binding name/key to update\n */\n shouldUpdateBinding(bindingName = '') {\n const bindingKey = Object.keys(this.inputs).find((bindingKey) => this.inputs[bindingKey].name === bindingName)\n\n if (bindingKey) this.inputs[bindingKey].shouldUpdate = true\n }\n\n /**\n * Executed at the beginning of a Material render call.\n * If any of the {@link inputs} has changed, run its onBeforeUpdate callback then updates our {@link arrayBuffer} array.\n * Also sets the {@link shouldUpdate} property to true so the {@link core/bindGroups/BindGroup.BindGroup | BindGroup} knows it will need to update the {@link GPUBuffer}.\n */\n update() {\n Object.keys(this.inputs).forEach((bindingKey) => {\n const binding = this.inputs[bindingKey]\n const bufferElement = this.bufferElements.find((bufferEl) => bufferEl.key === bindingKey)\n\n if (binding.shouldUpdate && bufferElement) {\n binding.onBeforeUpdate && binding.onBeforeUpdate()\n // we're going to directly update the arrayBuffer from the buffer element update method\n bufferElement.update(binding.value)\n\n this.shouldUpdate = true\n binding.shouldUpdate = false\n }\n })\n }\n\n /**\n * Extract the data corresponding to a specific {@link BufferElement} from a {@link Float32Array} holding the {@link BufferBinding#buffer | GPU buffer} data of this {@link BufferBinding}\n * @param parameters - parameters used to extract the data\n * @param parameters.result - {@link Float32Array} holding {@link GPUBuffer} data\n * @param parameters.bufferElementName - name of the {@link BufferElement} to use to extract the data\n * @returns - extracted data from the {@link Float32Array}\n */\n extractBufferElementDataFromBufferResult({\n result,\n bufferElementName,\n }: {\n result: Float32Array\n bufferElementName: BufferElement['name']\n }): Float32Array {\n const bufferElement = this.bufferElements.find((bufferElement) => bufferElement.name === bufferElementName)\n if (bufferElement) {\n return bufferElement.extractDataFromBufferResult(result)\n } else {\n return result\n }\n }\n}\n"],"names":["bindingKey","bufferElement"],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/bindings/SamplerBinding.mjs b/dist/esm/core/bindings/SamplerBinding.mjs new file mode 100644 index 000000000..f531f7851 --- /dev/null +++ b/dist/esm/core/bindings/SamplerBinding.mjs @@ -0,0 +1,64 @@ +import { Binding } from './Binding.mjs'; + +class SamplerBinding extends Binding { + /** + * SamplerBinding constructor + * @param parameters - {@link SamplerBindingParams | parameters} used to create our SamplerBindings + */ + constructor({ + label = "Sampler", + name = "sampler", + bindingType, + visibility, + sampler, + type = "filtering" + }) { + bindingType = bindingType ?? "sampler"; + super({ label, name, bindingType, visibility }); + this.options = { + ...this.options, + sampler, + type + }; + this.resource = sampler; + this.setWGSLFragment(); + } + /** + * Get {@link GPUBindGroupLayoutEntry#sampler | bind group layout entry resource} + * @readonly + */ + get resourceLayout() { + return { + sampler: { + type: this.options.type + // TODO set shouldResetBindGroupLayout to true if it changes afterwards + } + }; + } + /** + * Get the {@link GPUBindGroupEntry#resource | bind group resource} + */ + get resource() { + return this.sampler; + } + /** + * Set the {@link GPUBindGroupEntry#resource | bind group resource} + * @param value - new bind group resource + */ + set resource(value) { + if (value && this.sampler) + this.shouldResetBindGroup = true; + this.sampler = value; + } + /** + * Set the correct WGSL code snippet. + */ + setWGSLFragment() { + this.wgslGroupFragment = [ + `var ${this.name}: ${this.options.type === "comparison" ? `${this.bindingType}_comparison` : this.bindingType};` + ]; + } +} + +export { SamplerBinding }; +//# sourceMappingURL=SamplerBinding.mjs.map diff --git a/dist/esm/core/bindings/SamplerBinding.mjs.map b/dist/esm/core/bindings/SamplerBinding.mjs.map new file mode 100644 index 000000000..e51671486 --- /dev/null +++ b/dist/esm/core/bindings/SamplerBinding.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"SamplerBinding.mjs","sources":["../../../../src/core/bindings/SamplerBinding.ts"],"sourcesContent":["import { Binding, BindingParams } from './Binding'\n\n/** Defines a {@link SamplerBinding} {@link SamplerBinding#resource | resource} */\nexport type SamplerBindingResource = GPUSampler | null\n\n/**\n * An object defining all possible {@link SamplerBinding} class instancing parameters\n */\nexport interface SamplerBindingParams extends BindingParams {\n /** {@link SamplerBinding} {@link GPUBindGroup | GPU bind group} resource */\n sampler: SamplerBindingResource\n /** The bind group layout binding {@link GPUSamplerBindingLayout#type | type} of this {@link GPUSampler | GPU sampler} */\n type: GPUSamplerBindingType\n}\n\n/**\n * Used to handle GPUSampler bindings.\n *\n * Provide both {@link SamplerBinding#resourceLayout | resourceLayout} and {@link SamplerBinding#resource | resource} to the {@link GPUBindGroupLayout} and {@link GPUBindGroup}.
\n * Also create the appropriate WGSL code snippet to add to the shaders.\n */\nexport class SamplerBinding extends Binding {\n /** Our {@link SamplerBinding} resource, i.e. a {@link GPUSampler} */\n sampler: SamplerBindingResource\n /** An array of strings to append to our shaders code declaring all the WGSL variables representing this {@link SamplerBinding} */\n wgslGroupFragment: string[]\n /** Options used to create this {@link SamplerBinding} */\n options: SamplerBindingParams\n\n /**\n * SamplerBinding constructor\n * @param parameters - {@link SamplerBindingParams | parameters} used to create our SamplerBindings\n */\n constructor({\n label = 'Sampler',\n name = 'sampler',\n bindingType,\n visibility,\n sampler,\n type = 'filtering',\n }: SamplerBindingParams) {\n bindingType = bindingType ?? 'sampler'\n\n super({ label, name, bindingType, visibility })\n\n this.options = {\n ...this.options,\n sampler,\n type,\n }\n\n this.resource = sampler // should be a sampler\n\n this.setWGSLFragment()\n }\n\n /**\n * Get {@link GPUBindGroupLayoutEntry#sampler | bind group layout entry resource}\n * @readonly\n */\n get resourceLayout(): {\n /** {@link GPUBindGroupLayout | bind group layout} resource */\n sampler: GPUSamplerBindingLayout\n } {\n return {\n sampler: {\n type: this.options.type, // TODO set shouldResetBindGroupLayout to true if it changes afterwards\n },\n }\n }\n\n /**\n * Get the {@link GPUBindGroupEntry#resource | bind group resource}\n */\n get resource(): SamplerBindingResource {\n return this.sampler\n }\n\n /**\n * Set the {@link GPUBindGroupEntry#resource | bind group resource}\n * @param value - new bind group resource\n */\n set resource(value: SamplerBindingResource) {\n // resource changed, update bind group!\n if (value && this.sampler) this.shouldResetBindGroup = true\n this.sampler = value\n }\n\n /**\n * Set the correct WGSL code snippet.\n */\n setWGSLFragment() {\n this.wgslGroupFragment = [\n `var ${this.name}: ${this.options.type === 'comparison' ? `${this.bindingType}_comparison` : this.bindingType};`,\n ]\n }\n}\n"],"names":[],"mappings":";;AAqBO,MAAM,uBAAuB,OAAQ,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAY1C,WAAY,CAAA;AAAA,IACV,KAAQ,GAAA,SAAA;AAAA,IACR,IAAO,GAAA,SAAA;AAAA,IACP,WAAA;AAAA,IACA,UAAA;AAAA,IACA,OAAA;AAAA,IACA,IAAO,GAAA,WAAA;AAAA,GACgB,EAAA;AACvB,IAAA,WAAA,GAAc,WAAe,IAAA,SAAA,CAAA;AAE7B,IAAA,KAAA,CAAM,EAAE,KAAA,EAAO,IAAM,EAAA,WAAA,EAAa,YAAY,CAAA,CAAA;AAE9C,IAAA,IAAA,CAAK,OAAU,GAAA;AAAA,MACb,GAAG,IAAK,CAAA,OAAA;AAAA,MACR,OAAA;AAAA,MACA,IAAA;AAAA,KACF,CAAA;AAEA,IAAA,IAAA,CAAK,QAAW,GAAA,OAAA,CAAA;AAEhB,IAAA,IAAA,CAAK,eAAgB,EAAA,CAAA;AAAA,GACvB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,cAGF,GAAA;AACA,IAAO,OAAA;AAAA,MACL,OAAS,EAAA;AAAA,QACP,IAAA,EAAM,KAAK,OAAQ,CAAA,IAAA;AAAA;AAAA,OACrB;AAAA,KACF,CAAA;AAAA,GACF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,QAAmC,GAAA;AACrC,IAAA,OAAO,IAAK,CAAA,OAAA,CAAA;AAAA,GACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,SAAS,KAA+B,EAAA;AAE1C,IAAA,IAAI,SAAS,IAAK,CAAA,OAAA;AAAS,MAAA,IAAA,CAAK,oBAAuB,GAAA,IAAA,CAAA;AACvD,IAAA,IAAA,CAAK,OAAU,GAAA,KAAA,CAAA;AAAA,GACjB;AAAA;AAAA;AAAA;AAAA,EAKA,eAAkB,GAAA;AAChB,IAAA,IAAA,CAAK,iBAAoB,GAAA;AAAA,MACvB,CAAO,IAAA,EAAA,IAAA,CAAK,IAAI,CAAA,EAAA,EAAK,IAAK,CAAA,OAAA,CAAQ,IAAS,KAAA,YAAA,GAAe,CAAG,EAAA,IAAA,CAAK,WAAW,CAAA,WAAA,CAAA,GAAgB,KAAK,WAAW,CAAA,CAAA,CAAA;AAAA,KAC/G,CAAA;AAAA,GACF;AACF;;;;"} \ No newline at end of file diff --git a/dist/esm/core/bindings/TextureBinding.mjs b/dist/esm/core/bindings/TextureBinding.mjs new file mode 100644 index 000000000..7a6964768 --- /dev/null +++ b/dist/esm/core/bindings/TextureBinding.mjs @@ -0,0 +1,79 @@ +import { Binding } from './Binding.mjs'; +import { getBindGroupLayoutTextureBindingType, getTextureBindingWGSLVarType } from './utils.mjs'; + +class TextureBinding extends Binding { + /** + * TextureBinding constructor + * @param parameters - {@link TextureBindingParams | parameters} used to create our {@link TextureBinding} + */ + constructor({ + label = "Texture", + name = "texture", + bindingType, + visibility, + texture, + format = "rgba8unorm", + access = "write", + viewDimension = "2d", + multisampled = false + }) { + bindingType = bindingType ?? "texture"; + if (bindingType === "storage") { + visibility = "compute"; + } + super({ label, name, bindingType, visibility }); + this.options = { + ...this.options, + texture, + format, + access, + viewDimension, + multisampled + }; + this.resource = texture; + this.setWGSLFragment(); + } + /** + * Get bind group layout entry resource, either for {@link GPUBindGroupLayoutEntry#texture | texture} or {@link GPUBindGroupLayoutEntry#externalTexture | external texture} + * @readonly + */ + get resourceLayout() { + return getBindGroupLayoutTextureBindingType(this); + } + /** + * Get the {@link GPUBindGroupEntry#resource | bind group resource} + */ + get resource() { + return this.texture instanceof GPUTexture ? this.texture.createView({ label: this.options.label + " view" }) : this.texture instanceof GPUExternalTexture ? this.texture : null; + } + /** + * Set the {@link GPUBindGroupEntry#resource | bind group resource} + * @param value - new bind group resource + */ + set resource(value) { + if (value || this.texture) + this.shouldResetBindGroup = true; + this.texture = value; + } + /** + * Set or update our {@link Binding#bindingType | bindingType} and our WGSL code snippet + * @param bindingType - the new {@link Binding#bindingType | binding type} + */ + setBindingType(bindingType) { + if (bindingType !== this.bindingType) { + if (bindingType) + this.shouldResetBindGroupLayout = true; + this.bindingType = bindingType; + this.setWGSLFragment(); + } + } + /** + * Set the correct WGSL code snippet. + */ + setWGSLFragment() { + this.wgslGroupFragment = [`${getTextureBindingWGSLVarType(this)}`]; + } +} + +export { TextureBinding }; +//# sourceMappingURL=TextureBinding.mjs.map diff --git a/dist/esm/core/bindings/TextureBinding.mjs.map b/dist/esm/core/bindings/TextureBinding.mjs.map new file mode 100644 index 000000000..2cedfdf27 --- /dev/null +++ b/dist/esm/core/bindings/TextureBinding.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"TextureBinding.mjs","sources":["../../../../src/core/bindings/TextureBinding.ts"],"sourcesContent":["import { Binding, BindingMemoryAccessType, BindingParams, BindingType } from './Binding'\nimport { getBindGroupLayoutTextureBindingType, getTextureBindingWGSLVarType } from './utils'\n\n/** Defines a {@link TextureBinding} {@link TextureBinding#resource | resource} */\nexport type TextureBindingResource = GPUTexture | GPUExternalTexture | null\n\n/**\n * An object defining all possible {@link TextureBinding} class instancing parameters\n */\nexport interface TextureBindingParams extends BindingParams {\n /** {@link TextureBinding} {@link TextureBinding#resource | resource} */\n texture: TextureBindingResource\n /** The {@link GPUTexture | texture} format to use */\n format?: GPUTextureFormat\n /** The storage {@link GPUTexture | texture} binding memory access types (read only, write only or read/write) */\n access?: BindingMemoryAccessType\n /** The {@link GPUTexture | texture} view dimension to use */\n viewDimension?: GPUTextureViewDimension\n /** Whethe the {@link GPUTexture | texture} is a multisampled texture. Mainly used internally by depth textures if needed. */\n multisampled?: boolean\n}\n\n/**\n * Used to handle {@link GPUTexture} and {@link GPUExternalTexture} bindings.\n *\n * Provide both {@link TextureBinding#resourceLayout | resourceLayout} and {@link TextureBinding#resource | resource} to the {@link GPUBindGroupLayout} and {@link GPUBindGroup}.
\n * Also create the appropriate WGSL code snippet to add to the shaders.\n */\nexport class TextureBinding extends Binding {\n /** Our {@link TextureBinding} resource, i.e. a {@link GPUTexture} or {@link GPUExternalTexture} */\n texture: TextureBindingResource\n /** An array of strings to append to our shaders code declaring all the WGSL variables representing this {@link TextureBinding} */\n wgslGroupFragment: string[]\n /** Options used to create this {@link TextureBinding} */\n options: TextureBindingParams\n\n /**\n * TextureBinding constructor\n * @param parameters - {@link TextureBindingParams | parameters} used to create our {@link TextureBinding}\n */\n constructor({\n label = 'Texture',\n name = 'texture',\n bindingType,\n visibility,\n texture,\n format = 'rgba8unorm',\n access = 'write',\n viewDimension = '2d',\n multisampled = false,\n }: TextureBindingParams) {\n bindingType = bindingType ?? 'texture'\n\n if (bindingType === 'storage') {\n visibility = 'compute'\n }\n\n super({ label, name, bindingType, visibility })\n\n this.options = {\n ...this.options,\n texture,\n format,\n access,\n viewDimension,\n multisampled,\n }\n\n this.resource = texture // should be a texture or an external texture\n\n this.setWGSLFragment()\n }\n\n /**\n * Get bind group layout entry resource, either for {@link GPUBindGroupLayoutEntry#texture | texture} or {@link GPUBindGroupLayoutEntry#externalTexture | external texture}\n * @readonly\n */\n get resourceLayout():\n | GPUTextureBindingLayout\n | GPUExternalTextureBindingLayout\n | GPUStorageTextureBindingLayout\n | null {\n return getBindGroupLayoutTextureBindingType(this)\n }\n\n /**\n * Get the {@link GPUBindGroupEntry#resource | bind group resource}\n */\n get resource(): GPUExternalTexture | GPUTextureView | null {\n return this.texture instanceof GPUTexture\n ? this.texture.createView({ label: this.options.label + ' view' })\n : this.texture instanceof GPUExternalTexture\n ? this.texture\n : null\n }\n\n /**\n * Set the {@link GPUBindGroupEntry#resource | bind group resource}\n * @param value - new bind group resource\n */\n set resource(value: TextureBindingResource) {\n // resource changed, update bind group!\n if (value || this.texture) this.shouldResetBindGroup = true\n this.texture = value\n }\n\n /**\n * Set or update our {@link Binding#bindingType | bindingType} and our WGSL code snippet\n * @param bindingType - the new {@link Binding#bindingType | binding type}\n */\n setBindingType(bindingType: BindingType) {\n if (bindingType !== this.bindingType) {\n // binding type has changed!\n if (bindingType) this.shouldResetBindGroupLayout = true\n\n this.bindingType = bindingType\n this.setWGSLFragment()\n }\n }\n\n /**\n * Set the correct WGSL code snippet.\n */\n setWGSLFragment() {\n this.wgslGroupFragment = [`${getTextureBindingWGSLVarType(this)}`]\n }\n}\n"],"names":[],"mappings":";;;AA4BO,MAAM,uBAAuB,OAAQ,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAY1C,WAAY,CAAA;AAAA,IACV,KAAQ,GAAA,SAAA;AAAA,IACR,IAAO,GAAA,SAAA;AAAA,IACP,WAAA;AAAA,IACA,UAAA;AAAA,IACA,OAAA;AAAA,IACA,MAAS,GAAA,YAAA;AAAA,IACT,MAAS,GAAA,OAAA;AAAA,IACT,aAAgB,GAAA,IAAA;AAAA,IAChB,YAAe,GAAA,KAAA;AAAA,GACQ,EAAA;AACvB,IAAA,WAAA,GAAc,WAAe,IAAA,SAAA,CAAA;AAE7B,IAAA,IAAI,gBAAgB,SAAW,EAAA;AAC7B,MAAa,UAAA,GAAA,SAAA,CAAA;AAAA,KACf;AAEA,IAAA,KAAA,CAAM,EAAE,KAAA,EAAO,IAAM,EAAA,WAAA,EAAa,YAAY,CAAA,CAAA;AAE9C,IAAA,IAAA,CAAK,OAAU,GAAA;AAAA,MACb,GAAG,IAAK,CAAA,OAAA;AAAA,MACR,OAAA;AAAA,MACA,MAAA;AAAA,MACA,MAAA;AAAA,MACA,aAAA;AAAA,MACA,YAAA;AAAA,KACF,CAAA;AAEA,IAAA,IAAA,CAAK,QAAW,GAAA,OAAA,CAAA;AAEhB,IAAA,IAAA,CAAK,eAAgB,EAAA,CAAA;AAAA,GACvB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,cAIK,GAAA;AACP,IAAA,OAAO,qCAAqC,IAAI,CAAA,CAAA;AAAA,GAClD;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,QAAuD,GAAA;AACzD,IAAA,OAAO,KAAK,OAAmB,YAAA,UAAA,GAC3B,KAAK,OAAQ,CAAA,UAAA,CAAW,EAAE,KAAO,EAAA,IAAA,CAAK,OAAQ,CAAA,KAAA,GAAQ,SAAS,CAAA,GAC/D,KAAK,OAAmB,YAAA,kBAAA,GACxB,KAAK,OACL,GAAA,IAAA,CAAA;AAAA,GACN;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,SAAS,KAA+B,EAAA;AAE1C,IAAA,IAAI,SAAS,IAAK,CAAA,OAAA;AAAS,MAAA,IAAA,CAAK,oBAAuB,GAAA,IAAA,CAAA;AACvD,IAAA,IAAA,CAAK,OAAU,GAAA,KAAA,CAAA;AAAA,GACjB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,eAAe,WAA0B,EAAA;AACvC,IAAI,IAAA,WAAA,KAAgB,KAAK,WAAa,EAAA;AAEpC,MAAI,IAAA,WAAA;AAAa,QAAA,IAAA,CAAK,0BAA6B,GAAA,IAAA,CAAA;AAEnD,MAAA,IAAA,CAAK,WAAc,GAAA,WAAA,CAAA;AACnB,MAAA,IAAA,CAAK,eAAgB,EAAA,CAAA;AAAA,KACvB;AAAA,GACF;AAAA;AAAA;AAAA;AAAA,EAKA,eAAkB,GAAA;AAChB,IAAA,IAAA,CAAK,oBAAoB,CAAC,CAAA,EAAG,4BAA6B,CAAA,IAAI,CAAC,CAAE,CAAA,CAAA,CAAA;AAAA,GACnE;AACF;;;;"} \ No newline at end of file diff --git a/dist/esm/core/bindings/WritableBufferBinding.mjs b/dist/esm/core/bindings/WritableBufferBinding.mjs new file mode 100644 index 000000000..21356ea8a --- /dev/null +++ b/dist/esm/core/bindings/WritableBufferBinding.mjs @@ -0,0 +1,31 @@ +import { BufferBinding } from './BufferBinding.mjs'; + +class WritableBufferBinding extends BufferBinding { + /** + * WritableBufferBinding constructor + * @param parameters - {@link WritableBufferBindingParams | parameters} used to create our {@link WritableBufferBinding} + */ + constructor({ + label = "Work", + name = "work", + bindingType, + useStruct = true, + struct = {}, + visibility, + access = "read_write", + shouldCopyResult = false + }) { + bindingType = "storage"; + visibility = "compute"; + super({ label, name, bindingType, useStruct, struct, visibility, access }); + this.options = { + ...this.options, + shouldCopyResult + }; + this.shouldCopyResult = shouldCopyResult; + this.resultBuffer = null; + } +} + +export { WritableBufferBinding }; +//# sourceMappingURL=WritableBufferBinding.mjs.map diff --git a/dist/esm/core/bindings/WritableBufferBinding.mjs.map b/dist/esm/core/bindings/WritableBufferBinding.mjs.map new file mode 100644 index 000000000..12408fe5e --- /dev/null +++ b/dist/esm/core/bindings/WritableBufferBinding.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"WritableBufferBinding.mjs","sources":["../../../../src/core/bindings/WritableBufferBinding.ts"],"sourcesContent":["import { BufferBinding, BufferBindingParams } from './BufferBinding'\n\n/**\n * Parameters used to create a {@link WritableBufferBinding}\n */\nexport interface WritableBufferBindingParams extends BufferBindingParams {\n /** Whether whe should automatically copy the {@link WritableBufferBinding#buffer | GPU buffer} content into our {@link WritableBufferBinding#resultBuffer | result GPU buffer} */\n shouldCopyResult?: boolean\n}\n\n/**\n * Used to create a {@link BufferBinding} that can hold read/write storage bindings along with a {@link WritableBufferBinding#resultBuffer | result GPU buffer} that can be used to get data back from the GPU.\n *\n * Note that it is automatically created by the {@link core/bindGroups/BindGroup.BindGroup | BindGroup} when a {@link types/BindGroups.BindGroupInputs#storages | storages input} has its {@link BufferBindingParams#access | access} property set to `\"read_write\"`.\n */\nexport class WritableBufferBinding extends BufferBinding {\n /** Flag indicating whether whe should automatically copy the {@link buffer | GPU buffer} content into our {@link resultBuffer | result GPU buffer} */\n shouldCopyResult: boolean\n /** The result GPUBuffer */\n resultBuffer: GPUBuffer | null\n /** Options used to create this {@link WritableBufferBinding} */\n options: WritableBufferBindingParams\n\n /**\n * WritableBufferBinding constructor\n * @param parameters - {@link WritableBufferBindingParams | parameters} used to create our {@link WritableBufferBinding}\n */\n constructor({\n label = 'Work',\n name = 'work',\n bindingType,\n useStruct = true,\n struct = {},\n visibility,\n access = 'read_write',\n shouldCopyResult = false,\n }: WritableBufferBindingParams) {\n bindingType = 'storage'\n visibility = 'compute'\n\n super({ label, name, bindingType, useStruct, struct: struct, visibility, access })\n\n this.options = {\n ...this.options,\n shouldCopyResult,\n }\n\n this.shouldCopyResult = shouldCopyResult\n\n // can be used as our buffer copy destination\n this.resultBuffer = null\n }\n}\n"],"names":[],"mappings":";;AAeO,MAAM,8BAA8B,aAAc,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYvD,WAAY,CAAA;AAAA,IACV,KAAQ,GAAA,MAAA;AAAA,IACR,IAAO,GAAA,MAAA;AAAA,IACP,WAAA;AAAA,IACA,SAAY,GAAA,IAAA;AAAA,IACZ,SAAS,EAAC;AAAA,IACV,UAAA;AAAA,IACA,MAAS,GAAA,YAAA;AAAA,IACT,gBAAmB,GAAA,KAAA;AAAA,GACW,EAAA;AAC9B,IAAc,WAAA,GAAA,SAAA,CAAA;AACd,IAAa,UAAA,GAAA,SAAA,CAAA;AAEb,IAAM,KAAA,CAAA,EAAE,OAAO,IAAM,EAAA,WAAA,EAAa,WAAW,MAAgB,EAAA,UAAA,EAAY,QAAQ,CAAA,CAAA;AAEjF,IAAA,IAAA,CAAK,OAAU,GAAA;AAAA,MACb,GAAG,IAAK,CAAA,OAAA;AAAA,MACR,gBAAA;AAAA,KACF,CAAA;AAEA,IAAA,IAAA,CAAK,gBAAmB,GAAA,gBAAA,CAAA;AAGxB,IAAA,IAAA,CAAK,YAAe,GAAA,IAAA,CAAA;AAAA,GACtB;AACF;;;;"} \ No newline at end of file diff --git a/dist/esm/core/bindings/bufferElements/BufferArrayElement.mjs b/dist/esm/core/bindings/bufferElements/BufferArrayElement.mjs new file mode 100644 index 000000000..70774cebe --- /dev/null +++ b/dist/esm/core/bindings/bufferElements/BufferArrayElement.mjs @@ -0,0 +1,54 @@ +import { BufferElement, bytesPerSlot } from './BufferElement.mjs'; +import { throwWarning } from '../../../utils/utils.mjs'; + +class BufferArrayElement extends BufferElement { + /** + * BufferArrayElement constructor + * @param parameters - {@link BufferArrayElementParams | parameters} used to create our {@link BufferArrayElement} + */ + constructor({ name, key, type = "f32", arrayLength = 1 }) { + super({ name, key, type }); + this.arrayLength = arrayLength; + this.numElements = this.arrayLength / this.bufferLayout.numElements; + } + /** + * Get the array stride between two elements of the array, in indices + * @readonly + */ + get arrayStrideToIndex() { + return this.arrayStride / bytesPerSlot; + } + /** + * Set the {@link core/bindings/bufferElements/BufferElement.BufferElementAlignment | alignment} + * To compute how arrays are packed, we get the second item alignment as well and use it to calculate the arrayStride between two array elements. Using the arrayStride and the total number of elements, we can easily get the end alignment position. + * @param startOffset - offset at which to start inserting the values in the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array buffer} + */ + setAlignment(startOffset = 0) { + super.setAlignment(startOffset); + const nextAlignment = this.getElementAlignment(this.getPositionAtOffset(this.endOffset + 1)); + this.arrayStride = this.getByteCountBetweenPositions(this.alignment.end, nextAlignment.end); + this.alignment.end = this.getPositionAtOffset(this.endOffset + this.arrayStride * (this.numElements - 1)); + } + /** + * Update the {@link view} based on the new value + * @param value - new value to use + */ + update(value) { + if (ArrayBuffer.isView(value) || Array.isArray(value)) { + let valueIndex = 0; + const viewLength = this.byteCount / this.bufferLayout.View.BYTES_PER_ELEMENT; + const stride = Math.ceil(viewLength / this.numElements); + for (let i = 0; i < this.numElements; i++) { + for (let j = 0; j < this.bufferLayout.numElements; j++) { + this.view[j + i * stride] = value[valueIndex]; + valueIndex++; + } + } + } else { + throwWarning(`BufferArrayElement: value passed to ${this.name} is not an array: ${value}`); + } + } +} + +export { BufferArrayElement }; +//# sourceMappingURL=BufferArrayElement.mjs.map diff --git a/dist/esm/core/bindings/bufferElements/BufferArrayElement.mjs.map b/dist/esm/core/bindings/bufferElements/BufferArrayElement.mjs.map new file mode 100644 index 000000000..a3e14a5fb --- /dev/null +++ b/dist/esm/core/bindings/bufferElements/BufferArrayElement.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"BufferArrayElement.mjs","sources":["../../../../../src/core/bindings/bufferElements/BufferArrayElement.ts"],"sourcesContent":["import { BufferElement, BufferElementParams, bytesPerSlot } from './BufferElement'\nimport { throwWarning } from '../../../utils/utils'\n\n/**\n * Parameters used to create a {@link BufferArrayElement}\n */\nexport interface BufferArrayElementParams extends BufferElementParams {\n /** Initial length of the input {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array} */\n arrayLength: number\n}\n\n/**\n * Used to handle specific array {@link core/bindings/BufferBinding.BufferBinding | BufferBinding} types\n */\nexport class BufferArrayElement extends BufferElement {\n /** Initial length of the input {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array} */\n arrayLength: number\n /** Total number of elements (i.e. {@link arrayLength} divided by {@link core/bindings/utils.BufferLayout | buffer layout} number of elements */\n numElements: number\n /** Number of bytes in the {@link ArrayBuffer} between two elements {@link startOffset} */\n arrayStride: number\n\n /**\n * BufferArrayElement constructor\n * @param parameters - {@link BufferArrayElementParams | parameters} used to create our {@link BufferArrayElement}\n */\n constructor({ name, key, type = 'f32', arrayLength = 1 }: BufferArrayElementParams) {\n super({ name, key, type })\n\n this.arrayLength = arrayLength\n this.numElements = this.arrayLength / this.bufferLayout.numElements\n }\n\n /**\n * Get the array stride between two elements of the array, in indices\n * @readonly\n */\n get arrayStrideToIndex(): number {\n return this.arrayStride / bytesPerSlot\n }\n\n /**\n * Set the {@link core/bindings/bufferElements/BufferElement.BufferElementAlignment | alignment}\n * To compute how arrays are packed, we get the second item alignment as well and use it to calculate the arrayStride between two array elements. Using the arrayStride and the total number of elements, we can easily get the end alignment position.\n * @param startOffset - offset at which to start inserting the values in the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array buffer}\n */\n setAlignment(startOffset = 0) {\n super.setAlignment(startOffset)\n\n // repeat for a second element to know how things are laid out\n const nextAlignment = this.getElementAlignment(this.getPositionAtOffset(this.endOffset + 1))\n this.arrayStride = this.getByteCountBetweenPositions(this.alignment.end, nextAlignment.end)\n\n this.alignment.end = this.getPositionAtOffset(this.endOffset + this.arrayStride * (this.numElements - 1))\n }\n\n /**\n * Update the {@link view} based on the new value\n * @param value - new value to use\n */\n update(value) {\n if (ArrayBuffer.isView(value) || Array.isArray(value)) {\n let valueIndex = 0\n\n const viewLength = this.byteCount / this.bufferLayout.View.BYTES_PER_ELEMENT\n // arrayStride is our view length divided by the number of elements in our array\n const stride = Math.ceil(viewLength / this.numElements)\n\n for (let i = 0; i < this.numElements; i++) {\n for (let j = 0; j < this.bufferLayout.numElements; j++) {\n this.view[j + i * stride] = value[valueIndex]\n\n valueIndex++\n }\n }\n } else {\n throwWarning(`BufferArrayElement: value passed to ${this.name} is not an array: ${value}`)\n }\n }\n}\n"],"names":[],"mappings":";;;AAcO,MAAM,2BAA2B,aAAc,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYpD,WAAA,CAAY,EAAE,IAAM,EAAA,GAAA,EAAK,OAAO,KAAO,EAAA,WAAA,GAAc,GAA+B,EAAA;AAClF,IAAA,KAAA,CAAM,EAAE,IAAA,EAAM,GAAK,EAAA,IAAA,EAAM,CAAA,CAAA;AAEzB,IAAA,IAAA,CAAK,WAAc,GAAA,WAAA,CAAA;AACnB,IAAA,IAAA,CAAK,WAAc,GAAA,IAAA,CAAK,WAAc,GAAA,IAAA,CAAK,YAAa,CAAA,WAAA,CAAA;AAAA,GAC1D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,kBAA6B,GAAA;AAC/B,IAAA,OAAO,KAAK,WAAc,GAAA,YAAA,CAAA;AAAA,GAC5B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,YAAA,CAAa,cAAc,CAAG,EAAA;AAC5B,IAAA,KAAA,CAAM,aAAa,WAAW,CAAA,CAAA;AAG9B,IAAM,MAAA,aAAA,GAAgB,KAAK,mBAAoB,CAAA,IAAA,CAAK,oBAAoB,IAAK,CAAA,SAAA,GAAY,CAAC,CAAC,CAAA,CAAA;AAC3F,IAAA,IAAA,CAAK,cAAc,IAAK,CAAA,4BAAA,CAA6B,KAAK,SAAU,CAAA,GAAA,EAAK,cAAc,GAAG,CAAA,CAAA;AAE1F,IAAK,IAAA,CAAA,SAAA,CAAU,GAAM,GAAA,IAAA,CAAK,mBAAoB,CAAA,IAAA,CAAK,YAAY,IAAK,CAAA,WAAA,IAAe,IAAK,CAAA,WAAA,GAAc,CAAE,CAAA,CAAA,CAAA;AAAA,GAC1G;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,KAAO,EAAA;AACZ,IAAA,IAAI,YAAY,MAAO,CAAA,KAAK,KAAK,KAAM,CAAA,OAAA,CAAQ,KAAK,CAAG,EAAA;AACrD,MAAA,IAAI,UAAa,GAAA,CAAA,CAAA;AAEjB,MAAA,MAAM,UAAa,GAAA,IAAA,CAAK,SAAY,GAAA,IAAA,CAAK,aAAa,IAAK,CAAA,iBAAA,CAAA;AAE3D,MAAA,MAAM,MAAS,GAAA,IAAA,CAAK,IAAK,CAAA,UAAA,GAAa,KAAK,WAAW,CAAA,CAAA;AAEtD,MAAA,KAAA,IAAS,CAAI,GAAA,CAAA,EAAG,CAAI,GAAA,IAAA,CAAK,aAAa,CAAK,EAAA,EAAA;AACzC,QAAA,KAAA,IAAS,IAAI,CAAG,EAAA,CAAA,GAAI,IAAK,CAAA,YAAA,CAAa,aAAa,CAAK,EAAA,EAAA;AACtD,UAAA,IAAA,CAAK,KAAK,CAAI,GAAA,CAAA,GAAI,MAAM,CAAA,GAAI,MAAM,UAAU,CAAA,CAAA;AAE5C,UAAA,UAAA,EAAA,CAAA;AAAA,SACF;AAAA,OACF;AAAA,KACK,MAAA;AACL,MAAA,YAAA,CAAa,CAAuC,oCAAA,EAAA,IAAA,CAAK,IAAI,CAAA,kBAAA,EAAqB,KAAK,CAAE,CAAA,CAAA,CAAA;AAAA,KAC3F;AAAA,GACF;AACF;;;;"} \ No newline at end of file diff --git a/dist/esm/core/bindings/bufferElements/BufferElement.mjs b/dist/esm/core/bindings/bufferElements/BufferElement.mjs new file mode 100644 index 000000000..95645fd4b --- /dev/null +++ b/dist/esm/core/bindings/bufferElements/BufferElement.mjs @@ -0,0 +1,199 @@ +import { getBufferLayout } from '../utils.mjs'; + +const slotsPerRow = 4; +const bytesPerSlot = 4; +const bytesPerRow = slotsPerRow * bytesPerSlot; +class BufferElement { + /** + * BufferElement constructor + * @param parameters - {@link BufferElementParams | parameters} used to create our {@link BufferElement} + */ + constructor({ name, key, type = "f32" }) { + this.name = name; + this.key = key; + this.type = type; + this.bufferLayout = getBufferLayout(this.type.replace("array", "").replace("<", "").replace(">", "")); + this.alignment = { + start: { + row: 0, + byte: 0 + }, + end: { + row: 0, + byte: 0 + } + }; + } + /** + * Get the total number of rows used by this {@link BufferElement} + * @readonly + */ + get rowCount() { + return this.alignment.end.row - this.alignment.start.row + 1; + } + /** + * Get the total number of bytes used by this {@link BufferElement} based on {@link BufferElementAlignment | alignment} start and end offsets + * @readonly + */ + get byteCount() { + return Math.abs(this.endOffset - this.startOffset) + 1; + } + /** + * Get the total number of bytes used by this {@link BufferElement}, including final padding + * @readonly + */ + get paddedByteCount() { + return (this.alignment.end.row + 1) * bytesPerRow; + } + /** + * Get the offset (i.e. byte index) at which our {@link BufferElement} starts + * @readonly + */ + get startOffset() { + return this.getByteCountAtPosition(this.alignment.start); + } + /** + * Get the array offset (i.e. array index) at which our {@link BufferElement} starts + * @readonly + */ + get startOffsetToIndex() { + return this.startOffset / bytesPerSlot; + } + /** + * Get the offset (i.e. byte index) at which our {@link BufferElement} ends + * @readonly + */ + get endOffset() { + return this.getByteCountAtPosition(this.alignment.end); + } + /** + * Get the array offset (i.e. array index) at which our {@link BufferElement} ends + * @readonly + */ + get endOffsetToIndex() { + return Math.floor(this.endOffset / bytesPerSlot); + } + /** + * Get the position at given offset (i.e. byte index) + * @param offset - byte index to use + */ + getPositionAtOffset(offset = 0) { + return { + row: Math.floor(offset / bytesPerRow), + byte: offset % bytesPerRow + }; + } + /** + * Get the number of bytes at a given {@link BufferElementAlignmentPosition | position} + * @param position - {@link BufferElementAlignmentPosition | position} from which to count + * @returns - byte count at the given {@link BufferElementAlignmentPosition | position} + */ + getByteCountAtPosition(position = { row: 0, byte: 0 }) { + return position.row * bytesPerRow + position.byte; + } + /** + * Check that a {@link BufferElementAlignmentPosition#byte | byte position} does not overflow its max value (16) + * @param position - {@link BufferElementAlignmentPosition | position} + * @returns - updated {@link BufferElementAlignmentPosition | position} + */ + applyOverflowToPosition(position = { row: 0, byte: 0 }) { + if (position.byte > bytesPerRow - 1) { + const overflow = position.byte % bytesPerRow; + position.row += Math.floor(position.byte / bytesPerRow); + position.byte = overflow; + } + return position; + } + /** + * Get the number of bytes between two {@link BufferElementAlignmentPosition | positions} + * @param p1 - first {@link BufferElementAlignmentPosition | position} + * @param p2 - second {@link BufferElementAlignmentPosition | position} + * @returns - number of bytes + */ + getByteCountBetweenPositions(p1 = { row: 0, byte: 0 }, p2 = { row: 0, byte: 0 }) { + return Math.abs(this.getByteCountAtPosition(p2) - this.getByteCountAtPosition(p1)); + } + /** + * Compute the right alignment (i.e. start and end rows and bytes) given the size and align properties and the next available {@link BufferElementAlignmentPosition | position} + * @param nextPositionAvailable - next {@link BufferElementAlignmentPosition | position} at which we should insert this element + * @returns - computed {@link BufferElementAlignment | alignment} + */ + getElementAlignment(nextPositionAvailable = { row: 0, byte: 0 }) { + const alignment = { + start: nextPositionAvailable, + end: nextPositionAvailable + }; + const { size, align } = this.bufferLayout; + if (nextPositionAvailable.byte % align !== 0) { + nextPositionAvailable.byte += nextPositionAvailable.byte % align; + } + if (size <= bytesPerRow && nextPositionAvailable.byte + size > bytesPerRow) { + nextPositionAvailable.row += 1; + nextPositionAvailable.byte = 0; + } + alignment.end = { + row: nextPositionAvailable.row + Math.ceil(size / bytesPerRow) - 1, + byte: nextPositionAvailable.byte + (size % bytesPerRow === 0 ? bytesPerRow - 1 : size % bytesPerRow - 1) + // end of row ? then it ends on slot (bytesPerRow - 1) + }; + alignment.end = this.applyOverflowToPosition(alignment.end); + return alignment; + } + /** + * Set the {@link BufferElementAlignment | alignment} from a {@link BufferElementAlignmentPosition | position} + * @param position - {@link BufferElementAlignmentPosition | position} at which to start inserting the values in the {@link !core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array} + */ + setAlignmentFromPosition(position = { row: 0, byte: 0 }) { + this.alignment = this.getElementAlignment(position); + } + /** + * Set the {@link BufferElementAlignment | alignment} from an offset (byte count) + * @param startOffset - offset at which to start inserting the values in the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array} + */ + setAlignment(startOffset = 0) { + this.setAlignmentFromPosition(this.getPositionAtOffset(startOffset)); + } + /** + * Set the {@link view} + * @param arrayBuffer - the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array} + * @param arrayView - the {@link core/bindings/BufferBinding.BufferBinding#arrayView | buffer binding array view} + */ + setView(arrayBuffer, arrayView) { + this.view = new this.bufferLayout.View( + arrayBuffer, + this.startOffset, + this.byteCount / this.bufferLayout.View.BYTES_PER_ELEMENT + ); + } + /** + * Update the {@link view} based on the new value + * @param value - new value to use + */ + update(value) { + if (this.type === "f32" || this.type === "u32" || this.type === "i32") { + this.view[0] = value; + } else if (this.type === "vec2f") { + this.view[0] = value.x ?? value[0] ?? 0; + this.view[1] = value.y ?? value[1] ?? 0; + } else if (this.type === "vec3f") { + this.view[0] = value.x ?? value[0] ?? 0; + this.view[1] = value.y ?? value[1] ?? 0; + this.view[2] = value.z ?? value[2] ?? 0; + } else if (value.elements) { + this.view.set(value.elements); + } else if (ArrayBuffer.isView(value) || Array.isArray(value)) { + this.view.set(value); + } + } + /** + * Extract the data corresponding to this specific {@link BufferElement} from a {@link Float32Array} holding the {@link GPUBuffer} data of the parentMesh {@link core/bindings/BufferBinding.BufferBinding | BufferBinding} + * @param result - {@link Float32Array} holding {@link GPUBuffer} data + * @returns - extracted data from the {@link Float32Array} + */ + extractDataFromBufferResult(result) { + return result.slice(this.startOffsetToIndex, this.endOffsetToIndex); + } +} + +export { BufferElement, bytesPerRow, bytesPerSlot, slotsPerRow }; +//# sourceMappingURL=BufferElement.mjs.map diff --git a/dist/esm/core/bindings/bufferElements/BufferElement.mjs.map b/dist/esm/core/bindings/bufferElements/BufferElement.mjs.map new file mode 100644 index 000000000..1971d3ad4 --- /dev/null +++ b/dist/esm/core/bindings/bufferElements/BufferElement.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"BufferElement.mjs","sources":["../../../../../src/core/bindings/bufferElements/BufferElement.ts"],"sourcesContent":["import { BufferLayout, getBufferLayout, TypedArray, WGSLVariableType } from '../utils'\r\nimport { Vec2 } from '../../../math/Vec2'\r\nimport { Vec3 } from '../../../math/Vec3'\r\nimport { Quat } from '../../../math/Quat'\r\nimport { Mat4 } from '../../../math/Mat4'\r\n\r\n/** Number of slots per row */\r\nexport const slotsPerRow = 4\r\n/** Number of bytes per slot */\r\nexport const bytesPerSlot = 4\r\n/** Number of bytes per row */\r\nexport const bytesPerRow = slotsPerRow * bytesPerSlot\r\n\r\n/**\r\n * Defines a position in our array buffer with a row index and a byte index\r\n */\r\nexport interface BufferElementAlignmentPosition {\r\n /** row index of that position */\r\n row: number\r\n /** byte index of that position */\r\n byte: number\r\n}\r\n\r\n/**\r\n * Defines our {@link BufferElement} alignment:\r\n * Keep track of an entry start and end row and bytes indexes (16 bytes per row)\r\n */\r\nexport interface BufferElementAlignment {\r\n /** The row and byte indexes at which this {@link BufferElement} starts */\r\n start: BufferElementAlignmentPosition\r\n /** The row and byte indexes at which this {@link BufferElement} ends */\r\n end: BufferElementAlignmentPosition\r\n}\r\n\r\n/**\r\n * Parameters used to create a {@link BufferElement}\r\n */\r\nexport interface BufferElementParams {\r\n /** The name of the {@link BufferElement} */\r\n name: string\r\n /** The key of the {@link BufferElement} */\r\n key: string\r\n /** The WGSL variable type of the {@link BufferElement} */\r\n type: WGSLVariableType\r\n}\r\n\r\n/**\r\n * Used to handle each {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array} view and data layout alignment.\r\n * Compute the exact alignment offsets needed to fill an {@link ArrayBuffer} that will be sent to a {@link GPUBuffer}, based on an input type and value.\r\n * Also update the view array at the correct offset.\r\n *\r\n * So all our struct need to be packed into our arrayBuffer using a precise layout.\r\n * They will be stored in rows, each row made of 4 slots and each slots made of 4 bytes. Depending on the binding element type, its row and slot may vary and we may have to insert empty padded values.\r\n * All in all it looks like that:
\r\n *
\r\n *          slot 0    slot 1    slot 2    slot 3\r\n * row 0 | _ _ _ _ | _ _ _ _ | _ _ _ _ | _ _ _ _ |\r\n * row 1 | _ _ _ _ | _ _ _ _ | _ _ _ _ | _ _ _ _ |\r\n * row 2 | _ _ _ _ | _ _ _ _ | _ _ _ _ | _ _ _ _ |\r\n * 
\r\n * see https://webgpufundamentals.org/webgpu/lessons/resources/wgsl-offset-computer.html\r\n */\r\nexport class BufferElement {\r\n /** The name of the {@link BufferElement} */\r\n name: string\r\n /** The WGSL variable type of the {@link BufferElement} */\r\n type: WGSLVariableType\r\n /** The key of the {@link BufferElement} */\r\n key: string\r\n\r\n /** {@link BufferLayout} used to fill the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array} at the right offsets */\r\n bufferLayout: BufferLayout\r\n\r\n /**\r\n * Object defining exactly at which place a binding should be inserted into the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n */\r\n alignment: BufferElementAlignment\r\n\r\n /** Array containing the {@link BufferElement} values */\r\n view?: TypedArray\r\n\r\n /**\r\n * BufferElement constructor\r\n * @param parameters - {@link BufferElementParams | parameters} used to create our {@link BufferElement}\r\n */\r\n constructor({ name, key, type = 'f32' }: BufferElementParams) {\r\n this.name = name\r\n this.key = key\r\n this.type = type\r\n\r\n this.bufferLayout = getBufferLayout(this.type.replace('array', '').replace('<', '').replace('>', ''))\r\n\r\n // set init alignment\r\n this.alignment = {\r\n start: {\r\n row: 0,\r\n byte: 0,\r\n },\r\n end: {\r\n row: 0,\r\n byte: 0,\r\n },\r\n }\r\n }\r\n\r\n /**\r\n * Get the total number of rows used by this {@link BufferElement}\r\n * @readonly\r\n */\r\n get rowCount(): number {\r\n return this.alignment.end.row - this.alignment.start.row + 1\r\n }\r\n\r\n /**\r\n * Get the total number of bytes used by this {@link BufferElement} based on {@link BufferElementAlignment | alignment} start and end offsets\r\n * @readonly\r\n */\r\n get byteCount(): number {\r\n return Math.abs(this.endOffset - this.startOffset) + 1\r\n }\r\n\r\n /**\r\n * Get the total number of bytes used by this {@link BufferElement}, including final padding\r\n * @readonly\r\n */\r\n get paddedByteCount(): number {\r\n return (this.alignment.end.row + 1) * bytesPerRow\r\n }\r\n\r\n /**\r\n * Get the offset (i.e. byte index) at which our {@link BufferElement} starts\r\n * @readonly\r\n */\r\n get startOffset(): number {\r\n return this.getByteCountAtPosition(this.alignment.start)\r\n }\r\n\r\n /**\r\n * Get the array offset (i.e. array index) at which our {@link BufferElement} starts\r\n * @readonly\r\n */\r\n get startOffsetToIndex(): number {\r\n return this.startOffset / bytesPerSlot\r\n }\r\n\r\n /**\r\n * Get the offset (i.e. byte index) at which our {@link BufferElement} ends\r\n * @readonly\r\n */\r\n get endOffset(): number {\r\n return this.getByteCountAtPosition(this.alignment.end)\r\n }\r\n\r\n /**\r\n * Get the array offset (i.e. array index) at which our {@link BufferElement} ends\r\n * @readonly\r\n */\r\n get endOffsetToIndex(): number {\r\n return Math.floor(this.endOffset / bytesPerSlot)\r\n }\r\n\r\n /**\r\n * Get the position at given offset (i.e. byte index)\r\n * @param offset - byte index to use\r\n */\r\n getPositionAtOffset(offset = 0): BufferElementAlignmentPosition {\r\n return {\r\n row: Math.floor(offset / bytesPerRow),\r\n byte: offset % bytesPerRow,\r\n }\r\n }\r\n\r\n /**\r\n * Get the number of bytes at a given {@link BufferElementAlignmentPosition | position}\r\n * @param position - {@link BufferElementAlignmentPosition | position} from which to count\r\n * @returns - byte count at the given {@link BufferElementAlignmentPosition | position}\r\n */\r\n getByteCountAtPosition(position: BufferElementAlignmentPosition = { row: 0, byte: 0 }): number {\r\n return position.row * bytesPerRow + position.byte\r\n }\r\n\r\n /**\r\n * Check that a {@link BufferElementAlignmentPosition#byte | byte position} does not overflow its max value (16)\r\n * @param position - {@link BufferElementAlignmentPosition | position}\r\n * @returns - updated {@link BufferElementAlignmentPosition | position}\r\n */\r\n applyOverflowToPosition(\r\n position: BufferElementAlignmentPosition = { row: 0, byte: 0 }\r\n ): BufferElementAlignmentPosition {\r\n if (position.byte > bytesPerRow - 1) {\r\n const overflow = position.byte % bytesPerRow\r\n position.row += Math.floor(position.byte / bytesPerRow)\r\n position.byte = overflow\r\n }\r\n\r\n return position\r\n }\r\n\r\n /**\r\n * Get the number of bytes between two {@link BufferElementAlignmentPosition | positions}\r\n * @param p1 - first {@link BufferElementAlignmentPosition | position}\r\n * @param p2 - second {@link BufferElementAlignmentPosition | position}\r\n * @returns - number of bytes\r\n */\r\n getByteCountBetweenPositions(\r\n p1: BufferElementAlignmentPosition = { row: 0, byte: 0 },\r\n p2: BufferElementAlignmentPosition = { row: 0, byte: 0 }\r\n ): number {\r\n return Math.abs(this.getByteCountAtPosition(p2) - this.getByteCountAtPosition(p1))\r\n }\r\n\r\n /**\r\n * Compute the right alignment (i.e. start and end rows and bytes) given the size and align properties and the next available {@link BufferElementAlignmentPosition | position}\r\n * @param nextPositionAvailable - next {@link BufferElementAlignmentPosition | position} at which we should insert this element\r\n * @returns - computed {@link BufferElementAlignment | alignment}\r\n */\r\n getElementAlignment(\r\n nextPositionAvailable: BufferElementAlignmentPosition = { row: 0, byte: 0 }\r\n ): BufferElementAlignment {\r\n const alignment = {\r\n start: nextPositionAvailable,\r\n end: nextPositionAvailable,\r\n }\r\n\r\n const { size, align } = this.bufferLayout\r\n\r\n // check the alignment, i.e. even if there's enough space for our binding\r\n // we might have to pad the slot because some types need a specific alignment\r\n if (nextPositionAvailable.byte % align !== 0) {\r\n nextPositionAvailable.byte += nextPositionAvailable.byte % align\r\n }\r\n\r\n // in the case of a binding that could fit on one row\r\n // but we don't have space on the current row for this binding element\r\n // go to next row\r\n if (size <= bytesPerRow && nextPositionAvailable.byte + size > bytesPerRow) {\r\n nextPositionAvailable.row += 1\r\n nextPositionAvailable.byte = 0\r\n }\r\n\r\n alignment.end = {\r\n row: nextPositionAvailable.row + Math.ceil(size / bytesPerRow) - 1,\r\n byte: nextPositionAvailable.byte + (size % bytesPerRow === 0 ? bytesPerRow - 1 : (size % bytesPerRow) - 1), // end of row ? then it ends on slot (bytesPerRow - 1)\r\n }\r\n\r\n // now final check, if end slot has overflown\r\n alignment.end = this.applyOverflowToPosition(alignment.end)\r\n\r\n return alignment\r\n }\r\n\r\n /**\r\n * Set the {@link BufferElementAlignment | alignment} from a {@link BufferElementAlignmentPosition | position}\r\n * @param position - {@link BufferElementAlignmentPosition | position} at which to start inserting the values in the {@link !core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n */\r\n setAlignmentFromPosition(position: BufferElementAlignmentPosition = { row: 0, byte: 0 }) {\r\n this.alignment = this.getElementAlignment(position)\r\n }\r\n\r\n /**\r\n * Set the {@link BufferElementAlignment | alignment} from an offset (byte count)\r\n * @param startOffset - offset at which to start inserting the values in the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n */\r\n setAlignment(startOffset = 0) {\r\n this.setAlignmentFromPosition(this.getPositionAtOffset(startOffset))\r\n }\r\n\r\n /**\r\n * Set the {@link view}\r\n * @param arrayBuffer - the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n * @param arrayView - the {@link core/bindings/BufferBinding.BufferBinding#arrayView | buffer binding array view}\r\n */\r\n setView(arrayBuffer: ArrayBuffer, arrayView: DataView) {\r\n this.view = new this.bufferLayout.View(\r\n arrayBuffer,\r\n this.startOffset,\r\n this.byteCount / this.bufferLayout.View.BYTES_PER_ELEMENT\r\n )\r\n }\r\n\r\n /**\r\n * Update the {@link view} based on the new value\r\n * @param value - new value to use\r\n */\r\n update(value) {\r\n if (this.type === 'f32' || this.type === 'u32' || this.type === 'i32') {\r\n this.view[0] = value as number\r\n } else if (this.type === 'vec2f') {\r\n this.view[0] = (value as Vec2).x ?? value[0] ?? 0\r\n this.view[1] = (value as Vec2).y ?? value[1] ?? 0\r\n } else if (this.type === 'vec3f') {\r\n this.view[0] = (value as Vec3).x ?? value[0] ?? 0\r\n this.view[1] = (value as Vec3).y ?? value[1] ?? 0\r\n this.view[2] = (value as Vec3).z ?? value[2] ?? 0\r\n } else if ((value as Quat | Mat4).elements) {\r\n this.view.set((value as Quat | Mat4).elements)\r\n } else if (ArrayBuffer.isView(value) || Array.isArray(value)) {\r\n this.view.set(value as number[])\r\n }\r\n }\r\n\r\n /**\r\n * Extract the data corresponding to this specific {@link BufferElement} from a {@link Float32Array} holding the {@link GPUBuffer} data of the parentMesh {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}\r\n * @param result - {@link Float32Array} holding {@link GPUBuffer} data\r\n * @returns - extracted data from the {@link Float32Array}\r\n */\r\n extractDataFromBufferResult(result: Float32Array) {\r\n return result.slice(this.startOffsetToIndex, this.endOffsetToIndex)\r\n }\r\n}\r\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/bindings/bufferElements/BufferInterleavedArrayElement.mjs b/dist/esm/core/bindings/bufferElements/BufferInterleavedArrayElement.mjs new file mode 100644 index 000000000..1c98ed914 --- /dev/null +++ b/dist/esm/core/bindings/bufferElements/BufferInterleavedArrayElement.mjs @@ -0,0 +1,87 @@ +import { BufferArrayElement } from './BufferArrayElement.mjs'; + +class BufferInterleavedArrayElement extends BufferArrayElement { + /** + * BufferInterleavedArrayElement constructor + * @param parameters - {@link BufferArrayElementParams | parameters} used to create our {@link BufferInterleavedArrayElement} + */ + constructor({ name, key, type = "f32", arrayLength = 1 }) { + super({ name, key, type, arrayLength }); + this.arrayStride = 1; + this.arrayLength = arrayLength; + this.numElements = this.arrayLength / this.bufferLayout.numElements; + } + /** + * Get the total number of slots used by this {@link BufferInterleavedArrayElement} based on buffer layout size and total number of elements + * @readonly + */ + get byteCount() { + return this.bufferLayout.size * this.numElements; + } + /** + * Set the {@link core/bindings/bufferElements/BufferElement.BufferElementAlignment | alignment} + * To compute how arrays are packed, we need to compute the arrayStride between two elements beforehand and pass it here. Using the arrayStride and the total number of elements, we can easily get the end alignment position. + * @param startOffset - offset at which to start inserting the values in the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array} + * @param stride - Stride in the {@link ArrayBuffer} between two elements of the array + */ + setAlignment(startOffset = 0, stride = 0) { + this.alignment = this.getElementAlignment(this.getPositionAtOffset(startOffset)); + this.arrayStride = stride; + this.alignment.end = this.getPositionAtOffset(this.endOffset + stride * (this.numElements - 1)); + } + /** + * Set the {@link view} and {@link viewSetFunction} + * @param arrayBuffer - the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array} + * @param arrayView - the {@link core/bindings/BufferBinding.BufferBinding#arrayView | buffer binding array view} + */ + setView(arrayBuffer, arrayView) { + this.view = new this.bufferLayout.View(this.bufferLayout.numElements * this.numElements); + this.viewSetFunction = ((arrayView2) => { + switch (this.bufferLayout.View) { + case Int32Array: + return arrayView2.setInt32.bind(arrayView2); + case Uint16Array: + return arrayView2.setUint16.bind(arrayView2); + case Uint32Array: + return arrayView2.setUint32.bind(arrayView2); + case Float32Array: + default: + return arrayView2.setFloat32.bind(arrayView2); + } + })(arrayView); + } + /** + * Update the {@link view} based on the new value, and then update the {@link core/bindings/BufferBinding.BufferBinding#arrayView | buffer binding array view} using sub arrays + * @param value - new value to use + */ + update(value) { + super.update(value); + for (let i = 0; i < this.numElements; i++) { + const subarray = this.view.subarray( + i * this.bufferLayout.numElements, + i * this.bufferLayout.numElements + this.bufferLayout.numElements + ); + const startByteOffset = this.startOffset + i * this.arrayStride; + subarray.forEach((value2, index) => { + this.viewSetFunction(startByteOffset + index * this.bufferLayout.View.BYTES_PER_ELEMENT, value2, true); + }); + } + } + /** + * Extract the data corresponding to this specific {@link BufferInterleavedArrayElement} from a {@link Float32Array} holding the {@link GPUBuffer} data of the parentMesh {@link core/bindings/BufferBinding.BufferBinding | BufferBinding} + * @param result - {@link Float32Array} holding {@link GPUBuffer} data + */ + extractDataFromBufferResult(result) { + const interleavedResult = new Float32Array(this.arrayLength); + for (let i = 0; i < this.numElements; i++) { + const resultOffset = this.startOffsetToIndex + i * this.arrayStrideToIndex; + for (let j = 0; j < this.bufferLayout.numElements; j++) { + interleavedResult[i * this.bufferLayout.numElements + j] = result[resultOffset + j]; + } + } + return interleavedResult; + } +} + +export { BufferInterleavedArrayElement }; +//# sourceMappingURL=BufferInterleavedArrayElement.mjs.map diff --git a/dist/esm/core/bindings/bufferElements/BufferInterleavedArrayElement.mjs.map b/dist/esm/core/bindings/bufferElements/BufferInterleavedArrayElement.mjs.map new file mode 100644 index 000000000..a6cf72992 --- /dev/null +++ b/dist/esm/core/bindings/bufferElements/BufferInterleavedArrayElement.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"BufferInterleavedArrayElement.mjs","sources":["../../../../../src/core/bindings/bufferElements/BufferInterleavedArrayElement.ts"],"sourcesContent":["import { BufferArrayElement, BufferArrayElementParams } from './BufferArrayElement'\r\n\r\n/**\r\n * Used to compute alignment when dealing with arrays of Struct\r\n */\r\nexport class BufferInterleavedArrayElement extends BufferArrayElement {\r\n /** Corresponding {@link DataView} set function based on {@link view} type */\r\n viewSetFunction: DataView['setInt32'] | DataView['setUint16'] | DataView['setUint32'] | DataView['setFloat32']\r\n\r\n /**\r\n * BufferInterleavedArrayElement constructor\r\n * @param parameters - {@link BufferArrayElementParams | parameters} used to create our {@link BufferInterleavedArrayElement}\r\n */\r\n constructor({ name, key, type = 'f32', arrayLength = 1 }: BufferArrayElementParams) {\r\n super({ name, key, type, arrayLength })\r\n\r\n this.arrayStride = 1\r\n\r\n this.arrayLength = arrayLength\r\n this.numElements = this.arrayLength / this.bufferLayout.numElements\r\n }\r\n\r\n /**\r\n * Get the total number of slots used by this {@link BufferInterleavedArrayElement} based on buffer layout size and total number of elements\r\n * @readonly\r\n */\r\n get byteCount(): number {\r\n return this.bufferLayout.size * this.numElements\r\n }\r\n\r\n /**\r\n * Set the {@link core/bindings/bufferElements/BufferElement.BufferElementAlignment | alignment}\r\n * To compute how arrays are packed, we need to compute the arrayStride between two elements beforehand and pass it here. Using the arrayStride and the total number of elements, we can easily get the end alignment position.\r\n * @param startOffset - offset at which to start inserting the values in the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n * @param stride - Stride in the {@link ArrayBuffer} between two elements of the array\r\n */\r\n setAlignment(startOffset = 0, stride = 0) {\r\n this.alignment = this.getElementAlignment(this.getPositionAtOffset(startOffset))\r\n\r\n this.arrayStride = stride\r\n\r\n this.alignment.end = this.getPositionAtOffset(this.endOffset + stride * (this.numElements - 1))\r\n }\r\n\r\n /**\r\n * Set the {@link view} and {@link viewSetFunction}\r\n * @param arrayBuffer - the {@link core/bindings/BufferBinding.BufferBinding#arrayBuffer | buffer binding array}\r\n * @param arrayView - the {@link core/bindings/BufferBinding.BufferBinding#arrayView | buffer binding array view}\r\n */\r\n setView(arrayBuffer: ArrayBuffer, arrayView: DataView) {\r\n // our view will be a simple typed array, not linked to the array buffer\r\n this.view = new this.bufferLayout.View(this.bufferLayout.numElements * this.numElements)\r\n\r\n // but our viewSetFunction is linked to the array view\r\n this.viewSetFunction = ((arrayView) => {\r\n switch (this.bufferLayout.View) {\r\n case Int32Array:\r\n return arrayView.setInt32.bind(arrayView) as DataView['setInt32']\r\n case Uint16Array:\r\n return arrayView.setUint16.bind(arrayView) as DataView['setUint16']\r\n case Uint32Array:\r\n return arrayView.setUint32.bind(arrayView) as DataView['setUint32']\r\n case Float32Array:\r\n default:\r\n return arrayView.setFloat32.bind(arrayView) as DataView['setFloat32']\r\n }\r\n })(arrayView)\r\n }\r\n\r\n /**\r\n * Update the {@link view} based on the new value, and then update the {@link core/bindings/BufferBinding.BufferBinding#arrayView | buffer binding array view} using sub arrays\r\n * @param value - new value to use\r\n */\r\n update(value) {\r\n super.update(value)\r\n\r\n // now use our viewSetFunction to fill the array view with interleaved alignment\r\n for (let i = 0; i < this.numElements; i++) {\r\n const subarray = this.view.subarray(\r\n i * this.bufferLayout.numElements,\r\n i * this.bufferLayout.numElements + this.bufferLayout.numElements\r\n )\r\n\r\n const startByteOffset = this.startOffset + i * this.arrayStride\r\n\r\n // view set function need to be called for each subarray entry, so loop over subarray entries\r\n subarray.forEach((value, index) => {\r\n this.viewSetFunction(startByteOffset + index * this.bufferLayout.View.BYTES_PER_ELEMENT, value, true)\r\n })\r\n }\r\n }\r\n\r\n /**\r\n * Extract the data corresponding to this specific {@link BufferInterleavedArrayElement} from a {@link Float32Array} holding the {@link GPUBuffer} data of the parentMesh {@link core/bindings/BufferBinding.BufferBinding | BufferBinding}\r\n * @param result - {@link Float32Array} holding {@link GPUBuffer} data\r\n */\r\n extractDataFromBufferResult(result: Float32Array) {\r\n const interleavedResult = new Float32Array(this.arrayLength)\r\n for (let i = 0; i < this.numElements; i++) {\r\n const resultOffset = this.startOffsetToIndex + i * this.arrayStrideToIndex\r\n\r\n for (let j = 0; j < this.bufferLayout.numElements; j++) {\r\n interleavedResult[i * this.bufferLayout.numElements + j] = result[resultOffset + j]\r\n }\r\n }\r\n return interleavedResult\r\n }\r\n}\r\n"],"names":["arrayView","value"],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/bindings/utils.mjs b/dist/esm/core/bindings/utils.mjs new file mode 100644 index 000000000..6dd7252e6 --- /dev/null +++ b/dist/esm/core/bindings/utils.mjs @@ -0,0 +1,103 @@ +const getBufferLayout = (bufferType) => { + const bufferLayouts = { + i32: { numElements: 1, align: 4, size: 4, type: "i32", View: Int32Array }, + u32: { numElements: 1, align: 4, size: 4, type: "u32", View: Uint32Array }, + f32: { numElements: 1, align: 4, size: 4, type: "f32", View: Float32Array }, + f16: { numElements: 1, align: 2, size: 2, type: "u16", View: Uint16Array }, + vec2f: { numElements: 2, align: 8, size: 8, type: "f32", View: Float32Array }, + vec2i: { numElements: 2, align: 8, size: 8, type: "i32", View: Int32Array }, + vec2u: { numElements: 2, align: 8, size: 8, type: "u32", View: Uint32Array }, + vec2h: { numElements: 2, align: 4, size: 4, type: "u16", View: Uint16Array }, + vec3i: { numElements: 3, align: 16, size: 12, type: "i32", View: Int32Array }, + vec3u: { numElements: 3, align: 16, size: 12, type: "u32", View: Uint32Array }, + vec3f: { numElements: 3, align: 16, size: 12, type: "f32", View: Float32Array }, + vec3h: { numElements: 3, align: 8, size: 6, type: "u16", View: Uint16Array }, + vec4i: { numElements: 4, align: 16, size: 16, type: "i32", View: Int32Array }, + vec4u: { numElements: 4, align: 16, size: 16, type: "u32", View: Uint32Array }, + vec4f: { numElements: 4, align: 16, size: 16, type: "f32", View: Float32Array }, + vec4h: { numElements: 4, align: 8, size: 8, type: "u16", View: Uint16Array }, + // AlignOf(vecR) SizeOf(array) + mat2x2f: { numElements: 4, align: 8, size: 16, type: "f32", View: Float32Array }, + mat2x2h: { numElements: 4, align: 4, size: 8, type: "u16", View: Uint16Array }, + mat3x2f: { numElements: 6, align: 8, size: 24, type: "f32", View: Float32Array }, + mat3x2h: { numElements: 6, align: 4, size: 12, type: "u16", View: Uint16Array }, + mat4x2f: { numElements: 8, align: 8, size: 32, type: "f32", View: Float32Array }, + mat4x2h: { numElements: 8, align: 4, size: 16, type: "u16", View: Uint16Array }, + mat2x3f: { numElements: 8, align: 16, size: 32, pad: [3, 1], type: "f32", View: Float32Array }, + mat2x3h: { numElements: 8, align: 8, size: 16, pad: [3, 1], type: "u16", View: Uint16Array }, + mat3x3f: { numElements: 12, align: 16, size: 48, pad: [3, 1], type: "f32", View: Float32Array }, + mat3x3h: { numElements: 12, align: 8, size: 24, pad: [3, 1], type: "u16", View: Uint16Array }, + mat4x3f: { numElements: 16, align: 16, size: 64, pad: [3, 1], type: "f32", View: Float32Array }, + mat4x3h: { numElements: 16, align: 8, size: 32, pad: [3, 1], type: "u16", View: Uint16Array }, + mat2x4f: { numElements: 8, align: 16, size: 32, type: "f32", View: Float32Array }, + mat2x4h: { numElements: 8, align: 8, size: 16, type: "u16", View: Uint16Array }, + mat3x4f: { numElements: 12, align: 16, size: 48, pad: [3, 1], type: "f32", View: Float32Array }, + mat3x4h: { numElements: 12, align: 8, size: 24, pad: [3, 1], type: "u16", View: Uint16Array }, + mat4x4f: { numElements: 16, align: 16, size: 64, type: "f32", View: Float32Array }, + mat4x4h: { numElements: 16, align: 8, size: 32, type: "u16", View: Uint16Array } + }; + return bufferLayouts[bufferType]; +}; +const getBindingWGSLVarType = (binding) => { + return (() => { + switch (binding.bindingType) { + case "storage": + return `var<${binding.bindingType}, ${binding.options.access}>`; + case "uniform": + default: + return "var"; + } + })(); +}; +const getTextureBindingWGSLVarType = (binding) => { + if (binding.bindingType === "externalTexture") { + return `var ${binding.name}: texture_external;`; + } + return binding.bindingType === "storage" ? `var ${binding.name}: texture_storage_${binding.options.viewDimension}<${binding.options.format}, ${binding.options.access}>;` : binding.bindingType === "depth" ? `var ${binding.name}: texture_depth${binding.options.multisampled ? "_multisampled" : ""}_${binding.options.viewDimension};` : `var ${binding.name}: texture${binding.options.multisampled ? "_multisampled" : ""}_${binding.options.viewDimension};`; +}; +const getBindGroupLayoutBindingType = (binding) => { + if (binding.bindingType === "storage" && binding.options.access === "read_write") { + return "storage"; + } else if (binding.bindingType === "storage") { + return "read-only-storage"; + } else { + return "uniform"; + } +}; +const getBindGroupLayoutTextureBindingType = (binding) => { + return (() => { + switch (binding.bindingType) { + case "externalTexture": + return { externalTexture: {} }; + case "storage": + return { + storageTexture: { + format: binding.options.format, + viewDimension: binding.options.viewDimension + } + }; + case "texture": + return { + texture: { + multisampled: binding.options.multisampled, + viewDimension: binding.options.viewDimension, + sampleType: binding.options.multisampled ? "unfilterable-float" : "float" + } + }; + case "depth": + return { + texture: { + multisampled: binding.options.multisampled, + format: binding.options.format, + viewDimension: binding.options.viewDimension, + sampleType: "depth" + } + }; + default: + return null; + } + })(); +}; + +export { getBindGroupLayoutBindingType, getBindGroupLayoutTextureBindingType, getBindingWGSLVarType, getBufferLayout, getTextureBindingWGSLVarType }; +//# sourceMappingURL=utils.mjs.map diff --git a/dist/esm/core/bindings/utils.mjs.map b/dist/esm/core/bindings/utils.mjs.map new file mode 100644 index 000000000..2d80c0d88 --- /dev/null +++ b/dist/esm/core/bindings/utils.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"utils.mjs","sources":["../../../../src/core/bindings/utils.ts"],"sourcesContent":["import { BindingType } from './Binding'\nimport { BufferBinding } from './BufferBinding'\nimport { TextureBinding } from './TextureBinding'\n\n/** Defines a typed array */\nexport type TypedArray =\n | Int8Array\n | Uint8Array\n | Uint8ClampedArray\n | Int16Array\n | Uint16Array\n | Int32Array\n | Uint32Array\n | Float32Array\n | Float64Array\n\n/** Defines a typed array constructor */\nexport type TypedArrayConstructor =\n | Int8ArrayConstructor\n | Uint8ArrayConstructor\n | Int16ArrayConstructor\n | Uint16ArrayConstructor\n | Int32ArrayConstructor\n | Uint32ArrayConstructor\n | Float32ArrayConstructor\n | Float64ArrayConstructor\n\n/** Defines the possible WGSL variable types */\nexport type WGSLVariableType = string // TODO 'mat4x4f', 'mat3x3f', 'vec3f', 'vec2f', 'f32' etc\n\n/**\n * Defines a {@link BufferLayout} object used to pad our {@link GPUBuffer} arrays\n */\nexport type BufferLayout = {\n /** Number of elements hold by this variable type */\n numElements: number\n /** Required alignment by this variable type */\n align: number\n /** Size in bytes of this variable type */\n size: number\n /** Variable type */\n type: WGSLVariableType\n /** Typed array constructor required by this variable type */\n View: TypedArrayConstructor\n /** Pad values required by this variable type */\n pad?: number[]\n}\n\n// from https://github.com/greggman/webgpu-utils/blob/main/src/buffer-views.ts\n/**\n * Get the correct [buffer layout]{@link BufferLayout} for given [variable type]{@link WGSLVariableType}\n * @param bufferType - [variable type]{@link WGSLVariableType} to use\n * @returns - the [buffer layout]{@link BufferLayout}\n */\nexport const getBufferLayout = (bufferType: WGSLVariableType): BufferLayout => {\n const bufferLayouts = {\n i32: { numElements: 1, align: 4, size: 4, type: 'i32', View: Int32Array },\n u32: { numElements: 1, align: 4, size: 4, type: 'u32', View: Uint32Array },\n f32: { numElements: 1, align: 4, size: 4, type: 'f32', View: Float32Array },\n f16: { numElements: 1, align: 2, size: 2, type: 'u16', View: Uint16Array },\n\n vec2f: { numElements: 2, align: 8, size: 8, type: 'f32', View: Float32Array },\n vec2i: { numElements: 2, align: 8, size: 8, type: 'i32', View: Int32Array },\n vec2u: { numElements: 2, align: 8, size: 8, type: 'u32', View: Uint32Array },\n vec2h: { numElements: 2, align: 4, size: 4, type: 'u16', View: Uint16Array },\n vec3i: { numElements: 3, align: 16, size: 12, type: 'i32', View: Int32Array },\n vec3u: { numElements: 3, align: 16, size: 12, type: 'u32', View: Uint32Array },\n vec3f: { numElements: 3, align: 16, size: 12, type: 'f32', View: Float32Array },\n vec3h: { numElements: 3, align: 8, size: 6, type: 'u16', View: Uint16Array },\n vec4i: { numElements: 4, align: 16, size: 16, type: 'i32', View: Int32Array },\n vec4u: { numElements: 4, align: 16, size: 16, type: 'u32', View: Uint32Array },\n vec4f: { numElements: 4, align: 16, size: 16, type: 'f32', View: Float32Array },\n vec4h: { numElements: 4, align: 8, size: 8, type: 'u16', View: Uint16Array },\n\n // AlignOf(vecR)\tSizeOf(array)\n mat2x2f: { numElements: 4, align: 8, size: 16, type: 'f32', View: Float32Array },\n mat2x2h: { numElements: 4, align: 4, size: 8, type: 'u16', View: Uint16Array },\n mat3x2f: { numElements: 6, align: 8, size: 24, type: 'f32', View: Float32Array },\n mat3x2h: { numElements: 6, align: 4, size: 12, type: 'u16', View: Uint16Array },\n mat4x2f: { numElements: 8, align: 8, size: 32, type: 'f32', View: Float32Array },\n mat4x2h: { numElements: 8, align: 4, size: 16, type: 'u16', View: Uint16Array },\n mat2x3f: { numElements: 8, align: 16, size: 32, pad: [3, 1], type: 'f32', View: Float32Array },\n mat2x3h: { numElements: 8, align: 8, size: 16, pad: [3, 1], type: 'u16', View: Uint16Array },\n mat3x3f: { numElements: 12, align: 16, size: 48, pad: [3, 1], type: 'f32', View: Float32Array },\n mat3x3h: { numElements: 12, align: 8, size: 24, pad: [3, 1], type: 'u16', View: Uint16Array },\n mat4x3f: { numElements: 16, align: 16, size: 64, pad: [3, 1], type: 'f32', View: Float32Array },\n mat4x3h: { numElements: 16, align: 8, size: 32, pad: [3, 1], type: 'u16', View: Uint16Array },\n mat2x4f: { numElements: 8, align: 16, size: 32, type: 'f32', View: Float32Array },\n mat2x4h: { numElements: 8, align: 8, size: 16, type: 'u16', View: Uint16Array },\n mat3x4f: { numElements: 12, align: 16, size: 48, pad: [3, 1], type: 'f32', View: Float32Array },\n mat3x4h: { numElements: 12, align: 8, size: 24, pad: [3, 1], type: 'u16', View: Uint16Array },\n mat4x4f: { numElements: 16, align: 16, size: 64, type: 'f32', View: Float32Array },\n mat4x4h: { numElements: 16, align: 8, size: 32, type: 'u16', View: Uint16Array },\n }\n\n return bufferLayouts[bufferType]\n}\n\n/**\n * Get the correct WGSL variable declaration code fragment based on the given [buffer binding]{@link BufferBinding}\n * @param binding - [buffer binding]{@link BufferBinding} to use\n * @returns - WGSL variable declaration code fragment\n */\nexport const getBindingWGSLVarType = (binding: BufferBinding): string => {\n return (() => {\n switch (binding.bindingType) {\n case 'storage':\n return `var<${binding.bindingType}, ${binding.options.access}>`\n case 'uniform':\n default:\n return 'var'\n }\n })()\n}\n\n/**\n * Get the correct WGSL variable declaration code fragment based on the given [texture binding]{@link TextureBinding}\n * @param binding - [texture binding]{@link TextureBinding} to use\n * @returns - WGSL variable declaration code fragment\n */\nexport const getTextureBindingWGSLVarType = (binding: TextureBinding): string => {\n if (binding.bindingType === 'externalTexture') {\n return `var ${binding.name}: texture_external;`\n }\n\n return binding.bindingType === 'storage'\n ? `var ${binding.name}: texture_storage_${binding.options.viewDimension}<${binding.options.format}, ${binding.options.access}>;`\n : binding.bindingType === 'depth'\n ? `var ${binding.name}: texture_depth${binding.options.multisampled ? '_multisampled' : ''}_${\n binding.options.viewDimension\n };`\n : `var ${binding.name}: texture${binding.options.multisampled ? '_multisampled' : ''}_${\n binding.options.viewDimension\n };`\n}\n\n/**\n * Get the correct [bind group layout]{@link GPUBindGroupLayout} resource type based on the given [binding type]{@link BindingType}\n * @param binding - [buffer binding]{@link BufferBinding} to use\n * @returns - {@link GPUBindGroupLayout | bind group layout} resource type\n */\nexport const getBindGroupLayoutBindingType = (binding: BufferBinding): GPUBufferBindingType => {\n if (binding.bindingType === 'storage' && binding.options.access === 'read_write') {\n return 'storage'\n } else if (binding.bindingType === 'storage') {\n return 'read-only-storage'\n } else {\n return 'uniform'\n }\n}\n\n/**\n * Get the correct [bind group layout]{@link GPUBindGroupLayout} resource type based on the given [texture binding type]{@link BindingType}\n * @param binding - [texture binding]{@link TextureBinding} to use\n * @returns - [bind group layout]{@link GPUBindGroupLayout} resource type\n */\nexport const getBindGroupLayoutTextureBindingType = (\n binding: TextureBinding\n): GPUTextureBindingLayout | GPUExternalTextureBindingLayout | GPUStorageTextureBindingLayout | null => {\n return (() => {\n switch (binding.bindingType) {\n case 'externalTexture':\n return { externalTexture: {} }\n case 'storage':\n return {\n storageTexture: {\n format: binding.options.format,\n viewDimension: binding.options.viewDimension,\n } as GPUStorageTextureBindingLayout,\n }\n case 'texture':\n return {\n texture: {\n multisampled: binding.options.multisampled,\n viewDimension: binding.options.viewDimension,\n sampleType: binding.options.multisampled ? 'unfilterable-float' : 'float',\n } as GPUTextureBindingLayout,\n }\n case 'depth':\n return {\n texture: {\n multisampled: binding.options.multisampled,\n format: binding.options.format,\n viewDimension: binding.options.viewDimension,\n sampleType: 'depth',\n } as GPUTextureBindingLayout,\n }\n default:\n return null\n }\n })()\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/camera/Camera.mjs b/dist/esm/core/camera/Camera.mjs new file mode 100644 index 000000000..b01c4273c --- /dev/null +++ b/dist/esm/core/camera/Camera.mjs @@ -0,0 +1,281 @@ +import { Mat4 } from '../../math/Mat4.mjs'; +import { Object3D } from '../objects3D/Object3D.mjs'; +import { Vec3 } from '../../math/Vec3.mjs'; + +var __accessCheck = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); +}; +var __privateGet = (obj, member, getter) => { + __accessCheck(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); +}; +var __privateAdd = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); +}; +var __privateSet = (obj, member, value, setter) => { + __accessCheck(obj, member, "write to private field"); + setter ? setter.call(obj, value) : member.set(obj, value); + return value; +}; +var _fov, _near, _far, _pixelRatio; +class Camera extends Object3D { + /** + * Camera constructor + * @param parameters - {@link CameraParams | parameters} used to create our {@link Camera} + */ + constructor({ + fov = 50, + near = 0.1, + far = 150, + width = 1, + height = 1, + pixelRatio = 1, + onMatricesChanged = () => { + } + } = {}) { + super(); + /** Private {@link Camera} field of view */ + __privateAdd(this, _fov, void 0); + /** Private {@link Camera} near plane */ + __privateAdd(this, _near, void 0); + /** Private {@link Camera} far plane */ + __privateAdd(this, _far, void 0); + /** Private {@link Camera} pixel ratio, used in {@link CSSPerspective} calcs */ + __privateAdd(this, _pixelRatio, void 0); + this.position.set(0, 0, 10); + this.onMatricesChanged = onMatricesChanged; + this.size = { + width: 1, + height: 1 + }; + this.setPerspective({ fov, near, far, width, height, pixelRatio }); + } + /** + * Set our transform and projection matrices + */ + setMatrices() { + super.setMatrices(); + this.matrices = { + ...this.matrices, + view: { + matrix: new Mat4(), + shouldUpdate: false, + onUpdate: () => { + this.viewMatrix.copy(this.worldMatrix).invert(); + } + }, + projection: { + matrix: new Mat4(), + shouldUpdate: false, + onUpdate: () => this.updateProjectionMatrix() + } + }; + } + /** + * Get our view matrix + * @readonly + */ + get viewMatrix() { + return this.matrices.view.matrix; + } + set viewMatrix(value) { + this.matrices.view.matrix = value; + this.matrices.view.shouldUpdate = true; + } + /** + * Get our projection matrix + * @readonly + */ + get projectionMatrix() { + return this.matrices.projection.matrix; + } + set projectionMatrix(value) { + this.matrices.projection.matrix = value; + this.shouldUpdateProjectionMatrix(); + } + /** + * Set our projection matrix shouldUpdate flag to true (tell it to update) + */ + shouldUpdateProjectionMatrix() { + this.matrices.projection.shouldUpdate = true; + } + /** + * Update our model matrix and tell our view matrix to update as well + */ + updateModelMatrix() { + super.updateModelMatrix(); + this.setScreenRatios(); + this.matrices.view.shouldUpdate = true; + } + /** + * Update our world matrix and tell our view matrix to update as well + */ + updateWorldMatrix() { + super.updateWorldMatrix(); + this.matrices.view.shouldUpdate = true; + } + /** + * Get the {@link Camera} {@link fov | field of view} + */ + get fov() { + return __privateGet(this, _fov); + } + /** + * Set the {@link Camera} {@link fov | field of view}. Update the {@link projectionMatrix} only if the field of view actually changed + * @param fov - new field of view + */ + set fov(fov) { + fov = Math.max(1, Math.min(fov ?? this.fov, 179)); + if (fov !== this.fov) { + __privateSet(this, _fov, fov); + this.shouldUpdateProjectionMatrix(); + } + this.setScreenRatios(); + this.setCSSPerspective(); + } + /** + * Get the {@link Camera} {@link near} plane value. + */ + get near() { + return __privateGet(this, _near); + } + /** + * Set the {@link Camera} {@link near} plane value. Update the {@link projectionMatrix} only if the near plane actually changed + * @param near - new near plane value + */ + set near(near) { + near = Math.max(near ?? this.near, 0.01); + if (near !== this.near) { + __privateSet(this, _near, near); + this.shouldUpdateProjectionMatrix(); + } + } + /** + * Get / set the {@link Camera} {@link far} plane value. + */ + get far() { + return __privateGet(this, _far); + } + /** + * Set the {@link Camera} {@link far} plane value. Update {@link projectionMatrix} only if the far plane actually changed + * @param far - new far plane value + */ + set far(far) { + far = Math.max(far ?? this.far, this.near + 1); + if (far !== this.far) { + __privateSet(this, _far, far); + this.shouldUpdateProjectionMatrix(); + } + } + /** + * Get the {@link Camera} {@link pixelRatio} value. + */ + get pixelRatio() { + return __privateGet(this, _pixelRatio); + } + /** + * Set the {@link Camera} {@link pixelRatio} value. Update the {@link CSSPerspective} only if the pixel ratio actually changed + * @param pixelRatio - new pixel ratio value + */ + set pixelRatio(pixelRatio) { + __privateSet(this, _pixelRatio, pixelRatio ?? this.pixelRatio); + this.setCSSPerspective(); + } + /** + * Set the {@link Camera} {@link width} and {@link height}. Update the {@link projectionMatrix} only if the width or height actually changed + * @param size - {@link width} and {@link height} values to use + */ + setSize({ width, height }) { + if (width !== this.size.width || height !== this.size.height) { + this.shouldUpdateProjectionMatrix(); + } + this.size.width = width; + this.size.height = height; + this.setScreenRatios(); + this.setCSSPerspective(); + } + /** + * Sets the {@link Camera} perspective. Update the {@link projectionMatrix} if needed. + * @param parameters - {@link CameraPerspectiveOptions | parameters} to use for the perspective + */ + setPerspective({ + fov = this.fov, + near = this.near, + far = this.far, + width = this.size.width, + height = this.size.height, + pixelRatio = this.pixelRatio + } = {}) { + this.setSize({ width, height }); + this.pixelRatio = pixelRatio; + this.fov = fov; + this.near = near; + this.far = far; + } + /** + * Callback to run when the camera {@link modelMatrix | model matrix} has been updated + */ + onAfterMatrixStackUpdate() { + this.onMatricesChanged(); + } + /** + * Sets a {@link CSSPerspective} property based on {@link size}, {@link pixelRatio} and {@link fov}.
+ * Used to translate planes along the Z axis using pixel units as CSS would do.
+ * {@link https://stackoverflow.com/questions/22421439/convert-field-of-view-value-to-css3d-perspective-value | See reference} + */ + setCSSPerspective() { + this.CSSPerspective = Math.pow( + Math.pow(this.size.width / (2 * this.pixelRatio), 2) + Math.pow(this.size.height / (2 * this.pixelRatio), 2), + 0.5 + ) / Math.tan(this.fov * 0.5 * Math.PI / 180); + } + /** + * Sets visible width / height at a given z-depth from our {@link Camera} parameters.
+ * {@link https://discourse.threejs.org/t/functions-to-calculate-the-visible-width-height-at-a-given-z-depth-from-a-perspective-camera/269 | See reference} + * @param depth - depth to use for calculations + */ + setScreenRatios(depth = 0) { + const cameraOffset = this.position.z; + if (depth < cameraOffset) { + depth -= cameraOffset; + } else { + depth += cameraOffset; + } + const vFOV = this.fov * Math.PI / 180; + const height = 2 * Math.tan(vFOV / 2) * Math.abs(depth); + this.screenRatio = { + width: height * this.size.width / this.size.height, + height + }; + } + /** + * Rotate this {@link Camera} so it looks at the {@link Vec3 | target} + * @param target - {@link Vec3 | target} to look at + */ + lookAt(target = new Vec3()) { + const rotationMatrix = new Mat4().lookAt(this.position, target); + this.quaternion.setFromRotationMatrix(rotationMatrix); + this.shouldUpdateModelMatrix(); + } + /** + * Updates the {@link Camera} {@link projectionMatrix} + */ + updateProjectionMatrix() { + this.projectionMatrix.makePerspective({ + fov: this.fov, + aspect: this.size.width / this.size.height, + near: this.near, + far: this.far + }); + } +} +_fov = new WeakMap(); +_near = new WeakMap(); +_far = new WeakMap(); +_pixelRatio = new WeakMap(); + +export { Camera }; +//# sourceMappingURL=Camera.mjs.map diff --git a/dist/esm/core/camera/Camera.mjs.map b/dist/esm/core/camera/Camera.mjs.map new file mode 100644 index 000000000..6ee816680 --- /dev/null +++ b/dist/esm/core/camera/Camera.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"Camera.mjs","sources":["../../../../src/core/camera/Camera.ts"],"sourcesContent":["import { Mat4 } from '../../math/Mat4'\r\nimport { Object3D, Object3DMatricesType, Object3DTransformMatrix } from '../objects3D/Object3D'\r\nimport { RectSize } from '../DOM/DOMElement'\r\nimport { Vec3 } from '../../math/Vec3'\r\n\r\n/**\r\n * Defines Camera basic perspective options\r\n */\r\nexport interface CameraBasePerspectiveOptions {\r\n /** {@link Camera} perspective field of view. Should be greater than 0 and lower than 180 */\r\n fov?: number\r\n /** {@link Camera} near plane, the closest point where a mesh vertex is drawn */\r\n near?: number\r\n /** {@link Camera} far plane, the farthest point where a mesh vertex is drawn */\r\n far?: number\r\n}\r\n\r\n/**\r\n * Defines all Camera perspective options\r\n */\r\nexport interface CameraPerspectiveOptions extends CameraBasePerspectiveOptions {\r\n /** {@link Camera} frustum width */\r\n width?: number\r\n /** {@link Camera} frustum height */\r\n height?: number\r\n /** {@link Camera} pixel ratio */\r\n pixelRatio?: number\r\n}\r\n\r\n/**\r\n * An object defining all possible {@link Camera} class instancing parameters\r\n */\r\nexport interface CameraParams extends CameraPerspectiveOptions {\r\n /** callback to execute when one of the {@link Camera#matrices | camera matrices} changed */\r\n onMatricesChanged?: () => void\r\n}\r\n\r\n/** Defines all kind of possible {@link core/objects3D/ProjectedObject3D.ProjectedObject3D | ProjectedObject3D} matrix types */\r\nexport type CameraObject3DMatricesType = Object3DMatricesType | 'projection' | 'view'\r\n/** Defines all possible {@link Object3DTransformMatrix | matrix object} used by our {@link core/objects3D/ProjectedObject3D.ProjectedObject3D | ProjectedObject3D} */\r\nexport type CameraObject3DMatrices = Record\r\n\r\n/**\r\n * Used to create a perspective {@link Camera} and its projection, model and view matrices.\r\n *\r\n * {@link curtains/renderers/GPUCurtainsRenderer.GPUCurtainsRenderer | GPUCurtainsRenderer} and {@link core/renderers/GPUCameraRenderer.GPUCameraRenderer | GPUCameraRenderer} automatically create their own {@link Camera} under the hood, so it is unlikely you'd have to create one by yourself.\r\n *\r\n * {@link Camera} default perspective settings are:\r\n * - {@link Camera#fov | field of view}: 50\r\n * - {@link Camera#near | near plane}: 0.01\r\n * - {@link Camera#far | far plane}: 150\r\n *\r\n * Also note that the {@link Camera} default {@link Camera#position | position} is set at `(0, 0, 10)` so the object created with a default size do not appear too big nor too small.\r\n */\r\nexport class Camera extends Object3D {\r\n /** {@link CameraObject3DMatrices | Matrices object} of the {@link Camera} */\r\n matrices: CameraObject3DMatrices\r\n\r\n /** Private {@link Camera} field of view */\r\n #fov: number\r\n /** Private {@link Camera} near plane */\r\n #near: number\r\n /** Private {@link Camera} far plane */\r\n #far: number\r\n\r\n /** The {@link Camera} frustum width and height */\r\n size: RectSize\r\n /** Private {@link Camera} pixel ratio, used in {@link CSSPerspective} calcs */\r\n #pixelRatio: number\r\n\r\n /** Callback to execute when one of the camera {@link matrices} changed */\r\n onMatricesChanged?: () => void\r\n\r\n /** A number representing what CSS perspective value (in pixel) should be used to obtain the same perspective effect as this {@link Camera} */\r\n CSSPerspective: number\r\n /** An object containing the visible width / height at a given z-depth from our camera parameters */\r\n screenRatio: RectSize\r\n\r\n /**\r\n * Camera constructor\r\n * @param parameters - {@link CameraParams | parameters} used to create our {@link Camera}\r\n */\r\n constructor(\r\n {\r\n fov = 50,\r\n near = 0.1,\r\n far = 150,\r\n width = 1,\r\n height = 1,\r\n pixelRatio = 1,\r\n onMatricesChanged = () => {\r\n /* allow empty callback */\r\n },\r\n } = {} as CameraParams\r\n ) {\r\n // Object3D\r\n super()\r\n\r\n // camera can't be at position (0, 0, 0), it needs some recoil\r\n // arbitrarily set to 10 so objects of default size (1, 1, 1) don't appear too big\r\n this.position.set(0, 0, 10)\r\n\r\n // callback to run if any of the matrices changed\r\n this.onMatricesChanged = onMatricesChanged\r\n\r\n // create size object, will be set right after\r\n this.size = {\r\n width: 1,\r\n height: 1,\r\n }\r\n\r\n this.setPerspective({ fov, near, far, width, height, pixelRatio })\r\n }\r\n\r\n /**\r\n * Set our transform and projection matrices\r\n */\r\n setMatrices() {\r\n super.setMatrices()\r\n\r\n this.matrices = {\r\n ...this.matrices,\r\n view: {\r\n matrix: new Mat4(),\r\n shouldUpdate: false,\r\n onUpdate: () => {\r\n this.viewMatrix.copy(this.worldMatrix).invert()\r\n },\r\n },\r\n projection: {\r\n matrix: new Mat4(),\r\n shouldUpdate: false,\r\n onUpdate: () => this.updateProjectionMatrix(),\r\n },\r\n }\r\n }\r\n\r\n /**\r\n * Get our view matrix\r\n * @readonly\r\n */\r\n get viewMatrix(): Mat4 {\r\n return this.matrices.view.matrix\r\n }\r\n\r\n set viewMatrix(value: Mat4) {\r\n this.matrices.view.matrix = value\r\n this.matrices.view.shouldUpdate = true\r\n }\r\n\r\n /**\r\n * Get our projection matrix\r\n * @readonly\r\n */\r\n get projectionMatrix(): Mat4 {\r\n return this.matrices.projection.matrix\r\n }\r\n\r\n set projectionMatrix(value: Mat4) {\r\n this.matrices.projection.matrix = value\r\n this.shouldUpdateProjectionMatrix()\r\n }\r\n\r\n /**\r\n * Set our projection matrix shouldUpdate flag to true (tell it to update)\r\n */\r\n shouldUpdateProjectionMatrix() {\r\n this.matrices.projection.shouldUpdate = true\r\n }\r\n\r\n /**\r\n * Update our model matrix and tell our view matrix to update as well\r\n */\r\n updateModelMatrix() {\r\n super.updateModelMatrix()\r\n this.setScreenRatios()\r\n this.matrices.view.shouldUpdate = true\r\n }\r\n\r\n /**\r\n * Update our world matrix and tell our view matrix to update as well\r\n */\r\n updateWorldMatrix() {\r\n super.updateWorldMatrix()\r\n this.matrices.view.shouldUpdate = true\r\n }\r\n\r\n /**\r\n * Get the {@link Camera} {@link fov | field of view}\r\n */\r\n get fov(): number {\r\n return this.#fov\r\n }\r\n\r\n /**\r\n * Set the {@link Camera} {@link fov | field of view}. Update the {@link projectionMatrix} only if the field of view actually changed\r\n * @param fov - new field of view\r\n */\r\n set fov(fov: number) {\r\n // clamp between 1 and 179\r\n fov = Math.max(1, Math.min(fov ?? this.fov, 179))\r\n\r\n if (fov !== this.fov) {\r\n this.#fov = fov\r\n this.shouldUpdateProjectionMatrix()\r\n }\r\n\r\n this.setScreenRatios()\r\n this.setCSSPerspective()\r\n }\r\n\r\n /**\r\n * Get the {@link Camera} {@link near} plane value.\r\n */\r\n get near(): number {\r\n return this.#near\r\n }\r\n\r\n /**\r\n * Set the {@link Camera} {@link near} plane value. Update the {@link projectionMatrix} only if the near plane actually changed\r\n * @param near - new near plane value\r\n */\r\n set near(near: number) {\r\n near = Math.max(near ?? this.near, 0.01)\r\n\r\n if (near !== this.near) {\r\n this.#near = near\r\n this.shouldUpdateProjectionMatrix()\r\n }\r\n }\r\n\r\n /**\r\n * Get / set the {@link Camera} {@link far} plane value.\r\n */\r\n get far(): number {\r\n return this.#far\r\n }\r\n\r\n /**\r\n * Set the {@link Camera} {@link far} plane value. Update {@link projectionMatrix} only if the far plane actually changed\r\n * @param far - new far plane value\r\n */\r\n set far(far: number) {\r\n far = Math.max(far ?? this.far, this.near + 1)\r\n\r\n if (far !== this.far) {\r\n this.#far = far\r\n this.shouldUpdateProjectionMatrix()\r\n }\r\n }\r\n\r\n /**\r\n * Get the {@link Camera} {@link pixelRatio} value.\r\n */\r\n get pixelRatio() {\r\n return this.#pixelRatio\r\n }\r\n\r\n /**\r\n * Set the {@link Camera} {@link pixelRatio} value. Update the {@link CSSPerspective} only if the pixel ratio actually changed\r\n * @param pixelRatio - new pixel ratio value\r\n */\r\n set pixelRatio(pixelRatio: number) {\r\n this.#pixelRatio = pixelRatio ?? this.pixelRatio\r\n this.setCSSPerspective()\r\n }\r\n\r\n /**\r\n * Set the {@link Camera} {@link width} and {@link height}. Update the {@link projectionMatrix} only if the width or height actually changed\r\n * @param size - {@link width} and {@link height} values to use\r\n */\r\n setSize({ width, height }: RectSize) {\r\n if (width !== this.size.width || height !== this.size.height) {\r\n this.shouldUpdateProjectionMatrix()\r\n }\r\n\r\n this.size.width = width\r\n this.size.height = height\r\n\r\n this.setScreenRatios()\r\n this.setCSSPerspective()\r\n }\r\n\r\n /**\r\n * Sets the {@link Camera} perspective. Update the {@link projectionMatrix} if needed.\r\n * @param parameters - {@link CameraPerspectiveOptions | parameters} to use for the perspective\r\n */\r\n setPerspective({\r\n fov = this.fov,\r\n near = this.near,\r\n far = this.far,\r\n width = this.size.width,\r\n height = this.size.height,\r\n pixelRatio = this.pixelRatio,\r\n }: CameraPerspectiveOptions = {}) {\r\n this.setSize({ width, height })\r\n this.pixelRatio = pixelRatio\r\n this.fov = fov\r\n this.near = near\r\n this.far = far\r\n }\r\n\r\n /**\r\n * Callback to run when the camera {@link modelMatrix | model matrix} has been updated\r\n */\r\n onAfterMatrixStackUpdate() {\r\n // callback because matrices changed\r\n this.onMatricesChanged()\r\n }\r\n\r\n /**\r\n * Sets a {@link CSSPerspective} property based on {@link size}, {@link pixelRatio} and {@link fov}.
\r\n * Used to translate planes along the Z axis using pixel units as CSS would do.
\r\n * {@link https://stackoverflow.com/questions/22421439/convert-field-of-view-value-to-css3d-perspective-value | See reference}\r\n */\r\n setCSSPerspective() {\r\n this.CSSPerspective =\r\n Math.pow(\r\n Math.pow(this.size.width / (2 * this.pixelRatio), 2) + Math.pow(this.size.height / (2 * this.pixelRatio), 2),\r\n 0.5\r\n ) / Math.tan((this.fov * 0.5 * Math.PI) / 180)\r\n }\r\n\r\n /**\r\n * Sets visible width / height at a given z-depth from our {@link Camera} parameters.
\r\n * {@link https://discourse.threejs.org/t/functions-to-calculate-the-visible-width-height-at-a-given-z-depth-from-a-perspective-camera/269 | See reference}\r\n * @param depth - depth to use for calculations\r\n */\r\n setScreenRatios(depth = 0) {\r\n // compensate for cameras not positioned at z=0\r\n const cameraOffset = this.position.z\r\n if (depth < cameraOffset) {\r\n depth -= cameraOffset\r\n } else {\r\n depth += cameraOffset\r\n }\r\n\r\n // vertical fov in radians\r\n const vFOV = (this.fov * Math.PI) / 180\r\n\r\n // Math.abs to ensure the result is always positive\r\n const height = 2 * Math.tan(vFOV / 2) * Math.abs(depth)\r\n\r\n this.screenRatio = {\r\n width: (height * this.size.width) / this.size.height,\r\n height,\r\n }\r\n }\r\n\r\n /**\r\n * Rotate this {@link Camera} so it looks at the {@link Vec3 | target}\r\n * @param target - {@link Vec3 | target} to look at\r\n */\r\n lookAt(target: Vec3 = new Vec3()) {\r\n // since we know it's a camera, inverse position and target\r\n const rotationMatrix = new Mat4().lookAt(this.position, target)\r\n this.quaternion.setFromRotationMatrix(rotationMatrix)\r\n this.shouldUpdateModelMatrix()\r\n }\r\n\r\n /**\r\n * Updates the {@link Camera} {@link projectionMatrix}\r\n */\r\n updateProjectionMatrix() {\r\n this.projectionMatrix.makePerspective({\r\n fov: this.fov,\r\n aspect: this.size.width / this.size.height,\r\n near: this.near,\r\n far: this.far,\r\n })\r\n }\r\n}\r\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/computePasses/ComputePass.mjs b/dist/esm/core/computePasses/ComputePass.mjs new file mode 100644 index 000000000..36408eb20 --- /dev/null +++ b/dist/esm/core/computePasses/ComputePass.mjs @@ -0,0 +1,377 @@ +import { isRenderer } from '../renderers/utils.mjs'; +import { generateUUID } from '../../utils/utils.mjs'; +import { ComputeMaterial } from '../materials/ComputeMaterial.mjs'; +import { RenderTexture } from '../textures/RenderTexture.mjs'; +import { Texture } from '../textures/Texture.mjs'; + +var __accessCheck = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); +}; +var __privateGet = (obj, member, getter) => { + __accessCheck(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); +}; +var __privateAdd = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); +}; +var __privateSet = (obj, member, value, setter) => { + __accessCheck(obj, member, "write to private field"); + setter ? setter.call(obj, value) : member.set(obj, value); + return value; +}; +var _autoRender; +let computePassIndex = 0; +class ComputePass { + /** + * ComputePass constructor + * @param renderer - a {@link Renderer} class object or a {@link GPUCurtains} class object + * @param parameters - {@link ComputePassParams | parameters} used to create our {@link ComputePass} + */ + constructor(renderer, parameters = {}) { + /** + * Whether this {@link ComputePass} should be added to our {@link core/scenes/Scene.Scene | Scene} to let it handle the rendering process automatically + * @private + */ + __privateAdd(this, _autoRender, true); + // callbacks / events + /** function assigned to the {@link onReady} callback */ + this._onReadyCallback = () => { + }; + /** function assigned to the {@link onBeforeRender} callback */ + this._onBeforeRenderCallback = () => { + }; + /** function assigned to the {@link onRender} callback */ + this._onRenderCallback = () => { + }; + /** function assigned to the {@link onAfterRender} callback */ + this._onAfterRenderCallback = () => { + }; + /** function assigned to the {@link onAfterResize} callback */ + this._onAfterResizeCallback = () => { + }; + const type = "ComputePass"; + renderer = renderer && renderer.renderer || renderer; + isRenderer(renderer, parameters.label ? `${parameters.label} ${type}` : type); + parameters.label = parameters.label ?? "ComputePass " + renderer.computePasses?.length; + this.renderer = renderer; + this.type = type; + this.uuid = generateUUID(); + Object.defineProperty(this, "index", { value: computePassIndex++ }); + const { + label, + shaders, + renderOrder, + uniforms, + storages, + bindGroups, + samplers, + textures, + renderTextures, + autoRender, + useAsyncPipeline, + texturesOptions, + dispatchSize + } = parameters; + this.options = { + label, + shaders, + ...autoRender !== void 0 && { autoRender }, + ...renderOrder !== void 0 && { renderOrder }, + ...dispatchSize !== void 0 && { dispatchSize }, + useAsyncPipeline: useAsyncPipeline === void 0 ? true : useAsyncPipeline, + texturesOptions + // TODO default + }; + this.renderOrder = renderOrder ?? 0; + if (autoRender !== void 0) { + __privateSet(this, _autoRender, autoRender); + } + this.userData = {}; + this.ready = false; + this.setComputeMaterial({ + label: this.options.label, + shaders: this.options.shaders, + uniforms, + storages, + bindGroups, + samplers, + textures, + renderTextures, + useAsyncPipeline, + dispatchSize + }); + this.addToScene(); + } + /** + * Get or set whether the compute pass is ready to render (the material has been successfully compiled) + * @readonly + */ + get ready() { + return this._ready; + } + set ready(value) { + if (value) { + this._onReadyCallback && this._onReadyCallback(); + } + this._ready = value; + } + /** + * Add our compute pass to the scene and the renderer + */ + addToScene() { + this.renderer.computePasses.push(this); + if (__privateGet(this, _autoRender)) { + this.renderer.scene.addComputePass(this); + } + } + /** + * Remove our compute pass from the scene and the renderer + */ + removeFromScene() { + if (__privateGet(this, _autoRender)) { + this.renderer.scene.removeComputePass(this); + } + this.renderer.computePasses = this.renderer.computePasses.filter((computePass) => computePass.uuid !== this.uuid); + } + /** + * Create the compute pass material + * @param computeParameters - {@link ComputeMaterial} parameters + */ + setComputeMaterial(computeParameters) { + this.material = new ComputeMaterial(this.renderer, computeParameters); + } + /** + * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been lost to prepare everything for restoration. + * Basically set all the {@link GPUBuffer} to null so they will be reset next time we try to render + */ + loseContext() { + this.material.loseContext(); + } + /** + * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been restored + */ + restoreContext() { + this.material.restoreContext(); + } + /* TEXTURES */ + /** + * Get our {@link ComputeMaterial#textures | ComputeMaterial textures array} + * @readonly + */ + get textures() { + return this.material?.textures || []; + } + /** + * Get our {@link ComputeMaterial#renderTextures | ComputeMaterial render textures array} + * @readonly + */ + get renderTextures() { + return this.material?.renderTextures || []; + } + /** + * Create a new {@link Texture} + * @param options - {@link TextureParams | Texture parameters} + * @returns - newly created {@link Texture} + */ + createTexture(options) { + if (!options.name) { + options.name = "texture" + this.textures.length; + } + if (!options.label) { + options.label = this.options.label + " " + options.name; + } + const texture = new Texture(this.renderer, { ...options, ...this.options.texturesOptions }); + this.addTexture(texture); + return texture; + } + /** + * Add a {@link Texture} + * @param texture - {@link Texture} to add + */ + addTexture(texture) { + this.material.addTexture(texture); + } + /** + * Create a new {@link RenderTexture} + * @param options - {@link RenderTextureParams | RenderTexture parameters} + * @returns - newly created {@link RenderTexture} + */ + createRenderTexture(options) { + if (!options.name) { + options.name = "renderTexture" + this.renderTextures.length; + } + const renderTexture = new RenderTexture(this.renderer, options); + this.addRenderTexture(renderTexture); + return renderTexture; + } + /** + * Add a {@link RenderTexture} + * @param renderTexture - {@link RenderTexture} to add + */ + addRenderTexture(renderTexture) { + this.material.addTexture(renderTexture); + } + /** + * Get our {@link ComputeMaterial#uniforms | ComputeMaterial uniforms} + * @readonly + */ + get uniforms() { + return this.material?.uniforms; + } + /** + * Get our {@link ComputeMaterial#storages | ComputeMaterial storages} + * @readonly + */ + get storages() { + return this.material?.storages; + } + /** + * Called from the renderer, useful to trigger an after resize callback. + */ + resize() { + this._onAfterResizeCallback && this._onAfterResizeCallback(); + } + /** EVENTS **/ + /** + * Callback to run when the {@link ComputePass} is ready + * @param callback - callback to run when {@link ComputePass} is ready + */ + onReady(callback) { + if (callback) { + this._onReadyCallback = callback; + } + return this; + } + /** + * Callback to run before the {@link ComputePass} is rendered + * @param callback - callback to run just before {@link ComputePass} will be rendered + */ + onBeforeRender(callback) { + if (callback) { + this._onBeforeRenderCallback = callback; + } + return this; + } + /** + * Callback to run when the {@link ComputePass} is rendered + * @param callback - callback to run when {@link ComputePass} is rendered + */ + onRender(callback) { + if (callback) { + this._onRenderCallback = callback; + } + return this; + } + /** + * Callback to run after the {@link ComputePass} has been rendered + * @param callback - callback to run just after {@link ComputePass} has been rendered + */ + onAfterRender(callback) { + if (callback) { + this._onAfterRenderCallback = callback; + } + return this; + } + /** + * Callback used to run a custom render function instead of the default one. + * @param callback - Your custom render function where you will have to set all the {@link core/bindGroups/BindGroup.BindGroup | bind groups} and dispatch the workgroups by yourself. + */ + useCustomRender(callback) { + this.material.useCustomRender(callback); + return this; + } + /** + * Callback to run after the {@link core/renderers/GPURenderer.GPURenderer | renderer} has been resized + * @param callback - callback to run just after {@link core/renderers/GPURenderer.GPURenderer | renderer} has been resized + */ + onAfterResize(callback) { + if (callback) { + this._onAfterResizeCallback = callback; + } + return this; + } + /** + * Called before rendering the ComputePass + * Checks if the material is ready and eventually update its struct + */ + onBeforeRenderPass() { + if (!this.renderer.ready) + return; + if (this.material && this.material.ready && !this.ready) { + this.ready = true; + } + this._onBeforeRenderCallback && this._onBeforeRenderCallback(); + this.material.onBeforeRender(); + } + /** + * Render our {@link ComputeMaterial} + * @param pass - current compute pass encoder + */ + onRenderPass(pass) { + if (!this.material.ready) + return; + this._onRenderCallback && this._onRenderCallback(); + this.material.render(pass); + } + /** + * Called after having rendered the ComputePass + */ + onAfterRenderPass() { + this._onAfterRenderCallback && this._onAfterRenderCallback(); + } + /** + * Render our compute pass + * Basically just check if our {@link core/renderers/GPURenderer.GPURenderer | renderer} is ready, and then render our {@link ComputeMaterial} + * @param pass + */ + render(pass) { + this.onBeforeRenderPass(); + if (!this.renderer.ready) + return; + !this.renderer.production && pass.pushDebugGroup(this.options.label); + this.onRenderPass(pass); + !this.renderer.production && pass.popDebugGroup(); + this.onAfterRenderPass(); + } + /** + * Copy the result of our read/write GPUBuffer into our result binding array + * @param commandEncoder - current GPU command encoder + */ + copyBufferToResult(commandEncoder) { + this.material?.copyBufferToResult(commandEncoder); + } + /** + * Get the {@link core/bindings/WritableBufferBinding.WritableBufferBinding#resultBuffer | result GPU buffer} content by {@link core/bindings/WritableBufferBinding.WritableBufferBinding | binding} and {@link core/bindings/bufferElements/BufferElement.BufferElement | buffer element} names + * @param parameters - parameters used to get the result + * @param parameters.bindingName - {@link core/bindings/WritableBufferBinding.WritableBufferBinding#name | binding name} from which to get the result + * @param parameters.bufferElementName - optional {@link core/bindings/bufferElements/BufferElement.BufferElement | buffer element} (i.e. struct member) name if the result needs to be restrained to only one element + * @async + * @returns - the mapped content of the {@link GPUBuffer} as a {@link Float32Array} + */ + async getComputeResult({ + bindingName, + bufferElementName + }) { + return await this.material?.getComputeResult({ bindingName, bufferElementName }); + } + /** + * Remove the ComputePass from the scene and destroy it + */ + remove() { + this.removeFromScene(); + this.destroy(); + } + /** + * Destroy the ComputePass + */ + destroy() { + this.material?.destroy(); + } +} +_autoRender = new WeakMap(); + +export { ComputePass }; +//# sourceMappingURL=ComputePass.mjs.map diff --git a/dist/esm/core/computePasses/ComputePass.mjs.map b/dist/esm/core/computePasses/ComputePass.mjs.map new file mode 100644 index 000000000..64da7dfda --- /dev/null +++ b/dist/esm/core/computePasses/ComputePass.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"ComputePass.mjs","sources":["../../../../src/core/computePasses/ComputePass.ts"],"sourcesContent":["import { isRenderer, Renderer } from '../renderers/utils'\nimport { generateUUID } from '../../utils/utils'\nimport { ComputeMaterial } from '../materials/ComputeMaterial'\nimport { ComputeMaterialParams, MaterialParams, MaterialShaders } from '../../types/Materials'\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\nimport { RenderTexture, RenderTextureParams } from '../textures/RenderTexture'\nimport { Texture } from '../textures/Texture'\nimport { ExternalTextureParams, TextureParams } from '../../types/Textures'\n\n/** Defines {@link ComputePass} options */\nexport interface ComputePassOptions {\n /** The label of the {@link ComputePass} */\n label: string\n /** Controls the order in which this {@link ComputePass} should be rendered by our {@link core/scenes/Scene.Scene | Scene} */\n renderOrder?: number\n /** Whether the {@link ComputePass} should be added to our {@link core/scenes/Scene.Scene | Scene} to let it handle the rendering process automatically */\n autoRender?: boolean\n /** Compute shader passed to the {@link ComputePass} following the {@link types/Materials.ShaderOptions | shader object} notation */\n shaders: MaterialShaders\n /** whether the {@link core/pipelines/ComputePipelineEntry.ComputePipelineEntry#pipeline | compute pipeline} should be compiled asynchronously */\n useAsyncPipeline?: boolean\n /** Parameters used by this {@link ComputePass} to create a {@link Texture} */\n texturesOptions?: ExternalTextureParams\n /** Default {@link ComputeMaterial} work group dispatch size to use with this {@link ComputePass} */\n dispatchSize?: number | number[]\n}\n\n/**\n * An object defining all possible {@link ComputePass} class instancing parameters\n */\nexport interface ComputePassParams extends Partial, MaterialParams {}\n\nlet computePassIndex = 0\n\n/**\n * Used to create a {@link ComputePass}, i.e. run computations on the GPU.
\n * A {@link ComputePass} is basically a wrapper around a {@link ComputeMaterial} that handles most of the process.\n *\n * The default render behaviour of a {@link ComputePass} is to set its {@link core/bindGroups/BindGroup.BindGroup | bind groups} and then dispatch the workgroups based on the provided {@link ComputeMaterial#dispatchSize | dispatchSize}.
\n * However, most of the time you'd want a slightly more complex behaviour. The {@link ComputePass#useCustomRender | `useCustomRender` hook} lets you define a totally custom behaviour, but you'll have to set all the {@link core/bindGroups/BindGroup.BindGroup | bind groups} and dispatch the workgroups by yourself.\n *\n * @example\n * ```javascript\n * // set our main GPUCurtains instance\n * const gpuCurtains = new GPUCurtains({\n * container: '#canvas' // selector of our WebGPU canvas container\n * })\n *\n * // set the GPU device\n * // note this is asynchronous\n * await gpuCurtains.setDevice()\n *\n * // let's assume we are going to compute the positions of 100.000 particles\n * const nbParticles = 100_000\n *\n * const computePass = new ComputePass(gpuCurtains, {\n * label: 'My compute pass',\n * shaders: {\n * compute: {\n * code: computeShaderCode, // assume it is a valid WGSL compute shader\n * },\n * },\n * dispatchSize: Math.ceil(nbParticles / 64),\n * storages: {\n * particles: {\n * access: 'read_write',\n * struct: {\n * position: {\n * type: 'array',\n * value: new Float32Array(nbParticles * 4),\n * },\n * },\n * },\n * },\n * })\n * ```\n */\nexport class ComputePass {\n /** The type of the {@link ComputePass} */\n type: string\n /** The universal unique id of the {@link ComputePass} */\n uuid: string\n /** The index of the {@link ComputePass}, incremented each time a new one is instanced */\n index: number\n /** The {@link Renderer} used */\n renderer: Renderer\n /** Controls the order in which this {@link ComputePass} should be rendered by our {@link core/scenes/Scene.Scene | Scene} */\n renderOrder: number\n\n /** Options used to create this {@link ComputePass} */\n options: ComputePassOptions\n\n /** {@link ComputeMaterial} used by this {@link ComputePass} */\n material: ComputeMaterial\n\n /** Flag indicating whether this {@link ComputePass} is ready to be rendered */\n _ready: boolean\n\n /** Empty object to store any additional data or custom properties into your {@link ComputePass} */\n userData: Record\n\n /**\n * Whether this {@link ComputePass} should be added to our {@link core/scenes/Scene.Scene | Scene} to let it handle the rendering process automatically\n * @private\n */\n #autoRender = true\n\n // callbacks / events\n /** function assigned to the {@link onReady} callback */\n _onReadyCallback: () => void = () => {\n /* allow empty callback */\n }\n /** function assigned to the {@link onBeforeRender} callback */\n _onBeforeRenderCallback: () => void = () => {\n /* allow empty callback */\n }\n /** function assigned to the {@link onRender} callback */\n _onRenderCallback: () => void = () => {\n /* allow empty callback */\n }\n /** function assigned to the {@link onAfterRender} callback */\n _onAfterRenderCallback: () => void = () => {\n /* allow empty callback */\n }\n /** function assigned to the {@link onAfterResize} callback */\n _onAfterResizeCallback: () => void = () => {\n /* allow empty callback */\n }\n\n /**\n * ComputePass constructor\n * @param renderer - a {@link Renderer} class object or a {@link GPUCurtains} class object\n * @param parameters - {@link ComputePassParams | parameters} used to create our {@link ComputePass}\n */\n constructor(renderer: Renderer | GPUCurtains, parameters: ComputePassParams = {}) {\n const type = 'ComputePass'\n\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\n\n isRenderer(renderer, parameters.label ? `${parameters.label} ${type}` : type)\n\n parameters.label = parameters.label ?? 'ComputePass ' + renderer.computePasses?.length\n\n this.renderer = renderer\n this.type = type\n this.uuid = generateUUID()\n Object.defineProperty(this as ComputePass, 'index', { value: computePassIndex++ })\n\n const {\n label,\n shaders,\n renderOrder,\n uniforms,\n storages,\n bindGroups,\n samplers,\n textures,\n renderTextures,\n autoRender,\n useAsyncPipeline,\n texturesOptions,\n dispatchSize,\n } = parameters\n\n this.options = {\n label,\n shaders,\n ...(autoRender !== undefined && { autoRender }),\n ...(renderOrder !== undefined && { renderOrder }),\n ...(dispatchSize !== undefined && { dispatchSize }),\n useAsyncPipeline: useAsyncPipeline === undefined ? true : useAsyncPipeline,\n texturesOptions, // TODO default\n }\n\n this.renderOrder = renderOrder ?? 0\n\n if (autoRender !== undefined) {\n this.#autoRender = autoRender\n }\n\n this.userData = {}\n\n this.ready = false\n\n this.setComputeMaterial({\n label: this.options.label,\n shaders: this.options.shaders,\n uniforms,\n storages,\n bindGroups,\n samplers,\n textures,\n renderTextures,\n useAsyncPipeline,\n dispatchSize,\n })\n\n this.addToScene()\n }\n\n /**\n * Get or set whether the compute pass is ready to render (the material has been successfully compiled)\n * @readonly\n */\n get ready(): boolean {\n return this._ready\n }\n\n set ready(value: boolean) {\n if (value) {\n this._onReadyCallback && this._onReadyCallback()\n }\n this._ready = value\n }\n\n /**\n * Add our compute pass to the scene and the renderer\n */\n addToScene() {\n this.renderer.computePasses.push(this)\n\n if (this.#autoRender) {\n this.renderer.scene.addComputePass(this)\n }\n }\n\n /**\n * Remove our compute pass from the scene and the renderer\n */\n removeFromScene() {\n if (this.#autoRender) {\n this.renderer.scene.removeComputePass(this)\n }\n\n this.renderer.computePasses = this.renderer.computePasses.filter((computePass) => computePass.uuid !== this.uuid)\n }\n\n /**\n * Create the compute pass material\n * @param computeParameters - {@link ComputeMaterial} parameters\n */\n setComputeMaterial(computeParameters: ComputeMaterialParams) {\n this.material = new ComputeMaterial(this.renderer, computeParameters)\n }\n\n /**\n * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been lost to prepare everything for restoration.\n * Basically set all the {@link GPUBuffer} to null so they will be reset next time we try to render\n */\n loseContext() {\n this.material.loseContext()\n }\n\n /**\n * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been restored\n */\n restoreContext() {\n this.material.restoreContext()\n }\n\n /* TEXTURES */\n\n /**\n * Get our {@link ComputeMaterial#textures | ComputeMaterial textures array}\n * @readonly\n */\n get textures(): Texture[] {\n return this.material?.textures || []\n }\n\n /**\n * Get our {@link ComputeMaterial#renderTextures | ComputeMaterial render textures array}\n * @readonly\n */\n get renderTextures(): RenderTexture[] {\n return this.material?.renderTextures || []\n }\n\n /**\n * Create a new {@link Texture}\n * @param options - {@link TextureParams | Texture parameters}\n * @returns - newly created {@link Texture}\n */\n createTexture(options: TextureParams): Texture {\n if (!options.name) {\n options.name = 'texture' + this.textures.length\n }\n\n if (!options.label) {\n options.label = this.options.label + ' ' + options.name\n }\n\n const texture = new Texture(this.renderer, { ...options, ...this.options.texturesOptions })\n\n this.addTexture(texture)\n\n return texture\n }\n\n /**\n * Add a {@link Texture}\n * @param texture - {@link Texture} to add\n */\n addTexture(texture: Texture) {\n this.material.addTexture(texture)\n }\n\n /**\n * Create a new {@link RenderTexture}\n * @param options - {@link RenderTextureParams | RenderTexture parameters}\n * @returns - newly created {@link RenderTexture}\n */\n createRenderTexture(options: RenderTextureParams): RenderTexture {\n if (!options.name) {\n options.name = 'renderTexture' + this.renderTextures.length\n }\n\n const renderTexture = new RenderTexture(this.renderer, options)\n\n this.addRenderTexture(renderTexture)\n\n return renderTexture\n }\n\n /**\n * Add a {@link RenderTexture}\n * @param renderTexture - {@link RenderTexture} to add\n */\n addRenderTexture(renderTexture: RenderTexture) {\n this.material.addTexture(renderTexture)\n }\n\n /**\n * Get our {@link ComputeMaterial#uniforms | ComputeMaterial uniforms}\n * @readonly\n */\n get uniforms(): ComputeMaterial['uniforms'] {\n return this.material?.uniforms\n }\n\n /**\n * Get our {@link ComputeMaterial#storages | ComputeMaterial storages}\n * @readonly\n */\n get storages(): ComputeMaterial['storages'] {\n return this.material?.storages\n }\n\n /**\n * Called from the renderer, useful to trigger an after resize callback.\n */\n resize() {\n this._onAfterResizeCallback && this._onAfterResizeCallback()\n }\n\n /** EVENTS **/\n\n /**\n * Callback to run when the {@link ComputePass} is ready\n * @param callback - callback to run when {@link ComputePass} is ready\n */\n onReady(callback: () => void): ComputePass {\n if (callback) {\n this._onReadyCallback = callback\n }\n\n return this\n }\n\n /**\n * Callback to run before the {@link ComputePass} is rendered\n * @param callback - callback to run just before {@link ComputePass} will be rendered\n */\n onBeforeRender(callback: () => void): ComputePass {\n if (callback) {\n this._onBeforeRenderCallback = callback\n }\n\n return this\n }\n\n /**\n * Callback to run when the {@link ComputePass} is rendered\n * @param callback - callback to run when {@link ComputePass} is rendered\n */\n onRender(callback: () => void): ComputePass {\n if (callback) {\n this._onRenderCallback = callback\n }\n\n return this\n }\n\n /**\n * Callback to run after the {@link ComputePass} has been rendered\n * @param callback - callback to run just after {@link ComputePass} has been rendered\n */\n onAfterRender(callback: () => void): ComputePass {\n if (callback) {\n this._onAfterRenderCallback = callback\n }\n\n return this\n }\n\n /**\n * Callback used to run a custom render function instead of the default one.\n * @param callback - Your custom render function where you will have to set all the {@link core/bindGroups/BindGroup.BindGroup | bind groups} and dispatch the workgroups by yourself.\n */\n useCustomRender(callback: (pass: GPUComputePassEncoder) => void): ComputePass {\n this.material.useCustomRender(callback)\n return this\n }\n\n /**\n * Callback to run after the {@link core/renderers/GPURenderer.GPURenderer | renderer} has been resized\n * @param callback - callback to run just after {@link core/renderers/GPURenderer.GPURenderer | renderer} has been resized\n */\n onAfterResize(callback: () => void): ComputePass {\n if (callback) {\n this._onAfterResizeCallback = callback\n }\n\n return this\n }\n\n /**\n * Called before rendering the ComputePass\n * Checks if the material is ready and eventually update its struct\n */\n onBeforeRenderPass() {\n if (!this.renderer.ready) return\n\n if (this.material && this.material.ready && !this.ready) {\n this.ready = true\n }\n\n this._onBeforeRenderCallback && this._onBeforeRenderCallback()\n\n this.material.onBeforeRender()\n }\n\n /**\n * Render our {@link ComputeMaterial}\n * @param pass - current compute pass encoder\n */\n onRenderPass(pass: GPUComputePassEncoder) {\n if (!this.material.ready) return\n\n this._onRenderCallback && this._onRenderCallback()\n\n this.material.render(pass)\n }\n\n /**\n * Called after having rendered the ComputePass\n */\n onAfterRenderPass() {\n this._onAfterRenderCallback && this._onAfterRenderCallback()\n }\n\n /**\n * Render our compute pass\n * Basically just check if our {@link core/renderers/GPURenderer.GPURenderer | renderer} is ready, and then render our {@link ComputeMaterial}\n * @param pass\n */\n render(pass: GPUComputePassEncoder) {\n this.onBeforeRenderPass()\n\n // no point to render if the WebGPU device is not ready\n if (!this.renderer.ready) return\n\n !this.renderer.production && pass.pushDebugGroup(this.options.label)\n\n this.onRenderPass(pass)\n\n !this.renderer.production && pass.popDebugGroup()\n\n this.onAfterRenderPass()\n }\n\n /**\n * Copy the result of our read/write GPUBuffer into our result binding array\n * @param commandEncoder - current GPU command encoder\n */\n copyBufferToResult(commandEncoder: GPUCommandEncoder) {\n this.material?.copyBufferToResult(commandEncoder)\n }\n\n /**\n * Get the {@link core/bindings/WritableBufferBinding.WritableBufferBinding#resultBuffer | result GPU buffer} content by {@link core/bindings/WritableBufferBinding.WritableBufferBinding | binding} and {@link core/bindings/bufferElements/BufferElement.BufferElement | buffer element} names\n * @param parameters - parameters used to get the result\n * @param parameters.bindingName - {@link core/bindings/WritableBufferBinding.WritableBufferBinding#name | binding name} from which to get the result\n * @param parameters.bufferElementName - optional {@link core/bindings/bufferElements/BufferElement.BufferElement | buffer element} (i.e. struct member) name if the result needs to be restrained to only one element\n * @async\n * @returns - the mapped content of the {@link GPUBuffer} as a {@link Float32Array}\n */\n async getComputeResult({\n bindingName,\n bufferElementName,\n }: {\n bindingName?: string\n bufferElementName?: string\n }): Promise {\n return await this.material?.getComputeResult({ bindingName, bufferElementName })\n }\n\n /**\n * Remove the ComputePass from the scene and destroy it\n */\n remove() {\n this.removeFromScene()\n this.destroy()\n }\n\n /**\n * Destroy the ComputePass\n */\n destroy() {\n this.material?.destroy()\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/geometries/Geometry.mjs b/dist/esm/core/geometries/Geometry.mjs new file mode 100644 index 000000000..ab92791f3 --- /dev/null +++ b/dist/esm/core/geometries/Geometry.mjs @@ -0,0 +1,288 @@ +import { Box3 } from '../../math/Box3.mjs'; +import { throwWarning, throwError } from '../../utils/utils.mjs'; + +var __accessCheck = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); +}; +var __privateAdd = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); +}; +var __privateMethod = (obj, member, method) => { + __accessCheck(obj, member, "access private method"); + return method; +}; +var _setWGSLFragment, setWGSLFragment_fn; +class Geometry { + /** + * Geometry constructor + * @param parameters - {@link GeometryParams | parameters} used to create our Geometry + */ + constructor({ + verticesOrder = "ccw", + topology = "triangle-list", + instancesCount = 1, + vertexBuffers = [] + } = {}) { + /** + * Set the WGSL code snippet that will be appended to the vertex shader. + * @private + */ + __privateAdd(this, _setWGSLFragment); + this.verticesCount = 0; + this.verticesOrder = verticesOrder; + this.topology = topology; + this.instancesCount = instancesCount; + this.boundingBox = new Box3(); + this.type = "Geometry"; + this.vertexBuffers = []; + this.addVertexBuffer({ + name: "attributes" + }); + this.options = { + verticesOrder, + instancesCount, + vertexBuffers, + topology + }; + vertexBuffers.forEach((vertexBuffer) => { + this.addVertexBuffer({ + stepMode: vertexBuffer.stepMode ?? "vertex", + name: vertexBuffer.name, + attributes: vertexBuffer.attributes + }); + }); + } + /** + * Get whether this Geometry is ready to compute, i.e. if its first vertex buffer array has not been created yet + * @readonly + */ + get shouldCompute() { + return this.vertexBuffers.length && !this.vertexBuffers[0].array; + } + /** + * Get whether this geometry is ready to draw, i.e. it has been computed and all its vertex buffers have been created + * @readonly + */ + get ready() { + return !this.shouldCompute && !this.vertexBuffers.find((vertexBuffer) => !vertexBuffer.buffer); + } + /** + * Add a vertex buffer to our Geometry, set its attributes and return it + * @param parameters - vertex buffer {@link VertexBufferParams | parameters} + * @returns - newly created {@link VertexBuffer | vertex buffer} + */ + addVertexBuffer({ stepMode = "vertex", name, attributes = [] } = {}) { + const vertexBuffer = { + name: name ?? "attributes" + this.vertexBuffers.length, + stepMode, + arrayStride: 0, + bufferLength: 0, + attributes: [], + buffer: null + }; + attributes?.forEach((attribute) => { + this.setAttribute({ + vertexBuffer, + ...attribute + }); + }); + this.vertexBuffers.push(vertexBuffer); + return vertexBuffer; + } + /** + * Get a vertex buffer by name + * @param name - our vertex buffer name + * @returns - found {@link VertexBuffer | vertex buffer} or null if not found + */ + getVertexBufferByName(name = "") { + return this.vertexBuffers.find((vertexBuffer) => vertexBuffer.name === name); + } + /** + * Set a vertex buffer attribute + * @param parameters - attributes {@link VertexBufferAttributeParams | parameters} + */ + setAttribute({ + vertexBuffer = this.vertexBuffers[0], + name, + type = "vec3f", + bufferFormat = "float32x3", + size = 3, + array = new Float32Array(this.verticesCount * size), + verticesStride = 1 + }) { + const attributes = vertexBuffer.attributes; + const attributesLength = attributes.length; + if (!name) + name = "geometryAttribute" + attributesLength; + if (name === "position" && (type !== "vec3f" || bufferFormat !== "float32x3" || size !== 3)) { + throwWarning( + `Geometry 'position' attribute must have this exact properties set: + type: 'vec3f', + bufferFormat: 'float32x3', + size: 3` + ); + type = "vec3f"; + bufferFormat = "float32x3"; + size = 3; + } + const attributeCount = array.length / size; + if (name === "position") { + this.verticesCount = attributeCount; + } + if (vertexBuffer.stepMode === "vertex" && this.verticesCount && this.verticesCount !== attributeCount * verticesStride) { + throwError( + `Geometry vertex attribute error. Attribute array of size ${size} must be of length: ${this.verticesCount * size}, current given: ${array.length}. (${this.verticesCount} vertices).` + ); + } else if (vertexBuffer.stepMode === "instance" && attributeCount !== this.instancesCount) { + throwError( + `Geometry instance attribute error. Attribute array of size ${size} must be of length: ${this.instancesCount * size}, current given: ${array.length}. (${this.instancesCount} instances).` + ); + } + const attribute = { + name, + type, + bufferFormat, + size, + bufferLength: array.length, + offset: attributesLength ? attributes.reduce((accumulator, currentValue) => { + return accumulator + currentValue.bufferLength; + }, 0) : 0, + bufferOffset: attributesLength ? attributes[attributesLength - 1].bufferOffset + attributes[attributesLength - 1].size * 4 : 0, + array, + verticesStride + }; + vertexBuffer.bufferLength += attribute.bufferLength * verticesStride; + vertexBuffer.arrayStride += attribute.size; + vertexBuffer.attributes.push(attribute); + } + /** + * Get an attribute by name + * @param name - name of the attribute to find + * @returns - found {@link VertexBufferAttribute | attribute} or null if not found + */ + getAttributeByName(name) { + let attribute; + this.vertexBuffers.forEach((vertexBuffer) => { + attribute = vertexBuffer.attributes.find((attribute2) => attribute2.name === name); + }); + return attribute; + } + /** + * Compute a Geometry, which means iterate through all vertex buffers and create the attributes array that will be sent as buffers. + * Also compute the Geometry bounding box. + */ + computeGeometry() { + if (!this.shouldCompute) + return; + this.vertexBuffers.forEach((vertexBuffer, index) => { + if (index === 0) { + const hasPositionAttribute = vertexBuffer.attributes.find( + (attribute) => attribute.name === "position" + ); + if (!hasPositionAttribute) { + throwError(`Geometry must have a 'position' attribute`); + } + if (hasPositionAttribute.type !== "vec3f" || hasPositionAttribute.bufferFormat !== "float32x3" || hasPositionAttribute.size !== 3) { + throwWarning( + `Geometry 'position' attribute must have this exact properties set: + type: 'vec3f', + bufferFormat: 'float32x3', + size: 3` + ); + hasPositionAttribute.type = "vec3f"; + hasPositionAttribute.bufferFormat = "float32x3"; + hasPositionAttribute.size = 3; + } + } + vertexBuffer.array = new Float32Array(vertexBuffer.bufferLength); + let currentIndex = 0; + let attributeIndex = 0; + for (let i = 0; i < vertexBuffer.bufferLength; i += vertexBuffer.arrayStride) { + for (let j = 0; j < vertexBuffer.attributes.length; j++) { + const { name, size, array, verticesStride } = vertexBuffer.attributes[j]; + for (let s = 0; s < size; s++) { + const attributeValue = array[Math.floor(attributeIndex / verticesStride) * size + s]; + vertexBuffer.array[currentIndex] = attributeValue; + if (name === "position") { + if (s % 3 === 0) { + if (this.boundingBox.min.x > attributeValue) + this.boundingBox.min.x = attributeValue; + if (this.boundingBox.max.x < attributeValue) + this.boundingBox.max.x = attributeValue; + } else if (s % 3 === 1) { + if (this.boundingBox.min.y > attributeValue) + this.boundingBox.min.y = attributeValue; + if (this.boundingBox.max.y < attributeValue) + this.boundingBox.max.y = attributeValue; + } else if (s % 3 === 2) { + if (this.boundingBox.min.z > attributeValue) + this.boundingBox.min.z = attributeValue; + if (this.boundingBox.max.z < attributeValue) + this.boundingBox.max.z = attributeValue; + } + } + currentIndex++; + } + } + attributeIndex++; + } + }); + __privateMethod(this, _setWGSLFragment, setWGSLFragment_fn).call(this); + } + /** RENDER **/ + /** + * Set our render pass geometry vertex buffers + * @param pass - current render pass + */ + setGeometryBuffers(pass) { + this.vertexBuffers.forEach((vertexBuffer, index) => { + pass.setVertexBuffer(index, vertexBuffer.buffer); + }); + } + /** + * Draw our geometry + * @param pass - current render pass + */ + drawGeometry(pass) { + pass.draw(this.verticesCount, this.instancesCount); + } + /** + * Set our vertex buffers then draw the geometry + * @param pass - current render pass + */ + render(pass) { + if (!this.ready) + return; + this.setGeometryBuffers(pass); + this.drawGeometry(pass); + } + /** + * Destroy our geometry vertex buffers + */ + destroy() { + this.vertexBuffers.forEach((vertexBuffer) => { + vertexBuffer.buffer?.destroy(); + vertexBuffer.buffer = null; + }); + } +} +_setWGSLFragment = new WeakSet(); +setWGSLFragment_fn = function() { + let locationIndex = -1; + this.wgslStructFragment = `struct Attributes { + @builtin(vertex_index) vertexIndex : u32, + @builtin(instance_index) instanceIndex : u32,${this.vertexBuffers.map((vertexBuffer) => { + return vertexBuffer.attributes.map((attribute) => { + locationIndex++; + return ` + @location(${locationIndex}) ${attribute.name}: ${attribute.type}`; + }); + }).join(",")} +};`; +}; + +export { Geometry }; +//# sourceMappingURL=Geometry.mjs.map diff --git a/dist/esm/core/geometries/Geometry.mjs.map b/dist/esm/core/geometries/Geometry.mjs.map new file mode 100644 index 000000000..ab90b5c25 --- /dev/null +++ b/dist/esm/core/geometries/Geometry.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"Geometry.mjs","sources":["../../../../src/core/geometries/Geometry.ts"],"sourcesContent":["import { Box3 } from '../../math/Box3'\nimport { throwError, throwWarning } from '../../utils/utils'\nimport {\n GeometryOptions,\n GeometryParams,\n VertexBuffer,\n VertexBufferAttribute,\n VertexBufferAttributeParams,\n VertexBufferParams\n} from '../../types/Geometries'\n\n/**\n * Used to create a {@link Geometry} from given parameters like instances count or geometry attributes (vertices, uvs, normals).
\n * Holds all attributes arrays, bounding box and create as WGSL code snippet for the vertex shader input attributes.\n *\n * During the {@link Geometry#render | render}, the {@link Geometry} is responsible for setting the {@link Geometry#vertexBuffers | vertexBuffers} and drawing the vertices.\n *\n * @example\n * ```javascript\n * const vertices = new Float32Array([\n * // first triangle\n * 1, 1, 0,\n * 1, -1, 0,\n * -1, -1, 0,\n *\n * // second triangle\n * 1, 1, 0,\n * -1, -1, 0,\n * -1, 1, 0\n * ])\n *\n * // create a quad geometry made of 2 triangles\n * const geometry = new Geometry()\n *\n * geometry.setAttribute({\n * name: 'position',\n * type: 'vec3f',\n * bufferFormat: 'float32x3',\n * size: 3,\n * bufferLength: vertices.length,\n * array: vertices,\n * })\n * ```\n */\nexport class Geometry {\n /** Number of vertices defined by this geometry */\n verticesCount: number\n /** Vertices order to be drawn by the {@link core/pipelines/RenderPipelineEntry.RenderPipelineEntry | render pipeline} */\n verticesOrder: GPUFrontFace\n /** {@link https://www.w3.org/TR/webgpu/#enumdef-gpuprimitivetopology | Topology} to use with this {@link Geometry}, i.e. whether to draw triangles or points */\n topology: GPUPrimitiveTopology\n /** Number of instances of this geometry to draw */\n instancesCount: number\n /** Array of {@link VertexBuffer | vertex buffers} to use with this geometry */\n vertexBuffers: VertexBuffer[]\n /** Options used to create this geometry */\n options: GeometryOptions\n /** The type of the geometry */\n type: string\n\n /** The bounding box of the geometry, i.e. two {@link math/Vec3.Vec3 | Vec3} defining the min and max positions to wrap this geometry in a cube */\n boundingBox: Box3\n\n /** A string to append to our shaders code describing the WGSL structure representing this geometry attributes */\n wgslStructFragment: string\n\n /**\n * Geometry constructor\n * @param parameters - {@link GeometryParams | parameters} used to create our Geometry\n */\n constructor({\n verticesOrder = 'ccw',\n topology = 'triangle-list',\n instancesCount = 1,\n vertexBuffers = [],\n }: GeometryParams = {}) {\n this.verticesCount = 0\n this.verticesOrder = verticesOrder\n this.topology = topology\n this.instancesCount = instancesCount\n\n this.boundingBox = new Box3()\n\n this.type = 'Geometry'\n\n this.vertexBuffers = []\n\n // should contain our vertex position / uv data at least\n this.addVertexBuffer({\n name: 'attributes',\n })\n\n this.options = {\n verticesOrder,\n instancesCount,\n vertexBuffers,\n topology,\n }\n\n vertexBuffers.forEach((vertexBuffer) => {\n this.addVertexBuffer({\n stepMode: vertexBuffer.stepMode ?? 'vertex',\n name: vertexBuffer.name,\n attributes: vertexBuffer.attributes,\n })\n })\n }\n\n /**\n * Get whether this Geometry is ready to compute, i.e. if its first vertex buffer array has not been created yet\n * @readonly\n */\n get shouldCompute(): boolean {\n return this.vertexBuffers.length && !this.vertexBuffers[0].array\n }\n\n /**\n * Get whether this geometry is ready to draw, i.e. it has been computed and all its vertex buffers have been created\n * @readonly\n */\n get ready(): boolean {\n return !this.shouldCompute && !this.vertexBuffers.find((vertexBuffer) => !vertexBuffer.buffer)\n }\n\n /**\n * Add a vertex buffer to our Geometry, set its attributes and return it\n * @param parameters - vertex buffer {@link VertexBufferParams | parameters}\n * @returns - newly created {@link VertexBuffer | vertex buffer}\n */\n addVertexBuffer({ stepMode = 'vertex', name, attributes = [] }: VertexBufferParams = {}): VertexBuffer {\n const vertexBuffer = {\n name: name ?? 'attributes' + this.vertexBuffers.length,\n stepMode,\n arrayStride: 0,\n bufferLength: 0,\n attributes: [],\n buffer: null,\n }\n\n // set attributes right away if possible\n attributes?.forEach((attribute) => {\n this.setAttribute({\n vertexBuffer,\n ...attribute,\n } as VertexBufferAttributeParams)\n })\n\n this.vertexBuffers.push(vertexBuffer)\n\n return vertexBuffer\n }\n\n /**\n * Get a vertex buffer by name\n * @param name - our vertex buffer name\n * @returns - found {@link VertexBuffer | vertex buffer} or null if not found\n */\n getVertexBufferByName(name = ''): VertexBuffer | null {\n return this.vertexBuffers.find((vertexBuffer) => vertexBuffer.name === name)\n }\n\n /**\n * Set a vertex buffer attribute\n * @param parameters - attributes {@link VertexBufferAttributeParams | parameters}\n */\n setAttribute({\n vertexBuffer = this.vertexBuffers[0],\n name,\n type = 'vec3f',\n bufferFormat = 'float32x3',\n size = 3,\n array = new Float32Array(this.verticesCount * size),\n verticesStride = 1,\n }: VertexBufferAttributeParams) {\n const attributes = vertexBuffer.attributes\n const attributesLength = attributes.length\n\n if (!name) name = 'geometryAttribute' + attributesLength\n\n if (name === 'position' && (type !== 'vec3f' || bufferFormat !== 'float32x3' || size !== 3)) {\n throwWarning(\n `Geometry 'position' attribute must have this exact properties set:\\n\\ttype: 'vec3f',\\n\\tbufferFormat: 'float32x3',\\n\\tsize: 3`\n )\n type = 'vec3f'\n bufferFormat = 'float32x3'\n size = 3\n }\n\n const attributeCount = array.length / size\n\n if (name === 'position') {\n this.verticesCount = attributeCount\n }\n\n if (\n vertexBuffer.stepMode === 'vertex' &&\n this.verticesCount &&\n this.verticesCount !== attributeCount * verticesStride\n ) {\n throwError(\n `Geometry vertex attribute error. Attribute array of size ${size} must be of length: ${\n this.verticesCount * size\n }, current given: ${array.length}. (${this.verticesCount} vertices).`\n )\n } else if (vertexBuffer.stepMode === 'instance' && attributeCount !== this.instancesCount) {\n throwError(\n `Geometry instance attribute error. Attribute array of size ${size} must be of length: ${\n this.instancesCount * size\n }, current given: ${array.length}. (${this.instancesCount} instances).`\n )\n }\n\n const attribute = {\n name,\n type,\n bufferFormat,\n size,\n bufferLength: array.length,\n offset: attributesLength\n ? attributes.reduce((accumulator: number, currentValue) => {\n return accumulator + currentValue.bufferLength\n }, 0)\n : 0,\n bufferOffset: attributesLength\n ? attributes[attributesLength - 1].bufferOffset + attributes[attributesLength - 1].size * 4\n : 0,\n array,\n verticesStride: verticesStride,\n }\n\n vertexBuffer.bufferLength += attribute.bufferLength * verticesStride\n vertexBuffer.arrayStride += attribute.size\n vertexBuffer.attributes.push(attribute)\n }\n\n /**\n * Get an attribute by name\n * @param name - name of the attribute to find\n * @returns - found {@link VertexBufferAttribute | attribute} or null if not found\n */\n getAttributeByName(name: string): VertexBufferAttribute | null {\n let attribute\n this.vertexBuffers.forEach((vertexBuffer) => {\n attribute = vertexBuffer.attributes.find((attribute) => attribute.name === name)\n })\n\n return attribute\n }\n\n /**\n * Compute a Geometry, which means iterate through all vertex buffers and create the attributes array that will be sent as buffers.\n * Also compute the Geometry bounding box.\n */\n computeGeometry() {\n if (!this.shouldCompute) return\n\n this.vertexBuffers.forEach((vertexBuffer, index) => {\n if (index === 0) {\n const hasPositionAttribute = vertexBuffer.attributes.find(\n (attribute) => attribute.name === 'position'\n ) as VertexBufferAttribute | null\n\n if (!hasPositionAttribute) {\n throwError(`Geometry must have a 'position' attribute`)\n }\n\n if (\n hasPositionAttribute.type !== 'vec3f' ||\n hasPositionAttribute.bufferFormat !== 'float32x3' ||\n hasPositionAttribute.size !== 3\n ) {\n throwWarning(\n `Geometry 'position' attribute must have this exact properties set:\\n\\ttype: 'vec3f',\\n\\tbufferFormat: 'float32x3',\\n\\tsize: 3`\n )\n hasPositionAttribute.type = 'vec3f'\n hasPositionAttribute.bufferFormat = 'float32x3'\n hasPositionAttribute.size = 3\n }\n }\n\n vertexBuffer.array = new Float32Array(vertexBuffer.bufferLength)\n\n let currentIndex = 0\n let attributeIndex = 0\n for (let i = 0; i < vertexBuffer.bufferLength; i += vertexBuffer.arrayStride) {\n for (let j = 0; j < vertexBuffer.attributes.length; j++) {\n const { name, size, array, verticesStride } = vertexBuffer.attributes[j]\n\n for (let s = 0; s < size; s++) {\n const attributeValue = array[Math.floor(attributeIndex / verticesStride) * size + s]\n vertexBuffer.array[currentIndex] = attributeValue\n\n // compute bounding box\n if (name === 'position') {\n if (s % 3 === 0) {\n // x\n if (this.boundingBox.min.x > attributeValue) this.boundingBox.min.x = attributeValue\n if (this.boundingBox.max.x < attributeValue) this.boundingBox.max.x = attributeValue\n } else if (s % 3 === 1) {\n // y\n if (this.boundingBox.min.y > attributeValue) this.boundingBox.min.y = attributeValue\n if (this.boundingBox.max.y < attributeValue) this.boundingBox.max.y = attributeValue\n } else if (s % 3 === 2) {\n // z\n if (this.boundingBox.min.z > attributeValue) this.boundingBox.min.z = attributeValue\n if (this.boundingBox.max.z < attributeValue) this.boundingBox.max.z = attributeValue\n }\n }\n\n currentIndex++\n }\n }\n\n attributeIndex++\n }\n })\n\n this.#setWGSLFragment()\n }\n\n /**\n * Set the WGSL code snippet that will be appended to the vertex shader.\n * @private\n */\n #setWGSLFragment() {\n let locationIndex = -1\n this.wgslStructFragment = `struct Attributes {\\n\\t@builtin(vertex_index) vertexIndex : u32,\\n\\t@builtin(instance_index) instanceIndex : u32,${this.vertexBuffers\n .map((vertexBuffer) => {\n return vertexBuffer.attributes.map((attribute) => {\n locationIndex++\n return `\\n\\t@location(${locationIndex}) ${attribute.name}: ${attribute.type}`\n })\n })\n .join(',')}\\n};`\n }\n\n /** RENDER **/\n\n /**\n * Set our render pass geometry vertex buffers\n * @param pass - current render pass\n */\n setGeometryBuffers(pass: GPURenderPassEncoder) {\n this.vertexBuffers.forEach((vertexBuffer, index) => {\n pass.setVertexBuffer(index, vertexBuffer.buffer)\n })\n }\n\n /**\n * Draw our geometry\n * @param pass - current render pass\n */\n drawGeometry(pass: GPURenderPassEncoder) {\n pass.draw(this.verticesCount, this.instancesCount)\n }\n\n /**\n * Set our vertex buffers then draw the geometry\n * @param pass - current render pass\n */\n render(pass: GPURenderPassEncoder) {\n if (!this.ready) return\n\n this.setGeometryBuffers(pass)\n this.drawGeometry(pass)\n }\n\n /**\n * Destroy our geometry vertex buffers\n */\n destroy() {\n this.vertexBuffers.forEach((vertexBuffer) => {\n vertexBuffer.buffer?.destroy()\n vertexBuffer.buffer = null\n })\n }\n}\n"],"names":["attribute"],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/geometries/IndexedGeometry.mjs b/dist/esm/core/geometries/IndexedGeometry.mjs new file mode 100644 index 000000000..453bd52bb --- /dev/null +++ b/dist/esm/core/geometries/IndexedGeometry.mjs @@ -0,0 +1,71 @@ +import { Geometry } from './Geometry.mjs'; + +class IndexedGeometry extends Geometry { + /** + * IndexedGeometry constructor + * @param parameters - {@link GeometryParams | parameters} used to create our IndexedGeometry + */ + constructor({ + verticesOrder = "ccw", + topology = "triangle-list", + instancesCount = 1, + vertexBuffers = [] + } = {}) { + super({ verticesOrder, topology, instancesCount, vertexBuffers }); + this.type = "IndexedGeometry"; + } + /** + * Get whether this geometry is ready to draw, i.e. it has been computed, all its vertex buffers have been created and its index buffer has been created as well + * @readonly + */ + get ready() { + return !this.shouldCompute && !this.vertexBuffers.find((vertexBuffer) => !vertexBuffer.buffer) && this.indexBuffer && !!this.indexBuffer.buffer; + } + /** + * If we have less than 65.536 vertices, we should use a Uin16Array to hold our index buffer values + * @readonly + */ + get useUint16IndexArray() { + return this.verticesCount < 256 * 256; + } + /** + * Set our {@link indexBuffer} + * @param parameters - {@link IndexedGeometryIndexBufferOptions | parameters} used to create our index buffer + */ + setIndexBuffer({ bufferFormat = "uint32", array = new Uint32Array(0) }) { + this.indexBuffer = { + array, + bufferFormat, + bufferLength: array.length, + buffer: null + }; + } + /** RENDER **/ + /** + * First, set our render pass geometry vertex buffers + * Then, set our render pass geometry index buffer + * @param pass - current render pass + */ + setGeometryBuffers(pass) { + super.setGeometryBuffers(pass); + pass.setIndexBuffer(this.indexBuffer.buffer, this.indexBuffer.bufferFormat); + } + /** + * Override the parentMesh draw method to draw indexed geometry + * @param pass - current render pass + */ + drawGeometry(pass) { + pass.drawIndexed(this.indexBuffer.bufferLength, this.instancesCount); + } + /** + * Destroy our indexed geometry vertex buffers and index buffer + */ + destroy() { + super.destroy(); + this.indexBuffer?.buffer?.destroy(); + this.indexBuffer.buffer = null; + } +} + +export { IndexedGeometry }; +//# sourceMappingURL=IndexedGeometry.mjs.map diff --git a/dist/esm/core/geometries/IndexedGeometry.mjs.map b/dist/esm/core/geometries/IndexedGeometry.mjs.map new file mode 100644 index 000000000..1901ee8bd --- /dev/null +++ b/dist/esm/core/geometries/IndexedGeometry.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"IndexedGeometry.mjs","sources":["../../../../src/core/geometries/IndexedGeometry.ts"],"sourcesContent":["import { Geometry } from './Geometry'\r\nimport { GeometryParams } from '../../types/Geometries'\r\n\r\n/**\r\n * Defines the available options to create an {@link IndexedGeometry#indexBuffer | index buffer}\r\n */\r\nexport interface IndexedGeometryIndexBufferOptions {\r\n /** index buffer format */\r\n bufferFormat?: GPUIndexFormat\r\n /** index buffer array */\r\n array?: Uint16Array | Uint32Array\r\n}\r\n\r\n/**\r\n * Defines an {@link IndexedGeometry#indexBuffer | index buffer}\r\n */\r\nexport interface IndexBuffer {\r\n /** index buffer format */\r\n bufferFormat: GPUIndexFormat\r\n /** index buffer array */\r\n array: Uint16Array | Uint32Array\r\n /** index buffer length */\r\n bufferLength: number\r\n /** index buffer {@link GPUBuffer} */\r\n buffer?: GPUBuffer\r\n}\r\n\r\n/**\r\n * Used to create an {@link IndexedGeometry} which holds an index array to use as an index buffer.\r\n *\r\n * The index array represents the order in which the attributes should be processed. This allows to create smaller vertex, uv and normal arrays.\r\n *\r\n * During the {@link IndexedGeometry#render | render}, the {@link IndexedGeometry} is responsible for setting the {@link IndexedGeometry#vertexBuffers | vertexBuffers} and drawing the indexed vertices.\r\n *\r\n * @example\r\n * ```javascript\r\n * const vertices = new Float32Array([\r\n * -1, -1, 0,\r\n * 1, -1, 0,\r\n * -1, 1, 0,\r\n * 1, 1, 0\r\n * ])\r\n *\r\n * // vertices index (order in which they should be drawn)\r\n * const indexArray = new Uint16Array([0, 2, 1, 1, 2, 3])\r\n *\r\n * // create an indexed quad geometry made of 4 vertices\r\n * const indexedGeometry = new IndexedGeometry()\r\n *\r\n * indexedGeometry.setAttribute({\r\n * name: 'position',\r\n * type: 'vec3f',\r\n * bufferFormat: 'float32x3',\r\n * size: 3,\r\n * bufferLength: vertices.length,\r\n * array: vertices,\r\n * })\r\n *\r\n * indexedGeometry.setIndexBuffer({\r\n * array: indexArray,\r\n * bufferFormat: 'uint16',\r\n * })\r\n * ```\r\n */\r\nexport class IndexedGeometry extends Geometry {\r\n /** Object containing our index buffer format & length, array and GPUBuffer */\r\n indexBuffer: IndexBuffer\r\n\r\n /**\r\n * IndexedGeometry constructor\r\n * @param parameters - {@link GeometryParams | parameters} used to create our IndexedGeometry\r\n */\r\n constructor({\r\n verticesOrder = 'ccw',\r\n topology = 'triangle-list',\r\n instancesCount = 1,\r\n vertexBuffers = [],\r\n }: GeometryParams = {}) {\r\n super({ verticesOrder, topology, instancesCount, vertexBuffers })\r\n\r\n this.type = 'IndexedGeometry'\r\n }\r\n\r\n /**\r\n * Get whether this geometry is ready to draw, i.e. it has been computed, all its vertex buffers have been created and its index buffer has been created as well\r\n * @readonly\r\n */\r\n get ready(): boolean {\r\n return (\r\n !this.shouldCompute &&\r\n !this.vertexBuffers.find((vertexBuffer) => !vertexBuffer.buffer) &&\r\n this.indexBuffer &&\r\n !!this.indexBuffer.buffer\r\n )\r\n }\r\n\r\n /**\r\n * If we have less than 65.536 vertices, we should use a Uin16Array to hold our index buffer values\r\n * @readonly\r\n */\r\n get useUint16IndexArray(): boolean {\r\n return this.verticesCount < 256 * 256\r\n }\r\n\r\n /**\r\n * Set our {@link indexBuffer}\r\n * @param parameters - {@link IndexedGeometryIndexBufferOptions | parameters} used to create our index buffer\r\n */\r\n setIndexBuffer({ bufferFormat = 'uint32', array = new Uint32Array(0) }: IndexedGeometryIndexBufferOptions) {\r\n this.indexBuffer = {\r\n array,\r\n bufferFormat,\r\n bufferLength: array.length,\r\n buffer: null,\r\n }\r\n }\r\n\r\n /** RENDER **/\r\n\r\n /**\r\n * First, set our render pass geometry vertex buffers\r\n * Then, set our render pass geometry index buffer\r\n * @param pass - current render pass\r\n */\r\n setGeometryBuffers(pass: GPURenderPassEncoder) {\r\n super.setGeometryBuffers(pass)\r\n\r\n pass.setIndexBuffer(this.indexBuffer.buffer, this.indexBuffer.bufferFormat)\r\n }\r\n\r\n /**\r\n * Override the parentMesh draw method to draw indexed geometry\r\n * @param pass - current render pass\r\n */\r\n drawGeometry(pass: GPURenderPassEncoder) {\r\n pass.drawIndexed(this.indexBuffer.bufferLength, this.instancesCount)\r\n }\r\n\r\n /**\r\n * Destroy our indexed geometry vertex buffers and index buffer\r\n */\r\n destroy() {\r\n super.destroy()\r\n\r\n this.indexBuffer?.buffer?.destroy()\r\n this.indexBuffer.buffer = null\r\n }\r\n}\r\n"],"names":[],"mappings":";;AAgEO,MAAM,wBAAwB,QAAS,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ5C,WAAY,CAAA;AAAA,IACV,aAAgB,GAAA,KAAA;AAAA,IAChB,QAAW,GAAA,eAAA;AAAA,IACX,cAAiB,GAAA,CAAA;AAAA,IACjB,gBAAgB,EAAC;AAAA,GACnB,GAAoB,EAAI,EAAA;AACtB,IAAA,KAAA,CAAM,EAAE,aAAA,EAAe,QAAU,EAAA,cAAA,EAAgB,eAAe,CAAA,CAAA;AAEhE,IAAA,IAAA,CAAK,IAAO,GAAA,iBAAA,CAAA;AAAA,GACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,KAAiB,GAAA;AACnB,IAAA,OACE,CAAC,IAAK,CAAA,aAAA,IACN,CAAC,IAAK,CAAA,aAAA,CAAc,KAAK,CAAC,YAAA,KAAiB,CAAC,YAAA,CAAa,MAAM,CAC/D,IAAA,IAAA,CAAK,eACL,CAAC,CAAC,KAAK,WAAY,CAAA,MAAA,CAAA;AAAA,GAEvB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,mBAA+B,GAAA;AACjC,IAAO,OAAA,IAAA,CAAK,gBAAgB,GAAM,GAAA,GAAA,CAAA;AAAA,GACpC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAA,CAAe,EAAE,YAAe,GAAA,QAAA,EAAU,QAAQ,IAAI,WAAA,CAAY,CAAC,CAAA,EAAwC,EAAA;AACzG,IAAA,IAAA,CAAK,WAAc,GAAA;AAAA,MACjB,KAAA;AAAA,MACA,YAAA;AAAA,MACA,cAAc,KAAM,CAAA,MAAA;AAAA,MACpB,MAAQ,EAAA,IAAA;AAAA,KACV,CAAA;AAAA,GACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,mBAAmB,IAA4B,EAAA;AAC7C,IAAA,KAAA,CAAM,mBAAmB,IAAI,CAAA,CAAA;AAE7B,IAAA,IAAA,CAAK,eAAe,IAAK,CAAA,WAAA,CAAY,MAAQ,EAAA,IAAA,CAAK,YAAY,YAAY,CAAA,CAAA;AAAA,GAC5E;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,IAA4B,EAAA;AACvC,IAAA,IAAA,CAAK,WAAY,CAAA,IAAA,CAAK,WAAY,CAAA,YAAA,EAAc,KAAK,cAAc,CAAA,CAAA;AAAA,GACrE;AAAA;AAAA;AAAA;AAAA,EAKA,OAAU,GAAA;AACR,IAAA,KAAA,CAAM,OAAQ,EAAA,CAAA;AAEd,IAAK,IAAA,CAAA,WAAA,EAAa,QAAQ,OAAQ,EAAA,CAAA;AAClC,IAAA,IAAA,CAAK,YAAY,MAAS,GAAA,IAAA,CAAA;AAAA,GAC5B;AACF;;;;"} \ No newline at end of file diff --git a/dist/esm/core/geometries/PlaneGeometry.mjs b/dist/esm/core/geometries/PlaneGeometry.mjs new file mode 100644 index 000000000..4682c5ee6 --- /dev/null +++ b/dist/esm/core/geometries/PlaneGeometry.mjs @@ -0,0 +1,102 @@ +import { IndexedGeometry } from './IndexedGeometry.mjs'; + +class PlaneGeometry extends IndexedGeometry { + /** + * PlaneGeometry constructor + * @param parameters - {@link PlaneGeometryParams | parameters} used to create our PlaneGeometry + */ + constructor({ + widthSegments = 1, + heightSegments = 1, + instancesCount = 1, + vertexBuffers = [], + topology + } = {}) { + super({ verticesOrder: "cw", topology, instancesCount, vertexBuffers }); + this.type = "PlaneGeometry"; + widthSegments = Math.floor(widthSegments); + heightSegments = Math.floor(heightSegments); + this.definition = { + id: widthSegments * heightSegments + widthSegments, + width: widthSegments, + height: heightSegments, + count: widthSegments * heightSegments + }; + const verticesCount = (this.definition.width + 1) * (this.definition.height + 1); + const attributes = this.getIndexedVerticesAndUVs(verticesCount); + Object.keys(attributes).forEach((attributeKey) => { + this.setAttribute(attributes[attributeKey]); + }); + this.setIndexArray(); + } + /** + * Set our PlaneGeometry index array + */ + setIndexArray() { + const indexArray = this.useUint16IndexArray ? new Uint16Array(this.definition.count * 6) : new Uint32Array(this.definition.count * 6); + let index = 0; + for (let y = 0; y < this.definition.height; y++) { + for (let x = 0; x < this.definition.width; x++) { + indexArray[index++] = x + y * (this.definition.width + 1); + indexArray[index++] = this.definition.width + x + 1 + y * (this.definition.width + 1); + indexArray[index++] = x + 1 + y * (this.definition.width + 1); + indexArray[index++] = x + 1 + y * (this.definition.width + 1); + indexArray[index++] = this.definition.width + x + 1 + y * (this.definition.width + 1); + indexArray[index++] = this.definition.width + x + 2 + y * (this.definition.width + 1); + } + } + this.setIndexBuffer({ + array: indexArray, + bufferFormat: this.useUint16IndexArray ? "uint16" : "uint32" + }); + } + /** + * Compute the UV and position arrays based on our plane widthSegments and heightSegments values and return the corresponding attributes + * @param verticesCount - {@link Geometry#verticesCount | number of vertices} of our {@link PlaneGeometry} + * @returns - our position and uv {@link VertexBufferAttributeParams | attributes} + */ + getIndexedVerticesAndUVs(verticesCount) { + const uv = { + name: "uv", + type: "vec2f", + bufferFormat: "float32x2", + size: 2, + array: new Float32Array(verticesCount * 2) + }; + const position = { + name: "position", + type: "vec3f", + bufferFormat: "float32x3", + // nb of triangles * 3 vertices per triangle * 3 coordinates per triangle + size: 3, + array: new Float32Array(verticesCount * 3) + }; + const normal = { + name: "normal", + type: "vec3f", + bufferFormat: "float32x3", + // nb of triangles * 3 vertices per triangle * 3 coordinates per triangle + size: 3, + array: new Float32Array(verticesCount * 3) + }; + let positionOffset = 0; + let normalOffset = 0; + let uvOffset = 0; + for (let y = 0; y <= this.definition.height; y++) { + for (let x = 0; x <= this.definition.width; x++) { + uv.array[uvOffset++] = x / this.definition.width; + uv.array[uvOffset++] = 1 - y / this.definition.height; + position.array[positionOffset++] = x * 2 / this.definition.width - 1; + position.array[positionOffset++] = y * 2 / this.definition.height - 1; + position.array[positionOffset++] = 0; + normal.array[normalOffset++] = 0; + normal.array[normalOffset++] = 0; + normal.array[normalOffset++] = 1; + } + } + return { position, uv, normal }; + } +} + +export { PlaneGeometry }; +//# sourceMappingURL=PlaneGeometry.mjs.map diff --git a/dist/esm/core/geometries/PlaneGeometry.mjs.map b/dist/esm/core/geometries/PlaneGeometry.mjs.map new file mode 100644 index 000000000..52d3eb40f --- /dev/null +++ b/dist/esm/core/geometries/PlaneGeometry.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"PlaneGeometry.mjs","sources":["../../../../src/core/geometries/PlaneGeometry.ts"],"sourcesContent":["import { IndexedGeometry } from './IndexedGeometry'\nimport { Geometry } from './Geometry'\nimport { GeometryBaseParams, VertexBufferAttributeParams } from '../../types/Geometries'\n\n/**\n * Parameters used to create a {@link PlaneGeometry}\n */\nexport interface PlaneGeometryParams extends GeometryBaseParams {\n /** Number of segments along the X axis */\n widthSegments?: number\n /** Number of segments along the Y axis */\n heightSegments?: number\n}\n\n/**\n * Used to create an indexed plane geometry based on the number of segments along the X and Y axis.\n *\n * This is how it will look for a 3x2 quad. Indexing will take care of drawing the right vertices in the right order.\n *\n *
\n *  0---1---2---3\n *  |  /|  /|  /|\n *  |/  |/  |/  |\n *  4---5---6---7\n *  |  /|  /|  /|\n *  |/  |/  |/  |\n *  8---9---10--11\n * 
\n *\n * @example\n * ```javascript\n * const planeGeometry = new PlaneGeometry()\n * ```\n */\nexport class PlaneGeometry extends IndexedGeometry {\n /**\n * Defines our {@link PlaneGeometry} definition based on the provided {@link PlaneGeometryParams | parameters}\n */\n definition: {\n /** unique id based on width and height segments, used to get {@link PlaneGeometry} from cache */\n id: number\n /** number of segments along the X axis */\n width: number\n /** number of segments along the Y axis */\n height: number\n /** total number of segments */\n count: number\n }\n\n /**\n * PlaneGeometry constructor\n * @param parameters - {@link PlaneGeometryParams | parameters} used to create our PlaneGeometry\n */\n constructor({\n widthSegments = 1,\n heightSegments = 1,\n instancesCount = 1,\n vertexBuffers = [],\n topology,\n }: PlaneGeometryParams = {}) {\n // plane geometries vertices are defined in the clockwise order\n super({ verticesOrder: 'cw', topology, instancesCount, vertexBuffers })\n\n this.type = 'PlaneGeometry'\n\n widthSegments = Math.floor(widthSegments)\n heightSegments = Math.floor(heightSegments)\n\n // unique plane geometry id based on width and height\n // used to get a geometry from cache\n this.definition = {\n id: widthSegments * heightSegments + widthSegments,\n width: widthSegments,\n height: heightSegments,\n count: widthSegments * heightSegments,\n }\n\n const verticesCount = (this.definition.width + 1) * (this.definition.height + 1)\n const attributes = this.getIndexedVerticesAndUVs(verticesCount)\n\n Object.keys(attributes).forEach((attributeKey) => {\n this.setAttribute(attributes[attributeKey] as VertexBufferAttributeParams)\n })\n\n this.setIndexArray()\n }\n\n /**\n * Set our PlaneGeometry index array\n */\n setIndexArray() {\n const indexArray = this.useUint16IndexArray\n ? new Uint16Array(this.definition.count * 6)\n : new Uint32Array(this.definition.count * 6)\n\n let index = 0\n\n for (let y = 0; y < this.definition.height; y++) {\n for (let x = 0; x < this.definition.width; x++) {\n indexArray[index++] = x + y * (this.definition.width + 1)\n indexArray[index++] = this.definition.width + x + 1 + y * (this.definition.width + 1)\n indexArray[index++] = x + 1 + y * (this.definition.width + 1)\n\n indexArray[index++] = x + 1 + y * (this.definition.width + 1)\n indexArray[index++] = this.definition.width + x + 1 + y * (this.definition.width + 1)\n indexArray[index++] = this.definition.width + x + 2 + y * (this.definition.width + 1)\n }\n }\n\n this.setIndexBuffer({\n array: indexArray,\n bufferFormat: this.useUint16IndexArray ? 'uint16' : 'uint32',\n })\n }\n\n /**\n * Compute the UV and position arrays based on our plane widthSegments and heightSegments values and return the corresponding attributes\n * @param verticesCount - {@link Geometry#verticesCount | number of vertices} of our {@link PlaneGeometry}\n * @returns - our position and uv {@link VertexBufferAttributeParams | attributes}\n */\n getIndexedVerticesAndUVs(verticesCount: Geometry['verticesCount']): Record {\n // geometry vertices and UVs\n const uv = {\n name: 'uv',\n type: 'vec2f',\n bufferFormat: 'float32x2',\n size: 2,\n array: new Float32Array(verticesCount * 2),\n }\n\n const position = {\n name: 'position',\n type: 'vec3f',\n bufferFormat: 'float32x3',\n // nb of triangles * 3 vertices per triangle * 3 coordinates per triangle\n size: 3,\n array: new Float32Array(verticesCount * 3),\n }\n\n const normal = {\n name: 'normal',\n type: 'vec3f',\n bufferFormat: 'float32x3',\n // nb of triangles * 3 vertices per triangle * 3 coordinates per triangle\n size: 3,\n array: new Float32Array(verticesCount * 3),\n }\n\n let positionOffset = 0\n let normalOffset = 0\n let uvOffset = 0\n\n // this is how it will look for a 3x2 quad\n // indexing will take care of drawing the right vertices at the right time\n // 0---1---2---3\n // | //| //| //|\n // |// |// |// |\n // 4---5---6---7\n // | //| //| //|\n // |// |// |// |\n // 8---9---10--11\n\n for (let y = 0; y <= this.definition.height; y++) {\n for (let x = 0; x <= this.definition.width; x++) {\n // uv\n uv.array[uvOffset++] = x / this.definition.width\n uv.array[uvOffset++] = 1 - y / this.definition.height\n\n // vertex position\n position.array[positionOffset++] = (x * 2) / this.definition.width - 1\n position.array[positionOffset++] = (y * 2) / this.definition.height - 1\n position.array[positionOffset++] = 0\n\n // normals are simple\n normal.array[normalOffset++] = 0\n normal.array[normalOffset++] = 0\n normal.array[normalOffset++] = 1\n }\n }\n\n return { position, uv, normal } as Record\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/materials/ComputeMaterial.mjs b/dist/esm/core/materials/ComputeMaterial.mjs new file mode 100644 index 000000000..47ac2de98 --- /dev/null +++ b/dist/esm/core/materials/ComputeMaterial.mjs @@ -0,0 +1,166 @@ +import { Material } from './Material.mjs'; +import { isRenderer } from '../renderers/utils.mjs'; + +class ComputeMaterial extends Material { + /** + * ComputeMaterial constructor + * @param renderer - our {@link Renderer} class object + * @param parameters - {@link ComputeMaterialParams | parameters} used to create our {@link ComputeMaterial} + */ + constructor(renderer, parameters) { + renderer = renderer && renderer.renderer || renderer; + const type = "ComputeMaterial"; + isRenderer(renderer, type); + super(renderer, parameters); + this.type = type; + this.renderer = renderer; + let { shaders, dispatchSize } = parameters; + if (!shaders || !shaders.compute) { + shaders = { + compute: { + code: "", + entryPoint: "main" + } + }; + } + if (!shaders.compute.code) { + shaders.compute.code = "@compute @workgroup_size(1) fn main(){}"; + } + if (!shaders.compute.entryPoint) { + shaders.compute.entryPoint = "main"; + } + this.options = { + ...this.options, + shaders, + ...parameters.dispatchSize !== void 0 && { dispatchSize: parameters.dispatchSize } + }; + if (!dispatchSize) { + dispatchSize = 1; + } + if (Array.isArray(dispatchSize)) { + dispatchSize[0] = Math.ceil(dispatchSize[0] ?? 1); + dispatchSize[1] = Math.ceil(dispatchSize[1] ?? 1); + dispatchSize[2] = Math.ceil(dispatchSize[2] ?? 1); + } else if (!isNaN(dispatchSize)) { + dispatchSize = [Math.ceil(dispatchSize), 1, 1]; + } + this.dispatchSize = dispatchSize; + this.pipelineEntry = this.renderer.pipelineManager.createComputePipeline({ + renderer: this.renderer, + label: this.options.label + " compute pipeline", + shaders: this.options.shaders, + useAsync: this.options.useAsyncPipeline + }); + } + /** + * When all bind groups are created, add them to the {@link ComputePipelineEntry} + */ + setPipelineEntryProperties() { + this.pipelineEntry.setPipelineEntryProperties({ + bindGroups: this.bindGroups + }); + } + /** + * Compile the {@link ComputePipelineEntry} + * @async + */ + async compilePipelineEntry() { + await this.pipelineEntry.compilePipelineEntry(); + } + /** + * Check if all bind groups are ready, create them if needed, set {@link ComputePipelineEntry} bind group buffers and compile the pipeline + * @async + */ + async compileMaterial() { + super.compileMaterial(); + if (this.pipelineEntry && this.pipelineEntry.canCompile) { + this.setPipelineEntryProperties(); + await this.compilePipelineEntry(); + } + } + /** + * Get the complete code of a given shader including all the WGSL fragment code snippets added by the pipeline + * @param [shaderType="compute"] - shader to get the code from + * @returns - The corresponding shader code + */ + getShaderCode(shaderType = "compute") { + return super.getShaderCode(shaderType); + } + /** + * Get the added code of a given shader, i.e. all the WGSL fragment code snippets added by the pipeline + * @param [shaderType="compute"] - shader to get the code from + * @returns - The corresponding shader code + */ + getAddedShaderCode(shaderType = "compute") { + return super.getAddedShaderCode(shaderType); + } + /* RENDER */ + /** + * If a custom render function has been defined instead of the default one, register the callback + * @param callback - callback to run instead of the default render behaviour, which is to set the {@link bindGroups | bind groups} and dispatch the work groups based on the {@link dispatchSize | default dispatch size}. This is where you will have to set all the {@link core/bindGroups/BindGroup.BindGroup | bind groups} and dispatch the workgroups by yourself. + */ + useCustomRender(callback) { + if (callback) { + this._useCustomRenderCallback = callback; + } + } + /** + * Render the material if it is ready: + * Set the current pipeline, set the bind groups and dispatch the work groups + * @param pass - current compute pass encoder + */ + render(pass) { + if (!this.ready) + return; + this.setPipeline(pass); + if (this._useCustomRenderCallback !== void 0) { + this._useCustomRenderCallback(pass); + } else { + this.bindGroups.forEach((bindGroup) => { + pass.setBindGroup(bindGroup.index, bindGroup.bindGroup); + }); + pass.dispatchWorkgroups(this.dispatchSize[0], this.dispatchSize[1], this.dispatchSize[2]); + } + } + /* RESULT BUFFER */ + /** + * Copy all writable binding buffers that need it + * @param commandEncoder - current command encoder + */ + copyBufferToResult(commandEncoder) { + this.bindGroups.forEach((bindGroup) => { + bindGroup.bufferBindings.forEach((binding) => { + if (binding.shouldCopyResult && binding.resultBuffer.mapState === "unmapped") { + commandEncoder.copyBufferToBuffer(binding.buffer, 0, binding.resultBuffer, 0, binding.resultBuffer.size); + } + }); + }); + } + /** + * Get the {@link core/bindings/WritableBufferBinding.WritableBufferBinding#resultBuffer | result GPU buffer} content by {@link core/bindings/WritableBufferBinding.WritableBufferBinding | binding} and {@link core/bindings/bufferElements/BufferElement.BufferElement | buffer element} names + * @param parameters - parameters used to get the result + * @param parameters.bindingName - {@link core/bindings/WritableBufferBinding.WritableBufferBinding#name | binding name} from which to get the result + * @param parameters.bufferElementName - optional {@link core/bindings/bufferElements/BufferElement.BufferElement | buffer element} (i.e. struct member) name if the result needs to be restrained to only one element + * @async + * @returns - the mapped content of the {@link GPUBuffer} as a {@link Float32Array} + */ + async getComputeResult({ + bindingName = "", + bufferElementName = "" + }) { + const binding = this.getBufferBindingByName(bindingName); + if (binding && "resultBuffer" in binding && binding.resultBuffer.mapState === "unmapped") { + const result = await this.getBufferResult(binding.resultBuffer); + if (bufferElementName) { + return binding.extractBufferElementDataFromBufferResult({ result, bufferElementName }); + } else { + return result; + } + } else { + return new Float32Array(0); + } + } +} + +export { ComputeMaterial }; +//# sourceMappingURL=ComputeMaterial.mjs.map diff --git a/dist/esm/core/materials/ComputeMaterial.mjs.map b/dist/esm/core/materials/ComputeMaterial.mjs.map new file mode 100644 index 000000000..c55f2a81c --- /dev/null +++ b/dist/esm/core/materials/ComputeMaterial.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"ComputeMaterial.mjs","sources":["../../../../src/core/materials/ComputeMaterial.ts"],"sourcesContent":["import { Material } from './Material'\nimport { ComputeMaterialOptions, ComputeMaterialParams, FullShadersType } from '../../types/Materials'\nimport { isRenderer, Renderer } from '../renderers/utils'\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\nimport { ComputePipelineEntry } from '../pipelines/ComputePipelineEntry'\nimport { WritableBufferBinding } from '../bindings/WritableBufferBinding'\n\n/**\n * Create a {@link Material} specifically built to run computations on the GPU. Internally used by {@link core/computePasses/ComputePass.ComputePass | ComputePass}.\n *\n * ## Compute pipeline\n *\n * A {@link ComputeMaterial} automatically creates a {@link ComputePipelineEntry}. Once all the {@link core/bindGroups/BindGroup.BindGroup | BindGroup} have been created, they are sent with the compute shader code to the {@link ComputePipelineEntry}, which is in turns responsible for creating the {@link GPUComputePipeline}.\n *\n * After the {@link GPUComputePipeline} has been successfully compiled, the {@link ComputeMaterial} is considered to be ready and it can start running the compute shader computations.\n *\n */\nexport class ComputeMaterial extends Material {\n /** {@link ComputePipelineEntry | Compute pipeline entry} used by this {@link ComputeMaterial} */\n pipelineEntry: ComputePipelineEntry\n /** Options used to create this {@link ComputeMaterial} */\n options: ComputeMaterialOptions\n\n /** Default work group dispatch size to use with this {@link ComputeMaterial} */\n dispatchSize?: number | number[]\n\n /** function assigned to the {@link useCustomRender} callback */\n _useCustomRenderCallback: (pass: GPUComputePassEncoder) => void\n\n /**\n * ComputeMaterial constructor\n * @param renderer - our {@link Renderer} class object\n * @param parameters - {@link ComputeMaterialParams | parameters} used to create our {@link ComputeMaterial}\n */\n constructor(renderer: Renderer | GPUCurtains, parameters: ComputeMaterialParams) {\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\n\n const type = 'ComputeMaterial'\n\n isRenderer(renderer, type)\n\n super(renderer, parameters)\n\n this.type = type\n this.renderer = renderer\n\n let { shaders, dispatchSize } = parameters\n\n if (!shaders || !shaders.compute) {\n shaders = {\n compute: {\n code: '',\n entryPoint: 'main',\n },\n }\n }\n\n if (!shaders.compute.code) {\n shaders.compute.code = '@compute @workgroup_size(1) fn main(){}'\n }\n\n if (!shaders.compute.entryPoint) {\n shaders.compute.entryPoint = 'main'\n }\n\n this.options = {\n ...this.options,\n shaders,\n ...(parameters.dispatchSize !== undefined && { dispatchSize: parameters.dispatchSize }),\n }\n\n // set default dispatch size\n if (!dispatchSize) {\n dispatchSize = 1\n }\n\n if (Array.isArray(dispatchSize)) {\n dispatchSize[0] = Math.ceil(dispatchSize[0] ?? 1)\n dispatchSize[1] = Math.ceil(dispatchSize[1] ?? 1)\n dispatchSize[2] = Math.ceil(dispatchSize[2] ?? 1)\n } else if (!isNaN(dispatchSize)) {\n dispatchSize = [Math.ceil(dispatchSize), 1, 1]\n }\n\n this.dispatchSize = dispatchSize\n\n this.pipelineEntry = this.renderer.pipelineManager.createComputePipeline({\n renderer: this.renderer,\n label: this.options.label + ' compute pipeline',\n shaders: this.options.shaders,\n useAsync: this.options.useAsyncPipeline,\n })\n }\n\n /**\n * When all bind groups are created, add them to the {@link ComputePipelineEntry}\n */\n setPipelineEntryProperties() {\n this.pipelineEntry.setPipelineEntryProperties({\n bindGroups: this.bindGroups,\n })\n }\n\n /**\n * Compile the {@link ComputePipelineEntry}\n * @async\n */\n async compilePipelineEntry(): Promise {\n await this.pipelineEntry.compilePipelineEntry()\n }\n\n /**\n * Check if all bind groups are ready, create them if needed, set {@link ComputePipelineEntry} bind group buffers and compile the pipeline\n * @async\n */\n async compileMaterial() {\n super.compileMaterial()\n\n if (this.pipelineEntry && this.pipelineEntry.canCompile) {\n this.setPipelineEntryProperties()\n await this.compilePipelineEntry()\n }\n }\n\n /**\n * Get the complete code of a given shader including all the WGSL fragment code snippets added by the pipeline\n * @param [shaderType=\"compute\"] - shader to get the code from\n * @returns - The corresponding shader code\n */\n getShaderCode(shaderType: FullShadersType = 'compute'): string {\n return super.getShaderCode(shaderType)\n }\n\n /**\n * Get the added code of a given shader, i.e. all the WGSL fragment code snippets added by the pipeline\n * @param [shaderType=\"compute\"] - shader to get the code from\n * @returns - The corresponding shader code\n */\n getAddedShaderCode(shaderType: FullShadersType = 'compute'): string {\n return super.getAddedShaderCode(shaderType)\n }\n\n /* RENDER */\n\n /**\n * If a custom render function has been defined instead of the default one, register the callback\n * @param callback - callback to run instead of the default render behaviour, which is to set the {@link bindGroups | bind groups} and dispatch the work groups based on the {@link dispatchSize | default dispatch size}. This is where you will have to set all the {@link core/bindGroups/BindGroup.BindGroup | bind groups} and dispatch the workgroups by yourself.\n */\n useCustomRender(callback: (pass: GPUComputePassEncoder) => void) {\n if (callback) {\n this._useCustomRenderCallback = callback\n }\n }\n\n /**\n * Render the material if it is ready:\n * Set the current pipeline, set the bind groups and dispatch the work groups\n * @param pass - current compute pass encoder\n */\n render(pass: GPUComputePassEncoder) {\n // renderer or pipeline are not ready yet\n // not really needed since compute passes do already check it beforehand\n // mostly here as a safeguard\n if (!this.ready) return\n\n // set current pipeline\n this.setPipeline(pass)\n\n // if we declared a custom render function, call it\n if (this._useCustomRenderCallback !== undefined) {\n this._useCustomRenderCallback(pass)\n } else {\n // else just set our bind groups and dispatch\n this.bindGroups.forEach((bindGroup) => {\n pass.setBindGroup(bindGroup.index, bindGroup.bindGroup)\n })\n\n pass.dispatchWorkgroups(this.dispatchSize[0], this.dispatchSize[1], this.dispatchSize[2])\n }\n }\n\n /* RESULT BUFFER */\n\n /**\n * Copy all writable binding buffers that need it\n * @param commandEncoder - current command encoder\n */\n copyBufferToResult(commandEncoder: GPUCommandEncoder) {\n this.bindGroups.forEach((bindGroup) => {\n bindGroup.bufferBindings.forEach((binding: WritableBufferBinding) => {\n if (binding.shouldCopyResult && binding.resultBuffer.mapState === 'unmapped') {\n commandEncoder.copyBufferToBuffer(binding.buffer, 0, binding.resultBuffer, 0, binding.resultBuffer.size)\n }\n })\n })\n }\n\n /**\n * Get the {@link core/bindings/WritableBufferBinding.WritableBufferBinding#resultBuffer | result GPU buffer} content by {@link core/bindings/WritableBufferBinding.WritableBufferBinding | binding} and {@link core/bindings/bufferElements/BufferElement.BufferElement | buffer element} names\n * @param parameters - parameters used to get the result\n * @param parameters.bindingName - {@link core/bindings/WritableBufferBinding.WritableBufferBinding#name | binding name} from which to get the result\n * @param parameters.bufferElementName - optional {@link core/bindings/bufferElements/BufferElement.BufferElement | buffer element} (i.e. struct member) name if the result needs to be restrained to only one element\n * @async\n * @returns - the mapped content of the {@link GPUBuffer} as a {@link Float32Array}\n */\n async getComputeResult({\n bindingName = '',\n bufferElementName = '',\n }: {\n bindingName?: string\n bufferElementName?: string\n }): Promise {\n const binding = this.getBufferBindingByName(bindingName)\n\n if (binding && 'resultBuffer' in binding && binding.resultBuffer.mapState === 'unmapped') {\n const result = await this.getBufferResult(binding.resultBuffer)\n\n if (bufferElementName) {\n return binding.extractBufferElementDataFromBufferResult({ result, bufferElementName })\n } else {\n return result\n }\n } else {\n return new Float32Array(0)\n }\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/materials/Material.mjs b/dist/esm/core/materials/Material.mjs new file mode 100644 index 000000000..a71b31307 --- /dev/null +++ b/dist/esm/core/materials/Material.mjs @@ -0,0 +1,513 @@ +import { isRenderer } from '../renderers/utils.mjs'; +import { BindGroup } from '../bindGroups/BindGroup.mjs'; +import { TextureBindGroup } from '../bindGroups/TextureBindGroup.mjs'; +import { Sampler } from '../samplers/Sampler.mjs'; +import { Texture } from '../textures/Texture.mjs'; +import { RenderTexture } from '../textures/RenderTexture.mjs'; +import { generateUUID } from '../../utils/utils.mjs'; + +class Material { + /** + * Material constructor + * @param renderer - our renderer class object + * @param parameters - {@link types/Materials.MaterialParams | parameters} used to create our Material + */ + constructor(renderer, parameters) { + this.type = "Material"; + renderer = renderer && renderer.renderer || renderer; + isRenderer(renderer, this.type); + this.renderer = renderer; + this.uuid = generateUUID(); + const { + shaders, + label, + useAsyncPipeline, + uniforms, + storages, + bindings, + bindGroups, + samplers, + textures, + renderTextures + } = parameters; + this.options = { + shaders, + label, + useAsyncPipeline: useAsyncPipeline === void 0 ? true : useAsyncPipeline, + ...uniforms !== void 0 && { uniforms }, + ...storages !== void 0 && { storages }, + ...bindings !== void 0 && { bindings }, + ...bindGroups !== void 0 && { bindGroups }, + ...samplers !== void 0 && { samplers }, + ...textures !== void 0 && { textures }, + ...renderTextures !== void 0 && { renderTextures } + }; + this.bindGroups = []; + this.texturesBindGroups = []; + this.clonedBindGroups = []; + this.setBindGroups(); + this.setTextures(); + this.setSamplers(); + } + /** + * Check if all bind groups are ready, and create them if needed + */ + compileMaterial() { + const texturesBindGroupLength = this.texturesBindGroup.bindings.length ? 1 : 0; + const bindGroupsReady = this.bindGroups.length >= this.inputsBindGroups.length + texturesBindGroupLength; + if (!bindGroupsReady) { + this.createBindGroups(); + } + } + /** + * Get whether the renderer is ready, our pipeline entry and pipeline have been created and successfully compiled + * @readonly + */ + get ready() { + return !!(this.renderer.ready && this.pipelineEntry && this.pipelineEntry.pipeline && this.pipelineEntry.ready); + } + /** + * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been lost to prepare everything for restoration. + * Basically set all the {@link GPUBuffer} to null so they will be reset next time we try to render + */ + loseContext() { + this.textures.forEach((texture) => { + texture.texture = null; + texture.sourceUploaded = false; + }); + this.renderTextures.forEach((texture) => { + texture.texture = null; + }); + [...this.bindGroups, ...this.clonedBindGroups, ...this.inputsBindGroups].forEach( + (bindGroup) => bindGroup.loseContext() + ); + this.pipelineEntry.pipeline = null; + } + /** + * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been restored to recreate our bind groups. + */ + restoreContext() { + this.samplers.forEach((sampler) => { + sampler.createSampler(); + sampler.binding.resource = sampler.sampler; + }); + this.textures.forEach((texture) => { + texture.createTexture(); + texture.resize(); + }); + this.renderTextures.forEach((texture) => { + texture.resize(texture.size); + }); + [...this.bindGroups, ...this.clonedBindGroups, ...this.inputsBindGroups].forEach((bindGroup) => { + if (bindGroup.shouldCreateBindGroup) { + bindGroup.createBindGroup(); + } + bindGroup.bufferBindings.forEach((bufferBinding) => bufferBinding.shouldUpdate = true); + }); + } + /** + * Get the complete code of a given shader including all the WGSL fragment code snippets added by the pipeline + * @param [shaderType="full"] - shader to get the code from + * @returns - The corresponding shader code + */ + getShaderCode(shaderType = "full") { + if (!this.pipelineEntry) + return ""; + shaderType = (() => { + switch (shaderType) { + case "vertex": + case "fragment": + case "compute": + case "full": + return shaderType; + default: + return "full"; + } + })(); + return this.pipelineEntry.shaders[shaderType].code; + } + /** + * Get the added code of a given shader, i.e. all the WGSL fragment code snippets added by the pipeline + * @param [shaderType="vertex"] - shader to get the code from + * @returns - The corresponding shader code + */ + getAddedShaderCode(shaderType = "vertex") { + if (!this.pipelineEntry) + return ""; + shaderType = (() => { + switch (shaderType) { + case "vertex": + case "fragment": + case "compute": + return shaderType; + default: + return "vertex"; + } + })(); + return this.pipelineEntry.shaders[shaderType].head; + } + /* BIND GROUPS */ + /** + * Prepare and set our bind groups based on inputs and bindGroups Material parameters + */ + setBindGroups() { + this.uniforms = {}; + this.storages = {}; + this.inputsBindGroups = []; + this.inputsBindings = []; + if (this.options.uniforms || this.options.storages || this.options.bindings) { + const inputsBindGroup = new BindGroup(this.renderer, { + label: this.options.label + ": Bindings bind group", + uniforms: this.options.uniforms, + storages: this.options.storages, + bindings: this.options.bindings + }); + this.processBindGroupBindings(inputsBindGroup); + this.inputsBindGroups.push(inputsBindGroup); + } + this.options.bindGroups?.forEach((bindGroup) => { + this.processBindGroupBindings(bindGroup); + this.inputsBindGroups.push(bindGroup); + }); + } + /** + * Get the main {@link TextureBindGroup | texture bind group} created by this {@link Material} to manage all textures related struct + * @readonly + */ + get texturesBindGroup() { + return this.texturesBindGroups[0]; + } + /** + * Process all {@link BindGroup} struct and add them to the corresponding objects based on their binding types. Also store them in a inputsBindings array to facilitate further access to struct. + * @param bindGroup - The {@link BindGroup} to process + */ + processBindGroupBindings(bindGroup) { + bindGroup.bindings.forEach((inputBinding) => { + if (inputBinding.bindingType === "uniform") + this.uniforms = { + ...this.uniforms, + [inputBinding.name]: inputBinding.inputs + }; + if (inputBinding.bindingType === "storage") + this.storages = { + ...this.storages, + [inputBinding.name]: inputBinding.inputs + }; + this.inputsBindings.push(inputBinding); + }); + } + /** + * Create the bind groups if they need to be created + */ + createBindGroups() { + if (this.texturesBindGroup.shouldCreateBindGroup) { + this.texturesBindGroup.setIndex(this.bindGroups.length); + this.texturesBindGroup.createBindGroup(); + this.bindGroups.push(this.texturesBindGroup); + } + this.inputsBindGroups.forEach((bindGroup) => { + if (bindGroup.shouldCreateBindGroup) { + bindGroup.setIndex(this.bindGroups.length); + bindGroup.createBindGroup(); + this.bindGroups.push(bindGroup); + } + }); + this.options.bindGroups?.forEach((bindGroup) => { + if (!bindGroup.shouldCreateBindGroup && !this.bindGroups.find((bG) => bG.uuid === bindGroup.uuid)) { + bindGroup.setIndex(this.bindGroups.length); + this.bindGroups.push(bindGroup); + } + if (bindGroup instanceof TextureBindGroup && !this.texturesBindGroups.find((bG) => bG.uuid === bindGroup.uuid)) { + this.texturesBindGroups.push(bindGroup); + bindGroup.textures.forEach((texture) => { + if (texture instanceof Texture && !this.textures.find((t) => t.uuid === texture.uuid)) { + this.textures.push(texture); + } else if (texture instanceof RenderTexture && !this.renderTextures.find((t) => t.uuid === texture.uuid)) { + this.renderTextures.push(texture); + } + }); + } + }); + } + /** + * Clones a {@link BindGroup} from a list of buffers + * Useful to create a new bind group with already created buffers, but swapped + * @param parameters - parameters used to clone the {@link BindGroup | bind group} + * @param parameters.bindGroup - the BindGroup to clone + * @param parameters.bindings - our input binding buffers + * @param parameters.keepLayout - whether we should keep original bind group layout or not + * @returns - the cloned BindGroup + */ + cloneBindGroup({ + bindGroup, + bindings = [], + keepLayout = true + }) { + if (!bindGroup) + return null; + const clone = bindGroup.clone({ bindings, keepLayout }); + this.clonedBindGroups.push(clone); + return clone; + } + /** + * Get a corresponding {@link BindGroup} or {@link TextureBindGroup} from one of its binding name/key + * @param bindingName - the binding name/key to look for + * @returns - bind group found or null if not found + */ + getBindGroupByBindingName(bindingName = "") { + return (this.ready ? this.bindGroups : this.inputsBindGroups).find((bindGroup) => { + return bindGroup.bindings.find((binding) => binding.name === bindingName); + }); + } + /** + * Destroy a bind group, only if it is not used by another object + * @param bindGroup - bind group to eventually destroy + */ + destroyBindGroup(bindGroup) { + const objectsUsingBindGroup = this.renderer.getObjectsByBindGroup(bindGroup); + const shouldDestroy = !objectsUsingBindGroup || !objectsUsingBindGroup.find((object) => object.material.uuid !== this.uuid); + if (shouldDestroy) { + bindGroup.destroy(); + } + } + /** + * Destroy all bind groups + */ + destroyBindGroups() { + this.bindGroups.forEach((bindGroup) => this.destroyBindGroup(bindGroup)); + this.clonedBindGroups.forEach((bindGroup) => this.destroyBindGroup(bindGroup)); + this.texturesBindGroups.forEach((bindGroup) => this.destroyBindGroup(bindGroup)); + this.texturesBindGroups = []; + this.inputsBindGroups = []; + this.bindGroups = []; + this.clonedBindGroups = []; + } + /** + * {@link BindGroup#update | Update} all bind groups: + * - Update all {@link texturesBindGroups | textures bind groups} textures + * - Update its {@link BindGroup#bufferBindings | buffer bindings} + * - Check if it eventually needs a {@link BindGroup#resetBindGroup | reset} + * - Check if we need to flush the pipeline + */ + updateBindGroups() { + this.bindGroups.forEach((bindGroup) => { + bindGroup.update(); + if (bindGroup.needsPipelineFlush && this.pipelineEntry.ready) { + this.pipelineEntry.flushPipelineEntry(this.bindGroups); + bindGroup.needsPipelineFlush = false; + } + }); + } + /* INPUTS */ + /** + * Look for a {@link BindGroupBindingElement | binding} by name in all {@link inputsBindings | input bindings} + * @param bindingName - the binding name or key + * @returns - the found binding, or null if not found + */ + getBindingByName(bindingName = "") { + return this.inputsBindings.find((binding) => binding.name === bindingName); + } + /** + * Look for a {@link BindGroupBufferBindingElement | buffer binding} by name in all {@link inputsBindings | input bindings} + * @param bindingName - the binding name or key + * @returns - the found binding, or null if not found + */ + getBufferBindingByName(bindingName = "") { + return this.inputsBindings.find((binding) => binding.name === bindingName && "buffer" in binding); + } + /** + * Force a given buffer binding update flag to update it at next render + * @param bufferBindingName - the buffer binding name + * @param bindingName - the binding name + */ + shouldUpdateInputsBindings(bufferBindingName, bindingName) { + if (!bufferBindingName) + return; + const bufferBinding = this.getBindingByName(bufferBindingName); + if (bufferBinding) { + if (!bindingName) { + Object.keys(bufferBinding.inputs).forEach( + (bindingKey) => bufferBinding.shouldUpdateBinding(bindingKey) + ); + } else { + bufferBinding.shouldUpdateBinding(bindingName); + } + } + } + /* SAMPLERS & TEXTURES */ + /** + * Prepare our textures array and set the {@link TextureBindGroup} + */ + setTextures() { + this.textures = []; + this.renderTextures = []; + this.texturesBindGroups.push( + new TextureBindGroup(this.renderer, { + label: this.options.label + ": Textures bind group" + }) + ); + this.options.textures?.forEach((texture) => { + this.addTexture(texture); + }); + this.options.renderTextures?.forEach((texture) => { + this.addTexture(texture); + }); + } + /** + * Add a texture to our array, and add it to the textures bind group only if used in the shaders (avoid binding useless data) + * @param texture - texture to add + */ + addTexture(texture) { + if (texture instanceof Texture) { + this.textures.push(texture); + } else if (texture instanceof RenderTexture) { + this.renderTextures.push(texture); + } + if (this.options.shaders.vertex && this.options.shaders.vertex.code.indexOf(texture.options.name) !== -1 || this.options.shaders.fragment && this.options.shaders.fragment.code.indexOf(texture.options.name) !== -1 || this.options.shaders.compute && this.options.shaders.compute.code.indexOf(texture.options.name) !== -1) { + this.texturesBindGroup.addTexture(texture); + } + } + /** + * Destroy a {@link Texture} or {@link RenderTexture}, only if it is not used by another object or cached. + * @param texture - {@link Texture} or {@link RenderTexture} to eventually destroy + */ + destroyTexture(texture) { + if (texture.options.cache) + return; + const objectsUsingTexture = this.renderer.getObjectsByTexture(texture); + const shouldDestroy = !objectsUsingTexture || !objectsUsingTexture.some((object) => object.material.uuid !== this.uuid); + if (shouldDestroy) { + texture.destroy(); + } + } + /** + * Destroy all the Material textures + */ + destroyTextures() { + this.textures?.forEach((texture) => this.destroyTexture(texture)); + this.renderTextures?.forEach((texture) => this.destroyTexture(texture)); + this.textures = []; + this.renderTextures = []; + } + /** + * Prepare our samplers array and always add a default sampler if not already passed as parameter + */ + setSamplers() { + this.samplers = []; + this.options.samplers?.forEach((sampler) => { + this.addSampler(sampler); + }); + const hasDefaultSampler = this.samplers.find((sampler) => sampler.name === "defaultSampler"); + if (!hasDefaultSampler) { + const sampler = new Sampler(this.renderer, { name: "defaultSampler" }); + this.addSampler(sampler); + } + } + /** + * Add a sampler to our array, and add it to the textures bind group only if used in the shaders (avoid binding useless data) + * @param sampler - sampler to add + */ + addSampler(sampler) { + this.samplers.push(sampler); + if (this.options.shaders.vertex && this.options.shaders.vertex.code.indexOf(sampler.name) !== -1 || this.options.shaders.fragment && this.options.shaders.fragment.code.indexOf(sampler.name) !== -1 || this.options.shaders.compute && this.options.shaders.compute.code.indexOf(sampler.name) !== -1) { + this.texturesBindGroup.addSampler(sampler); + } + } + /* BUFFER RESULTS */ + /** + * Map a {@link GPUBuffer} and put a copy of the data into a {@link Float32Array} + * @param buffer - {@link GPUBuffer} to map + * @async + * @returns - {@link Float32Array} holding the {@link GPUBuffer} data + */ + async getBufferResult(buffer) { + await buffer.mapAsync(GPUMapMode.READ); + const result = new Float32Array(buffer.getMappedRange().slice(0)); + buffer.unmap(); + return result; + } + /** + * Map the content of a {@link BufferBinding#buffer | GPU buffer} and put a copy of the data into a {@link Float32Array} + * @param bindingName - The name of the {@link inputsBindings | input bindings} from which to map the {@link BufferBinding#buffer | GPU buffer} + * @async + * @returns - {@link Float32Array} holding the {@link GPUBuffer} data + */ + async getBufferBindingResultByBindingName(bindingName = "") { + const binding = this.getBufferBindingByName(bindingName); + if (binding && "buffer" in binding) { + const dstBuffer = this.renderer.copyBufferToBuffer({ + srcBuffer: binding.buffer + }); + return await this.getBufferResult(dstBuffer); + } else { + return new Float32Array(0); + } + } + /** + * Map the content of a specific {@link BufferElement | buffer element} belonging to a {@link BufferBinding#buffer | GPU buffer} and put a copy of the data into a {@link Float32Array} + * @param parameters - parameters used to get the result + * @param parameters.bindingName - The name of the {@link inputsBindings | input bindings} from which to map the {@link BufferBinding#buffer | GPU buffer} + * @param parameters.bufferElementName - The name of the {@link BufferElement | buffer element} from which to extract the data afterwards + * @returns - {@link Float32Array} holding {@link GPUBuffer} data + */ + async getBufferElementResultByNames({ + bindingName, + bufferElementName + }) { + const result = await this.getBufferBindingResultByBindingName(bindingName); + if (!bufferElementName || result.length) { + return result; + } else { + const binding = this.getBufferBindingByName(bindingName); + if (binding) { + return binding.extractBufferElementDataFromBufferResult({ result, bufferElementName }); + } else { + return result; + } + } + } + /* RENDER */ + /** + * Called before rendering the Material. + * First, check if we need to create our bind groups or pipeline + * Then render the {@link textures} + * Finally updates all the {@link bindGroups | bind groups} + */ + onBeforeRender() { + this.compileMaterial(); + this.textures.forEach((texture) => { + texture.render(); + }); + this.updateBindGroups(); + } + /** + * Set the current pipeline + * @param pass - current pass encoder + */ + setPipeline(pass) { + this.renderer.pipelineManager.setCurrentPipeline(pass, this.pipelineEntry); + } + /** + * Render the material if it is ready: + * Set the current pipeline and set the bind groups + * @param pass - current pass encoder + */ + render(pass) { + if (!this.ready) + return; + this.setPipeline(pass); + this.bindGroups.forEach((bindGroup) => { + pass.setBindGroup(bindGroup.index, bindGroup.bindGroup); + }); + } + /** + * Destroy the Material + */ + destroy() { + this.destroyBindGroups(); + this.destroyTextures(); + } +} + +export { Material }; +//# sourceMappingURL=Material.mjs.map diff --git a/dist/esm/core/materials/Material.mjs.map b/dist/esm/core/materials/Material.mjs.map new file mode 100644 index 000000000..2c22393a3 --- /dev/null +++ b/dist/esm/core/materials/Material.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"Material.mjs","sources":["../../../../src/core/materials/Material.ts"],"sourcesContent":["import { isRenderer, Renderer } from '../renderers/utils'\nimport { BindGroup } from '../bindGroups/BindGroup'\nimport { TextureBindGroup } from '../bindGroups/TextureBindGroup'\nimport { Sampler } from '../samplers/Sampler'\nimport { AllowedPipelineEntries } from '../pipelines/PipelineManager'\nimport { BufferBinding, BufferBindingInput } from '../bindings/BufferBinding'\nimport { AllowedBindGroups, BindGroupBindingElement, BindGroupBufferBindingElement } from '../../types/BindGroups'\nimport { Texture } from '../textures/Texture'\nimport { FullShadersType, MaterialOptions, MaterialParams, ShaderOptions } from '../../types/Materials'\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\nimport { RenderTexture } from '../textures/RenderTexture'\nimport { Binding } from '../bindings/Binding'\nimport { generateUUID } from '../../utils/utils'\nimport { BufferElement } from '../bindings/bufferElements/BufferElement'\n\n/**\n * Used as a base to create a {@link Material}.
\n * The purpose of {@link Material} is to create and update the {@link BindGroup | bind groups} and their bindings (GPU buffers, textures and samplers), create a {@link core/pipelines/PipelineEntry.PipelineEntry | PipelineEntry} and use them to {@link Material#render | render}.\n *\n * ## Bind groups\n *\n * A {@link Material} automatically creates a {@link TextureBindGroup}, but it is actually added to the active {@link Material#bindGroups | bind groups array} only if necessary, which means if your shaders use a {@link GPUSampler}, a {@link GPUTexture} or a {@link GPUExternalTexture}.\n *\n * Another {@link BindGroup} will be created if you pass any {@link MaterialParams#uniforms | uniforms} or {@link MaterialParams#storages | storages} parameters.\n *\n * Finally, you can also pass already created {@link BindGroup} to a {@link Material} via the {@link MaterialParams#bindGroups | bindGroups} parameter.\n *\n * ----\n *\n * Note that this class is not intended to be used as is, but as a base for {@link core/materials/ComputeMaterial.ComputeMaterial | ComputeMaterial} and {@link core/materials/RenderMaterial.RenderMaterial | RenderMaterial} classes.\n */\nexport class Material {\n /** The type of the {@link Material} */\n type: string\n /** The universal unique id of the {@link Material} */\n uuid: string\n /** The {@link Renderer} used */\n renderer: Renderer\n /** Options used to create this {@link Material} */\n options: MaterialOptions\n\n /** Pipeline entry used by this {@link Material} */\n pipelineEntry: AllowedPipelineEntries\n\n /**\n * Array of {@link BindGroup | bind groups} used by this {@link Material}\n * This array respects a specific order:\n * 1. The {@link texturesBindGroup | textures bind groups}\n * 2. The {@link BindGroup | bind group} created using {@link types/BindGroups.BindGroupInputs#uniforms | uniforms} and {@link types/BindGroups.BindGroupInputs#storages | storages} parameters if any\n * 3. Additional {@link MaterialParams#bindGroups | bind groups} parameters if any\n */\n bindGroups: AllowedBindGroups[]\n /** Array of {@link TextureBindGroup | texture bind groups} used by this {@link Material} */\n texturesBindGroups: TextureBindGroup[]\n /** Array of {@link BindGroup | bind groups} created using the {@link types/BindGroups.BindGroupInputs#uniforms | uniforms} and {@link types/BindGroups.BindGroupInputs#storages | storages} parameters when instancing this {@link Material} */\n inputsBindGroups: BindGroup[]\n /** Array of {@link BindGroup | cloned bind groups} created by this {@link Material} */\n clonedBindGroups: AllowedBindGroups[]\n\n /** Object containing all uniforms inputs handled by this {@link Material} */\n uniforms: Record>\n /** Object containing all read only or read/write storages inputs handled by this {@link Material} */\n storages: Record>\n\n /** Array of {@link Binding | bindings} created using the {@link types/BindGroups.BindGroupInputs#uniforms | uniforms} and {@link types/BindGroups.BindGroupInputs#storages | storages} parameters when instancing this {@link Material} */\n inputsBindings: BindGroupBindingElement[]\n\n /** Array of {@link Texture} handled by this {@link Material} */\n textures: Texture[]\n /** Array of {@link RenderTexture} handled by this {@link Material} */\n renderTextures: RenderTexture[]\n /** Array of {@link Sampler} handled by this {@link Material} */\n samplers: Sampler[]\n\n /**\n * Material constructor\n * @param renderer - our renderer class object\n * @param parameters - {@link types/Materials.MaterialParams | parameters} used to create our Material\n */\n constructor(renderer: Renderer | GPUCurtains, parameters: MaterialParams) {\n this.type = 'Material'\n\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\n\n isRenderer(renderer, this.type)\n\n this.renderer = renderer\n\n this.uuid = generateUUID()\n\n const {\n shaders,\n label,\n useAsyncPipeline,\n uniforms,\n storages,\n bindings,\n bindGroups,\n samplers,\n textures,\n renderTextures,\n } = parameters\n\n this.options = {\n shaders,\n label,\n useAsyncPipeline: useAsyncPipeline === undefined ? true : useAsyncPipeline,\n ...(uniforms !== undefined && { uniforms }),\n ...(storages !== undefined && { storages }),\n ...(bindings !== undefined && { bindings }),\n ...(bindGroups !== undefined && { bindGroups }),\n ...(samplers !== undefined && { samplers }),\n ...(textures !== undefined && { textures }),\n ...(renderTextures !== undefined && { renderTextures }),\n }\n\n this.bindGroups = []\n this.texturesBindGroups = []\n this.clonedBindGroups = []\n\n this.setBindGroups()\n\n this.setTextures()\n this.setSamplers()\n }\n\n /**\n * Check if all bind groups are ready, and create them if needed\n */\n compileMaterial() {\n const texturesBindGroupLength = this.texturesBindGroup.bindings.length ? 1 : 0\n const bindGroupsReady = this.bindGroups.length >= this.inputsBindGroups.length + texturesBindGroupLength\n\n if (!bindGroupsReady) {\n this.createBindGroups()\n }\n }\n\n /**\n * Get whether the renderer is ready, our pipeline entry and pipeline have been created and successfully compiled\n * @readonly\n */\n get ready(): boolean {\n return !!(this.renderer.ready && this.pipelineEntry && this.pipelineEntry.pipeline && this.pipelineEntry.ready)\n }\n\n /**\n * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been lost to prepare everything for restoration.\n * Basically set all the {@link GPUBuffer} to null so they will be reset next time we try to render\n */\n loseContext() {\n // start with the textures\n this.textures.forEach((texture) => {\n texture.texture = null\n texture.sourceUploaded = false\n })\n\n this.renderTextures.forEach((texture) => {\n texture.texture = null\n })\n\n // then bind groups and struct\n ;[...this.bindGroups, ...this.clonedBindGroups, ...this.inputsBindGroups].forEach((bindGroup) =>\n bindGroup.loseContext()\n )\n\n // reset pipeline as well\n this.pipelineEntry.pipeline = null\n }\n\n /**\n * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been restored to recreate our bind groups.\n */\n restoreContext() {\n // start with the samplers and textures\n this.samplers.forEach((sampler) => {\n // the samplers have all been recreated by the renderer, just update the reference\n sampler.createSampler()\n sampler.binding.resource = sampler.sampler\n })\n\n // recreate the textures and resize them\n this.textures.forEach((texture) => {\n texture.createTexture()\n texture.resize()\n })\n\n this.renderTextures.forEach((texture) => {\n texture.resize(texture.size)\n })\n\n // now the bind groups\n ;[...this.bindGroups, ...this.clonedBindGroups, ...this.inputsBindGroups].forEach((bindGroup) => {\n if (bindGroup.shouldCreateBindGroup) {\n bindGroup.createBindGroup()\n }\n\n // finally re-write all our buffers\n bindGroup.bufferBindings.forEach((bufferBinding) => (bufferBinding.shouldUpdate = true))\n })\n }\n\n /**\n * Get the complete code of a given shader including all the WGSL fragment code snippets added by the pipeline\n * @param [shaderType=\"full\"] - shader to get the code from\n * @returns - The corresponding shader code\n */\n getShaderCode(shaderType: FullShadersType = 'full'): string {\n if (!this.pipelineEntry) return ''\n\n shaderType = (() => {\n switch (shaderType) {\n case 'vertex':\n case 'fragment':\n case 'compute':\n case 'full':\n return shaderType\n default:\n return 'full'\n }\n })()\n\n return this.pipelineEntry.shaders[shaderType].code\n }\n\n /**\n * Get the added code of a given shader, i.e. all the WGSL fragment code snippets added by the pipeline\n * @param [shaderType=\"vertex\"] - shader to get the code from\n * @returns - The corresponding shader code\n */\n getAddedShaderCode(shaderType: FullShadersType = 'vertex'): string {\n if (!this.pipelineEntry) return ''\n\n shaderType = (() => {\n switch (shaderType) {\n case 'vertex':\n case 'fragment':\n case 'compute':\n return shaderType\n default:\n return 'vertex'\n }\n })()\n\n return this.pipelineEntry.shaders[shaderType].head\n }\n\n /* BIND GROUPS */\n\n /**\n * Prepare and set our bind groups based on inputs and bindGroups Material parameters\n */\n setBindGroups() {\n this.uniforms = {}\n this.storages = {}\n\n this.inputsBindGroups = []\n this.inputsBindings = []\n\n if (this.options.uniforms || this.options.storages || this.options.bindings) {\n const inputsBindGroup = new BindGroup(this.renderer, {\n label: this.options.label + ': Bindings bind group',\n uniforms: this.options.uniforms,\n storages: this.options.storages,\n bindings: this.options.bindings,\n })\n\n this.processBindGroupBindings(inputsBindGroup)\n this.inputsBindGroups.push(inputsBindGroup)\n }\n\n this.options.bindGroups?.forEach((bindGroup) => {\n this.processBindGroupBindings(bindGroup)\n this.inputsBindGroups.push(bindGroup)\n })\n }\n\n /**\n * Get the main {@link TextureBindGroup | texture bind group} created by this {@link Material} to manage all textures related struct\n * @readonly\n */\n get texturesBindGroup(): TextureBindGroup {\n return this.texturesBindGroups[0]\n }\n\n /**\n * Process all {@link BindGroup} struct and add them to the corresponding objects based on their binding types. Also store them in a inputsBindings array to facilitate further access to struct.\n * @param bindGroup - The {@link BindGroup} to process\n */\n processBindGroupBindings(bindGroup: BindGroup) {\n bindGroup.bindings.forEach((inputBinding) => {\n if (inputBinding.bindingType === 'uniform')\n this.uniforms = {\n ...this.uniforms,\n [inputBinding.name]: (inputBinding as BindGroupBufferBindingElement).inputs,\n }\n if (inputBinding.bindingType === 'storage')\n this.storages = {\n ...this.storages,\n [inputBinding.name]: (inputBinding as BindGroupBufferBindingElement).inputs,\n }\n\n this.inputsBindings.push(inputBinding)\n })\n }\n\n /**\n * Create the bind groups if they need to be created\n */\n createBindGroups() {\n // textures first\n if (this.texturesBindGroup.shouldCreateBindGroup) {\n this.texturesBindGroup.setIndex(this.bindGroups.length)\n this.texturesBindGroup.createBindGroup()\n\n this.bindGroups.push(this.texturesBindGroup)\n }\n\n // then uniforms/storages inputs\n this.inputsBindGroups.forEach((bindGroup) => {\n if (bindGroup.shouldCreateBindGroup) {\n bindGroup.setIndex(this.bindGroups.length)\n bindGroup.createBindGroup()\n\n this.bindGroups.push(bindGroup)\n }\n })\n\n // finally, bindGroups inputs\n this.options.bindGroups?.forEach((bindGroup) => {\n // it has been created but not been added yet? add it!\n if (!bindGroup.shouldCreateBindGroup && !this.bindGroups.find((bG) => bG.uuid === bindGroup.uuid)) {\n bindGroup.setIndex(this.bindGroups.length)\n this.bindGroups.push(bindGroup)\n }\n\n // add it to our textures bind groups as well if needed\n if (bindGroup instanceof TextureBindGroup && !this.texturesBindGroups.find((bG) => bG.uuid === bindGroup.uuid)) {\n this.texturesBindGroups.push(bindGroup)\n\n // also add the textures?\n bindGroup.textures.forEach((texture) => {\n if (texture instanceof Texture && !this.textures.find((t) => t.uuid === texture.uuid)) {\n this.textures.push(texture)\n } else if (texture instanceof RenderTexture && !this.renderTextures.find((t) => t.uuid === texture.uuid)) {\n this.renderTextures.push(texture)\n }\n })\n }\n })\n }\n\n /**\n * Clones a {@link BindGroup} from a list of buffers\n * Useful to create a new bind group with already created buffers, but swapped\n * @param parameters - parameters used to clone the {@link BindGroup | bind group}\n * @param parameters.bindGroup - the BindGroup to clone\n * @param parameters.bindings - our input binding buffers\n * @param parameters.keepLayout - whether we should keep original bind group layout or not\n * @returns - the cloned BindGroup\n */\n cloneBindGroup({\n bindGroup,\n bindings = [],\n keepLayout = true,\n }: {\n bindGroup?: AllowedBindGroups\n bindings?: BindGroupBindingElement[]\n keepLayout?: boolean\n }): AllowedBindGroups | null {\n if (!bindGroup) return null\n\n const clone = bindGroup.clone({ bindings, keepLayout })\n this.clonedBindGroups.push(clone)\n\n return clone\n }\n\n /**\n * Get a corresponding {@link BindGroup} or {@link TextureBindGroup} from one of its binding name/key\n * @param bindingName - the binding name/key to look for\n * @returns - bind group found or null if not found\n */\n getBindGroupByBindingName(bindingName: BufferBinding['name'] = ''): AllowedBindGroups | null {\n return (this.ready ? this.bindGroups : this.inputsBindGroups).find((bindGroup) => {\n return bindGroup.bindings.find((binding) => binding.name === bindingName)\n })\n }\n\n /**\n * Destroy a bind group, only if it is not used by another object\n * @param bindGroup - bind group to eventually destroy\n */\n destroyBindGroup(bindGroup: AllowedBindGroups) {\n // check if this bind group is used by another object before actually destroying it\n const objectsUsingBindGroup = this.renderer.getObjectsByBindGroup(bindGroup)\n\n const shouldDestroy =\n !objectsUsingBindGroup || !objectsUsingBindGroup.find((object) => object.material.uuid !== this.uuid)\n\n if (shouldDestroy) {\n bindGroup.destroy()\n }\n }\n\n /**\n * Destroy all bind groups\n */\n destroyBindGroups() {\n this.bindGroups.forEach((bindGroup) => this.destroyBindGroup(bindGroup))\n this.clonedBindGroups.forEach((bindGroup) => this.destroyBindGroup(bindGroup))\n this.texturesBindGroups.forEach((bindGroup) => this.destroyBindGroup(bindGroup))\n this.texturesBindGroups = []\n this.inputsBindGroups = []\n this.bindGroups = []\n this.clonedBindGroups = []\n }\n\n /**\n * {@link BindGroup#update | Update} all bind groups:\n * - Update all {@link texturesBindGroups | textures bind groups} textures\n * - Update its {@link BindGroup#bufferBindings | buffer bindings}\n * - Check if it eventually needs a {@link BindGroup#resetBindGroup | reset}\n * - Check if we need to flush the pipeline\n */\n updateBindGroups() {\n // now update all bind groups in use and check if they need to flush the pipeline\n this.bindGroups.forEach((bindGroup) => {\n bindGroup.update()\n\n // if a bind group needs to flush the pipeline\n // usually happens if one of the struct bindingType has changed,\n // which means the shader should be re-patched and recreated\n if (bindGroup.needsPipelineFlush && this.pipelineEntry.ready) {\n this.pipelineEntry.flushPipelineEntry(this.bindGroups)\n bindGroup.needsPipelineFlush = false\n }\n })\n }\n\n /* INPUTS */\n\n /**\n * Look for a {@link BindGroupBindingElement | binding} by name in all {@link inputsBindings | input bindings}\n * @param bindingName - the binding name or key\n * @returns - the found binding, or null if not found\n */\n getBindingByName(bindingName: Binding['name'] = ''): BindGroupBindingElement | undefined {\n return this.inputsBindings.find((binding) => binding.name === bindingName)\n }\n\n /**\n * Look for a {@link BindGroupBufferBindingElement | buffer binding} by name in all {@link inputsBindings | input bindings}\n * @param bindingName - the binding name or key\n * @returns - the found binding, or null if not found\n */\n getBufferBindingByName(bindingName: Binding['name'] = ''): BindGroupBufferBindingElement | undefined {\n return this.inputsBindings.find((binding) => binding.name === bindingName && 'buffer' in binding) as\n | BindGroupBufferBindingElement\n | undefined\n }\n\n /**\n * Force a given buffer binding update flag to update it at next render\n * @param bufferBindingName - the buffer binding name\n * @param bindingName - the binding name\n */\n shouldUpdateInputsBindings(bufferBindingName?: BufferBinding['name'], bindingName?: BufferBindingInput['name']) {\n if (!bufferBindingName) return\n\n const bufferBinding = this.getBindingByName(bufferBindingName)\n if (bufferBinding) {\n if (!bindingName) {\n Object.keys((bufferBinding as BindGroupBufferBindingElement).inputs).forEach((bindingKey) =>\n (bufferBinding as BindGroupBufferBindingElement).shouldUpdateBinding(bindingKey)\n )\n } else {\n ;(bufferBinding as BindGroupBufferBindingElement).shouldUpdateBinding(bindingName)\n }\n }\n }\n\n /* SAMPLERS & TEXTURES */\n\n /**\n * Prepare our textures array and set the {@link TextureBindGroup}\n */\n setTextures() {\n this.textures = []\n this.renderTextures = []\n this.texturesBindGroups.push(\n new TextureBindGroup(this.renderer, {\n label: this.options.label + ': Textures bind group',\n })\n )\n\n this.options.textures?.forEach((texture) => {\n this.addTexture(texture)\n })\n\n this.options.renderTextures?.forEach((texture) => {\n this.addTexture(texture)\n })\n }\n\n /**\n * Add a texture to our array, and add it to the textures bind group only if used in the shaders (avoid binding useless data)\n * @param texture - texture to add\n */\n addTexture(texture: Texture | RenderTexture) {\n if (texture instanceof Texture) {\n this.textures.push(texture)\n } else if (texture instanceof RenderTexture) {\n this.renderTextures.push(texture)\n }\n\n // is it used in our shaders?\n if (\n (this.options.shaders.vertex && this.options.shaders.vertex.code.indexOf(texture.options.name) !== -1) ||\n (this.options.shaders.fragment &&\n (this.options.shaders.fragment as ShaderOptions).code.indexOf(texture.options.name) !== -1) ||\n (this.options.shaders.compute && this.options.shaders.compute.code.indexOf(texture.options.name) !== -1)\n ) {\n this.texturesBindGroup.addTexture(texture)\n }\n }\n\n /**\n * Destroy a {@link Texture} or {@link RenderTexture}, only if it is not used by another object or cached.\n * @param texture - {@link Texture} or {@link RenderTexture} to eventually destroy\n */\n destroyTexture(texture: Texture | RenderTexture) {\n // do not destroy a texture that must stay in cache\n if ((texture as Texture).options.cache) return\n\n // check if this texture is used by another object before actually destroying it\n const objectsUsingTexture = this.renderer.getObjectsByTexture(texture)\n\n const shouldDestroy =\n !objectsUsingTexture || !objectsUsingTexture.some((object) => object.material.uuid !== this.uuid)\n\n if (shouldDestroy) {\n texture.destroy()\n }\n }\n\n /**\n * Destroy all the Material textures\n */\n destroyTextures() {\n this.textures?.forEach((texture) => this.destroyTexture(texture))\n this.renderTextures?.forEach((texture) => this.destroyTexture(texture))\n this.textures = []\n this.renderTextures = []\n }\n\n /**\n * Prepare our samplers array and always add a default sampler if not already passed as parameter\n */\n setSamplers() {\n this.samplers = []\n\n this.options.samplers?.forEach((sampler) => {\n this.addSampler(sampler)\n })\n\n // create our default sampler if needed\n const hasDefaultSampler = this.samplers.find((sampler) => sampler.name === 'defaultSampler')\n if (!hasDefaultSampler) {\n const sampler = new Sampler(this.renderer, { name: 'defaultSampler' })\n this.addSampler(sampler)\n }\n }\n\n /**\n * Add a sampler to our array, and add it to the textures bind group only if used in the shaders (avoid binding useless data)\n * @param sampler - sampler to add\n */\n addSampler(sampler: Sampler) {\n this.samplers.push(sampler)\n\n // is it used in our shaders?\n if (\n (this.options.shaders.vertex && this.options.shaders.vertex.code.indexOf(sampler.name) !== -1) ||\n (this.options.shaders.fragment &&\n (this.options.shaders.fragment as ShaderOptions).code.indexOf(sampler.name) !== -1) ||\n (this.options.shaders.compute && this.options.shaders.compute.code.indexOf(sampler.name) !== -1)\n ) {\n this.texturesBindGroup.addSampler(sampler)\n }\n }\n\n /* BUFFER RESULTS */\n\n /**\n * Map a {@link GPUBuffer} and put a copy of the data into a {@link Float32Array}\n * @param buffer - {@link GPUBuffer} to map\n * @async\n * @returns - {@link Float32Array} holding the {@link GPUBuffer} data\n */\n async getBufferResult(buffer: GPUBuffer): Promise {\n await buffer.mapAsync(GPUMapMode.READ)\n const result = new Float32Array(buffer.getMappedRange().slice(0))\n buffer.unmap()\n\n return result\n }\n\n /**\n * Map the content of a {@link BufferBinding#buffer | GPU buffer} and put a copy of the data into a {@link Float32Array}\n * @param bindingName - The name of the {@link inputsBindings | input bindings} from which to map the {@link BufferBinding#buffer | GPU buffer}\n * @async\n * @returns - {@link Float32Array} holding the {@link GPUBuffer} data\n */\n async getBufferBindingResultByBindingName(bindingName: Binding['name'] = ''): Promise {\n const binding = this.getBufferBindingByName(bindingName)\n if (binding && 'buffer' in binding) {\n const dstBuffer = this.renderer.copyBufferToBuffer({\n srcBuffer: binding.buffer,\n })\n return await this.getBufferResult(dstBuffer)\n } else {\n return new Float32Array(0)\n }\n }\n\n /**\n * Map the content of a specific {@link BufferElement | buffer element} belonging to a {@link BufferBinding#buffer | GPU buffer} and put a copy of the data into a {@link Float32Array}\n * @param parameters - parameters used to get the result\n * @param parameters.bindingName - The name of the {@link inputsBindings | input bindings} from which to map the {@link BufferBinding#buffer | GPU buffer}\n * @param parameters.bufferElementName - The name of the {@link BufferElement | buffer element} from which to extract the data afterwards\n * @returns - {@link Float32Array} holding {@link GPUBuffer} data\n */\n async getBufferElementResultByNames({\n bindingName,\n bufferElementName,\n }: {\n bindingName: Binding['name']\n bufferElementName: BufferElement['name']\n }): Promise {\n const result = await this.getBufferBindingResultByBindingName(bindingName)\n\n if (!bufferElementName || result.length) {\n return result\n } else {\n const binding = this.getBufferBindingByName(bindingName)\n if (binding) {\n return binding.extractBufferElementDataFromBufferResult({ result, bufferElementName })\n } else {\n return result\n }\n }\n }\n\n /* RENDER */\n\n /**\n * Called before rendering the Material.\n * First, check if we need to create our bind groups or pipeline\n * Then render the {@link textures}\n * Finally updates all the {@link bindGroups | bind groups}\n */\n onBeforeRender() {\n // set our material if needed\n this.compileMaterial()\n\n // first what needs to be done for all textures\n this.textures.forEach((texture) => {\n texture.render()\n })\n\n // update bind groups\n this.updateBindGroups()\n }\n\n /**\n * Set the current pipeline\n * @param pass - current pass encoder\n */\n setPipeline(pass: GPURenderPassEncoder | GPUComputePassEncoder) {\n this.renderer.pipelineManager.setCurrentPipeline(pass, this.pipelineEntry)\n }\n\n /**\n * Render the material if it is ready:\n * Set the current pipeline and set the bind groups\n * @param pass - current pass encoder\n */\n render(pass: GPURenderPassEncoder | GPUComputePassEncoder) {\n // renderer or pipeline are not ready yet\n // not really needed since meshes/compute passes do already check it beforehand\n // mostly here as a safeguard\n if (!this.ready) return\n\n // set current pipeline\n this.setPipeline(pass)\n\n // set bind groups\n this.bindGroups.forEach((bindGroup) => {\n pass.setBindGroup(bindGroup.index, bindGroup.bindGroup)\n })\n }\n\n /**\n * Destroy the Material\n */\n destroy() {\n // destroy all buffers created with createBuffer\n this.destroyBindGroups()\n this.destroyTextures()\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/materials/RenderMaterial.mjs b/dist/esm/core/materials/RenderMaterial.mjs new file mode 100644 index 000000000..311a29fcb --- /dev/null +++ b/dist/esm/core/materials/RenderMaterial.mjs @@ -0,0 +1,121 @@ +import { Material } from './Material.mjs'; +import { isRenderer } from '../renderers/utils.mjs'; +import { throwWarning } from '../../utils/utils.mjs'; + +class RenderMaterial extends Material { + /** + * RenderMaterial constructor + * @param renderer - our renderer class object + * @param parameters - {@link RenderMaterialParams | parameters} used to create our RenderMaterial + */ + constructor(renderer, parameters) { + renderer = renderer && renderer.renderer || renderer; + const type = "RenderMaterial"; + isRenderer(renderer, type); + super(renderer, parameters); + this.type = type; + this.renderer = renderer; + const { shaders, label, useAsyncPipeline, uniforms, storages, bindGroups, ...renderingOptions } = parameters; + if (!shaders.vertex.entryPoint) { + shaders.vertex.entryPoint = "main"; + } + if (shaders.fragment && !shaders.fragment.entryPoint) { + shaders.fragment.entryPoint = "main"; + } + renderingOptions.targetFormat = renderingOptions.targetFormat ?? this.renderer.options.preferredFormat; + this.options = { + ...this.options, + shaders, + rendering: renderingOptions + }; + this.pipelineEntry = this.renderer.pipelineManager.createRenderPipeline({ + renderer: this.renderer, + label: this.options.label + " render pipeline", + shaders: this.options.shaders, + useAsync: this.options.useAsyncPipeline, + ...this.options.rendering + }); + this.attributes = null; + } + /** + * When all bind groups and attributes are created, add them to the {@link RenderPipelineEntry} + */ + setPipelineEntryProperties() { + this.pipelineEntry.setPipelineEntryProperties({ + attributes: this.attributes, + bindGroups: this.bindGroups + }); + } + /** + * Compile the {@link RenderPipelineEntry} + * @async + */ + async compilePipelineEntry() { + await this.pipelineEntry.compilePipelineEntry(); + } + /** + * Check if attributes and all bind groups are ready, create them if needed and set {@link RenderPipelineEntry} bind group buffers and compile the pipeline + * @async + */ + async compileMaterial() { + super.compileMaterial(); + if (this.attributes && this.pipelineEntry && this.pipelineEntry.canCompile) { + this.setPipelineEntryProperties(); + await this.compilePipelineEntry(); + } + } + /** + * Set or reset one of the {@link RenderMaterialRenderingOptions | rendering options}. Should be use with great caution, because if the {@link RenderPipelineEntry#pipeline | render pipeline} has already been compiled, it can cause a pipeline flush. + * @param renderingOptions - new {@link RenderMaterialRenderingOptions | rendering options} properties to be set + */ + setRenderingOptions(renderingOptions = {}) { + const newProperties = Object.keys(renderingOptions).filter( + (key) => renderingOptions[key] !== this.options.rendering[key] + ); + this.options.rendering = { ...this.options.rendering, ...renderingOptions }; + if (this.pipelineEntry) { + this.pipelineEntry.options = { ...this.pipelineEntry.options, ...this.options.rendering }; + if (this.pipelineEntry.ready && newProperties.length) { + throwWarning( + `${this.options.label}: the change of rendering options is causing this RenderMaterial pipeline to be flushed and recompiled. This should be avoided. Rendering options that caused this: { ${newProperties.map( + (key) => `"${key}": ${Array.isArray(renderingOptions[key]) ? renderingOptions[key].map((optKey) => `${JSON.stringify(optKey)}`).join(", ") : renderingOptions[key]}` + ).join(", ")} }` + ); + this.pipelineEntry.flushPipelineEntry(this.bindGroups); + } + } + } + /* ATTRIBUTES */ + /** + * Compute geometry if needed and get all useful geometry properties needed to create attributes buffers + * @param geometry - the geometry to draw + */ + setAttributesFromGeometry(geometry) { + this.attributes = { + wgslStructFragment: geometry.wgslStructFragment, + vertexBuffers: geometry.vertexBuffers + }; + } + /* BIND GROUPS */ + /** + * Create the bind groups if they need to be created, but first add Camera bind group if needed + */ + createBindGroups() { + const bindGroupStartIndex = this.options.rendering.useProjection ? 1 : 0; + if (this.texturesBindGroup.shouldCreateBindGroup) { + this.texturesBindGroup.setIndex(this.bindGroups.length + bindGroupStartIndex); + this.texturesBindGroup.createBindGroup(); + this.bindGroups.push(this.texturesBindGroup); + } + this.inputsBindGroups.forEach((bindGroup) => { + if (bindGroup.shouldCreateBindGroup) { + bindGroup.setIndex(this.bindGroups.length + bindGroupStartIndex); + bindGroup.createBindGroup(); + this.bindGroups.push(bindGroup); + } + }); + } +} + +export { RenderMaterial }; +//# sourceMappingURL=RenderMaterial.mjs.map diff --git a/dist/esm/core/materials/RenderMaterial.mjs.map b/dist/esm/core/materials/RenderMaterial.mjs.map new file mode 100644 index 000000000..cf0fe9ace --- /dev/null +++ b/dist/esm/core/materials/RenderMaterial.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"RenderMaterial.mjs","sources":["../../../../src/core/materials/RenderMaterial.ts"],"sourcesContent":["import { Material } from './Material'\nimport { BindGroup } from '../bindGroups/BindGroup'\nimport { isRenderer, Renderer } from '../renderers/utils'\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\nimport {\n AllowedGeometries,\n RenderMaterialAttributes,\n RenderMaterialOptions,\n RenderMaterialParams,\n RenderMaterialRenderingOptions,\n ShaderOptions,\n} from '../../types/Materials'\nimport { RenderPipelineEntry } from '../pipelines/RenderPipelineEntry'\nimport { RenderPipelineEntryParams } from '../../types/PipelineEntries'\nimport { throwWarning } from '../../utils/utils'\n\n/**\n * Create a {@link Material} specifically built to draw the vertices of a {@link core/geometries/Geometry.Geometry | Geometry}. Internally used by all kind of Meshes.\n *\n * ## Render pipeline\n *\n * A {@link RenderMaterial} automatically creates a {@link RenderPipelineEntry}. Once all the {@link BindGroup} have been created, they are sent with the shaders code and the {@link RenderMaterialOptions#rendering | rendering options} to the {@link RenderPipelineEntry}, which is in turns responsible for creating the {@link GPURenderPipeline}.\n *\n * After the {@link GPURenderPipeline} has been successfully compiled, the {@link RenderMaterial} is considered to be ready.\n */\nexport class RenderMaterial extends Material {\n /** {@link RenderPipelineEntry | Render pipeline entry} used by this {@link RenderMaterial} */\n pipelineEntry: RenderPipelineEntry\n /** Mandatory {@link RenderMaterialAttributes | geometry attributes} to pass to the {@link RenderPipelineEntry | render pipeline entry} */\n attributes: RenderMaterialAttributes | null\n /** Options used to create this {@link RenderMaterial} */\n options: RenderMaterialOptions\n\n /**\n * RenderMaterial constructor\n * @param renderer - our renderer class object\n * @param parameters - {@link RenderMaterialParams | parameters} used to create our RenderMaterial\n */\n constructor(renderer: Renderer | GPUCurtains, parameters: RenderMaterialParams) {\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\n\n const type = 'RenderMaterial'\n\n isRenderer(renderer, type)\n\n super(renderer, parameters)\n\n this.type = type\n this.renderer = renderer\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { shaders, label, useAsyncPipeline, uniforms, storages, bindGroups, ...renderingOptions } = parameters\n\n if (!shaders.vertex.entryPoint) {\n shaders.vertex.entryPoint = 'main'\n }\n\n if (shaders.fragment && !(shaders.fragment as ShaderOptions).entryPoint) {\n ;(shaders.fragment as ShaderOptions).entryPoint = 'main'\n }\n\n // patch target format if not set\n renderingOptions.targetFormat = renderingOptions.targetFormat ?? this.renderer.options.preferredFormat\n\n this.options = {\n ...this.options,\n shaders,\n rendering: renderingOptions,\n } as RenderMaterialOptions\n\n this.pipelineEntry = this.renderer.pipelineManager.createRenderPipeline({\n renderer: this.renderer,\n label: this.options.label + ' render pipeline',\n shaders: this.options.shaders,\n useAsync: this.options.useAsyncPipeline,\n ...this.options.rendering,\n } as RenderPipelineEntryParams)\n\n this.attributes = null\n }\n\n /**\n * When all bind groups and attributes are created, add them to the {@link RenderPipelineEntry}\n */\n setPipelineEntryProperties() {\n this.pipelineEntry.setPipelineEntryProperties({\n attributes: this.attributes,\n bindGroups: this.bindGroups,\n })\n }\n\n /**\n * Compile the {@link RenderPipelineEntry}\n * @async\n */\n async compilePipelineEntry(): Promise {\n await this.pipelineEntry.compilePipelineEntry()\n }\n\n /**\n * Check if attributes and all bind groups are ready, create them if needed and set {@link RenderPipelineEntry} bind group buffers and compile the pipeline\n * @async\n */\n async compileMaterial() {\n super.compileMaterial()\n\n if (this.attributes && this.pipelineEntry && this.pipelineEntry.canCompile) {\n this.setPipelineEntryProperties()\n await this.compilePipelineEntry()\n }\n }\n\n /**\n * Set or reset one of the {@link RenderMaterialRenderingOptions | rendering options}. Should be use with great caution, because if the {@link RenderPipelineEntry#pipeline | render pipeline} has already been compiled, it can cause a pipeline flush.\n * @param renderingOptions - new {@link RenderMaterialRenderingOptions | rendering options} properties to be set\n */\n setRenderingOptions(renderingOptions: Partial = {}) {\n const newProperties = Object.keys(renderingOptions).filter(\n (key) => renderingOptions[key] !== this.options.rendering[key]\n )\n\n this.options.rendering = { ...this.options.rendering, ...renderingOptions }\n\n if (this.pipelineEntry) {\n this.pipelineEntry.options = { ...this.pipelineEntry.options, ...this.options.rendering }\n\n if (this.pipelineEntry.ready && newProperties.length) {\n throwWarning(\n `${\n this.options.label\n }: the change of rendering options is causing this RenderMaterial pipeline to be flushed and recompiled. This should be avoided. Rendering options that caused this: { ${newProperties\n .map(\n (key) =>\n `\"${key}\": ${\n Array.isArray(renderingOptions[key])\n ? renderingOptions[key].map((optKey) => `${JSON.stringify(optKey)}`).join(', ')\n : renderingOptions[key]\n }`\n )\n .join(', ')} }`\n )\n\n this.pipelineEntry.flushPipelineEntry(this.bindGroups)\n }\n }\n }\n\n /* ATTRIBUTES */\n\n /**\n * Compute geometry if needed and get all useful geometry properties needed to create attributes buffers\n * @param geometry - the geometry to draw\n */\n setAttributesFromGeometry(geometry: AllowedGeometries) {\n this.attributes = {\n wgslStructFragment: geometry.wgslStructFragment,\n vertexBuffers: geometry.vertexBuffers,\n }\n }\n\n /* BIND GROUPS */\n\n /**\n * Create the bind groups if they need to be created, but first add Camera bind group if needed\n */\n createBindGroups() {\n // camera first!\n // if ((this.renderer as CameraRenderer).cameraBindGroup && this.options.rendering.useProjection) {\n // this.bindGroups.push((this.renderer as CameraRenderer).cameraBindGroup)\n // }\n //\n // super.createBindGroups()\n\n // TODO! need to chose whether we should add the camera bind group here\n // in such case we need to find a way not to bind it inside the render call\n // because it is already bound by the scene class at each render to avoid extra WebGPU commands\n const bindGroupStartIndex = this.options.rendering.useProjection ? 1 : 0\n\n // textures first\n if (this.texturesBindGroup.shouldCreateBindGroup) {\n this.texturesBindGroup.setIndex(this.bindGroups.length + bindGroupStartIndex) // bindGroup 0 is our renderer camera\n this.texturesBindGroup.createBindGroup()\n\n this.bindGroups.push(this.texturesBindGroup)\n }\n\n // then uniforms\n this.inputsBindGroups.forEach((bindGroup) => {\n if (bindGroup.shouldCreateBindGroup) {\n bindGroup.setIndex(this.bindGroups.length + bindGroupStartIndex)\n bindGroup.createBindGroup()\n\n this.bindGroups.push(bindGroup)\n }\n })\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/meshes/FullscreenPlane.mjs b/dist/esm/core/meshes/FullscreenPlane.mjs new file mode 100644 index 000000000..0ecf7d426 --- /dev/null +++ b/dist/esm/core/meshes/FullscreenPlane.mjs @@ -0,0 +1,56 @@ +import { MeshBaseMixin } from './mixins/MeshBaseMixin.mjs'; +import { isRenderer } from '../renderers/utils.mjs'; +import { PlaneGeometry } from '../geometries/PlaneGeometry.mjs'; +import { Vec2 } from '../../math/Vec2.mjs'; +import { cacheManager } from '../../utils/CacheManager.mjs'; + +class FullscreenPlane extends MeshBaseMixin(class { +}) { + /** + * FullscreenPlane constructor + * @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link FullscreenPlane} + * @param parameters - {@link MeshBaseRenderParams | parameters} use to create this {@link FullscreenPlane} + */ + constructor(renderer, parameters = {}) { + renderer = renderer && renderer.renderer || renderer; + isRenderer(renderer, parameters.label ? parameters.label + " FullscreenQuadMesh" : "FullscreenQuadMesh"); + let geometry = cacheManager.getPlaneGeometryByID(2); + if (!geometry) { + geometry = new PlaneGeometry({ widthSegments: 1, heightSegments: 1 }); + cacheManager.addPlaneGeometry(geometry); + } + super(renderer, null, { geometry, ...parameters }); + this.size = { + document: { + width: this.renderer.boundingRect.width, + height: this.renderer.boundingRect.height, + top: this.renderer.boundingRect.top, + left: this.renderer.boundingRect.left + } + }; + this.type = "FullscreenQuadMesh"; + } + /** + * Resize our {@link FullscreenPlane} + * @param boundingRect - the new bounding rectangle + */ + resize(boundingRect = null) { + this.size.document = boundingRect ?? this.renderer.boundingRect; + super.resize(boundingRect); + } + /** + * Take the pointer {@link Vec2 | vector} position relative to the document and returns it relative to our {@link FullscreenPlane} + * It ranges from -1 to 1 on both axis + * @param mouseCoords - pointer {@link Vec2 | vector} coordinates + * @returns - the mapped {@link Vec2 | vector} coordinates in the [-1, 1] range + */ + mouseToPlaneCoords(mouseCoords = new Vec2()) { + return new Vec2( + (mouseCoords.x - this.size.document.left) / this.size.document.width * 2 - 1, + 1 - (mouseCoords.y - this.size.document.top) / this.size.document.height * 2 + ); + } +} + +export { FullscreenPlane }; +//# sourceMappingURL=FullscreenPlane.mjs.map diff --git a/dist/esm/core/meshes/FullscreenPlane.mjs.map b/dist/esm/core/meshes/FullscreenPlane.mjs.map new file mode 100644 index 000000000..62e07b3b2 --- /dev/null +++ b/dist/esm/core/meshes/FullscreenPlane.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"FullscreenPlane.mjs","sources":["../../../../src/core/meshes/FullscreenPlane.ts"],"sourcesContent":["import { MeshBaseMixin, MeshBaseRenderParams } from './mixins/MeshBaseMixin'\nimport { isRenderer, Renderer } from '../renderers/utils'\nimport { PlaneGeometry } from '../geometries/PlaneGeometry'\nimport { DOMElementBoundingRect, RectBBox } from '../DOM/DOMElement'\nimport { Vec2 } from '../../math/Vec2'\nimport { cacheManager } from '../../utils/CacheManager'\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\n\n/**\n * Create a 1x1 quad (or plane) covering the full viewport, useful for postprocessing or background effects.\n *\n * It consists of a {@link PlaneGeometry} and {@link core/materials/RenderMaterial.RenderMaterial | RenderMaterial} and a few utilities method to help create {@link core/textures/Texture.Texture | Texture} and {@link core/textures/RenderTexture.RenderTexture | RenderTexture}.\n *\n * @example\n * ```javascript\n * // set our main GPUCurtains instance\n * const gpuCurtains = new GPUCurtains({\n * container: '#canvas' // selector of our WebGPU canvas container\n * })\n *\n * // set the GPU device\n * // note this is asynchronous\n * await gpuCurtains.setDevice()\n *\n * // create a fullscreen plane\n * // will use the normals colors as default shading\n * const fullscreenPlane = new FullscreenPlane(gpuCurtains, {\n * label: 'My fullscreen plane',\n * shaders: {\n * fragment: {\n * code: fragmentCode, // assume it is a valid WGSL fragment shader\n * },\n * },\n * })\n * ```\n */\nexport class FullscreenPlane extends MeshBaseMixin(class {}) {\n /** The type of the {@link FullscreenPlane} */\n type: string\n /** Object defining the {@link FullscreenPlane} size */\n size: {\n /** document HTML size */\n document: RectBBox\n }\n\n /**\n * FullscreenPlane constructor\n * @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link FullscreenPlane}\n * @param parameters - {@link MeshBaseRenderParams | parameters} use to create this {@link FullscreenPlane}\n */\n constructor(renderer: Renderer | GPUCurtains, parameters = {} as MeshBaseRenderParams) {\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\n\n isRenderer(renderer, parameters.label ? parameters.label + ' FullscreenQuadMesh' : 'FullscreenQuadMesh')\n\n // can we get a cached geometry?\n let geometry = cacheManager.getPlaneGeometryByID(2) // 1 * 1 + 1\n\n if (!geometry) {\n // we need to create a new plane geometry\n geometry = new PlaneGeometry({ widthSegments: 1, heightSegments: 1 })\n cacheManager.addPlaneGeometry(geometry)\n }\n\n // @ts-ignore\n super(renderer, null, { geometry, ...parameters })\n\n this.size = {\n document: {\n width: this.renderer.boundingRect.width,\n height: this.renderer.boundingRect.height,\n top: this.renderer.boundingRect.top,\n left: this.renderer.boundingRect.left,\n },\n }\n\n this.type = 'FullscreenQuadMesh'\n }\n\n /**\n * Resize our {@link FullscreenPlane}\n * @param boundingRect - the new bounding rectangle\n */\n resize(boundingRect: DOMElementBoundingRect | null = null) {\n this.size.document = boundingRect ?? this.renderer.boundingRect\n\n super.resize(boundingRect)\n }\n\n /**\n * Take the pointer {@link Vec2 | vector} position relative to the document and returns it relative to our {@link FullscreenPlane}\n * It ranges from -1 to 1 on both axis\n * @param mouseCoords - pointer {@link Vec2 | vector} coordinates\n * @returns - the mapped {@link Vec2 | vector} coordinates in the [-1, 1] range\n */\n mouseToPlaneCoords(mouseCoords: Vec2 = new Vec2()): Vec2 {\n // mouse position conversion from document to plane space\n return new Vec2(\n ((mouseCoords.x - this.size.document.left) / this.size.document.width) * 2 - 1,\n 1 - ((mouseCoords.y - this.size.document.top) / this.size.document.height) * 2\n )\n }\n}\n"],"names":[],"mappings":";;;;;;AAoCa,MAAA,eAAA,SAAwB,cAAc,MAAM;AAAC,CAAC,CAAE,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAc3D,WAAY,CAAA,QAAA,EAAkC,UAAa,GAAA,EAA4B,EAAA;AAErF,IAAY,QAAA,GAAA,QAAA,IAAa,SAAyB,QAAc,IAAA,QAAA,CAAA;AAEhE,IAAA,UAAA,CAAW,UAAU,UAAW,CAAA,KAAA,GAAQ,UAAW,CAAA,KAAA,GAAQ,wBAAwB,oBAAoB,CAAA,CAAA;AAGvG,IAAI,IAAA,QAAA,GAAW,YAAa,CAAA,oBAAA,CAAqB,CAAC,CAAA,CAAA;AAElD,IAAA,IAAI,CAAC,QAAU,EAAA;AAEb,MAAA,QAAA,GAAW,IAAI,aAAc,CAAA,EAAE,eAAe,CAAG,EAAA,cAAA,EAAgB,GAAG,CAAA,CAAA;AACpE,MAAA,YAAA,CAAa,iBAAiB,QAAQ,CAAA,CAAA;AAAA,KACxC;AAGA,IAAA,KAAA,CAAM,UAAU,IAAM,EAAA,EAAE,QAAU,EAAA,GAAG,YAAY,CAAA,CAAA;AAEjD,IAAA,IAAA,CAAK,IAAO,GAAA;AAAA,MACV,QAAU,EAAA;AAAA,QACR,KAAA,EAAO,IAAK,CAAA,QAAA,CAAS,YAAa,CAAA,KAAA;AAAA,QAClC,MAAA,EAAQ,IAAK,CAAA,QAAA,CAAS,YAAa,CAAA,MAAA;AAAA,QACnC,GAAA,EAAK,IAAK,CAAA,QAAA,CAAS,YAAa,CAAA,GAAA;AAAA,QAChC,IAAA,EAAM,IAAK,CAAA,QAAA,CAAS,YAAa,CAAA,IAAA;AAAA,OACnC;AAAA,KACF,CAAA;AAEA,IAAA,IAAA,CAAK,IAAO,GAAA,oBAAA,CAAA;AAAA,GACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAA,CAAO,eAA8C,IAAM,EAAA;AACzD,IAAA,IAAA,CAAK,IAAK,CAAA,QAAA,GAAW,YAAgB,IAAA,IAAA,CAAK,QAAS,CAAA,YAAA,CAAA;AAEnD,IAAA,KAAA,CAAM,OAAO,YAAY,CAAA,CAAA;AAAA,GAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,kBAAmB,CAAA,WAAA,GAAoB,IAAI,IAAA,EAAc,EAAA;AAEvD,IAAA,OAAO,IAAI,IAAA;AAAA,MACP,CAAA,WAAA,CAAY,CAAI,GAAA,IAAA,CAAK,IAAK,CAAA,QAAA,CAAS,QAAQ,IAAK,CAAA,IAAA,CAAK,QAAS,CAAA,KAAA,GAAS,CAAI,GAAA,CAAA;AAAA,MAC7E,CAAA,GAAA,CAAM,WAAY,CAAA,CAAA,GAAI,IAAK,CAAA,IAAA,CAAK,SAAS,GAAO,IAAA,IAAA,CAAK,IAAK,CAAA,QAAA,CAAS,MAAU,GAAA,CAAA;AAAA,KAC/E,CAAA;AAAA,GACF;AACF;;;;"} \ No newline at end of file diff --git a/dist/esm/core/meshes/Mesh.mjs b/dist/esm/core/meshes/Mesh.mjs new file mode 100644 index 000000000..4008b3c29 --- /dev/null +++ b/dist/esm/core/meshes/Mesh.mjs @@ -0,0 +1,20 @@ +import { isCameraRenderer } from '../renderers/utils.mjs'; +import { ProjectedObject3D } from '../objects3D/ProjectedObject3D.mjs'; +import { ProjectedMeshBaseMixin } from './mixins/ProjectedMeshBaseMixin.mjs'; + +class Mesh extends ProjectedMeshBaseMixin(ProjectedObject3D) { + /** + * Mesh constructor + * @param renderer - {@link CameraRenderer} object or {@link GPUCurtains} class object used to create this {@link Mesh} + * @param parameters - {@link MeshBaseParams | parameters} use to create this {@link Mesh} + */ + constructor(renderer, parameters) { + renderer = renderer && renderer.renderer || renderer; + isCameraRenderer(renderer, parameters.label ? parameters.label + " Mesh" : "Mesh"); + super(renderer, null, parameters); + this.type = "Mesh"; + } +} + +export { Mesh }; +//# sourceMappingURL=Mesh.mjs.map diff --git a/dist/esm/core/meshes/Mesh.mjs.map b/dist/esm/core/meshes/Mesh.mjs.map new file mode 100644 index 000000000..9aba3e493 --- /dev/null +++ b/dist/esm/core/meshes/Mesh.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"Mesh.mjs","sources":["../../../../src/core/meshes/Mesh.ts"],"sourcesContent":["import { CameraRenderer, isCameraRenderer } from '../renderers/utils'\nimport { ProjectedObject3D } from '../objects3D/ProjectedObject3D'\nimport { ProjectedMeshBaseMixin } from './mixins/ProjectedMeshBaseMixin'\nimport { MeshBaseParams } from './mixins/MeshBaseMixin'\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\n\n/**\n * Create a 3D Mesh.\n *\n * A 3D Mesh is a basically a {@link ProjectedObject3D} with a {@link core/geometries/Geometry.Geometry | Geometry} and a {@link core/materials/RenderMaterial.RenderMaterial | RenderMaterial}.\n *\n * You need to pass at least a valid {@link core/geometries/Geometry.Geometry | Geometry} as parameter.
\n * If no shaders are provided, it will use the normals colors as default shading.\n *\n * @example\n * ```javascript\n * // set our main GPUCurtains instance\n * const gpuCurtains = new GPUCurtains({\n * container: '#canvas' // selector of our WebGPU canvas container\n * })\n *\n * // set the GPU device\n * // note this is asynchronous\n * await gpuCurtains.setDevice()\n *\n * // create a mesh with a box geometry\n * // will use the normals colors as default shading\n * const mesh = new Mesh(gpuCurtains, {\n * label: 'My mesh',\n * geometry: new BoxGeometry(),\n * })\n * ```\n */\nexport class Mesh extends ProjectedMeshBaseMixin(ProjectedObject3D) {\n /**\n * Mesh constructor\n * @param renderer - {@link CameraRenderer} object or {@link GPUCurtains} class object used to create this {@link Mesh}\n * @param parameters - {@link MeshBaseParams | parameters} use to create this {@link Mesh}\n */\n constructor(renderer: CameraRenderer | GPUCurtains, parameters: MeshBaseParams) {\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as CameraRenderer)\n\n isCameraRenderer(renderer, parameters.label ? parameters.label + ' Mesh' : 'Mesh')\n\n // @ts-ignore\n super(renderer, null, parameters)\n\n this.type = 'Mesh'\n }\n}\n"],"names":[],"mappings":";;;;AAiCa,MAAA,IAAA,SAAa,sBAAuB,CAAA,iBAAiB,CAAE,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMlE,WAAA,CAAY,UAAwC,UAA4B,EAAA;AAE9E,IAAY,QAAA,GAAA,QAAA,IAAa,SAAyB,QAAc,IAAA,QAAA,CAAA;AAEhE,IAAA,gBAAA,CAAiB,UAAU,UAAW,CAAA,KAAA,GAAQ,UAAW,CAAA,KAAA,GAAQ,UAAU,MAAM,CAAA,CAAA;AAGjF,IAAM,KAAA,CAAA,QAAA,EAAU,MAAM,UAAU,CAAA,CAAA;AAEhC,IAAA,IAAA,CAAK,IAAO,GAAA,MAAA,CAAA;AAAA,GACd;AACF;;;;"} \ No newline at end of file diff --git a/dist/esm/core/meshes/mixins/MeshBaseMixin.mjs b/dist/esm/core/meshes/mixins/MeshBaseMixin.mjs new file mode 100644 index 000000000..feeaaa922 --- /dev/null +++ b/dist/esm/core/meshes/mixins/MeshBaseMixin.mjs @@ -0,0 +1,602 @@ +import { generateUUID, throwWarning } from '../../../utils/utils.mjs'; +import { isRenderer } from '../../renderers/utils.mjs'; +import { RenderMaterial } from '../../materials/RenderMaterial.mjs'; +import { Texture } from '../../textures/Texture.mjs'; +import { Geometry } from '../../geometries/Geometry.mjs'; +import { RenderTexture } from '../../textures/RenderTexture.mjs'; +import default_vsWgsl from '../../shaders/chunks/default_vs.wgsl.mjs'; +import default_fsWgsl from '../../shaders/chunks/default_fs.wgsl.mjs'; + +var __accessCheck = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); +}; +var __privateGet = (obj, member, getter) => { + __accessCheck(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); +}; +var __privateAdd = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); +}; +var __privateSet = (obj, member, value, setter) => { + __accessCheck(obj, member, "write to private field"); + setter ? setter.call(obj, value) : member.set(obj, value); + return value; +}; +let meshIndex = 0; +const defaultMeshBaseParams = { + // geometry + geometry: new Geometry(), + // material + shaders: {}, + autoRender: true, + useProjection: false, + useAsyncPipeline: true, + // rendering + cullMode: "back", + depth: true, + depthWriteEnabled: true, + depthCompare: "less", + depthFormat: "depth24plus", + transparent: false, + visible: true, + renderOrder: 0, + // textures + texturesOptions: {} +}; +function MeshBaseMixin(Base) { + var _autoRender, _a; + return _a = class extends Base { + /** + * MeshBase constructor + * + * @typedef MeshBaseArrayParams + * @type {array} + * @property {(Renderer|GPUCurtains)} 0 - our {@link Renderer} class object + * @property {(string|HTMLElement|null)} 1 - a DOM HTML Element that can be bound to a Mesh + * @property {MeshBaseParams} 2 - {@link MeshBaseParams | Mesh base parameters} + * + * @param {MeshBaseArrayParams} params - our MeshBaseMixin parameters + */ + constructor(...params) { + super( + params[0], + params[1], + { ...defaultMeshBaseParams, ...params[2] } + ); + /** Whether we should add this {@link MeshBase} to our {@link core/scenes/Scene.Scene | Scene} to let it handle the rendering process automatically */ + __privateAdd(this, _autoRender, true); + // callbacks / events + /** function assigned to the {@link onReady} callback */ + this._onReadyCallback = () => { + }; + /** function assigned to the {@link onBeforeRender} callback */ + this._onBeforeRenderCallback = () => { + }; + /** function assigned to the {@link onRender} callback */ + this._onRenderCallback = () => { + }; + /** function assigned to the {@link onAfterRender} callback */ + this._onAfterRenderCallback = () => { + }; + /** function assigned to the {@link onAfterResize} callback */ + this._onAfterResizeCallback = () => { + }; + let renderer = params[0]; + const parameters = { ...defaultMeshBaseParams, ...params[2] }; + this.type = "MeshBase"; + this.uuid = generateUUID(); + Object.defineProperty(this, "index", { value: meshIndex++ }); + renderer = renderer && renderer.renderer || renderer; + isRenderer(renderer, parameters.label ? parameters.label + " " + this.type : this.type); + this.renderer = renderer; + const { + label, + shaders, + geometry, + visible, + renderOrder, + outputTarget, + texturesOptions, + autoRender, + ...meshParameters + } = parameters; + this.outputTarget = outputTarget ?? null; + meshParameters.sampleCount = !!meshParameters.sampleCount ? meshParameters.sampleCount : this.outputTarget ? this.outputTarget.renderPass.options.sampleCount : this.renderer && this.renderer.renderPass ? this.renderer.renderPass.options.sampleCount : 1; + this.options = { + ...this.options ?? {}, + // merge possible lower options? + label: label ?? "Mesh " + this.renderer.meshes.length, + shaders, + texturesOptions, + ...outputTarget !== void 0 && { outputTarget }, + ...autoRender !== void 0 && { autoRender }, + ...meshParameters.useAsyncPipeline !== void 0 && { useAsyncPipeline: meshParameters.useAsyncPipeline } + }; + this.geometry = geometry; + if (autoRender !== void 0) { + __privateSet(this, _autoRender, autoRender); + } + this.visible = visible; + this.renderOrder = renderOrder; + this.ready = false; + this.userData = {}; + this.computeGeometry(); + this.setMaterial({ + label: this.options.label, + shaders: this.options.shaders, + ...{ ...meshParameters, verticesOrder: geometry.verticesOrder, topology: geometry.topology } + }); + this.addToScene(); + } + /** + * Get private #autoRender value + * @readonly + */ + get autoRender() { + return __privateGet(this, _autoRender); + } + /** + * Get/set whether a Mesh is ready or not + * @readonly + */ + get ready() { + return this._ready; + } + set ready(value) { + if (value) { + this._onReadyCallback && this._onReadyCallback(); + } + this._ready = value; + } + /* SCENE */ + /** + * Add a Mesh to the renderer and the {@link core/scenes/Scene.Scene | Scene}. Can patch the {@link RenderMaterial} render options to match the {@link RenderPass} used to draw this Mesh. + */ + addToScene() { + this.renderer.meshes.push(this); + this.setRenderingOptionsForRenderPass(this.outputTarget ? this.outputTarget.renderPass : this.renderer.renderPass); + if (__privateGet(this, _autoRender)) { + this.renderer.scene.addMesh(this); + } + } + /** + * Remove a Mesh from the renderer and the {@link core/scenes/Scene.Scene | Scene} + */ + removeFromScene() { + if (__privateGet(this, _autoRender)) { + this.renderer.scene.removeMesh(this); + } + this.renderer.meshes = this.renderer.meshes.filter((m) => m.uuid !== this.uuid); + } + /** + * Set or update the {@link RenderMaterial} {@link types/Materials.RenderMaterialRenderingOptions | rendering options} to match the {@link RenderPass#descriptor | RenderPass descriptor} used to draw this Mesh. + * @param renderPass - {@link RenderPass | RenderPass} used to draw this Mesh, default to the {@link core/renderers/GPURenderer.GPURenderer#renderPass | renderer renderPass}. + */ + setRenderingOptionsForRenderPass(renderPass) { + const renderingOptions = { + sampleCount: renderPass.options.sampleCount, + // color attachments + ...renderPass.options.colorAttachments.length && { + targetFormat: renderPass.options.colorAttachments[0].targetFormat, + // multiple render targets? + ...renderPass.options.colorAttachments.length > 1 && { + additionalTargets: renderPass.options.colorAttachments.filter((c, i) => i > 0).map((colorAttachment) => { + return { + format: colorAttachment.targetFormat + }; + }) + } + }, + // depth + depth: renderPass.options.useDepth, + ...renderPass.options.useDepth && { + depthFormat: renderPass.options.depthFormat + } + }; + this.material?.setRenderingOptions(renderingOptions); + } + /** + * Set a new {@link Renderer} for this Mesh + * @param renderer - new {@link Renderer} to set + */ + setRenderer(renderer) { + renderer = renderer && renderer.renderer || renderer; + if (!renderer || !(renderer.type === "GPURenderer" || renderer.type === "GPUCameraRenderer" || renderer.type === "GPUCurtainsRenderer")) { + throwWarning( + `${this.options.label}: Cannot set ${renderer} as a renderer because it is not of a valid Renderer type.` + ); + return; + } + const oldRenderer = this.renderer; + this.removeFromScene(); + this.renderer = renderer; + this.addToScene(); + if (!oldRenderer.meshes.length) { + oldRenderer.onBeforeRenderScene.add( + (commandEncoder) => { + oldRenderer.forceClear(commandEncoder); + }, + { once: true } + ); + } + } + /** + * Assign or remove a {@link RenderTarget} to this Mesh + * Since this manipulates the {@link core/scenes/Scene.Scene | Scene} stacks, it can be used to remove a RenderTarget as well. + * @param outputTarget - the RenderTarget to assign or null if we want to remove the current RenderTarget + */ + setOutputTarget(outputTarget) { + if (outputTarget && outputTarget.type !== "RenderTarget") { + throwWarning(`${this.options.label ?? this.type}: outputTarget is not a RenderTarget: ${outputTarget}`); + return; + } + this.removeFromScene(); + this.outputTarget = outputTarget; + this.addToScene(); + } + /** + * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been lost to prepare everything for restoration. + * Basically set all the {@link GPUBuffer} to null so they will be reset next time we try to draw the Mesh + */ + loseContext() { + this.geometry.vertexBuffers.forEach((vertexBuffer) => { + vertexBuffer.buffer = null; + }); + if ("indexBuffer" in this.geometry) { + this.geometry.indexBuffer.buffer = null; + } + this.material.loseContext(); + } + /** + * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been restored + */ + restoreContext() { + this.material.restoreContext(); + } + /* SHADERS */ + /** + * Set default shaders if one or both of them are missing + */ + setShaders() { + let { shaders } = this.options; + if (!shaders) { + shaders = { + vertex: { + code: default_vsWgsl, + entryPoint: "main" + }, + fragment: { + code: default_fsWgsl, + entryPoint: "main" + } + }; + } else { + if (!shaders.vertex || !shaders.vertex.code) { + shaders.vertex = { + code: default_vsWgsl, + entryPoint: "main" + }; + } + if (shaders.fragment === void 0 || shaders.fragment && !shaders.fragment.code) { + shaders.fragment = { + code: default_fsWgsl, + entryPoint: "main" + }; + } + } + } + /* GEOMETRY */ + /** + * Compute the Mesh geometry if needed + */ + computeGeometry() { + if (this.geometry.shouldCompute) { + this.geometry.computeGeometry(); + } + } + /** + * Create the Mesh Geometry vertex and index buffers if needed + */ + createGeometryBuffers() { + if (!this.geometry.ready) { + this.geometry.vertexBuffers.forEach((vertexBuffer) => { + if (!vertexBuffer.buffer) { + vertexBuffer.buffer = this.renderer.createBuffer({ + label: this.options.label + " geometry: " + vertexBuffer.name + " buffer", + size: vertexBuffer.array.byteLength, + usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST + }); + this.renderer.queueWriteBuffer(vertexBuffer.buffer, 0, vertexBuffer.array); + } + }); + if ("indexBuffer" in this.geometry && this.geometry.indexBuffer && !this.geometry.indexBuffer.buffer) { + this.geometry.indexBuffer.buffer = this.renderer.createBuffer({ + label: this.options.label + " geometry: index buffer", + size: this.geometry.indexBuffer.array.byteLength, + usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST + }); + this.renderer.queueWriteBuffer(this.geometry.indexBuffer.buffer, 0, this.geometry.indexBuffer.array); + } + } + } + /** + * Set our Mesh geometry: create buffers and add attributes to material + */ + setGeometry() { + if (this.geometry && this.renderer.ready) { + this.createGeometryBuffers(); + this.setMaterialGeometryAttributes(); + } + } + /* MATERIAL */ + /** + * Set a Mesh transparent property, then set its material + * @param meshParameters - {@link RenderMaterialParams | RenderMaterial parameters} + */ + setMaterial(meshParameters) { + this.transparent = meshParameters.transparent; + this.setShaders(); + this.material = new RenderMaterial(this.renderer, meshParameters); + this.material.options.textures?.filter((texture) => texture instanceof Texture).forEach((texture) => this.onTextureAdded(texture)); + } + /** + * Set Mesh material attributes + */ + setMaterialGeometryAttributes() { + if (this.material && !this.material.attributes) { + this.material.setAttributesFromGeometry(this.geometry); + } + } + /* TEXTURES */ + /** + * Get our {@link RenderMaterial#textures | RenderMaterial textures array} + * @readonly + */ + get textures() { + return this.material?.textures || []; + } + /** + * Get our {@link RenderMaterial#renderTextures | RenderMaterial render textures array} + * @readonly + */ + get renderTextures() { + return this.material?.renderTextures || []; + } + /** + * Create a new {@link Texture} + * @param options - {@link TextureParams | Texture parameters} + * @returns - newly created {@link Texture} + */ + createTexture(options) { + if (!options.name) { + options.name = "texture" + this.textures.length; + } + if (!options.label) { + options.label = this.options.label + " " + options.name; + } + const texture = new Texture(this.renderer, { ...options, ...this.options.texturesOptions }); + this.addTexture(texture); + return texture; + } + /** + * Add a {@link Texture} + * @param texture - {@link Texture} to add + */ + addTexture(texture) { + this.material.addTexture(texture); + this.onTextureAdded(texture); + } + /** + * Callback run when a new {@link Texture} has been added + * @param texture - newly created Texture + */ + onTextureAdded(texture) { + texture.parentMesh = this; + } + /** + * Create a new {@link RenderTexture} + * @param options - {@link RenderTextureParams | RenderTexture parameters} + * @returns - newly created {@link RenderTexture} + */ + createRenderTexture(options) { + if (!options.name) { + options.name = "renderTexture" + this.renderTextures.length; + } + const renderTexture = new RenderTexture(this.renderer, options); + this.addRenderTexture(renderTexture); + return renderTexture; + } + /** + * Add a {@link RenderTexture} + * @param renderTexture - {@link RenderTexture} to add + */ + addRenderTexture(renderTexture) { + this.material.addTexture(renderTexture); + } + /* BINDINGS */ + /** + * Get the current {@link RenderMaterial} uniforms + * @readonly + */ + get uniforms() { + return this.material?.uniforms; + } + /** + * Get the current {@link RenderMaterial} storages + * @readonly + */ + get storages() { + return this.material?.storages; + } + /* RESIZE */ + /** + * Resize the Mesh's textures + * @param boundingRect + */ + resize(boundingRect) { + if (super.resize) { + super.resize(boundingRect); + } + this.renderTextures?.forEach((renderTexture) => { + if (renderTexture.options.fromTexture) { + renderTexture.copy(renderTexture.options.fromTexture); + } + }); + this.textures?.forEach((texture) => { + texture.resize(); + }); + this._onAfterResizeCallback && this._onAfterResizeCallback(); + } + /* EVENTS */ + /** + * Assign a callback function to _onReadyCallback + * @param callback - callback to run when {@link MeshBase} is ready + * @returns - our Mesh + */ + onReady(callback) { + if (callback) { + this._onReadyCallback = callback; + } + return this; + } + /** + * Assign a callback function to _onBeforeRenderCallback + * @param callback - callback to run just before {@link MeshBase} will be rendered + * @returns - our Mesh + */ + onBeforeRender(callback) { + if (callback) { + this._onBeforeRenderCallback = callback; + } + return this; + } + /** + * Assign a callback function to _onRenderCallback + * @param callback - callback to run when {@link MeshBase} is rendered + * @returns - our Mesh + */ + onRender(callback) { + if (callback) { + this._onRenderCallback = callback; + } + return this; + } + /** + * Assign a callback function to _onAfterRenderCallback + * @param callback - callback to run just after {@link MeshBase} has been rendered + * @returns - our Mesh + */ + onAfterRender(callback) { + if (callback) { + this._onAfterRenderCallback = callback; + } + return this; + } + /** + * Assign a callback function to _onAfterResizeCallback + * @param callback - callback to run just after {@link MeshBase} has been resized + * @returns - our Mesh + */ + onAfterResize(callback) { + if (callback) { + this._onAfterResizeCallback = callback; + } + return this; + } + /* RENDER */ + /** + * Called before rendering the Mesh + * Set the geometry if needed (create buffers and add attributes to the {@link RenderMaterial}) + * Then executes {@link RenderMaterial#onBeforeRender}: create its bind groups and pipeline if needed and eventually update its struct + */ + onBeforeRenderPass() { + if (!this.renderer.ready) + return; + if (this.material && this.material.ready && this.geometry && this.geometry.ready && !this.ready) { + this.ready = true; + } + this.setGeometry(); + this._onBeforeRenderCallback && this._onBeforeRenderCallback(); + this.material.onBeforeRender(); + } + /** + * Render our {@link MeshBase} if the {@link RenderMaterial} is ready + * @param pass - current render pass encoder + */ + onRenderPass(pass) { + if (!this.material.ready) + return; + this._onRenderCallback && this._onRenderCallback(); + this.material.render(pass); + this.geometry.render(pass); + } + /** + * Called after having rendered the Mesh + */ + onAfterRenderPass() { + this._onAfterRenderCallback && this._onAfterRenderCallback(); + } + /** + * Render our Mesh + * - Execute {@link onBeforeRenderPass} + * - Stop here if {@link Renderer} is not ready or Mesh is not {@link visible} + * - Execute super render call if it exists + * - {@link onRenderPass | render} our {@link material} and {@link geometry} + * - Execute {@link onAfterRenderPass} + * @param pass - current render pass encoder + */ + render(pass) { + this.onBeforeRenderPass(); + if (!this.renderer.ready || !this.visible) + return; + if (super.render) { + super.render(); + } + !this.renderer.production && pass.pushDebugGroup(this.options.label); + this.onRenderPass(pass); + !this.renderer.production && pass.popDebugGroup(); + this.onAfterRenderPass(); + } + /* DESTROY */ + /** + * Remove the Mesh from the {@link core/scenes/Scene.Scene | Scene} and destroy it + */ + remove() { + this.removeFromScene(); + this.destroy(); + if (!this.renderer.meshes.length) { + this.renderer.onBeforeRenderScene.add( + (commandEncoder) => { + this.renderer.forceClear(commandEncoder); + }, + { once: true } + ); + } + } + /** + * Destroy the Mesh + */ + destroy() { + if (super.destroy) { + super.destroy(); + } + this.material?.destroy(); + this.geometry.vertexBuffers.forEach((vertexBuffer) => { + this.renderer.removeBuffer( + vertexBuffer.buffer, + this.options.label + " geometry: " + vertexBuffer.name + " buffer" + ); + }); + if ("indexBuffer" in this.geometry) { + this.renderer.removeBuffer(this.geometry.indexBuffer.buffer); + } + this.geometry?.destroy(); + } + }, _autoRender = new WeakMap(), _a; +} + +export { MeshBaseMixin }; +//# sourceMappingURL=MeshBaseMixin.mjs.map diff --git a/dist/esm/core/meshes/mixins/MeshBaseMixin.mjs.map b/dist/esm/core/meshes/mixins/MeshBaseMixin.mjs.map new file mode 100644 index 000000000..d9671bd90 --- /dev/null +++ b/dist/esm/core/meshes/mixins/MeshBaseMixin.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"MeshBaseMixin.mjs","sources":["../../../../../src/core/meshes/mixins/MeshBaseMixin.ts"],"sourcesContent":["import { generateUUID, throwWarning } from '../../../utils/utils'\nimport { isRenderer, Renderer } from '../../renderers/utils'\nimport { RenderMaterial } from '../../materials/RenderMaterial'\nimport { Texture } from '../../textures/Texture'\nimport { Geometry } from '../../geometries/Geometry'\nimport { RenderTexture, RenderTextureParams } from '../../textures/RenderTexture'\nimport { ExternalTextureParams, TextureParams, TextureParent } from '../../../types/Textures'\nimport { RenderTarget } from '../../renderPasses/RenderTarget'\nimport { GPUCurtains } from '../../../curtains/GPUCurtains'\nimport { ProjectedMesh } from '../../renderers/GPURenderer'\nimport { Material } from '../../materials/Material'\nimport { DOMElementBoundingRect } from '../../DOM/DOMElement'\nimport { AllowedGeometries, RenderMaterialParams, ShaderOptions } from '../../../types/Materials'\nimport { ProjectedMeshBaseClass } from './ProjectedMeshBaseMixin'\nimport default_vsWgsl from '../../shaders/chunks/default_vs.wgsl'\nimport default_fsWgsl from '../../shaders/chunks/default_fs.wgsl'\nimport { RenderPass } from '../../renderPasses/RenderPass'\n\nlet meshIndex = 0\n\nexport interface MeshBaseRenderParams extends RenderMaterialParams {\n /** Whether we should add this Mesh to our {@link core/scenes/Scene.Scene | Scene} to let it handle the rendering process automatically */\n autoRender?: boolean\n /** Flag indicating whether to draw this Mesh or not */\n visible?: boolean\n /** Controls the order in which this Mesh should be rendered by our {@link core/scenes/Scene.Scene | Scene} */\n renderOrder?: number\n /** Optional {@link RenderTarget} to render this Mesh to instead of the canvas context. */\n outputTarget?: RenderTarget\n /** Parameters used by this Mesh to create a {@link Texture} */\n texturesOptions?: ExternalTextureParams\n}\n\n/**\n * Base parameters used to create a Mesh\n */\nexport interface MeshBaseParams extends MeshBaseRenderParams {\n /** Geometry to use */\n geometry: AllowedGeometries\n}\n\n/**\n * Base options used to create this Mesh\n */\nexport interface MeshBaseOptions {\n /** The label of this Mesh, sent to various GPU objects for debugging purpose */\n label?: MeshBaseParams['label']\n /** Shaders to use by this Mesh {@link RenderMaterial} */\n shaders?: MeshBaseParams['shaders']\n /** Parameters used by this Mesh to create a {@link Texture} */\n texturesOptions?: ExternalTextureParams\n /** {@link RenderTarget} to render this Mesh to instead of the canvas context, if any. */\n outputTarget?: RenderTarget | null\n /** Whether we should add this Mesh to our {@link core/scenes/Scene.Scene | Scene} to let it handle the rendering process automatically */\n autoRender?: boolean\n /** Whether to compile this Mesh {@link RenderMaterial} {@link core/pipelines/RenderPipelineEntry.RenderPipelineEntry#pipeline | render pipeline} asynchronously or not */\n useAsyncPipeline?: boolean\n}\n\n/** @const - Default Mesh parameters to merge with user defined parameters */\nconst defaultMeshBaseParams: MeshBaseParams = {\n // geometry\n geometry: new Geometry(),\n // material\n shaders: {},\n autoRender: true,\n useProjection: false,\n useAsyncPipeline: true,\n // rendering\n cullMode: 'back',\n depth: true,\n depthWriteEnabled: true,\n depthCompare: 'less',\n depthFormat: 'depth24plus',\n transparent: false,\n visible: true,\n renderOrder: 0,\n // textures\n texturesOptions: {},\n}\n\n// based on https://stackoverflow.com/a/75673107/13354068\n// we declare first a class, and then the mixin with a return type\n/**\n * This class describes the properties and methods to set up a basic Mesh, implemented in the {@link MeshBaseMixin}:\n * - Set and render the {@link Geometry} and {@link RenderMaterial}\n * - Add helpers to create {@link Texture} and {@link RenderTexture}\n * - Handle resizing, device lost/restoration and destroying the resources\n */\nexport declare class MeshBaseClass {\n /** The type of the {@link MeshBaseClass} */\n type: string\n /** The universal unique id of the {@link MeshBaseClass} */\n readonly uuid: string\n /** Index of this {@link MeshBaseClass}, i.e. creation order */\n readonly index: number\n /** The {@link Renderer} used */\n renderer: Renderer\n\n /** Options used to create this {@link MeshBaseClass} */\n options: MeshBaseOptions\n\n /** {@link RenderMaterial} used by this {@link MeshBaseClass} */\n material: RenderMaterial\n /** {@link AllowedGeometries | Geometry} used by this {@link MeshBaseClass} */\n geometry: MeshBaseParams['geometry']\n\n /** {@link RenderTarget} to render this Mesh to instead of the canvas context, if any. */\n outputTarget: null | RenderTarget\n\n /** Controls the order in which this {@link MeshBaseClass} should be rendered by our {@link core/scenes/Scene.Scene | Scene} */\n renderOrder: number\n /** Whether this {@link MeshBaseClass} should be treated as transparent. Impacts the {@link core/pipelines/RenderPipelineEntry.RenderPipelineEntry#pipeline | render pipeline} blend properties */\n transparent: boolean\n\n /** Flag indicating whether to draw this {@link MeshBaseClass} or not */\n visible: boolean\n /** Flag indicating whether this {@link MeshBaseClass} is ready to be drawn */\n _ready: boolean\n\n /** Empty object to store any additional data or custom properties into your Mesh. */\n userData: Record\n\n // callbacks\n /** function assigned to the {@link onReady} callback */\n _onReadyCallback: () => void\n /** function assigned to the {@link onBeforeRender} callback */\n _onBeforeRenderCallback: () => void\n /** function assigned to the {@link onRender} callback */\n _onRenderCallback: () => void\n /** function assigned to the {@link onAfterRender} callback */\n _onAfterRenderCallback: () => void\n /** function assigned to the {@link onAfterResize} callback */\n _onAfterResizeCallback: () => void\n /**\n * Assign a callback function to _onReadyCallback\n * @param callback - callback to run when {@link MeshBaseClass} is ready\n * @returns - our Mesh\n */\n onReady: (callback: () => void) => MeshBaseClass | ProjectedMeshBaseClass\n /**\n * Assign a callback function to _onBeforeRenderCallback\n * @param callback - callback to run just before {@link MeshBaseClass} will be rendered\n * @returns - our Mesh\n */\n onBeforeRender: (callback: () => void) => MeshBaseClass | ProjectedMeshBaseClass\n /**\n * Assign a callback function to _onRenderCallback\n * @param callback - callback to run when {@link MeshBaseClass} is rendered\n * @returns - our Mesh\n */\n onRender: (callback: () => void) => MeshBaseClass | ProjectedMeshBaseClass\n /**\n * Assign a callback function to _onAfterRenderCallback\n * @param callback - callback to run just after {@link MeshBaseClass} has been rendered\n * @returns - our Mesh\n */\n onAfterRender: (callback: () => void) => MeshBaseClass | ProjectedMeshBaseClass\n /**\n * Assign a callback function to _onBeforeRenderCallback\n * @param callback - callback to run just after {@link MeshBaseClass} has been resized\n * @returns - our Mesh\n */\n onAfterResize: (callback: () => void) => MeshBaseClass | ProjectedMeshBaseClass\n\n /**\n * {@link MeshBaseClass} constructor\n * @param renderer - our {@link Renderer} class object\n * @param element - a DOM HTML Element that can be bound to a Mesh\n * @param parameters - {@link MeshBaseParams | Mesh base parameters}\n */\n constructor(renderer: Renderer, element: HTMLElement | null, parameters: MeshBaseParams)\n\n /**\n * Get private #autoRender value\n * @readonly\n */\n get autoRender(): boolean // allow to read value from child classes\n\n /**\n * Get/set whether a Mesh is ready or not\n * @readonly\n */\n get ready(): boolean\n set ready(value: boolean)\n\n /**\n * Add a Mesh to the renderer and the {@link core/scenes/Scene.Scene | Scene}\n */\n addToScene(): void\n\n /**\n * Remove a Mesh from the renderer and the {@link core/scenes/Scene.Scene | Scene}\n */\n removeFromScene(): void\n\n /**\n * Set or update the {@link RenderMaterial} {@link types/Materials.RenderMaterialRenderingOptions | rendering options} to match the {@link RenderPass#descriptor | RenderPass descriptor} used to draw this Mesh.\n * @param renderPass - {@link RenderPass | RenderPass} used to draw this Mesh, default to the {@link core/renderers/GPURenderer.GPURenderer#renderPass | renderer renderPass}.\n */\n setRenderingOptionsForRenderPass(renderPass: RenderPass): void\n\n /**\n * Set a new {@link Renderer} for this Mesh\n * @param renderer - new {@link Renderer} to set\n */\n setRenderer(renderer: Renderer | GPUCurtains): void\n\n /**\n * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been lost to prepare everything for restoration.\n * Basically set all the {@link GPUBuffer} to null so they will be reset next time we try to draw the Mesh\n */\n loseContext(): void\n\n /**\n * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been restored\n */\n restoreContext(): void\n\n /**\n * Set default shaders if one or both of them are missing\n */\n setShaders(): void\n\n /**\n * Compute the Mesh geometry if needed\n */\n computeGeometry(): void\n\n /**\n * Create the Mesh Geometry vertex and index buffers if needed\n */\n createGeometryBuffers(): void\n\n /**\n * Set our Mesh geometry: create buffers and add attributes to material\n */\n setGeometry(): void\n\n /**\n * Set a Mesh transparent property, then set its material\n * @param meshParameters - {@link RenderMaterialParams | RenderMaterial parameters}\n */\n setMaterial(meshParameters: RenderMaterialParams): void\n\n /**\n * Set Mesh material attributes\n */\n setMaterialGeometryAttributes(): void\n\n /**\n * Get our {@link RenderMaterial#textures | RenderMaterial textures array}\n * @readonly\n */\n get textures(): Texture[]\n\n /**\n * Get our {@link RenderMaterial#renderTextures | RenderMaterial render textures array}\n * @readonly\n */\n get renderTextures(): RenderTexture[]\n\n /**\n * Create a new {@link Texture}\n * @param options - {@link TextureParams | Texture parameters}\n * @returns - newly created Texture\n */\n createTexture(options: TextureParams): Texture\n\n /**\n * Add a {@link Texture}\n * @param texture - {@link Texture} to add\n */\n addTexture(texture: Texture)\n\n /**\n * Callback run when a new {@link Texture} has been created\n * @param texture - newly created Texture\n */\n onTextureAdded(texture: Texture): void\n\n /**\n * Create a new {@link RenderTexture}\n * @param options - {@link RenderTextureParams | RenderTexture parameters}\n * @returns - newly created RenderTexture\n */\n createRenderTexture(options: RenderTextureParams): RenderTexture\n\n /**\n * Add a {@link RenderTexture}\n * @param renderTexture - {@link RenderTexture} to add\n */\n addRenderTexture(renderTexture: RenderTexture)\n\n /**\n * Assign or remove a {@link RenderTarget} to this Mesh\n * Since this manipulates the {@link core/scenes/Scene.Scene | Scene} stacks, it can be used to remove a RenderTarget as well.\n * @param outputTarget - the RenderTarget to assign or null if we want to remove the current RenderTarget\n */\n setOutputTarget(outputTarget: RenderTarget | null): void\n\n /**\n * Get the current {@link RenderMaterial} uniforms\n * @readonly\n */\n get uniforms(): Material['uniforms']\n\n /**\n * Get the current {@link RenderMaterial} storages\n * @readonly\n */\n get storages(): Material['storages']\n\n /**\n * Resize the Mesh's textures\n * @param boundingRect\n */\n resize(boundingRect?: DOMElementBoundingRect): void\n\n /**\n * Called before rendering the Mesh\n * Set the geometry if needed (create buffers and add attributes to the {@link RenderMaterial})\n * Then executes {@link RenderMaterial#onBeforeRender}: create its bind groups and pipeline if needed and eventually update its struct\n */\n onBeforeRenderPass(): void\n\n /**\n * Render our {@link MeshBaseClass} if the {@link RenderMaterial} is ready\n * @param pass - current render pass encoder\n */\n onRenderPass(pass: GPURenderPassEncoder): void\n\n /**\n * Called after having rendered the Mesh\n */\n onAfterRenderPass(): void\n\n /**\n * Render our Mesh\n * - Execute {@link onBeforeRenderPass}\n * - Stop here if {@link Renderer} is not ready or Mesh is not {@link visible}\n * - Execute super render call if it exists\n * - {@link onRenderPass | render} our {@link material} and {@link geometry}\n * - Execute {@link onAfterRenderPass}\n * @param pass - current render pass encoder\n */\n render(pass: GPURenderPassEncoder): void\n\n /**\n * Remove the Mesh from the {@link core/scenes/Scene.Scene | Scene} and destroy it\n */\n remove(): void\n\n /**\n * Destroy the Mesh\n */\n destroy(): void\n}\n\n/**\n * To get started, we need a type which we'll use to extend\n * other classes from. The main responsibility is to declare\n * that the type being passed in is a class.\n * We use a generic version which can apply a constraint on\n * the class which this mixin is applied to\n * @typeParam T - the base constructor\n */\nexport type MixinConstructor = new (...args: any[]) => T\n\n/**\n * Used to mix the basic Mesh properties and methods defined in {@link MeshBaseClass} (basically, set a {@link Geometry} and a {@link RenderMaterial} and render them, add helpers to create {@link Texture} and {@link RenderTexture}) with a given Base of type {@link core/objects3D/Object3D.Object3D | Object3D}, {@link core/objects3D/ProjectedObject3D.ProjectedObject3D | ProjectedObject3D}, {@link curtains/objects3D/DOMObject3D.DOMObject3D | DOMObject3D} or an empty class.\n * @exports MeshBaseMixin\n * @param Base - the class to mix onto\n * @returns - the mixed classes, creating a basic Mesh.\n */\nfunction MeshBaseMixin(Base: TBase): MixinConstructor & TBase {\n /**\n * MeshBase defines our base properties and methods\n */\n return class MeshBase extends Base implements MeshBaseClass {\n /** The type of the {@link MeshBase} */\n type: string\n /** The universal unique id of the {@link MeshBase} */\n readonly uuid: string\n /** Index of this {@link MeshBase}, i.e. creation order */\n readonly index: number\n /** The {@link Renderer} used */\n renderer: Renderer\n\n /** Options used to create this {@link MeshBase} */\n options: MeshBaseOptions\n\n /** {@link RenderMaterial} used by this {@link MeshBase} */\n material: RenderMaterial\n /** {@link AllowedGeometries | Geometry} used by this {@link MeshBase} */\n geometry: MeshBaseParams['geometry']\n\n /** {@link RenderTarget} to render this Mesh to, if any */\n outputTarget: null | RenderTarget\n\n /** Controls the order in which this {@link MeshBase} should be rendered by our {@link core/scenes/Scene.Scene | Scene} */\n renderOrder: number\n /** Whether this {@link MeshBase} should be treated as transparent. Impacts the {@link core/pipelines/RenderPipelineEntry.RenderPipelineEntry#pipeline | render pipeline} blend properties */\n transparent: boolean\n\n /** Flag indicating whether to draw this {@link MeshBase} or not */\n visible: boolean\n /** Flag indicating whether this {@link MeshBase} is ready to be drawn */\n _ready: boolean\n\n /** Empty object to store any additional data or custom properties into your {@link MeshBase}. */\n userData: Record\n\n /** Whether we should add this {@link MeshBase} to our {@link core/scenes/Scene.Scene | Scene} to let it handle the rendering process automatically */\n #autoRender = true\n\n // callbacks / events\n /** function assigned to the {@link onReady} callback */\n _onReadyCallback: () => void = () => {\n /* allow empty callback */\n }\n /** function assigned to the {@link onBeforeRender} callback */\n _onBeforeRenderCallback: () => void = () => {\n /* allow empty callback */\n }\n /** function assigned to the {@link onRender} callback */\n _onRenderCallback: () => void = () => {\n /* allow empty callback */\n }\n /** function assigned to the {@link onAfterRender} callback */\n _onAfterRenderCallback: () => void = () => {\n /* allow empty callback */\n }\n /** function assigned to the {@link onAfterResize} callback */\n _onAfterResizeCallback: () => void = () => {\n /* allow empty callback */\n }\n\n /**\n * MeshBase constructor\n *\n * @typedef MeshBaseArrayParams\n * @type {array}\n * @property {(Renderer|GPUCurtains)} 0 - our {@link Renderer} class object\n * @property {(string|HTMLElement|null)} 1 - a DOM HTML Element that can be bound to a Mesh\n * @property {MeshBaseParams} 2 - {@link MeshBaseParams | Mesh base parameters}\n *\n * @param {MeshBaseArrayParams} params - our MeshBaseMixin parameters\n */\n constructor(...params: any[]) {\n super(\n params[0] as Renderer | GPUCurtains,\n params[1] as HTMLElement | string | null,\n { ...defaultMeshBaseParams, ...params[2] } as MeshBaseParams\n )\n\n let renderer = params[0]\n const parameters = { ...defaultMeshBaseParams, ...params[2] }\n\n this.type = 'MeshBase'\n\n this.uuid = generateUUID()\n Object.defineProperty(this as MeshBase, 'index', { value: meshIndex++ })\n\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\n\n isRenderer(renderer, parameters.label ? parameters.label + ' ' + this.type : this.type)\n\n this.renderer = renderer\n\n const {\n label,\n shaders,\n geometry,\n visible,\n renderOrder,\n outputTarget,\n texturesOptions,\n autoRender,\n ...meshParameters\n } = parameters\n\n this.outputTarget = outputTarget ?? null\n\n // set default sample count\n meshParameters.sampleCount = !!meshParameters.sampleCount\n ? meshParameters.sampleCount\n : this.outputTarget\n ? this.outputTarget.renderPass.options.sampleCount\n : this.renderer && this.renderer.renderPass\n ? this.renderer.renderPass.options.sampleCount\n : 1\n\n this.options = {\n ...(this.options ?? {}), // merge possible lower options?\n label: label ?? 'Mesh ' + this.renderer.meshes.length,\n shaders,\n texturesOptions,\n ...(outputTarget !== undefined && { outputTarget }),\n ...(autoRender !== undefined && { autoRender }),\n ...(meshParameters.useAsyncPipeline !== undefined && { useAsyncPipeline: meshParameters.useAsyncPipeline }),\n }\n\n this.geometry = geometry\n\n if (autoRender !== undefined) {\n this.#autoRender = autoRender\n }\n\n this.visible = visible\n this.renderOrder = renderOrder\n this.ready = false\n\n this.userData = {}\n\n this.computeGeometry()\n\n this.setMaterial({\n label: this.options.label,\n shaders: this.options.shaders,\n ...{ ...meshParameters, verticesOrder: geometry.verticesOrder, topology: geometry.topology },\n } as RenderMaterialParams)\n\n this.addToScene()\n }\n\n /**\n * Get private #autoRender value\n * @readonly\n */\n get autoRender(): boolean {\n return this.#autoRender\n }\n\n /**\n * Get/set whether a Mesh is ready or not\n * @readonly\n */\n get ready(): boolean {\n return this._ready\n }\n\n set ready(value: boolean) {\n if (value) {\n this._onReadyCallback && this._onReadyCallback()\n }\n this._ready = value\n }\n\n /* SCENE */\n\n /**\n * Add a Mesh to the renderer and the {@link core/scenes/Scene.Scene | Scene}. Can patch the {@link RenderMaterial} render options to match the {@link RenderPass} used to draw this Mesh.\n */\n addToScene() {\n this.renderer.meshes.push(this as unknown as ProjectedMesh)\n\n this.setRenderingOptionsForRenderPass(this.outputTarget ? this.outputTarget.renderPass : this.renderer.renderPass)\n\n if (this.#autoRender) {\n this.renderer.scene.addMesh(this as unknown as ProjectedMesh)\n }\n }\n\n /**\n * Remove a Mesh from the renderer and the {@link core/scenes/Scene.Scene | Scene}\n */\n removeFromScene() {\n if (this.#autoRender) {\n this.renderer.scene.removeMesh(this as unknown as ProjectedMesh)\n }\n\n this.renderer.meshes = this.renderer.meshes.filter((m) => m.uuid !== this.uuid)\n }\n\n /**\n * Set or update the {@link RenderMaterial} {@link types/Materials.RenderMaterialRenderingOptions | rendering options} to match the {@link RenderPass#descriptor | RenderPass descriptor} used to draw this Mesh.\n * @param renderPass - {@link RenderPass | RenderPass} used to draw this Mesh, default to the {@link core/renderers/GPURenderer.GPURenderer#renderPass | renderer renderPass}.\n */\n setRenderingOptionsForRenderPass(renderPass: RenderPass) {\n // a Mesh render material rendering options MUST match the render pass descriptor used to draw it!\n const renderingOptions = {\n sampleCount: renderPass.options.sampleCount,\n // color attachments\n ...(renderPass.options.colorAttachments.length && {\n targetFormat: renderPass.options.colorAttachments[0].targetFormat,\n // multiple render targets?\n ...(renderPass.options.colorAttachments.length > 1 && {\n additionalTargets: renderPass.options.colorAttachments\n .filter((c, i) => i > 0)\n .map((colorAttachment) => {\n return {\n format: colorAttachment.targetFormat,\n }\n }),\n }),\n }),\n // depth\n depth: renderPass.options.useDepth,\n ...(renderPass.options.useDepth && {\n depthFormat: renderPass.options.depthFormat,\n }),\n }\n\n this.material?.setRenderingOptions(renderingOptions)\n }\n\n /**\n * Set a new {@link Renderer} for this Mesh\n * @param renderer - new {@link Renderer} to set\n */\n setRenderer(renderer: Renderer | GPUCurtains) {\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\n\n if (\n !renderer ||\n !(\n renderer.type === 'GPURenderer' ||\n renderer.type === 'GPUCameraRenderer' ||\n renderer.type === 'GPUCurtainsRenderer'\n )\n ) {\n throwWarning(\n `${this.options.label}: Cannot set ${renderer} as a renderer because it is not of a valid Renderer type.`\n )\n return\n }\n\n const oldRenderer = this.renderer\n this.removeFromScene()\n this.renderer = renderer\n this.addToScene()\n\n // if old renderer does not contain any meshes any more\n // clear it\n if (!oldRenderer.meshes.length) {\n oldRenderer.onBeforeRenderScene.add(\n (commandEncoder) => {\n oldRenderer.forceClear(commandEncoder)\n },\n { once: true }\n )\n }\n }\n\n /**\n * Assign or remove a {@link RenderTarget} to this Mesh\n * Since this manipulates the {@link core/scenes/Scene.Scene | Scene} stacks, it can be used to remove a RenderTarget as well.\n * @param outputTarget - the RenderTarget to assign or null if we want to remove the current RenderTarget\n */\n setOutputTarget(outputTarget: RenderTarget | null) {\n if (outputTarget && outputTarget.type !== 'RenderTarget') {\n throwWarning(`${this.options.label ?? this.type}: outputTarget is not a RenderTarget: ${outputTarget}`)\n return\n }\n\n // ensure the mesh is in the correct scene stack\n this.removeFromScene()\n this.outputTarget = outputTarget\n this.addToScene()\n }\n\n /**\n * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been lost to prepare everything for restoration.\n * Basically set all the {@link GPUBuffer} to null so they will be reset next time we try to draw the Mesh\n */\n loseContext() {\n // first the geometry\n this.geometry.vertexBuffers.forEach((vertexBuffer) => {\n vertexBuffer.buffer = null\n })\n\n if ('indexBuffer' in this.geometry) {\n this.geometry.indexBuffer.buffer = null\n }\n\n // then the material\n this.material.loseContext()\n }\n\n /**\n * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been restored\n */\n restoreContext() {\n this.material.restoreContext()\n }\n\n /* SHADERS */\n\n /**\n * Set default shaders if one or both of them are missing\n */\n setShaders() {\n let { shaders } = this.options\n\n if (!shaders) {\n shaders = {\n vertex: {\n code: default_vsWgsl,\n entryPoint: 'main',\n },\n fragment: {\n code: default_fsWgsl,\n entryPoint: 'main',\n },\n }\n } else {\n if (!shaders.vertex || !shaders.vertex.code) {\n shaders.vertex = {\n code: default_vsWgsl,\n entryPoint: 'main',\n }\n }\n\n if (shaders.fragment === undefined || (shaders.fragment && !(shaders.fragment as ShaderOptions).code)) {\n shaders.fragment = {\n code: default_fsWgsl,\n entryPoint: 'main',\n }\n }\n }\n }\n\n /* GEOMETRY */\n\n /**\n * Compute the Mesh geometry if needed\n */\n computeGeometry() {\n if (this.geometry.shouldCompute) {\n this.geometry.computeGeometry()\n }\n }\n\n /**\n * Create the Mesh Geometry vertex and index buffers if needed\n */\n createGeometryBuffers() {\n if (!this.geometry.ready) {\n this.geometry.vertexBuffers.forEach((vertexBuffer) => {\n if (!vertexBuffer.buffer) {\n vertexBuffer.buffer = this.renderer.createBuffer({\n label: this.options.label + ' geometry: ' + vertexBuffer.name + ' buffer',\n size: vertexBuffer.array.byteLength,\n usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,\n })\n\n this.renderer.queueWriteBuffer(vertexBuffer.buffer, 0, vertexBuffer.array)\n }\n })\n\n // if it's an indexed geometry, create index GPUBuffer as well\n if ('indexBuffer' in this.geometry && this.geometry.indexBuffer && !this.geometry.indexBuffer.buffer) {\n this.geometry.indexBuffer.buffer = this.renderer.createBuffer({\n label: this.options.label + ' geometry: index buffer',\n size: this.geometry.indexBuffer.array.byteLength,\n usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,\n })\n\n this.renderer.queueWriteBuffer(this.geometry.indexBuffer.buffer, 0, this.geometry.indexBuffer.array)\n }\n }\n }\n\n /**\n * Set our Mesh geometry: create buffers and add attributes to material\n */\n setGeometry() {\n if (this.geometry && this.renderer.ready) {\n this.createGeometryBuffers()\n this.setMaterialGeometryAttributes()\n }\n }\n\n /* MATERIAL */\n\n /**\n * Set a Mesh transparent property, then set its material\n * @param meshParameters - {@link RenderMaterialParams | RenderMaterial parameters}\n */\n setMaterial(meshParameters: RenderMaterialParams) {\n this.transparent = meshParameters.transparent\n\n this.setShaders()\n\n this.material = new RenderMaterial(this.renderer, meshParameters)\n // add eventual textures passed as parameters\n this.material.options.textures\n ?.filter((texture) => texture instanceof Texture)\n .forEach((texture) => this.onTextureAdded(texture))\n }\n\n /**\n * Set Mesh material attributes\n */\n setMaterialGeometryAttributes() {\n if (this.material && !this.material.attributes) {\n this.material.setAttributesFromGeometry(this.geometry)\n }\n }\n\n /* TEXTURES */\n\n /**\n * Get our {@link RenderMaterial#textures | RenderMaterial textures array}\n * @readonly\n */\n get textures(): Texture[] {\n return this.material?.textures || []\n }\n\n /**\n * Get our {@link RenderMaterial#renderTextures | RenderMaterial render textures array}\n * @readonly\n */\n get renderTextures(): RenderTexture[] {\n return this.material?.renderTextures || []\n }\n\n /**\n * Create a new {@link Texture}\n * @param options - {@link TextureParams | Texture parameters}\n * @returns - newly created {@link Texture}\n */\n createTexture(options: TextureParams): Texture {\n if (!options.name) {\n options.name = 'texture' + this.textures.length\n }\n\n if (!options.label) {\n options.label = this.options.label + ' ' + options.name\n }\n\n const texture = new Texture(this.renderer, { ...options, ...this.options.texturesOptions })\n\n this.addTexture(texture)\n\n return texture\n }\n\n /**\n * Add a {@link Texture}\n * @param texture - {@link Texture} to add\n */\n addTexture(texture: Texture) {\n this.material.addTexture(texture)\n this.onTextureAdded(texture)\n }\n\n /**\n * Callback run when a new {@link Texture} has been added\n * @param texture - newly created Texture\n */\n onTextureAdded(texture: Texture) {\n texture.parentMesh = this as unknown as TextureParent\n }\n\n /**\n * Create a new {@link RenderTexture}\n * @param options - {@link RenderTextureParams | RenderTexture parameters}\n * @returns - newly created {@link RenderTexture}\n */\n createRenderTexture(options: RenderTextureParams): RenderTexture {\n if (!options.name) {\n options.name = 'renderTexture' + this.renderTextures.length\n }\n\n const renderTexture = new RenderTexture(this.renderer, options)\n\n this.addRenderTexture(renderTexture)\n\n return renderTexture\n }\n\n /**\n * Add a {@link RenderTexture}\n * @param renderTexture - {@link RenderTexture} to add\n */\n addRenderTexture(renderTexture: RenderTexture) {\n this.material.addTexture(renderTexture)\n }\n\n /* BINDINGS */\n\n /**\n * Get the current {@link RenderMaterial} uniforms\n * @readonly\n */\n get uniforms(): Material['uniforms'] {\n return this.material?.uniforms\n }\n\n /**\n * Get the current {@link RenderMaterial} storages\n * @readonly\n */\n get storages(): Material['storages'] {\n return this.material?.storages\n }\n\n /* RESIZE */\n\n /**\n * Resize the Mesh's textures\n * @param boundingRect\n */\n resize(boundingRect?: DOMElementBoundingRect | null) {\n // @ts-ignore\n if (super.resize) {\n // @ts-ignore\n super.resize(boundingRect)\n }\n\n this.renderTextures?.forEach((renderTexture) => {\n // copy from original textures again if needed\n if (renderTexture.options.fromTexture) {\n renderTexture.copy(renderTexture.options.fromTexture)\n }\n })\n\n // resize textures\n this.textures?.forEach((texture) => {\n texture.resize()\n })\n\n this._onAfterResizeCallback && this._onAfterResizeCallback()\n }\n\n /* EVENTS */\n\n /**\n * Assign a callback function to _onReadyCallback\n * @param callback - callback to run when {@link MeshBase} is ready\n * @returns - our Mesh\n */\n onReady(callback: () => void): MeshBase | ProjectedMeshBaseClass {\n if (callback) {\n this._onReadyCallback = callback\n }\n\n return this\n }\n\n /**\n * Assign a callback function to _onBeforeRenderCallback\n * @param callback - callback to run just before {@link MeshBase} will be rendered\n * @returns - our Mesh\n */\n onBeforeRender(callback: () => void): MeshBase | ProjectedMeshBaseClass {\n if (callback) {\n this._onBeforeRenderCallback = callback\n }\n\n return this\n }\n\n /**\n * Assign a callback function to _onRenderCallback\n * @param callback - callback to run when {@link MeshBase} is rendered\n * @returns - our Mesh\n */\n onRender(callback: () => void): MeshBase | ProjectedMeshBaseClass {\n if (callback) {\n this._onRenderCallback = callback\n }\n\n return this\n }\n\n /**\n * Assign a callback function to _onAfterRenderCallback\n * @param callback - callback to run just after {@link MeshBase} has been rendered\n * @returns - our Mesh\n */\n onAfterRender(callback: () => void): MeshBase | ProjectedMeshBaseClass {\n if (callback) {\n this._onAfterRenderCallback = callback\n }\n\n return this\n }\n\n /**\n * Assign a callback function to _onAfterResizeCallback\n * @param callback - callback to run just after {@link MeshBase} has been resized\n * @returns - our Mesh\n */\n onAfterResize(callback: () => void): MeshBase | ProjectedMeshBaseClass {\n if (callback) {\n this._onAfterResizeCallback = callback\n }\n\n return this\n }\n\n /* RENDER */\n\n /**\n * Called before rendering the Mesh\n * Set the geometry if needed (create buffers and add attributes to the {@link RenderMaterial})\n * Then executes {@link RenderMaterial#onBeforeRender}: create its bind groups and pipeline if needed and eventually update its struct\n */\n onBeforeRenderPass() {\n if (!this.renderer.ready) return\n\n if (this.material && this.material.ready && this.geometry && this.geometry.ready && !this.ready) {\n this.ready = true\n }\n\n this.setGeometry()\n\n this._onBeforeRenderCallback && this._onBeforeRenderCallback()\n\n this.material.onBeforeRender()\n }\n\n /**\n * Render our {@link MeshBase} if the {@link RenderMaterial} is ready\n * @param pass - current render pass encoder\n */\n onRenderPass(pass: GPURenderPassEncoder) {\n if (!this.material.ready) return\n\n this._onRenderCallback && this._onRenderCallback()\n\n // render ou material\n this.material.render(pass)\n // then render our geometry\n this.geometry.render(pass)\n }\n\n /**\n * Called after having rendered the Mesh\n */\n onAfterRenderPass() {\n this._onAfterRenderCallback && this._onAfterRenderCallback()\n }\n\n /**\n * Render our Mesh\n * - Execute {@link onBeforeRenderPass}\n * - Stop here if {@link Renderer} is not ready or Mesh is not {@link visible}\n * - Execute super render call if it exists\n * - {@link onRenderPass | render} our {@link material} and {@link geometry}\n * - Execute {@link onAfterRenderPass}\n * @param pass - current render pass encoder\n */\n render(pass: GPURenderPassEncoder) {\n this.onBeforeRenderPass()\n\n // no point to render if the WebGPU device is not ready\n if (!this.renderer.ready || !this.visible) return\n\n // @ts-ignore\n if (super.render) {\n // @ts-ignore\n super.render()\n }\n\n !this.renderer.production && pass.pushDebugGroup(this.options.label)\n\n this.onRenderPass(pass)\n\n !this.renderer.production && pass.popDebugGroup()\n\n this.onAfterRenderPass()\n }\n\n /* DESTROY */\n\n /**\n * Remove the Mesh from the {@link core/scenes/Scene.Scene | Scene} and destroy it\n */\n remove() {\n this.removeFromScene()\n this.destroy()\n\n // if the renderer does not contain any meshes any more\n // clear it\n if (!this.renderer.meshes.length) {\n this.renderer.onBeforeRenderScene.add(\n (commandEncoder) => {\n this.renderer.forceClear(commandEncoder)\n },\n { once: true }\n )\n }\n }\n\n /**\n * Destroy the Mesh\n */\n destroy() {\n // @ts-ignore\n if (super.destroy) {\n // @ts-ignore\n super.destroy()\n }\n\n this.material?.destroy()\n\n // remove geometry buffers from device cache\n this.geometry.vertexBuffers.forEach((vertexBuffer) => {\n // use original vertex buffer label in case it has been swapped (usually by a compute pass)\n this.renderer.removeBuffer(\n vertexBuffer.buffer,\n this.options.label + ' geometry: ' + vertexBuffer.name + ' buffer'\n )\n })\n\n if ('indexBuffer' in this.geometry) {\n this.renderer.removeBuffer(this.geometry.indexBuffer.buffer)\n }\n\n this.geometry?.destroy()\n }\n }\n}\n\nexport { MeshBaseMixin }\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/meshes/mixins/ProjectedMeshBaseMixin.mjs b/dist/esm/core/meshes/mixins/ProjectedMeshBaseMixin.mjs new file mode 100644 index 000000000..5b928f989 --- /dev/null +++ b/dist/esm/core/meshes/mixins/ProjectedMeshBaseMixin.mjs @@ -0,0 +1,245 @@ +import { isCameraRenderer } from '../../renderers/utils.mjs'; +import { DOMFrustum } from '../../DOM/DOMFrustum.mjs'; +import { MeshBaseMixin } from './MeshBaseMixin.mjs'; +import default_projected_vsWgsl from '../../shaders/chunks/default_projected_vs.wgsl.mjs'; +import default_normal_fsWgsl from '../../shaders/chunks/default_normal_fs.wgsl.mjs'; + +const defaultProjectedMeshParams = { + // frustum culling and visibility + frustumCulled: true, + DOMFrustumMargins: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } +}; +function ProjectedMeshBaseMixin(Base) { + return class ProjectedMeshBase extends MeshBaseMixin(Base) { + /** + * ProjectedMeshBase constructor + * + * @typedef MeshBaseArrayParams + * @type {array} + * @property {(CameraRenderer|GPUCurtains)} 0 - our renderer class object + * @property {(string|HTMLElement|null)} 1 - the DOM HTML Element that can be bound to a Mesh + * @property {ProjectedMeshParameters} 2 - Projected Mesh parameters + * + * @param {MeshBaseArrayParams} params - our MeshBaseMixin parameters + */ + constructor(...params) { + super( + params[0], + params[1], + { ...defaultProjectedMeshParams, ...params[2], ...{ useProjection: true } } + ); + // callbacks / events + /** function assigned to the {@link onReEnterView} callback */ + this._onReEnterViewCallback = () => { + }; + /** function assigned to the {@link onLeaveView} callback */ + this._onLeaveViewCallback = () => { + }; + let renderer = params[0]; + const parameters = { + ...defaultProjectedMeshParams, + ...params[2], + ...{ useProjection: true } + }; + this.type = "MeshTransformed"; + renderer = renderer && renderer.renderer || renderer; + isCameraRenderer(renderer, parameters.label ? parameters.label + " " + this.type : this.type); + this.renderer = renderer; + const { geometry, frustumCulled, DOMFrustumMargins } = parameters; + this.options = { + ...this.options ?? {}, + // merge possible lower options? + frustumCulled, + DOMFrustumMargins + }; + this.setDOMFrustum(); + this.geometry = geometry; + this.shouldUpdateMatrixStack(); + } + /* SHADERS */ + /** + * Set default shaders if one or both of them are missing + */ + setShaders() { + let { shaders } = this.options; + if (!shaders) { + shaders = { + vertex: { + code: default_projected_vsWgsl, + entryPoint: "main" + }, + fragment: { + code: default_normal_fsWgsl, + entryPoint: "main" + } + }; + } else { + if (!shaders.vertex || !shaders.vertex.code) { + shaders.vertex = { + code: default_projected_vsWgsl, + entryPoint: "main" + }; + } + if (shaders.fragment === void 0 || shaders.fragment && !shaders.fragment.code) { + shaders.fragment = { + code: default_normal_fsWgsl, + entryPoint: "main" + }; + } + } + } + /* GEOMETRY */ + /** + * Set the Mesh frustum culling + */ + setDOMFrustum() { + this.domFrustum = new DOMFrustum({ + boundingBox: this.geometry.boundingBox, + modelViewProjectionMatrix: this.modelViewProjectionMatrix, + containerBoundingRect: this.renderer.boundingRect, + DOMFrustumMargins: this.options.DOMFrustumMargins, + onReEnterView: () => { + this._onReEnterViewCallback && this._onReEnterViewCallback(); + }, + onLeaveView: () => { + this._onLeaveViewCallback && this._onLeaveViewCallback(); + } + }); + this.DOMFrustumMargins = this.domFrustum.DOMFrustumMargins; + this.frustumCulled = this.options.frustumCulled; + this.domFrustum.shouldUpdate = this.frustumCulled; + } + /* MATERIAL */ + /** + * Set a Mesh matrices uniforms inputs then call {@link MeshBaseClass} super method + * @param meshParameters - {@link ProjectedRenderMaterialParams | RenderMaterial parameters} + */ + setMaterial(meshParameters) { + const { frustumCulled, DOMFrustumMargins, ...materialParameters } = meshParameters; + const matricesUniforms = { + label: "Matrices", + struct: { + model: { + name: "model", + type: "mat4x4f", + value: this.modelMatrix + }, + world: { + name: "world", + type: "mat4x4f", + value: this.worldMatrix + }, + modelView: { + // model view matrix (world matrix multiplied by camera view matrix) + name: "modelView", + type: "mat4x4f", + value: this.modelViewMatrix + }, + modelViewProjection: { + name: "modelViewProjection", + type: "mat4x4f", + value: this.modelViewProjectionMatrix + } + } + }; + if (!materialParameters.uniforms) + materialParameters.uniforms = {}; + materialParameters.uniforms.matrices = matricesUniforms; + super.setMaterial(materialParameters); + } + /* SIZE & TRANSFORMS */ + /** + * Resize our {@link ProjectedMeshBaseClass} + * @param boundingRect - the new bounding rectangle + */ + resize(boundingRect) { + if (this.domFrustum) + this.domFrustum.setContainerBoundingRect(this.renderer.boundingRect); + super.resize(boundingRect); + } + /** + * Apply scale and resize textures + */ + applyScale() { + super.applyScale(); + this.textures.forEach((texture) => texture.resize()); + } + /** + * Get our {@link DOMFrustum} projected bounding rectangle + * @readonly + */ + get projectedBoundingRect() { + return this.domFrustum?.projectedBoundingRect; + } + /** + * At least one of the matrix has been updated, update according uniforms and frustum + */ + onAfterMatrixStackUpdate() { + if (this.material) { + this.material.shouldUpdateInputsBindings("matrices"); + } + if (this.domFrustum) + this.domFrustum.shouldUpdate = true; + } + /* EVENTS */ + /** + * Assign a callback function to _onReEnterViewCallback + * @param callback - callback to run when {@link ProjectedMeshBaseClass} is reentering the view frustum + * @returns - our Mesh + */ + onReEnterView(callback) { + if (callback) { + this._onReEnterViewCallback = callback; + } + return this; + } + /** + * Assign a callback function to _onLeaveViewCallback + * @param callback - callback to run when {@link ProjectedMeshBaseClass} is leaving the view frustum + * @returns - our Mesh + */ + onLeaveView(callback) { + if (callback) { + this._onLeaveViewCallback = callback; + } + return this; + } + /* RENDER */ + /** + * Called before rendering the Mesh to update matrices and {@link DOMFrustum}. + * First, we update our matrices to have fresh results. It eventually calls onAfterMatrixStackUpdate() if at least one matrix has been updated. + * Then we check if we need to update the {@link DOMFrustum} projected bounding rectangle. + * Finally we call {@link MeshBaseClass#onBeforeRenderPass | Mesh base onBeforeRenderPass} super + */ + onBeforeRenderPass() { + this.updateMatrixStack(); + if (this.domFrustum && this.domFrustum.shouldUpdate && this.frustumCulled) { + this.domFrustum.computeProjectedToDocumentCoords(); + this.domFrustum.shouldUpdate = false; + } + super.onBeforeRenderPass(); + } + /** + * Only render the Mesh if it is in view frustum. + * Since render() is actually called before onRenderPass(), we are sure to have fresh frustum bounding rectangle values here. + * @param pass - current render pass + */ + onRenderPass(pass) { + if (!this.material.ready) + return; + this._onRenderCallback && this._onRenderCallback(); + if (this.domFrustum && this.domFrustum.isIntersecting || !this.frustumCulled) { + this.material.render(pass); + this.geometry.render(pass); + } + } + }; +} + +export { ProjectedMeshBaseMixin }; +//# sourceMappingURL=ProjectedMeshBaseMixin.mjs.map diff --git a/dist/esm/core/meshes/mixins/ProjectedMeshBaseMixin.mjs.map b/dist/esm/core/meshes/mixins/ProjectedMeshBaseMixin.mjs.map new file mode 100644 index 000000000..64bfe285a --- /dev/null +++ b/dist/esm/core/meshes/mixins/ProjectedMeshBaseMixin.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"ProjectedMeshBaseMixin.mjs","sources":["../../../../../src/core/meshes/mixins/ProjectedMeshBaseMixin.ts"],"sourcesContent":["import { CameraRenderer, isCameraRenderer } from '../../renderers/utils'\nimport { DOMFrustum } from '../../DOM/DOMFrustum'\nimport { MeshBaseClass, MeshBaseMixin, MeshBaseOptions, MeshBaseParams, MixinConstructor } from './MeshBaseMixin'\nimport { GPUCurtains } from '../../../curtains/GPUCurtains'\nimport { DOMElementBoundingRect, RectCoords } from '../../DOM/DOMElement'\nimport { RenderMaterialParams, ShaderOptions } from '../../../types/Materials'\nimport { ProjectedObject3D } from '../../objects3D/ProjectedObject3D'\nimport { DOMObject3D } from '../../../curtains/objects3D/DOMObject3D'\nimport default_projected_vsWgsl from '../../shaders/chunks/default_projected_vs.wgsl'\nimport default_normal_fsWgsl from '../../shaders/chunks/default_normal_fs.wgsl'\n\n/**\n * Base parameters used to create a ProjectedMesh\n */\nexport interface ProjectedMeshBaseParams {\n /** Whether this ProjectedMesh should be frustum culled (not drawn when outside of {@link CameraRenderer#camera | camera} frustum) */\n frustumCulled?: boolean\n /** Margins (in pixels) to applied to the {@link ProjectedMeshBaseClass#domFrustum | DOM Frustum} to determine if this ProjectedMesh should be frustum culled or not */\n DOMFrustumMargins?: RectCoords\n}\n\n/** Parameters used to create a ProjectedMesh */\nexport interface ProjectedMeshParameters extends MeshBaseParams, ProjectedMeshBaseParams {}\n\nexport interface ProjectedRenderMaterialParams extends RenderMaterialParams, ProjectedMeshBaseParams {}\n\n/** @const - Default ProjectedMesh parameters to merge with user defined parameters */\nconst defaultProjectedMeshParams: ProjectedMeshBaseParams = {\n // frustum culling and visibility\n frustumCulled: true,\n DOMFrustumMargins: {\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n },\n}\n\n/** Base options used to create this ProjectedMesh */\nexport interface ProjectedMeshBaseOptions extends MeshBaseOptions, Partial {}\n\n/**\n * This class describes the properties and methods to set up a Projected Mesh (i.e. a basic {@link MeshBaseClass | Mesh} with {@link ProjectedObject3D} transformations matrices and a {@link core/camera/Camera.Camera | Camera} to use for projection), implemented in the {@link ProjectedMeshBaseMixin}:\n * - Handle the frustum culling (check if the {@link ProjectedObject3D} currently lies inside the {@link core/camera/Camera.Camera | Camera} frustum)\n * - Add callbacks for when the Mesh enters or leaves the {@link core/camera/Camera.Camera | Camera} frustum\n */\nexport declare class ProjectedMeshBaseClass extends MeshBaseClass {\n /** The {@link CameraRenderer} used */\n renderer: CameraRenderer\n /** The ProjectedMesh {@link DOMFrustum} class object */\n domFrustum: DOMFrustum\n /** Whether this ProjectedMesh should be frustum culled (not drawn when outside of {@link CameraRenderer#camera | camera} frustum) */\n frustumCulled: boolean\n /** Margins (in pixels) to applied to the {@link ProjectedMeshBaseClass#domFrustum | DOM Frustum} to determine if this ProjectedMesh should be frustum culled or not */\n DOMFrustumMargins: RectCoords\n\n // callbacks\n /** function assigned to the {@link onReEnterView} callback */\n _onReEnterViewCallback: () => void\n /** function assigned to the {@link onLeaveView} callback */\n _onLeaveViewCallback: () => void\n\n /**\n * {@link ProjectedMeshBaseClass} constructor\n * @param renderer - our {@link CameraRenderer} class object\n * @param element - a DOM HTML Element that can be bound to a Mesh\n * @param parameters - {@link ProjectedMeshParameters | Projected Mesh base parameters}\n */\n constructor(renderer: CameraRenderer, element: HTMLElement | null, parameters: ProjectedMeshParameters)\n\n /**\n * Set default shaders if one or both of them are missing\n */\n setShaders(): void\n\n /**\n * Override {@link MeshBaseClass} method to add the domFrustum\n */\n computeGeometry(): void\n\n /**\n * Set a Mesh matrices uniforms inputs then call {@link MeshBaseClass} super method\n * @param meshParameters - {@link RenderMaterialParams | RenderMaterial parameters}\n */\n setMaterial(meshParameters: ProjectedRenderMaterialParams): void\n\n /**\n * Resize our Mesh\n * @param boundingRect - the new bounding rectangle\n */\n resize(boundingRect: DOMElementBoundingRect | null): void\n\n /**\n * Apply scale and resize textures\n */\n applyScale(): void\n\n /**\n * Get our {@link DOMFrustum} projected bounding rectangle\n * @readonly\n */\n get projectedBoundingRect(): DOMElementBoundingRect\n\n /**\n * At least one of the matrix has been updated, update according uniforms and frustum\n */\n onAfterMatrixStackUpdate(): void\n\n /**\n * Assign a callback function to _onReEnterViewCallback\n * @param callback - callback to run when {@link ProjectedMeshBaseClass} is reentering the view frustum\n * @returns - our Mesh\n */\n onReEnterView: (callback: () => void) => ProjectedMeshBaseClass\n /**\n * Assign a callback function to _onLeaveViewCallback\n * @param callback - callback to run when {@link ProjectedMeshBaseClass} is leaving the view frustum\n * @returns - our Mesh\n */\n onLeaveView: (callback: () => void) => ProjectedMeshBaseClass\n\n /**\n * Called before rendering the Mesh to update matrices and {@link DOMFrustum}.\n * First, we update our matrices to have fresh results. It eventually calls onAfterMatrixStackUpdate() if at least one matrix has been updated.\n * Then we check if we need to update the {@link DOMFrustum} projected bounding rectangle.\n * Finally we call {@link MeshBaseClass#onBeforeRenderPass | Mesh base onBeforeRenderPass} super\n */\n onBeforeRenderPass(): void\n\n /**\n * Only render the Mesh if it is in view frustum.\n * Since render() is actually called before onRenderPass(), we are sure to have fresh frustum bounding rectangle values here.\n * @param pass - current render pass\n */\n onRenderPass(pass: GPURenderPassEncoder): void\n}\n\n/**\n * Used to add the properties and methods defined in {@link ProjectedMeshBaseClass} to the {@link MeshBaseClass} and mix it with a given Base of type {@link ProjectedObject3D} or {@link DOMObject3D}.\n * @exports\n * @param Base - the class to mix onto, should be of {@link ProjectedObject3D} or {@link DOMObject3D} type\n * @returns - the mixed classes, creating a Projected Mesh.\n */\nfunction ProjectedMeshBaseMixin>(\n Base: TBase\n): MixinConstructor & TBase {\n /**\n * ProjectedMeshBase defines our base properties and methods\n */\n return class ProjectedMeshBase extends MeshBaseMixin(Base) {\n /** The {@link CameraRenderer} used */\n renderer: CameraRenderer\n /** The ProjectedMesh {@link DOMFrustum} class object */\n domFrustum: DOMFrustum\n /** Whether this ProjectedMesh should be frustum culled (not drawn when outside of {@link CameraRenderer#camera | camera} frustum) */\n frustumCulled: boolean\n /** Margins (in pixels) to applied to the {@link ProjectedMeshBaseClass#domFrustum | DOM Frustum} to determine if this ProjectedMesh should be frustum culled or not */\n DOMFrustumMargins: RectCoords\n\n /** Options used to create this {@link ProjectedMeshBaseClass} */\n options: ProjectedMeshBaseOptions\n\n // callbacks / events\n /** function assigned to the {@link onReEnterView} callback */\n _onReEnterViewCallback: () => void = () => {\n /* allow empty callback */\n }\n /** function assigned to the {@link onLeaveView} callback */\n _onLeaveViewCallback: () => void = () => {\n /* allow empty callback */\n }\n\n /**\n * ProjectedMeshBase constructor\n *\n * @typedef MeshBaseArrayParams\n * @type {array}\n * @property {(CameraRenderer|GPUCurtains)} 0 - our renderer class object\n * @property {(string|HTMLElement|null)} 1 - the DOM HTML Element that can be bound to a Mesh\n * @property {ProjectedMeshParameters} 2 - Projected Mesh parameters\n *\n * @param {MeshBaseArrayParams} params - our MeshBaseMixin parameters\n */\n constructor(...params: any[]) {\n super(\n params[0] as CameraRenderer | GPUCurtains,\n params[1] as HTMLElement | string,\n { ...defaultProjectedMeshParams, ...params[2], ...{ useProjection: true } } as ProjectedMeshParameters\n )\n\n let renderer = params[0]\n\n // force this mesh to use projection!\n const parameters = {\n ...defaultProjectedMeshParams,\n ...params[2],\n ...{ useProjection: true },\n } as ProjectedMeshParameters\n\n this.type = 'MeshTransformed'\n\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as CameraRenderer)\n\n isCameraRenderer(renderer, parameters.label ? parameters.label + ' ' + this.type : this.type)\n\n this.renderer = renderer\n\n const { geometry, frustumCulled, DOMFrustumMargins } = parameters\n\n this.options = {\n ...(this.options ?? {}), // merge possible lower options?\n frustumCulled,\n DOMFrustumMargins,\n }\n\n this.setDOMFrustum()\n\n // explicitly needed for DOM Frustum\n this.geometry = geometry\n\n // tell the model and projection matrices to update right away\n this.shouldUpdateMatrixStack()\n }\n\n /* SHADERS */\n\n /**\n * Set default shaders if one or both of them are missing\n */\n setShaders() {\n let { shaders } = this.options\n\n if (!shaders) {\n shaders = {\n vertex: {\n code: default_projected_vsWgsl,\n entryPoint: 'main',\n },\n fragment: {\n code: default_normal_fsWgsl,\n entryPoint: 'main',\n },\n }\n } else {\n if (!shaders.vertex || !shaders.vertex.code) {\n shaders.vertex = {\n code: default_projected_vsWgsl,\n entryPoint: 'main',\n }\n }\n\n if (shaders.fragment === undefined || (shaders.fragment && !(shaders.fragment as ShaderOptions).code)) {\n shaders.fragment = {\n code: default_normal_fsWgsl,\n entryPoint: 'main',\n }\n }\n }\n }\n\n /* GEOMETRY */\n\n /**\n * Set the Mesh frustum culling\n */\n setDOMFrustum() {\n this.domFrustum = new DOMFrustum({\n boundingBox: this.geometry.boundingBox,\n modelViewProjectionMatrix: this.modelViewProjectionMatrix,\n containerBoundingRect: this.renderer.boundingRect,\n DOMFrustumMargins: this.options.DOMFrustumMargins,\n onReEnterView: () => {\n this._onReEnterViewCallback && this._onReEnterViewCallback()\n },\n onLeaveView: () => {\n this._onLeaveViewCallback && this._onLeaveViewCallback()\n },\n })\n\n this.DOMFrustumMargins = this.domFrustum.DOMFrustumMargins\n this.frustumCulled = this.options.frustumCulled\n this.domFrustum.shouldUpdate = this.frustumCulled\n }\n\n /* MATERIAL */\n\n /**\n * Set a Mesh matrices uniforms inputs then call {@link MeshBaseClass} super method\n * @param meshParameters - {@link ProjectedRenderMaterialParams | RenderMaterial parameters}\n */\n setMaterial(meshParameters: ProjectedRenderMaterialParams) {\n const { frustumCulled, DOMFrustumMargins, ...materialParameters } = meshParameters\n\n // add matrices uniforms\n const matricesUniforms = {\n label: 'Matrices',\n struct: {\n model: {\n name: 'model',\n type: 'mat4x4f',\n value: this.modelMatrix,\n },\n world: {\n name: 'world',\n type: 'mat4x4f',\n value: this.worldMatrix,\n },\n modelView: {\n // model view matrix (world matrix multiplied by camera view matrix)\n name: 'modelView',\n type: 'mat4x4f',\n value: this.modelViewMatrix,\n },\n modelViewProjection: {\n name: 'modelViewProjection',\n type: 'mat4x4f',\n value: this.modelViewProjectionMatrix,\n },\n },\n }\n\n if (!materialParameters.uniforms) materialParameters.uniforms = {}\n materialParameters.uniforms.matrices = matricesUniforms\n\n super.setMaterial(materialParameters)\n }\n\n /* SIZE & TRANSFORMS */\n\n /**\n * Resize our {@link ProjectedMeshBaseClass}\n * @param boundingRect - the new bounding rectangle\n */\n resize(boundingRect?: DOMElementBoundingRect | null) {\n if (this.domFrustum) this.domFrustum.setContainerBoundingRect(this.renderer.boundingRect)\n\n super.resize(boundingRect)\n }\n\n /**\n * Apply scale and resize textures\n */\n applyScale() {\n super.applyScale()\n\n // resize textures on scale change!\n this.textures.forEach((texture) => texture.resize())\n }\n\n /**\n * Get our {@link DOMFrustum} projected bounding rectangle\n * @readonly\n */\n get projectedBoundingRect(): DOMElementBoundingRect {\n return this.domFrustum?.projectedBoundingRect\n }\n\n /**\n * At least one of the matrix has been updated, update according uniforms and frustum\n */\n onAfterMatrixStackUpdate() {\n if (this.material) {\n this.material.shouldUpdateInputsBindings('matrices')\n }\n\n if (this.domFrustum) this.domFrustum.shouldUpdate = true\n }\n\n /* EVENTS */\n\n /**\n * Assign a callback function to _onReEnterViewCallback\n * @param callback - callback to run when {@link ProjectedMeshBaseClass} is reentering the view frustum\n * @returns - our Mesh\n */\n onReEnterView(callback: () => void): ProjectedMeshBaseClass {\n if (callback) {\n this._onReEnterViewCallback = callback\n }\n\n return this\n }\n\n /**\n * Assign a callback function to _onLeaveViewCallback\n * @param callback - callback to run when {@link ProjectedMeshBaseClass} is leaving the view frustum\n * @returns - our Mesh\n */\n onLeaveView(callback: () => void): ProjectedMeshBaseClass {\n if (callback) {\n this._onLeaveViewCallback = callback\n }\n\n return this\n }\n\n /* RENDER */\n\n /**\n * Called before rendering the Mesh to update matrices and {@link DOMFrustum}.\n * First, we update our matrices to have fresh results. It eventually calls onAfterMatrixStackUpdate() if at least one matrix has been updated.\n * Then we check if we need to update the {@link DOMFrustum} projected bounding rectangle.\n * Finally we call {@link MeshBaseClass#onBeforeRenderPass | Mesh base onBeforeRenderPass} super\n */\n onBeforeRenderPass() {\n this.updateMatrixStack()\n\n if (this.domFrustum && this.domFrustum.shouldUpdate && this.frustumCulled) {\n this.domFrustum.computeProjectedToDocumentCoords()\n this.domFrustum.shouldUpdate = false\n }\n\n super.onBeforeRenderPass()\n }\n\n /**\n * Only render the Mesh if it is in view frustum.\n * Since render() is actually called before onRenderPass(), we are sure to have fresh frustum bounding rectangle values here.\n * @param pass - current render pass\n */\n onRenderPass(pass: GPURenderPassEncoder) {\n if (!this.material.ready) return\n\n this._onRenderCallback && this._onRenderCallback()\n\n if ((this.domFrustum && this.domFrustum.isIntersecting) || !this.frustumCulled) {\n // render ou material\n this.material.render(pass)\n // then render our geometry\n this.geometry.render(pass)\n }\n }\n }\n}\n\nexport { ProjectedMeshBaseMixin }\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/objects3D/Object3D.mjs b/dist/esm/core/objects3D/Object3D.mjs new file mode 100644 index 000000000..629fd9eae --- /dev/null +++ b/dist/esm/core/objects3D/Object3D.mjs @@ -0,0 +1,267 @@ +import { Vec3 } from '../../math/Vec3.mjs'; +import { Quat } from '../../math/Quat.mjs'; +import { Mat4 } from '../../math/Mat4.mjs'; + +let objectIndex = 0; +class Object3D { + /** + * Object3D constructor + */ + constructor() { + this.parent = null; + this.children = []; + Object.defineProperty(this, "object3DIndex", { value: objectIndex++ }); + this.setMatrices(); + this.setTransforms(); + } + /* PARENT */ + /** + * Get the parent of this {@link Object3D} if any + */ + get parent() { + return this._parent; + } + /** + * Set the parent of this {@link Object3D} + * @param value - new parent to set, could be an {@link Object3D} or null + */ + set parent(value) { + if (this.parent) { + this.parent.children = this.parent.children.filter((child) => child.object3DIndex !== this.object3DIndex); + } + this._parent = value; + this._parent?.children.push(this); + } + /* TRANSFORMS */ + /** + * Set our transforms properties and {@link Vec3#onChange | vectors onChange} callbacks + */ + setTransforms() { + this.transforms = { + origin: { + model: new Vec3() + }, + quaternion: new Quat(), + rotation: new Vec3(), + position: { + world: new Vec3() + }, + scale: new Vec3(1) + }; + this.rotation.onChange(() => this.applyRotation()); + this.position.onChange(() => this.applyPosition()); + this.scale.onChange(() => this.applyScale()); + this.transformOrigin.onChange(() => this.applyTransformOrigin()); + } + /** + * Get our rotation {@link Vec3 | vector} + */ + get rotation() { + return this.transforms.rotation; + } + /** + * Set our rotation {@link Vec3 | vector} + * @param value - new rotation {@link Vec3 | vector} + */ + set rotation(value) { + this.transforms.rotation = value; + this.applyRotation(); + } + /** + * Get our {@link Quat | quaternion} + */ + get quaternion() { + return this.transforms.quaternion; + } + /** + * Set our {@link Quat | quaternion} + * @param value - new {@link Quat | quaternion} + */ + set quaternion(value) { + this.transforms.quaternion = value; + } + /** + * Get our position {@link Vec3 | vector} + */ + get position() { + return this.transforms.position.world; + } + /** + * Set our position {@link Vec3 | vector} + * @param value - new position {@link Vec3 | vector} + */ + set position(value) { + this.transforms.position.world = value; + } + /** + * Get our scale {@link Vec3 | vector} + */ + get scale() { + return this.transforms.scale; + } + /** + * Set our scale {@link Vec3 | vector} + * @param value - new scale {@link Vec3 | vector} + */ + set scale(value) { + this.transforms.scale = value; + this.applyScale(); + } + /** + * Get our transform origin {@link Vec3 | vector} + */ + get transformOrigin() { + return this.transforms.origin.model; + } + /** + * Set our transform origin {@link Vec3 | vector} + * @param value - new transform origin {@link Vec3 | vector} + */ + set transformOrigin(value) { + this.transforms.origin.model = value; + } + /** + * Apply our rotation and tell our {@link modelMatrix | model matrix} to update + */ + applyRotation() { + this.quaternion.setFromVec3(this.rotation); + this.shouldUpdateModelMatrix(); + } + /** + * Tell our {@link modelMatrix | model matrix} to update + */ + applyPosition() { + this.shouldUpdateModelMatrix(); + } + /** + * Tell our {@link modelMatrix | model matrix} to update + */ + applyScale() { + this.shouldUpdateModelMatrix(); + } + /** + * Tell our {@link modelMatrix | model matrix} to update + */ + applyTransformOrigin() { + this.shouldUpdateModelMatrix(); + } + /* MATRICES */ + /** + * Set our {@link modelMatrix | model matrix} and {@link worldMatrix | world matrix} + */ + setMatrices() { + this.matrices = { + model: { + matrix: new Mat4(), + shouldUpdate: false, + onUpdate: () => this.updateModelMatrix() + }, + world: { + matrix: new Mat4(), + shouldUpdate: false, + onUpdate: () => this.updateWorldMatrix() + } + }; + } + /** + * Get our {@link Mat4 | model matrix} + */ + get modelMatrix() { + return this.matrices.model.matrix; + } + /** + * Set our {@link Mat4 | model matrix} + * @param value - new {@link Mat4 | model matrix} + */ + set modelMatrix(value) { + this.matrices.model.matrix = value; + this.shouldUpdateModelMatrix(); + } + /** + * Set our {@link modelMatrix | model matrix} shouldUpdate flag to true (tell it to update) + */ + shouldUpdateModelMatrix() { + this.matrices.model.shouldUpdate = true; + this.shouldUpdateWorldMatrix(); + } + /** + * Get our {@link Mat4 | world matrix} + */ + get worldMatrix() { + return this.matrices.world.matrix; + } + /** + * Set our {@link Mat4 | world matrix} + * @param value - new {@link Mat4 | world matrix} + */ + set worldMatrix(value) { + this.matrices.world.matrix = value; + this.shouldUpdateWorldMatrix(); + } + /** + * Set our {@link worldMatrix | world matrix} shouldUpdate flag to true (tell it to update) + */ + shouldUpdateWorldMatrix() { + this.matrices.world.shouldUpdate = true; + } + /** + * Rotate this {@link Object3D} so it looks at the {@link Vec3 | target} + * @param target - {@link Vec3 | target} to look at + */ + lookAt(target = new Vec3()) { + const rotationMatrix = new Mat4().lookAt(target, this.position); + this.quaternion.setFromRotationMatrix(rotationMatrix); + this.shouldUpdateModelMatrix(); + } + /** + * Update our {@link modelMatrix | model matrix} + */ + updateModelMatrix() { + this.modelMatrix = this.modelMatrix.composeFromOrigin( + this.position, + this.quaternion, + this.scale, + this.transformOrigin + ); + this.shouldUpdateWorldMatrix(); + } + /** + * Update our {@link worldMatrix | model matrix} + */ + updateWorldMatrix() { + if (!this.parent) { + this.worldMatrix.copy(this.modelMatrix); + } else { + this.worldMatrix.multiplyMatrices(this.parent.worldMatrix, this.modelMatrix); + } + this.children.forEach((child) => { + child.shouldUpdateWorldMatrix(); + }); + } + /** + * Callback to run if at least one matrix of the stack has been updated + */ + onAfterMatrixStackUpdate() { + } + /** + * Check at each render whether we should update our matrices, and update them if needed + */ + updateMatrixStack() { + if (this.parent && this.parent.constructor.name === "Object3D") { + this.parent.updateMatrixStack(); + } + const matrixShouldUpdate = !!Object.keys(this.matrices).find((matrixName) => this.matrices[matrixName].shouldUpdate); + if (matrixShouldUpdate) { + for (const matrixName in this.matrices) { + if (this.matrices[matrixName].shouldUpdate) { + this.matrices[matrixName].onUpdate(); + this.matrices[matrixName].shouldUpdate = false; + } + } + this.onAfterMatrixStackUpdate(); + } + } +} + +export { Object3D }; +//# sourceMappingURL=Object3D.mjs.map diff --git a/dist/esm/core/objects3D/Object3D.mjs.map b/dist/esm/core/objects3D/Object3D.mjs.map new file mode 100644 index 000000000..15c3ca7a3 --- /dev/null +++ b/dist/esm/core/objects3D/Object3D.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"Object3D.mjs","sources":["../../../../src/core/objects3D/Object3D.ts"],"sourcesContent":["import { Vec3 } from '../../math/Vec3'\nimport { Quat } from '../../math/Quat'\nimport { Mat4 } from '../../math/Mat4'\n\nlet objectIndex = 0\n\n/** Defines all kind of possible {@link Object3D} matrix types */\nexport type Object3DMatricesType = 'model' | 'world'\n\n/**\n * Defines an {@link Object3D} matrix object\n */\nexport interface Object3DTransformMatrix {\n /** The {@link Mat4 | matrix} used */\n matrix: Mat4\n /** Whether we should update the {@link Mat4 | matrix} */\n shouldUpdate: boolean\n /** Function to update our {@link Mat4 | matrix} */\n onUpdate: () => void\n}\n\n/** Defines all possible {@link Object3DTransformMatrix | matrix object} used by our {@link Object3D} */\nexport type Object3DMatrices = Record\n\n/**\n * Defines all necessary {@link Vec3 | vectors}/{@link Quat | quaternions} to compute a 3D {@link Mat4 | model matrix}\n */\nexport interface Object3DTransforms {\n /** Transformation origin object */\n origin: {\n /** Transformation origin {@link Vec3 | vector} relative to the {@link Object3D} */\n model: Vec3\n }\n /** Model {@link Quat | quaternion} defining its rotation in 3D space */\n quaternion: Quat\n /** Model rotation {@link Vec3 | vector} used to compute its {@link Quat | quaternion} */\n rotation: Vec3\n /** Position object */\n position: {\n /** Position {@link Vec3 | vector} relative to the 3D world */\n world: Vec3\n }\n /** Model 3D scale {@link Vec3 | vector} */\n scale: Vec3\n}\n\n/**\n * Used to create an object with transformation properties such as position, scale, rotation and transform origin {@link Vec3 | vectors} and a {@link Quat | quaternion} in order to compute the {@link Object3D#modelMatrix | model matrix} and {@link Object3D#worldMatrix | world matrix}.\n *\n * If an {@link Object3D} does not have any {@link Object3D#parent | parent}, then its {@link Object3D#modelMatrix | model matrix} and {@link Object3D#worldMatrix | world matrix} are the same.\n *\n * The transformations {@link Vec3 | vectors} are reactive to changes, which mean that updating one of their components will automatically update the {@link Object3D#modelMatrix | model matrix} and {@link Object3D#worldMatrix | world matrix}.\n */\nexport class Object3D {\n /** {@link Object3DTransforms | Transformation object} of the {@link Object3D} */\n transforms: Object3DTransforms\n /** {@link Object3DMatrices | Matrices object} of the {@link Object3D} */\n matrices: Object3DMatrices\n\n /** Parent {@link Object3D} in the scene graph, used to compute the {@link worldMatrix | world matrix} */\n private _parent: null | Object3D\n /** Children {@link Object3D} in the scene graph, used to compute their own {@link worldMatrix | world matrix} */\n children: Object3D[]\n\n /** Index (order of creation) of this {@link Object3D}. Used in the {@link parent} / {@link children} relation. */\n object3DIndex: number\n\n /**\n * Object3D constructor\n */\n constructor() {\n this.parent = null\n this.children = []\n\n Object.defineProperty(this as Object3D, 'object3DIndex', { value: objectIndex++ })\n\n this.setMatrices()\n this.setTransforms()\n }\n\n /* PARENT */\n\n /**\n * Get the parent of this {@link Object3D} if any\n */\n get parent(): Object3D | null {\n return this._parent\n }\n\n /**\n * Set the parent of this {@link Object3D}\n * @param value - new parent to set, could be an {@link Object3D} or null\n */\n set parent(value: Object3D | null) {\n if (this.parent) {\n this.parent.children = this.parent.children.filter((child) => child.object3DIndex !== this.object3DIndex)\n }\n this._parent = value\n this._parent?.children.push(this)\n }\n\n /* TRANSFORMS */\n\n /**\n * Set our transforms properties and {@link Vec3#onChange | vectors onChange} callbacks\n */\n setTransforms() {\n this.transforms = {\n origin: {\n model: new Vec3(),\n },\n quaternion: new Quat(),\n rotation: new Vec3(),\n position: {\n world: new Vec3(),\n },\n scale: new Vec3(1),\n }\n\n this.rotation.onChange(() => this.applyRotation())\n this.position.onChange(() => this.applyPosition())\n this.scale.onChange(() => this.applyScale())\n this.transformOrigin.onChange(() => this.applyTransformOrigin())\n }\n\n /**\n * Get our rotation {@link Vec3 | vector}\n */\n get rotation(): Vec3 {\n return this.transforms.rotation\n }\n\n /**\n * Set our rotation {@link Vec3 | vector}\n * @param value - new rotation {@link Vec3 | vector}\n */\n set rotation(value: Vec3) {\n this.transforms.rotation = value\n this.applyRotation()\n }\n\n /**\n * Get our {@link Quat | quaternion}\n */\n get quaternion(): Quat {\n return this.transforms.quaternion\n }\n\n /**\n * Set our {@link Quat | quaternion}\n * @param value - new {@link Quat | quaternion}\n */\n set quaternion(value: Quat) {\n this.transforms.quaternion = value\n }\n\n /**\n * Get our position {@link Vec3 | vector}\n */\n get position(): Vec3 {\n return this.transforms.position.world\n }\n\n /**\n * Set our position {@link Vec3 | vector}\n * @param value - new position {@link Vec3 | vector}\n */\n set position(value: Vec3) {\n this.transforms.position.world = value\n }\n\n /**\n * Get our scale {@link Vec3 | vector}\n */\n get scale(): Vec3 {\n return this.transforms.scale\n }\n\n /**\n * Set our scale {@link Vec3 | vector}\n * @param value - new scale {@link Vec3 | vector}\n */\n set scale(value: Vec3) {\n // force scale to 1 on Z axis\n this.transforms.scale = value\n this.applyScale()\n }\n\n /**\n * Get our transform origin {@link Vec3 | vector}\n */\n get transformOrigin(): Vec3 {\n return this.transforms.origin.model\n }\n\n /**\n * Set our transform origin {@link Vec3 | vector}\n * @param value - new transform origin {@link Vec3 | vector}\n */\n set transformOrigin(value: Vec3) {\n this.transforms.origin.model = value\n }\n\n /**\n * Apply our rotation and tell our {@link modelMatrix | model matrix} to update\n */\n applyRotation() {\n this.quaternion.setFromVec3(this.rotation)\n\n this.shouldUpdateModelMatrix()\n }\n\n /**\n * Tell our {@link modelMatrix | model matrix} to update\n */\n applyPosition() {\n this.shouldUpdateModelMatrix()\n }\n\n /**\n * Tell our {@link modelMatrix | model matrix} to update\n */\n applyScale() {\n this.shouldUpdateModelMatrix()\n }\n\n /**\n * Tell our {@link modelMatrix | model matrix} to update\n */\n applyTransformOrigin() {\n this.shouldUpdateModelMatrix()\n }\n\n /* MATRICES */\n\n /**\n * Set our {@link modelMatrix | model matrix} and {@link worldMatrix | world matrix}\n */\n setMatrices() {\n this.matrices = {\n model: {\n matrix: new Mat4(),\n shouldUpdate: false,\n onUpdate: () => this.updateModelMatrix(),\n },\n world: {\n matrix: new Mat4(),\n shouldUpdate: false,\n onUpdate: () => this.updateWorldMatrix(),\n },\n }\n }\n\n /**\n * Get our {@link Mat4 | model matrix}\n */\n get modelMatrix(): Mat4 {\n return this.matrices.model.matrix\n }\n\n /**\n * Set our {@link Mat4 | model matrix}\n * @param value - new {@link Mat4 | model matrix}\n */\n set modelMatrix(value: Mat4) {\n this.matrices.model.matrix = value\n this.shouldUpdateModelMatrix()\n }\n\n /**\n * Set our {@link modelMatrix | model matrix} shouldUpdate flag to true (tell it to update)\n */\n shouldUpdateModelMatrix() {\n this.matrices.model.shouldUpdate = true\n this.shouldUpdateWorldMatrix()\n }\n\n /**\n * Get our {@link Mat4 | world matrix}\n */\n get worldMatrix(): Mat4 {\n return this.matrices.world.matrix\n }\n\n /**\n * Set our {@link Mat4 | world matrix}\n * @param value - new {@link Mat4 | world matrix}\n */\n set worldMatrix(value: Mat4) {\n this.matrices.world.matrix = value\n this.shouldUpdateWorldMatrix()\n }\n\n /**\n * Set our {@link worldMatrix | world matrix} shouldUpdate flag to true (tell it to update)\n */\n shouldUpdateWorldMatrix() {\n this.matrices.world.shouldUpdate = true\n }\n\n /**\n * Rotate this {@link Object3D} so it looks at the {@link Vec3 | target}\n * @param target - {@link Vec3 | target} to look at\n */\n lookAt(target: Vec3 = new Vec3()) {\n const rotationMatrix = new Mat4().lookAt(target, this.position)\n this.quaternion.setFromRotationMatrix(rotationMatrix)\n this.shouldUpdateModelMatrix()\n }\n\n /**\n * Update our {@link modelMatrix | model matrix}\n */\n updateModelMatrix() {\n // compose our model transformation matrix from custom origin\n this.modelMatrix = this.modelMatrix.composeFromOrigin(\n this.position,\n this.quaternion,\n this.scale,\n this.transformOrigin\n )\n\n // tell our world matrix to update\n this.shouldUpdateWorldMatrix()\n }\n\n /**\n * Update our {@link worldMatrix | model matrix}\n */\n updateWorldMatrix() {\n if (!this.parent) {\n this.worldMatrix.copy(this.modelMatrix)\n } else {\n this.worldMatrix.multiplyMatrices(this.parent.worldMatrix, this.modelMatrix)\n }\n\n // update the children world matrix as well\n this.children.forEach((child) => {\n child.shouldUpdateWorldMatrix()\n })\n }\n\n /**\n * Callback to run if at least one matrix of the stack has been updated\n */\n onAfterMatrixStackUpdate() {\n /* will be used by the classes extending Object3D */\n }\n\n /**\n * Check at each render whether we should update our matrices, and update them if needed\n */\n updateMatrixStack() {\n // if it has a parent and it is an Object3D\n // it means nothing updates it in the render loop, so do it from here\n if (this.parent && this.parent.constructor.name === 'Object3D') {\n this.parent.updateMatrixStack()\n }\n\n // check if at least one matrix should update\n const matrixShouldUpdate = !!Object.keys(this.matrices).find((matrixName) => this.matrices[matrixName].shouldUpdate)\n\n if (matrixShouldUpdate) {\n for (const matrixName in this.matrices) {\n if (this.matrices[matrixName].shouldUpdate) {\n this.matrices[matrixName].onUpdate()\n this.matrices[matrixName].shouldUpdate = false\n }\n }\n\n // callback to run if at least one matrix of the stack has been updated\n this.onAfterMatrixStackUpdate()\n }\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/objects3D/ProjectedObject3D.mjs b/dist/esm/core/objects3D/ProjectedObject3D.mjs new file mode 100644 index 000000000..5f9d8c078 --- /dev/null +++ b/dist/esm/core/objects3D/ProjectedObject3D.mjs @@ -0,0 +1,133 @@ +import { Object3D } from './Object3D.mjs'; +import { isCameraRenderer } from '../renderers/utils.mjs'; +import { Mat4 } from '../../math/Mat4.mjs'; + +class ProjectedObject3D extends Object3D { + /** + * ProjectedObject3D constructor + * @param renderer - {@link CameraRenderer} object or {@link GPUCurtains} class object used to create this {@link ProjectedObject3D} + */ + constructor(renderer) { + super(); + renderer = renderer && renderer.renderer || renderer; + isCameraRenderer(renderer, "ProjectedObject3D"); + this.camera = renderer.camera; + } + /** + * Tell our projection matrix stack to update + */ + applyPosition() { + super.applyPosition(); + this.shouldUpdateProjectionMatrixStack(); + } + /** + * Tell our projection matrix stack to update + */ + applyRotation() { + super.applyRotation(); + this.shouldUpdateProjectionMatrixStack(); + } + /** + * Tell our projection matrix stack to update + */ + applyScale() { + super.applyScale(); + this.shouldUpdateProjectionMatrixStack(); + } + /** + * Tell our projection matrix stack to update + */ + applyTransformOrigin() { + super.applyTransformOrigin(); + this.shouldUpdateProjectionMatrixStack(); + } + /** + * Set our transform and projection matrices + */ + setMatrices() { + super.setMatrices(); + this.matrices = { + ...this.matrices, + modelView: { + matrix: new Mat4(), + shouldUpdate: false, + onUpdate: () => { + this.modelViewMatrix.multiplyMatrices(this.viewMatrix, this.worldMatrix); + } + }, + modelViewProjection: { + matrix: new Mat4(), + shouldUpdate: false, + onUpdate: () => { + this.modelViewProjectionMatrix.multiplyMatrices(this.projectionMatrix, this.modelViewMatrix); + } + } + }; + } + /** + * Get our {@link modelViewMatrix | model view matrix} + */ + get modelViewMatrix() { + return this.matrices.modelView.matrix; + } + /** + * Set our {@link modelViewMatrix | model view matrix} + * @param value - new {@link modelViewMatrix | model view matrix} + */ + set modelViewMatrix(value) { + this.matrices.modelView.matrix = value; + this.matrices.modelView.shouldUpdate = true; + } + /** + * Get our {@link Camera#viewMatrix | camera view matrix} + * @readonly + */ + get viewMatrix() { + return this.camera.viewMatrix; + } + /** + * Get our {@link Camera#projectionMatrix | camera projection matrix} + * @readonly + */ + get projectionMatrix() { + return this.camera.projectionMatrix; + } + /** + * Get our {@link modelViewProjectionMatrix | model view projection matrix} + */ + get modelViewProjectionMatrix() { + return this.matrices.modelViewProjection.matrix; + } + /** + * Set our {@link modelViewProjectionMatrix | model view projection matrix} + * @param value - new {@link modelViewProjectionMatrix | model view projection matrix}s + */ + set modelViewProjectionMatrix(value) { + this.matrices.modelViewProjection.matrix = value; + this.matrices.modelViewProjection.shouldUpdate = true; + } + /** + * Set our projection matrices shouldUpdate flags to true (tell them to update) + */ + shouldUpdateProjectionMatrixStack() { + this.matrices.modelView.shouldUpdate = true; + this.matrices.modelViewProjection.shouldUpdate = true; + } + /** + * When the world matrix update, tell our projection matrix to update as well + */ + shouldUpdateWorldMatrix() { + super.shouldUpdateWorldMatrix(); + this.shouldUpdateProjectionMatrixStack(); + } + /** + * Tell all our matrices to update + */ + shouldUpdateMatrixStack() { + this.shouldUpdateModelMatrix(); + this.shouldUpdateProjectionMatrixStack(); + } +} + +export { ProjectedObject3D }; +//# sourceMappingURL=ProjectedObject3D.mjs.map diff --git a/dist/esm/core/objects3D/ProjectedObject3D.mjs.map b/dist/esm/core/objects3D/ProjectedObject3D.mjs.map new file mode 100644 index 000000000..45f350592 --- /dev/null +++ b/dist/esm/core/objects3D/ProjectedObject3D.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"ProjectedObject3D.mjs","sources":["../../../../src/core/objects3D/ProjectedObject3D.ts"],"sourcesContent":["import { Object3D, Object3DMatricesType, Object3DTransformMatrix } from './Object3D'\r\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\r\nimport { CameraRenderer, isCameraRenderer } from '../renderers/utils'\r\nimport { Mat4 } from '../../math/Mat4'\r\nimport { Camera } from '../camera/Camera'\r\n\r\n/** Defines all kind of possible {@link ProjectedObject3D} matrix types */\r\nexport type ProjectedObject3DMatricesType = Object3DMatricesType | 'modelView' | 'modelViewProjection'\r\n/** Defines all possible {@link Object3DTransformMatrix | matrix object} used by our {@link ProjectedObject3D} */\r\nexport type ProjectedObject3DMatrices = Record\r\n\r\n/**\r\n * Used to apply the {@link Camera#projectionMatrix | projection} and {@link Camera#viewMatrix | view} matrices of a {@link Camera} to an {@link Object3D}, in order to compute {@link ProjectedObject3D#modelViewMatrix | modelView} and {@link ProjectedObject3D#modelViewProjectionMatrix | modelViewProjection} matrices.\r\n */\r\nexport class ProjectedObject3D extends Object3D {\r\n /** {@link Camera | Camera} object used to compute {@link ProjectedObject3D#modelViewMatrix | model view} and {@link ProjectedObject3D#modelViewProjectionMatrix | model view projection} matrices */\r\n camera: Camera\r\n\r\n /** {@link ProjectedObject3DMatrices | Matrices object} of the {@link ProjectedObject3D} */\r\n matrices: ProjectedObject3DMatrices\r\n\r\n /**\r\n * ProjectedObject3D constructor\r\n * @param renderer - {@link CameraRenderer} object or {@link GPUCurtains} class object used to create this {@link ProjectedObject3D}\r\n */\r\n constructor(renderer: CameraRenderer | GPUCurtains) {\r\n super()\r\n // we could pass our curtains object OR our curtains renderer object\r\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as CameraRenderer)\r\n\r\n isCameraRenderer(renderer, 'ProjectedObject3D')\r\n\r\n this.camera = renderer.camera\r\n }\r\n\r\n /**\r\n * Tell our projection matrix stack to update\r\n */\r\n applyPosition() {\r\n super.applyPosition()\r\n this.shouldUpdateProjectionMatrixStack()\r\n }\r\n\r\n /**\r\n * Tell our projection matrix stack to update\r\n */\r\n applyRotation() {\r\n super.applyRotation()\r\n this.shouldUpdateProjectionMatrixStack()\r\n }\r\n\r\n /**\r\n * Tell our projection matrix stack to update\r\n */\r\n applyScale() {\r\n super.applyScale()\r\n this.shouldUpdateProjectionMatrixStack()\r\n }\r\n\r\n /**\r\n * Tell our projection matrix stack to update\r\n */\r\n applyTransformOrigin() {\r\n super.applyTransformOrigin()\r\n this.shouldUpdateProjectionMatrixStack()\r\n }\r\n\r\n /**\r\n * Set our transform and projection matrices\r\n */\r\n setMatrices() {\r\n super.setMatrices()\r\n\r\n this.matrices = {\r\n ...this.matrices,\r\n modelView: {\r\n matrix: new Mat4(),\r\n shouldUpdate: false,\r\n onUpdate: () => {\r\n // our model view matrix is our model matrix multiplied with our camera view matrix\r\n this.modelViewMatrix.multiplyMatrices(this.viewMatrix, this.worldMatrix)\r\n },\r\n },\r\n modelViewProjection: {\r\n matrix: new Mat4(),\r\n shouldUpdate: false,\r\n onUpdate: () => {\r\n // our modelViewProjection matrix, useful for bounding box calculations and frustum culling\r\n // this is the result of our projection matrix multiplied by our modelView matrix\r\n this.modelViewProjectionMatrix.multiplyMatrices(this.projectionMatrix, this.modelViewMatrix)\r\n },\r\n },\r\n }\r\n }\r\n\r\n /**\r\n * Get our {@link modelViewMatrix | model view matrix}\r\n */\r\n get modelViewMatrix(): Mat4 {\r\n return this.matrices.modelView.matrix\r\n }\r\n\r\n /**\r\n * Set our {@link modelViewMatrix | model view matrix}\r\n * @param value - new {@link modelViewMatrix | model view matrix}\r\n */\r\n set modelViewMatrix(value: Mat4) {\r\n this.matrices.modelView.matrix = value\r\n this.matrices.modelView.shouldUpdate = true\r\n }\r\n\r\n /**\r\n * Get our {@link Camera#viewMatrix | camera view matrix}\r\n * @readonly\r\n */\r\n get viewMatrix(): Mat4 {\r\n return this.camera.viewMatrix\r\n }\r\n\r\n /**\r\n * Get our {@link Camera#projectionMatrix | camera projection matrix}\r\n * @readonly\r\n */\r\n get projectionMatrix(): Mat4 {\r\n return this.camera.projectionMatrix\r\n }\r\n\r\n /**\r\n * Get our {@link modelViewProjectionMatrix | model view projection matrix}\r\n */\r\n get modelViewProjectionMatrix(): Mat4 {\r\n return this.matrices.modelViewProjection.matrix\r\n }\r\n\r\n /**\r\n * Set our {@link modelViewProjectionMatrix | model view projection matrix}\r\n * @param value - new {@link modelViewProjectionMatrix | model view projection matrix}s\r\n */\r\n set modelViewProjectionMatrix(value: Mat4) {\r\n this.matrices.modelViewProjection.matrix = value\r\n this.matrices.modelViewProjection.shouldUpdate = true\r\n }\r\n\r\n /**\r\n * Set our projection matrices shouldUpdate flags to true (tell them to update)\r\n */\r\n shouldUpdateProjectionMatrixStack() {\r\n this.matrices.modelView.shouldUpdate = true\r\n this.matrices.modelViewProjection.shouldUpdate = true\r\n }\r\n\r\n /**\r\n * When the world matrix update, tell our projection matrix to update as well\r\n */\r\n shouldUpdateWorldMatrix() {\r\n super.shouldUpdateWorldMatrix()\r\n this.shouldUpdateProjectionMatrixStack()\r\n }\r\n\r\n /**\r\n * Tell all our matrices to update\r\n */\r\n shouldUpdateMatrixStack() {\r\n this.shouldUpdateModelMatrix()\r\n this.shouldUpdateProjectionMatrixStack()\r\n }\r\n}\r\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/pipelines/ComputePipelineEntry.mjs b/dist/esm/core/pipelines/ComputePipelineEntry.mjs new file mode 100644 index 000000000..926f09b42 --- /dev/null +++ b/dist/esm/core/pipelines/ComputePipelineEntry.mjs @@ -0,0 +1,150 @@ +import { PipelineEntry } from './PipelineEntry.mjs'; +import { isRenderer } from '../renderers/utils.mjs'; +import { throwError } from '../../utils/utils.mjs'; + +class ComputePipelineEntry extends PipelineEntry { + /** + * ComputePipelineEntry constructor + * @param parameters - {@link PipelineEntryParams | parameters} used to create this {@link ComputePipelineEntry} + */ + constructor(parameters) { + let { renderer } = parameters; + const { label } = parameters; + renderer = renderer && renderer.renderer || renderer; + const type = "ComputePipelineEntry"; + isRenderer(renderer, label ? label + " " + type : type); + super(parameters); + this.type = type; + this.shaders = { + compute: { + head: "", + code: "", + module: null + } + }; + this.descriptor = null; + } + /** + * Set {@link ComputePipelineEntry} properties (in this case the {@link bindGroups | bind groups}) + * @param parameters - the {@link core/materials/ComputeMaterial.ComputeMaterial#bindGroups | bind groups} to use + */ + setPipelineEntryProperties(parameters) { + const { bindGroups } = parameters; + this.setPipelineEntryBindGroups(bindGroups); + } + /* SHADERS */ + /** + * Patch the shaders by appending all the {@link bindGroups | bind groups}) WGSL code fragments to the given {@link PipelineEntryParams#shaders | parameter shader code} + */ + patchShaders() { + this.shaders.compute.head = ""; + this.shaders.compute.code = ""; + const groupsBindings = []; + this.bindGroups.forEach((bindGroup) => { + let bindIndex = 0; + bindGroup.bindings.forEach((binding, bindingIndex) => { + binding.wgslGroupFragment.forEach((groupFragment, groupFragmentIndex) => { + groupsBindings.push({ + groupIndex: bindGroup.index, + visibility: binding.visibility, + bindIndex, + wgslStructFragment: binding.wgslStructFragment, + wgslGroupFragment: groupFragment, + newLine: bindingIndex === bindGroup.bindings.length - 1 && groupFragmentIndex === binding.wgslGroupFragment.length - 1 + }); + bindIndex++; + }); + }); + }); + groupsBindings.forEach((groupBinding) => { + if (groupBinding.wgslStructFragment && this.shaders.compute.head.indexOf(groupBinding.wgslStructFragment) === -1) { + this.shaders.compute.head = ` +${groupBinding.wgslStructFragment} +${this.shaders.compute.head}`; + } + if (this.shaders.compute.head.indexOf(groupBinding.wgslGroupFragment) === -1) { + this.shaders.compute.head = `${this.shaders.compute.head} +@group(${groupBinding.groupIndex}) @binding(${groupBinding.bindIndex}) ${groupBinding.wgslGroupFragment}`; + } + if (groupBinding.newLine) + this.shaders.compute.head += ` +`; + }); + this.shaders.compute.code = this.shaders.compute.head + this.options.shaders.compute.code; + } + /* SETUP */ + /** + * Create the {@link shaders}: patch them and create the {@link GPUShaderModule} + */ + createShaders() { + this.patchShaders(); + this.shaders.compute.module = this.createShaderModule({ + code: this.shaders.compute.code, + type: "compute" + }); + } + /** + * Create the compute pipeline {@link descriptor} + */ + createPipelineDescriptor() { + if (!this.shaders.compute.module) + return; + this.descriptor = { + label: this.options.label, + layout: this.layout, + compute: { + module: this.shaders.compute.module, + entryPoint: this.options.shaders.compute.entryPoint + } + }; + } + /** + * Create the compute {@link pipeline} + */ + createComputePipeline() { + if (!this.shaders.compute.module) + return; + try { + this.pipeline = this.renderer.createComputePipeline(this.descriptor); + } catch (error) { + this.status.error = error; + throwError(error); + } + } + /** + * Asynchronously create the compute {@link pipeline} + * @async + * @returns - void promise result + */ + async createComputePipelineAsync() { + if (!this.shaders.compute.module) + return; + try { + this.pipeline = await this.renderer.createComputePipelineAsync(this.descriptor); + this.status.compiled = true; + this.status.compiling = false; + this.status.error = null; + } catch (error) { + this.status.error = error; + throwError(error); + } + } + /** + * Call {@link PipelineEntry#compilePipelineEntry | PipelineEntry compilePipelineEntry} method, then create our compute {@link pipeline} + * @async + */ + async compilePipelineEntry() { + super.compilePipelineEntry(); + if (this.options.useAsync) { + await this.createComputePipelineAsync(); + } else { + this.createComputePipeline(); + this.status.compiled = true; + this.status.compiling = false; + this.status.error = null; + } + } +} + +export { ComputePipelineEntry }; +//# sourceMappingURL=ComputePipelineEntry.mjs.map diff --git a/dist/esm/core/pipelines/ComputePipelineEntry.mjs.map b/dist/esm/core/pipelines/ComputePipelineEntry.mjs.map new file mode 100644 index 000000000..3147a6110 --- /dev/null +++ b/dist/esm/core/pipelines/ComputePipelineEntry.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"ComputePipelineEntry.mjs","sources":["../../../../src/core/pipelines/ComputePipelineEntry.ts"],"sourcesContent":["import { PipelineEntry } from './PipelineEntry'\nimport { isRenderer, Renderer } from '../renderers/utils'\nimport { throwError } from '../../utils/utils'\nimport { PipelineEntryParams, PipelineEntryPropertiesParams, PipelineEntryShaders } from '../../types/PipelineEntries'\nimport { BindGroupBufferBindingElement } from '../../types/BindGroups'\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\n\n/**\n * Used to create a {@link PipelineEntry} specifically designed to handle {@link core/materials/ComputeMaterial.ComputeMaterial | ComputeMaterial}.\n *\n * ## Shaders patching\n *\n * The {@link ComputePipelineEntry} uses each of its {@link ComputePipelineEntry#bindGroups | bind groups} {@link core/bindings/Binding.Binding | Binding} to patch the given compute shader before creating the {@link GPUShaderModule}.
\n * It will prepend every {@link core/bindings/Binding.Binding | Binding} WGSL code snippets (or fragments) with the correct bind group and bindings indices.\n *\n * ## Pipeline compilation\n *\n * The {@link ComputePipelineEntry} will then create a {@link GPUComputePipeline} (asynchronously by default).\n */\nexport class ComputePipelineEntry extends PipelineEntry {\n /** Shaders to use with this {@link ComputePipelineEntry} */\n shaders: PipelineEntryShaders\n /** {@link GPUComputePipelineDescriptor | Compute pipeline descriptor} based on {@link layout} and {@link shaders} */\n descriptor: GPUComputePipelineDescriptor | null\n\n /**\n * ComputePipelineEntry constructor\n * @param parameters - {@link PipelineEntryParams | parameters} used to create this {@link ComputePipelineEntry}\n */\n constructor(parameters: PipelineEntryParams) {\n let { renderer } = parameters\n const { label } = parameters\n\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\n\n const type = 'ComputePipelineEntry'\n\n isRenderer(renderer, label ? label + ' ' + type : type)\n\n super(parameters)\n\n this.type = type\n\n this.shaders = {\n compute: {\n head: '',\n code: '',\n module: null,\n },\n }\n\n this.descriptor = null\n }\n\n /**\n * Set {@link ComputePipelineEntry} properties (in this case the {@link bindGroups | bind groups})\n * @param parameters - the {@link core/materials/ComputeMaterial.ComputeMaterial#bindGroups | bind groups} to use\n */\n setPipelineEntryProperties(parameters: PipelineEntryPropertiesParams) {\n const { bindGroups } = parameters\n\n this.setPipelineEntryBindGroups(bindGroups)\n }\n\n /* SHADERS */\n\n /**\n * Patch the shaders by appending all the {@link bindGroups | bind groups}) WGSL code fragments to the given {@link PipelineEntryParams#shaders | parameter shader code}\n */\n patchShaders() {\n this.shaders.compute.head = ''\n this.shaders.compute.code = ''\n\n const groupsBindings = []\n this.bindGroups.forEach((bindGroup) => {\n let bindIndex = 0\n bindGroup.bindings.forEach((binding, bindingIndex) => {\n binding.wgslGroupFragment.forEach((groupFragment, groupFragmentIndex) => {\n groupsBindings.push({\n groupIndex: bindGroup.index,\n visibility: binding.visibility,\n bindIndex,\n wgslStructFragment: (binding as BindGroupBufferBindingElement).wgslStructFragment,\n wgslGroupFragment: groupFragment,\n newLine:\n bindingIndex === bindGroup.bindings.length - 1 &&\n groupFragmentIndex === binding.wgslGroupFragment.length - 1,\n })\n\n bindIndex++\n })\n })\n })\n\n groupsBindings.forEach((groupBinding) => {\n // do not duplicate structs\n if (\n groupBinding.wgslStructFragment &&\n this.shaders.compute.head.indexOf(groupBinding.wgslStructFragment) === -1\n ) {\n this.shaders.compute.head = `\\n${groupBinding.wgslStructFragment}\\n${this.shaders.compute.head}`\n }\n\n // do not duplicate struct var as well\n if (this.shaders.compute.head.indexOf(groupBinding.wgslGroupFragment) === -1) {\n this.shaders.compute.head = `${this.shaders.compute.head}\\n@group(${groupBinding.groupIndex}) @binding(${groupBinding.bindIndex}) ${groupBinding.wgslGroupFragment}`\n }\n\n if (groupBinding.newLine) this.shaders.compute.head += `\\n`\n })\n\n this.shaders.compute.code = this.shaders.compute.head + this.options.shaders.compute.code\n }\n\n /* SETUP */\n\n /**\n * Create the {@link shaders}: patch them and create the {@link GPUShaderModule}\n */\n createShaders() {\n this.patchShaders()\n\n this.shaders.compute.module = this.createShaderModule({\n code: this.shaders.compute.code,\n type: 'compute',\n })\n }\n\n /**\n * Create the compute pipeline {@link descriptor}\n */\n createPipelineDescriptor() {\n if (!this.shaders.compute.module) return\n\n this.descriptor = {\n label: this.options.label,\n layout: this.layout,\n compute: {\n module: this.shaders.compute.module,\n entryPoint: this.options.shaders.compute.entryPoint,\n },\n }\n }\n\n /**\n * Create the compute {@link pipeline}\n */\n createComputePipeline() {\n if (!this.shaders.compute.module) return\n\n try {\n this.pipeline = this.renderer.createComputePipeline(this.descriptor)\n } catch (error) {\n this.status.error = error\n throwError(error)\n }\n }\n\n /**\n * Asynchronously create the compute {@link pipeline}\n * @async\n * @returns - void promise result\n */\n async createComputePipelineAsync(): Promise {\n if (!this.shaders.compute.module) return\n\n try {\n this.pipeline = await this.renderer.createComputePipelineAsync(this.descriptor)\n this.status.compiled = true\n this.status.compiling = false\n this.status.error = null\n } catch (error) {\n this.status.error = error\n throwError(error)\n }\n }\n\n /**\n * Call {@link PipelineEntry#compilePipelineEntry | PipelineEntry compilePipelineEntry} method, then create our compute {@link pipeline}\n * @async\n */\n async compilePipelineEntry(): Promise {\n super.compilePipelineEntry()\n\n if (this.options.useAsync) {\n await this.createComputePipelineAsync()\n } else {\n this.createComputePipeline()\n this.status.compiled = true\n this.status.compiling = false\n this.status.error = null\n }\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/pipelines/PipelineEntry.mjs b/dist/esm/core/pipelines/PipelineEntry.mjs new file mode 100644 index 000000000..fb2d0efad --- /dev/null +++ b/dist/esm/core/pipelines/PipelineEntry.mjs @@ -0,0 +1,139 @@ +import { isRenderer } from '../renderers/utils.mjs'; + +let pipelineId = 0; +class PipelineEntry { + /** + * PipelineEntry constructor + * @param parameters - {@link PipelineEntryParams | parameters} used to create this {@link PipelineEntry} + */ + constructor(parameters) { + this.type = "PipelineEntry"; + let { renderer } = parameters; + const { label, shaders, useAsync } = parameters; + renderer = renderer && renderer.renderer || renderer; + isRenderer(renderer, label ? label + " " + this.type : this.type); + this.renderer = renderer; + Object.defineProperty(this, "index", { value: pipelineId++ }); + this.layout = null; + this.pipeline = null; + this.status = { + compiling: false, + compiled: false, + error: null + }; + this.options = { + label, + shaders, + useAsync: useAsync !== void 0 ? useAsync : true + }; + } + /** + * Get whether the {@link pipeline} is ready, i.e. successfully compiled + * @readonly + */ + get ready() { + return !this.status.compiling && this.status.compiled && !this.status.error; + } + /** + * Get whether the {@link pipeline} is ready to be compiled, i.e. we have not already tried to compile it, and it's not currently compiling neither + * @readonly + */ + get canCompile() { + return !this.status.compiling && !this.status.compiled && !this.status.error; + } + /** + * Set our {@link PipelineEntry#bindGroups | pipeline entry bind groups} + * @param bindGroups - {@link core/materials/Material.Material#bindGroups | bind groups} to use with this {@link PipelineEntry} + */ + setPipelineEntryBindGroups(bindGroups) { + this.bindGroups = bindGroups; + } + /* SHADERS */ + /** + * Create a {@link GPUShaderModule} + * @param parameters - Parameters used + * @param parameters.code - patched WGSL code string + * @param parameters.type - {@link MaterialShadersType | shader type} + * @returns - compiled {@link GPUShaderModule} if successful + */ + createShaderModule({ code = "", type = "vertex" }) { + const shaderModule = this.renderer.createShaderModule({ + label: this.options.label + ": " + type + "Shader module", + code + }); + if ("getCompilationInfo" in shaderModule && !this.renderer.production) { + shaderModule.getCompilationInfo().then((compilationInfo) => { + for (const message of compilationInfo.messages) { + let formattedMessage = ""; + if (message.lineNum) { + formattedMessage += `Line ${message.lineNum}:${message.linePos} - ${code.substring( + message.offset, + message.offset + message.length + )} +`; + } + formattedMessage += message.message; + switch (message.type) { + case "error": + console.error(`${this.options.label} compilation error: +${formattedMessage}`); + break; + case "warning": + console.warn(`${this.options.label} compilation warning: +${formattedMessage}`); + break; + case "info": + console.log(`${this.options.label} compilation information: +${formattedMessage}`); + break; + } + } + }); + } + return shaderModule; + } + /* SETUP */ + /** + * Create the {@link PipelineEntry} shaders + */ + createShaders() { + } + /** + * Create the pipeline entry {@link layout} + */ + createPipelineLayout() { + this.layout = this.renderer.createPipelineLayout({ + label: this.options.label + " layout", + bindGroupLayouts: this.bindGroups.map((bindGroup) => bindGroup.bindGroupLayout) + }); + } + /** + * Create the {@link PipelineEntry} descriptor + */ + createPipelineDescriptor() { + } + /** + * Flush a {@link PipelineEntry}, i.e. reset its {@link bindGroups | bind groups}, {@link layout} and descriptor and recompile the {@link pipeline} + * Used when one of the bind group or rendering property has changed + * @param newBindGroups - new {@link bindGroups | bind groups} in case they have changed + */ + flushPipelineEntry(newBindGroups = []) { + this.status.compiling = false; + this.status.compiled = false; + this.status.error = null; + this.setPipelineEntryBindGroups(newBindGroups); + this.compilePipelineEntry(); + } + /** + * Set up a {@link pipeline} by creating the shaders, the {@link layout} and the descriptor + */ + compilePipelineEntry() { + this.status.compiling = true; + this.createShaders(); + this.createPipelineLayout(); + this.createPipelineDescriptor(); + } +} + +export { PipelineEntry }; +//# sourceMappingURL=PipelineEntry.mjs.map diff --git a/dist/esm/core/pipelines/PipelineEntry.mjs.map b/dist/esm/core/pipelines/PipelineEntry.mjs.map new file mode 100644 index 000000000..d629ccd9a --- /dev/null +++ b/dist/esm/core/pipelines/PipelineEntry.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"PipelineEntry.mjs","sources":["../../../../src/core/pipelines/PipelineEntry.ts"],"sourcesContent":["import { isRenderer, Renderer } from '../renderers/utils'\nimport { PipelineEntryOptions, PipelineEntryParams, PipelineEntryStatus } from '../../types/PipelineEntries'\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\nimport { AllowedBindGroups } from '../../types/BindGroups'\nimport { MaterialShadersType } from '../../types/Materials'\n\nlet pipelineId = 0\n\n/**\n * Used as a base class to create a pipeline entry.
\n * {@link PipelineEntry} roles are:\n * - Patch the given {@link core/materials/Material.Material | Material} shaders code and create the corresponding {@link GPUShaderModule}.\n * - Create a {@link GPUPipelineLayout | pipeline layout} with the given {@link core/materials/Material.Material#bindGroups | bind groups}\n * - Create a GPU pipeline\n */\nexport class PipelineEntry {\n /** The type of the {@link PipelineEntry} */\n type: string\n /** The {@link Renderer} used to create this {@link PipelineEntry} */\n renderer: Renderer\n /** Index of this {@link PipelineEntry}, i.e. creation order */\n readonly index: number\n /** {@link GPUPipelineLayout | Pipeline layout} created based on the given {@link bindGroups | bind groups} */\n layout: GPUPipelineLayout | null\n /** The GPU pipeline */\n pipeline: GPURenderPipeline | GPUComputePipeline | null\n /** The pipeline {@link PipelineEntryStatus | compilation status} */\n status: PipelineEntryStatus\n /** Options used to create this {@link PipelineEntry} */\n options: PipelineEntryOptions\n\n /** {@link core/materials/Material.Material#bindGroups | bind groups} used to patch the shaders and create the {@link PipelineEntry#layout | pipeline layout} */\n bindGroups: AllowedBindGroups[]\n\n /**\n * PipelineEntry constructor\n * @param parameters - {@link PipelineEntryParams | parameters} used to create this {@link PipelineEntry}\n */\n constructor(parameters: PipelineEntryParams) {\n this.type = 'PipelineEntry'\n\n let { renderer } = parameters\n const { label, shaders, useAsync } = parameters\n\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\n\n isRenderer(renderer, label ? label + ' ' + this.type : this.type)\n\n this.renderer = renderer\n\n Object.defineProperty(this as PipelineEntry, 'index', { value: pipelineId++ })\n\n this.layout = null\n this.pipeline = null\n\n this.status = {\n compiling: false,\n compiled: false,\n error: null,\n }\n\n this.options = {\n label,\n shaders,\n useAsync: useAsync !== undefined ? useAsync : true,\n }\n }\n\n /**\n * Get whether the {@link pipeline} is ready, i.e. successfully compiled\n * @readonly\n */\n get ready(): boolean {\n return !this.status.compiling && this.status.compiled && !this.status.error\n }\n\n /**\n * Get whether the {@link pipeline} is ready to be compiled, i.e. we have not already tried to compile it, and it's not currently compiling neither\n * @readonly\n */\n get canCompile(): boolean {\n return !this.status.compiling && !this.status.compiled && !this.status.error\n }\n\n /**\n * Set our {@link PipelineEntry#bindGroups | pipeline entry bind groups}\n * @param bindGroups - {@link core/materials/Material.Material#bindGroups | bind groups} to use with this {@link PipelineEntry}\n */\n setPipelineEntryBindGroups(bindGroups: AllowedBindGroups[]) {\n this.bindGroups = bindGroups\n }\n\n /* SHADERS */\n\n /**\n * Create a {@link GPUShaderModule}\n * @param parameters - Parameters used\n * @param parameters.code - patched WGSL code string\n * @param parameters.type - {@link MaterialShadersType | shader type}\n * @returns - compiled {@link GPUShaderModule} if successful\n */\n createShaderModule({ code = '', type = 'vertex' }: { code: string; type: MaterialShadersType }): GPUShaderModule {\n const shaderModule = this.renderer.createShaderModule({\n label: this.options.label + ': ' + type + 'Shader module',\n code,\n })\n\n if ('getCompilationInfo' in shaderModule && !this.renderer.production) {\n shaderModule.getCompilationInfo().then((compilationInfo) => {\n for (const message of compilationInfo.messages) {\n let formattedMessage = ''\n if (message.lineNum) {\n formattedMessage += `Line ${message.lineNum}:${message.linePos} - ${code.substring(\n message.offset,\n message.offset + message.length\n )}\\n`\n }\n formattedMessage += message.message\n\n switch (message.type) {\n case 'error':\n // TODO mesh onError?\n console.error(`${this.options.label} compilation error:\\n${formattedMessage}`)\n break\n case 'warning':\n console.warn(`${this.options.label} compilation warning:\\n${formattedMessage}`)\n break\n case 'info':\n console.log(`${this.options.label} compilation information:\\n${formattedMessage}`)\n break\n }\n }\n })\n }\n\n return shaderModule\n }\n\n /* SETUP */\n\n /**\n * Create the {@link PipelineEntry} shaders\n */\n createShaders() {\n /* will be overriden */\n }\n\n /**\n * Create the pipeline entry {@link layout}\n */\n createPipelineLayout() {\n this.layout = this.renderer.createPipelineLayout({\n label: this.options.label + ' layout',\n bindGroupLayouts: this.bindGroups.map((bindGroup) => bindGroup.bindGroupLayout),\n })\n }\n\n /**\n * Create the {@link PipelineEntry} descriptor\n */\n createPipelineDescriptor() {\n /* will be overriden */\n }\n\n /**\n * Flush a {@link PipelineEntry}, i.e. reset its {@link bindGroups | bind groups}, {@link layout} and descriptor and recompile the {@link pipeline}\n * Used when one of the bind group or rendering property has changed\n * @param newBindGroups - new {@link bindGroups | bind groups} in case they have changed\n */\n flushPipelineEntry(newBindGroups: AllowedBindGroups[] = []) {\n this.status.compiling = false\n this.status.compiled = false\n this.status.error = null\n\n this.setPipelineEntryBindGroups(newBindGroups)\n this.compilePipelineEntry()\n }\n\n /**\n * Set up a {@link pipeline} by creating the shaders, the {@link layout} and the descriptor\n */\n compilePipelineEntry() {\n this.status.compiling = true\n\n this.createShaders()\n this.createPipelineLayout()\n this.createPipelineDescriptor()\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/pipelines/PipelineManager.mjs b/dist/esm/core/pipelines/PipelineManager.mjs new file mode 100644 index 000000000..d806e412d --- /dev/null +++ b/dist/esm/core/pipelines/PipelineManager.mjs @@ -0,0 +1,97 @@ +import { RenderPipelineEntry } from './RenderPipelineEntry.mjs'; +import { ComputePipelineEntry } from './ComputePipelineEntry.mjs'; + +class PipelineManager { + constructor() { + this.type = "PipelineManager"; + this.currentPipelineIndex = null; + this.pipelineEntries = []; + } + /** + * Checks if the provided {@link RenderPipelineEntryBaseParams | RenderPipelineEntry parameters} belongs to an already created {@link RenderPipelineEntry}. + * @param parameters - {@link RenderPipelineEntryBaseParams | RenderPipelineEntry parameters} + * @returns - the found {@link RenderPipelineEntry}, or null if not found + */ + isSameRenderPipeline(parameters) { + const { + shaders, + cullMode, + depth, + depthWriteEnabled, + depthCompare, + transparent, + verticesOrder, + topology, + sampleCount + } = parameters; + return this.pipelineEntries.filter((pipelineEntry) => pipelineEntry instanceof RenderPipelineEntry).find((pipelineEntry) => { + const { options } = pipelineEntry; + const sameFragmentShader = !shaders.fragment && !options.shaders.fragment || shaders.fragment.code?.localeCompare(options.shaders.fragment.code) === 0 && shaders.fragment.entryPoint === options.shaders.fragment.entryPoint; + return shaders.vertex.code.localeCompare(options.shaders.vertex.code) === 0 && shaders.vertex.entryPoint === options.shaders.vertex.entryPoint && sameFragmentShader && cullMode === options.cullMode && depth === options.depth && depthWriteEnabled === options.depthWriteEnabled && depthCompare === options.depthCompare && transparent === options.transparent && sampleCount === options.sampleCount && verticesOrder === options.verticesOrder && topology === options.topology; + }); + } + /** + * Check if a {@link RenderPipelineEntry} has already been created with the given {@link RenderPipelineEntryParams | parameters}. + * Use it if found, else create a new one and add it to the {@link pipelineEntries} array. + * @param parameters - {@link RenderPipelineEntryParams | RenderPipelineEntry parameters} + * @returns - {@link RenderPipelineEntry}, either from cache or newly created + */ + createRenderPipeline(parameters) { + const existingPipelineEntry = this.isSameRenderPipeline(parameters); + if (existingPipelineEntry) { + return existingPipelineEntry; + } else { + const pipelineEntry = new RenderPipelineEntry(parameters); + this.pipelineEntries.push(pipelineEntry); + return pipelineEntry; + } + } + /** + * Checks if the provided {@link PipelineEntryParams | parameters} belongs to an already created {@link ComputePipelineEntry}. + * @param parameters - {@link PipelineEntryParams | PipelineEntry parameters} + * @returns - the found {@link ComputePipelineEntry}, or null if not found + */ + isSameComputePipeline(parameters) { + const { shaders } = parameters; + return this.pipelineEntries.filter((pipelineEntry) => pipelineEntry instanceof ComputePipelineEntry).find((pipelineEntry) => { + const { options } = pipelineEntry; + return shaders.compute.code.localeCompare(options.shaders.compute.code) === 0 && shaders.compute.entryPoint === options.shaders.compute.entryPoint; + }); + } + /** + * Check if a {@link ComputePipelineEntry} has already been created with the given {@link PipelineEntryParams | parameters}. + * Use it if found, else create a new one and add it to the {@link pipelineEntries} array. + * @param parameters - {@link PipelineEntryParams | PipelineEntry parameters} + * @returns - newly created {@link ComputePipelineEntry} + */ + createComputePipeline(parameters) { + const existingPipelineEntry = this.isSameComputePipeline(parameters); + if (existingPipelineEntry) { + return existingPipelineEntry; + } else { + const pipelineEntry = new ComputePipelineEntry(parameters); + this.pipelineEntries.push(pipelineEntry); + return pipelineEntry; + } + } + /** + * Check if the given {@link AllowedPipelineEntries | PipelineEntry} is already set, if not set it + * @param pass - current pass encoder + * @param pipelineEntry - the {@link AllowedPipelineEntries | PipelineEntry} to set + */ + setCurrentPipeline(pass, pipelineEntry) { + if (pipelineEntry.index !== this.currentPipelineIndex) { + pass.setPipeline(pipelineEntry.pipeline); + this.currentPipelineIndex = pipelineEntry.index; + } + } + /** + * Reset the {@link PipelineManager#currentPipelineIndex | current pipeline index} so the next {@link AllowedPipelineEntries | PipelineEntry} will be set for sure + */ + resetCurrentPipeline() { + this.currentPipelineIndex = null; + } +} + +export { PipelineManager }; +//# sourceMappingURL=PipelineManager.mjs.map diff --git a/dist/esm/core/pipelines/PipelineManager.mjs.map b/dist/esm/core/pipelines/PipelineManager.mjs.map new file mode 100644 index 000000000..7aefd1ed1 --- /dev/null +++ b/dist/esm/core/pipelines/PipelineManager.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"PipelineManager.mjs","sources":["../../../../src/core/pipelines/PipelineManager.ts"],"sourcesContent":["import { RenderPipelineEntry } from './RenderPipelineEntry'\nimport { ComputePipelineEntry } from './ComputePipelineEntry'\nimport {\n PipelineEntryParams,\n RenderPipelineEntryBaseParams,\n RenderPipelineEntryParams,\n} from '../../types/PipelineEntries'\nimport { ShaderOptions } from '../../types/Materials'\n\n/** Defines all types of allowed {@link core/pipelines/PipelineEntry.PipelineEntry | PipelineEntry} class objects */\nexport type AllowedPipelineEntries = RenderPipelineEntry | ComputePipelineEntry\n\n/**\n * Used to create and keep track of both {@link ComputePipelineEntry} and {@link RenderPipelineEntry}.
\n * Perform checks to eventually use a cached pipeline entry instead of creating a new one.
\n * The end goal is to cache pipelines and reuse them (as well as bind groups).
\n * Also responsible for setting the current pass encoder pipeline in order to avoid redundant setPipeline calls.
\n * Created internally by the {@link core/renderers/GPUDeviceManager.GPUDeviceManager | GPUDeviceManager}.
\n * @see {@link https://toji.dev/webgpu-best-practices/bind-groups#grouping-resources-based-on-frequency-of-change | WebGPU Bind Group best practices}\n */\nexport class PipelineManager {\n /** The type of the {@link PipelineManager} */\n type: string\n /** Keep track of the current bound pipeline in order to avoid redundant setPipeline calls */\n currentPipelineIndex: number | null\n /** Array of already created {@link ComputePipelineEntry} and {@link RenderPipelineEntry} */\n pipelineEntries: AllowedPipelineEntries[]\n\n constructor() {\n this.type = 'PipelineManager'\n\n this.currentPipelineIndex = null\n this.pipelineEntries = []\n }\n\n /**\n * Checks if the provided {@link RenderPipelineEntryBaseParams | RenderPipelineEntry parameters} belongs to an already created {@link RenderPipelineEntry}.\n * @param parameters - {@link RenderPipelineEntryBaseParams | RenderPipelineEntry parameters}\n * @returns - the found {@link RenderPipelineEntry}, or null if not found\n */\n isSameRenderPipeline(parameters: RenderPipelineEntryBaseParams): RenderPipelineEntry | null {\n const {\n shaders,\n cullMode,\n depth,\n depthWriteEnabled,\n depthCompare,\n transparent,\n verticesOrder,\n topology,\n sampleCount,\n } = parameters\n\n return this.pipelineEntries\n .filter((pipelineEntry) => pipelineEntry instanceof RenderPipelineEntry)\n .find((pipelineEntry: RenderPipelineEntry) => {\n const { options } = pipelineEntry\n\n // TODO ugly :(\n\n const sameFragmentShader =\n (!shaders.fragment && !options.shaders.fragment) ||\n ((shaders.fragment as ShaderOptions).code?.localeCompare((options.shaders.fragment as ShaderOptions).code) ===\n 0 &&\n (shaders.fragment as ShaderOptions).entryPoint === (options.shaders.fragment as ShaderOptions).entryPoint)\n\n return (\n shaders.vertex.code.localeCompare(options.shaders.vertex.code) === 0 &&\n shaders.vertex.entryPoint === options.shaders.vertex.entryPoint &&\n sameFragmentShader &&\n cullMode === options.cullMode &&\n depth === options.depth &&\n depthWriteEnabled === options.depthWriteEnabled &&\n depthCompare === options.depthCompare &&\n transparent === options.transparent &&\n sampleCount === options.sampleCount &&\n verticesOrder === options.verticesOrder &&\n topology === options.topology\n )\n }) as RenderPipelineEntry | null\n }\n\n /**\n * Check if a {@link RenderPipelineEntry} has already been created with the given {@link RenderPipelineEntryParams | parameters}.\n * Use it if found, else create a new one and add it to the {@link pipelineEntries} array.\n * @param parameters - {@link RenderPipelineEntryParams | RenderPipelineEntry parameters}\n * @returns - {@link RenderPipelineEntry}, either from cache or newly created\n */\n createRenderPipeline(parameters: RenderPipelineEntryParams): RenderPipelineEntry {\n const existingPipelineEntry = this.isSameRenderPipeline(parameters)\n\n if (existingPipelineEntry) {\n return existingPipelineEntry\n } else {\n const pipelineEntry = new RenderPipelineEntry(parameters)\n\n this.pipelineEntries.push(pipelineEntry)\n\n return pipelineEntry\n }\n }\n\n /**\n * Checks if the provided {@link PipelineEntryParams | parameters} belongs to an already created {@link ComputePipelineEntry}.\n * @param parameters - {@link PipelineEntryParams | PipelineEntry parameters}\n * @returns - the found {@link ComputePipelineEntry}, or null if not found\n */\n isSameComputePipeline(parameters: PipelineEntryParams) {\n const { shaders } = parameters\n\n return this.pipelineEntries\n .filter((pipelineEntry) => pipelineEntry instanceof ComputePipelineEntry)\n .find((pipelineEntry: ComputePipelineEntry) => {\n const { options } = pipelineEntry\n\n return (\n shaders.compute.code.localeCompare(options.shaders.compute.code) === 0 &&\n shaders.compute.entryPoint === options.shaders.compute.entryPoint\n )\n }) as ComputePipelineEntry | null\n }\n\n /**\n * Check if a {@link ComputePipelineEntry} has already been created with the given {@link PipelineEntryParams | parameters}.\n * Use it if found, else create a new one and add it to the {@link pipelineEntries} array.\n * @param parameters - {@link PipelineEntryParams | PipelineEntry parameters}\n * @returns - newly created {@link ComputePipelineEntry}\n */\n createComputePipeline(parameters: PipelineEntryParams): ComputePipelineEntry {\n const existingPipelineEntry = this.isSameComputePipeline(parameters)\n\n if (existingPipelineEntry) {\n return existingPipelineEntry\n } else {\n const pipelineEntry = new ComputePipelineEntry(parameters)\n\n this.pipelineEntries.push(pipelineEntry)\n\n return pipelineEntry\n }\n }\n\n /**\n * Check if the given {@link AllowedPipelineEntries | PipelineEntry} is already set, if not set it\n * @param pass - current pass encoder\n * @param pipelineEntry - the {@link AllowedPipelineEntries | PipelineEntry} to set\n */\n setCurrentPipeline(pass: GPURenderPassEncoder | GPUComputePassEncoder, pipelineEntry: AllowedPipelineEntries) {\n if (pipelineEntry.index !== this.currentPipelineIndex) {\n pass.setPipeline(pipelineEntry.pipeline as GPURenderPipeline & GPUComputePipeline)\n this.currentPipelineIndex = pipelineEntry.index\n }\n }\n\n /**\n * Reset the {@link PipelineManager#currentPipelineIndex | current pipeline index} so the next {@link AllowedPipelineEntries | PipelineEntry} will be set for sure\n */\n resetCurrentPipeline() {\n this.currentPipelineIndex = null\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/pipelines/RenderPipelineEntry.mjs b/dist/esm/core/pipelines/RenderPipelineEntry.mjs new file mode 100644 index 000000000..5488aa526 --- /dev/null +++ b/dist/esm/core/pipelines/RenderPipelineEntry.mjs @@ -0,0 +1,328 @@ +import { PipelineEntry } from './PipelineEntry.mjs'; +import { ShaderChunks, ProjectedShaderChunks } from '../shaders/ShaderChunks.mjs'; +import { isRenderer } from '../renderers/utils.mjs'; +import { throwError } from '../../utils/utils.mjs'; + +class RenderPipelineEntry extends PipelineEntry { + /** + * RenderPipelineEntry constructor + * @param parameters - {@link RenderPipelineEntryParams | parameters} used to create this {@link RenderPipelineEntry} + */ + constructor(parameters) { + let { renderer } = parameters; + const { label, ...renderingOptions } = parameters; + renderer = renderer && renderer.renderer || renderer; + const type = "RenderPipelineEntry"; + isRenderer(renderer, label ? label + " " + type : type); + super(parameters); + this.type = type; + this.shaders = { + vertex: { + head: "", + code: "", + module: null + }, + fragment: { + head: "", + code: "", + module: null + }, + full: { + head: "", + code: "", + module: null + } + }; + this.descriptor = null; + this.options = { + ...this.options, + ...renderingOptions + }; + } + // TODO! need to chose whether we should silently add the camera bind group here + // or explicitly in the RenderMaterial class createBindGroups() method + /** + * Merge our {@link bindGroups | pipeline entry bind groups} with the {@link core/renderers/GPUCameraRenderer.GPUCameraRenderer#cameraBindGroup | camera bind group} if needed and set them + * @param bindGroups - {@link core/materials/RenderMaterial.RenderMaterial#bindGroups | bind groups} to use with this {@link RenderPipelineEntry} + */ + setPipelineEntryBindGroups(bindGroups) { + this.bindGroups = "cameraBindGroup" in this.renderer && this.options.useProjection ? [this.renderer.cameraBindGroup, ...bindGroups] : bindGroups; + } + /** + * Set {@link RenderPipelineEntry} properties (in this case the {@link bindGroups | bind groups} and {@link attributes}) + * @param parameters - the {@link core/materials/RenderMaterial.RenderMaterial#bindGroups | bind groups} and {@link core/materials/RenderMaterial.RenderMaterial#attributes | attributes} to use + */ + setPipelineEntryProperties(parameters) { + const { attributes, bindGroups } = parameters; + this.attributes = attributes; + this.setPipelineEntryBindGroups(bindGroups); + } + /* SHADERS */ + /** + * Patch the shaders by appending all the necessary shader chunks, {@link bindGroups | bind groups}) and {@link attributes} WGSL code fragments to the given {@link types/PipelineEntries.PipelineEntryParams#shaders | parameter shader code} + */ + patchShaders() { + this.shaders.vertex.head = ""; + this.shaders.vertex.code = ""; + this.shaders.fragment.head = ""; + this.shaders.fragment.code = ""; + this.shaders.full.head = ""; + this.shaders.full.code = ""; + for (const chunk in ShaderChunks.vertex) { + this.shaders.vertex.head = `${ShaderChunks.vertex[chunk]} +${this.shaders.vertex.head}`; + this.shaders.full.head = `${ShaderChunks.vertex[chunk]} +${this.shaders.full.head}`; + } + if (this.options.shaders.fragment) { + for (const chunk in ShaderChunks.fragment) { + this.shaders.fragment.head = `${ShaderChunks.fragment[chunk]} +${this.shaders.fragment.head}`; + if (this.shaders.full.head.indexOf(ShaderChunks.fragment[chunk]) === -1) { + this.shaders.full.head = `${ShaderChunks.fragment[chunk]} +${this.shaders.full.head}`; + } + } + } + if (this.options.useProjection) { + for (const chunk in ProjectedShaderChunks.vertex) { + this.shaders.vertex.head = `${ProjectedShaderChunks.vertex[chunk]} +${this.shaders.vertex.head}`; + this.shaders.full.head = `${ProjectedShaderChunks.vertex[chunk]} +${this.shaders.full.head}`; + } + if (this.options.shaders.fragment) { + for (const chunk in ProjectedShaderChunks.fragment) { + this.shaders.fragment.head = `${ProjectedShaderChunks.fragment[chunk]} +${this.shaders.fragment.head}`; + if (this.shaders.full.head.indexOf(ProjectedShaderChunks.fragment[chunk]) === -1) { + this.shaders.full.head = `${ProjectedShaderChunks.fragment[chunk]} +${this.shaders.full.head}`; + } + } + } + } + const groupsBindings = []; + this.bindGroups.forEach((bindGroup) => { + let bindIndex = 0; + bindGroup.bindings.forEach((binding, bindingIndex) => { + binding.wgslGroupFragment.forEach((groupFragment, groupFragmentIndex) => { + groupsBindings.push({ + groupIndex: bindGroup.index, + visibility: binding.visibility, + bindIndex, + wgslStructFragment: binding.wgslStructFragment, + wgslGroupFragment: groupFragment, + newLine: bindingIndex === bindGroup.bindings.length - 1 && groupFragmentIndex === binding.wgslGroupFragment.length - 1 + }); + bindIndex++; + }); + }); + }); + groupsBindings.forEach((groupBinding) => { + if (groupBinding.visibility === GPUShaderStage.VERTEX || groupBinding.visibility === (GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE)) { + if (groupBinding.wgslStructFragment && this.shaders.vertex.head.indexOf(groupBinding.wgslStructFragment) === -1) { + this.shaders.vertex.head = ` +${groupBinding.wgslStructFragment} +${this.shaders.vertex.head}`; + } + if (this.shaders.vertex.head.indexOf(groupBinding.wgslGroupFragment) === -1) { + this.shaders.vertex.head = `${this.shaders.vertex.head} +@group(${groupBinding.groupIndex}) @binding(${groupBinding.bindIndex}) ${groupBinding.wgslGroupFragment}`; + if (groupBinding.newLine) + this.shaders.vertex.head += ` +`; + } + } + if (this.options.shaders.fragment && (groupBinding.visibility === GPUShaderStage.FRAGMENT || groupBinding.visibility === (GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE))) { + if (groupBinding.wgslStructFragment && this.shaders.fragment.head.indexOf(groupBinding.wgslStructFragment) === -1) { + this.shaders.fragment.head = ` +${groupBinding.wgslStructFragment} +${this.shaders.fragment.head}`; + } + if (this.shaders.fragment.head.indexOf(groupBinding.wgslGroupFragment) === -1) { + this.shaders.fragment.head = `${this.shaders.fragment.head} +@group(${groupBinding.groupIndex}) @binding(${groupBinding.bindIndex}) ${groupBinding.wgslGroupFragment}`; + if (groupBinding.newLine) + this.shaders.fragment.head += ` +`; + } + } + if (groupBinding.wgslStructFragment && this.shaders.full.head.indexOf(groupBinding.wgslStructFragment) === -1) { + this.shaders.full.head = ` +${groupBinding.wgslStructFragment} +${this.shaders.full.head}`; + } + if (this.shaders.full.head.indexOf(groupBinding.wgslGroupFragment) === -1) { + this.shaders.full.head = `${this.shaders.full.head} +@group(${groupBinding.groupIndex}) @binding(${groupBinding.bindIndex}) ${groupBinding.wgslGroupFragment}`; + if (groupBinding.newLine) + this.shaders.full.head += ` +`; + } + }); + this.shaders.vertex.head = `${this.attributes.wgslStructFragment} +${this.shaders.vertex.head}`; + this.shaders.full.head = `${this.attributes.wgslStructFragment} +${this.shaders.full.head}`; + this.shaders.vertex.code = this.shaders.vertex.head + this.options.shaders.vertex.code; + if (typeof this.options.shaders.fragment === "object") + this.shaders.fragment.code = this.shaders.fragment.head + this.options.shaders.fragment.code; + if (typeof this.options.shaders.fragment === "object") { + if (this.options.shaders.vertex.entryPoint !== this.options.shaders.fragment.entryPoint && this.options.shaders.vertex.code.localeCompare(this.options.shaders.fragment.code) === 0) { + this.shaders.full.code = this.shaders.full.head + this.options.shaders.vertex.code; + } else { + this.shaders.full.code = this.shaders.full.head + this.options.shaders.vertex.code + this.options.shaders.fragment.code; + } + } + } + /* SETUP */ + /** + * Get whether the shaders modules have been created + * @readonly + */ + get shadersModulesReady() { + return !(!this.shaders.vertex.module || this.options.shaders.fragment && !this.shaders.fragment.module); + } + /** + * Create the {@link shaders}: patch them and create the {@link GPUShaderModule} + */ + createShaders() { + this.patchShaders(); + const isSameShader = typeof this.options.shaders.fragment === "object" && this.options.shaders.vertex.entryPoint !== this.options.shaders.fragment.entryPoint && this.options.shaders.vertex.code.localeCompare(this.options.shaders.fragment.code) === 0; + this.shaders.vertex.module = this.createShaderModule({ + code: this.shaders[isSameShader ? "full" : "vertex"].code, + type: "vertex" + }); + if (this.options.shaders.fragment) { + this.shaders.fragment.module = this.createShaderModule({ + code: this.shaders[isSameShader ? "full" : "fragment"].code, + type: "fragment" + }); + } + } + /** + * Create the render pipeline {@link descriptor} + */ + createPipelineDescriptor() { + if (!this.shadersModulesReady) + return; + let vertexLocationIndex = -1; + const blend = this.options.blend ?? (this.options.transparent && { + color: { + srcFactor: "src-alpha", + dstFactor: "one-minus-src-alpha" + }, + alpha: { + srcFactor: "one", + dstFactor: "one-minus-src-alpha" + } + }); + this.descriptor = { + label: this.options.label, + layout: this.layout, + vertex: { + module: this.shaders.vertex.module, + entryPoint: this.options.shaders.vertex.entryPoint, + buffers: this.attributes.vertexBuffers.map((vertexBuffer) => { + return { + stepMode: vertexBuffer.stepMode, + arrayStride: vertexBuffer.arrayStride * 4, + // 4 bytes each + attributes: vertexBuffer.attributes.map((attribute) => { + vertexLocationIndex++; + return { + shaderLocation: vertexLocationIndex, + offset: attribute.bufferOffset, + // previous attribute size * 4 + format: attribute.bufferFormat + }; + }) + }; + }) + }, + ...this.options.shaders.fragment && { + fragment: { + module: this.shaders.fragment.module, + entryPoint: this.options.shaders.fragment.entryPoint, + targets: [ + { + format: this.options.targetFormat ?? this.renderer.options.preferredFormat, + ...blend && { + blend + } + }, + ...this.options.additionalTargets ?? [] + // merge with additional targets if any + ] + } + }, + primitive: { + topology: this.options.topology, + frontFace: this.options.verticesOrder, + cullMode: this.options.cullMode + }, + ...this.options.depth && { + depthStencil: { + depthWriteEnabled: this.options.depthWriteEnabled, + depthCompare: this.options.depthCompare, + format: this.options.depthFormat + } + }, + ...this.options.sampleCount > 1 && { + multisample: { + count: this.options.sampleCount + } + } + }; + } + /** + * Create the render {@link pipeline} + */ + createRenderPipeline() { + if (!this.shadersModulesReady) + return; + try { + this.pipeline = this.renderer.createRenderPipeline(this.descriptor); + } catch (error) { + this.status.error = error; + throwError(error); + } + } + /** + * Asynchronously create the render {@link pipeline} + * @async + * @returns - void promise result + */ + async createRenderPipelineAsync() { + if (!this.shadersModulesReady) + return; + try { + this.pipeline = await this.renderer.createRenderPipelineAsync(this.descriptor); + this.status.compiled = true; + this.status.compiling = false; + this.status.error = null; + } catch (error) { + this.status.error = error; + throwError(error); + } + } + /** + * Call {@link PipelineEntry#compilePipelineEntry | PipelineEntry compilePipelineEntry} method, then create our render {@link pipeline} + * @async + */ + async compilePipelineEntry() { + super.compilePipelineEntry(); + if (this.options.useAsync) { + await this.createRenderPipelineAsync(); + } else { + this.createRenderPipeline(); + this.status.compiled = true; + this.status.compiling = false; + this.status.error = null; + } + } +} + +export { RenderPipelineEntry }; +//# sourceMappingURL=RenderPipelineEntry.mjs.map diff --git a/dist/esm/core/pipelines/RenderPipelineEntry.mjs.map b/dist/esm/core/pipelines/RenderPipelineEntry.mjs.map new file mode 100644 index 000000000..fbff9a17d --- /dev/null +++ b/dist/esm/core/pipelines/RenderPipelineEntry.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"RenderPipelineEntry.mjs","sources":["../../../../src/core/pipelines/RenderPipelineEntry.ts"],"sourcesContent":["import { PipelineEntry } from './PipelineEntry'\nimport { ProjectedShaderChunks, ShaderChunks } from '../shaders/ShaderChunks'\nimport { isRenderer, Renderer } from '../renderers/utils'\nimport { throwError } from '../../utils/utils'\nimport {\n PipelineEntryShaders,\n RenderPipelineEntryOptions,\n RenderPipelineEntryParams,\n RenderPipelineEntryPropertiesParams,\n} from '../../types/PipelineEntries'\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\nimport { AllowedBindGroups, BindGroupBufferBindingElement } from '../../types/BindGroups'\nimport { RenderMaterialAttributes, ShaderOptions } from '../../types/Materials'\n\n/**\n * Used to create a {@link PipelineEntry} specifically designed to handle {@link core/materials/RenderMaterial.RenderMaterial | RenderMaterial}.\n *\n * ## Shaders patching\n *\n * The {@link RenderPipelineEntry} uses each of its {@link RenderPipelineEntry#bindGroups | bind groups} {@link core/bindings/Binding.Binding | Binding} to patch the given compute shader before creating the {@link GPUShaderModule}.
\n * It will prepend every {@link core/bindings/Binding.Binding | Binding} WGSL code snippets (or fragments) with the correct bind group and bindings indices.\n *\n * ## Pipeline compilation\n *\n * The {@link RenderPipelineEntry} will then create a {@link GPURenderPipeline} (asynchronously by default).\n */\nexport class RenderPipelineEntry extends PipelineEntry {\n /** Shaders to use with this {@link RenderPipelineEntry} */\n shaders: PipelineEntryShaders\n /** {@link RenderMaterialAttributes | Geometry attributes} sent to the {@link RenderPipelineEntry} */\n attributes: RenderMaterialAttributes\n /** {@link GPURenderPipelineDescriptor | Render pipeline descriptor} based on {@link layout} and {@link shaders} */\n descriptor: GPURenderPipelineDescriptor | null\n /** Options used to create this {@link RenderPipelineEntry} */\n options: RenderPipelineEntryOptions\n\n /**\n * RenderPipelineEntry constructor\n * @param parameters - {@link RenderPipelineEntryParams | parameters} used to create this {@link RenderPipelineEntry}\n */\n constructor(parameters: RenderPipelineEntryParams) {\n let { renderer } = parameters\n const { label, ...renderingOptions } = parameters\n\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\n\n const type = 'RenderPipelineEntry'\n\n isRenderer(renderer, label ? label + ' ' + type : type)\n\n super(parameters)\n\n this.type = type\n\n this.shaders = {\n vertex: {\n head: '',\n code: '',\n module: null,\n },\n fragment: {\n head: '',\n code: '',\n module: null,\n },\n full: {\n head: '',\n code: '',\n module: null,\n },\n }\n\n this.descriptor = null\n\n this.options = {\n ...this.options,\n ...renderingOptions,\n } as RenderPipelineEntryOptions\n }\n\n // TODO! need to chose whether we should silently add the camera bind group here\n // or explicitly in the RenderMaterial class createBindGroups() method\n /**\n * Merge our {@link bindGroups | pipeline entry bind groups} with the {@link core/renderers/GPUCameraRenderer.GPUCameraRenderer#cameraBindGroup | camera bind group} if needed and set them\n * @param bindGroups - {@link core/materials/RenderMaterial.RenderMaterial#bindGroups | bind groups} to use with this {@link RenderPipelineEntry}\n */\n setPipelineEntryBindGroups(bindGroups: AllowedBindGroups[]) {\n this.bindGroups =\n 'cameraBindGroup' in this.renderer && this.options.useProjection\n ? [this.renderer.cameraBindGroup, ...bindGroups]\n : bindGroups\n }\n\n /**\n * Set {@link RenderPipelineEntry} properties (in this case the {@link bindGroups | bind groups} and {@link attributes})\n * @param parameters - the {@link core/materials/RenderMaterial.RenderMaterial#bindGroups | bind groups} and {@link core/materials/RenderMaterial.RenderMaterial#attributes | attributes} to use\n */\n setPipelineEntryProperties(parameters: RenderPipelineEntryPropertiesParams) {\n const { attributes, bindGroups } = parameters\n\n this.attributes = attributes\n\n this.setPipelineEntryBindGroups(bindGroups)\n }\n\n /* SHADERS */\n\n /**\n * Patch the shaders by appending all the necessary shader chunks, {@link bindGroups | bind groups}) and {@link attributes} WGSL code fragments to the given {@link types/PipelineEntries.PipelineEntryParams#shaders | parameter shader code}\n */\n patchShaders() {\n this.shaders.vertex.head = ''\n this.shaders.vertex.code = ''\n this.shaders.fragment.head = ''\n this.shaders.fragment.code = ''\n this.shaders.full.head = ''\n this.shaders.full.code = ''\n\n // first add chunks\n for (const chunk in ShaderChunks.vertex) {\n this.shaders.vertex.head = `${ShaderChunks.vertex[chunk]}\\n${this.shaders.vertex.head}`\n this.shaders.full.head = `${ShaderChunks.vertex[chunk]}\\n${this.shaders.full.head}`\n }\n\n if (this.options.shaders.fragment) {\n for (const chunk in ShaderChunks.fragment) {\n this.shaders.fragment.head = `${ShaderChunks.fragment[chunk]}\\n${this.shaders.fragment.head}`\n\n if (this.shaders.full.head.indexOf(ShaderChunks.fragment[chunk]) === -1) {\n this.shaders.full.head = `${ShaderChunks.fragment[chunk]}\\n${this.shaders.full.head}`\n }\n }\n }\n\n if (this.options.useProjection) {\n for (const chunk in ProjectedShaderChunks.vertex) {\n this.shaders.vertex.head = `${ProjectedShaderChunks.vertex[chunk]}\\n${this.shaders.vertex.head}`\n this.shaders.full.head = `${ProjectedShaderChunks.vertex[chunk]}\\n${this.shaders.full.head}`\n }\n\n if (this.options.shaders.fragment) {\n for (const chunk in ProjectedShaderChunks.fragment) {\n this.shaders.fragment.head = `${ProjectedShaderChunks.fragment[chunk]}\\n${this.shaders.fragment.head}`\n\n if (this.shaders.full.head.indexOf(ProjectedShaderChunks.fragment[chunk]) === -1) {\n this.shaders.full.head = `${ProjectedShaderChunks.fragment[chunk]}\\n${this.shaders.full.head}`\n }\n }\n }\n }\n\n const groupsBindings = []\n this.bindGroups.forEach((bindGroup) => {\n let bindIndex = 0\n bindGroup.bindings.forEach((binding, bindingIndex) => {\n binding.wgslGroupFragment.forEach((groupFragment, groupFragmentIndex) => {\n groupsBindings.push({\n groupIndex: bindGroup.index,\n visibility: binding.visibility,\n bindIndex,\n wgslStructFragment: (binding as BindGroupBufferBindingElement).wgslStructFragment,\n wgslGroupFragment: groupFragment,\n newLine:\n bindingIndex === bindGroup.bindings.length - 1 &&\n groupFragmentIndex === binding.wgslGroupFragment.length - 1,\n })\n\n bindIndex++\n })\n })\n })\n\n groupsBindings.forEach((groupBinding) => {\n if (\n groupBinding.visibility === GPUShaderStage.VERTEX ||\n groupBinding.visibility === (GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE)\n ) {\n // do not duplicate structs\n if (\n groupBinding.wgslStructFragment &&\n this.shaders.vertex.head.indexOf(groupBinding.wgslStructFragment) === -1\n ) {\n this.shaders.vertex.head = `\\n${groupBinding.wgslStructFragment}\\n${this.shaders.vertex.head}`\n }\n\n // do not duplicate struct var as well\n if (this.shaders.vertex.head.indexOf(groupBinding.wgslGroupFragment) === -1) {\n this.shaders.vertex.head = `${this.shaders.vertex.head}\\n@group(${groupBinding.groupIndex}) @binding(${groupBinding.bindIndex}) ${groupBinding.wgslGroupFragment}`\n\n if (groupBinding.newLine) this.shaders.vertex.head += `\\n`\n }\n }\n\n if (\n this.options.shaders.fragment &&\n (groupBinding.visibility === GPUShaderStage.FRAGMENT ||\n groupBinding.visibility === (GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT | GPUShaderStage.COMPUTE))\n ) {\n // do not duplicate structs\n if (\n groupBinding.wgslStructFragment &&\n this.shaders.fragment.head.indexOf(groupBinding.wgslStructFragment) === -1\n ) {\n this.shaders.fragment.head = `\\n${groupBinding.wgslStructFragment}\\n${this.shaders.fragment.head}`\n }\n\n // do not duplicate struct var as well\n if (this.shaders.fragment.head.indexOf(groupBinding.wgslGroupFragment) === -1) {\n this.shaders.fragment.head = `${this.shaders.fragment.head}\\n@group(${groupBinding.groupIndex}) @binding(${groupBinding.bindIndex}) ${groupBinding.wgslGroupFragment}`\n\n if (groupBinding.newLine) this.shaders.fragment.head += `\\n`\n }\n }\n\n if (groupBinding.wgslStructFragment && this.shaders.full.head.indexOf(groupBinding.wgslStructFragment) === -1) {\n this.shaders.full.head = `\\n${groupBinding.wgslStructFragment}\\n${this.shaders.full.head}`\n }\n\n if (this.shaders.full.head.indexOf(groupBinding.wgslGroupFragment) === -1) {\n this.shaders.full.head = `${this.shaders.full.head}\\n@group(${groupBinding.groupIndex}) @binding(${groupBinding.bindIndex}) ${groupBinding.wgslGroupFragment}`\n\n if (groupBinding.newLine) this.shaders.full.head += `\\n`\n }\n })\n\n // add attributes to vertex shader only\n this.shaders.vertex.head = `${this.attributes.wgslStructFragment}\\n${this.shaders.vertex.head}`\n this.shaders.full.head = `${this.attributes.wgslStructFragment}\\n${this.shaders.full.head}`\n\n this.shaders.vertex.code = this.shaders.vertex.head + this.options.shaders.vertex.code\n\n if (typeof this.options.shaders.fragment === 'object')\n this.shaders.fragment.code = this.shaders.fragment.head + this.options.shaders.fragment.code\n\n // check if its one shader string with different entry points\n if (typeof this.options.shaders.fragment === 'object') {\n if (\n this.options.shaders.vertex.entryPoint !== this.options.shaders.fragment.entryPoint &&\n this.options.shaders.vertex.code.localeCompare(this.options.shaders.fragment.code) === 0\n ) {\n this.shaders.full.code = this.shaders.full.head + this.options.shaders.vertex.code\n } else {\n this.shaders.full.code =\n this.shaders.full.head + this.options.shaders.vertex.code + this.options.shaders.fragment.code\n }\n }\n }\n\n /* SETUP */\n\n /**\n * Get whether the shaders modules have been created\n * @readonly\n */\n get shadersModulesReady(): boolean {\n return !(!this.shaders.vertex.module || (this.options.shaders.fragment && !this.shaders.fragment.module))\n }\n\n /**\n * Create the {@link shaders}: patch them and create the {@link GPUShaderModule}\n */\n createShaders() {\n this.patchShaders()\n\n const isSameShader =\n typeof this.options.shaders.fragment === 'object' &&\n this.options.shaders.vertex.entryPoint !== this.options.shaders.fragment.entryPoint &&\n this.options.shaders.vertex.code.localeCompare(this.options.shaders.fragment.code) === 0\n\n this.shaders.vertex.module = this.createShaderModule({\n code: this.shaders[isSameShader ? 'full' : 'vertex'].code,\n type: 'vertex',\n })\n\n if (this.options.shaders.fragment) {\n this.shaders.fragment.module = this.createShaderModule({\n code: this.shaders[isSameShader ? 'full' : 'fragment'].code,\n type: 'fragment',\n })\n }\n }\n\n /**\n * Create the render pipeline {@link descriptor}\n */\n createPipelineDescriptor() {\n if (!this.shadersModulesReady) return\n\n let vertexLocationIndex = -1\n\n // we will assume our renderer alphaMode is set to 'premultiplied'\n // we either disable blending if mesh if opaque\n // use a custom blending if set\n // or use this blend equation if mesh is transparent (see https://limnu.com/webgl-blending-youre-probably-wrong/)\n const blend =\n this.options.blend ??\n (this.options.transparent && {\n color: {\n srcFactor: 'src-alpha',\n dstFactor: 'one-minus-src-alpha',\n },\n alpha: {\n srcFactor: 'one',\n dstFactor: 'one-minus-src-alpha',\n },\n })\n\n this.descriptor = {\n label: this.options.label,\n layout: this.layout,\n vertex: {\n module: this.shaders.vertex.module,\n entryPoint: this.options.shaders.vertex.entryPoint,\n buffers: this.attributes.vertexBuffers.map((vertexBuffer) => {\n return {\n stepMode: vertexBuffer.stepMode,\n arrayStride: vertexBuffer.arrayStride * 4, // 4 bytes each\n attributes: vertexBuffer.attributes.map((attribute) => {\n vertexLocationIndex++\n return {\n shaderLocation: vertexLocationIndex,\n offset: attribute.bufferOffset, // previous attribute size * 4\n format: attribute.bufferFormat,\n }\n }),\n }\n }),\n },\n ...(this.options.shaders.fragment && {\n fragment: {\n module: this.shaders.fragment.module,\n entryPoint: (this.options.shaders.fragment as ShaderOptions).entryPoint,\n targets: [\n {\n format: this.options.targetFormat ?? this.renderer.options.preferredFormat,\n ...(blend && {\n blend,\n }),\n },\n ...(this.options.additionalTargets ?? []), // merge with additional targets if any\n ],\n },\n }),\n primitive: {\n topology: this.options.topology,\n frontFace: this.options.verticesOrder,\n cullMode: this.options.cullMode,\n },\n ...(this.options.depth && {\n depthStencil: {\n depthWriteEnabled: this.options.depthWriteEnabled,\n depthCompare: this.options.depthCompare,\n format: this.options.depthFormat,\n },\n }),\n ...(this.options.sampleCount > 1 && {\n multisample: {\n count: this.options.sampleCount,\n },\n }),\n } as GPURenderPipelineDescriptor\n }\n\n /**\n * Create the render {@link pipeline}\n */\n createRenderPipeline() {\n if (!this.shadersModulesReady) return\n\n try {\n this.pipeline = this.renderer.createRenderPipeline(this.descriptor)\n } catch (error) {\n this.status.error = error\n throwError(error)\n }\n }\n\n /**\n * Asynchronously create the render {@link pipeline}\n * @async\n * @returns - void promise result\n */\n async createRenderPipelineAsync(): Promise {\n if (!this.shadersModulesReady) return\n\n try {\n this.pipeline = await this.renderer.createRenderPipelineAsync(this.descriptor)\n this.status.compiled = true\n this.status.compiling = false\n this.status.error = null\n } catch (error) {\n this.status.error = error\n throwError(error)\n }\n }\n\n /**\n * Call {@link PipelineEntry#compilePipelineEntry | PipelineEntry compilePipelineEntry} method, then create our render {@link pipeline}\n * @async\n */\n async compilePipelineEntry(): Promise {\n super.compilePipelineEntry()\n\n if (this.options.useAsync) {\n await this.createRenderPipelineAsync()\n } else {\n this.createRenderPipeline()\n this.status.compiled = true\n this.status.compiling = false\n this.status.error = null\n }\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/renderPasses/RenderPass.mjs b/dist/esm/core/renderPasses/RenderPass.mjs new file mode 100644 index 000000000..3d4fc2ef6 --- /dev/null +++ b/dist/esm/core/renderPasses/RenderPass.mjs @@ -0,0 +1,247 @@ +import { isRenderer } from '../renderers/utils.mjs'; +import { generateUUID } from '../../utils/utils.mjs'; +import { RenderTexture } from '../textures/RenderTexture.mjs'; + +class RenderPass { + /** + * RenderPass constructor + * @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link RenderPass} + * @param parameters - {@link RenderPassParams | parameters} used to create this {@link RenderPass} + */ + constructor(renderer, { + label = "Render Pass", + sampleCount = 4, + qualityRatio = 1, + // color + useColorAttachments = true, + shouldUpdateView = true, + loadOp = "clear", + storeOp = "store", + clearValue = [0, 0, 0, 0], + targetFormat, + colorAttachments = [], + // depth + useDepth = true, + depthTexture = null, + depthLoadOp = "clear", + depthStoreOp = "store", + depthClearValue = 1, + depthFormat = "depth24plus" + } = {}) { + renderer = renderer && renderer.renderer || renderer; + isRenderer(renderer, "RenderPass"); + this.type = "RenderPass"; + this.uuid = generateUUID(); + this.renderer = renderer; + if (useColorAttachments) { + const defaultColorAttachment = { + loadOp, + storeOp, + clearValue, + targetFormat: targetFormat ?? this.renderer.options.preferredFormat + }; + if (!colorAttachments.length) { + colorAttachments = [defaultColorAttachment]; + } else { + colorAttachments = colorAttachments.map((colorAttachment) => { + return { ...defaultColorAttachment, ...colorAttachment }; + }); + } + } + this.options = { + label, + sampleCount, + qualityRatio, + // color + useColorAttachments, + shouldUpdateView, + loadOp, + storeOp, + clearValue, + targetFormat: targetFormat ?? this.renderer.options.preferredFormat, + colorAttachments, + // depth + useDepth, + ...depthTexture !== void 0 && { depthTexture }, + depthLoadOp, + depthStoreOp, + depthClearValue, + depthFormat + }; + this.setClearValue(clearValue); + if (this.options.useDepth) { + this.createDepthTexture(); + } + this.viewTextures = []; + if (this.options.useColorAttachments) { + this.createViewTextures(); + } + this.setRenderPassDescriptor(); + } + /** + * Create and set our {@link depthTexture | depth texture} + */ + createDepthTexture() { + if (this.options.depthTexture) { + this.depthTexture = this.options.depthTexture; + this.options.depthFormat = this.options.depthTexture.options.format; + } else { + this.depthTexture = new RenderTexture(this.renderer, { + label: this.options.label + " depth texture", + name: "depthTexture", + usage: "depth", + format: this.options.depthFormat, + sampleCount: this.options.sampleCount, + qualityRatio: this.options.qualityRatio + }); + } + } + /** + * Create and set our {@link viewTextures | view textures} + */ + createViewTextures() { + this.options.colorAttachments.forEach((colorAttachment, index) => { + this.viewTextures.push( + new RenderTexture(this.renderer, { + label: `${this.options.label} colorAttachment[${index}] view texture`, + name: `colorAttachment${index}ViewTexture`, + format: colorAttachment.targetFormat, + sampleCount: this.options.sampleCount, + qualityRatio: this.options.qualityRatio + }) + ); + }); + } + /** + * Set our render pass {@link descriptor} + */ + setRenderPassDescriptor() { + this.descriptor = { + label: this.options.label + " descriptor", + colorAttachments: this.options.colorAttachments.map((colorAttachment, index) => { + return { + // view + view: this.viewTextures[index].texture.createView({ + label: this.viewTextures[index].texture.label + " view" + }), + // clear values + clearValue: colorAttachment.clearValue, + // loadOp: 'clear' specifies to clear the texture to the clear value before drawing + // The other option is 'load' which means load the existing contents of the texture into the GPU so we can draw over what's already there. + loadOp: colorAttachment.loadOp, + // storeOp: 'store' means store the result of what we draw. + // We could also pass 'discard' which would throw away what we draw. + // see https://webgpufundamentals.org/webgpu/lessons/webgpu-multisampling.html + storeOp: colorAttachment.storeOp + }; + }), + ...this.options.useDepth && { + depthStencilAttachment: { + view: this.depthTexture.texture.createView({ + label: this.depthTexture.texture.label + " view" + }), + depthClearValue: this.options.depthClearValue, + // the same way loadOp is working, we can specify if we want to clear or load the previous depth buffer result + depthLoadOp: this.options.depthLoadOp, + depthStoreOp: this.options.depthStoreOp + } + } + }; + } + /** + * Resize our {@link RenderPass}: reset its {@link RenderTexture} + */ + resize() { + if (this.options.useDepth) { + this.descriptor.depthStencilAttachment.view = this.depthTexture.texture.createView({ + label: this.depthTexture.options.label + " view" + }); + } + this.viewTextures.forEach((viewTexture, index) => { + this.descriptor.colorAttachments[index].view = viewTexture.texture.createView({ + label: viewTexture.options.label + " view" + }); + }); + } + /** + * Set the {@link descriptor} {@link GPULoadOp | load operation} + * @param loadOp - new {@link GPULoadOp | load operation} to use + * @param colorAttachmentIndex - index of the color attachment for which to use this load operation + */ + setLoadOp(loadOp = "clear", colorAttachmentIndex = 0) { + this.options.loadOp = loadOp; + if (this.options.useColorAttachments && this.descriptor) { + if (this.descriptor.colorAttachments && this.descriptor.colorAttachments[colorAttachmentIndex]) { + this.descriptor.colorAttachments[colorAttachmentIndex].loadOp = loadOp; + } + } + } + /** + * Set the {@link descriptor} {@link GPULoadOp | depth load operation} + * @param depthLoadOp - new {@link GPULoadOp | depth load operation} to use + */ + setDepthLoadOp(depthLoadOp = "clear") { + this.options.depthLoadOp = depthLoadOp; + if (this.options.useDepth && this.descriptor.depthStencilAttachment) { + this.descriptor.depthStencilAttachment.depthLoadOp = depthLoadOp; + } + } + /** + * Set our {@link GPUColor | clear colors value}.
+ * Beware that if the {@link renderer} is using {@link core/renderers/GPURenderer.GPURenderer#alphaMode | premultiplied alpha mode}, your R, G and B channels should be premultiplied by your alpha channel. + * @param clearValue - new {@link GPUColor | clear colors value} to use + * @param colorAttachmentIndex - index of the color attachment for which to use this clear value + */ + setClearValue(clearValue = [0, 0, 0, 0], colorAttachmentIndex = 0) { + if (this.renderer.alphaMode === "premultiplied") { + const alpha = clearValue[3]; + clearValue[0] = Math.min(clearValue[0], alpha); + clearValue[1] = Math.min(clearValue[1], alpha); + clearValue[2] = Math.min(clearValue[2], alpha); + } else { + this.options.clearValue = clearValue; + } + if (this.descriptor && this.descriptor.colorAttachments && this.descriptor.colorAttachments[colorAttachmentIndex]) { + this.descriptor.colorAttachments[colorAttachmentIndex].clearValue = clearValue; + } + } + /** + * Set the current {@link descriptor} texture {@link GPURenderPassColorAttachment#view | view} and {@link GPURenderPassColorAttachment#resolveTarget | resolveTarget} (depending on whether we're using multisampling) + * @param renderTexture - {@link GPUTexture} to use, or the {@link core/renderers/GPURenderer.GPURenderer#context | context} {@link GPUTexture | current texture} if null. + * @returns - the {@link GPUTexture | texture} to render to. + */ + updateView(renderTexture = null) { + if (!this.options.colorAttachments.length || !this.options.shouldUpdateView) { + return renderTexture; + } + if (!renderTexture) { + renderTexture = this.renderer.context.getCurrentTexture(); + renderTexture.label = `${this.renderer.type} context current texture`; + } + if (this.options.sampleCount > 1) { + this.descriptor.colorAttachments[0].view = this.viewTextures[0].texture.createView({ + label: this.viewTextures[0].options.label + " view" + }); + this.descriptor.colorAttachments[0].resolveTarget = renderTexture.createView({ + label: renderTexture.label + " resolve target view" + }); + } else { + this.descriptor.colorAttachments[0].view = renderTexture.createView({ + label: renderTexture.label + " view" + }); + } + return renderTexture; + } + /** + * Destroy our {@link RenderPass} + */ + destroy() { + this.viewTextures.forEach((viewTexture) => viewTexture.destroy()); + if (!this.options.depthTexture && this.depthTexture) { + this.depthTexture.destroy(); + } + } +} + +export { RenderPass }; +//# sourceMappingURL=RenderPass.mjs.map diff --git a/dist/esm/core/renderPasses/RenderPass.mjs.map b/dist/esm/core/renderPasses/RenderPass.mjs.map new file mode 100644 index 000000000..b945a3f11 --- /dev/null +++ b/dist/esm/core/renderPasses/RenderPass.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"RenderPass.mjs","sources":["../../../../src/core/renderPasses/RenderPass.ts"],"sourcesContent":["import { isRenderer, Renderer } from '../renderers/utils'\nimport { generateUUID } from '../../utils/utils'\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\nimport { RenderTexture } from '../textures/RenderTexture'\n\n/** Define the parameters of a color attachment */\nexport interface ColorAttachmentParams {\n /** The {@link GPULoadOp | load operation} to perform while drawing this {@link RenderPass} */\n loadOp?: GPULoadOp\n /** The {@link GPUStoreOp | store operation} to perform while drawing this {@link RenderPass} */\n storeOp?: GPUStoreOp\n /** The {@link GPUColor | color values} to clear to before drawing this {@link RenderPass} */\n clearValue?: GPUColor\n /** Optional format of the color attachment texture */\n targetFormat: GPUTextureFormat\n}\n\n/**\n * Parameters used to create this {@link RenderPass}\n */\nexport interface RenderPassParams {\n /** The label of the {@link RenderPass}, sent to various GPU objects for debugging purpose */\n label?: string\n\n /** Whether the {@link RenderPass | view and depth textures} should use multisampling or not */\n sampleCount?: GPUSize32\n\n /** Force all the {@link RenderPass} textures size to be set to the given ratio of the {@link core/renderers/GPURenderer.GPURenderer#displayBoundingRect | renderer display bounding rectangle}. Used mainly to shrink the rendered definition. */\n qualityRatio?: number\n\n /** Whether this {@link RenderPass} should handle a view texture */\n useColorAttachments?: boolean\n /** Whether the main (first {@link colorAttachments}) view texture should be updated each frame */\n shouldUpdateView?: boolean\n /** The {@link GPULoadOp | load operation} to perform while drawing this {@link RenderPass} */\n loadOp?: GPULoadOp\n /** The {@link GPUStoreOp | store operation} to perform while drawing this {@link RenderPass} */\n storeOp?: GPUStoreOp\n /** The {@link GPUColor | color values} to clear to before drawing this {@link RenderPass} */\n clearValue?: GPUColor\n /** Optional format of the color attachment texture */\n targetFormat: GPUTextureFormat\n /** Define all the color attachments parameters to use here in case this {@link RenderPass} should output to multiple color attachments (Multiple Render Targets) */\n colorAttachments?: ColorAttachmentParams[]\n\n /** Whether this {@link RenderPass} should handle a depth texture */\n useDepth?: boolean\n /** Whether this {@link RenderPass} should use an already created depth texture */\n depthTexture?: RenderTexture\n /** The {@link GPULoadOp | depth load operation} to perform while drawing this {@link RenderPass} */\n depthLoadOp?: GPULoadOp\n /** The {@link GPUStoreOp | depth store operation} to perform while drawing this {@link RenderPass} */\n depthStoreOp?: GPUStoreOp\n /** The depth clear value to clear to before drawing this {@link RenderPass} */\n depthClearValue?: number\n /** Optional format of the depth texture */\n depthFormat?: GPUTextureFormat\n}\n\n/**\n * Used by {@link core/renderPasses/RenderTarget.RenderTarget | RenderTarget} and the {@link Renderer} to render to one or multiple {@link RenderPass#viewTextures | view textures} (and optionally a {@link RenderPass#depthTexture | depth texture}), using a specific {@link GPURenderPassDescriptor | render pass descriptor}.\n */\nexport class RenderPass {\n /** {@link Renderer} used by this {@link RenderPass} */\n renderer: Renderer\n /** The type of the {@link RenderPass} */\n type: string\n /** The universal unique id of this {@link RenderPass} */\n readonly uuid: string\n\n /** Options used to create this {@link RenderPass} */\n options: RenderPassParams\n\n /** Depth {@link RenderTexture} to use with this {@link RenderPass} if it should handle depth */\n depthTexture: RenderTexture | undefined\n\n /** Array of {@link RenderTexture} used for this {@link RenderPass} color attachments view textures */\n viewTextures: RenderTexture[]\n\n /** The {@link RenderPass} {@link GPURenderPassDescriptor | descriptor} */\n descriptor: GPURenderPassDescriptor\n\n /**\n * RenderPass constructor\n * @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link RenderPass}\n * @param parameters - {@link RenderPassParams | parameters} used to create this {@link RenderPass}\n */\n constructor(\n renderer: Renderer | GPUCurtains,\n {\n label = 'Render Pass',\n sampleCount = 4,\n qualityRatio = 1,\n // color\n useColorAttachments = true,\n shouldUpdateView = true,\n loadOp = 'clear' as GPULoadOp,\n storeOp = 'store' as GPUStoreOp,\n clearValue = [0, 0, 0, 0],\n targetFormat,\n colorAttachments = [],\n // depth\n useDepth = true,\n depthTexture = null,\n depthLoadOp = 'clear' as GPULoadOp,\n depthStoreOp = 'store' as GPUStoreOp,\n depthClearValue = 1,\n depthFormat = 'depth24plus' as GPUTextureFormat,\n } = {} as RenderPassParams\n ) {\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\n\n isRenderer(renderer, 'RenderPass')\n\n this.type = 'RenderPass'\n this.uuid = generateUUID()\n\n this.renderer = renderer\n\n if (useColorAttachments) {\n const defaultColorAttachment = {\n loadOp,\n storeOp,\n clearValue,\n targetFormat: targetFormat ?? this.renderer.options.preferredFormat,\n }\n\n if (!colorAttachments.length) {\n colorAttachments = [defaultColorAttachment]\n } else {\n colorAttachments = colorAttachments.map((colorAttachment) => {\n return { ...defaultColorAttachment, ...colorAttachment }\n })\n }\n }\n\n this.options = {\n label,\n sampleCount,\n qualityRatio,\n // color\n useColorAttachments,\n shouldUpdateView,\n loadOp,\n storeOp,\n clearValue,\n targetFormat: targetFormat ?? this.renderer.options.preferredFormat,\n colorAttachments,\n // depth\n useDepth,\n ...(depthTexture !== undefined && { depthTexture }),\n depthLoadOp,\n depthStoreOp,\n depthClearValue,\n depthFormat,\n }\n\n this.setClearValue(clearValue)\n\n // if needed, create a depth texture before our descriptor\n if (this.options.useDepth) {\n this.createDepthTexture()\n }\n\n // if needed, create a view texture before our descriptor\n this.viewTextures = []\n if (this.options.useColorAttachments) {\n this.createViewTextures()\n }\n\n this.setRenderPassDescriptor()\n }\n\n /**\n * Create and set our {@link depthTexture | depth texture}\n */\n createDepthTexture() {\n if (this.options.depthTexture) {\n this.depthTexture = this.options.depthTexture\n // adjust depth format as well\n this.options.depthFormat = this.options.depthTexture.options.format\n } else {\n this.depthTexture = new RenderTexture(this.renderer, {\n label: this.options.label + ' depth texture',\n name: 'depthTexture',\n usage: 'depth',\n format: this.options.depthFormat,\n sampleCount: this.options.sampleCount,\n qualityRatio: this.options.qualityRatio,\n })\n }\n }\n\n /**\n * Create and set our {@link viewTextures | view textures}\n */\n createViewTextures() {\n this.options.colorAttachments.forEach((colorAttachment, index) => {\n this.viewTextures.push(\n new RenderTexture(this.renderer, {\n label: `${this.options.label} colorAttachment[${index}] view texture`,\n name: `colorAttachment${index}ViewTexture`,\n format: colorAttachment.targetFormat,\n sampleCount: this.options.sampleCount,\n qualityRatio: this.options.qualityRatio,\n })\n )\n })\n }\n\n /**\n * Set our render pass {@link descriptor}\n */\n setRenderPassDescriptor() {\n this.descriptor = {\n label: this.options.label + ' descriptor',\n colorAttachments: this.options.colorAttachments.map((colorAttachment, index) => {\n return {\n // view\n view: this.viewTextures[index].texture.createView({\n label: this.viewTextures[index].texture.label + ' view',\n }),\n // clear values\n clearValue: colorAttachment.clearValue,\n // loadOp: 'clear' specifies to clear the texture to the clear value before drawing\n // The other option is 'load' which means load the existing contents of the texture into the GPU so we can draw over what's already there.\n loadOp: colorAttachment.loadOp,\n // storeOp: 'store' means store the result of what we draw.\n // We could also pass 'discard' which would throw away what we draw.\n // see https://webgpufundamentals.org/webgpu/lessons/webgpu-multisampling.html\n storeOp: colorAttachment.storeOp,\n }\n }),\n\n ...(this.options.useDepth && {\n depthStencilAttachment: {\n view: this.depthTexture.texture.createView({\n label: this.depthTexture.texture.label + ' view',\n }),\n depthClearValue: this.options.depthClearValue,\n // the same way loadOp is working, we can specify if we want to clear or load the previous depth buffer result\n depthLoadOp: this.options.depthLoadOp,\n depthStoreOp: this.options.depthStoreOp,\n },\n }),\n } as GPURenderPassDescriptor\n }\n\n /**\n * Resize our {@link RenderPass}: reset its {@link RenderTexture}\n */\n resize() {\n // reassign textures\n if (this.options.useDepth) {\n this.descriptor.depthStencilAttachment.view = this.depthTexture.texture.createView({\n label: this.depthTexture.options.label + ' view',\n })\n }\n\n this.viewTextures.forEach((viewTexture, index) => {\n this.descriptor.colorAttachments[index].view = viewTexture.texture.createView({\n label: viewTexture.options.label + ' view',\n })\n })\n }\n\n /**\n * Set the {@link descriptor} {@link GPULoadOp | load operation}\n * @param loadOp - new {@link GPULoadOp | load operation} to use\n * @param colorAttachmentIndex - index of the color attachment for which to use this load operation\n */\n setLoadOp(loadOp: GPULoadOp = 'clear', colorAttachmentIndex = 0) {\n this.options.loadOp = loadOp\n if (this.options.useColorAttachments && this.descriptor) {\n if (this.descriptor.colorAttachments && this.descriptor.colorAttachments[colorAttachmentIndex]) {\n this.descriptor.colorAttachments[colorAttachmentIndex].loadOp = loadOp\n }\n }\n }\n\n /**\n * Set the {@link descriptor} {@link GPULoadOp | depth load operation}\n * @param depthLoadOp - new {@link GPULoadOp | depth load operation} to use\n */\n setDepthLoadOp(depthLoadOp: GPULoadOp = 'clear') {\n this.options.depthLoadOp = depthLoadOp\n if (this.options.useDepth && this.descriptor.depthStencilAttachment) {\n this.descriptor.depthStencilAttachment.depthLoadOp = depthLoadOp\n }\n }\n\n /**\n * Set our {@link GPUColor | clear colors value}.
\n * Beware that if the {@link renderer} is using {@link core/renderers/GPURenderer.GPURenderer#alphaMode | premultiplied alpha mode}, your R, G and B channels should be premultiplied by your alpha channel.\n * @param clearValue - new {@link GPUColor | clear colors value} to use\n * @param colorAttachmentIndex - index of the color attachment for which to use this clear value\n */\n setClearValue(clearValue: GPUColor = [0, 0, 0, 0], colorAttachmentIndex = 0) {\n if (this.renderer.alphaMode === 'premultiplied') {\n const alpha = clearValue[3]\n clearValue[0] = Math.min(clearValue[0], alpha)\n clearValue[1] = Math.min(clearValue[1], alpha)\n clearValue[2] = Math.min(clearValue[2], alpha)\n } else {\n this.options.clearValue = clearValue\n }\n\n if (this.descriptor && this.descriptor.colorAttachments && this.descriptor.colorAttachments[colorAttachmentIndex]) {\n this.descriptor.colorAttachments[colorAttachmentIndex].clearValue = clearValue\n }\n }\n\n /**\n * Set the current {@link descriptor} texture {@link GPURenderPassColorAttachment#view | view} and {@link GPURenderPassColorAttachment#resolveTarget | resolveTarget} (depending on whether we're using multisampling)\n * @param renderTexture - {@link GPUTexture} to use, or the {@link core/renderers/GPURenderer.GPURenderer#context | context} {@link GPUTexture | current texture} if null.\n * @returns - the {@link GPUTexture | texture} to render to.\n */\n updateView(renderTexture: GPUTexture | null = null): GPUTexture | null {\n if (!this.options.colorAttachments.length || !this.options.shouldUpdateView) {\n return renderTexture\n }\n\n if (!renderTexture) {\n renderTexture = this.renderer.context.getCurrentTexture()\n renderTexture.label = `${this.renderer.type} context current texture`\n }\n\n if (this.options.sampleCount > 1) {\n this.descriptor.colorAttachments[0].view = this.viewTextures[0].texture.createView({\n label: this.viewTextures[0].options.label + ' view',\n })\n this.descriptor.colorAttachments[0].resolveTarget = renderTexture.createView({\n label: renderTexture.label + ' resolve target view',\n })\n } else {\n this.descriptor.colorAttachments[0].view = renderTexture.createView({\n label: renderTexture.label + ' view',\n })\n }\n\n return renderTexture\n }\n\n /**\n * Destroy our {@link RenderPass}\n */\n destroy() {\n this.viewTextures.forEach((viewTexture) => viewTexture.destroy())\n\n if (!this.options.depthTexture && this.depthTexture) {\n this.depthTexture.destroy()\n }\n }\n}\n"],"names":[],"mappings":";;;;AA8DO,MAAM,UAAW,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyBtB,YACE,QACA,EAAA;AAAA,IACE,KAAQ,GAAA,aAAA;AAAA,IACR,WAAc,GAAA,CAAA;AAAA,IACd,YAAe,GAAA,CAAA;AAAA;AAAA,IAEf,mBAAsB,GAAA,IAAA;AAAA,IACtB,gBAAmB,GAAA,IAAA;AAAA,IACnB,MAAS,GAAA,OAAA;AAAA,IACT,OAAU,GAAA,OAAA;AAAA,IACV,UAAa,GAAA,CAAC,CAAG,EAAA,CAAA,EAAG,GAAG,CAAC,CAAA;AAAA,IACxB,YAAA;AAAA,IACA,mBAAmB,EAAC;AAAA;AAAA,IAEpB,QAAW,GAAA,IAAA;AAAA,IACX,YAAe,GAAA,IAAA;AAAA,IACf,WAAc,GAAA,OAAA;AAAA,IACd,YAAe,GAAA,OAAA;AAAA,IACf,eAAkB,GAAA,CAAA;AAAA,IAClB,WAAc,GAAA,aAAA;AAAA,GAChB,GAAI,EACJ,EAAA;AAEA,IAAY,QAAA,GAAA,QAAA,IAAa,SAAyB,QAAc,IAAA,QAAA,CAAA;AAEhE,IAAA,UAAA,CAAW,UAAU,YAAY,CAAA,CAAA;AAEjC,IAAA,IAAA,CAAK,IAAO,GAAA,YAAA,CAAA;AACZ,IAAA,IAAA,CAAK,OAAO,YAAa,EAAA,CAAA;AAEzB,IAAA,IAAA,CAAK,QAAW,GAAA,QAAA,CAAA;AAEhB,IAAA,IAAI,mBAAqB,EAAA;AACvB,MAAA,MAAM,sBAAyB,GAAA;AAAA,QAC7B,MAAA;AAAA,QACA,OAAA;AAAA,QACA,UAAA;AAAA,QACA,YAAc,EAAA,YAAA,IAAgB,IAAK,CAAA,QAAA,CAAS,OAAQ,CAAA,eAAA;AAAA,OACtD,CAAA;AAEA,MAAI,IAAA,CAAC,iBAAiB,MAAQ,EAAA;AAC5B,QAAA,gBAAA,GAAmB,CAAC,sBAAsB,CAAA,CAAA;AAAA,OACrC,MAAA;AACL,QAAmB,gBAAA,GAAA,gBAAA,CAAiB,GAAI,CAAA,CAAC,eAAoB,KAAA;AAC3D,UAAA,OAAO,EAAE,GAAG,sBAAwB,EAAA,GAAG,eAAgB,EAAA,CAAA;AAAA,SACxD,CAAA,CAAA;AAAA,OACH;AAAA,KACF;AAEA,IAAA,IAAA,CAAK,OAAU,GAAA;AAAA,MACb,KAAA;AAAA,MACA,WAAA;AAAA,MACA,YAAA;AAAA;AAAA,MAEA,mBAAA;AAAA,MACA,gBAAA;AAAA,MACA,MAAA;AAAA,MACA,OAAA;AAAA,MACA,UAAA;AAAA,MACA,YAAc,EAAA,YAAA,IAAgB,IAAK,CAAA,QAAA,CAAS,OAAQ,CAAA,eAAA;AAAA,MACpD,gBAAA;AAAA;AAAA,MAEA,QAAA;AAAA,MACA,GAAI,YAAA,KAAiB,KAAa,CAAA,IAAA,EAAE,YAAa,EAAA;AAAA,MACjD,WAAA;AAAA,MACA,YAAA;AAAA,MACA,eAAA;AAAA,MACA,WAAA;AAAA,KACF,CAAA;AAEA,IAAA,IAAA,CAAK,cAAc,UAAU,CAAA,CAAA;AAG7B,IAAI,IAAA,IAAA,CAAK,QAAQ,QAAU,EAAA;AACzB,MAAA,IAAA,CAAK,kBAAmB,EAAA,CAAA;AAAA,KAC1B;AAGA,IAAA,IAAA,CAAK,eAAe,EAAC,CAAA;AACrB,IAAI,IAAA,IAAA,CAAK,QAAQ,mBAAqB,EAAA;AACpC,MAAA,IAAA,CAAK,kBAAmB,EAAA,CAAA;AAAA,KAC1B;AAEA,IAAA,IAAA,CAAK,uBAAwB,EAAA,CAAA;AAAA,GAC/B;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAqB,GAAA;AACnB,IAAI,IAAA,IAAA,CAAK,QAAQ,YAAc,EAAA;AAC7B,MAAK,IAAA,CAAA,YAAA,GAAe,KAAK,OAAQ,CAAA,YAAA,CAAA;AAEjC,MAAA,IAAA,CAAK,OAAQ,CAAA,WAAA,GAAc,IAAK,CAAA,OAAA,CAAQ,aAAa,OAAQ,CAAA,MAAA,CAAA;AAAA,KACxD,MAAA;AACL,MAAA,IAAA,CAAK,YAAe,GAAA,IAAI,aAAc,CAAA,IAAA,CAAK,QAAU,EAAA;AAAA,QACnD,KAAA,EAAO,IAAK,CAAA,OAAA,CAAQ,KAAQ,GAAA,gBAAA;AAAA,QAC5B,IAAM,EAAA,cAAA;AAAA,QACN,KAAO,EAAA,OAAA;AAAA,QACP,MAAA,EAAQ,KAAK,OAAQ,CAAA,WAAA;AAAA,QACrB,WAAA,EAAa,KAAK,OAAQ,CAAA,WAAA;AAAA,QAC1B,YAAA,EAAc,KAAK,OAAQ,CAAA,YAAA;AAAA,OAC5B,CAAA,CAAA;AAAA,KACH;AAAA,GACF;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAqB,GAAA;AACnB,IAAA,IAAA,CAAK,OAAQ,CAAA,gBAAA,CAAiB,OAAQ,CAAA,CAAC,iBAAiB,KAAU,KAAA;AAChE,MAAA,IAAA,CAAK,YAAa,CAAA,IAAA;AAAA,QAChB,IAAI,aAAc,CAAA,IAAA,CAAK,QAAU,EAAA;AAAA,UAC/B,OAAO,CAAG,EAAA,IAAA,CAAK,OAAQ,CAAA,KAAK,oBAAoB,KAAK,CAAA,cAAA,CAAA;AAAA,UACrD,IAAA,EAAM,kBAAkB,KAAK,CAAA,WAAA,CAAA;AAAA,UAC7B,QAAQ,eAAgB,CAAA,YAAA;AAAA,UACxB,WAAA,EAAa,KAAK,OAAQ,CAAA,WAAA;AAAA,UAC1B,YAAA,EAAc,KAAK,OAAQ,CAAA,YAAA;AAAA,SAC5B,CAAA;AAAA,OACH,CAAA;AAAA,KACD,CAAA,CAAA;AAAA,GACH;AAAA;AAAA;AAAA;AAAA,EAKA,uBAA0B,GAAA;AACxB,IAAA,IAAA,CAAK,UAAa,GAAA;AAAA,MAChB,KAAA,EAAO,IAAK,CAAA,OAAA,CAAQ,KAAQ,GAAA,aAAA;AAAA,MAC5B,kBAAkB,IAAK,CAAA,OAAA,CAAQ,iBAAiB,GAAI,CAAA,CAAC,iBAAiB,KAAU,KAAA;AAC9E,QAAO,OAAA;AAAA;AAAA,UAEL,MAAM,IAAK,CAAA,YAAA,CAAa,KAAK,CAAA,CAAE,QAAQ,UAAW,CAAA;AAAA,YAChD,OAAO,IAAK,CAAA,YAAA,CAAa,KAAK,CAAA,CAAE,QAAQ,KAAQ,GAAA,OAAA;AAAA,WACjD,CAAA;AAAA;AAAA,UAED,YAAY,eAAgB,CAAA,UAAA;AAAA;AAAA;AAAA,UAG5B,QAAQ,eAAgB,CAAA,MAAA;AAAA;AAAA;AAAA;AAAA,UAIxB,SAAS,eAAgB,CAAA,OAAA;AAAA,SAC3B,CAAA;AAAA,OACD,CAAA;AAAA,MAED,GAAI,IAAK,CAAA,OAAA,CAAQ,QAAY,IAAA;AAAA,QAC3B,sBAAwB,EAAA;AAAA,UACtB,IAAM,EAAA,IAAA,CAAK,YAAa,CAAA,OAAA,CAAQ,UAAW,CAAA;AAAA,YACzC,KAAO,EAAA,IAAA,CAAK,YAAa,CAAA,OAAA,CAAQ,KAAQ,GAAA,OAAA;AAAA,WAC1C,CAAA;AAAA,UACD,eAAA,EAAiB,KAAK,OAAQ,CAAA,eAAA;AAAA;AAAA,UAE9B,WAAA,EAAa,KAAK,OAAQ,CAAA,WAAA;AAAA,UAC1B,YAAA,EAAc,KAAK,OAAQ,CAAA,YAAA;AAAA,SAC7B;AAAA,OACF;AAAA,KACF,CAAA;AAAA,GACF;AAAA;AAAA;AAAA;AAAA,EAKA,MAAS,GAAA;AAEP,IAAI,IAAA,IAAA,CAAK,QAAQ,QAAU,EAAA;AACzB,MAAA,IAAA,CAAK,WAAW,sBAAuB,CAAA,IAAA,GAAO,IAAK,CAAA,YAAA,CAAa,QAAQ,UAAW,CAAA;AAAA,QACjF,KAAO,EAAA,IAAA,CAAK,YAAa,CAAA,OAAA,CAAQ,KAAQ,GAAA,OAAA;AAAA,OAC1C,CAAA,CAAA;AAAA,KACH;AAEA,IAAA,IAAA,CAAK,YAAa,CAAA,OAAA,CAAQ,CAAC,WAAA,EAAa,KAAU,KAAA;AAChD,MAAA,IAAA,CAAK,WAAW,gBAAiB,CAAA,KAAK,EAAE,IAAO,GAAA,WAAA,CAAY,QAAQ,UAAW,CAAA;AAAA,QAC5E,KAAA,EAAO,WAAY,CAAA,OAAA,CAAQ,KAAQ,GAAA,OAAA;AAAA,OACpC,CAAA,CAAA;AAAA,KACF,CAAA,CAAA;AAAA,GACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,SAAU,CAAA,MAAA,GAAoB,OAAS,EAAA,oBAAA,GAAuB,CAAG,EAAA;AAC/D,IAAA,IAAA,CAAK,QAAQ,MAAS,GAAA,MAAA,CAAA;AACtB,IAAA,IAAI,IAAK,CAAA,OAAA,CAAQ,mBAAuB,IAAA,IAAA,CAAK,UAAY,EAAA;AACvD,MAAA,IAAI,KAAK,UAAW,CAAA,gBAAA,IAAoB,KAAK,UAAW,CAAA,gBAAA,CAAiB,oBAAoB,CAAG,EAAA;AAC9F,QAAA,IAAA,CAAK,UAAW,CAAA,gBAAA,CAAiB,oBAAoB,CAAA,CAAE,MAAS,GAAA,MAAA,CAAA;AAAA,OAClE;AAAA,KACF;AAAA,GACF;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,cAAA,CAAe,cAAyB,OAAS,EAAA;AAC/C,IAAA,IAAA,CAAK,QAAQ,WAAc,GAAA,WAAA,CAAA;AAC3B,IAAA,IAAI,IAAK,CAAA,OAAA,CAAQ,QAAY,IAAA,IAAA,CAAK,WAAW,sBAAwB,EAAA;AACnE,MAAK,IAAA,CAAA,UAAA,CAAW,uBAAuB,WAAc,GAAA,WAAA,CAAA;AAAA,KACvD;AAAA,GACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,aAAA,CAAc,aAAuB,CAAC,CAAA,EAAG,GAAG,CAAG,EAAA,CAAC,CAAG,EAAA,oBAAA,GAAuB,CAAG,EAAA;AAC3E,IAAI,IAAA,IAAA,CAAK,QAAS,CAAA,SAAA,KAAc,eAAiB,EAAA;AAC/C,MAAM,MAAA,KAAA,GAAQ,WAAW,CAAC,CAAA,CAAA;AAC1B,MAAA,UAAA,CAAW,CAAC,CAAI,GAAA,IAAA,CAAK,IAAI,UAAW,CAAA,CAAC,GAAG,KAAK,CAAA,CAAA;AAC7C,MAAA,UAAA,CAAW,CAAC,CAAI,GAAA,IAAA,CAAK,IAAI,UAAW,CAAA,CAAC,GAAG,KAAK,CAAA,CAAA;AAC7C,MAAA,UAAA,CAAW,CAAC,CAAI,GAAA,IAAA,CAAK,IAAI,UAAW,CAAA,CAAC,GAAG,KAAK,CAAA,CAAA;AAAA,KACxC,MAAA;AACL,MAAA,IAAA,CAAK,QAAQ,UAAa,GAAA,UAAA,CAAA;AAAA,KAC5B;AAEA,IAAI,IAAA,IAAA,CAAK,cAAc,IAAK,CAAA,UAAA,CAAW,oBAAoB,IAAK,CAAA,UAAA,CAAW,gBAAiB,CAAA,oBAAoB,CAAG,EAAA;AACjH,MAAA,IAAA,CAAK,UAAW,CAAA,gBAAA,CAAiB,oBAAoB,CAAA,CAAE,UAAa,GAAA,UAAA,CAAA;AAAA,KACtE;AAAA,GACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,UAAA,CAAW,gBAAmC,IAAyB,EAAA;AACrE,IAAI,IAAA,CAAC,KAAK,OAAQ,CAAA,gBAAA,CAAiB,UAAU,CAAC,IAAA,CAAK,QAAQ,gBAAkB,EAAA;AAC3E,MAAO,OAAA,aAAA,CAAA;AAAA,KACT;AAEA,IAAA,IAAI,CAAC,aAAe,EAAA;AAClB,MAAgB,aAAA,GAAA,IAAA,CAAK,QAAS,CAAA,OAAA,CAAQ,iBAAkB,EAAA,CAAA;AACxD,MAAA,aAAA,CAAc,KAAQ,GAAA,CAAA,EAAG,IAAK,CAAA,QAAA,CAAS,IAAI,CAAA,wBAAA,CAAA,CAAA;AAAA,KAC7C;AAEA,IAAI,IAAA,IAAA,CAAK,OAAQ,CAAA,WAAA,GAAc,CAAG,EAAA;AAChC,MAAK,IAAA,CAAA,UAAA,CAAW,gBAAiB,CAAA,CAAC,CAAE,CAAA,IAAA,GAAO,KAAK,YAAa,CAAA,CAAC,CAAE,CAAA,OAAA,CAAQ,UAAW,CAAA;AAAA,QACjF,OAAO,IAAK,CAAA,YAAA,CAAa,CAAC,CAAA,CAAE,QAAQ,KAAQ,GAAA,OAAA;AAAA,OAC7C,CAAA,CAAA;AACD,MAAA,IAAA,CAAK,WAAW,gBAAiB,CAAA,CAAC,CAAE,CAAA,aAAA,GAAgB,cAAc,UAAW,CAAA;AAAA,QAC3E,KAAA,EAAO,cAAc,KAAQ,GAAA,sBAAA;AAAA,OAC9B,CAAA,CAAA;AAAA,KACI,MAAA;AACL,MAAA,IAAA,CAAK,WAAW,gBAAiB,CAAA,CAAC,CAAE,CAAA,IAAA,GAAO,cAAc,UAAW,CAAA;AAAA,QAClE,KAAA,EAAO,cAAc,KAAQ,GAAA,OAAA;AAAA,OAC9B,CAAA,CAAA;AAAA,KACH;AAEA,IAAO,OAAA,aAAA,CAAA;AAAA,GACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAU,GAAA;AACR,IAAA,IAAA,CAAK,aAAa,OAAQ,CAAA,CAAC,WAAgB,KAAA,WAAA,CAAY,SAAS,CAAA,CAAA;AAEhE,IAAA,IAAI,CAAC,IAAA,CAAK,OAAQ,CAAA,YAAA,IAAgB,KAAK,YAAc,EAAA;AACnD,MAAA,IAAA,CAAK,aAAa,OAAQ,EAAA,CAAA;AAAA,KAC5B;AAAA,GACF;AACF;;;;"} \ No newline at end of file diff --git a/dist/esm/core/renderPasses/RenderTarget.mjs b/dist/esm/core/renderPasses/RenderTarget.mjs new file mode 100644 index 000000000..5f70e762a --- /dev/null +++ b/dist/esm/core/renderPasses/RenderTarget.mjs @@ -0,0 +1,121 @@ +import { isRenderer } from '../renderers/utils.mjs'; +import { RenderPass } from './RenderPass.mjs'; +import { RenderTexture } from '../textures/RenderTexture.mjs'; +import { generateUUID } from '../../utils/utils.mjs'; + +var __accessCheck = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); +}; +var __privateGet = (obj, member, getter) => { + __accessCheck(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); +}; +var __privateAdd = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); +}; +var __privateSet = (obj, member, value, setter) => { + __accessCheck(obj, member, "write to private field"); + setter ? setter.call(obj, value) : member.set(obj, value); + return value; +}; +var _autoRender; +class RenderTarget { + /** + * RenderTarget constructor + * @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link RenderTarget} + * @param parameters - {@link RenderTargetParams | parameters} use to create this {@link RenderTarget} + */ + constructor(renderer, parameters = {}) { + /** Whether we should add this {@link RenderTarget} to our {@link core/scenes/Scene.Scene | Scene} to let it handle the rendering process automatically */ + __privateAdd(this, _autoRender, true); + renderer = renderer && renderer.renderer || renderer; + isRenderer(renderer, "RenderTarget"); + this.type = "RenderTarget"; + this.renderer = renderer; + this.uuid = generateUUID(); + const { label, targetFormat, depthTexture, autoRender, ...renderPassParams } = parameters; + this.options = { + label, + ...renderPassParams, + ...depthTexture && { depthTexture }, + targetFormat: targetFormat ?? this.renderer.options.preferredFormat, + autoRender: autoRender === void 0 ? true : autoRender + }; + if (autoRender !== void 0) { + __privateSet(this, _autoRender, autoRender); + } + this.renderPass = new RenderPass(this.renderer, { + label: this.options.label ? `${this.options.label} Render Pass` : "Render Target Render Pass", + targetFormat: this.options.targetFormat, + depthTexture: this.options.depthTexture ?? this.renderer.renderPass.depthTexture, + // reuse renderer depth texture for every pass + ...renderPassParams + }); + if (renderPassParams.useColorAttachments !== false) { + this.renderTexture = new RenderTexture(this.renderer, { + label: this.options.label ? `${this.options.label} Render Texture` : "Render Target render texture", + name: "renderTexture", + format: this.options.targetFormat, + ...this.options.qualityRatio !== void 0 && { qualityRatio: this.options.qualityRatio } + }); + } + this.addToScene(); + } + /** + * Add the {@link RenderTarget} to the renderer and the {@link core/scenes/Scene.Scene | Scene} + */ + addToScene() { + this.renderer.renderTargets.push(this); + if (__privateGet(this, _autoRender)) { + this.renderer.scene.addRenderTarget(this); + } + } + /** + * Remove the {@link RenderTarget} from the renderer and the {@link core/scenes/Scene.Scene | Scene} + */ + removeFromScene() { + if (__privateGet(this, _autoRender)) { + this.renderer.scene.removeRenderTarget(this); + } + this.renderer.renderTargets = this.renderer.renderTargets.filter((renderTarget) => renderTarget.uuid !== this.uuid); + } + /** + * Resize our {@link renderPass} + */ + resize() { + this.renderPass.options.depthTexture.texture = this.options.depthTexture ? this.options.depthTexture.texture : this.renderer.renderPass.depthTexture.texture; + this.renderPass?.resize(); + } + /** + * Remove our {@link RenderTarget}. Alias of {@link RenderTarget#destroy} + */ + remove() { + this.destroy(); + } + /** + * Destroy our {@link RenderTarget} + */ + destroy() { + this.renderer.meshes.forEach((mesh) => { + if (mesh.outputTarget && mesh.outputTarget.uuid === this.uuid) { + mesh.setOutputTarget(null); + } + }); + this.renderer.shaderPasses.forEach((shaderPass) => { + if (shaderPass.outputTarget && shaderPass.outputTarget.uuid === this.uuid) { + shaderPass.outputTarget = null; + shaderPass.setOutputTarget(null); + } + }); + this.removeFromScene(); + this.renderPass?.destroy(); + this.renderTexture?.destroy(); + } +} +_autoRender = new WeakMap(); + +export { RenderTarget }; +//# sourceMappingURL=RenderTarget.mjs.map diff --git a/dist/esm/core/renderPasses/RenderTarget.mjs.map b/dist/esm/core/renderPasses/RenderTarget.mjs.map new file mode 100644 index 000000000..32ad0e6d5 --- /dev/null +++ b/dist/esm/core/renderPasses/RenderTarget.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"RenderTarget.mjs","sources":["../../../../src/core/renderPasses/RenderTarget.ts"],"sourcesContent":["import { isRenderer, Renderer } from '../renderers/utils'\nimport { RenderPass, RenderPassParams } from './RenderPass'\nimport { RenderTexture } from '../textures/RenderTexture'\nimport { generateUUID } from '../../utils/utils'\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\n\n/**\n * Parameters used to create a {@link RenderTarget}\n */\nexport interface RenderTargetParams extends RenderPassParams {\n /** Whether we should add this {@link RenderTarget} to our {@link core/scenes/Scene.Scene | Scene} to let it handle the rendering process automatically */\n autoRender?: boolean\n}\n\n/**\n * Used to draw to {@link RenderPass#viewTextures | RenderPass view textures} (and eventually {@link RenderPass#depthTexture | depth texture}) instead of directly to screen.\n *\n * The meshes assigned to a {@link RenderTarget} will be drawn before the other objects in the {@link core/scenes/Scene.Scene | Scene} rendering loop.\n *\n * Can also be assigned as ShaderPass {@link core/renderPasses/ShaderPass.ShaderPass#inputTarget | input} or {@link core/renderPasses/ShaderPass.ShaderPass#outputTarget | output} targets.\n *\n * If the {@link RenderPass} created handle color attachments, then a {@link RenderTarget#renderTexture | RenderTexture} will be created to update and/or resolve the content of the current view. This {@link RenderTarget#renderTexture | RenderTexture} could therefore usually be used to access the current content of this {@link RenderTarget}.\n *\n * @example\n * ```javascript\n * // set our main GPUCurtains instance\n * const gpuCurtains = new GPUCurtains({\n * container: '#canvas' // selector of our WebGPU canvas container\n * })\n *\n * // set the GPU device\n * // note this is asynchronous\n * await gpuCurtains.setDevice()\n *\n * const outputTarget = new RenderTarget(gpuCurtains, {\n * label: 'My render target',\n * })\n * ```\n */\nexport class RenderTarget {\n /** {@link Renderer} used by this {@link RenderTarget} */\n renderer: Renderer\n /** The type of the {@link RenderTarget} */\n type: string\n /** The universal unique id of this {@link RenderTarget} */\n readonly uuid: string\n\n /** Options used to create this {@link RenderTarget} */\n options: RenderTargetParams\n\n /** {@link RenderPass} used by this {@link RenderTarget} */\n renderPass: RenderPass\n /** {@link RenderTexture} that will be resolved by the {@link renderPass} when {@link RenderPass#updateView | setting the current texture} */\n renderTexture?: RenderTexture\n\n /** Whether we should add this {@link RenderTarget} to our {@link core/scenes/Scene.Scene | Scene} to let it handle the rendering process automatically */\n #autoRender = true\n\n /**\n * RenderTarget constructor\n * @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link RenderTarget}\n * @param parameters - {@link RenderTargetParams | parameters} use to create this {@link RenderTarget}\n */\n constructor(renderer: Renderer | GPUCurtains, parameters = {} as RenderTargetParams) {\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\n\n isRenderer(renderer, 'RenderTarget')\n\n this.type = 'RenderTarget'\n this.renderer = renderer\n this.uuid = generateUUID()\n\n const { label, targetFormat, depthTexture, autoRender, ...renderPassParams } = parameters\n\n this.options = {\n label,\n ...renderPassParams,\n ...(depthTexture && { depthTexture }),\n targetFormat: targetFormat ?? this.renderer.options.preferredFormat,\n autoRender: autoRender === undefined ? true : autoRender,\n } as RenderTargetParams\n\n if (autoRender !== undefined) {\n this.#autoRender = autoRender\n }\n\n this.renderPass = new RenderPass(this.renderer, {\n label: this.options.label ? `${this.options.label} Render Pass` : 'Render Target Render Pass',\n targetFormat: this.options.targetFormat,\n depthTexture: this.options.depthTexture ?? this.renderer.renderPass.depthTexture, // reuse renderer depth texture for every pass\n ...renderPassParams,\n })\n\n if (renderPassParams.useColorAttachments !== false) {\n // this is the texture that will be resolved when setting the current render pass texture\n this.renderTexture = new RenderTexture(this.renderer, {\n label: this.options.label ? `${this.options.label} Render Texture` : 'Render Target render texture',\n name: 'renderTexture',\n format: this.options.targetFormat,\n ...(this.options.qualityRatio !== undefined && { qualityRatio: this.options.qualityRatio }),\n })\n }\n\n this.addToScene()\n }\n\n /**\n * Add the {@link RenderTarget} to the renderer and the {@link core/scenes/Scene.Scene | Scene}\n */\n addToScene() {\n this.renderer.renderTargets.push(this)\n\n if (this.#autoRender) {\n this.renderer.scene.addRenderTarget(this)\n }\n }\n\n /**\n * Remove the {@link RenderTarget} from the renderer and the {@link core/scenes/Scene.Scene | Scene}\n */\n removeFromScene() {\n if (this.#autoRender) {\n this.renderer.scene.removeRenderTarget(this)\n }\n\n this.renderer.renderTargets = this.renderer.renderTargets.filter((renderTarget) => renderTarget.uuid !== this.uuid)\n }\n\n /**\n * Resize our {@link renderPass}\n */\n resize() {\n // reset the newly created depth texture\n this.renderPass.options.depthTexture.texture = this.options.depthTexture\n ? this.options.depthTexture.texture\n : this.renderer.renderPass.depthTexture.texture\n\n this.renderPass?.resize()\n }\n\n /**\n * Remove our {@link RenderTarget}. Alias of {@link RenderTarget#destroy}\n */\n remove() {\n this.destroy()\n }\n\n /**\n * Destroy our {@link RenderTarget}\n */\n destroy() {\n // release mesh struct\n this.renderer.meshes.forEach((mesh) => {\n if (mesh.outputTarget && mesh.outputTarget.uuid === this.uuid) {\n mesh.setOutputTarget(null)\n }\n })\n\n // release shader passes struct\n this.renderer.shaderPasses.forEach((shaderPass) => {\n if (shaderPass.outputTarget && shaderPass.outputTarget.uuid === this.uuid) {\n // force render target to null before removing / re-adding to scene\n shaderPass.outputTarget = null\n shaderPass.setOutputTarget(null)\n }\n })\n\n // remove from scene and renderer array\n this.removeFromScene()\n\n this.renderPass?.destroy()\n this.renderTexture?.destroy()\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/renderPasses/ShaderPass.mjs b/dist/esm/core/renderPasses/ShaderPass.mjs new file mode 100644 index 000000000..ee6f1f83b --- /dev/null +++ b/dist/esm/core/renderPasses/ShaderPass.mjs @@ -0,0 +1,101 @@ +import { FullscreenPlane } from '../meshes/FullscreenPlane.mjs'; +import { isRenderer } from '../renderers/utils.mjs'; +import default_pass_fsWGSl from '../shaders/chunks/default_pass_fs.wgsl.mjs'; +import { throwWarning } from '../../utils/utils.mjs'; + +class ShaderPass extends FullscreenPlane { + /** + * ShaderPass constructor + * @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link ShaderPass} + * @param parameters - {@link ShaderPassParams | parameters} use to create this {@link ShaderPass} + */ + constructor(renderer, parameters = {}) { + renderer = renderer && renderer.renderer || renderer; + isRenderer(renderer, parameters.label ? parameters.label + " ShaderPass" : "ShaderPass"); + parameters.transparent = true; + parameters.label = parameters.label ?? "ShaderPass " + renderer.shaderPasses?.length; + parameters.sampleCount = !!parameters.sampleCount ? parameters.sampleCount : renderer && renderer.postProcessingPass ? renderer && renderer.postProcessingPass.options.sampleCount : 1; + if (!parameters.shaders) { + parameters.shaders = {}; + } + if (!parameters.shaders.fragment) { + parameters.shaders.fragment = { + code: default_pass_fsWGSl, + entryPoint: "main" + }; + } + parameters.depth = false; + super(renderer, parameters); + if (parameters.inputTarget) { + this.setInputTarget(parameters.inputTarget); + } + if (this.outputTarget) { + this.setRenderingOptionsForRenderPass(this.outputTarget.renderPass); + } + this.type = "ShaderPass"; + this.createRenderTexture({ + label: parameters.label ? `${parameters.label} render texture` : "Shader pass render texture", + name: "renderTexture", + fromTexture: this.inputTarget ? this.inputTarget.renderTexture : null, + ...this.outputTarget && this.outputTarget.options.qualityRatio && { qualityRatio: this.outputTarget.options.qualityRatio } + }); + } + /** + * Get our main {@link RenderTexture}, the one that contains our post processed content + * @readonly + */ + get renderTexture() { + return this.renderTextures.find((texture) => texture.options.name === "renderTexture"); + } + // TODO + /** + * Assign or remove a {@link RenderTarget} to this {@link ShaderPass} + * Since this manipulates the {@link core/scenes/Scene.Scene | Scene} stacks, it can be used to remove a RenderTarget as well. + * Also copy or remove the {@link RenderTarget#renderTexture | render target render texture} into the {@link ShaderPass} {@link renderTexture} + * @param inputTarget - the {@link RenderTarget} to assign or null if we want to remove the current {@link RenderTarget} + */ + setInputTarget(inputTarget) { + if (inputTarget && inputTarget.type !== "RenderTarget") { + throwWarning(`${this.options.label ?? this.type}: inputTarget is not a RenderTarget: ${inputTarget}`); + return; + } + this.removeFromScene(); + this.inputTarget = inputTarget; + this.addToScene(); + if (this.renderTexture) { + if (inputTarget) { + this.renderTexture.copy(this.inputTarget.renderTexture); + } else { + this.renderTexture.options.fromTexture = null; + this.renderTexture.createTexture(); + } + } + } + /** + * Add the {@link ShaderPass} to the renderer and the {@link core/scenes/Scene.Scene | Scene} + */ + addToScene() { + this.renderer.shaderPasses.push(this); + this.setRenderingOptionsForRenderPass( + this.outputTarget ? this.outputTarget.renderPass : this.renderer.postProcessingPass + ); + if (this.autoRender) { + this.renderer.scene.addShaderPass(this); + } + } + /** + * Remove the {@link ShaderPass} from the renderer and the {@link core/scenes/Scene.Scene | Scene} + */ + removeFromScene() { + if (this.outputTarget) { + this.outputTarget.destroy(); + } + if (this.autoRender) { + this.renderer.scene.removeShaderPass(this); + } + this.renderer.shaderPasses = this.renderer.shaderPasses.filter((sP) => sP.uuid !== this.uuid); + } +} + +export { ShaderPass }; +//# sourceMappingURL=ShaderPass.mjs.map diff --git a/dist/esm/core/renderPasses/ShaderPass.mjs.map b/dist/esm/core/renderPasses/ShaderPass.mjs.map new file mode 100644 index 000000000..15fd2b9e9 --- /dev/null +++ b/dist/esm/core/renderPasses/ShaderPass.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"ShaderPass.mjs","sources":["../../../../src/core/renderPasses/ShaderPass.ts"],"sourcesContent":["import { FullscreenPlane } from '../meshes/FullscreenPlane'\nimport { isRenderer, Renderer } from '../renderers/utils'\nimport { RenderTarget } from './RenderTarget'\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\nimport { MeshBaseRenderParams } from '../meshes/mixins/MeshBaseMixin'\nimport { RenderTexture } from '../textures/RenderTexture'\nimport default_pass_fsWGSl from '../shaders/chunks/default_pass_fs.wgsl'\nimport { throwWarning } from '../../utils/utils'\n\n/**\n * Parameters used to create a {@link ShaderPass}\n */\nexport interface ShaderPassParams extends MeshBaseRenderParams {\n /** Optional input {@link RenderTarget} to assign to the {@link ShaderPass}. Used to automatically copy the content of the given {@link RenderTarget} texture into the {@link ShaderPass#renderTexture | ShaderPass renderTexture}. */\n inputTarget?: RenderTarget\n}\n\n/**\n * Used to apply postprocessing, i.e. draw meshes to a {@link RenderTexture} and then draw a {@link FullscreenPlane} using that texture as an input.\n *\n * A ShaderPass could either post process the whole scene or just a bunch of meshes using a specific {@link RenderTarget}.\n *\n * @example\n * ```javascript\n * // set our main GPUCurtains instance\n * const gpuCurtains = new GPUCurtains({\n * container: '#canvas' // selector of our WebGPU canvas container\n * })\n *\n * // set the GPU device\n * // note this is asynchronous\n * await gpuCurtains.setDevice()\n *\n * // create a ShaderPass\n * const shaderPass = new ShaderPass(gpuCurtain, {\n * label: 'My shader pass',\n * shaders: {\n * fragment: {\n * code: shaderPassCode, // assume it is a valid WGSL fragment shader\n * },\n * },\n * })\n * ```\n */\nexport class ShaderPass extends FullscreenPlane {\n /** Optional input {@link RenderTarget} to assign to the {@link ShaderPass}. Used to automatically copy the content of the given {@link RenderTarget} texture into the {@link ShaderPass#renderTexture | ShaderPass renderTexture}. */\n inputTarget: RenderTarget | undefined\n\n /**\n * ShaderPass constructor\n * @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link ShaderPass}\n * @param parameters - {@link ShaderPassParams | parameters} use to create this {@link ShaderPass}\n */\n constructor(renderer: Renderer | GPUCurtains, parameters: ShaderPassParams = {}) {\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\n\n isRenderer(renderer, parameters.label ? parameters.label + ' ShaderPass' : 'ShaderPass')\n\n // force transparency to allow for correct blending between successive passes\n parameters.transparent = true\n parameters.label = parameters.label ?? 'ShaderPass ' + renderer.shaderPasses?.length\n\n // set default sample count to post processing render pass\n parameters.sampleCount = !!parameters.sampleCount\n ? parameters.sampleCount\n : renderer && renderer.postProcessingPass\n ? renderer && renderer.postProcessingPass.options.sampleCount\n : 1\n\n if (!parameters.shaders) {\n parameters.shaders = {}\n }\n\n if (!parameters.shaders.fragment) {\n parameters.shaders.fragment = {\n code: default_pass_fsWGSl,\n entryPoint: 'main',\n }\n }\n\n // force the postprocessing passes to not use depth\n parameters.depth = false\n\n super(renderer, parameters)\n\n if (parameters.inputTarget) {\n this.setInputTarget(parameters.inputTarget)\n }\n\n if (this.outputTarget) {\n // patch to match outputTarget if needed\n this.setRenderingOptionsForRenderPass(this.outputTarget.renderPass)\n }\n\n this.type = 'ShaderPass'\n\n this.createRenderTexture({\n label: parameters.label ? `${parameters.label} render texture` : 'Shader pass render texture',\n name: 'renderTexture',\n fromTexture: this.inputTarget ? this.inputTarget.renderTexture : null,\n ...(this.outputTarget &&\n this.outputTarget.options.qualityRatio && { qualityRatio: this.outputTarget.options.qualityRatio }),\n })\n }\n\n /**\n * Get our main {@link RenderTexture}, the one that contains our post processed content\n * @readonly\n */\n get renderTexture(): RenderTexture | undefined {\n return this.renderTextures.find((texture) => texture.options.name === 'renderTexture')\n }\n\n // TODO\n /**\n * Assign or remove a {@link RenderTarget} to this {@link ShaderPass}\n * Since this manipulates the {@link core/scenes/Scene.Scene | Scene} stacks, it can be used to remove a RenderTarget as well.\n * Also copy or remove the {@link RenderTarget#renderTexture | render target render texture} into the {@link ShaderPass} {@link renderTexture}\n * @param inputTarget - the {@link RenderTarget} to assign or null if we want to remove the current {@link RenderTarget}\n */\n setInputTarget(inputTarget: RenderTarget | null) {\n if (inputTarget && inputTarget.type !== 'RenderTarget') {\n throwWarning(`${this.options.label ?? this.type}: inputTarget is not a RenderTarget: ${inputTarget}`)\n return\n }\n\n // ensure the mesh is in the correct scene stack\n this.removeFromScene()\n this.inputTarget = inputTarget\n this.addToScene()\n\n // it might not have been created yet\n if (this.renderTexture) {\n if (inputTarget) {\n this.renderTexture.copy(this.inputTarget.renderTexture)\n } else {\n this.renderTexture.options.fromTexture = null\n this.renderTexture.createTexture()\n }\n }\n }\n\n /**\n * Add the {@link ShaderPass} to the renderer and the {@link core/scenes/Scene.Scene | Scene}\n */\n addToScene() {\n this.renderer.shaderPasses.push(this)\n\n this.setRenderingOptionsForRenderPass(\n this.outputTarget ? this.outputTarget.renderPass : this.renderer.postProcessingPass\n )\n\n if (this.autoRender) {\n this.renderer.scene.addShaderPass(this)\n }\n }\n\n /**\n * Remove the {@link ShaderPass} from the renderer and the {@link core/scenes/Scene.Scene | Scene}\n */\n removeFromScene() {\n if (this.outputTarget) {\n this.outputTarget.destroy()\n }\n\n if (this.autoRender) {\n this.renderer.scene.removeShaderPass(this)\n }\n\n this.renderer.shaderPasses = this.renderer.shaderPasses.filter((sP) => sP.uuid !== this.uuid)\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/renderers/GPUCameraRenderer.mjs b/dist/esm/core/renderers/GPUCameraRenderer.mjs new file mode 100644 index 000000000..02b05de2d --- /dev/null +++ b/dist/esm/core/renderers/GPUCameraRenderer.mjs @@ -0,0 +1,222 @@ +import { GPURenderer } from './GPURenderer.mjs'; +import { Camera } from '../camera/Camera.mjs'; +import { BufferBinding } from '../bindings/BufferBinding.mjs'; +import { BindGroup } from '../bindGroups/BindGroup.mjs'; +import { Vec3 } from '../../math/Vec3.mjs'; + +class GPUCameraRenderer extends GPURenderer { + /** + * GPUCameraRenderer constructor + * @param parameters - {@link GPUCameraRendererParams | parameters} used to create this {@link GPUCameraRenderer} + */ + constructor({ + deviceManager, + container, + pixelRatio = 1, + preferredFormat, + alphaMode = "premultiplied", + renderPass, + camera = {} + }) { + super({ + deviceManager, + container, + pixelRatio, + preferredFormat, + alphaMode, + renderPass + }); + this.type = "GPUCameraRenderer"; + camera = { ...{ fov: 50, near: 0.1, far: 150 }, ...camera }; + this.options = { + ...this.options, + camera + }; + this.setCamera(camera); + this.setCameraBindGroupAndBinding(); + } + /** + * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} is lost. + * Reset all our samplers, force all our scene objects and camera bind group to lose context. + */ + loseContext() { + super.loseContext(); + this.cameraBindGroup.loseContext(); + } + /** + * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} should be restored. + * Configure the context again, resize the {@link core/renderPasses/RenderTarget.RenderTarget | render targets} and {@link core/textures/RenderTexture.RenderTexture | render textures}, restore our {@link renderedObjects | rendered objects} context, re-write our {@link cameraBufferBinding | camera buffer binding}. + * @async + */ + async restoreContext() { + this.cameraBufferBinding.shouldUpdate = true; + return super.restoreContext(); + } + /** + * Set the {@link camera} + * @param cameraParameters - {@link CameraBasePerspectiveOptions | parameters} used to create the {@link camera} + */ + setCamera(cameraParameters) { + const width = this.boundingRect ? this.boundingRect.width : 1; + const height = this.boundingRect ? this.boundingRect.height : 1; + this.camera = new Camera({ + fov: cameraParameters.fov, + near: cameraParameters.near, + far: cameraParameters.far, + width, + height, + pixelRatio: this.pixelRatio, + onMatricesChanged: () => { + this.onCameraMatricesChanged(); + } + }); + } + /** + * Update the {@link ProjectedMesh | projected meshes} sizes and positions when the {@link camera} {@link Camera#position | position} changes + */ + onCameraMatricesChanged() { + this.updateCameraBindings(); + this.meshes.forEach((mesh) => { + if ("modelViewMatrix" in mesh) { + mesh.shouldUpdateMatrixStack(); + } + }); + } + /** + * Set the {@link cameraBufferBinding | camera buffer binding} and {@link cameraBindGroup | camera bind group} + */ + setCameraBindGroupAndBinding() { + this.cameraBufferBinding = new BufferBinding({ + label: "Camera", + name: "camera", + visibility: "vertex", + struct: { + model: { + // camera model matrix + name: "model", + type: "mat4x4f", + value: this.camera.modelMatrix + }, + view: { + // camera view matrix + name: "view", + type: "mat4x4f", + value: this.camera.viewMatrix + }, + projection: { + // camera projection matrix + name: "projection", + type: "mat4x4f", + value: this.camera.projectionMatrix + } + } + }); + this.cameraBindGroup = new BindGroup(this, { + label: "Camera Uniform bind group", + bindings: [this.cameraBufferBinding] + }); + } + /** + * Create the {@link cameraBindGroup | camera bind group} buffers + */ + setCameraBindGroup() { + if (this.cameraBindGroup && this.cameraBindGroup.shouldCreateBindGroup) { + this.cameraBindGroup.setIndex(0); + this.cameraBindGroup.createBindGroup(); + } + } + /** + * Tell our {@link cameraBufferBinding | camera buffer binding} that we should update its struct + */ + updateCameraBindings() { + this.cameraBufferBinding?.shouldUpdateBinding("model"); + this.cameraBufferBinding?.shouldUpdateBinding("view"); + this.cameraBufferBinding?.shouldUpdateBinding("projection"); + } + /** + * Get all objects ({@link RenderedMesh | rendered meshes} or {@link core/computePasses/ComputePass.ComputePass | compute passes}) using a given {@link AllowedBindGroups | bind group}, including {@link cameraBindGroup | camera bind group}. + * Useful to know if a resource is used by multiple objects and if it is safe to destroy it or not. + * @param bindGroup - {@link AllowedBindGroups | bind group} to check + */ + getObjectsByBindGroup(bindGroup) { + return this.deviceRenderedObjects.filter((object) => { + return [ + ...object.material.bindGroups, + ...object.material.inputsBindGroups, + ...object.material.clonedBindGroups, + this.cameraBindGroup + ].some((bG) => bG.uuid === bindGroup.uuid); + }); + } + /** + * Set our {@link camera} perspective matrix new parameters (fov, near plane and far plane) + * @param parameters - {@link CameraBasePerspectiveOptions | parameters} to use for the perspective + */ + setPerspective({ fov, near, far } = {}) { + this.camera?.setPerspective({ + fov, + near, + far, + width: this.boundingRect.width, + height: this.boundingRect.height, + pixelRatio: this.pixelRatio + }); + } + /** + * Set our {@link camera} {@link Camera#position | position} + * @param position - new {@link Camera#position | position} + */ + setCameraPosition(position = new Vec3(0, 0, 1)) { + this.camera.position.copy(position); + } + /** + * Call our {@link GPURenderer#onResize | GPURenderer onResize method} and resize our {@link camera} as well + */ + onResize() { + super.onResize(); + this.setPerspective(); + this.updateCameraBindings(); + } + /* RENDER */ + /** + * Update the camera model matrix, check if the {@link cameraBindGroup | camera bind group} should be created, create it if needed and then update it + */ + updateCamera() { + this.camera?.updateMatrixStack(); + this.setCameraBindGroup(); + this.cameraBindGroup?.update(); + } + /** + * Render a single {@link RenderedMesh | mesh} (binds the {@link cameraBindGroup | camera bind group} if needed) + * @param commandEncoder - current {@link GPUCommandEncoder} + * @param mesh - {@link RenderedMesh | mesh} to render + */ + renderSingleMesh(commandEncoder, mesh) { + const pass = commandEncoder.beginRenderPass(this.renderPass.descriptor); + if (mesh.material.options.rendering.useProjection) { + pass.setBindGroup(this.cameraBindGroup.index, this.cameraBindGroup.bindGroup); + } + mesh.render(pass); + pass.end(); + } + /** + * {@link updateCamera | Update the camera} and then call our {@link GPURenderer#render | GPURenderer render method} + * @param commandEncoder - current {@link GPUCommandEncoder} + */ + render(commandEncoder) { + if (!this.ready) + return; + this.updateCamera(); + super.render(commandEncoder); + } + /** + * Destroy our {@link GPUCameraRenderer} + */ + destroy() { + this.cameraBindGroup?.destroy(); + super.destroy(); + } +} + +export { GPUCameraRenderer }; +//# sourceMappingURL=GPUCameraRenderer.mjs.map diff --git a/dist/esm/core/renderers/GPUCameraRenderer.mjs.map b/dist/esm/core/renderers/GPUCameraRenderer.mjs.map new file mode 100644 index 000000000..0a58588c0 --- /dev/null +++ b/dist/esm/core/renderers/GPUCameraRenderer.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"GPUCameraRenderer.mjs","sources":["../../../../src/core/renderers/GPUCameraRenderer.ts"],"sourcesContent":["import { GPURenderer, GPURendererParams, ProjectedMesh, RenderedMesh, SceneObject } from './GPURenderer'\r\nimport { Camera, CameraBasePerspectiveOptions } from '../camera/Camera'\r\nimport { BufferBinding } from '../bindings/BufferBinding'\r\nimport { BindGroup } from '../bindGroups/BindGroup'\r\nimport { Vec3 } from '../../math/Vec3'\r\nimport { AllowedBindGroups } from '../../types/BindGroups'\r\n\r\n/**\r\n * Parameters used to create a {@link GPUCameraRenderer}\r\n */\r\nexport interface GPUCameraRendererParams extends GPURendererParams {\r\n /** An object defining {@link CameraBasePerspectiveOptions | camera perspective parameters} */\r\n camera: CameraBasePerspectiveOptions\r\n}\r\n\r\n/**\r\n * This renderer also creates a {@link Camera} and its associated {@link cameraBufferBinding | binding} and {@link cameraBindGroup | bind group}.
\r\n * Can be safely used to render compute passes and meshes if they do not need to be tied to the DOM.\r\n *\r\n * @example\r\n * ```javascript\r\n * // first, we need a WebGPU device, that's what GPUDeviceManager is for\r\n * const gpuDeviceManager = new GPUDeviceManager({\r\n * label: 'Custom device manager',\r\n * })\r\n *\r\n * // we need to wait for the WebGPU device to be created\r\n * await gpuDeviceManager.init()\r\n *\r\n * // then we can create a camera renderer\r\n * const gpuCameraRenderer = new GPUCameraRenderer({\r\n * deviceManager: gpuDeviceManager, // we need the WebGPU device to create the renderer context\r\n * container: document.querySelector('#canvas'),\r\n * })\r\n * ```\r\n */\r\nexport class GPUCameraRenderer extends GPURenderer {\r\n /** {@link Camera} used by this {@link GPUCameraRenderer} */\r\n camera: Camera\r\n /** {@link BufferBinding | binding} handling the {@link camera} matrices */\r\n cameraBufferBinding: BufferBinding\r\n /** {@link BindGroup | bind group} handling the {@link cameraBufferBinding | camera buffer binding} */\r\n cameraBindGroup: BindGroup\r\n\r\n /** Options used to create this {@link GPUCameraRenderer} */\r\n options: GPUCameraRendererParams\r\n\r\n /**\r\n * GPUCameraRenderer constructor\r\n * @param parameters - {@link GPUCameraRendererParams | parameters} used to create this {@link GPUCameraRenderer}\r\n */\r\n constructor({\r\n deviceManager,\r\n container,\r\n pixelRatio = 1,\r\n preferredFormat,\r\n alphaMode = 'premultiplied',\r\n renderPass,\r\n camera = {},\r\n }: GPUCameraRendererParams) {\r\n super({\r\n deviceManager,\r\n container,\r\n pixelRatio,\r\n preferredFormat,\r\n alphaMode,\r\n renderPass,\r\n })\r\n\r\n this.type = 'GPUCameraRenderer'\r\n\r\n camera = { ...{ fov: 50, near: 0.1, far: 150 }, ...camera }\r\n\r\n this.options = {\r\n ...this.options,\r\n camera,\r\n }\r\n\r\n this.setCamera(camera)\r\n\r\n this.setCameraBindGroupAndBinding()\r\n }\r\n\r\n /**\r\n * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} is lost.\r\n * Reset all our samplers, force all our scene objects and camera bind group to lose context.\r\n */\r\n loseContext() {\r\n super.loseContext()\r\n // lose camera bind group context as well\r\n this.cameraBindGroup.loseContext()\r\n }\r\n\r\n /**\r\n * Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} should be restored.\r\n * Configure the context again, resize the {@link core/renderPasses/RenderTarget.RenderTarget | render targets} and {@link core/textures/RenderTexture.RenderTexture | render textures}, restore our {@link renderedObjects | rendered objects} context, re-write our {@link cameraBufferBinding | camera buffer binding}.\r\n * @async\r\n */\r\n async restoreContext(): Promise {\r\n this.cameraBufferBinding.shouldUpdate = true\r\n return super.restoreContext()\r\n }\r\n\r\n /**\r\n * Set the {@link camera}\r\n * @param cameraParameters - {@link CameraBasePerspectiveOptions | parameters} used to create the {@link camera}\r\n */\r\n setCamera(cameraParameters: CameraBasePerspectiveOptions) {\r\n const width = this.boundingRect ? this.boundingRect.width : 1\r\n const height = this.boundingRect ? this.boundingRect.height : 1\r\n\r\n this.camera = new Camera({\r\n fov: cameraParameters.fov,\r\n near: cameraParameters.near,\r\n far: cameraParameters.far,\r\n width,\r\n height,\r\n pixelRatio: this.pixelRatio,\r\n onMatricesChanged: () => {\r\n this.onCameraMatricesChanged()\r\n },\r\n })\r\n }\r\n\r\n /**\r\n * Update the {@link ProjectedMesh | projected meshes} sizes and positions when the {@link camera} {@link Camera#position | position} changes\r\n */\r\n onCameraMatricesChanged() {\r\n this.updateCameraBindings()\r\n\r\n this.meshes.forEach((mesh) => {\r\n if ('modelViewMatrix' in mesh) {\r\n mesh.shouldUpdateMatrixStack()\r\n }\r\n })\r\n }\r\n\r\n /**\r\n * Set the {@link cameraBufferBinding | camera buffer binding} and {@link cameraBindGroup | camera bind group}\r\n */\r\n setCameraBindGroupAndBinding() {\r\n this.cameraBufferBinding = new BufferBinding({\r\n label: 'Camera',\r\n name: 'camera',\r\n visibility: 'vertex',\r\n struct: {\r\n model: {\r\n // camera model matrix\r\n name: 'model',\r\n type: 'mat4x4f',\r\n value: this.camera.modelMatrix,\r\n },\r\n view: {\r\n // camera view matrix\r\n name: 'view',\r\n type: 'mat4x4f',\r\n value: this.camera.viewMatrix,\r\n },\r\n projection: {\r\n // camera projection matrix\r\n name: 'projection',\r\n type: 'mat4x4f',\r\n value: this.camera.projectionMatrix,\r\n },\r\n },\r\n })\r\n\r\n // now initialize bind group\r\n this.cameraBindGroup = new BindGroup(this, {\r\n label: 'Camera Uniform bind group',\r\n bindings: [this.cameraBufferBinding],\r\n })\r\n }\r\n\r\n /**\r\n * Create the {@link cameraBindGroup | camera bind group} buffers\r\n */\r\n setCameraBindGroup() {\r\n if (this.cameraBindGroup && this.cameraBindGroup.shouldCreateBindGroup) {\r\n this.cameraBindGroup.setIndex(0)\r\n this.cameraBindGroup.createBindGroup()\r\n }\r\n }\r\n\r\n /**\r\n * Tell our {@link cameraBufferBinding | camera buffer binding} that we should update its struct\r\n */\r\n updateCameraBindings() {\r\n this.cameraBufferBinding?.shouldUpdateBinding('model')\r\n this.cameraBufferBinding?.shouldUpdateBinding('view')\r\n this.cameraBufferBinding?.shouldUpdateBinding('projection')\r\n }\r\n\r\n /**\r\n * Get all objects ({@link RenderedMesh | rendered meshes} or {@link core/computePasses/ComputePass.ComputePass | compute passes}) using a given {@link AllowedBindGroups | bind group}, including {@link cameraBindGroup | camera bind group}.\r\n * Useful to know if a resource is used by multiple objects and if it is safe to destroy it or not.\r\n * @param bindGroup - {@link AllowedBindGroups | bind group} to check\r\n */\r\n getObjectsByBindGroup(bindGroup: AllowedBindGroups): undefined | SceneObject[] {\r\n return this.deviceRenderedObjects.filter((object) => {\r\n return [\r\n ...object.material.bindGroups,\r\n ...object.material.inputsBindGroups,\r\n ...object.material.clonedBindGroups,\r\n this.cameraBindGroup,\r\n ].some((bG) => bG.uuid === bindGroup.uuid)\r\n })\r\n }\r\n\r\n /**\r\n * Set our {@link camera} perspective matrix new parameters (fov, near plane and far plane)\r\n * @param parameters - {@link CameraBasePerspectiveOptions | parameters} to use for the perspective\r\n */\r\n setPerspective({ fov, near, far }: CameraBasePerspectiveOptions = {}) {\r\n this.camera?.setPerspective({\r\n fov,\r\n near,\r\n far,\r\n width: this.boundingRect.width,\r\n height: this.boundingRect.height,\r\n pixelRatio: this.pixelRatio,\r\n })\r\n }\r\n\r\n /**\r\n * Set our {@link camera} {@link Camera#position | position}\r\n * @param position - new {@link Camera#position | position}\r\n */\r\n setCameraPosition(position: Vec3 = new Vec3(0, 0, 1)) {\r\n this.camera.position.copy(position)\r\n }\r\n\r\n /**\r\n * Call our {@link GPURenderer#onResize | GPURenderer onResize method} and resize our {@link camera} as well\r\n */\r\n onResize() {\r\n super.onResize()\r\n this.setPerspective()\r\n this.updateCameraBindings()\r\n }\r\n\r\n /* RENDER */\r\n\r\n /**\r\n * Update the camera model matrix, check if the {@link cameraBindGroup | camera bind group} should be created, create it if needed and then update it\r\n */\r\n updateCamera() {\r\n this.camera?.updateMatrixStack()\r\n this.setCameraBindGroup()\r\n this.cameraBindGroup?.update()\r\n }\r\n\r\n /**\r\n * Render a single {@link RenderedMesh | mesh} (binds the {@link cameraBindGroup | camera bind group} if needed)\r\n * @param commandEncoder - current {@link GPUCommandEncoder}\r\n * @param mesh - {@link RenderedMesh | mesh} to render\r\n */\r\n renderSingleMesh(commandEncoder: GPUCommandEncoder, mesh: RenderedMesh) {\r\n const pass = commandEncoder.beginRenderPass(this.renderPass.descriptor)\r\n\r\n // bind camera if needed\r\n if (mesh.material.options.rendering.useProjection) {\r\n pass.setBindGroup(this.cameraBindGroup.index, this.cameraBindGroup.bindGroup)\r\n }\r\n\r\n mesh.render(pass)\r\n pass.end()\r\n }\r\n\r\n /**\r\n * {@link updateCamera | Update the camera} and then call our {@link GPURenderer#render | GPURenderer render method}\r\n * @param commandEncoder - current {@link GPUCommandEncoder}\r\n */\r\n render(commandEncoder: GPUCommandEncoder) {\r\n if (!this.ready) return\r\n\r\n this.updateCamera()\r\n super.render(commandEncoder)\r\n }\r\n\r\n /**\r\n * Destroy our {@link GPUCameraRenderer}\r\n */\r\n destroy() {\r\n this.cameraBindGroup?.destroy()\r\n super.destroy()\r\n }\r\n}\r\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/renderers/GPUDeviceManager.mjs b/dist/esm/core/renderers/GPUDeviceManager.mjs new file mode 100644 index 000000000..6d3a38ad8 --- /dev/null +++ b/dist/esm/core/renderers/GPUDeviceManager.mjs @@ -0,0 +1,294 @@ +import { throwError, throwWarning } from '../../utils/utils.mjs'; +import { generateMips } from './utils.mjs'; +import { PipelineManager } from '../pipelines/PipelineManager.mjs'; + +class GPUDeviceManager { + /** + * GPUDeviceManager constructor + * @param parameters - {@link GPUDeviceManagerParams | parameters} used to create this {@link GPUDeviceManager} + */ + constructor({ + label, + production = false, + onError = () => { + }, + onDeviceLost = (info) => { + } + }) { + this.index = 0; + this.label = label ?? "GPUDeviceManager instance"; + this.production = production; + this.ready = false; + this.onError = onError; + this.onDeviceLost = onDeviceLost; + this.gpu = navigator.gpu; + this.setPipelineManager(); + this.setDeviceObjects(); + } + /** + * Set our {@link adapter} and {@link device} if possible + */ + async setAdapterAndDevice() { + await this.setAdapter(); + await this.setDevice(); + } + /** + * Set up our {@link adapter} and {@link device} and all the already created {@link renderers} contexts + */ + async init() { + await this.setAdapterAndDevice(); + if (this.device) { + this.renderers.forEach((renderer) => { + if (!renderer.context) { + renderer.setContext(); + } + }); + } + } + /** + * Set our {@link adapter} if possible. + * The adapter represents a specific GPU. Some devices have multiple GPUs. + * @async + */ + async setAdapter() { + if (!this.gpu) { + this.onError(); + throwError("GPURenderer: WebGPU is not supported on your browser/OS. No 'gpu' object in 'navigator'."); + } + try { + this.adapter = await this.gpu?.requestAdapter(); + this.adapter?.requestAdapterInfo().then((infos) => { + this.adapterInfos = infos; + }); + } catch (error) { + this.onError(); + throwError("GPUDeviceManager: WebGPU is not supported on your browser/OS. 'requestAdapter' failed."); + } + } + /** + * Set our {@link device} + * @async + */ + async setDevice() { + try { + this.device = await this.adapter?.requestDevice({ + label: this.label + " " + this.index + }); + if (this.device) { + this.ready = true; + this.index++; + } + } catch (error) { + this.onError(); + throwError(`${this.label}: WebGPU is not supported on your browser/OS. 'requestDevice' failed: ${error}`); + } + this.device?.lost.then((info) => { + throwWarning(`${this.label}: WebGPU device was lost: ${info.message}`); + this.loseDevice(); + if (info.reason !== "destroyed") { + this.onDeviceLost(info); + } + }); + } + /** + * Set our {@link pipelineManager | pipeline manager} + */ + setPipelineManager() { + this.pipelineManager = new PipelineManager(); + } + /** + * Called when the {@link device} is lost. + * Reset all our renderers + */ + loseDevice() { + this.ready = false; + this.samplers.forEach((sampler) => sampler.sampler = null); + this.renderers.forEach((renderer) => renderer.loseContext()); + this.buffers = []; + } + /** + * Called when the {@link device} should be restored. + * Restore all our renderers + */ + async restoreDevice() { + await this.setAdapterAndDevice(); + if (this.device) { + this.samplers.forEach((sampler) => { + const { type, ...samplerOptions } = sampler.options; + sampler.sampler = this.device.createSampler({ + label: sampler.label, + ...samplerOptions + }); + }); + this.renderers.forEach((renderer) => renderer.restoreContext()); + } + } + /** + * Set all objects arrays that we'll keep track of + */ + setDeviceObjects() { + this.renderers = []; + this.bindGroups = []; + this.buffers = []; + this.samplers = []; + this.textures = []; + this.texturesQueue = []; + } + /** + * Add a {@link Renderer} to our {@link renderers} array + * @param renderer - {@link Renderer} to add + */ + addRenderer(renderer) { + this.renderers.push(renderer); + } + /** + * Remove a {@link Renderer} from our {@link renderers} array + * @param renderer - {@link Renderer} to remove + */ + removeRenderer(renderer) { + this.renderers = this.renderers.filter((r) => r.uuid !== renderer.uuid); + } + /** + * Get all the rendered objects (i.e. compute passes, meshes, ping pong planes and shader passes) created by this {@link GPUDeviceManager} + * @readonly + */ + get deviceRenderedObjects() { + return this.renderers.map((renderer) => renderer.renderedObjects).flat(); + } + /** + * Add a {@link AllowedBindGroups | bind group} to our {@link bindGroups | bind groups array} + * @param bindGroup - {@link AllowedBindGroups | bind group} to add + */ + addBindGroup(bindGroup) { + if (!this.bindGroups.find((bG) => bG.uuid === bindGroup.uuid)) { + this.bindGroups.push(bindGroup); + } + } + /** + * Remove a {@link AllowedBindGroups | bind group} from our {@link bindGroups | bind groups array} + * @param bindGroup - {@link AllowedBindGroups | bind group} to remove + */ + removeBindGroup(bindGroup) { + this.bindGroups = this.bindGroups.filter((bG) => bG.uuid !== bindGroup.uuid); + } + /** + * Add a {@link GPUBuffer} to our our {@link buffers} array + * @param buffer - {@link GPUBuffer} to add + */ + addBuffer(buffer) { + this.buffers.push(buffer); + } + /** + * Remove a {@link GPUBuffer} from our {@link buffers} array + * @param buffer - {@link GPUBuffer} to remove + * @param [originalLabel] - original {@link GPUBuffer} label in case the buffer has been swapped and its label has changed + */ + removeBuffer(buffer, originalLabel) { + if (buffer) { + this.buffers = this.buffers.filter((b) => { + return !(b.label === (originalLabel ?? buffer.label) && b.size === buffer.size); + }); + } + } + /** + * Add a {@link Sampler} to our {@link samplers} array + * @param sampler - {@link Sampler} to add + */ + addSampler(sampler) { + this.samplers.push(sampler); + } + /** + * Remove a {@link Sampler} from our {@link samplers} array + * @param sampler - {@link Sampler} to remove + */ + removeSampler(sampler) { + this.samplers = this.samplers.filter((s) => s.uuid !== sampler.uuid); + } + /** + * Add a {@link Texture} to our {@link textures} array + * @param texture - {@link Texture} to add + */ + addTexture(texture) { + this.textures.push(texture); + } + /** + * Upload a {@link Texture#texture | texture} to the GPU + * @param texture - {@link Texture} class object with the {@link Texture#texture | texture} to upload + */ + uploadTexture(texture) { + if (texture.source) { + try { + this.device?.queue.copyExternalImageToTexture( + { + source: texture.source, + flipY: texture.options.flipY + }, + { texture: texture.texture, premultipliedAlpha: texture.options.premultipliedAlpha }, + { width: texture.size.width, height: texture.size.height } + ); + if (texture.texture.mipLevelCount > 1) { + generateMips(this.device, texture.texture); + } + this.texturesQueue.push(texture); + } catch ({ message }) { + throwError(`GPUDeviceManager: could not upload texture: ${texture.options.name} because: ${message}`); + } + } else { + this.device?.queue.writeTexture( + { texture: texture.texture }, + new Uint8Array(texture.options.placeholderColor), + { bytesPerRow: texture.size.width * 4 }, + { width: texture.size.width, height: texture.size.height } + ); + } + } + /** + * Remove a {@link Texture} from our {@link textures} array + * @param texture - {@link Texture} to remove + */ + removeTexture(texture) { + this.textures = this.textures.filter((t) => t.uuid !== texture.uuid); + } + /** + * Render everything: + * - call all our {@link renderers} {@link core/renderers/GPURenderer.GPURenderer#onBeforeCommandEncoder | onBeforeCommandEncoder} callbacks + * - create a {@link GPUCommandEncoder} + * - render all our {@link renderers} + * - submit our {@link GPUCommandBuffer} + * - upload {@link Texture#texture | textures} that do not have a parentMesh + * - empty our {@link texturesQueue} array + * - call all our {@link renderers} {@link core/renderers/GPURenderer.GPURenderer#onAfterCommandEncoder | onAfterCommandEncoder} callbacks + */ + render() { + if (!this.ready) + return; + this.renderers.forEach((renderer) => renderer.onBeforeCommandEncoder()); + const commandEncoder = this.device?.createCommandEncoder({ label: this.label + " command encoder" }); + !this.production && commandEncoder.pushDebugGroup(this.label + " command encoder: main render loop"); + this.renderers.forEach((renderer) => renderer.render(commandEncoder)); + !this.production && commandEncoder.popDebugGroup(); + const commandBuffer = commandEncoder.finish(); + this.device?.queue.submit([commandBuffer]); + this.textures.filter((texture) => !texture.parentMesh && texture.sourceLoaded && !texture.sourceUploaded).forEach((texture) => this.uploadTexture(texture)); + this.texturesQueue.forEach((texture) => { + texture.sourceUploaded = true; + }); + this.texturesQueue = []; + this.renderers.forEach((renderer) => renderer.onAfterCommandEncoder()); + } + /** + * Destroy the {@link GPUDeviceManager} and its {@link renderers} + */ + destroy() { + this.device?.destroy(); + this.device = null; + this.renderers.forEach((renderer) => renderer.destroy()); + this.bindGroups.forEach((bindGroup) => bindGroup.destroy()); + this.buffers.forEach((buffer) => buffer?.destroy()); + this.textures.forEach((texture) => texture.destroy()); + this.setDeviceObjects(); + } +} + +export { GPUDeviceManager }; +//# sourceMappingURL=GPUDeviceManager.mjs.map diff --git a/dist/esm/core/renderers/GPUDeviceManager.mjs.map b/dist/esm/core/renderers/GPUDeviceManager.mjs.map new file mode 100644 index 000000000..e29ac62ab --- /dev/null +++ b/dist/esm/core/renderers/GPUDeviceManager.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"GPUDeviceManager.mjs","sources":["../../../../src/core/renderers/GPUDeviceManager.ts"],"sourcesContent":["import { throwError, throwWarning } from '../../utils/utils'\nimport { generateMips, Renderer } from './utils'\nimport { Sampler } from '../samplers/Sampler'\nimport { PipelineManager } from '../pipelines/PipelineManager'\nimport { SceneObject } from './GPURenderer'\nimport { Texture } from '../textures/Texture'\nimport { AllowedBindGroups } from '../../types/BindGroups'\n\n/**\n * Parameters used to create a {@link GPUDeviceManager}\n */\nexport interface GPUDeviceManagerParams {\n /** The label of the {@link GPUDeviceManager}, used to create the {@link GPUDevice} for debugging purpose */\n label?: string\n /** Flag indicating whether we're running the production mode or not. If not, useful warnings could be logged to the console */\n production?: boolean\n /** Callback to run if there's any error while trying to set up the {@link GPUAdapter | adapter} or {@link GPUDevice | device} */\n onError?: () => void\n /** Callback to run whenever the {@link GPUDeviceManager#device | device} is lost */\n onDeviceLost?: (info?: GPUDeviceLostInfo) => void\n}\n\n/**\n * Responsible for the WebGPU {@link GPUAdapter | adapter} and {@link GPUDevice | device} creations, losing and restoration.\n *\n * It will create all the GPU objects that need a {@link GPUDevice | device} to do so, as well as a {@link PipelineManager}. It will also keep a track of all the {@link Renderer}, {@link AllowedBindGroups | bind groups}, {@link Sampler}, {@link Texture} and {@link GPUBuffer | GPU buffers} created.\n *\n * The {@link GPUDeviceManager} is also responsible for creating the {@link GPUCommandBuffer}, rendering all the {@link Renderer} and then submitting the {@link GPUCommandBuffer} at each {@link GPUDeviceManager#render | render} calls.\n */\nexport class GPUDeviceManager {\n /** Number of times a {@link GPUDevice} has been created */\n index: number\n /** The label of the {@link GPUDeviceManager}, used to create the {@link GPUDevice} for debugging purpose */\n label: string\n\n /** Flag indicating whether we're running the production mode or not. If not, useful warnings could be logged to the console */\n production: boolean\n\n /** The navigator {@link GPU} object */\n gpu: GPU | undefined\n /** The WebGPU {@link GPUAdapter | adapter} used */\n adapter: GPUAdapter | void\n /** The WebGPU {@link GPUAdapter | adapter} informations */\n adapterInfos: GPUAdapterInfo | undefined\n /** The WebGPU {@link GPUDevice | device} used */\n device: GPUDevice | undefined\n /** Flag indicating whether the {@link GPUDeviceManager} is ready, i.e. its {@link adapter} and {@link device} have been successfully created */\n ready: boolean\n\n /** The {@link PipelineManager} used to cache {@link GPURenderPipeline} and {@link GPUComputePipeline} and set them only when appropriate */\n pipelineManager: PipelineManager\n\n /** Array of {@link Renderer | renderers} using that {@link GPUDeviceManager} */\n renderers: Renderer[]\n /** An array containing all our created {@link AllowedBindGroups} */\n bindGroups: AllowedBindGroups[]\n /** An array containing all our created {@link GPUBuffer} */\n buffers: GPUBuffer[]\n /** An array containing all our created {@link Sampler} */\n samplers: Sampler[]\n /** An array containing all our created {@link Texture} */\n textures: Texture[]\n /** An array to keep track of the newly uploaded {@link Texture | textures} and set their {@link Texture#sourceUploaded | sourceUploaded} property */\n texturesQueue: Texture[]\n\n /** Callback to run if there's any error while trying to set up the {@link GPUAdapter | adapter} or {@link GPUDevice | device} */\n onError: () => void\n /** Callback to run whenever the {@link device} is lost */\n onDeviceLost: (info?: GPUDeviceLostInfo) => void\n\n /**\n * GPUDeviceManager constructor\n * @param parameters - {@link GPUDeviceManagerParams | parameters} used to create this {@link GPUDeviceManager}\n */\n constructor({\n label,\n production = false,\n onError = () => {\n /* allow empty callbacks */\n },\n onDeviceLost = (info?: GPUDeviceLostInfo) => {\n /* allow empty callbacks */\n },\n }: GPUDeviceManagerParams) {\n this.index = 0\n this.label = label ?? 'GPUDeviceManager instance'\n this.production = production\n this.ready = false\n\n this.onError = onError\n this.onDeviceLost = onDeviceLost\n\n this.gpu = navigator.gpu\n\n this.setPipelineManager()\n this.setDeviceObjects()\n }\n\n /**\n * Set our {@link adapter} and {@link device} if possible\n */\n async setAdapterAndDevice() {\n await this.setAdapter()\n await this.setDevice()\n }\n\n /**\n * Set up our {@link adapter} and {@link device} and all the already created {@link renderers} contexts\n */\n async init() {\n await this.setAdapterAndDevice()\n\n // set context\n if (this.device) {\n this.renderers.forEach((renderer) => {\n if (!renderer.context) {\n renderer.setContext()\n }\n })\n }\n }\n\n /**\n * Set our {@link adapter} if possible.\n * The adapter represents a specific GPU. Some devices have multiple GPUs.\n * @async\n */\n async setAdapter() {\n if (!this.gpu) {\n this.onError()\n throwError(\"GPURenderer: WebGPU is not supported on your browser/OS. No 'gpu' object in 'navigator'.\")\n }\n\n try {\n this.adapter = await this.gpu?.requestAdapter()\n ;(this.adapter as GPUAdapter)?.requestAdapterInfo().then((infos) => {\n this.adapterInfos = infos\n })\n } catch (error) {\n this.onError()\n throwError(\"GPUDeviceManager: WebGPU is not supported on your browser/OS. 'requestAdapter' failed.\")\n }\n }\n\n /**\n * Set our {@link device}\n * @async\n */\n async setDevice() {\n try {\n this.device = await (this.adapter as GPUAdapter)?.requestDevice({\n label: this.label + ' ' + this.index,\n })\n\n if (this.device) {\n this.ready = true\n this.index++\n }\n } catch (error) {\n this.onError()\n throwError(`${this.label}: WebGPU is not supported on your browser/OS. 'requestDevice' failed: ${error}`)\n }\n\n this.device?.lost.then((info) => {\n throwWarning(`${this.label}: WebGPU device was lost: ${info.message}`)\n\n this.loseDevice()\n\n // do not call onDeviceLost event if the device was intentionally destroyed\n if (info.reason !== 'destroyed') {\n this.onDeviceLost(info)\n }\n })\n }\n\n /**\n * Set our {@link pipelineManager | pipeline manager}\n */\n setPipelineManager() {\n this.pipelineManager = new PipelineManager()\n }\n\n /**\n * Called when the {@link device} is lost.\n * Reset all our renderers\n */\n loseDevice() {\n this.ready = false\n\n // first clean all samplers\n this.samplers.forEach((sampler) => (sampler.sampler = null))\n\n this.renderers.forEach((renderer) => renderer.loseContext())\n\n // reset the buffers array, it would eventually be repopulated while restoring the device\n this.buffers = []\n }\n\n /**\n * Called when the {@link device} should be restored.\n * Restore all our renderers\n */\n async restoreDevice() {\n await this.setAdapterAndDevice()\n\n if (this.device) {\n // now recreate all the samplers\n this.samplers.forEach((sampler) => {\n const { type, ...samplerOptions } = sampler.options\n sampler.sampler = this.device.createSampler({\n label: sampler.label,\n ...samplerOptions,\n })\n })\n\n // then the renderers\n this.renderers.forEach((renderer) => renderer.restoreContext())\n }\n }\n\n /**\n * Set all objects arrays that we'll keep track of\n */\n setDeviceObjects() {\n // keep track of renderers, bind groups, buffers, samplers, textures\n this.renderers = []\n this.bindGroups = []\n this.buffers = []\n this.samplers = []\n this.textures = []\n\n // keep track of all textures that are being uploaded\n this.texturesQueue = []\n }\n\n /**\n * Add a {@link Renderer} to our {@link renderers} array\n * @param renderer - {@link Renderer} to add\n */\n addRenderer(renderer: Renderer) {\n this.renderers.push(renderer)\n }\n\n /**\n * Remove a {@link Renderer} from our {@link renderers} array\n * @param renderer - {@link Renderer} to remove\n */\n removeRenderer(renderer: Renderer) {\n this.renderers = this.renderers.filter((r) => r.uuid !== renderer.uuid)\n }\n\n /**\n * Get all the rendered objects (i.e. compute passes, meshes, ping pong planes and shader passes) created by this {@link GPUDeviceManager}\n * @readonly\n */\n get deviceRenderedObjects(): SceneObject[] {\n return this.renderers.map((renderer) => renderer.renderedObjects).flat()\n }\n\n /**\n * Add a {@link AllowedBindGroups | bind group} to our {@link bindGroups | bind groups array}\n * @param bindGroup - {@link AllowedBindGroups | bind group} to add\n */\n addBindGroup(bindGroup: AllowedBindGroups) {\n if (!this.bindGroups.find((bG) => bG.uuid === bindGroup.uuid)) {\n this.bindGroups.push(bindGroup)\n }\n }\n\n /**\n * Remove a {@link AllowedBindGroups | bind group} from our {@link bindGroups | bind groups array}\n * @param bindGroup - {@link AllowedBindGroups | bind group} to remove\n */\n removeBindGroup(bindGroup: AllowedBindGroups) {\n this.bindGroups = this.bindGroups.filter((bG) => bG.uuid !== bindGroup.uuid)\n }\n\n /**\n * Add a {@link GPUBuffer} to our our {@link buffers} array\n * @param buffer - {@link GPUBuffer} to add\n */\n addBuffer(buffer: GPUBuffer) {\n this.buffers.push(buffer)\n }\n\n /**\n * Remove a {@link GPUBuffer} from our {@link buffers} array\n * @param buffer - {@link GPUBuffer} to remove\n * @param [originalLabel] - original {@link GPUBuffer} label in case the buffer has been swapped and its label has changed\n */\n removeBuffer(buffer: GPUBuffer, originalLabel?: string) {\n if (buffer) {\n this.buffers = this.buffers.filter((b) => {\n return !(b.label === (originalLabel ?? buffer.label) && b.size === buffer.size)\n })\n }\n }\n\n /**\n * Add a {@link Sampler} to our {@link samplers} array\n * @param sampler - {@link Sampler} to add\n */\n addSampler(sampler: Sampler) {\n this.samplers.push(sampler)\n }\n\n /**\n * Remove a {@link Sampler} from our {@link samplers} array\n * @param sampler - {@link Sampler} to remove\n */\n removeSampler(sampler: Sampler) {\n this.samplers = this.samplers.filter((s) => s.uuid !== sampler.uuid)\n }\n\n /**\n * Add a {@link Texture} to our {@link textures} array\n * @param texture - {@link Texture} to add\n */\n addTexture(texture: Texture) {\n this.textures.push(texture)\n }\n\n /**\n * Upload a {@link Texture#texture | texture} to the GPU\n * @param texture - {@link Texture} class object with the {@link Texture#texture | texture} to upload\n */\n uploadTexture(texture: Texture) {\n if (texture.source) {\n try {\n this.device?.queue.copyExternalImageToTexture(\n {\n source: texture.source as GPUImageCopyExternalImageSource,\n flipY: texture.options.flipY,\n } as GPUImageCopyExternalImage,\n { texture: texture.texture as GPUTexture, premultipliedAlpha: texture.options.premultipliedAlpha },\n { width: texture.size.width, height: texture.size.height }\n )\n\n if ((texture.texture as GPUTexture).mipLevelCount > 1) {\n generateMips(this.device, texture.texture as GPUTexture)\n }\n\n // add to our textures queue array to track when it has been uploaded\n this.texturesQueue.push(texture)\n } catch ({ message }) {\n throwError(`GPUDeviceManager: could not upload texture: ${texture.options.name} because: ${message}`)\n }\n } else {\n this.device?.queue.writeTexture(\n { texture: texture.texture as GPUTexture },\n new Uint8Array(texture.options.placeholderColor),\n { bytesPerRow: texture.size.width * 4 },\n { width: texture.size.width, height: texture.size.height }\n )\n }\n }\n\n /**\n * Remove a {@link Texture} from our {@link textures} array\n * @param texture - {@link Texture} to remove\n */\n removeTexture(texture: Texture) {\n this.textures = this.textures.filter((t) => t.uuid !== texture.uuid)\n }\n\n /**\n * Render everything:\n * - call all our {@link renderers} {@link core/renderers/GPURenderer.GPURenderer#onBeforeCommandEncoder | onBeforeCommandEncoder} callbacks\n * - create a {@link GPUCommandEncoder}\n * - render all our {@link renderers}\n * - submit our {@link GPUCommandBuffer}\n * - upload {@link Texture#texture | textures} that do not have a parentMesh\n * - empty our {@link texturesQueue} array\n * - call all our {@link renderers} {@link core/renderers/GPURenderer.GPURenderer#onAfterCommandEncoder | onAfterCommandEncoder} callbacks\n */\n render() {\n if (!this.ready) return\n\n this.renderers.forEach((renderer) => renderer.onBeforeCommandEncoder())\n\n const commandEncoder = this.device?.createCommandEncoder({ label: this.label + ' command encoder' })\n !this.production && commandEncoder.pushDebugGroup(this.label + ' command encoder: main render loop')\n\n this.renderers.forEach((renderer) => renderer.render(commandEncoder))\n\n !this.production && commandEncoder.popDebugGroup()\n const commandBuffer = commandEncoder.finish()\n this.device?.queue.submit([commandBuffer])\n\n // handle textures\n // first check if media textures without parentMesh need to be uploaded\n this.textures\n .filter((texture) => !texture.parentMesh && texture.sourceLoaded && !texture.sourceUploaded)\n .forEach((texture) => this.uploadTexture(texture))\n\n // no need to use device.queue.onSubmittedWorkDone\n // as [Kai Ninomiya](https://github.com/kainino0x) stated:\n // \"Anything you submit() after the copyExternalImageToTexture() is guaranteed to see the result of that call.\"\n this.texturesQueue.forEach((texture) => {\n texture.sourceUploaded = true\n })\n\n // clear texture queue\n this.texturesQueue = []\n\n this.renderers.forEach((renderer) => renderer.onAfterCommandEncoder())\n }\n\n /**\n * Destroy the {@link GPUDeviceManager} and its {@link renderers}\n */\n destroy() {\n this.device?.destroy()\n this.device = null\n\n this.renderers.forEach((renderer) => renderer.destroy())\n\n // now clear everything that could have been left behind\n this.bindGroups.forEach((bindGroup) => bindGroup.destroy())\n this.buffers.forEach((buffer) => buffer?.destroy())\n\n this.textures.forEach((texture) => texture.destroy())\n\n this.setDeviceObjects()\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/renderers/GPURenderer.mjs b/dist/esm/core/renderers/GPURenderer.mjs new file mode 100644 index 000000000..d66daeeb2 --- /dev/null +++ b/dist/esm/core/renderers/GPURenderer.mjs @@ -0,0 +1,742 @@ +import { DOMElement } from '../DOM/DOMElement.mjs'; +import { Scene } from '../scenes/Scene.mjs'; +import { RenderPass } from '../renderPasses/RenderPass.mjs'; +import { generateUUID, throwWarning } from '../../utils/utils.mjs'; +import { ComputePass } from '../computePasses/ComputePass.mjs'; +import { TasksQueueManager } from '../../utils/TasksQueueManager.mjs'; + +class GPURenderer { + /** + * GPURenderer constructor + * @param parameters - {@link GPURendererParams | parameters} used to create this {@link GPURenderer} + */ + constructor({ + deviceManager, + container, + pixelRatio = 1, + preferredFormat, + alphaMode = "premultiplied", + renderPass + }) { + // callbacks / events + /** function assigned to the {@link onBeforeRender} callback */ + this._onBeforeRenderCallback = (commandEncoder) => { + }; + /** function assigned to the {@link onAfterRender} callback */ + this._onAfterRenderCallback = (commandEncoder) => { + }; + /** function assigned to the {@link onAfterResize} callback */ + this._onAfterResizeCallback = () => { + }; + this.type = "GPURenderer"; + this.uuid = generateUUID(); + this.deviceManager = deviceManager; + this.deviceManager.addRenderer(this); + renderPass = { ...{ useDepth: true, sampleCount: 4, clearValue: [0, 0, 0, 0] }, ...renderPass }; + preferredFormat = preferredFormat ?? this.deviceManager.gpu?.getPreferredCanvasFormat(); + this.options = { + deviceManager, + container, + pixelRatio, + preferredFormat, + alphaMode, + renderPass + }; + this.pixelRatio = pixelRatio ?? window.devicePixelRatio ?? 1; + this.alphaMode = alphaMode; + this.setTasksQueues(); + this.setRendererObjects(); + const isContainerCanvas = container instanceof HTMLCanvasElement; + this.canvas = isContainerCanvas ? container : document.createElement("canvas"); + this.domElement = new DOMElement({ + element: container, + priority: 5, + // renderer callback need to be called first + onSizeChanged: (boundingRect) => this.resize(boundingRect) + }); + if (!isContainerCanvas) { + this.domElement.element.appendChild(this.canvas); + } + if (this.deviceManager.device) { + this.setContext(); + } + } + /** + * Set {@link canvas} size + * @param boundingRect - new {@link domElement | DOM Element} {@link DOMElement#boundingRect | bounding rectangle} + */ + setSize(boundingRect) { + this.canvas.style.width = Math.floor(boundingRect.width) + "px"; + this.canvas.style.height = Math.floor(boundingRect.height) + "px"; + this.canvas.width = this.getScaledDisplayBoundingRect(boundingRect).width; + this.canvas.height = this.getScaledDisplayBoundingRect(boundingRect).height; + } + /** + * Resize our {@link GPURenderer} + * @param boundingRect - new {@link domElement | DOM Element} {@link DOMElement#boundingRect | bounding rectangle} + */ + resize(boundingRect = null) { + if (!this.domElement && !boundingRect) + return; + if (!boundingRect) + boundingRect = this.domElement.element.getBoundingClientRect(); + this.setSize(boundingRect); + this.onResize(); + this._onAfterResizeCallback && this._onAfterResizeCallback(); + } + /** + * Resize all tracked objects + */ + onResize() { + this.renderTextures.forEach((renderTexture) => { + renderTexture.resize(); + }); + this.renderPass?.resize(); + this.postProcessingPass?.resize(); + this.renderTargets.forEach((renderTarget) => renderTarget.resize()); + this.computePasses.forEach((computePass) => computePass.resize()); + this.pingPongPlanes.forEach((pingPongPlane) => pingPongPlane.resize(this.boundingRect)); + this.shaderPasses.forEach((shaderPass) => shaderPass.resize(this.boundingRect)); + this.meshes.forEach((mesh) => { + if (!("domElement" in mesh)) { + mesh.resize(this.boundingRect); + } else { + this.onBeforeCommandEncoderCreation.add( + () => { + if (!mesh.domElement.isResizing) { + mesh.domElement.setSize(); + } + }, + { once: true } + ); + } + }); + } + /** + * Get our {@link domElement | DOM Element} {@link DOMElement#boundingRect | bounding rectangle} + */ + get boundingRect() { + if (!!this.domElement.boundingRect) { + return this.domElement.boundingRect; + } else { + const boundingRect = this.domElement.element?.getBoundingClientRect(); + return { + top: boundingRect.top, + right: boundingRect.right, + bottom: boundingRect.bottom, + left: boundingRect.left, + width: boundingRect.width, + height: boundingRect.height, + x: boundingRect.x, + y: boundingRect.y + }; + } + } + /** + * Get our {@link domElement | DOM Element} {@link DOMElement#boundingRect | bounding rectangle} accounting for current {@link pixelRatio | pixel ratio} + */ + get displayBoundingRect() { + return this.getScaledDisplayBoundingRect(this.boundingRect); + } + /** + * Get the display bounding rectangle accounting for current {@link pixelRatio | pixel ratio} and max texture dimensions + * @param boundingRect - bounding rectangle to check against + */ + getScaledDisplayBoundingRect(boundingRect) { + const devicePixelRatio = window.devicePixelRatio ?? 1; + const scaleBoundingRect = this.pixelRatio / devicePixelRatio; + const displayBoundingRect = Object.keys(boundingRect).reduce( + (a, key) => ({ ...a, [key]: boundingRect[key] * scaleBoundingRect }), + { + x: 0, + y: 0, + width: 0, + height: 0, + top: 0, + right: 0, + bottom: 0, + left: 0 + } + ); + if (this.device) { + displayBoundingRect.width = Math.min(this.device.limits.maxTextureDimension2D, displayBoundingRect.width); + displayBoundingRect.height = Math.min(this.device.limits.maxTextureDimension2D, displayBoundingRect.height); + displayBoundingRect.right = Math.min( + displayBoundingRect.width + displayBoundingRect.left, + displayBoundingRect.right + ); + displayBoundingRect.bottom = Math.min( + displayBoundingRect.height + displayBoundingRect.top, + displayBoundingRect.bottom + ); + } + return displayBoundingRect; + } + /* USEFUL DEVICE MANAGER OBJECTS */ + /** + * Get our {@link GPUDeviceManager#device | device} + * @readonly + */ + get device() { + return this.deviceManager.device; + } + /** + * Get whether our {@link GPUDeviceManager} is ready (i.e. its {@link GPUDeviceManager#adapter | adapter} and {@link GPUDeviceManager#device | device} are set) its {@link context} is set and its size is set + * @readonly + */ + get ready() { + return this.deviceManager.ready && !!this.context && !!this.canvas.style.width; + } + /** + * Get our {@link GPUDeviceManager#production | GPUDeviceManager production flag} + * @readonly + */ + get production() { + return this.deviceManager.production; + } + /** + * Get all the created {@link GPUDeviceManager#samplers | samplers} + * @readonly + */ + get samplers() { + return this.deviceManager.samplers; + } + /** + * Get all the created {@link GPUDeviceManager#buffers | GPU buffers} + * @readonly + */ + get buffers() { + return this.deviceManager.buffers; + } + /** + * Get the {@link GPUDeviceManager#pipelineManager | pipeline manager} + * @readonly + */ + get pipelineManager() { + return this.deviceManager.pipelineManager; + } + /** + * Get all the rendered objects (i.e. compute passes, meshes, ping pong planes and shader passes) created by the {@link GPUDeviceManager} + * @readonly + */ + get deviceRenderedObjects() { + return this.deviceManager.deviceRenderedObjects; + } + /** + * Configure our {@link context} with the given options + */ + configureContext() { + this.context.configure({ + device: this.device, + format: this.options.preferredFormat, + alphaMode: this.alphaMode, + // needed so we can copy textures for post processing usage + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST + //viewFormats: [] + }); + } + /** + * Set our {@link context} if possible and set {@link renderPass | main render pass} and {@link scene} + */ + setContext() { + this.context = this.canvas.getContext("webgpu"); + if (this.device) { + this.configureContext(); + this.setMainRenderPasses(); + this.setScene(); + } + } + /** + * Called when the {@link GPUDeviceManager#device | device} is lost. + * Force all our scene objects to lose context. + */ + loseContext() { + this.renderedObjects.forEach((sceneObject) => sceneObject.loseContext()); + } + /** + * Called when the {@link GPUDeviceManager#device | device} should be restored. + * Configure the context again, resize the {@link RenderTarget | render targets} and {@link RenderTexture | render textures}, restore our {@link renderedObjects | rendered objects} context. + * @async + */ + restoreContext() { + this.configureContext(); + this.renderTextures.forEach((renderTexture) => { + renderTexture.createTexture(); + }); + this.renderPass?.resize(); + this.postProcessingPass?.resize(); + this.renderTargets.forEach((renderTarget) => renderTarget.resize()); + this.renderedObjects.forEach((sceneObject) => sceneObject.restoreContext()); + } + /* PIPELINES, SCENE & MAIN RENDER PASS */ + /** + * Set our {@link renderPass | main render pass} that will be used to render the result of our draw commands back to the screen and our {@link postProcessingPass | postprocessing pass} that will be used for any additional postprocessing render passes. + */ + setMainRenderPasses() { + this.renderPass = new RenderPass(this, { + label: "Main render pass", + targetFormat: this.options.preferredFormat, + ...this.options.renderPass + }); + this.postProcessingPass = new RenderPass(this, { + label: "Post processing render pass", + targetFormat: this.options.preferredFormat, + // no need to handle depth or perform MSAA on a fullscreen quad + useDepth: false, + sampleCount: 1 + }); + } + /** + * Set our {@link scene} + */ + setScene() { + this.scene = new Scene({ renderer: this }); + } + /* BUFFERS & BINDINGS */ + /** + * Create a {@link GPUBuffer} + * @param bufferDescriptor - {@link GPUBufferDescriptor | GPU buffer descriptor} + * @returns - newly created {@link GPUBuffer} + */ + createBuffer(bufferDescriptor) { + const buffer = this.device?.createBuffer(bufferDescriptor); + this.deviceManager.addBuffer(buffer); + return buffer; + } + /** + * Remove a {@link GPUBuffer} from our {@link GPUDeviceManager#buffers | GPU buffers array} + * @param buffer - {@link GPUBuffer} to remove + * @param [originalLabel] - original {@link GPUBuffer} label in case the buffer has been swapped and its label has changed + */ + removeBuffer(buffer, originalLabel) { + this.deviceManager.removeBuffer(buffer, originalLabel); + } + /** + * Write to a {@link GPUBuffer} + * @param buffer - {@link GPUBuffer} to write to + * @param bufferOffset - {@link GPUSize64 | buffer offset} + * @param data - {@link BufferSource | data} to write + */ + queueWriteBuffer(buffer, bufferOffset, data) { + this.device?.queue.writeBuffer(buffer, bufferOffset, data); + } + /** + * Copy a source {@link GPUBuffer} into a destination {@link GPUBuffer} + * @param parameters - parameters used to realize the copy + * @param parameters.srcBuffer - source {@link GPUBuffer} + * @param [parameters.dstBuffer] - destination {@link GPUBuffer}. Will create a new one if none provided. + * @param [parameters.commandEncoder] - {@link GPUCommandEncoder} to use for the copy. Will create a new one and submit the command buffer if none provided. + * @returns - destination {@link GPUBuffer} after copy + */ + copyBufferToBuffer({ + srcBuffer, + dstBuffer, + commandEncoder + }) { + if (!srcBuffer) { + throwWarning(`${this.type}: cannot copy to buffer because the source buffer has not been provided`); + return null; + } + if (!dstBuffer) { + dstBuffer = this.createBuffer({ + label: this.type + ": destination copy buffer from: " + srcBuffer.label, + size: srcBuffer.size, + usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST + }); + } + if (srcBuffer.mapState !== "unmapped") { + throwWarning(`${this.type}: Cannot copy from ${srcBuffer} because it is currently mapped`); + return; + } + if (dstBuffer.mapState !== "unmapped") { + throwWarning(`${this.type}: Cannot copy from ${dstBuffer} because it is currently mapped`); + return; + } + const hasCommandEncoder = !!commandEncoder; + if (!hasCommandEncoder) { + commandEncoder = this.device?.createCommandEncoder({ label: "Copy buffer command encoder" }); + !this.production && commandEncoder.pushDebugGroup("Copy buffer command encoder"); + } + commandEncoder.copyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, dstBuffer.size); + if (!hasCommandEncoder) { + !this.production && commandEncoder.popDebugGroup(); + const commandBuffer = commandEncoder.finish(); + this.device?.queue.submit([commandBuffer]); + } + return dstBuffer; + } + /* BIND GROUPS & LAYOUTS */ + /** + * Get all created {@link AllowedBindGroups | bind group} tracked by our {@link GPUDeviceManager} + * @readonly + */ + get bindGroups() { + return this.deviceManager.bindGroups; + } + /** + * Add a {@link AllowedBindGroups | bind group} to our {@link GPUDeviceManager#bindGroups | bind groups array} + * @param bindGroup - {@link AllowedBindGroups | bind group} to add + */ + addBindGroup(bindGroup) { + this.deviceManager.addBindGroup(bindGroup); + } + /** + * Remove a {@link AllowedBindGroups | bind group} from our {@link GPUDeviceManager#bindGroups | bind groups array} + * @param bindGroup - {@link AllowedBindGroups | bind group} to remove + */ + removeBindGroup(bindGroup) { + this.deviceManager.removeBindGroup(bindGroup); + } + /** + * Create a {@link GPUBindGroupLayout} + * @param bindGroupLayoutDescriptor - {@link GPUBindGroupLayoutDescriptor | GPU bind group layout descriptor} + * @returns - newly created {@link GPUBindGroupLayout} + */ + createBindGroupLayout(bindGroupLayoutDescriptor) { + return this.device?.createBindGroupLayout(bindGroupLayoutDescriptor); + } + /** + * Create a {@link GPUBindGroup} + * @param bindGroupDescriptor - {@link GPUBindGroupDescriptor | GPU bind group descriptor} + * @returns - newly created {@link GPUBindGroup} + */ + createBindGroup(bindGroupDescriptor) { + return this.device?.createBindGroup(bindGroupDescriptor); + } + /* SHADERS & PIPELINES */ + /** + * Create a {@link GPUShaderModule} + * @param shaderModuleDescriptor - {@link shaderModuleDescriptor | shader module descriptor} + * @returns - newly created {@link GPUShaderModule} + */ + createShaderModule(shaderModuleDescriptor) { + return this.device?.createShaderModule(shaderModuleDescriptor); + } + /** + * Create a {@link GPUPipelineLayout} + * @param pipelineLayoutDescriptor - {@link GPUPipelineLayoutDescriptor | GPU pipeline layout descriptor} + * @returns - newly created {@link GPUPipelineLayout} + */ + createPipelineLayout(pipelineLayoutDescriptor) { + return this.device?.createPipelineLayout(pipelineLayoutDescriptor); + } + /** + * Create a {@link GPURenderPipeline} + * @param pipelineDescriptor - {@link GPURenderPipelineDescriptor | GPU render pipeline descriptor} + * @returns - newly created {@link GPURenderPipeline} + */ + createRenderPipeline(pipelineDescriptor) { + return this.device?.createRenderPipeline(pipelineDescriptor); + } + /** + * Asynchronously create a {@link GPURenderPipeline} + * @async + * @param pipelineDescriptor - {@link GPURenderPipelineDescriptor | GPU render pipeline descriptor} + * @returns - newly created {@link GPURenderPipeline} + */ + async createRenderPipelineAsync(pipelineDescriptor) { + return await this.device?.createRenderPipelineAsync(pipelineDescriptor); + } + /** + * Create a {@link GPUComputePipeline} + * @param pipelineDescriptor - {@link GPUComputePipelineDescriptor | GPU compute pipeline descriptor} + * @returns - newly created {@link GPUComputePipeline} + */ + createComputePipeline(pipelineDescriptor) { + return this.device?.createComputePipeline(pipelineDescriptor); + } + /** + * Asynchronously create a {@link GPUComputePipeline} + * @async + * @param pipelineDescriptor - {@link GPUComputePipelineDescriptor | GPU compute pipeline descriptor} + * @returns - newly created {@link GPUComputePipeline} + */ + async createComputePipelineAsync(pipelineDescriptor) { + return await this.device?.createComputePipelineAsync(pipelineDescriptor); + } + /* TEXTURES */ + /** + * Get all created {@link Texture} tracked by our {@link GPUDeviceManager} + * @readonly + */ + get textures() { + return this.deviceManager.textures; + } + /** + * Add a {@link Texture} to our {@link GPUDeviceManager#textures | textures array} + * @param texture - {@link Texture} to add + */ + addTexture(texture) { + this.deviceManager.addTexture(texture); + } + /** + * Remove a {@link Texture} from our {@link GPUDeviceManager#textures | textures array} + * @param texture - {@link Texture} to remove + */ + removeTexture(texture) { + this.deviceManager.removeTexture(texture); + } + /** + * Add a {@link RenderTexture} to our {@link renderTextures} array + * @param texture - {@link RenderTexture} to add + */ + addRenderTexture(texture) { + this.renderTextures.push(texture); + } + /** + * Remove a {@link RenderTexture} from our {@link renderTextures} array + * @param texture - {@link RenderTexture} to remove + */ + removeRenderTexture(texture) { + this.renderTextures = this.renderTextures.filter((t) => t.uuid !== texture.uuid); + } + /** + * Create a {@link GPUTexture} + * @param textureDescriptor - {@link GPUTextureDescriptor | GPU texture descriptor} + * @returns - newly created {@link GPUTexture} + */ + createTexture(textureDescriptor) { + return this.device?.createTexture(textureDescriptor); + } + /** + * Upload a {@link Texture#texture | texture} to the GPU + * @param texture - {@link Texture} class object with the {@link Texture#texture | texture} to upload + */ + uploadTexture(texture) { + this.deviceManager.uploadTexture(texture); + } + /** + * Import a {@link GPUExternalTexture} + * @param video - {@link HTMLVideoElement} source + * @returns - {@link GPUExternalTexture} + */ + importExternalTexture(video) { + return this.device?.importExternalTexture({ source: video }); + } + /** + * Check if a {@link Sampler} has already been created with the same {@link Sampler#options | parameters}. + * Use it if found, else create a new one and add it to the {@link GPUDeviceManager#samplers | samplers array}. + * @param sampler - {@link Sampler} to create + * @returns - the {@link GPUSampler} + */ + createSampler(sampler) { + const existingSampler = this.samplers.find((existingSampler2) => { + return JSON.stringify(existingSampler2.options) === JSON.stringify(sampler.options) && existingSampler2.sampler; + }); + if (existingSampler) { + return existingSampler.sampler; + } else { + const { type, ...samplerOptions } = sampler.options; + const gpuSampler = this.device?.createSampler({ + label: sampler.label, + ...samplerOptions + }); + this.deviceManager.addSampler(sampler); + return gpuSampler; + } + } + /** + * Remove a {@link Sampler} from our {@link GPUDeviceManager#samplers | samplers array} + * @param sampler - {@link Sampler} to remove + */ + removeSampler(sampler) { + this.deviceManager.removeSampler(sampler); + } + /* OBJECTS & TASKS */ + /** + * Set different tasks queue managers to execute callbacks at different phases of our render call: + * - {@link onBeforeCommandEncoderCreation}: callbacks executed before the creation of the command encoder + * - {@link onBeforeRenderScene}: callbacks executed after the creation of the command encoder and before rendering the {@link Scene} + * - {@link onAfterRenderScene}: callbacks executed after the creation of the command encoder and after rendering the {@link Scene} + * - {@link onAfterCommandEncoderSubmission}: callbacks executed after the submission of the command encoder + */ + setTasksQueues() { + this.onBeforeCommandEncoderCreation = new TasksQueueManager(); + this.onBeforeRenderScene = new TasksQueueManager(); + this.onAfterRenderScene = new TasksQueueManager(); + this.onAfterCommandEncoderSubmission = new TasksQueueManager(); + } + /** + * Set all objects arrays that we'll keep track of + */ + setRendererObjects() { + this.computePasses = []; + this.pingPongPlanes = []; + this.shaderPasses = []; + this.renderTargets = []; + this.meshes = []; + this.renderTextures = []; + } + /** + * Get all this {@link GPURenderer} rendered objects (i.e. compute passes, meshes, ping pong planes and shader passes) + * @readonly + */ + get renderedObjects() { + return [...this.computePasses, ...this.meshes, ...this.shaderPasses, ...this.pingPongPlanes]; + } + /** + * Get all objects ({@link RenderedMesh | rendered meshes} or {@link ComputePass | compute passes}) using a given {@link AllowedBindGroups | bind group}. + * Useful to know if a resource is used by multiple objects and if it is safe to destroy it or not. + * @param bindGroup - {@link AllowedBindGroups | bind group} to check + */ + getObjectsByBindGroup(bindGroup) { + return this.deviceRenderedObjects.filter((object) => { + return [ + ...object.material.bindGroups, + ...object.material.inputsBindGroups, + ...object.material.clonedBindGroups + ].some((bG) => bG.uuid === bindGroup.uuid); + }); + } + /** + * Get all objects ({@link RenderedMesh | rendered meshes} or {@link ComputePass | compute passes}) using a given {@link Texture} or {@link RenderTexture}. + * Useful to know if a resource is used by multiple objects and if it is safe to destroy it or not. + * @param texture - {@link Texture} or {@link RenderTexture} to check + */ + getObjectsByTexture(texture) { + return this.deviceRenderedObjects.filter((object) => { + return [...object.material.textures, ...object.material.renderTextures].some((t) => t.uuid === texture.uuid); + }); + } + /* EVENTS */ + /** + * Assign a callback function to _onBeforeRenderCallback + * @param callback - callback to run just before the {@link render} method will be executed + * @returns - our {@link GPURenderer} + */ + onBeforeRender(callback) { + if (callback) { + this._onBeforeRenderCallback = callback; + } + return this; + } + /** + * Assign a callback function to _onAfterRenderCallback + * @param callback - callback to run just after the {@link render} method has been executed + * @returns - our {@link GPURenderer} + */ + onAfterRender(callback) { + if (callback) { + this._onAfterRenderCallback = callback; + } + return this; + } + /** + * Assign a callback function to _onAfterResizeCallback + * @param callback - callback to run just after the {@link GPURenderer} has been resized + * @returns - our {@link GPURenderer} + */ + onAfterResize(callback) { + if (callback) { + this._onAfterResizeCallback = callback; + } + return this; + } + /* RENDER */ + /** + * Render a single {@link ComputePass} + * @param commandEncoder - current {@link GPUCommandEncoder} + * @param computePass - {@link ComputePass} + */ + renderSingleComputePass(commandEncoder, computePass) { + const pass = commandEncoder.beginComputePass(); + computePass.render(pass); + pass.end(); + computePass.copyBufferToResult(commandEncoder); + } + /** + * Render a single {@link RenderedMesh | Mesh} + * @param commandEncoder - current {@link GPUCommandEncoder} + * @param mesh - {@link RenderedMesh | Mesh} to render + */ + renderSingleMesh(commandEncoder, mesh) { + const pass = commandEncoder.beginRenderPass(this.renderPass.descriptor); + mesh.render(pass); + pass.end(); + } + /** + * Render an array of objects (either {@link RenderedMesh | Meshes} or {@link ComputePass}) once. This method won't call any of the renderer render hooks like {@link onBeforeRender}, {@link onAfterRender} + * @param objects - Array of {@link RenderedMesh | Meshes} or {@link ComputePass} to render + */ + renderOnce(objects) { + const commandEncoder = this.device?.createCommandEncoder({ + label: "Render once command encoder" + }); + !this.production && commandEncoder.pushDebugGroup("Render once command encoder"); + this.pipelineManager.resetCurrentPipeline(); + objects.forEach((object) => { + if (object instanceof ComputePass) { + this.renderSingleComputePass(commandEncoder, object); + } else { + this.renderSingleMesh(commandEncoder, object); + } + }); + !this.production && commandEncoder.popDebugGroup(); + const commandBuffer = commandEncoder.finish(); + this.device?.queue.submit([commandBuffer]); + this.pipelineManager.resetCurrentPipeline(); + } + /** + * Force to clear a {@link GPURenderer} content to its {@link RenderPass#options.clearValue | clear value} by rendering and empty pass. + * @param commandEncoder + */ + forceClear(commandEncoder) { + const hasCommandEncoder = !!commandEncoder; + if (!hasCommandEncoder) { + commandEncoder = this.device?.createCommandEncoder({ label: "Force clear command encoder" }); + !this.production && commandEncoder.pushDebugGroup("Force clear command encoder"); + } + this.renderPass.updateView(); + const pass = commandEncoder.beginRenderPass(this.renderPass.descriptor); + pass.end(); + if (!hasCommandEncoder) { + !this.production && commandEncoder.popDebugGroup(); + const commandBuffer = commandEncoder.finish(); + this.device?.queue.submit([commandBuffer]); + } + } + /** + * Called by the {@link GPUDeviceManager#render | GPUDeviceManager render method} before the {@link GPUCommandEncoder} has been created + */ + onBeforeCommandEncoder() { + if (!this.ready) + return; + this.onBeforeCommandEncoderCreation.execute(); + } + /** + * Called by the {@link GPUDeviceManager#render | GPUDeviceManager render method} after the {@link GPUCommandEncoder} has been created. + */ + onAfterCommandEncoder() { + if (!this.ready) + return; + this.onAfterCommandEncoderSubmission.execute(); + } + /** + * Called at each draw call to render our scene and its content + * @param commandEncoder - current {@link GPUCommandEncoder} + */ + render(commandEncoder) { + if (!this.ready) + return; + this._onBeforeRenderCallback && this._onBeforeRenderCallback(commandEncoder); + this.onBeforeRenderScene.execute(commandEncoder); + this.scene?.render(commandEncoder); + this._onAfterRenderCallback && this._onAfterRenderCallback(commandEncoder); + this.onAfterRenderScene.execute(commandEncoder); + } + /** + * Destroy our {@link GPURenderer} and everything that needs to be destroyed as well + */ + destroy() { + this.domElement?.destroy(); + this.renderPass?.destroy(); + this.postProcessingPass?.destroy(); + this.renderTargets.forEach((renderTarget) => renderTarget.destroy()); + this.renderedObjects.forEach((sceneObject) => sceneObject.remove()); + this.renderTextures.forEach((texture) => texture.destroy()); + this.context?.unconfigure(); + } +} + +export { GPURenderer }; +//# sourceMappingURL=GPURenderer.mjs.map diff --git a/dist/esm/core/renderers/GPURenderer.mjs.map b/dist/esm/core/renderers/GPURenderer.mjs.map new file mode 100644 index 000000000..14823b89f --- /dev/null +++ b/dist/esm/core/renderers/GPURenderer.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"GPURenderer.mjs","sources":["../../../../src/core/renderers/GPURenderer.ts"],"sourcesContent":["import { PipelineManager } from '../pipelines/PipelineManager'\nimport { DOMElement, DOMElementBoundingRect } from '../DOM/DOMElement'\nimport { Scene } from '../scenes/Scene'\nimport { RenderPass, RenderPassParams } from '../renderPasses/RenderPass'\nimport { generateUUID, throwWarning } from '../../utils/utils'\n\nimport { ComputePass } from '../computePasses/ComputePass'\nimport { PingPongPlane } from '../../curtains/meshes/PingPongPlane'\nimport { ShaderPass } from '../renderPasses/ShaderPass'\nimport { RenderTarget } from '../renderPasses/RenderTarget'\nimport { Texture } from '../textures/Texture'\nimport { Sampler } from '../samplers/Sampler'\n\nimport { DOMMesh } from '../../curtains/meshes/DOMMesh'\nimport { Plane } from '../../curtains/meshes/Plane'\nimport { Mesh } from '../meshes/Mesh'\nimport { TasksQueueManager } from '../../utils/TasksQueueManager'\nimport { AllowedBindGroups } from '../../types/BindGroups'\nimport { RenderTexture } from '../textures/RenderTexture'\nimport { GPUDeviceManager } from './GPUDeviceManager'\nimport { FullscreenPlane } from '../meshes/FullscreenPlane'\n\n/**\n * Parameters used to create a {@link GPURenderer}\n */\nexport interface GPURendererParams {\n /** The {@link GPUDeviceManager} used to create this {@link GPURenderer} */\n deviceManager: GPUDeviceManager\n /** {@link HTMLElement} or selector used as a container for our {@link GPURenderer#canvas | canvas} */\n container: string | HTMLElement\n /** Pixel ratio to use for rendering */\n pixelRatio?: number\n /** Texture rendering {@link GPUTextureFormat | preferred format} */\n preferredFormat?: GPUTextureFormat\n /** Set the {@link GPUCanvasContext | context} alpha mode */\n alphaMode?: GPUCanvasAlphaMode\n\n /** The {@link GPURenderer#renderPass | renderer RenderPass} parameters */\n renderPass?: {\n /** Whether the {@link GPURenderer#renderPass | renderer RenderPass} should handle depth. Default to `true` */\n useDepth: RenderPassParams['useDepth']\n /** The {@link GPURenderer#renderPass | renderer RenderPass} sample count (i.e. whether it should use multisampled antialiasing). Default to `4` */\n sampleCount: RenderPassParams['sampleCount']\n /** The {@link GPUColor | color values} to clear to before drawing the {@link GPURenderer#renderPass | renderer RenderPass}. Default to `[0, 0, 0, 0]` */\n clearValue: GPUColor\n }\n}\n\n/** Any Mesh that is bound to a DOM Element */\nexport type DOMProjectedMesh = DOMMesh | Plane\n/** Any Mesh that is projected (i.e use a {@link core/camera/Camera.Camera | Camera} to compute a model view projection matrix) */\nexport type ProjectedMesh = Mesh | DOMProjectedMesh\n/** Any Mesh that can be drawn, including fullscreen quad meshes used for post processing */\nexport type RenderedMesh = ProjectedMesh | PingPongPlane | ShaderPass | FullscreenPlane\n/** Any Mesh or Compute pass */\nexport type SceneObject = RenderedMesh | ComputePass\n\n/**\n * Base renderer class, that could technically be used to render compute passes and draw fullscreen quads, even tho it is strongly advised to use at least the {@link core/renderers/GPUCameraRenderer.GPUCameraRenderer | GPUCameraRenderer} class instead.\n * A renderer is responsible for:\n * - Setting a {@link GPUCanvasContext | context}\n * - Handling the {@link HTMLCanvasElement | canvas} onto everything is drawn\n * - Creating a {@link RenderPass} that will handle our render and depth textures and the render pass descriptor\n * - Keeping track of every specific class objects created relative to computing and rendering\n * - Creating a {@link Scene} class that will take care of the rendering process of all previously mentioned objects\n */\nexport class GPURenderer {\n /** The type of the {@link GPURenderer} */\n type: string\n /** The universal unique id of this {@link GPURenderer} */\n readonly uuid: string\n\n /** The {@link GPUDeviceManager} used to create this {@link GPURenderer} */\n deviceManager: GPUDeviceManager\n\n /** {@link HTMLCanvasElement} onto everything is drawn */\n canvas: HTMLCanvasElement\n /** The WebGPU {@link GPUCanvasContext | context} used */\n context: null | GPUCanvasContext\n /** Set the {@link GPUCanvasContext | context} alpha mode */\n alphaMode?: GPUCanvasAlphaMode\n\n /** Options used to create this {@link GPURenderer} */\n options: GPURendererParams\n\n /** The {@link RenderPass | render pass} used to render our result to screen */\n renderPass: RenderPass\n /** Additional {@link RenderPass | render pass} used by {@link ShaderPass} for compositing / post processing. Does not handle depth */\n postProcessingPass: RenderPass\n\n /** The {@link Scene} used */\n scene: Scene\n\n /** An array containing all our created {@link ComputePass} */\n computePasses: ComputePass[]\n /** An array containing all our created {@link PingPongPlane} */\n pingPongPlanes: PingPongPlane[]\n /** An array containing all our created {@link ShaderPass} */\n shaderPasses: ShaderPass[]\n /** An array containing all our created {@link RenderTarget} */\n renderTargets: RenderTarget[]\n /** An array containing all our created {@link ProjectedMesh | projected meshes} */\n meshes: ProjectedMesh[]\n /** An array containing all our created {@link RenderTexture} */\n renderTextures: RenderTexture[]\n\n /** Pixel ratio to use for rendering */\n pixelRatio: number\n\n /** {@link DOMElement} that will track our canvas container size */\n domElement: DOMElement\n\n /** Allow to add callbacks to be executed at each render before the {@link GPUCommandEncoder} is created */\n onBeforeCommandEncoderCreation: TasksQueueManager\n /** Allow to add callbacks to be executed at each render after the {@link GPUCommandEncoder} has been created but before the {@link Scene} is rendered */\n onBeforeRenderScene: TasksQueueManager\n /** Allow to add callbacks to be executed at each render after the {@link GPUCommandEncoder} has been created and after the {@link Scene} has been rendered */\n onAfterRenderScene: TasksQueueManager\n /** Allow to add callbacks to be executed at each render after the {@link Scene} has been rendered and the {@link GPUCommandEncoder} has been submitted */\n onAfterCommandEncoderSubmission: TasksQueueManager\n\n // callbacks / events\n /** function assigned to the {@link onBeforeRender} callback */\n _onBeforeRenderCallback = (commandEncoder: GPUCommandEncoder) => {\n /* allow empty callback */\n }\n /** function assigned to the {@link onAfterRender} callback */\n _onAfterRenderCallback = (commandEncoder: GPUCommandEncoder) => {\n /* allow empty callback */\n }\n /** function assigned to the {@link onAfterResize} callback */\n _onAfterResizeCallback: () => void = () => {\n /* allow empty callback */\n }\n\n /**\n * GPURenderer constructor\n * @param parameters - {@link GPURendererParams | parameters} used to create this {@link GPURenderer}\n */\n constructor({\n deviceManager,\n container,\n pixelRatio = 1,\n preferredFormat,\n alphaMode = 'premultiplied',\n renderPass,\n }: GPURendererParams) {\n this.type = 'GPURenderer'\n this.uuid = generateUUID()\n\n this.deviceManager = deviceManager\n this.deviceManager.addRenderer(this)\n\n // render pass default values\n renderPass = { ...{ useDepth: true, sampleCount: 4, clearValue: [0, 0, 0, 0] }, ...renderPass }\n preferredFormat = preferredFormat ?? this.deviceManager.gpu?.getPreferredCanvasFormat()\n\n this.options = {\n deviceManager,\n container,\n pixelRatio,\n preferredFormat,\n alphaMode,\n renderPass,\n }\n\n this.pixelRatio = pixelRatio ?? window.devicePixelRatio ?? 1\n this.alphaMode = alphaMode\n\n this.setTasksQueues()\n this.setRendererObjects()\n\n // create the canvas\n const isContainerCanvas = container instanceof HTMLCanvasElement\n this.canvas = isContainerCanvas ? (container as HTMLCanvasElement) : document.createElement('canvas')\n\n // needed to get container bounding box\n this.domElement = new DOMElement({\n element: container,\n priority: 5, // renderer callback need to be called first\n onSizeChanged: (boundingRect) => this.resize(boundingRect),\n })\n\n if (!isContainerCanvas) {\n // append the canvas\n this.domElement.element.appendChild(this.canvas)\n }\n\n // device is already available? create the context!\n if (this.deviceManager.device) {\n this.setContext()\n }\n }\n\n /**\n * Set {@link canvas} size\n * @param boundingRect - new {@link domElement | DOM Element} {@link DOMElement#boundingRect | bounding rectangle}\n */\n setSize(boundingRect: DOMElementBoundingRect) {\n this.canvas.style.width = Math.floor(boundingRect.width) + 'px'\n this.canvas.style.height = Math.floor(boundingRect.height) + 'px'\n\n this.canvas.width = this.getScaledDisplayBoundingRect(boundingRect).width\n this.canvas.height = this.getScaledDisplayBoundingRect(boundingRect).height\n }\n\n /**\n * Resize our {@link GPURenderer}\n * @param boundingRect - new {@link domElement | DOM Element} {@link DOMElement#boundingRect | bounding rectangle}\n */\n resize(boundingRect: DOMElementBoundingRect | null = null) {\n if (!this.domElement && !boundingRect) return\n\n if (!boundingRect) boundingRect = this.domElement.element.getBoundingClientRect()\n\n this.setSize(boundingRect)\n\n this.onResize()\n\n this._onAfterResizeCallback && this._onAfterResizeCallback()\n }\n\n /**\n * Resize all tracked objects\n */\n onResize() {\n // resize render textures first\n this.renderTextures.forEach((renderTexture) => {\n renderTexture.resize()\n })\n\n // resize render & shader passes\n this.renderPass?.resize()\n this.postProcessingPass?.resize()\n\n this.renderTargets.forEach((renderTarget) => renderTarget.resize())\n\n // force compute passes onAfterResize callback\n this.computePasses.forEach((computePass) => computePass.resize())\n\n // now resize meshes that are bound to the renderer size\n // especially useful to resize render textures\n this.pingPongPlanes.forEach((pingPongPlane) => pingPongPlane.resize(this.boundingRect))\n this.shaderPasses.forEach((shaderPass) => shaderPass.resize(this.boundingRect))\n this.meshes.forEach((mesh) => {\n if (!('domElement' in mesh)) {\n // resize meshes that do not have a bound DOM element\n mesh.resize(this.boundingRect)\n } else {\n this.onBeforeCommandEncoderCreation.add(\n () => {\n // update position for DOM meshes only if they're not currently being resized\n if (!mesh.domElement.isResizing) {\n mesh.domElement.setSize()\n }\n },\n { once: true }\n )\n }\n })\n }\n\n /**\n * Get our {@link domElement | DOM Element} {@link DOMElement#boundingRect | bounding rectangle}\n */\n get boundingRect(): DOMElementBoundingRect {\n if (!!this.domElement.boundingRect) {\n return this.domElement.boundingRect\n } else {\n const boundingRect = this.domElement.element?.getBoundingClientRect()\n return {\n top: boundingRect.top,\n right: boundingRect.right,\n bottom: boundingRect.bottom,\n left: boundingRect.left,\n width: boundingRect.width,\n height: boundingRect.height,\n x: boundingRect.x,\n y: boundingRect.y,\n }\n }\n }\n\n /**\n * Get our {@link domElement | DOM Element} {@link DOMElement#boundingRect | bounding rectangle} accounting for current {@link pixelRatio | pixel ratio}\n */\n get displayBoundingRect(): DOMElementBoundingRect {\n return this.getScaledDisplayBoundingRect(this.boundingRect)\n }\n\n /**\n * Get the display bounding rectangle accounting for current {@link pixelRatio | pixel ratio} and max texture dimensions\n * @param boundingRect - bounding rectangle to check against\n */\n getScaledDisplayBoundingRect(boundingRect: DOMElementBoundingRect): DOMElementBoundingRect {\n const devicePixelRatio = window.devicePixelRatio ?? 1\n const scaleBoundingRect = this.pixelRatio / devicePixelRatio\n\n const displayBoundingRect = Object.keys(boundingRect).reduce(\n (a, key) => ({ ...a, [key]: boundingRect[key] * scaleBoundingRect }),\n {\n x: 0,\n y: 0,\n width: 0,\n height: 0,\n top: 0,\n right: 0,\n bottom: 0,\n left: 0,\n }\n )\n\n // clamp width and height based on limits\n if (this.device) {\n displayBoundingRect.width = Math.min(this.device.limits.maxTextureDimension2D, displayBoundingRect.width)\n displayBoundingRect.height = Math.min(this.device.limits.maxTextureDimension2D, displayBoundingRect.height)\n\n displayBoundingRect.right = Math.min(\n displayBoundingRect.width + displayBoundingRect.left,\n displayBoundingRect.right\n )\n displayBoundingRect.bottom = Math.min(\n displayBoundingRect.height + displayBoundingRect.top,\n displayBoundingRect.bottom\n )\n }\n\n return displayBoundingRect\n }\n\n /* USEFUL DEVICE MANAGER OBJECTS */\n\n /**\n * Get our {@link GPUDeviceManager#device | device}\n * @readonly\n */\n get device(): GPUDevice | undefined {\n return this.deviceManager.device\n }\n\n /**\n * Get whether our {@link GPUDeviceManager} is ready (i.e. its {@link GPUDeviceManager#adapter | adapter} and {@link GPUDeviceManager#device | device} are set) its {@link context} is set and its size is set\n * @readonly\n */\n get ready(): boolean {\n return this.deviceManager.ready && !!this.context && !!this.canvas.style.width\n }\n\n /**\n * Get our {@link GPUDeviceManager#production | GPUDeviceManager production flag}\n * @readonly\n */\n get production(): boolean {\n return this.deviceManager.production\n }\n\n /**\n * Get all the created {@link GPUDeviceManager#samplers | samplers}\n * @readonly\n */\n get samplers(): Sampler[] {\n return this.deviceManager.samplers\n }\n\n /**\n * Get all the created {@link GPUDeviceManager#buffers | GPU buffers}\n * @readonly\n */\n get buffers(): GPUBuffer[] {\n return this.deviceManager.buffers\n }\n\n /**\n * Get the {@link GPUDeviceManager#pipelineManager | pipeline manager}\n * @readonly\n */\n get pipelineManager(): PipelineManager {\n return this.deviceManager.pipelineManager\n }\n\n /**\n * Get all the rendered objects (i.e. compute passes, meshes, ping pong planes and shader passes) created by the {@link GPUDeviceManager}\n * @readonly\n */\n get deviceRenderedObjects(): SceneObject[] {\n return this.deviceManager.deviceRenderedObjects\n }\n\n /**\n * Configure our {@link context} with the given options\n */\n configureContext() {\n this.context.configure({\n device: this.device,\n format: this.options.preferredFormat,\n alphaMode: this.alphaMode,\n // needed so we can copy textures for post processing usage\n usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,\n //viewFormats: []\n })\n }\n\n /**\n * Set our {@link context} if possible and set {@link renderPass | main render pass} and {@link scene}\n */\n setContext() {\n this.context = this.canvas.getContext('webgpu')\n\n if (this.device) {\n this.configureContext()\n\n this.setMainRenderPasses()\n this.setScene()\n }\n }\n\n /**\n * Called when the {@link GPUDeviceManager#device | device} is lost.\n * Force all our scene objects to lose context.\n */\n loseContext() {\n // force all our scene objects to lose context\n this.renderedObjects.forEach((sceneObject) => sceneObject.loseContext())\n }\n\n /**\n * Called when the {@link GPUDeviceManager#device | device} should be restored.\n * Configure the context again, resize the {@link RenderTarget | render targets} and {@link RenderTexture | render textures}, restore our {@link renderedObjects | rendered objects} context.\n * @async\n */\n restoreContext() {\n this.configureContext()\n\n // recreate all render textures first\n this.renderTextures.forEach((renderTexture) => {\n renderTexture.createTexture()\n })\n\n // resize render passes/recreate their textures\n this.renderPass?.resize()\n this.postProcessingPass?.resize()\n\n this.renderTargets.forEach((renderTarget) => renderTarget.resize())\n\n // restore context of all our scene objects\n this.renderedObjects.forEach((sceneObject) => sceneObject.restoreContext())\n }\n\n /* PIPELINES, SCENE & MAIN RENDER PASS */\n\n /**\n * Set our {@link renderPass | main render pass} that will be used to render the result of our draw commands back to the screen and our {@link postProcessingPass | postprocessing pass} that will be used for any additional postprocessing render passes.\n */\n setMainRenderPasses() {\n this.renderPass = new RenderPass(this, {\n label: 'Main render pass',\n targetFormat: this.options.preferredFormat,\n ...this.options.renderPass,\n } as RenderPassParams)\n\n this.postProcessingPass = new RenderPass(this, {\n label: 'Post processing render pass',\n targetFormat: this.options.preferredFormat,\n // no need to handle depth or perform MSAA on a fullscreen quad\n useDepth: false,\n sampleCount: 1,\n })\n }\n\n /**\n * Set our {@link scene}\n */\n setScene() {\n this.scene = new Scene({ renderer: this })\n }\n\n /* BUFFERS & BINDINGS */\n\n /**\n * Create a {@link GPUBuffer}\n * @param bufferDescriptor - {@link GPUBufferDescriptor | GPU buffer descriptor}\n * @returns - newly created {@link GPUBuffer}\n */\n createBuffer(bufferDescriptor: GPUBufferDescriptor): GPUBuffer {\n const buffer = this.device?.createBuffer(bufferDescriptor)\n this.deviceManager.addBuffer(buffer)\n return buffer\n }\n\n /**\n * Remove a {@link GPUBuffer} from our {@link GPUDeviceManager#buffers | GPU buffers array}\n * @param buffer - {@link GPUBuffer} to remove\n * @param [originalLabel] - original {@link GPUBuffer} label in case the buffer has been swapped and its label has changed\n */\n removeBuffer(buffer: GPUBuffer, originalLabel?: string) {\n this.deviceManager.removeBuffer(buffer, originalLabel)\n }\n\n /**\n * Write to a {@link GPUBuffer}\n * @param buffer - {@link GPUBuffer} to write to\n * @param bufferOffset - {@link GPUSize64 | buffer offset}\n * @param data - {@link BufferSource | data} to write\n */\n queueWriteBuffer(buffer: GPUBuffer, bufferOffset: GPUSize64, data: BufferSource) {\n this.device?.queue.writeBuffer(buffer, bufferOffset, data)\n }\n\n /**\n * Copy a source {@link GPUBuffer} into a destination {@link GPUBuffer}\n * @param parameters - parameters used to realize the copy\n * @param parameters.srcBuffer - source {@link GPUBuffer}\n * @param [parameters.dstBuffer] - destination {@link GPUBuffer}. Will create a new one if none provided.\n * @param [parameters.commandEncoder] - {@link GPUCommandEncoder} to use for the copy. Will create a new one and submit the command buffer if none provided.\n * @returns - destination {@link GPUBuffer} after copy\n */\n copyBufferToBuffer({\n srcBuffer,\n dstBuffer,\n commandEncoder,\n }: {\n srcBuffer: GPUBuffer\n dstBuffer?: GPUBuffer\n commandEncoder?: GPUCommandEncoder\n }): GPUBuffer | null {\n if (!srcBuffer) {\n throwWarning(`${this.type}: cannot copy to buffer because the source buffer has not been provided`)\n return null\n }\n\n if (!dstBuffer) {\n dstBuffer = this.createBuffer({\n label: this.type + ': destination copy buffer from: ' + srcBuffer.label,\n size: srcBuffer.size,\n usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,\n })\n }\n\n if (srcBuffer.mapState !== 'unmapped') {\n throwWarning(`${this.type}: Cannot copy from ${srcBuffer} because it is currently mapped`)\n return\n }\n if (dstBuffer.mapState !== 'unmapped') {\n throwWarning(`${this.type}: Cannot copy from ${dstBuffer} because it is currently mapped`)\n return\n }\n\n // if there's no command encoder provided, we'll have to create one and submit it after the copy process\n const hasCommandEncoder = !!commandEncoder\n\n if (!hasCommandEncoder) {\n commandEncoder = this.device?.createCommandEncoder({ label: 'Copy buffer command encoder' })\n !this.production && commandEncoder.pushDebugGroup('Copy buffer command encoder')\n }\n\n commandEncoder.copyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, dstBuffer.size)\n\n if (!hasCommandEncoder) {\n !this.production && commandEncoder.popDebugGroup()\n const commandBuffer = commandEncoder.finish()\n this.device?.queue.submit([commandBuffer])\n }\n\n return dstBuffer\n }\n\n /* BIND GROUPS & LAYOUTS */\n\n /**\n * Get all created {@link AllowedBindGroups | bind group} tracked by our {@link GPUDeviceManager}\n * @readonly\n */\n get bindGroups(): AllowedBindGroups[] {\n return this.deviceManager.bindGroups\n }\n\n /**\n * Add a {@link AllowedBindGroups | bind group} to our {@link GPUDeviceManager#bindGroups | bind groups array}\n * @param bindGroup - {@link AllowedBindGroups | bind group} to add\n */\n addBindGroup(bindGroup: AllowedBindGroups) {\n this.deviceManager.addBindGroup(bindGroup)\n }\n\n /**\n * Remove a {@link AllowedBindGroups | bind group} from our {@link GPUDeviceManager#bindGroups | bind groups array}\n * @param bindGroup - {@link AllowedBindGroups | bind group} to remove\n */\n removeBindGroup(bindGroup: AllowedBindGroups) {\n this.deviceManager.removeBindGroup(bindGroup)\n }\n\n /**\n * Create a {@link GPUBindGroupLayout}\n * @param bindGroupLayoutDescriptor - {@link GPUBindGroupLayoutDescriptor | GPU bind group layout descriptor}\n * @returns - newly created {@link GPUBindGroupLayout}\n */\n createBindGroupLayout(bindGroupLayoutDescriptor: GPUBindGroupLayoutDescriptor): GPUBindGroupLayout {\n return this.device?.createBindGroupLayout(bindGroupLayoutDescriptor)\n }\n\n /**\n * Create a {@link GPUBindGroup}\n * @param bindGroupDescriptor - {@link GPUBindGroupDescriptor | GPU bind group descriptor}\n * @returns - newly created {@link GPUBindGroup}\n */\n createBindGroup(bindGroupDescriptor: GPUBindGroupDescriptor): GPUBindGroup {\n return this.device?.createBindGroup(bindGroupDescriptor)\n }\n\n /* SHADERS & PIPELINES */\n\n /**\n * Create a {@link GPUShaderModule}\n * @param shaderModuleDescriptor - {@link shaderModuleDescriptor | shader module descriptor}\n * @returns - newly created {@link GPUShaderModule}\n */\n createShaderModule(shaderModuleDescriptor: GPUShaderModuleDescriptor): GPUShaderModule {\n return this.device?.createShaderModule(shaderModuleDescriptor)\n }\n\n /**\n * Create a {@link GPUPipelineLayout}\n * @param pipelineLayoutDescriptor - {@link GPUPipelineLayoutDescriptor | GPU pipeline layout descriptor}\n * @returns - newly created {@link GPUPipelineLayout}\n */\n createPipelineLayout(pipelineLayoutDescriptor: GPUPipelineLayoutDescriptor): GPUPipelineLayout {\n return this.device?.createPipelineLayout(pipelineLayoutDescriptor)\n }\n\n /**\n * Create a {@link GPURenderPipeline}\n * @param pipelineDescriptor - {@link GPURenderPipelineDescriptor | GPU render pipeline descriptor}\n * @returns - newly created {@link GPURenderPipeline}\n */\n createRenderPipeline(pipelineDescriptor: GPURenderPipelineDescriptor): GPURenderPipeline {\n return this.device?.createRenderPipeline(pipelineDescriptor)\n }\n\n /**\n * Asynchronously create a {@link GPURenderPipeline}\n * @async\n * @param pipelineDescriptor - {@link GPURenderPipelineDescriptor | GPU render pipeline descriptor}\n * @returns - newly created {@link GPURenderPipeline}\n */\n async createRenderPipelineAsync(pipelineDescriptor: GPURenderPipelineDescriptor): Promise {\n return await this.device?.createRenderPipelineAsync(pipelineDescriptor)\n }\n\n /**\n * Create a {@link GPUComputePipeline}\n * @param pipelineDescriptor - {@link GPUComputePipelineDescriptor | GPU compute pipeline descriptor}\n * @returns - newly created {@link GPUComputePipeline}\n */\n createComputePipeline(pipelineDescriptor: GPUComputePipelineDescriptor): GPUComputePipeline {\n return this.device?.createComputePipeline(pipelineDescriptor)\n }\n\n /**\n * Asynchronously create a {@link GPUComputePipeline}\n * @async\n * @param pipelineDescriptor - {@link GPUComputePipelineDescriptor | GPU compute pipeline descriptor}\n * @returns - newly created {@link GPUComputePipeline}\n */\n async createComputePipelineAsync(pipelineDescriptor: GPUComputePipelineDescriptor): Promise {\n return await this.device?.createComputePipelineAsync(pipelineDescriptor)\n }\n\n /* TEXTURES */\n\n /**\n * Get all created {@link Texture} tracked by our {@link GPUDeviceManager}\n * @readonly\n */\n get textures(): Texture[] {\n return this.deviceManager.textures\n }\n\n /**\n * Add a {@link Texture} to our {@link GPUDeviceManager#textures | textures array}\n * @param texture - {@link Texture} to add\n */\n addTexture(texture: Texture) {\n this.deviceManager.addTexture(texture)\n }\n\n /**\n * Remove a {@link Texture} from our {@link GPUDeviceManager#textures | textures array}\n * @param texture - {@link Texture} to remove\n */\n removeTexture(texture: Texture) {\n this.deviceManager.removeTexture(texture)\n }\n\n /**\n * Add a {@link RenderTexture} to our {@link renderTextures} array\n * @param texture - {@link RenderTexture} to add\n */\n addRenderTexture(texture: RenderTexture) {\n this.renderTextures.push(texture)\n }\n\n /**\n * Remove a {@link RenderTexture} from our {@link renderTextures} array\n * @param texture - {@link RenderTexture} to remove\n */\n removeRenderTexture(texture: RenderTexture) {\n this.renderTextures = this.renderTextures.filter((t) => t.uuid !== texture.uuid)\n }\n\n /**\n * Create a {@link GPUTexture}\n * @param textureDescriptor - {@link GPUTextureDescriptor | GPU texture descriptor}\n * @returns - newly created {@link GPUTexture}\n */\n createTexture(textureDescriptor: GPUTextureDescriptor): GPUTexture {\n return this.device?.createTexture(textureDescriptor)\n }\n\n /**\n * Upload a {@link Texture#texture | texture} to the GPU\n * @param texture - {@link Texture} class object with the {@link Texture#texture | texture} to upload\n */\n uploadTexture(texture: Texture) {\n this.deviceManager.uploadTexture(texture)\n }\n\n /**\n * Import a {@link GPUExternalTexture}\n * @param video - {@link HTMLVideoElement} source\n * @returns - {@link GPUExternalTexture}\n */\n importExternalTexture(video: HTMLVideoElement): GPUExternalTexture {\n // TODO WebCodecs may be the way to go when time comes!\n // https://developer.chrome.com/blog/new-in-webgpu-113/#use-webcodecs-videoframe-source-in-importexternaltexture\n // see onVideoFrameCallback method in Texture class\n // const videoFrame = new VideoFrame(video)\n // return this.device?.importExternalTexture({ source: videoFrame })\n return this.device?.importExternalTexture({ source: video })\n }\n\n /**\n * Check if a {@link Sampler} has already been created with the same {@link Sampler#options | parameters}.\n * Use it if found, else create a new one and add it to the {@link GPUDeviceManager#samplers | samplers array}.\n * @param sampler - {@link Sampler} to create\n * @returns - the {@link GPUSampler}\n */\n createSampler(sampler: Sampler): GPUSampler {\n const existingSampler = this.samplers.find((existingSampler) => {\n return JSON.stringify(existingSampler.options) === JSON.stringify(sampler.options) && existingSampler.sampler\n })\n\n if (existingSampler) {\n return existingSampler.sampler\n } else {\n const { type, ...samplerOptions } = sampler.options\n const gpuSampler: GPUSampler = this.device?.createSampler({\n label: sampler.label,\n ...samplerOptions,\n })\n\n this.deviceManager.addSampler(sampler)\n\n return gpuSampler\n }\n }\n\n /**\n * Remove a {@link Sampler} from our {@link GPUDeviceManager#samplers | samplers array}\n * @param sampler - {@link Sampler} to remove\n */\n removeSampler(sampler: Sampler) {\n this.deviceManager.removeSampler(sampler)\n }\n\n /* OBJECTS & TASKS */\n\n /**\n * Set different tasks queue managers to execute callbacks at different phases of our render call:\n * - {@link onBeforeCommandEncoderCreation}: callbacks executed before the creation of the command encoder\n * - {@link onBeforeRenderScene}: callbacks executed after the creation of the command encoder and before rendering the {@link Scene}\n * - {@link onAfterRenderScene}: callbacks executed after the creation of the command encoder and after rendering the {@link Scene}\n * - {@link onAfterCommandEncoderSubmission}: callbacks executed after the submission of the command encoder\n */\n setTasksQueues() {\n this.onBeforeCommandEncoderCreation = new TasksQueueManager()\n this.onBeforeRenderScene = new TasksQueueManager()\n this.onAfterRenderScene = new TasksQueueManager()\n this.onAfterCommandEncoderSubmission = new TasksQueueManager()\n }\n\n /**\n * Set all objects arrays that we'll keep track of\n */\n setRendererObjects() {\n // keep track of compute passes, meshes, etc.\n this.computePasses = []\n this.pingPongPlanes = []\n this.shaderPasses = []\n this.renderTargets = []\n this.meshes = []\n this.renderTextures = []\n }\n\n /**\n * Get all this {@link GPURenderer} rendered objects (i.e. compute passes, meshes, ping pong planes and shader passes)\n * @readonly\n */\n get renderedObjects(): SceneObject[] {\n return [...this.computePasses, ...this.meshes, ...this.shaderPasses, ...this.pingPongPlanes]\n }\n\n /**\n * Get all objects ({@link RenderedMesh | rendered meshes} or {@link ComputePass | compute passes}) using a given {@link AllowedBindGroups | bind group}.\n * Useful to know if a resource is used by multiple objects and if it is safe to destroy it or not.\n * @param bindGroup - {@link AllowedBindGroups | bind group} to check\n */\n getObjectsByBindGroup(bindGroup: AllowedBindGroups): undefined | SceneObject[] {\n return this.deviceRenderedObjects.filter((object) => {\n return [\n ...object.material.bindGroups,\n ...object.material.inputsBindGroups,\n ...object.material.clonedBindGroups,\n ].some((bG) => bG.uuid === bindGroup.uuid)\n })\n }\n\n /**\n * Get all objects ({@link RenderedMesh | rendered meshes} or {@link ComputePass | compute passes}) using a given {@link Texture} or {@link RenderTexture}.\n * Useful to know if a resource is used by multiple objects and if it is safe to destroy it or not.\n * @param texture - {@link Texture} or {@link RenderTexture} to check\n */\n getObjectsByTexture(texture: Texture | RenderTexture): undefined | SceneObject[] {\n return this.deviceRenderedObjects.filter((object) => {\n return [...object.material.textures, ...object.material.renderTextures].some((t) => t.uuid === texture.uuid)\n })\n }\n\n /* EVENTS */\n\n /**\n * Assign a callback function to _onBeforeRenderCallback\n * @param callback - callback to run just before the {@link render} method will be executed\n * @returns - our {@link GPURenderer}\n */\n onBeforeRender(callback: (commandEncoder?: GPUCommandEncoder) => void) {\n if (callback) {\n this._onBeforeRenderCallback = callback\n }\n\n return this\n }\n\n /**\n * Assign a callback function to _onAfterRenderCallback\n * @param callback - callback to run just after the {@link render} method has been executed\n * @returns - our {@link GPURenderer}\n */\n onAfterRender(callback: (commandEncoder?: GPUCommandEncoder) => void) {\n if (callback) {\n this._onAfterRenderCallback = callback\n }\n\n return this\n }\n\n /**\n * Assign a callback function to _onAfterResizeCallback\n * @param callback - callback to run just after the {@link GPURenderer} has been resized\n * @returns - our {@link GPURenderer}\n */\n onAfterResize(callback: (commandEncoder?: GPUCommandEncoder) => void) {\n if (callback) {\n this._onAfterResizeCallback = callback\n }\n\n return this\n }\n\n /* RENDER */\n\n /**\n * Render a single {@link ComputePass}\n * @param commandEncoder - current {@link GPUCommandEncoder}\n * @param computePass - {@link ComputePass}\n */\n renderSingleComputePass(commandEncoder: GPUCommandEncoder, computePass: ComputePass) {\n const pass = commandEncoder.beginComputePass()\n computePass.render(pass)\n pass.end()\n\n computePass.copyBufferToResult(commandEncoder)\n }\n\n /**\n * Render a single {@link RenderedMesh | Mesh}\n * @param commandEncoder - current {@link GPUCommandEncoder}\n * @param mesh - {@link RenderedMesh | Mesh} to render\n */\n renderSingleMesh(commandEncoder: GPUCommandEncoder, mesh: RenderedMesh) {\n const pass = commandEncoder.beginRenderPass(this.renderPass.descriptor)\n mesh.render(pass)\n pass.end()\n }\n\n /**\n * Render an array of objects (either {@link RenderedMesh | Meshes} or {@link ComputePass}) once. This method won't call any of the renderer render hooks like {@link onBeforeRender}, {@link onAfterRender}\n * @param objects - Array of {@link RenderedMesh | Meshes} or {@link ComputePass} to render\n */\n renderOnce(objects: SceneObject[]) {\n const commandEncoder = this.device?.createCommandEncoder({\n label: 'Render once command encoder',\n })\n !this.production && commandEncoder.pushDebugGroup('Render once command encoder')\n\n this.pipelineManager.resetCurrentPipeline()\n\n objects.forEach((object) => {\n if (object instanceof ComputePass) {\n this.renderSingleComputePass(commandEncoder, object)\n } else {\n this.renderSingleMesh(commandEncoder, object)\n }\n })\n\n !this.production && commandEncoder.popDebugGroup()\n const commandBuffer = commandEncoder.finish()\n this.device?.queue.submit([commandBuffer])\n\n this.pipelineManager.resetCurrentPipeline()\n }\n\n /**\n * Force to clear a {@link GPURenderer} content to its {@link RenderPass#options.clearValue | clear value} by rendering and empty pass.\n * @param commandEncoder\n */\n forceClear(commandEncoder?: GPUCommandEncoder) {\n // if there's no command encoder provided, we'll have to create one and submit it after the copy process\n const hasCommandEncoder = !!commandEncoder\n\n if (!hasCommandEncoder) {\n commandEncoder = this.device?.createCommandEncoder({ label: 'Force clear command encoder' })\n !this.production && commandEncoder.pushDebugGroup('Force clear command encoder')\n }\n\n this.renderPass.updateView()\n const pass = commandEncoder.beginRenderPass(this.renderPass.descriptor)\n pass.end()\n\n if (!hasCommandEncoder) {\n !this.production && commandEncoder.popDebugGroup()\n const commandBuffer = commandEncoder.finish()\n this.device?.queue.submit([commandBuffer])\n }\n }\n\n /**\n * Called by the {@link GPUDeviceManager#render | GPUDeviceManager render method} before the {@link GPUCommandEncoder} has been created\n */\n onBeforeCommandEncoder() {\n if (!this.ready) return\n // now render!\n this.onBeforeCommandEncoderCreation.execute()\n }\n\n /**\n * Called by the {@link GPUDeviceManager#render | GPUDeviceManager render method} after the {@link GPUCommandEncoder} has been created.\n */\n onAfterCommandEncoder() {\n if (!this.ready) return\n\n this.onAfterCommandEncoderSubmission.execute()\n }\n\n /**\n * Called at each draw call to render our scene and its content\n * @param commandEncoder - current {@link GPUCommandEncoder}\n */\n render(commandEncoder: GPUCommandEncoder) {\n if (!this.ready) return\n\n this._onBeforeRenderCallback && this._onBeforeRenderCallback(commandEncoder)\n this.onBeforeRenderScene.execute(commandEncoder)\n\n this.scene?.render(commandEncoder)\n\n this._onAfterRenderCallback && this._onAfterRenderCallback(commandEncoder)\n this.onAfterRenderScene.execute(commandEncoder)\n }\n\n /**\n * Destroy our {@link GPURenderer} and everything that needs to be destroyed as well\n */\n destroy() {\n this.domElement?.destroy()\n\n // destroy render passes\n this.renderPass?.destroy()\n this.postProcessingPass?.destroy()\n\n this.renderTargets.forEach((renderTarget) => renderTarget.destroy())\n this.renderedObjects.forEach((sceneObject) => sceneObject.remove())\n\n this.renderTextures.forEach((texture) => texture.destroy())\n\n this.context?.unconfigure()\n }\n}\n"],"names":["existingSampler"],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/renderers/utils.mjs b/dist/esm/core/renderers/utils.mjs new file mode 100644 index 000000000..4521764b6 --- /dev/null +++ b/dist/esm/core/renderers/utils.mjs @@ -0,0 +1,137 @@ +import { throwError } from '../../utils/utils.mjs'; + +const formatRendererError = (renderer, rendererType = "GPURenderer", type) => { + const error = type ? `Unable to create ${type} because the ${rendererType} is not defined: ${renderer}` : `The ${rendererType} is not defined: ${renderer}`; + throwError(error); +}; +const isRenderer = (renderer, type) => { + const isRenderer2 = renderer && (renderer.type === "GPURenderer" || renderer.type === "GPUCameraRenderer" || renderer.type === "GPUCurtainsRenderer"); + if (!isRenderer2) { + formatRendererError(renderer, "GPURenderer", type); + } + return isRenderer2; +}; +const isCameraRenderer = (renderer, type) => { + const isCameraRenderer2 = renderer && (renderer.type === "GPUCameraRenderer" || renderer.type === "GPUCurtainsRenderer"); + if (!isCameraRenderer2) { + formatRendererError(renderer, "GPUCameraRenderer", type); + } + return isCameraRenderer2; +}; +const isCurtainsRenderer = (renderer, type) => { + const isCurtainsRenderer2 = renderer && renderer.type === "GPUCurtainsRenderer"; + if (!isCurtainsRenderer2) { + formatRendererError(renderer, "GPUCurtainsRenderer", type); + } + return isCurtainsRenderer2; +}; +const generateMips = /* @__PURE__ */ (() => { + let sampler; + let module; + const pipelineByFormat = {}; + return function generateMips2(device, texture) { + if (!module) { + module = device.createShaderModule({ + label: "textured quad shaders for mip level generation", + code: ` + struct VSOutput { + @builtin(position) position: vec4f, + @location(0) texcoord: vec2f, + }; + + @vertex fn vs( + @builtin(vertex_index) vertexIndex : u32 + ) -> VSOutput { + var pos = array( + + vec2f( 0.0, 0.0), // center + vec2f( 1.0, 0.0), // right, center + vec2f( 0.0, 1.0), // center, top + + // 2st triangle + vec2f( 0.0, 1.0), // center, top + vec2f( 1.0, 0.0), // right, center + vec2f( 1.0, 1.0), // right, top + ); + + var vsOutput: VSOutput; + let xy = pos[vertexIndex]; + vsOutput.position = vec4f(xy * 2.0 - 1.0, 0.0, 1.0); + vsOutput.texcoord = vec2f(xy.x, 1.0 - xy.y); + return vsOutput; + } + + @group(0) @binding(0) var ourSampler: sampler; + @group(0) @binding(1) var ourTexture: texture_2d; + + @fragment fn fs(fsInput: VSOutput) -> @location(0) vec4f { + return textureSample(ourTexture, ourSampler, fsInput.texcoord); + } + ` + }); + sampler = device.createSampler({ + minFilter: "linear" + }); + } + if (!pipelineByFormat[texture.format]) { + pipelineByFormat[texture.format] = device.createRenderPipeline({ + label: "mip level generator pipeline", + layout: "auto", + vertex: { + module, + entryPoint: "vs" + }, + fragment: { + module, + entryPoint: "fs", + targets: [{ format: texture.format }] + } + }); + } + const pipeline = pipelineByFormat[texture.format]; + const encoder = device.createCommandEncoder({ + label: "mip gen encoder" + }); + let width = texture.width; + let height = texture.height; + let baseMipLevel = 0; + while (width > 1 || height > 1) { + width = Math.max(1, width / 2 | 0); + height = Math.max(1, height / 2 | 0); + const bindGroup = device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: sampler }, + { + binding: 1, + resource: texture.createView({ + baseMipLevel, + mipLevelCount: 1 + }) + } + ] + }); + ++baseMipLevel; + const renderPassDescriptor = { + label: "our basic canvas renderPass", + colorAttachments: [ + { + view: texture.createView({ baseMipLevel, mipLevelCount: 1 }), + loadOp: "clear", + storeOp: "store" + } + ] + }; + const pass = encoder.beginRenderPass(renderPassDescriptor); + pass.setPipeline(pipeline); + pass.setBindGroup(0, bindGroup); + pass.draw(6); + pass.end(); + } + const commandBuffer = encoder.finish(); + device.queue.submit([commandBuffer]); + }; +})(); + +export { generateMips, isCameraRenderer, isCurtainsRenderer, isRenderer }; +//# sourceMappingURL=utils.mjs.map diff --git a/dist/esm/core/renderers/utils.mjs.map b/dist/esm/core/renderers/utils.mjs.map new file mode 100644 index 000000000..fc6aa6927 --- /dev/null +++ b/dist/esm/core/renderers/utils.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"utils.mjs","sources":["../../../../src/core/renderers/utils.ts"],"sourcesContent":["import { throwError } from '../../utils/utils'\nimport { GPURenderer } from './GPURenderer'\nimport { GPUCameraRenderer } from './GPUCameraRenderer'\nimport { GPUCurtainsRenderer } from '../../curtains/renderers/GPUCurtainsRenderer'\nimport { WritableBufferBinding } from '../bindings/WritableBufferBinding'\n\n/**\n * A Renderer could be either a {@link GPURenderer}, a {@link GPUCameraRenderer} or a {@link GPUCurtainsRenderer}\n * @type {Renderer}\n */\nexport type Renderer = GPUCurtainsRenderer | GPUCameraRenderer | GPURenderer\n/**\n * A CameraRenderer could be either a {@link GPUCameraRenderer} or a {@link GPUCurtainsRenderer}\n * @type {CameraRenderer}\n */\nexport type CameraRenderer = GPUCurtainsRenderer | GPUCameraRenderer\n\n/**\n * Format a renderer error based on given renderer, renderer type and object type\n * @param renderer - renderer that failed the test\n * @param rendererType - expected renderer type\n * @param type - object type\n */\nconst formatRendererError = (renderer: Renderer, rendererType = 'GPURenderer', type: string | null): void => {\n const error = type\n ? `Unable to create ${type} because the ${rendererType} is not defined: ${renderer}`\n : `The ${rendererType} is not defined: ${renderer}`\n throwError(error)\n}\n\n/**\n * Check if the given renderer is a {@link Renderer}\n * @param renderer - renderer to test\n * @param type - object type used to format the error if needed\n * @returns - whether the given renderer is a {@link Renderer}\n */\nexport const isRenderer = (renderer: Renderer | undefined, type: string | null): boolean => {\n const isRenderer =\n renderer &&\n (renderer.type === 'GPURenderer' ||\n renderer.type === 'GPUCameraRenderer' ||\n renderer.type === 'GPUCurtainsRenderer')\n\n if (!isRenderer) {\n formatRendererError(renderer, 'GPURenderer', type)\n }\n\n return isRenderer\n}\n\n/**\n * Check if the given renderer is a {@link CameraRenderer}\n * @param renderer - renderer to test\n * @param type - object type used to format the error if needed\n * @returns - whether the given renderer is a {@link CameraRenderer}\n */\nexport const isCameraRenderer = (renderer: CameraRenderer | undefined, type: string | null): boolean => {\n const isCameraRenderer =\n renderer && (renderer.type === 'GPUCameraRenderer' || renderer.type === 'GPUCurtainsRenderer')\n\n if (!isCameraRenderer) {\n formatRendererError(renderer, 'GPUCameraRenderer', type)\n }\n\n return isCameraRenderer\n}\n\n/**\n * Check if the given renderer is a {@link GPUCurtainsRenderer}\n * @param renderer - renderer to test\n * @param type - object type used to format the error if needed\n * @returns - whether the given renderer is a {@link GPUCurtainsRenderer}\n */\nexport const isCurtainsRenderer = (renderer: GPUCurtainsRenderer | undefined, type: string | null): boolean => {\n const isCurtainsRenderer = renderer && renderer.type === 'GPUCurtainsRenderer'\n\n if (!isCurtainsRenderer) {\n formatRendererError(renderer, 'GPUCurtainsRenderer', type)\n }\n\n return isCurtainsRenderer\n}\n\n/**\n * Helper to generate mips on the GPU\n * Taken from https://webgpufundamentals.org/webgpu/lessons/webgpu-importing-textures.html\n */\nexport const generateMips = (() => {\n let sampler\n let module\n const pipelineByFormat = {}\n\n return function generateMips(device: GPUDevice, texture: GPUTexture) {\n if (!module) {\n module = device.createShaderModule({\n label: 'textured quad shaders for mip level generation',\n code: `\n struct VSOutput {\n @builtin(position) position: vec4f,\n @location(0) texcoord: vec2f,\n };\n\n @vertex fn vs(\n @builtin(vertex_index) vertexIndex : u32\n ) -> VSOutput {\n var pos = array(\n\n vec2f( 0.0, 0.0), // center\n vec2f( 1.0, 0.0), // right, center\n vec2f( 0.0, 1.0), // center, top\n\n // 2st triangle\n vec2f( 0.0, 1.0), // center, top\n vec2f( 1.0, 0.0), // right, center\n vec2f( 1.0, 1.0), // right, top\n );\n\n var vsOutput: VSOutput;\n let xy = pos[vertexIndex];\n vsOutput.position = vec4f(xy * 2.0 - 1.0, 0.0, 1.0);\n vsOutput.texcoord = vec2f(xy.x, 1.0 - xy.y);\n return vsOutput;\n }\n\n @group(0) @binding(0) var ourSampler: sampler;\n @group(0) @binding(1) var ourTexture: texture_2d;\n\n @fragment fn fs(fsInput: VSOutput) -> @location(0) vec4f {\n return textureSample(ourTexture, ourSampler, fsInput.texcoord);\n }\n `,\n })\n\n sampler = device.createSampler({\n minFilter: 'linear',\n })\n }\n\n if (!pipelineByFormat[texture.format]) {\n pipelineByFormat[texture.format] = device.createRenderPipeline({\n label: 'mip level generator pipeline',\n layout: 'auto',\n vertex: {\n module,\n entryPoint: 'vs',\n },\n fragment: {\n module,\n entryPoint: 'fs',\n targets: [{ format: texture.format }],\n },\n })\n }\n const pipeline = pipelineByFormat[texture.format]\n\n const encoder = device.createCommandEncoder({\n label: 'mip gen encoder',\n })\n\n let width = texture.width\n let height = texture.height\n let baseMipLevel = 0\n while (width > 1 || height > 1) {\n width = Math.max(1, (width / 2) | 0)\n height = Math.max(1, (height / 2) | 0)\n\n const bindGroup = device.createBindGroup({\n layout: pipeline.getBindGroupLayout(0),\n entries: [\n { binding: 0, resource: sampler },\n {\n binding: 1,\n resource: texture.createView({\n baseMipLevel,\n mipLevelCount: 1,\n }),\n },\n ],\n })\n\n ++baseMipLevel\n\n const renderPassDescriptor = {\n label: 'our basic canvas renderPass',\n colorAttachments: [\n {\n view: texture.createView({ baseMipLevel, mipLevelCount: 1 }),\n loadOp: 'clear',\n storeOp: 'store',\n },\n ],\n }\n\n const pass = encoder.beginRenderPass(renderPassDescriptor as GPURenderPassDescriptor)\n pass.setPipeline(pipeline)\n pass.setBindGroup(0, bindGroup)\n pass.draw(6) // call our vertex shader 6 times\n pass.end()\n }\n\n const commandBuffer = encoder.finish()\n device.queue.submit([commandBuffer])\n }\n})()\n"],"names":["isRenderer","isCameraRenderer","isCurtainsRenderer","generateMips"],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/samplers/Sampler.mjs b/dist/esm/core/samplers/Sampler.mjs new file mode 100644 index 000000000..948b81559 --- /dev/null +++ b/dist/esm/core/samplers/Sampler.mjs @@ -0,0 +1,70 @@ +import { isRenderer } from '../renderers/utils.mjs'; +import { SamplerBinding } from '../bindings/SamplerBinding.mjs'; +import { generateUUID, throwWarning } from '../../utils/utils.mjs'; + +class Sampler { + /** + * Sampler constructor + * @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link Sampler} + * @param parameters - {@link SamplerParams | parameters} used to create this {@link Sampler} + */ + constructor(renderer, { + label = "Sampler", + name, + addressModeU = "repeat", + addressModeV = "repeat", + magFilter = "linear", + minFilter = "linear", + mipmapFilter = "linear", + maxAnisotropy = 1, + type = "filtering", + compare + } = {}) { + this.type = "Sampler"; + this.uuid = generateUUID(); + renderer = renderer && renderer.renderer || renderer; + isRenderer(renderer, label ? label + " " + this.type : this.type); + this.renderer = renderer; + this.label = label; + if (!name && !this.renderer.production) { + name = "sampler" + this.renderer.samplers.length; + throwWarning( + `Sampler: you are trying to create a sampler without the mandatory name parameter. A default name will be used instead: ${name}` + ); + } + this.name = name; + this.options = { + addressModeU, + addressModeV, + magFilter, + minFilter, + mipmapFilter, + maxAnisotropy, + type, + ...compare !== void 0 && { compare } + }; + this.createSampler(); + this.createBinding(); + } + /** + * Set the {@link GPUSampler} + */ + createSampler() { + this.sampler = this.renderer.createSampler(this); + } + /** + * Set the {@link SamplerBinding | binding} + */ + createBinding() { + this.binding = new SamplerBinding({ + label: this.label, + name: this.name, + bindingType: "sampler", + sampler: this.sampler, + type: this.options.type + }); + } +} + +export { Sampler }; +//# sourceMappingURL=Sampler.mjs.map diff --git a/dist/esm/core/samplers/Sampler.mjs.map b/dist/esm/core/samplers/Sampler.mjs.map new file mode 100644 index 000000000..45aa9ebd1 --- /dev/null +++ b/dist/esm/core/samplers/Sampler.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"Sampler.mjs","sources":["../../../../src/core/samplers/Sampler.ts"],"sourcesContent":["import { isRenderer, Renderer } from '../renderers/utils'\nimport { SamplerBinding } from '../bindings/SamplerBinding'\nimport { generateUUID, throwWarning } from '../../utils/utils'\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\n\n/** Options used to create a {@link Sampler} */\nexport interface SamplerOptions extends Partial, GPUSamplerBindingLayout {}\n\n/**\n * Parameters used to create a {@link Sampler}\n */\nexport interface SamplerParams extends SamplerOptions {\n /** Name of the {@link Sampler} to use in the {@link SamplerBinding | binding} */\n name: string\n}\n\n/**\n * Used to create a {@link GPUSampler} and its associated {@link SamplerBinding}.\n *\n * @example\n * ```javascript\n * // set our main GPUCurtains instance\n * const gpuCurtains = new GPUCurtains({\n * container: '#canvas' // selector of our WebGPU canvas container\n * })\n *\n * // set the GPU device\n * // note this is asynchronous\n * await gpuCurtains.setDevice()\n *\n * const mirrorSampler = new Sampler(gpuCurtains, {\n * label: 'Mirror sampler',\n * name: 'mirrorSampler',\n * addressModeU: 'mirror-repeat',\n * addressModeV: 'mirror-repeat',\n * })\n * ```\n */\nexport class Sampler {\n /** The type of the {@link Sampler} */\n type: string\n /** The universal unique id of this {@link Sampler} */\n readonly uuid: string\n /** {@link Renderer} used by this {@link Sampler} */\n renderer: Renderer\n /** The label of the {@link Sampler}, used to create the {@link GPUSampler} for debugging purpose */\n label: string\n /** Name of the {@link Sampler} to use in the {@link SamplerBinding | binding} */\n name: string\n /** Options used to create this {@link Sampler} */\n options: SamplerOptions\n\n /** {@link GPUSampler} */\n sampler: GPUSampler\n /** {@link SamplerBinding | binding} to pass to a {@link core/bindGroups/TextureBindGroup.TextureBindGroup | bind group} */\n binding: SamplerBinding\n\n /**\n * Sampler constructor\n * @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link Sampler}\n * @param parameters - {@link SamplerParams | parameters} used to create this {@link Sampler}\n */\n constructor(\n renderer: GPUCurtains | Renderer,\n {\n label = 'Sampler',\n name,\n addressModeU = 'repeat',\n addressModeV = 'repeat',\n magFilter = 'linear',\n minFilter = 'linear',\n mipmapFilter = 'linear',\n maxAnisotropy = 1,\n type = 'filtering',\n compare,\n } = {} as SamplerParams\n ) {\n this.type = 'Sampler'\n this.uuid = generateUUID()\n\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\n\n isRenderer(renderer, label ? label + ' ' + this.type : this.type)\n\n this.renderer = renderer\n\n this.label = label\n\n if (!name && !this.renderer.production) {\n name = 'sampler' + this.renderer.samplers.length\n throwWarning(\n `Sampler: you are trying to create a sampler without the mandatory name parameter. A default name will be used instead: ${name}`\n )\n }\n\n this.name = name\n\n this.options = {\n addressModeU,\n addressModeV,\n magFilter,\n minFilter,\n mipmapFilter,\n maxAnisotropy,\n type,\n ...(compare !== undefined && { compare }),\n } as SamplerOptions\n\n this.createSampler()\n this.createBinding()\n }\n\n /**\n * Set the {@link GPUSampler}\n */\n createSampler() {\n this.sampler = this.renderer.createSampler(this)\n }\n\n /**\n * Set the {@link SamplerBinding | binding}\n */\n createBinding() {\n this.binding = new SamplerBinding({\n label: this.label,\n name: this.name,\n bindingType: 'sampler',\n sampler: this.sampler,\n type: this.options.type,\n })\n }\n}\n"],"names":[],"mappings":";;;;AAsCO,MAAM,OAAQ,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBnB,YACE,QACA,EAAA;AAAA,IACE,KAAQ,GAAA,SAAA;AAAA,IACR,IAAA;AAAA,IACA,YAAe,GAAA,QAAA;AAAA,IACf,YAAe,GAAA,QAAA;AAAA,IACf,SAAY,GAAA,QAAA;AAAA,IACZ,SAAY,GAAA,QAAA;AAAA,IACZ,YAAe,GAAA,QAAA;AAAA,IACf,aAAgB,GAAA,CAAA;AAAA,IAChB,IAAO,GAAA,WAAA;AAAA,IACP,OAAA;AAAA,GACF,GAAI,EACJ,EAAA;AACA,IAAA,IAAA,CAAK,IAAO,GAAA,SAAA,CAAA;AACZ,IAAA,IAAA,CAAK,OAAO,YAAa,EAAA,CAAA;AAGzB,IAAY,QAAA,GAAA,QAAA,IAAa,SAAyB,QAAc,IAAA,QAAA,CAAA;AAEhE,IAAA,UAAA,CAAW,UAAU,KAAQ,GAAA,KAAA,GAAQ,MAAM,IAAK,CAAA,IAAA,GAAO,KAAK,IAAI,CAAA,CAAA;AAEhE,IAAA,IAAA,CAAK,QAAW,GAAA,QAAA,CAAA;AAEhB,IAAA,IAAA,CAAK,KAAQ,GAAA,KAAA,CAAA;AAEb,IAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,IAAA,CAAK,SAAS,UAAY,EAAA;AACtC,MAAO,IAAA,GAAA,SAAA,GAAY,IAAK,CAAA,QAAA,CAAS,QAAS,CAAA,MAAA,CAAA;AAC1C,MAAA,YAAA;AAAA,QACE,0HAA0H,IAAI,CAAA,CAAA;AAAA,OAChI,CAAA;AAAA,KACF;AAEA,IAAA,IAAA,CAAK,IAAO,GAAA,IAAA,CAAA;AAEZ,IAAA,IAAA,CAAK,OAAU,GAAA;AAAA,MACb,YAAA;AAAA,MACA,YAAA;AAAA,MACA,SAAA;AAAA,MACA,SAAA;AAAA,MACA,YAAA;AAAA,MACA,aAAA;AAAA,MACA,IAAA;AAAA,MACA,GAAI,OAAA,KAAY,KAAa,CAAA,IAAA,EAAE,OAAQ,EAAA;AAAA,KACzC,CAAA;AAEA,IAAA,IAAA,CAAK,aAAc,EAAA,CAAA;AACnB,IAAA,IAAA,CAAK,aAAc,EAAA,CAAA;AAAA,GACrB;AAAA;AAAA;AAAA;AAAA,EAKA,aAAgB,GAAA;AACd,IAAA,IAAA,CAAK,OAAU,GAAA,IAAA,CAAK,QAAS,CAAA,aAAA,CAAc,IAAI,CAAA,CAAA;AAAA,GACjD;AAAA;AAAA;AAAA;AAAA,EAKA,aAAgB,GAAA;AACd,IAAK,IAAA,CAAA,OAAA,GAAU,IAAI,cAAe,CAAA;AAAA,MAChC,OAAO,IAAK,CAAA,KAAA;AAAA,MACZ,MAAM,IAAK,CAAA,IAAA;AAAA,MACX,WAAa,EAAA,SAAA;AAAA,MACb,SAAS,IAAK,CAAA,OAAA;AAAA,MACd,IAAA,EAAM,KAAK,OAAQ,CAAA,IAAA;AAAA,KACpB,CAAA,CAAA;AAAA,GACH;AACF;;;;"} \ No newline at end of file diff --git a/dist/esm/core/scenes/Scene.mjs b/dist/esm/core/scenes/Scene.mjs new file mode 100644 index 000000000..abd8941e5 --- /dev/null +++ b/dist/esm/core/scenes/Scene.mjs @@ -0,0 +1,369 @@ +import { isRenderer } from '../renderers/utils.mjs'; +import { ShaderPass } from '../renderPasses/ShaderPass.mjs'; +import { PingPongPlane } from '../../curtains/meshes/PingPongPlane.mjs'; +import { RenderTarget } from '../renderPasses/RenderTarget.mjs'; +import { DOMMesh } from '../../curtains/meshes/DOMMesh.mjs'; +import { Plane } from '../../curtains/meshes/Plane.mjs'; + +class Scene { + /** + * Scene constructor + * @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link Scene} + */ + constructor({ renderer }) { + renderer = renderer && renderer.renderer || renderer; + isRenderer(renderer, "Scene"); + this.renderer = renderer; + this.computePassEntries = []; + this.renderPassEntries = { + /** Array of {@link RenderPassEntry} that will handle {@link PingPongPlane}. Each {@link PingPongPlane} will be added as a distinct {@link RenderPassEntry} here */ + pingPong: [], + /** Array of {@link RenderPassEntry} that will render to a specific {@link RenderTarget}. Each {@link RenderTarget} will be added as a distinct {@link RenderPassEntry} here */ + renderTarget: [], + /** Array of {@link RenderPassEntry} that will render directly to the screen. Our first entry will contain all the Meshes that do not have any {@link RenderTarget} assigned. Following entries will be created for every global {@link ShaderPass} */ + screen: [ + // add our basic scene entry + { + renderPass: this.renderer.renderPass, + renderTexture: null, + onBeforeRenderPass: null, + onAfterRenderPass: null, + element: null, + // explicitly set to null + stack: { + unProjected: { + opaque: [], + transparent: [] + }, + projected: { + opaque: [], + transparent: [] + } + } + } + ] + }; + } + /** + * Get the number of meshes a {@link RenderPassEntry | render pass entry} should draw. + * @param renderPassEntry - The {@link RenderPassEntry | render pass entry} to test + */ + getRenderPassEntryLength(renderPassEntry) { + if (!renderPassEntry) { + return 0; + } else { + return renderPassEntry.element ? renderPassEntry.element.visible ? 1 : 0 : renderPassEntry.stack.unProjected.opaque.length + renderPassEntry.stack.unProjected.transparent.length + renderPassEntry.stack.projected.opaque.length + renderPassEntry.stack.projected.transparent.length; + } + } + /** + * Add a {@link ComputePass} to our scene {@link computePassEntries} array + * @param computePass - {@link ComputePass} to add + */ + addComputePass(computePass) { + this.computePassEntries.push(computePass); + this.computePassEntries.sort((a, b) => { + if (a.renderOrder !== b.renderOrder) { + return a.renderOrder - b.renderOrder; + } else { + return a.index - b.index; + } + }); + } + /** + * Remove a {@link ComputePass} from our scene {@link computePassEntries} array + * @param computePass - {@link ComputePass} to remove + */ + removeComputePass(computePass) { + this.computePassEntries = this.computePassEntries.filter((cP) => cP.uuid !== computePass.uuid); + } + /** + * Add a {@link RenderTarget} to our scene {@link renderPassEntries} outputTarget array. + * Every Meshes later added to this {@link RenderTarget} will be rendered to the {@link RenderTarget#renderTexture | RenderTarget RenderTexture} using the {@link RenderTarget#renderPass.descriptor | RenderTarget RenderPass descriptor} + * @param renderTarget - {@link RenderTarget} to add + */ + addRenderTarget(renderTarget) { + if (!this.renderPassEntries.renderTarget.find((entry) => entry.renderPass.uuid === renderTarget.renderPass.uuid)) + this.renderPassEntries.renderTarget.push({ + renderPass: renderTarget.renderPass, + renderTexture: renderTarget.renderTexture, + onBeforeRenderPass: null, + onAfterRenderPass: null, + element: null, + // explicitly set to null + stack: { + unProjected: { + opaque: [], + transparent: [] + }, + projected: { + opaque: [], + transparent: [] + } + } + }); + } + /** + * Remove a {@link RenderTarget} from our scene {@link renderPassEntries} outputTarget array. + * @param renderTarget - {@link RenderTarget} to add + */ + removeRenderTarget(renderTarget) { + this.renderPassEntries.renderTarget = this.renderPassEntries.renderTarget.filter( + (entry) => entry.renderPass.uuid !== renderTarget.renderPass.uuid + ); + } + /** + * Get the correct {@link renderPassEntries | render pass entry} (either {@link renderPassEntries} outputTarget or {@link renderPassEntries} screen) {@link Stack} onto which this Mesh should be added, depending on whether it's projected or not + * @param mesh - Mesh to check + * @returns - the corresponding render pass entry {@link Stack} + */ + getMeshProjectionStack(mesh) { + const renderPassEntry = mesh.outputTarget ? this.renderPassEntries.renderTarget.find( + (passEntry) => passEntry.renderPass.uuid === mesh.outputTarget.renderPass.uuid + ) : this.renderPassEntries.screen[0]; + const { stack } = renderPassEntry; + return mesh.material.options.rendering.useProjection ? stack.projected : stack.unProjected; + } + /** + * Add a Mesh to the correct {@link renderPassEntries | render pass entry} {@link Stack} array. + * Meshes are then ordered by their {@link core/meshes/mixins/MeshBaseMixin.MeshBaseClass#index | indexes (order of creation]}, position along the Z axis in case they are transparent and then {@link core/meshes/mixins/MeshBaseMixin.MeshBaseClass#renderOrder | renderOrder} + * @param mesh - Mesh to add + */ + addMesh(mesh) { + const projectionStack = this.getMeshProjectionStack(mesh); + const similarMeshes = mesh.transparent ? [...projectionStack.transparent] : [...projectionStack.opaque]; + let siblingMeshIndex = -1; + for (let i = similarMeshes.length - 1; i >= 0; i--) { + if (similarMeshes[i].material.pipelineEntry.index === mesh.material.pipelineEntry.index) { + siblingMeshIndex = i + 1; + break; + } + } + siblingMeshIndex = Math.max(0, siblingMeshIndex); + similarMeshes.splice(siblingMeshIndex, 0, mesh); + similarMeshes.sort((a, b) => a.index - b.index); + if ((mesh instanceof DOMMesh || mesh instanceof Plane) && mesh.transparent) { + similarMeshes.sort( + (a, b) => b.documentPosition.z - a.documentPosition.z + ); + } + similarMeshes.sort((a, b) => a.renderOrder - b.renderOrder); + mesh.transparent ? projectionStack.transparent = similarMeshes : projectionStack.opaque = similarMeshes; + } + /** + * Remove a Mesh from our {@link Scene} + * @param mesh - Mesh to remove + */ + removeMesh(mesh) { + const projectionStack = this.getMeshProjectionStack(mesh); + if (mesh.transparent) { + projectionStack.transparent = projectionStack.transparent.filter((m) => m.uuid !== mesh.uuid); + } else { + projectionStack.opaque = projectionStack.opaque.filter((m) => m.uuid !== mesh.uuid); + } + } + /** + * Add a {@link ShaderPass} to our scene {@link renderPassEntries} screen array. + * Before rendering the {@link ShaderPass}, we will copy the correct input texture into its {@link ShaderPass#renderTexture | renderTexture} + * This also handles the {@link renderPassEntries} screen array entries order: We will first draw selective passes, then our main screen pass and finally global post processing passes. + * @see {@link https://codesandbox.io/p/sandbox/webgpu-render-to-2-textures-without-texture-copy-c4sx4s?file=%2Fsrc%2Findex.js%3A10%2C4 | minimal code example} + * @param shaderPass - {@link ShaderPass} to add + */ + addShaderPass(shaderPass) { + const onBeforeRenderPass = shaderPass.inputTarget || shaderPass.outputTarget ? null : (commandEncoder, swapChainTexture) => { + if (shaderPass.renderTexture && swapChainTexture) { + commandEncoder.copyTextureToTexture( + { + texture: swapChainTexture + }, + { + texture: shaderPass.renderTexture.texture + }, + [shaderPass.renderTexture.size.width, shaderPass.renderTexture.size.height] + ); + } + this.renderer.postProcessingPass.setLoadOp("clear"); + }; + const onAfterRenderPass = shaderPass.outputTarget ? null : (commandEncoder, swapChainTexture) => { + if (shaderPass.renderTexture && swapChainTexture) { + commandEncoder.copyTextureToTexture( + { + texture: swapChainTexture + }, + { + texture: shaderPass.renderTexture.texture + }, + [shaderPass.renderTexture.size.width, shaderPass.renderTexture.size.height] + ); + } + }; + const shaderPassEntry = { + // use output target or postprocessing render pass + renderPass: shaderPass.outputTarget ? shaderPass.outputTarget.renderPass : this.renderer.postProcessingPass, + // render to output target renderTexture or directly to screen + renderTexture: shaderPass.outputTarget ? shaderPass.outputTarget.renderTexture : null, + onBeforeRenderPass, + onAfterRenderPass, + element: shaderPass, + stack: null + // explicitly set to null + }; + this.renderPassEntries.screen.push(shaderPassEntry); + this.renderPassEntries.screen.sort((a, b) => { + const isPostProA = a.element && !a.element.outputTarget; + const renderOrderA = a.element ? a.element.renderOrder : 0; + const indexA = a.element ? a.element.index : 0; + const isPostProB = b.element && !b.element.outputTarget; + const renderOrderB = b.element ? b.element.renderOrder : 0; + const indexB = b.element ? b.element.index : 0; + if (isPostProA && !isPostProB) { + return 1; + } else if (!isPostProA && isPostProB) { + return -1; + } else if (renderOrderA !== renderOrderB) { + return renderOrderA - renderOrderB; + } else { + return indexA - indexB; + } + }); + } + /** + * Remove a {@link ShaderPass} from our scene {@link renderPassEntries} screen array + * @param shaderPass - {@link ShaderPass} to remove + */ + removeShaderPass(shaderPass) { + this.renderPassEntries.screen = this.renderPassEntries.screen.filter( + (entry) => !entry.element || entry.element.uuid !== shaderPass.uuid + ); + } + /** + * Add a {@link PingPongPlane} to our scene {@link renderPassEntries} pingPong array. + * After rendering the {@link PingPongPlane}, we will copy the context current texture into its {@link PingPongPlane#renderTexture | renderTexture} so we'll be able to use it as an input for the next pass + * @see {@link https://codesandbox.io/p/sandbox/webgpu-render-ping-pong-to-texture-use-in-quad-gwjx9p | minimal code example} + * @param pingPongPlane + */ + addPingPongPlane(pingPongPlane) { + this.renderPassEntries.pingPong.push({ + renderPass: pingPongPlane.outputTarget.renderPass, + renderTexture: pingPongPlane.outputTarget.renderTexture, + onBeforeRenderPass: null, + onAfterRenderPass: (commandEncoder, swapChainTexture) => { + commandEncoder.copyTextureToTexture( + { + texture: swapChainTexture + }, + { + texture: pingPongPlane.renderTexture.texture + }, + [pingPongPlane.renderTexture.size.width, pingPongPlane.renderTexture.size.height] + ); + }, + element: pingPongPlane, + stack: null + // explicitly set to null + }); + this.renderPassEntries.pingPong.sort((a, b) => a.element.renderOrder - b.element.renderOrder); + } + /** + * Remove a {@link PingPongPlane} from our scene {@link renderPassEntries} pingPong array. + * @param pingPongPlane - {@link PingPongPlane} to remove + */ + removePingPongPlane(pingPongPlane) { + this.renderPassEntries.pingPong = this.renderPassEntries.pingPong.filter( + (entry) => entry.element.uuid !== pingPongPlane.uuid + ); + } + /** + * Get any rendered object or {@link RenderTarget} {@link RenderPassEntry}. Useful to override a {@link RenderPassEntry#onBeforeRenderPass | RenderPassEntry onBeforeRenderPass} or {@link RenderPassEntry#onAfterRenderPass | RenderPassEntry onAfterRenderPass} default behavior. + * @param object - The object from which we want to get the parentMesh {@link RenderPassEntry} + * @returns - the {@link RenderPassEntry} if found + */ + getObjectRenderPassEntry(object) { + if (object instanceof RenderTarget) { + return this.renderPassEntries.renderTarget.find((entry) => entry.renderPass.uuid === object.renderPass.uuid); + } else if (object instanceof PingPongPlane) { + return this.renderPassEntries.pingPong.find((entry) => entry.element.uuid === object.uuid); + } else if (object instanceof ShaderPass) { + return this.renderPassEntries.screen.find((entry) => entry.element?.uuid === object.uuid); + } else { + const entryType = object.outputTarget ? "renderTarget" : "screen"; + return this.renderPassEntries[entryType].find((entry) => { + return [ + ...entry.stack.unProjected.opaque, + ...entry.stack.unProjected.transparent, + ...entry.stack.projected.opaque, + ...entry.stack.projected.transparent + ].some((mesh) => mesh.uuid === object.uuid); + }); + } + } + /** + * Here we render a {@link RenderPassEntry}: + * - Set its {@link RenderPass#descriptor | renderPass descriptor} view or resolveTarget and get it at as swap chain texture + * - Execute {@link RenderPassEntry#onBeforeRenderPass | onBeforeRenderPass} callback if specified + * - Begin the {@link GPURenderPassEncoder | GPU render pass encoder} using our {@link RenderPass#descriptor | renderPass descriptor} + * - Render the single element if specified or the render pass entry {@link Stack}: draw unprojected opaque / transparent meshes first, then set the {@link CameraRenderer#cameraBindGroup | camera bind group} and draw projected opaque / transparent meshes + * - End the {@link GPURenderPassEncoder | GPU render pass encoder} + * - Execute {@link RenderPassEntry#onAfterRenderPass | onAfterRenderPass} callback if specified + * - Reset {@link core/pipelines/PipelineManager.PipelineManager#currentPipelineIndex | pipeline manager current pipeline} + * @param commandEncoder - current {@link GPUCommandEncoder} + * @param renderPassEntry - {@link RenderPassEntry} to render + */ + renderSinglePassEntry(commandEncoder, renderPassEntry) { + const swapChainTexture = renderPassEntry.renderPass.updateView(renderPassEntry.renderTexture?.texture); + renderPassEntry.onBeforeRenderPass && renderPassEntry.onBeforeRenderPass(commandEncoder, swapChainTexture); + const pass = commandEncoder.beginRenderPass(renderPassEntry.renderPass.descriptor); + !this.renderer.production && pass.pushDebugGroup( + renderPassEntry.element ? `${renderPassEntry.element.options.label} render pass using ${renderPassEntry.renderPass.options.label} descriptor` : `Render stack pass using ${renderPassEntry.renderPass.options.label}${renderPassEntry.renderTexture ? " onto " + renderPassEntry.renderTexture.options.label : ""}` + ); + if (renderPassEntry.element) { + renderPassEntry.element.render(pass); + } else if (renderPassEntry.stack) { + renderPassEntry.stack.unProjected.opaque.forEach((mesh) => mesh.render(pass)); + renderPassEntry.stack.unProjected.transparent.forEach((mesh) => mesh.render(pass)); + if (renderPassEntry.stack.projected.opaque.length || renderPassEntry.stack.projected.transparent.length) { + if (this.renderer.cameraBindGroup) { + pass.setBindGroup( + this.renderer.cameraBindGroup.index, + this.renderer.cameraBindGroup.bindGroup + ); + } + renderPassEntry.stack.projected.opaque.forEach((mesh) => mesh.render(pass)); + renderPassEntry.stack.projected.transparent.forEach((mesh) => mesh.render(pass)); + } + } + !this.renderer.production && pass.popDebugGroup(); + pass.end(); + renderPassEntry.onAfterRenderPass && renderPassEntry.onAfterRenderPass(commandEncoder, swapChainTexture); + this.renderer.pipelineManager.resetCurrentPipeline(); + } + /** + * Render our {@link Scene} + * - Render {@link computePassEntries} first + * - Then our {@link renderPassEntries} + * @param commandEncoder - current {@link GPUCommandEncoder} + */ + render(commandEncoder) { + this.computePassEntries.forEach((computePass) => { + const pass = commandEncoder.beginComputePass(); + computePass.render(pass); + pass.end(); + computePass.copyBufferToResult(commandEncoder); + this.renderer.pipelineManager.resetCurrentPipeline(); + }); + for (const renderPassEntryType in this.renderPassEntries) { + let passDrawnCount = 0; + this.renderPassEntries[renderPassEntryType].forEach((renderPassEntry) => { + if (!this.getRenderPassEntryLength(renderPassEntry)) + return; + renderPassEntry.renderPass.setLoadOp( + renderPassEntryType === "screen" && passDrawnCount !== 0 ? "load" : "clear" + ); + passDrawnCount++; + this.renderSinglePassEntry(commandEncoder, renderPassEntry); + }); + } + } +} + +export { Scene }; +//# sourceMappingURL=Scene.mjs.map diff --git a/dist/esm/core/scenes/Scene.mjs.map b/dist/esm/core/scenes/Scene.mjs.map new file mode 100644 index 000000000..9a375826a --- /dev/null +++ b/dist/esm/core/scenes/Scene.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"Scene.mjs","sources":["../../../../src/core/scenes/Scene.ts"],"sourcesContent":["import { CameraRenderer, isRenderer, Renderer } from '../renderers/utils'\nimport { DOMProjectedMesh, ProjectedMesh, RenderedMesh } from '../renderers/GPURenderer'\nimport { ShaderPass } from '../renderPasses/ShaderPass'\nimport { PingPongPlane } from '../../curtains/meshes/PingPongPlane'\nimport { ComputePass } from '../computePasses/ComputePass'\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\nimport { RenderTarget } from '../renderPasses/RenderTarget'\nimport { DOMMesh } from '../../curtains/meshes/DOMMesh'\nimport { Plane } from '../../curtains/meshes/Plane'\nimport { RenderPass } from '../renderPasses/RenderPass'\nimport { RenderTexture } from '../textures/RenderTexture'\n\n/**\n * Meshes rendering order is dependant of their transparency setting\n */\nexport interface ProjectionStack {\n /** opaque Meshes will be drawn first */\n opaque: ProjectedMesh[]\n /** transparent Meshes will be drawn last */\n transparent: ProjectedMesh[]\n}\n\n/** Meshes will be stacked in 2 different objects whether they are projected (use a {@link core/camera/Camera.Camera | Camera}) or not */\nexport type ProjectionType = 'unProjected' | 'projected'\n\n/**\n * Meshes will be put into two stacks of projected/unprojected transparent and opaques meshes arrays\n */\nexport type Stack = Record\n\n/**\n * A RenderPassEntry object is used to group Meshes based on their rendering target\n */\nexport interface RenderPassEntry {\n /** {@link RenderPass} target used onto which render */\n renderPass: RenderPass\n /** {@link RenderTexture} to render to if any (if not specified then this {@link RenderPassEntry} Meshes will be rendered directly to screen) */\n renderTexture: RenderTexture | null\n /** Optional function to execute just before rendering the Meshes, useful for eventual texture copy */\n onBeforeRenderPass: ((commandEncoder?: GPUCommandEncoder, swapChainTexture?: GPUTexture) => void) | null\n /** Optional function to execute just after rendering the Meshes, useful for eventual texture copy */\n onAfterRenderPass: ((commandEncoder?: GPUCommandEncoder, swapChainTexture?: GPUTexture) => void) | null\n /** If this {@link RenderPassEntry} needs to render only one Mesh */\n element: PingPongPlane | ShaderPass | null\n /** If this {@link RenderPassEntry} needs to render multiple Meshes, then use a {@link Stack} object */\n stack: Stack | null\n}\n\n/** Defines all our possible render targets */\nexport type RenderPassEntriesType = 'pingPong' | 'renderTarget' | 'screen'\n/** Defines our render pass entries object */\nexport type RenderPassEntries = Record\n\n/**\n * Used to by the {@link Renderer} render everything that needs to be rendered (compute passes and meshes) in the right order with the right pass descriptors and target textures, perform textures copy at the right time, etc.\n *\n * ## Render order\n *\n * - Run all the {@link ComputePass} first, sorted by their {@link ComputePass#renderOrder | renderOrder}\n * - Then render all {@link renderPassEntries} pingPong entries Meshes, sorted by their {@link PingPongPlane#renderOrder | renderOrder}\n * - Then all Meshes that need to be rendered into specific {@link renderPassEntries} outputTarget entries:\n * - First, the opaque unprojected Meshes (i.e. opaque {@link core/meshes/FullscreenPlane.FullscreenPlane | FullscreenPlane}, if any), sorted by their {@link core/meshes/FullscreenPlane.FullscreenPlane#renderOrder | renderOrder}\n * - Then, the transparent unprojected Meshes (i.e. transparent {@link core/meshes/FullscreenPlane.FullscreenPlane | FullscreenPlane}, if any), sorted by their {@link core/meshes/FullscreenPlane.FullscreenPlane#renderOrder | renderOrder}\n * - Then, the opaque projected Meshes (i.e. opaque {@link core/meshes/Mesh.Mesh | Mesh}, {@link DOMMesh} or {@link Plane}), sorted by their {@link core/meshes/Mesh.Mesh#renderOrder | renderOrder}\n * - Finally, the transparent projected Meshes (i.e. transparent {@link core/meshes/Mesh.Mesh | Mesh}, {@link DOMMesh} or {@link Plane}), sorted by their Z position and then their {@link core/meshes/Mesh.Mesh#renderOrder | renderOrder}\n * - Finally all Meshes that need to be rendered directly to the {@link renderPassEntries} screen (the {@link Renderer} current texture), in the same order than above.\n */\nexport class Scene {\n /** {@link Renderer} used by this {@link Scene} */\n renderer: Renderer\n /** Array of {@link ComputePass} to render, ordered by {@link ComputePass#renderOrder | renderOrder} */\n computePassEntries: ComputePass[]\n /**\n * A {@link RenderPassEntries} object that will contain every Meshes that need to be drawn, put inside each one of our three entries type arrays: 'pingPong', 'outputTarget' and 'screen'.\n * - The {@link Scene} will first render all {@link renderPassEntries} pingPong entries Meshes\n * - Then all Meshes that need to be rendered into specific {@link renderPassEntries} outputTarget entries\n * - Finally all Meshes that need to be rendered to the {@link renderPassEntries} screen\n */\n renderPassEntries: RenderPassEntries\n\n /**\n * Scene constructor\n * @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link Scene}\n */\n constructor({ renderer }: { renderer: Renderer | GPUCurtains }) {\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\n\n isRenderer(renderer, 'Scene')\n\n this.renderer = renderer\n\n this.computePassEntries = []\n\n this.renderPassEntries = {\n /** Array of {@link RenderPassEntry} that will handle {@link PingPongPlane}. Each {@link PingPongPlane} will be added as a distinct {@link RenderPassEntry} here */\n pingPong: [] as RenderPassEntry[],\n /** Array of {@link RenderPassEntry} that will render to a specific {@link RenderTarget}. Each {@link RenderTarget} will be added as a distinct {@link RenderPassEntry} here */\n renderTarget: [] as RenderPassEntry[],\n /** Array of {@link RenderPassEntry} that will render directly to the screen. Our first entry will contain all the Meshes that do not have any {@link RenderTarget} assigned. Following entries will be created for every global {@link ShaderPass} */\n screen: [\n // add our basic scene entry\n {\n renderPass: this.renderer.renderPass,\n renderTexture: null,\n onBeforeRenderPass: null,\n onAfterRenderPass: null,\n element: null, // explicitly set to null\n stack: {\n unProjected: {\n opaque: [],\n transparent: [],\n },\n projected: {\n opaque: [],\n transparent: [],\n },\n },\n },\n ] as RenderPassEntry[],\n }\n }\n\n /**\n * Get the number of meshes a {@link RenderPassEntry | render pass entry} should draw.\n * @param renderPassEntry - The {@link RenderPassEntry | render pass entry} to test\n */\n getRenderPassEntryLength(renderPassEntry: RenderPassEntry): number {\n if (!renderPassEntry) {\n return 0\n } else {\n return renderPassEntry.element\n ? renderPassEntry.element.visible\n ? 1\n : 0\n : renderPassEntry.stack.unProjected.opaque.length +\n renderPassEntry.stack.unProjected.transparent.length +\n renderPassEntry.stack.projected.opaque.length +\n renderPassEntry.stack.projected.transparent.length\n }\n }\n\n /**\n * Add a {@link ComputePass} to our scene {@link computePassEntries} array\n * @param computePass - {@link ComputePass} to add\n */\n addComputePass(computePass: ComputePass) {\n this.computePassEntries.push(computePass)\n this.computePassEntries.sort((a, b) => {\n if (a.renderOrder !== b.renderOrder) {\n return a.renderOrder - b.renderOrder\n } else {\n return a.index - b.index\n }\n })\n }\n\n /**\n * Remove a {@link ComputePass} from our scene {@link computePassEntries} array\n * @param computePass - {@link ComputePass} to remove\n */\n removeComputePass(computePass: ComputePass) {\n this.computePassEntries = this.computePassEntries.filter((cP) => cP.uuid !== computePass.uuid)\n }\n\n /**\n * Add a {@link RenderTarget} to our scene {@link renderPassEntries} outputTarget array.\n * Every Meshes later added to this {@link RenderTarget} will be rendered to the {@link RenderTarget#renderTexture | RenderTarget RenderTexture} using the {@link RenderTarget#renderPass.descriptor | RenderTarget RenderPass descriptor}\n * @param renderTarget - {@link RenderTarget} to add\n */\n addRenderTarget(renderTarget: RenderTarget) {\n // if RT is not already in the render pass entries\n if (!this.renderPassEntries.renderTarget.find((entry) => entry.renderPass.uuid === renderTarget.renderPass.uuid))\n this.renderPassEntries.renderTarget.push({\n renderPass: renderTarget.renderPass,\n renderTexture: renderTarget.renderTexture,\n onBeforeRenderPass: null,\n onAfterRenderPass: null,\n element: null, // explicitly set to null\n stack: {\n unProjected: {\n opaque: [],\n transparent: [],\n },\n projected: {\n opaque: [],\n transparent: [],\n },\n },\n } as RenderPassEntry)\n }\n\n /**\n * Remove a {@link RenderTarget} from our scene {@link renderPassEntries} outputTarget array.\n * @param renderTarget - {@link RenderTarget} to add\n */\n removeRenderTarget(renderTarget: RenderTarget) {\n this.renderPassEntries.renderTarget = this.renderPassEntries.renderTarget.filter(\n (entry) => entry.renderPass.uuid !== renderTarget.renderPass.uuid\n )\n }\n\n /**\n * Get the correct {@link renderPassEntries | render pass entry} (either {@link renderPassEntries} outputTarget or {@link renderPassEntries} screen) {@link Stack} onto which this Mesh should be added, depending on whether it's projected or not\n * @param mesh - Mesh to check\n * @returns - the corresponding render pass entry {@link Stack}\n */\n getMeshProjectionStack(mesh: ProjectedMesh): ProjectionStack {\n // first get correct render pass enty and stack\n const renderPassEntry = mesh.outputTarget\n ? this.renderPassEntries.renderTarget.find(\n (passEntry) => passEntry.renderPass.uuid === mesh.outputTarget.renderPass.uuid\n )\n : this.renderPassEntries.screen[0]\n\n const { stack } = renderPassEntry\n\n return mesh.material.options.rendering.useProjection ? stack.projected : stack.unProjected\n }\n\n /**\n * Add a Mesh to the correct {@link renderPassEntries | render pass entry} {@link Stack} array.\n * Meshes are then ordered by their {@link core/meshes/mixins/MeshBaseMixin.MeshBaseClass#index | indexes (order of creation]}, position along the Z axis in case they are transparent and then {@link core/meshes/mixins/MeshBaseMixin.MeshBaseClass#renderOrder | renderOrder}\n * @param mesh - Mesh to add\n */\n addMesh(mesh: ProjectedMesh) {\n const projectionStack = this.getMeshProjectionStack(mesh)\n\n // rebuild stack\n const similarMeshes = mesh.transparent ? [...projectionStack.transparent] : [...projectionStack.opaque]\n\n // find if there's already a plane with the same pipeline with a findLastIndex function\n let siblingMeshIndex = -1\n\n for (let i = similarMeshes.length - 1; i >= 0; i--) {\n if (similarMeshes[i].material.pipelineEntry.index === mesh.material.pipelineEntry.index) {\n siblingMeshIndex = i + 1\n break\n }\n }\n\n // if findIndex returned -1 (no matching pipeline)\n siblingMeshIndex = Math.max(0, siblingMeshIndex)\n\n // add it to our stack plane array\n similarMeshes.splice(siblingMeshIndex, 0, mesh)\n similarMeshes.sort((a, b) => a.index - b.index)\n\n // sort by Z pos if transparent\n if ((mesh instanceof DOMMesh || mesh instanceof Plane) && mesh.transparent) {\n similarMeshes.sort(\n (a, b) => (b as DOMProjectedMesh).documentPosition.z - (a as DOMProjectedMesh).documentPosition.z\n )\n }\n\n // then sort by their render order\n similarMeshes.sort((a, b) => a.renderOrder - b.renderOrder)\n\n mesh.transparent ? (projectionStack.transparent = similarMeshes) : (projectionStack.opaque = similarMeshes)\n }\n\n /**\n * Remove a Mesh from our {@link Scene}\n * @param mesh - Mesh to remove\n */\n removeMesh(mesh: ProjectedMesh) {\n const projectionStack = this.getMeshProjectionStack(mesh)\n\n if (mesh.transparent) {\n projectionStack.transparent = projectionStack.transparent.filter((m) => m.uuid !== mesh.uuid)\n } else {\n projectionStack.opaque = projectionStack.opaque.filter((m) => m.uuid !== mesh.uuid)\n }\n }\n\n /**\n * Add a {@link ShaderPass} to our scene {@link renderPassEntries} screen array.\n * Before rendering the {@link ShaderPass}, we will copy the correct input texture into its {@link ShaderPass#renderTexture | renderTexture}\n * This also handles the {@link renderPassEntries} screen array entries order: We will first draw selective passes, then our main screen pass and finally global post processing passes.\n * @see {@link https://codesandbox.io/p/sandbox/webgpu-render-to-2-textures-without-texture-copy-c4sx4s?file=%2Fsrc%2Findex.js%3A10%2C4 | minimal code example}\n * @param shaderPass - {@link ShaderPass} to add\n */\n addShaderPass(shaderPass: ShaderPass) {\n const onBeforeRenderPass =\n shaderPass.inputTarget || shaderPass.outputTarget\n ? null\n : (commandEncoder, swapChainTexture) => {\n // draw the content into our render texture\n // if it's a global postprocessing pass, copy the context current texture into its renderTexture\n // we don't need to do that if it has an inputTarget\n // because in this case its renderTexture is already a copy of the render target content\n if (shaderPass.renderTexture && swapChainTexture) {\n commandEncoder.copyTextureToTexture(\n {\n texture: swapChainTexture,\n },\n {\n texture: shaderPass.renderTexture.texture,\n },\n [shaderPass.renderTexture.size.width, shaderPass.renderTexture.size.height]\n )\n }\n\n // if we want to post process the whole scene, clear render pass content\n this.renderer.postProcessingPass.setLoadOp('clear')\n }\n\n const onAfterRenderPass = shaderPass.outputTarget\n ? null\n : (commandEncoder, swapChainTexture) => {\n // if we rendered to the screen,\n // copy the context current texture result back into the shaderPass renderTexture\n if (shaderPass.renderTexture && swapChainTexture) {\n commandEncoder.copyTextureToTexture(\n {\n texture: swapChainTexture,\n },\n {\n texture: shaderPass.renderTexture.texture,\n },\n [shaderPass.renderTexture.size.width, shaderPass.renderTexture.size.height]\n )\n }\n }\n\n const shaderPassEntry = {\n // use output target or postprocessing render pass\n renderPass: shaderPass.outputTarget ? shaderPass.outputTarget.renderPass : this.renderer.postProcessingPass,\n // render to output target renderTexture or directly to screen\n renderTexture: shaderPass.outputTarget ? shaderPass.outputTarget.renderTexture : null,\n onBeforeRenderPass,\n onAfterRenderPass,\n element: shaderPass,\n stack: null, // explicitly set to null\n }\n\n this.renderPassEntries.screen.push(shaderPassEntry)\n\n // screen passes are sorted by 2 criteria\n // first we draw render passes that have an output target OR our main render pass, ordered by renderOrder\n // then we draw our full postprocessing pass, ordered by renderOrder\n this.renderPassEntries.screen.sort((a, b) => {\n const isPostProA = a.element && !a.element.outputTarget\n const renderOrderA = a.element ? a.element.renderOrder : 0\n const indexA = a.element ? a.element.index : 0\n\n const isPostProB = b.element && !b.element.outputTarget\n const renderOrderB = b.element ? b.element.renderOrder : 0\n const indexB = b.element ? b.element.index : 0\n\n if (isPostProA && !isPostProB) {\n return 1\n } else if (!isPostProA && isPostProB) {\n return -1\n } else if (renderOrderA !== renderOrderB) {\n return renderOrderA - renderOrderB\n } else {\n return indexA - indexB\n }\n })\n }\n\n /**\n * Remove a {@link ShaderPass} from our scene {@link renderPassEntries} screen array\n * @param shaderPass - {@link ShaderPass} to remove\n */\n removeShaderPass(shaderPass: ShaderPass) {\n this.renderPassEntries.screen = this.renderPassEntries.screen.filter(\n (entry) => !entry.element || entry.element.uuid !== shaderPass.uuid\n )\n }\n\n /**\n * Add a {@link PingPongPlane} to our scene {@link renderPassEntries} pingPong array.\n * After rendering the {@link PingPongPlane}, we will copy the context current texture into its {@link PingPongPlane#renderTexture | renderTexture} so we'll be able to use it as an input for the next pass\n * @see {@link https://codesandbox.io/p/sandbox/webgpu-render-ping-pong-to-texture-use-in-quad-gwjx9p | minimal code example}\n * @param pingPongPlane\n */\n addPingPongPlane(pingPongPlane: PingPongPlane) {\n this.renderPassEntries.pingPong.push({\n renderPass: pingPongPlane.outputTarget.renderPass,\n renderTexture: pingPongPlane.outputTarget.renderTexture,\n onBeforeRenderPass: null,\n onAfterRenderPass: (commandEncoder, swapChainTexture) => {\n // Copy the rendering results from the swapChainTexture into our |pingPongPlane texture|.\n commandEncoder.copyTextureToTexture(\n {\n texture: swapChainTexture,\n },\n {\n texture: pingPongPlane.renderTexture.texture,\n },\n [pingPongPlane.renderTexture.size.width, pingPongPlane.renderTexture.size.height]\n )\n },\n element: pingPongPlane,\n stack: null, // explicitly set to null\n } as RenderPassEntry)\n\n // sort by their render order\n this.renderPassEntries.pingPong.sort((a, b) => a.element.renderOrder - b.element.renderOrder)\n }\n\n /**\n * Remove a {@link PingPongPlane} from our scene {@link renderPassEntries} pingPong array.\n * @param pingPongPlane - {@link PingPongPlane} to remove\n */\n removePingPongPlane(pingPongPlane: PingPongPlane) {\n this.renderPassEntries.pingPong = this.renderPassEntries.pingPong.filter(\n (entry) => entry.element.uuid !== pingPongPlane.uuid\n )\n }\n\n /**\n * Get any rendered object or {@link RenderTarget} {@link RenderPassEntry}. Useful to override a {@link RenderPassEntry#onBeforeRenderPass | RenderPassEntry onBeforeRenderPass} or {@link RenderPassEntry#onAfterRenderPass | RenderPassEntry onAfterRenderPass} default behavior.\n * @param object - The object from which we want to get the parentMesh {@link RenderPassEntry}\n * @returns - the {@link RenderPassEntry} if found\n */\n getObjectRenderPassEntry(object: RenderedMesh | RenderTarget): RenderPassEntry | undefined {\n if (object instanceof RenderTarget) {\n return this.renderPassEntries.renderTarget.find((entry) => entry.renderPass.uuid === object.renderPass.uuid)\n } else if (object instanceof PingPongPlane) {\n return this.renderPassEntries.pingPong.find((entry) => entry.element.uuid === object.uuid)\n } else if (object instanceof ShaderPass) {\n return this.renderPassEntries.screen.find((entry) => entry.element?.uuid === object.uuid)\n } else {\n const entryType = object.outputTarget ? 'renderTarget' : 'screen'\n return this.renderPassEntries[entryType].find((entry) => {\n return [\n ...entry.stack.unProjected.opaque,\n ...entry.stack.unProjected.transparent,\n ...entry.stack.projected.opaque,\n ...entry.stack.projected.transparent,\n ].some((mesh) => mesh.uuid === object.uuid)\n })\n }\n }\n\n /**\n * Here we render a {@link RenderPassEntry}:\n * - Set its {@link RenderPass#descriptor | renderPass descriptor} view or resolveTarget and get it at as swap chain texture\n * - Execute {@link RenderPassEntry#onBeforeRenderPass | onBeforeRenderPass} callback if specified\n * - Begin the {@link GPURenderPassEncoder | GPU render pass encoder} using our {@link RenderPass#descriptor | renderPass descriptor}\n * - Render the single element if specified or the render pass entry {@link Stack}: draw unprojected opaque / transparent meshes first, then set the {@link CameraRenderer#cameraBindGroup | camera bind group} and draw projected opaque / transparent meshes\n * - End the {@link GPURenderPassEncoder | GPU render pass encoder}\n * - Execute {@link RenderPassEntry#onAfterRenderPass | onAfterRenderPass} callback if specified\n * - Reset {@link core/pipelines/PipelineManager.PipelineManager#currentPipelineIndex | pipeline manager current pipeline}\n * @param commandEncoder - current {@link GPUCommandEncoder}\n * @param renderPassEntry - {@link RenderPassEntry} to render\n */\n renderSinglePassEntry(commandEncoder: GPUCommandEncoder, renderPassEntry: RenderPassEntry) {\n // set the pass texture to render to\n const swapChainTexture = renderPassEntry.renderPass.updateView(renderPassEntry.renderTexture?.texture)\n\n renderPassEntry.onBeforeRenderPass && renderPassEntry.onBeforeRenderPass(commandEncoder, swapChainTexture)\n\n // now begin our actual render pass\n const pass = commandEncoder.beginRenderPass(renderPassEntry.renderPass.descriptor)\n !this.renderer.production &&\n pass.pushDebugGroup(\n renderPassEntry.element\n ? `${renderPassEntry.element.options.label} render pass using ${renderPassEntry.renderPass.options.label} descriptor`\n : `Render stack pass using ${renderPassEntry.renderPass.options.label}${\n renderPassEntry.renderTexture ? ' onto ' + renderPassEntry.renderTexture.options.label : ''\n }`\n )\n\n // pass entries can have a single element or a stack\n if (renderPassEntry.element) {\n renderPassEntry.element.render(pass)\n } else if (renderPassEntry.stack) {\n // draw unProjected regular meshes\n renderPassEntry.stack.unProjected.opaque.forEach((mesh) => mesh.render(pass))\n renderPassEntry.stack.unProjected.transparent.forEach((mesh) => mesh.render(pass))\n\n // then draw projected meshes\n if (renderPassEntry.stack.projected.opaque.length || renderPassEntry.stack.projected.transparent.length) {\n if ((this.renderer as CameraRenderer).cameraBindGroup) {\n // set camera bind group once\n pass.setBindGroup(\n (this.renderer as CameraRenderer).cameraBindGroup.index,\n (this.renderer as CameraRenderer).cameraBindGroup.bindGroup\n )\n }\n\n renderPassEntry.stack.projected.opaque.forEach((mesh) => mesh.render(pass))\n renderPassEntry.stack.projected.transparent.forEach((mesh) => mesh.render(pass))\n }\n }\n\n !this.renderer.production && pass.popDebugGroup()\n pass.end()\n\n renderPassEntry.onAfterRenderPass && renderPassEntry.onAfterRenderPass(commandEncoder, swapChainTexture)\n\n this.renderer.pipelineManager.resetCurrentPipeline()\n }\n\n /**\n * Render our {@link Scene}\n * - Render {@link computePassEntries} first\n * - Then our {@link renderPassEntries}\n * @param commandEncoder - current {@link GPUCommandEncoder}\n */\n render(commandEncoder: GPUCommandEncoder) {\n this.computePassEntries.forEach((computePass) => {\n const pass = commandEncoder.beginComputePass()\n computePass.render(pass)\n pass.end()\n\n computePass.copyBufferToResult(commandEncoder)\n\n this.renderer.pipelineManager.resetCurrentPipeline()\n })\n\n for (const renderPassEntryType in this.renderPassEntries) {\n let passDrawnCount = 0\n\n this.renderPassEntries[renderPassEntryType].forEach((renderPassEntry) => {\n // early bail if there's nothing to draw\n if (!this.getRenderPassEntryLength(renderPassEntry)) return\n\n // if we're drawing to screen and it's not our first pass, load result from previous passes\n // post processing scene pass will clear content inside onBeforeRenderPass anyway\n renderPassEntry.renderPass.setLoadOp(\n renderPassEntryType === 'screen' && passDrawnCount !== 0 ? 'load' : 'clear'\n )\n\n passDrawnCount++\n\n this.renderSinglePassEntry(commandEncoder, renderPassEntry)\n })\n }\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/shaders/ShaderChunks.mjs b/dist/esm/core/shaders/ShaderChunks.mjs new file mode 100644 index 000000000..94092a89b --- /dev/null +++ b/dist/esm/core/shaders/ShaderChunks.mjs @@ -0,0 +1,30 @@ +import get_output_position from './chunks/get_output_position.wgsl.mjs'; +import get_uv_cover from './chunks/get_uv_cover.wgsl.mjs'; +import get_vertex_to_uv_coords from './chunks/get_vertex_to_uv_coords.wgsl.mjs'; + +const ShaderChunks = { + /** WGSL code chunks added to the vertex shader */ + vertex: { + /** Applies given texture matrix to given uv coordinates */ + get_uv_cover + }, + /** WGSL code chunks added to the fragment shader */ + fragment: { + /** Applies given texture matrix to given uv coordinates */ + get_uv_cover, + /** Convert vertex position to uv coordinates */ + get_vertex_to_uv_coords + } +}; +const ProjectedShaderChunks = { + /** WGSL code chunks added to the vertex shader */ + vertex: { + /** Get output vec4f position vector by applying model view projection matrix to vec3f attribute position vector */ + get_output_position + }, + /** WGSL code chunks added to the fragment shader */ + fragment: {} +}; + +export { ProjectedShaderChunks, ShaderChunks }; +//# sourceMappingURL=ShaderChunks.mjs.map diff --git a/dist/esm/core/shaders/ShaderChunks.mjs.map b/dist/esm/core/shaders/ShaderChunks.mjs.map new file mode 100644 index 000000000..31a66fa65 --- /dev/null +++ b/dist/esm/core/shaders/ShaderChunks.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"ShaderChunks.mjs","sources":["../../../../src/core/shaders/ShaderChunks.ts"],"sourcesContent":["import { RenderMaterialShadersType } from '../../types/Materials'\nimport get_output_position from './chunks/get_output_position.wgsl'\nimport get_uv_cover from './chunks/get_uv_cover.wgsl'\nimport get_vertex_to_uv_coords from './chunks/get_vertex_to_uv_coords.wgsl'\n\n/** Defines {@link ShaderChunks} object structure */\nexport type ShaderChunks = Record>\n/** Defines {@link ProjectedShaderChunks} object structure */\nexport type ProjectedShaderChunks = Record>\n\n/**\n * Useful WGSL code chunks added to the vertex and/or fragment shaders\n */\nexport const ShaderChunks = {\n /** WGSL code chunks added to the vertex shader */\n vertex: {\n /** Applies given texture matrix to given uv coordinates */\n get_uv_cover,\n },\n /** WGSL code chunks added to the fragment shader */\n fragment: {\n /** Applies given texture matrix to given uv coordinates */\n get_uv_cover,\n /** Convert vertex position to uv coordinates */\n get_vertex_to_uv_coords,\n },\n} as ShaderChunks\n\n/**\n * Useful WGSL code chunks added to the projected Meshes vertex and/or fragment shaders\n */\nexport const ProjectedShaderChunks = {\n /** WGSL code chunks added to the vertex shader */\n vertex: {\n /** Get output vec4f position vector by applying model view projection matrix to vec3f attribute position vector */\n get_output_position,\n },\n /** WGSL code chunks added to the fragment shader */\n fragment: {},\n} as ProjectedShaderChunks\n"],"names":[],"mappings":";;;;AAaO,MAAM,YAAe,GAAA;AAAA;AAAA,EAE1B,MAAQ,EAAA;AAAA;AAAA,IAEN,YAAA;AAAA,GACF;AAAA;AAAA,EAEA,QAAU,EAAA;AAAA;AAAA,IAER,YAAA;AAAA;AAAA,IAEA,uBAAA;AAAA,GACF;AACF,EAAA;AAKO,MAAM,qBAAwB,GAAA;AAAA;AAAA,EAEnC,MAAQ,EAAA;AAAA;AAAA,IAEN,mBAAA;AAAA,GACF;AAAA;AAAA,EAEA,UAAU,EAAC;AACb;;;;"} \ No newline at end of file diff --git a/dist/esm/core/shaders/chunks/default_fs.wgsl.mjs b/dist/esm/core/shaders/chunks/default_fs.wgsl.mjs new file mode 100644 index 000000000..40cd33054 --- /dev/null +++ b/dist/esm/core/shaders/chunks/default_fs.wgsl.mjs @@ -0,0 +1,10 @@ +var default_fsWgsl = ( + /* wgsl */ + ` +@fragment fn main() -> @location(0) vec4f { + return vec4(0.0, 0.0, 0.0, 1.0); +}` +); + +export { default_fsWgsl as default }; +//# sourceMappingURL=default_fs.wgsl.mjs.map diff --git a/dist/esm/core/shaders/chunks/default_fs.wgsl.mjs.map b/dist/esm/core/shaders/chunks/default_fs.wgsl.mjs.map new file mode 100644 index 000000000..050e33090 --- /dev/null +++ b/dist/esm/core/shaders/chunks/default_fs.wgsl.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"default_fs.wgsl.mjs","sources":["../../../../../src/core/shaders/chunks/default_fs.wgsl.js"],"sourcesContent":["export default /* wgsl */ `\n@fragment fn main() -> @location(0) vec4f {\n return vec4(0.0, 0.0, 0.0, 1.0);\n}`\n"],"names":[],"mappings":"AAAA,qBAAA;AAAA;AAAA,EAA0B,CAAA;AAAA;AAAA;AAAA,CAAA,CAAA;AAAA;;;;"} \ No newline at end of file diff --git a/dist/esm/core/shaders/chunks/default_normal_fs.wgsl.mjs b/dist/esm/core/shaders/chunks/default_normal_fs.wgsl.mjs new file mode 100644 index 000000000..17ab2b391 --- /dev/null +++ b/dist/esm/core/shaders/chunks/default_normal_fs.wgsl.mjs @@ -0,0 +1,17 @@ +var default_normal_fsWgsl = ( + /* wgsl */ + ` +struct VSOutput { + @builtin(position) position: vec4f, + @location(0) uv: vec2f, + @location(1) normal: vec3f, +}; + +@fragment fn main(fsInput: VSOutput) -> @location(0) vec4f { + // normals + return vec4(normalize(fsInput.normal) * 0.5 + 0.5, 1.0); +}` +); + +export { default_normal_fsWgsl as default }; +//# sourceMappingURL=default_normal_fs.wgsl.mjs.map diff --git a/dist/esm/core/shaders/chunks/default_normal_fs.wgsl.mjs.map b/dist/esm/core/shaders/chunks/default_normal_fs.wgsl.mjs.map new file mode 100644 index 000000000..ca71cd9fc --- /dev/null +++ b/dist/esm/core/shaders/chunks/default_normal_fs.wgsl.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"default_normal_fs.wgsl.mjs","sources":["../../../../../src/core/shaders/chunks/default_normal_fs.wgsl.js"],"sourcesContent":["export default /* wgsl */ `\nstruct VSOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n @location(1) normal: vec3f,\n};\n\n@fragment fn main(fsInput: VSOutput) -> @location(0) vec4f {\n // normals\n return vec4(normalize(fsInput.normal) * 0.5 + 0.5, 1.0);\n}`\n"],"names":[],"mappings":"AAAA,4BAAA;AAAA;AAAA,EAA0B,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA,CAAA;AAAA;;;;"} \ No newline at end of file diff --git a/dist/esm/core/shaders/chunks/default_pass_fs.wgsl.mjs b/dist/esm/core/shaders/chunks/default_pass_fs.wgsl.mjs new file mode 100644 index 000000000..da0a81d97 --- /dev/null +++ b/dist/esm/core/shaders/chunks/default_pass_fs.wgsl.mjs @@ -0,0 +1,15 @@ +var default_pass_fsWGSl = ( + /* wgsl */ + ` +struct VSOutput { + @builtin(position) position: vec4f, + @location(0) uv: vec2f, +}; + +@fragment fn main(fsInput: VSOutput) -> @location(0) vec4f { + return textureSample(renderTexture, defaultSampler, fsInput.uv); +}` +); + +export { default_pass_fsWGSl as default }; +//# sourceMappingURL=default_pass_fs.wgsl.mjs.map diff --git a/dist/esm/core/shaders/chunks/default_pass_fs.wgsl.mjs.map b/dist/esm/core/shaders/chunks/default_pass_fs.wgsl.mjs.map new file mode 100644 index 000000000..1521ba17f --- /dev/null +++ b/dist/esm/core/shaders/chunks/default_pass_fs.wgsl.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"default_pass_fs.wgsl.mjs","sources":["../../../../../src/core/shaders/chunks/default_pass_fs.wgsl.js"],"sourcesContent":["export default /* wgsl */ `\nstruct VSOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n};\n\n@fragment fn main(fsInput: VSOutput) -> @location(0) vec4f {\n return textureSample(renderTexture, defaultSampler, fsInput.uv);\n}`\n"],"names":[],"mappings":"AAAA,0BAAA;AAAA;AAAA,EAA0B,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA,CAAA;AAAA;;;;"} \ No newline at end of file diff --git a/dist/esm/core/shaders/chunks/default_projected_vs.wgsl.mjs b/dist/esm/core/shaders/chunks/default_projected_vs.wgsl.mjs new file mode 100644 index 000000000..1bc9484c3 --- /dev/null +++ b/dist/esm/core/shaders/chunks/default_projected_vs.wgsl.mjs @@ -0,0 +1,24 @@ +var default_projected_vsWgsl = ( + /* wgsl */ + ` +struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) uv: vec2f, + @location(1) normal: vec3f, +}; + +@vertex fn main( + attributes: Attributes, +) -> VertexOutput { + var vsOutput: VertexOutput; + + vsOutput.position = getOutputPosition(attributes.position); + vsOutput.uv = attributes.uv; + vsOutput.normal = attributes.normal; + + return vsOutput; +}` +); + +export { default_projected_vsWgsl as default }; +//# sourceMappingURL=default_projected_vs.wgsl.mjs.map diff --git a/dist/esm/core/shaders/chunks/default_projected_vs.wgsl.mjs.map b/dist/esm/core/shaders/chunks/default_projected_vs.wgsl.mjs.map new file mode 100644 index 000000000..bc5118558 --- /dev/null +++ b/dist/esm/core/shaders/chunks/default_projected_vs.wgsl.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"default_projected_vs.wgsl.mjs","sources":["../../../../../src/core/shaders/chunks/default_projected_vs.wgsl.js"],"sourcesContent":["export default /* wgsl */ `\r\nstruct VertexOutput {\r\n @builtin(position) position: vec4f,\r\n @location(0) uv: vec2f,\r\n @location(1) normal: vec3f,\r\n};\r\n\r\n@vertex fn main(\r\n attributes: Attributes,\r\n) -> VertexOutput {\r\n var vsOutput: VertexOutput;\r\n\r\n vsOutput.position = getOutputPosition(attributes.position);\r\n vsOutput.uv = attributes.uv;\r\n vsOutput.normal = attributes.normal;\r\n \r\n return vsOutput;\r\n}`\r\n"],"names":[],"mappings":"AAAA,+BAAA;AAAA;AAAA,EAA0B,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA,CAAA;AAAA;;;;"} \ No newline at end of file diff --git a/dist/esm/core/shaders/chunks/default_vs.wgsl.mjs b/dist/esm/core/shaders/chunks/default_vs.wgsl.mjs new file mode 100644 index 000000000..4a00316e2 --- /dev/null +++ b/dist/esm/core/shaders/chunks/default_vs.wgsl.mjs @@ -0,0 +1,22 @@ +var default_vsWgsl = ( + /* wgsl */ + ` +struct VertexOutput { + @builtin(position) position: vec4f, + @location(0) uv: vec2f, +}; + +@vertex fn main( + attributes: Attributes, +) -> VertexOutput { + var vsOutput: VertexOutput; + + vsOutput.position = vec4f(attributes.position, 1.0); + vsOutput.uv = attributes.uv; + + return vsOutput; +}` +); + +export { default_vsWgsl as default }; +//# sourceMappingURL=default_vs.wgsl.mjs.map diff --git a/dist/esm/core/shaders/chunks/default_vs.wgsl.mjs.map b/dist/esm/core/shaders/chunks/default_vs.wgsl.mjs.map new file mode 100644 index 000000000..c5634b18f --- /dev/null +++ b/dist/esm/core/shaders/chunks/default_vs.wgsl.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"default_vs.wgsl.mjs","sources":["../../../../../src/core/shaders/chunks/default_vs.wgsl.js"],"sourcesContent":["export default /* wgsl */ `\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) uv: vec2f,\n};\n\n@vertex fn main(\n attributes: Attributes,\n) -> VertexOutput {\n var vsOutput: VertexOutput;\n\n vsOutput.position = vec4f(attributes.position, 1.0);\n vsOutput.uv = attributes.uv;\n \n return vsOutput;\n}`\n"],"names":[],"mappings":"AAAA,qBAAA;AAAA;AAAA,EAA0B,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA,CAAA;AAAA;;;;"} \ No newline at end of file diff --git a/dist/esm/core/shaders/chunks/get_output_position.wgsl.mjs b/dist/esm/core/shaders/chunks/get_output_position.wgsl.mjs new file mode 100644 index 000000000..9eeb31a76 --- /dev/null +++ b/dist/esm/core/shaders/chunks/get_output_position.wgsl.mjs @@ -0,0 +1,10 @@ +var get_output_position = ( + /* wgsl */ + ` +fn getOutputPosition(position: vec3f) -> vec4f { + return matrices.modelViewProjection * vec4f(position, 1.0); +}` +); + +export { get_output_position as default }; +//# sourceMappingURL=get_output_position.wgsl.mjs.map diff --git a/dist/esm/core/shaders/chunks/get_output_position.wgsl.mjs.map b/dist/esm/core/shaders/chunks/get_output_position.wgsl.mjs.map new file mode 100644 index 000000000..2c839d569 --- /dev/null +++ b/dist/esm/core/shaders/chunks/get_output_position.wgsl.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"get_output_position.wgsl.mjs","sources":["../../../../../src/core/shaders/chunks/get_output_position.wgsl.js"],"sourcesContent":["export default /* wgsl */ `\r\nfn getOutputPosition(position: vec3f) -> vec4f {\r\n return matrices.modelViewProjection * vec4f(position, 1.0);\r\n}`\r\n"],"names":[],"mappings":"AAAA,0BAAA;AAAA;AAAA,EAA0B,CAAA;AAAA;AAAA;AAAA,CAAA,CAAA;AAAA;;;;"} \ No newline at end of file diff --git a/dist/esm/core/shaders/chunks/get_uv_cover.wgsl.mjs b/dist/esm/core/shaders/chunks/get_uv_cover.wgsl.mjs new file mode 100644 index 000000000..23e5e3aa9 --- /dev/null +++ b/dist/esm/core/shaders/chunks/get_uv_cover.wgsl.mjs @@ -0,0 +1,10 @@ +var get_uv_cover = ( + /* wgsl */ + ` +fn getUVCover(uv: vec2f, textureMatrix: mat4x4f) -> vec2f { + return (textureMatrix * vec4f(uv, 0.0, 1.0)).xy; +}` +); + +export { get_uv_cover as default }; +//# sourceMappingURL=get_uv_cover.wgsl.mjs.map diff --git a/dist/esm/core/shaders/chunks/get_uv_cover.wgsl.mjs.map b/dist/esm/core/shaders/chunks/get_uv_cover.wgsl.mjs.map new file mode 100644 index 000000000..bb2d40c1c --- /dev/null +++ b/dist/esm/core/shaders/chunks/get_uv_cover.wgsl.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"get_uv_cover.wgsl.mjs","sources":["../../../../../src/core/shaders/chunks/get_uv_cover.wgsl.js"],"sourcesContent":["export default /* wgsl */ `\nfn getUVCover(uv: vec2f, textureMatrix: mat4x4f) -> vec2f {\n return (textureMatrix * vec4f(uv, 0.0, 1.0)).xy;\n}`\n"],"names":[],"mappings":"AAAA,mBAAA;AAAA;AAAA,EAA0B,CAAA;AAAA;AAAA;AAAA,CAAA,CAAA;AAAA;;;;"} \ No newline at end of file diff --git a/dist/esm/core/shaders/chunks/get_vertex_to_uv_coords.wgsl.mjs b/dist/esm/core/shaders/chunks/get_vertex_to_uv_coords.wgsl.mjs new file mode 100644 index 000000000..e8fd2183c --- /dev/null +++ b/dist/esm/core/shaders/chunks/get_vertex_to_uv_coords.wgsl.mjs @@ -0,0 +1,21 @@ +var get_vertex_to_uv_coords = ( + /* wgsl */ + ` +fn getVertex2DToUVCoords(vertex: vec2f) -> vec2f { + return vec2( + vertex.x * 0.5 + 0.5, + 0.5 - vertex.y * 0.5 + ); +} + +fn getVertex3DToUVCoords(vertex: vec3f) -> vec2f { + return vec2( + vertex.x * 0.5 + 0.5, + 0.5 - vertex.y * 0.5 + ); +} +` +); + +export { get_vertex_to_uv_coords as default }; +//# sourceMappingURL=get_vertex_to_uv_coords.wgsl.mjs.map diff --git a/dist/esm/core/shaders/chunks/get_vertex_to_uv_coords.wgsl.mjs.map b/dist/esm/core/shaders/chunks/get_vertex_to_uv_coords.wgsl.mjs.map new file mode 100644 index 000000000..9410285fb --- /dev/null +++ b/dist/esm/core/shaders/chunks/get_vertex_to_uv_coords.wgsl.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"get_vertex_to_uv_coords.wgsl.mjs","sources":["../../../../../src/core/shaders/chunks/get_vertex_to_uv_coords.wgsl.js"],"sourcesContent":["export default /* wgsl */ `\nfn getVertex2DToUVCoords(vertex: vec2f) -> vec2f {\n return vec2(\n vertex.x * 0.5 + 0.5,\n 0.5 - vertex.y * 0.5\n );\n}\n\nfn getVertex3DToUVCoords(vertex: vec3f) -> vec2f {\n return vec2(\n vertex.x * 0.5 + 0.5,\n 0.5 - vertex.y * 0.5\n );\n}\n`\n"],"names":[],"mappings":"AAAA,8BAAA;AAAA;AAAA,EAA0B,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAAA;;;;"} \ No newline at end of file diff --git a/dist/esm/core/textures/RenderTexture.mjs b/dist/esm/core/textures/RenderTexture.mjs new file mode 100644 index 000000000..bf45e0b68 --- /dev/null +++ b/dist/esm/core/textures/RenderTexture.mjs @@ -0,0 +1,170 @@ +import { isRenderer } from '../renderers/utils.mjs'; +import { TextureBinding } from '../bindings/TextureBinding.mjs'; +import { generateUUID } from '../../utils/utils.mjs'; + +var __accessCheck = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); +}; +var __privateGet = (obj, member, getter) => { + __accessCheck(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); +}; +var __privateAdd = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); +}; +var __privateSet = (obj, member, value, setter) => { + __accessCheck(obj, member, "write to private field"); + setter ? setter.call(obj, value) : member.set(obj, value); + return value; +}; +var _autoResize; +const defaultRenderTextureParams = { + label: "RenderTexture", + name: "renderTexture", + usage: "texture", + access: "write", + fromTexture: null, + viewDimension: "2d", + sampleCount: 1, + qualityRatio: 1 +}; +class RenderTexture { + /** + * RenderTexture constructor + * @param renderer - {@link Renderer | renderer} object or {@link GPUCurtains} class object used to create this {@link RenderTexture} + * @param parameters - {@link RenderTextureParams | parameters} used to create this {@link RenderTexture} + */ + constructor(renderer, parameters = defaultRenderTextureParams) { + /** Whether this texture should be automatically resized when the {@link Renderer renderer} size changes. Default to true. */ + __privateAdd(this, _autoResize, true); + renderer = renderer && renderer.renderer || renderer; + isRenderer(renderer, parameters.label ? parameters.label + " RenderTexture" : "RenderTexture"); + this.type = "RenderTexture"; + this.renderer = renderer; + this.uuid = generateUUID(); + this.options = { ...defaultRenderTextureParams, ...parameters }; + if (!this.options.format) { + this.options.format = this.renderer.options.preferredFormat; + } + this.size = this.options.fixedSize ? { + width: this.options.fixedSize.width * this.options.qualityRatio, + height: this.options.fixedSize.height * this.options.qualityRatio, + depth: this.options.fixedSize.depth + } : { + width: Math.floor(this.renderer.displayBoundingRect.width * this.options.qualityRatio), + height: Math.floor(this.renderer.displayBoundingRect.height * this.options.qualityRatio), + depth: 1 + }; + if (this.options.fixedSize) { + __privateSet(this, _autoResize, false); + } + this.setBindings(); + this.renderer.addRenderTexture(this); + this.createTexture(); + } + /** + * Copy another {@link RenderTexture} into this {@link RenderTexture} + * @param texture - {@link RenderTexture} to copy + */ + copy(texture) { + this.options.fromTexture = texture; + this.createTexture(); + } + /** + * Copy a {@link GPUTexture} directly into this {@link RenderTexture}. Mainly used for depth textures. + * @param texture - {@link GPUTexture} to copy + */ + copyGPUTexture(texture) { + this.size = { + width: texture.width, + height: texture.height, + depth: texture.depthOrArrayLayers + }; + this.texture = texture; + this.textureBinding.resource = this.texture; + } + /** + * Create the {@link GPUTexture | texture} (or copy it from source) and update the {@link TextureBinding#resource | binding resource} + */ + createTexture() { + if (this.options.fromTexture) { + this.options.format = this.options.fromTexture.options.format; + this.copyGPUTexture(this.options.fromTexture.texture); + return; + } + this.texture?.destroy(); + this.texture = this.renderer.createTexture({ + label: this.options.label, + format: this.options.format, + size: [this.size.width, this.size.height, this.size.depth ?? 1], + dimensions: this.options.viewDimension === "1d" ? "1d" : this.options.viewDimension === "3d" ? "3d" : "2d", + sampleCount: this.options.sampleCount, + usage: ( + // TODO let user chose? + // see https://matrix.to/#/!MFogdGJfnZLrDmgkBN:matrix.org/$vESU70SeCkcsrJQdyQGMWBtCgVd3XqnHcBxFDKTKKSQ?via=matrix.org&via=mozilla.org&via=hej.im + this.options.usage !== "storage" ? GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT : GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST + ) + }); + this.textureBinding.resource = this.texture; + } + /** + * Set our {@link RenderTexture#bindings | bindings} + */ + setBindings() { + this.bindings = [ + new TextureBinding({ + label: this.options.label + ": " + this.options.name + " render texture", + name: this.options.name, + texture: this.texture, + bindingType: this.options.usage, + format: this.options.format, + viewDimension: this.options.viewDimension, + multisampled: this.options.sampleCount > 1 + }) + ]; + } + /** + * Get our {@link TextureBinding | texture binding} + * @readonly + */ + get textureBinding() { + return this.bindings[0]; + } + /** + * Resize our {@link RenderTexture}, which means recreate it/copy it again and tell the {@link core/bindGroups/TextureBindGroup.TextureBindGroup | texture bind group} to update + * @param size - the optional new {@link TextureSize | size} to set + */ + resize(size = null) { + if (!__privateGet(this, _autoResize)) + return; + if (!size) { + size = { + width: Math.floor(this.renderer.displayBoundingRect.width * this.options.qualityRatio), + height: Math.floor(this.renderer.displayBoundingRect.height * this.options.qualityRatio), + depth: 1 + }; + } + if (size.width === this.size.width && size.height === this.size.height && size.depth === this.size.depth) { + return; + } + this.size = size; + this.createTexture(); + } + /** + * Destroy our {@link RenderTexture} + */ + destroy() { + this.renderer.removeRenderTexture(this); + if (!this.options.fromTexture) { + this.texture?.destroy(); + } + this.texture = null; + } +} +_autoResize = new WeakMap(); + +export { RenderTexture }; +//# sourceMappingURL=RenderTexture.mjs.map diff --git a/dist/esm/core/textures/RenderTexture.mjs.map b/dist/esm/core/textures/RenderTexture.mjs.map new file mode 100644 index 000000000..543e7f62e --- /dev/null +++ b/dist/esm/core/textures/RenderTexture.mjs.map @@ -0,0 +1 @@ +{"version":3,"file":"RenderTexture.mjs","sources":["../../../../src/core/textures/RenderTexture.ts"],"sourcesContent":["import { isRenderer, Renderer } from '../renderers/utils'\nimport { TextureBinding, TextureBindingParams } from '../bindings/TextureBinding'\nimport { BindGroupBindingElement } from '../../types/BindGroups'\nimport { GPUCurtains } from '../../curtains/GPUCurtains'\nimport { BindingMemoryAccessType, TextureBindingType } from '../bindings/Binding'\nimport { generateUUID } from '../../utils/utils'\nimport { Texture } from './Texture'\nimport { TextureSize } from '../../types/Textures'\n\n/**\n * Define the possible binding types of a {@link RenderTexture}\n */\nexport type RenderTextureBindingType = Exclude\n\n/**\n * Base parameters used to create a {@link RenderTexture}\n */\nexport interface RenderTextureBaseParams {\n /** The label of the {@link RenderTexture}, used to create various GPU objects for debugging purpose */\n label?: string\n /** Name of the {@link RenderTexture} to use in the {@link TextureBinding | texture binding} */\n name?: string\n\n /** Optional fixed size of the {@link RenderTexture#texture | texture}. If set, the {@link RenderTexture} will never be resized and always keep that size. */\n fixedSize?: TextureSize\n\n /** Force the texture size to be set to the given ratio of the {@link core/renderers/GPURenderer.GPURenderer#displayBoundingRect | renderer display bounding rectangle} or {@link fixedSize}. Used mainly to shrink render target definition. */\n qualityRatio?: number\n\n /** Whether to use this {@link RenderTexture} as a regular, storage or depth texture */\n usage?: RenderTextureBindingType\n /** Optional format of the {@link RenderTexture#texture | texture}, mainly used for storage textures */\n format?: GPUTextureFormat\n /** Optional texture binding memory access type, mainly used for storage textures */\n access?: BindingMemoryAccessType\n /** Optional {@link RenderTexture#texture | texture} view dimension to use */\n viewDimension?: GPUTextureViewDimension\n /** Sample count of the {@link RenderTexture#texture | texture}, used for multisampling */\n sampleCount?: GPUSize32\n}\n\n/**\n * Parameters used to create a {@link RenderTexture}\n */\nexport interface RenderTextureParams extends RenderTextureBaseParams {\n /** Optional texture to use as a copy source input. Could be a {@link RenderTexture} or {@link Texture} */\n fromTexture?: RenderTexture | Texture | null\n}\n\n/** @const - default {@link RenderTexture} parameters */\nconst defaultRenderTextureParams: RenderTextureParams = {\n label: 'RenderTexture',\n name: 'renderTexture',\n usage: 'texture',\n access: 'write',\n fromTexture: null,\n viewDimension: '2d',\n sampleCount: 1,\n qualityRatio: 1,\n}\n\n/**\n * Used to create {@link GPUTexture | texture} that can be used as copy source/destination for {@link core/renderPasses/RenderPass.RenderPass | RenderPass} and {@link core/renderPasses/RenderTarget.RenderTarget | RenderTarget}.
\n * Basically useful for copying anything outputted to the screen at one point or another.\n *\n * Will create a {@link GPUTexture} and its associated {@link TextureBinding}.\n *\n * @example\n * ```javascript\n * // set our main GPUCurtains instance\n * const gpuCurtains = new GPUCurtains({\n * container: '#canvas' // selector of our WebGPU canvas container\n * })\n *\n * // set the GPU device\n * // note this is asynchronous\n * await gpuCurtains.setDevice()\n *\n * // create a render texture\n * const renderTexture = new RenderTexture(gpuCurtains, {\n * label: 'My render texture',\n * name: 'renderTexture',\n * })\n * ```\n */\nexport class RenderTexture {\n /** {@link Renderer | renderer} used by this {@link RenderTexture} */\n renderer: Renderer\n /** The type of the {@link RenderTexture} */\n type: string\n /** The universal unique id of this {@link RenderTexture} */\n readonly uuid: string\n\n /** The {@link GPUTexture} used */\n texture: GPUTexture\n\n /** Size of the {@link RenderTexture#texture | texture} source, usually our {@link Renderer#displayBoundingRect | renderer display bounding rectangle size} */\n size: TextureSize\n\n /** Options used to create this {@link RenderTexture} */\n options: RenderTextureParams\n\n /** Array of {@link core/bindings/Binding.Binding | bindings} that will actually only hold one {@link TextureBinding | texture binding} */\n bindings: BindGroupBindingElement[]\n\n /** Whether this texture should be automatically resized when the {@link Renderer renderer} size changes. Default to true. */\n #autoResize = true\n\n /**\n * RenderTexture constructor\n * @param renderer - {@link Renderer | renderer} object or {@link GPUCurtains} class object used to create this {@link RenderTexture}\n * @param parameters - {@link RenderTextureParams | parameters} used to create this {@link RenderTexture}\n */\n constructor(renderer: Renderer | GPUCurtains, parameters = defaultRenderTextureParams) {\n // we could pass our curtains object OR our curtains renderer object\n renderer = (renderer && (renderer as GPUCurtains).renderer) || (renderer as Renderer)\n\n isRenderer(renderer, parameters.label ? parameters.label + ' RenderTexture' : 'RenderTexture')\n\n this.type = 'RenderTexture'\n\n this.renderer = renderer\n\n this.uuid = generateUUID()\n\n this.options = { ...defaultRenderTextureParams, ...parameters }\n\n if (!this.options.format) {\n this.options.format = this.renderer.options.preferredFormat\n }\n\n // sizes\n this.size = this.options.fixedSize\n ? {\n width: this.options.fixedSize.width * this.options.qualityRatio,\n height: this.options.fixedSize.height * this.options.qualityRatio,\n depth: this.options.fixedSize.depth,\n }\n : {\n width: Math.floor(this.renderer.displayBoundingRect.width * this.options.qualityRatio),\n height: Math.floor(this.renderer.displayBoundingRect.height * this.options.qualityRatio),\n depth: 1,\n }\n\n if (this.options.fixedSize) {\n this.#autoResize = false\n }\n\n // struct\n this.setBindings()\n\n // texture\n this.renderer.addRenderTexture(this)\n this.createTexture()\n }\n\n /**\n * Copy another {@link RenderTexture} into this {@link RenderTexture}\n * @param texture - {@link RenderTexture} to copy\n */\n copy(texture: RenderTexture | Texture) {\n this.options.fromTexture = texture\n this.createTexture()\n }\n\n /**\n * Copy a {@link GPUTexture} directly into this {@link RenderTexture}. Mainly used for depth textures.\n * @param texture - {@link GPUTexture} to copy\n */\n copyGPUTexture(texture: GPUTexture) {\n this.size = {\n width: texture.width,\n height: texture.height,\n depth: texture.depthOrArrayLayers,\n }\n\n this.texture = texture\n this.textureBinding.resource = this.texture\n }\n\n /**\n * Create the {@link GPUTexture | texture} (or copy it from source) and update the {@link TextureBinding#resource | binding resource}\n */\n createTexture() {\n if (this.options.fromTexture) {\n // copy the GPU texture\n this.options.format = this.options.fromTexture.options.format\n this.copyGPUTexture(this.options.fromTexture.texture)\n return\n }\n\n this.texture?.destroy()\n\n this.texture = this.renderer.createTexture({\n label: this.options.label,\n format: this.options.format,\n size: [this.size.width, this.size.height, this.size.depth ?? 1],\n dimensions: this.options.viewDimension === '1d' ? '1d' : this.options.viewDimension === '3d' ? '3d' : '2d',\n sampleCount: this.options.sampleCount,\n usage:\n // TODO let user chose?\n // see https://matrix.to/#/!MFogdGJfnZLrDmgkBN:matrix.org/$vESU70SeCkcsrJQdyQGMWBtCgVd3XqnHcBxFDKTKKSQ?via=matrix.org&via=mozilla.org&via=hej.im\n this.options.usage !== 'storage'\n ? GPUTextureUsage.TEXTURE_BINDING |\n GPUTextureUsage.COPY_SRC |\n GPUTextureUsage.COPY_DST |\n GPUTextureUsage.RENDER_ATTACHMENT\n : GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,\n } as GPUTextureDescriptor)\n\n // update texture binding\n this.textureBinding.resource = this.texture\n }\n\n /**\n * Set our {@link RenderTexture#bindings | bindings}\n */\n setBindings() {\n this.bindings = [\n new TextureBinding({\n label: this.options.label + ': ' + this.options.name + ' render texture',\n name: this.options.name,\n texture: this.texture,\n bindingType: this.options.usage,\n format: this.options.format,\n viewDimension: this.options.viewDimension,\n multisampled: this.options.sampleCount > 1,\n } as TextureBindingParams),\n ]\n }\n\n /**\n * Get our {@link TextureBinding | texture binding}\n * @readonly\n */\n get textureBinding(): TextureBinding {\n return this.bindings[0] as TextureBinding\n }\n\n /**\n * Resize our {@link RenderTexture}, which means recreate it/copy it again and tell the {@link core/bindGroups/TextureBindGroup.TextureBindGroup | texture bind group} to update\n * @param size - the optional new {@link TextureSize | size} to set\n */\n resize(size: TextureSize | null = null) {\n if (!this.#autoResize) return\n\n if (!size) {\n size = {\n width: Math.floor(this.renderer.displayBoundingRect.width * this.options.qualityRatio),\n height: Math.floor(this.renderer.displayBoundingRect.height * this.options.qualityRatio),\n depth: 1,\n }\n }\n\n // no real resize, bail!\n if (size.width === this.size.width && size.height === this.size.height && size.depth === this.size.depth) {\n return\n }\n\n this.size = size\n this.createTexture()\n }\n\n /**\n * Destroy our {@link RenderTexture}\n */\n destroy() {\n this.renderer.removeRenderTexture(this)\n\n // destroy the GPU texture only if it's not a copy of another texture\n if (!this.options.fromTexture) {\n this.texture?.destroy()\n }\n\n this.texture = null\n }\n}\n"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/dist/esm/core/textures/Texture.mjs b/dist/esm/core/textures/Texture.mjs new file mode 100644 index 000000000..e32586946 --- /dev/null +++ b/dist/esm/core/textures/Texture.mjs @@ -0,0 +1,506 @@ +import { Vec3 } from '../../math/Vec3.mjs'; +import { isRenderer } from '../renderers/utils.mjs'; +import { TextureBinding } from '../bindings/TextureBinding.mjs'; +import { BufferBinding } from '../bindings/BufferBinding.mjs'; +import { Object3D } from '../objects3D/Object3D.mjs'; +import { Mat4 } from '../../math/Mat4.mjs'; +import { generateUUID, throwWarning } from '../../utils/utils.mjs'; + +var __accessCheck = (obj, member, msg) => { + if (!member.has(obj)) + throw TypeError("Cannot " + msg); +}; +var __privateGet = (obj, member, getter) => { + __accessCheck(obj, member, "read from private field"); + return getter ? getter.call(obj) : member.get(obj); +}; +var __privateAdd = (obj, member, value) => { + if (member.has(obj)) + throw TypeError("Cannot add the same private member more than once"); + member instanceof WeakSet ? member.add(obj) : member.set(obj, value); +}; +var _parentRatio, _sourceRatio, _coverScale, _rotationMatrix; +const defaultTextureParams = { + name: "texture", + generateMips: false, + flipY: false, + format: "rgba8unorm", + premultipliedAlpha: true, + placeholderColor: [0, 0, 0, 255], + // default to black + useExternalTextures: true, + fromTexture: null, + viewDimension: "2d", + cache: true +}; +class Texture extends Object3D { + /** + * Texture constructor + * @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link Texture} + * @param parameters - {@link TextureParams | parameters} used to create this {@link Texture} + */ + constructor(renderer, parameters = defaultTextureParams) { + super(); + /** Private {@link Vec3 | vector} used for {@link#modelMatrix} calculations, based on {@link parentMesh} {@link RectSize | size} */ + __privateAdd(this, _parentRatio, new Vec3(1)); + /** Private {@link Vec3 | vector} used for {@link modelMatrix} calculations, based on {@link size | source size} */ + __privateAdd(this, _sourceRatio, new Vec3(1)); + /** Private {@link Vec3 | vector} used for {@link modelMatrix} calculations, based on #parentRatio and #sourceRatio */ + __privateAdd(this, _coverScale, new Vec3(1)); + /** Private rotation {@link Mat4 | matrix} based on texture {@link quaternion} */ + __privateAdd(this, _rotationMatrix, new Mat4()); + // callbacks / events + /** function assigned to the {@link onSourceLoaded} callback */ + this._onSourceLoadedCallback = () => { + }; + /** function assigned to the {@link onSourceUploaded} callback */ + this._onSourceUploadedCallback = () => { + }; + this.type = "Texture"; + renderer = renderer && renderer.renderer || renderer; + isRenderer(renderer, parameters.label ? parameters.label + " " + this.type : this.type); + this.renderer = renderer; + this.uuid = generateUUID(); + const defaultOptions = { + ...defaultTextureParams, + source: parameters.fromTexture ? parameters.fromTexture.options.source : null, + sourceType: parameters.fromTexture ? parameters.fromTexture.options.sourceType : null + }; + this.options = { ...defaultOptions, ...parameters }; + this.options.label = this.options.label ?? this.options.name; + this.texture = null; + this.externalTexture = null; + this.source = null; + this.size = { + width: 1, + height: 1, + depth: 1 + }; + this.textureMatrix = new BufferBinding({ + label: this.options.label + ": model matrix", + name: this.options.name + "Matrix", + useStruct: false, + struct: { + matrix: { + name: this.options.name + "Matrix", + type: "mat4x4f", + value: this.modelMatrix + } + } + }); + this.setBindings(); + this._parentMesh = null; + this.sourceLoaded = false; + this.sourceUploaded = false; + this.shouldUpdate = false; + this.renderer.addTexture(this); + this.createTexture(); + } + /** + * Set our {@link bindings} + */ + setBindings() { + this.bindings = [ + new TextureBinding({ + label: this.options.label + ": texture", + name: this.options.name, + texture: this.options.sourceType === "externalVideo" ? this.externalTexture : this.texture, + bindingType: this.options.sourceType === "externalVideo" ? "externalTexture" : "texture", + viewDimension: this.options.viewDimension + }), + this.textureMatrix + ]; + } + /** + * Get our {@link TextureBinding | GPU texture binding} + * @readonly + */ + get textureBinding() { + return this.bindings[0]; + } + /** + * Get our texture {@link parentMesh} + */ + get parentMesh() { + return this._parentMesh; + } + /** + * Set our texture {@link parentMesh} + * @param value - texture {@link parentMesh} to set (i.e. any kind of {@link core/renderers/GPURenderer.RenderedMesh | Mesh} + */ + set parentMesh(value) { + this._parentMesh = value; + this.resize(); + } + /** + * Get whether our {@link source} has been loaded + */ + get sourceLoaded() { + return this._sourceLoaded; + } + /** + * Set whether our {@link source} has been loaded + * @param value - boolean flag indicating if the {@link source} has been loaded + */ + set sourceLoaded(value) { + if (value && !this.sourceLoaded) { + this._onSourceLoadedCallback && this._onSourceLoadedCallback(); + } + this._sourceLoaded = value; + } + /** + * Get whether our {@link source} has been uploaded + */ + get sourceUploaded() { + return this._sourceUploaded; + } + /** + * Set whether our {@link source} has been uploaded + * @param value - boolean flag indicating if the {@link source} has been uploaded + */ + set sourceUploaded(value) { + if (value && !this.sourceUploaded) { + this._onSourceUploadedCallback && this._onSourceUploadedCallback(); + } + this._sourceUploaded = value; + } + /** + * Set our texture {@link transforms} object + */ + setTransforms() { + super.setTransforms(); + this.transforms.quaternion.setAxisOrder("ZXY"); + this.transforms.origin.model.set(0.5, 0.5, 0); + } + /* TEXTURE MATRIX */ + /** + * Update the {@link modelMatrix} + */ + updateModelMatrix() { + if (!this.parentMesh) + return; + const parentScale = this.parentMesh.scale ? this.parentMesh.scale : new Vec3(1, 1, 1); + const parentWidth = this.parentMesh.boundingRect ? this.parentMesh.boundingRect.width * parentScale.x : this.size.width; + const parentHeight = this.parentMesh.boundingRect ? this.parentMesh.boundingRect.height * parentScale.y : this.size.height; + const parentRatio = parentWidth / parentHeight; + const sourceRatio = this.size.width / this.size.height; + if (parentWidth > parentHeight) { + __privateGet(this, _parentRatio).set(parentRatio, 1, 1); + __privateGet(this, _sourceRatio).set(1 / sourceRatio, 1, 1); + } else { + __privateGet(this, _parentRatio).set(1, 1 / parentRatio, 1); + __privateGet(this, _sourceRatio).set(1, sourceRatio, 1); + } + const coverRatio = parentRatio > sourceRatio !== parentWidth > parentHeight ? 1 : parentWidth > parentHeight ? __privateGet(this, _parentRatio).x * __privateGet(this, _sourceRatio).x : __privateGet(this, _sourceRatio).y * __privateGet(this, _parentRatio).y; + __privateGet(this, _coverScale).set(1 / (coverRatio * this.scale.x), 1 / (coverRatio * this.scale.y), 1); + __privateGet(this, _rotationMatrix).rotateFromQuaternion(this.quaternion); + this.modelMatrix.identity().premultiplyTranslate(this.transformOrigin.clone().multiplyScalar(-1)).premultiplyScale(__privateGet(this, _coverScale)).premultiplyScale(__privateGet(this, _parentRatio)).premultiply(__privateGet(this, _rotationMatrix)).premultiplyScale(__privateGet(this, _sourceRatio)).premultiplyTranslate(this.transformOrigin).translate(this.position); + } + /** + * If our {@link modelMatrix} has been updated, tell the {@link textureMatrix | texture matrix binding} to update as well + */ + onAfterMatrixStackUpdate() { + this.textureMatrix.shouldUpdateBinding(this.options.name + "Matrix"); + } + /** + * Resize our {@link Texture} + */ + resize() { + if (this.source && this.source instanceof HTMLCanvasElement && (this.source.width !== this.size.width || this.source.height !== this.size.height)) { + this.setSourceSize(); + this.createTexture(); + } + this.shouldUpdateModelMatrix(); + } + /** + * Get the number of mip levels create based on {@link size} + * @param sizes - Array containing our texture width, height and depth + * @returns - number of mip levels + */ + getNumMipLevels(...sizes) { + const maxSize = Math.max(...sizes); + return 1 + Math.log2(maxSize) | 0; + } + /** + * Tell the {@link Renderer} to upload or texture + */ + uploadTexture() { + this.renderer.uploadTexture(this); + this.shouldUpdate = false; + } + /** + * Import a {@link GPUExternalTexture} from the {@link Renderer}, update the {@link textureBinding} and its {@link core/bindGroups/TextureBindGroup.TextureBindGroup | bind group} + */ + uploadVideoTexture() { + this.externalTexture = this.renderer.importExternalTexture(this.source); + this.textureBinding.resource = this.externalTexture; + this.textureBinding.setBindingType("externalTexture"); + this.shouldUpdate = false; + this.sourceUploaded = true; + } + /** + * Copy a {@link Texture} + * @param texture - {@link Texture} to copy + */ + copy(texture) { + if (this.options.sourceType === "externalVideo" && texture.options.sourceType !== "externalVideo") { + throwWarning(`${this.options.label}: cannot copy a GPUTexture to a GPUExternalTexture`); + return; + } else if (this.options.sourceType !== "externalVideo" && texture.options.sourceType === "externalVideo") { + throwWarning(`${this.options.label}: cannot copy a GPUExternalTexture to a GPUTexture`); + return; + } + this.options.fromTexture = texture; + this.options.sourceType = texture.options.sourceType; + this.options.generateMips = texture.options.generateMips; + this.options.flipY = texture.options.flipY; + this.options.format = texture.options.format; + this.options.premultipliedAlpha = texture.options.premultipliedAlpha; + this.options.placeholderColor = texture.options.placeholderColor; + this.options.useExternalTextures = texture.options.useExternalTextures; + this.sourceLoaded = texture.sourceLoaded; + this.sourceUploaded = texture.sourceUploaded; + if (texture.texture) { + if (texture.sourceLoaded) { + this.size = texture.size; + this.source = texture.source; + this.resize(); + } + if (texture.sourceUploaded) { + this.texture = texture.texture; + this.textureBinding.resource = this.texture; + } else { + this.createTexture(); + } + } + } + /** + * Set the {@link texture | GPU texture} + */ + createTexture() { + const options = { + label: this.options.label, + format: this.options.format, + size: [this.size.width, this.size.height, this.size.depth], + // [1, 1] if no source + dimensions: this.options.viewDimension === "1d" ? "1d" : this.options.viewDimension === "3d" ? "3d" : "2d", + //sampleCount: this.source ? this.renderer.sampleCount : 1, + usage: !!this.source ? GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT : GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST + }; + if (this.options.sourceType !== "externalVideo") { + options.mipLevelCount = this.options.generateMips ? this.getNumMipLevels(this.size.width, this.size.height) : 1; + this.texture?.destroy(); + this.texture = this.renderer.createTexture(options); + this.textureBinding.resource = this.texture; + } + this.shouldUpdate = true; + } + /* SOURCES */ + /** + * Set the {@link size} based on the {@link source} + */ + setSourceSize() { + this.size = { + width: this.source.naturalWidth || this.source.width || this.source.videoWidth, + height: this.source.naturalHeight || this.source.height || this.source.videoHeight, + depth: 1 + }; + } + /** + * Load an {@link HTMLImageElement} from a URL and create an {@link ImageBitmap} to use as a {@link source} + * @async + * @param url - URL of the image to load + * @returns - the newly created {@link ImageBitmap} + */ + async loadImageBitmap(url) { + const res = await fetch(url); + const blob = await res.blob(); + return await createImageBitmap(blob, { colorSpaceConversion: "none" }); + } + /** + * Load and create an {@link ImageBitmap} from a URL or {@link HTMLImageElement}, use it as a {@link source} and create the {@link GPUTexture} + * @async + * @param source - the image URL or {@link HTMLImageElement} to load + * @returns - the newly created {@link ImageBitmap} + */ + async loadImage(source) { + const url = typeof source === "string" ? source : source.getAttribute("src"); + this.options.source = url; + this.options.sourceType = "image"; + const cachedTexture = this.renderer.textures.find((t) => t.options.source === url); + if (cachedTexture && cachedTexture.texture && cachedTexture.sourceUploaded) { + this.copy(cachedTexture); + return; + } + this.sourceLoaded = false; + this.sourceUploaded = false; + this.source = await this.loadImageBitmap(this.options.source); + this.setSourceSize(); + this.resize(); + this.sourceLoaded = true; + this.createTexture(); + } + // weirldy enough, we don't have to do anything in that callback + // because the