@luma.gl/engine
Advanced tools
Comparing version 9.1.0-alpha.19 to 9.1.0-beta.1
@@ -25,1 +25,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=animation-loop-template.js.map |
@@ -461,1 +461,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=animation-loop.js.map |
@@ -5,1 +5,2 @@ // luma.gl | ||
export {}; | ||
//# sourceMappingURL=animation-props.js.map |
@@ -34,1 +34,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=make-animation-loop.js.map |
@@ -18,1 +18,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=request-animation-frame.js.map |
@@ -60,1 +60,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=key-frames.js.map |
@@ -108,1 +108,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=timeline.js.map |
@@ -43,1 +43,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=load-file.js.map |
@@ -17,1 +17,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=random.js.map |
@@ -36,2 +36,3 @@ import type { Texture, TextureProps, Sampler, TextureView, Device, Texture1DData, Texture2DData, Texture3DData, TextureArrayData, TextureCubeData, TextureCubeArrayData } from '@luma.gl/core'; | ||
readonly device: Device; | ||
readonly id: string; | ||
texture: Texture; | ||
@@ -45,2 +46,4 @@ sampler: Sampler; | ||
protected rejectReady: (error: Error) => void; | ||
get [Symbol.toStringTag](): string; | ||
toString(): string; | ||
constructor(device: Device, props: AsyncTextureProps); | ||
@@ -47,0 +50,0 @@ initAsync(props: AsyncTextureProps): Promise<void>; |
// luma.gl, MIT license | ||
// Copyright (c) vis.gl contributors | ||
import { loadImageBitmap } from "../application-utils/load-file.js"; | ||
import { uid } from "../utils/uid.js"; | ||
/** | ||
@@ -12,2 +13,3 @@ * It is very convenient to be able to initialize textures with promises | ||
device; | ||
id; | ||
// TODO - should we type these as possibly `null`? It will make usage harder? | ||
@@ -25,4 +27,12 @@ // @ts-expect-error | ||
rejectReady = () => { }; | ||
get [Symbol.toStringTag]() { | ||
return 'AsyncTexture'; | ||
} | ||
toString() { | ||
return `AsyncTexture:"${this.id}"(${this.isReady ? 'ready' : 'loading'})`; | ||
} | ||
constructor(device, props) { | ||
this.device = device; | ||
this.id = props.id || uid('async-texture'); | ||
// this.id = typeof props?.data === 'string' ? props.data.slice(-20) : uid('async-texture'); | ||
// Signature: new AsyncTexture(device, {data: url}) | ||
@@ -106,1 +116,2 @@ if (typeof props?.data === 'string' && props.dimension === '2d') { | ||
} | ||
//# sourceMappingURL=async-texture.js.map |
@@ -83,1 +83,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=buffer-transform.js.map |
@@ -255,1 +255,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=computation.js.map |
// luma.gl | ||
// SPDX-License-Identifier: MIT | ||
// Copyright (c) vis.gl contributors | ||
import { Texture } from '@luma.gl/core'; | ||
/** | ||
@@ -34,3 +35,18 @@ * Helper class for working with repeated transformations / computations | ||
constructor(device, props) { | ||
super({ current: device.createFramebuffer(props), next: device.createFramebuffer(props) }); | ||
props = { ...props }; | ||
let colorAttachments = props.colorAttachments?.map(colorAttachment => typeof colorAttachment !== 'string' | ||
? colorAttachment | ||
: device.createTexture({ | ||
format: colorAttachment, | ||
usage: Texture.COPY_DST | Texture.RENDER_ATTACHMENT | ||
})); | ||
const current = device.createFramebuffer({ ...props, colorAttachments }); | ||
colorAttachments = props.colorAttachments?.map(colorAttachment => typeof colorAttachment !== 'string' | ||
? colorAttachment | ||
: device.createTexture({ | ||
format: colorAttachment, | ||
usage: Texture.COPY_DST | Texture.RENDER_ATTACHMENT | ||
})); | ||
const next = device.createFramebuffer({ ...props, colorAttachments }); | ||
super({ current, next }); | ||
} | ||
@@ -76,1 +92,2 @@ /** | ||
} | ||
//# sourceMappingURL=swap.js.map |
// luma.gl | ||
// SPDX-License-Identifier: MIT | ||
// Copyright (c) vis.gl contributors | ||
import { getPassthroughFS } from '@luma.gl/shadertools'; | ||
import { Model } from "../model/model.js"; | ||
import { getPassthroughFS } from '@luma.gl/shadertools'; | ||
import { uid } from "../utils/uid.js"; | ||
const FS_OUTPUT_VARIABLE = 'transform_output'; | ||
@@ -30,3 +31,3 @@ /** | ||
this.model = new Model(this.device, { | ||
id: props.id || 'texture-transform-model', | ||
id: props.id || uid('texture-transform-model'), | ||
fs: props.fs || | ||
@@ -113,1 +114,2 @@ getPassthroughFS({ | ||
} | ||
//# sourceMappingURL=texture-transform.js.map |
@@ -47,1 +47,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=copy-texture-to-image.js.map |
@@ -52,1 +52,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=debug-framebuffer.js.map |
@@ -28,1 +28,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=debug-shader-layout.js.map |
@@ -40,1 +40,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=pixel-data-utils.js.map |
@@ -12,3 +12,5 @@ import type { RenderPipelineProps, ComputePipelineProps } from '@luma.gl/core'; | ||
readonly device: Device; | ||
readonly cachingEnabled: boolean; | ||
readonly destroyPolicy: 'unused' | 'never'; | ||
readonly debug: boolean; | ||
private _hashCounter; | ||
@@ -18,7 +20,15 @@ private readonly _hashes; | ||
private readonly _computePipelineCache; | ||
get [Symbol.toStringTag](): string; | ||
toString(): string; | ||
constructor(device: Device); | ||
/** Return a RenderPipeline matching props. Reuses a similar pipeline if already created. */ | ||
/** Return a RenderPipeline matching supplied props. Reuses an equivalent pipeline if already created. */ | ||
createRenderPipeline(props: RenderPipelineProps): RenderPipeline; | ||
/** Return a ComputePipeline matching supplied props. Reuses an equivalent pipeline if already created. */ | ||
createComputePipeline(props: ComputePipelineProps): ComputePipeline; | ||
release(pipeline: RenderPipeline | ComputePipeline): void; | ||
/** Destroy a cached pipeline, removing it from the cache (depending on destroy policy) */ | ||
private _destroyPipeline; | ||
/** Get the appropriate cache for the type of pipeline */ | ||
private _getCache; | ||
/** Calculate a hash based on all the inputs for a compute pipeline */ | ||
private _hashComputePipeline; | ||
@@ -25,0 +35,0 @@ /** Calculate a hash based on all the inputs for a render pipeline */ |
// luma.gl | ||
// SPDX-License-Identifier: MIT | ||
// Copyright (c) vis.gl contributors | ||
import { RenderPipeline, ComputePipeline } from '@luma.gl/core'; | ||
import { RenderPipeline, ComputePipeline, log } from '@luma.gl/core'; | ||
import { uid } from "../utils/uid.js"; | ||
/** | ||
@@ -17,3 +18,5 @@ * Efficiently creates / caches pipelines | ||
device; | ||
cachingEnabled; | ||
destroyPolicy; | ||
debug; | ||
_hashCounter = 0; | ||
@@ -23,26 +26,53 @@ _hashes = {}; | ||
_computePipelineCache = {}; | ||
get [Symbol.toStringTag]() { | ||
return 'PipelineFactory'; | ||
} | ||
toString() { | ||
return `PipelineFactory(${this.device.id})`; | ||
} | ||
constructor(device) { | ||
this.device = device; | ||
this.destroyPolicy = device.props._factoryDestroyPolicy; | ||
this.cachingEnabled = device.props._cachePipelines; | ||
this.destroyPolicy = device.props._cacheDestroyPolicy; | ||
this.debug = device.props.debugFactories; | ||
} | ||
/** Return a RenderPipeline matching props. Reuses a similar pipeline if already created. */ | ||
/** Return a RenderPipeline matching supplied props. Reuses an equivalent pipeline if already created. */ | ||
createRenderPipeline(props) { | ||
if (!this.cachingEnabled) { | ||
return this.device.createRenderPipeline(props); | ||
} | ||
const allProps = { ...RenderPipeline.defaultProps, ...props }; | ||
const cache = this._renderPipelineCache; | ||
const hash = this._hashRenderPipeline(allProps); | ||
if (!this._renderPipelineCache[hash]) { | ||
const pipeline = this.device.createRenderPipeline({ | ||
let pipeline = cache[hash]?.pipeline; | ||
if (!pipeline) { | ||
pipeline = this.device.createRenderPipeline({ | ||
...allProps, | ||
id: allProps.id ? `${allProps.id}-cached` : undefined | ||
id: allProps.id ? `${allProps.id}-cached` : uid('unnamed-cached') | ||
}); | ||
pipeline.hash = hash; | ||
this._renderPipelineCache[hash] = { pipeline, useCount: 0 }; | ||
cache[hash] = { pipeline, useCount: 1 }; | ||
if (this.debug) { | ||
log.warn(`${this}: ${pipeline} created, count=${cache[hash].useCount}`)(); | ||
} | ||
} | ||
this._renderPipelineCache[hash].useCount++; | ||
return this._renderPipelineCache[hash].pipeline; | ||
else { | ||
cache[hash].useCount++; | ||
if (this.debug) { | ||
log.warn(`${this}: ${cache[hash].pipeline} reused, count=${cache[hash].useCount}, (id=${props.id})`)(); | ||
} | ||
} | ||
return pipeline; | ||
} | ||
/** Return a ComputePipeline matching supplied props. Reuses an equivalent pipeline if already created. */ | ||
createComputePipeline(props) { | ||
if (!this.cachingEnabled) { | ||
return this.device.createComputePipeline(props); | ||
} | ||
const allProps = { ...ComputePipeline.defaultProps, ...props }; | ||
const cache = this._computePipelineCache; | ||
const hash = this._hashComputePipeline(allProps); | ||
if (!this._computePipelineCache[hash]) { | ||
const pipeline = this.device.createComputePipeline({ | ||
let pipeline = cache[hash]?.pipeline; | ||
if (!pipeline) { | ||
pipeline = this.device.createComputePipeline({ | ||
...allProps, | ||
@@ -52,22 +82,72 @@ id: allProps.id ? `${allProps.id}-cached` : undefined | ||
pipeline.hash = hash; | ||
this._computePipelineCache[hash] = { pipeline, useCount: 0 }; | ||
cache[hash] = { pipeline, useCount: 1 }; | ||
if (this.debug) { | ||
log.warn(`${this}: ${pipeline} created, count=${cache[hash].useCount}`)(); | ||
} | ||
} | ||
this._computePipelineCache[hash].useCount++; | ||
return this._computePipelineCache[hash].pipeline; | ||
else { | ||
cache[hash].useCount++; | ||
if (this.debug) { | ||
log.warn(`${this}: ${cache[hash].pipeline} reused, count=${cache[hash].useCount}, (id=${props.id})`)(); | ||
} | ||
} | ||
return pipeline; | ||
} | ||
release(pipeline) { | ||
if (!this.cachingEnabled) { | ||
pipeline.destroy(); | ||
return; | ||
} | ||
const cache = this._getCache(pipeline); | ||
const hash = pipeline.hash; | ||
const cache = pipeline instanceof ComputePipeline ? this._computePipelineCache : this._renderPipelineCache; | ||
cache[hash].useCount--; | ||
if (cache[hash].useCount === 0) { | ||
if (this.destroyPolicy === 'unused') { | ||
cache[hash].pipeline.destroy(); | ||
delete cache[hash]; | ||
this._destroyPipeline(pipeline); | ||
if (this.debug) { | ||
log.warn(`${this}: ${pipeline} released and destroyed`)(); | ||
} | ||
} | ||
else if (cache[hash].useCount < 0) { | ||
log.error(`${this}: ${pipeline} released, useCount < 0, resetting`)(); | ||
cache[hash].useCount = 0; | ||
} | ||
else if (this.debug) { | ||
log.warn(`${this}: ${pipeline} released, count=${cache[hash].useCount}`)(); | ||
} | ||
} | ||
// PRIVATE | ||
/** Destroy a cached pipeline, removing it from the cache (depending on destroy policy) */ | ||
_destroyPipeline(pipeline) { | ||
const cache = this._getCache(pipeline); | ||
switch (this.destroyPolicy) { | ||
case 'never': | ||
return false; | ||
case 'unused': | ||
delete cache[pipeline.hash]; | ||
pipeline.destroy(); | ||
return true; | ||
} | ||
} | ||
/** Get the appropriate cache for the type of pipeline */ | ||
_getCache(pipeline) { | ||
let cache; | ||
if (pipeline instanceof ComputePipeline) { | ||
cache = this._computePipelineCache; | ||
} | ||
if (pipeline instanceof RenderPipeline) { | ||
cache = this._renderPipelineCache; | ||
} | ||
if (!cache) { | ||
throw new Error(`${this}`); | ||
} | ||
if (!cache[pipeline.hash]) { | ||
throw new Error(`${this}: ${pipeline} matched incorrect entry`); | ||
} | ||
return cache; | ||
} | ||
/** Calculate a hash based on all the inputs for a compute pipeline */ | ||
_hashComputePipeline(props) { | ||
const { type } = this.device; | ||
const shaderHash = this._getHash(props.shader.source); | ||
return `${shaderHash}`; | ||
return `${type}/C/${shaderHash}`; | ||
} | ||
@@ -83,6 +163,8 @@ /** Calculate a hash based on all the inputs for a render pipeline */ | ||
const bufferLayoutHash = this._getHash(JSON.stringify(props.bufferLayout)); | ||
switch (this.device.type) { | ||
const { type } = this.device; | ||
switch (type) { | ||
case 'webgl': | ||
// WebGL is more dynamic | ||
return `${vsHash}/${fsHash}V${varyingHash}BL${bufferLayoutHash}`; | ||
return `${type}/R/${vsHash}/${fsHash}V${varyingHash}BL${bufferLayoutHash}`; | ||
case 'webgpu': | ||
default: | ||
@@ -93,3 +175,3 @@ // On WebGPU we need to rebuild the pipeline if topology, parameters or bufferLayout change | ||
// create a deepHash() to deduplicate? | ||
return `${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${parameterHash}BL${bufferLayoutHash}`; | ||
return `${type}/R/${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${parameterHash}BL${bufferLayoutHash}`; | ||
} | ||
@@ -104,1 +186,2 @@ } | ||
} | ||
//# sourceMappingURL=pipeline-factory.js.map |
@@ -8,4 +8,8 @@ import { Device, Shader, ShaderProps } from '@luma.gl/core'; | ||
readonly device: Device; | ||
readonly cachingEnabled: boolean; | ||
readonly destroyPolicy: 'unused' | 'never'; | ||
readonly debug: boolean; | ||
private readonly _cache; | ||
get [Symbol.toStringTag](): string; | ||
toString(): string; | ||
/** @internal */ | ||
@@ -17,4 +21,4 @@ constructor(device: Device); | ||
release(shader: Shader): void; | ||
private _hashShader; | ||
protected _hashShader(value: Shader | ShaderProps): string; | ||
} | ||
//# sourceMappingURL=shader-factory.d.ts.map |
// luma.gl | ||
// SPDX-License-Identifier: MIT | ||
// Copyright (c) vis.gl contributors | ||
import { Shader } from '@luma.gl/core'; | ||
import { Shader, log } from '@luma.gl/core'; | ||
/** Manages a cached pool of Shaders for reuse. */ | ||
@@ -14,11 +14,24 @@ export class ShaderFactory { | ||
device; | ||
cachingEnabled; | ||
destroyPolicy; | ||
debug; | ||
_cache = {}; | ||
get [Symbol.toStringTag]() { | ||
return 'ShaderFactory'; | ||
} | ||
toString() { | ||
return `${this[Symbol.toStringTag]}(${this.device.id})`; | ||
} | ||
/** @internal */ | ||
constructor(device) { | ||
this.device = device; | ||
this.destroyPolicy = device.props._factoryDestroyPolicy; | ||
this.cachingEnabled = device.props._cacheShaders; | ||
this.destroyPolicy = device.props._cacheDestroyPolicy; | ||
this.debug = true; // device.props.debugFactories; | ||
} | ||
/** Requests a {@link Shader} from the cache, creating a new Shader only if necessary. */ | ||
createShader(props) { | ||
if (!this.cachingEnabled) { | ||
return this.device.createShader(props); | ||
} | ||
const key = this._hashShader(props); | ||
@@ -31,5 +44,13 @@ let cacheEntry = this._cache[key]; | ||
}); | ||
this._cache[key] = cacheEntry = { shader, useCount: 0 }; | ||
this._cache[key] = cacheEntry = { shader, useCount: 1 }; | ||
if (this.debug) { | ||
log.warn(`${this}: Created new shader ${shader.id}`)(); | ||
} | ||
} | ||
cacheEntry.useCount++; | ||
else { | ||
cacheEntry.useCount++; | ||
if (this.debug) { | ||
log.warn(`${this}: Reusing shader ${cacheEntry.shader.id} count=${cacheEntry.useCount}`)(); | ||
} | ||
} | ||
return cacheEntry.shader; | ||
@@ -39,2 +60,6 @@ } | ||
release(shader) { | ||
if (!this.cachingEnabled) { | ||
shader.destroy(); | ||
return; | ||
} | ||
const key = this._hashShader(shader); | ||
@@ -48,4 +73,13 @@ const cacheEntry = this._cache[key]; | ||
cacheEntry.shader.destroy(); | ||
if (this.debug) { | ||
log.warn(`${this}: Releasing shader ${shader.id}, destroyed`)(); | ||
} | ||
} | ||
} | ||
else if (cacheEntry.useCount < 0) { | ||
throw new Error(`ShaderFactory: Shader ${shader.id} released too many times`); | ||
} | ||
else if (this.debug) { | ||
log.warn(`${this}: Releasing shader ${shader.id} count=${cacheEntry.useCount}`)(); | ||
} | ||
} | ||
@@ -58,1 +92,2 @@ } | ||
} | ||
//# sourceMappingURL=shader-factory.js.map |
@@ -19,1 +19,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=cone-geometry.js.map |
@@ -202,1 +202,2 @@ // luma.gl | ||
}; | ||
//# sourceMappingURL=cube-geometry.js.map |
@@ -17,1 +17,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=cylinder-geometry.js.map |
@@ -156,1 +156,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=ico-sphere-geometry.js.map |
@@ -106,1 +106,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=plane-geometry.js.map |
@@ -88,1 +88,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=sphere-geometry.js.map |
@@ -112,1 +112,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=truncated-cone-geometry.js.map |
@@ -5,1 +5,2 @@ // luma.gl | ||
export {}; | ||
//# sourceMappingURL=geometry-table.js.map |
@@ -43,1 +43,2 @@ // luma.gl | ||
// } | ||
//# sourceMappingURL=geometry-utils.js.map |
@@ -93,1 +93,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=geometry.js.map |
@@ -103,1 +103,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=gpu-geometry.js.map |
@@ -46,1 +46,2 @@ "use strict"; | ||
*/ | ||
//# sourceMappingURL=gpu-table.js.map |
@@ -47,1 +47,2 @@ // luma.gl | ||
export { LegacyPickingManager } from "./modules/picking/legacy-picking-manager.js"; | ||
//# sourceMappingURL=index.js.map |
@@ -119,2 +119,4 @@ import type { TypedArray } from '@math.gl/types'; | ||
_lastDrawTimestamp: number; | ||
get [Symbol.toStringTag](): string; | ||
toString(): string; | ||
constructor(device: Device, props: ModelProps); | ||
@@ -200,8 +202,10 @@ destroy(): void; | ||
*/ | ||
setUniforms(uniforms: Record<string, UniformValue>): void; | ||
setUniformsWebGL(uniforms: Record<string, UniformValue>): void; | ||
/** | ||
* @deprecated Updates shader module settings (which results in uniforms being set) | ||
*/ | ||
updateModuleSettings(props: Record<string, any>): void; | ||
/** Get texture / texture view from any async textures */ | ||
updateModuleSettingsWebGL(props: Record<string, any>): void; | ||
/** Check that bindings are loaded. Returns id of first binding that is still loading. */ | ||
_areBindingsLoading(): string | false; | ||
/** Extracts texture view from loaded async textures. Returns null if any textures have not yet been loaded. */ | ||
_getBindings(): Record<string, Binding>; | ||
@@ -208,0 +212,0 @@ /** Get the timestamp of the latest updated bound GPU memory resource (buffer/texture). */ |
@@ -114,2 +114,8 @@ // luma.gl | ||
_lastDrawTimestamp = -1; | ||
get [Symbol.toStringTag]() { | ||
return 'Model'; | ||
} | ||
toString() { | ||
return `Model(${this.id})`; | ||
} | ||
constructor(device, props) { | ||
@@ -175,3 +181,4 @@ this.props = { ...Model.defaultProps, ...props }; | ||
this.vertexArray = device.createVertexArray({ | ||
renderPipeline: this.pipeline | ||
shaderLayout: this.pipeline.shaderLayout, | ||
bufferLayout: this.pipeline.bufferLayout | ||
}); | ||
@@ -205,7 +212,7 @@ // Now we can apply geometry attributes | ||
if (props.uniforms) { | ||
this.setUniforms(props.uniforms); | ||
this.setUniformsWebGL(props.uniforms); | ||
} | ||
if (props.moduleSettings) { | ||
// log.warn('Model.props.moduleSettings is deprecated. Use Model.shaderInputs.setProps()')(); | ||
this.updateModuleSettings(props.moduleSettings); | ||
this.updateModuleSettingsWebGL(props.moduleSettings); | ||
} | ||
@@ -219,13 +226,15 @@ if (props.transformFeedback) { | ||
destroy() { | ||
if (this._destroyed) | ||
return; | ||
this.pipelineFactory.release(this.pipeline); | ||
this.shaderFactory.release(this.pipeline.vs); | ||
if (this.pipeline.fs) { | ||
this.shaderFactory.release(this.pipeline.fs); | ||
if (!this._destroyed) { | ||
// Release pipeline before we destroy the shaders used by the pipeline | ||
this.pipelineFactory.release(this.pipeline); | ||
// Release the shaders | ||
this.shaderFactory.release(this.pipeline.vs); | ||
if (this.pipeline.fs) { | ||
this.shaderFactory.release(this.pipeline.fs); | ||
} | ||
this._uniformStore.destroy(); | ||
// TODO - mark resource as managed and destroyIfManaged() ? | ||
this._gpuGeometry?.destroy(); | ||
this._destroyed = true; | ||
} | ||
this._uniformStore.destroy(); | ||
// TODO - mark resource as managed and destroyIfManaged() ? | ||
this._gpuGeometry?.destroy(); | ||
this._destroyed = true; | ||
} | ||
@@ -254,5 +263,17 @@ // Draw call | ||
draw(renderPass) { | ||
this.predraw(); | ||
const loadingBinding = this._areBindingsLoading(); | ||
if (loadingBinding) { | ||
log.info(LOG_DRAW_PRIORITY, `>>> DRAWING ABORTED ${this.id}: ${loadingBinding} not loaded`)(); | ||
return false; | ||
} | ||
try { | ||
renderPass.pushDebugGroup(`${this}.predraw(${renderPass})`); | ||
this.predraw(); | ||
} | ||
finally { | ||
renderPass.popDebugGroup(); | ||
} | ||
let drawSuccess; | ||
try { | ||
renderPass.pushDebugGroup(`${this}.draw(${renderPass})`); | ||
this._logDrawCallStart(); | ||
@@ -293,2 +314,3 @@ // Update the pipeline if invalidated | ||
finally { | ||
renderPass.popDebugGroup(); | ||
this._logDrawCallEnd(); | ||
@@ -351,3 +373,4 @@ } | ||
this.vertexArray = this.device.createVertexArray({ | ||
renderPipeline: this.pipeline | ||
shaderLayout: this.pipeline.shaderLayout, | ||
bufferLayout: this.pipeline.bufferLayout | ||
}); | ||
@@ -495,3 +518,3 @@ // Reapply geometry attributes to the new vertex array | ||
*/ | ||
setUniforms(uniforms) { | ||
setUniformsWebGL(uniforms) { | ||
if (!isObjectEmpty(uniforms)) { | ||
@@ -506,3 +529,3 @@ this.pipeline.setUniformsWebGL(uniforms); | ||
*/ | ||
updateModuleSettings(props) { | ||
updateModuleSettingsWebGL(props) { | ||
// log.warn('Model.updateModuleSettings is deprecated. Use Model.shaderInputs.setProps()')(); | ||
@@ -515,16 +538,26 @@ const { bindings, uniforms } = splitUniformsAndBindings(this._getModuleUniforms(props)); | ||
// Internal methods | ||
/** Get texture / texture view from any async textures */ | ||
/** Check that bindings are loaded. Returns id of first binding that is still loading. */ | ||
_areBindingsLoading() { | ||
for (const binding of Object.values(this.bindings)) { | ||
if (binding instanceof AsyncTexture && !binding.isReady) { | ||
return binding.id; | ||
} | ||
} | ||
return false; | ||
} | ||
/** Extracts texture view from loaded async textures. Returns null if any textures have not yet been loaded. */ | ||
_getBindings() { | ||
// Extract actual textures from async textures. If not loaded, null | ||
return Object.entries(this.bindings).reduce((acc, [name, binding]) => { | ||
const validBindings = {}; | ||
for (const [name, binding] of Object.entries(this.bindings)) { | ||
if (binding instanceof AsyncTexture) { | ||
// Check that async textures are loaded | ||
if (binding.isReady) { | ||
acc[name] = binding.texture; | ||
validBindings[name] = binding.texture; | ||
} | ||
} | ||
else { | ||
acc[name] = binding; | ||
validBindings[name] = binding; | ||
} | ||
return acc; | ||
}, {}); | ||
} | ||
return validBindings; | ||
} | ||
@@ -731,1 +764,2 @@ /** Get the timestamp of the latest updated bound GPU memory resource (buffer/texture). */ | ||
} | ||
//# sourceMappingURL=model.js.map |
@@ -21,1 +21,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=split-uniforms-and-bindings.js.map |
@@ -5,6 +5,23 @@ // luma.gl | ||
import { ClipSpace } from "./clip-space.js"; | ||
const BACKGROUND_FS_WGSL = /* wgsl */ `\ | ||
@group(0) @binding(0) var backgroundTexture: texture_2d<f32>; | ||
@group(0) @binding(1) var backgroundTextureSampler: sampler; | ||
fn billboardTexture_getTextureUV(coordinates: vec2<f32>) -> vec2<f32> { | ||
let iTexSize: vec2<u32> = textureDimensions(backgroundTexture, 0) * 2; | ||
let texSize: vec2<f32> = vec2<f32>(f32(iTexSize.x), f32(iTexSize.y)); | ||
var position: vec2<f32> = coordinates.xy / texSize; | ||
return position; | ||
} | ||
@fragment | ||
fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4<f32> { | ||
let position: vec2<f32> = billboardTexture_getTextureUV(inputs.coordinate); | ||
return textureSample(backgroundTexture, backgroundTextureSampler, position); | ||
} | ||
`; | ||
const BACKGROUND_FS = /* glsl */ `\ | ||
#version 300 es | ||
precision highp float; | ||
precision highp float; | ||
uniform sampler2D backgroundTexture; | ||
@@ -14,3 +31,3 @@ out vec4 fragColor; | ||
vec2 billboardTexture_getTextureUV() { | ||
ivec2 iTexSize = textureSize(backgroundTexture, 0) * 2; | ||
ivec2 iTexSize = textureDimensions(backgroundTexture, 0) * 2; | ||
vec2 texSize = vec2(float(iTexSize.x), float(iTexSize.y)); | ||
@@ -33,2 +50,3 @@ vec2 position = gl_FragCoord.xy / texSize; | ||
id: props.id || 'background-texture-model', | ||
source: BACKGROUND_FS_WGSL, | ||
fs: BACKGROUND_FS, | ||
@@ -63,1 +81,2 @@ parameters: { | ||
} | ||
//# sourceMappingURL=billboard-texture-model.js.map |
@@ -37,1 +37,2 @@ // luma.gl | ||
}; | ||
//# sourceMappingURL=billboard-texture-module.js.map |
@@ -6,23 +6,25 @@ // luma.gl | ||
import { Geometry } from "../geometry/geometry.js"; | ||
import { uid } from "../utils/uid.js"; | ||
const CLIPSPACE_VERTEX_SHADER_WGSL = /* wgsl */ `\ | ||
struct VertexInput { | ||
aClipSpacePosition: vec2<f32>; | ||
aTexCoord: vec2<f32>; | ||
aCoordinate: vec2<f32>; | ||
struct VertexInputs { | ||
@location(0) clipSpacePosition: vec2<f32>, | ||
@location(1) texCoord: vec2<f32>, | ||
@location(2) coordinate: vec2<f32> | ||
} | ||
struct FragmentInput { | ||
@builtin(position) Position : vec4<f32>; | ||
@location(0) position : vec2<f32>; | ||
@location(1) coordinate : vec2<f32>; | ||
@location(2) uv : vec2<f32>; | ||
struct FragmentInputs { | ||
@builtin(position) Position : vec4<f32>, | ||
@location(0) position : vec2<f32>, | ||
@location(1) coordinate : vec2<f32>, | ||
@location(2) uv : vec2<f32> | ||
}; | ||
@stage(vertex) | ||
fn vertexMain(input: VertexInput) -> FragmentInput { | ||
var output: FragmentInput; | ||
output.Position = vec4(aClipSpacePosition, 0., 1.); | ||
output.position = input.aClipSpacePosition; | ||
output.coordinate = input.aCoordinate; | ||
output.uv = aTexCoord; | ||
@vertex | ||
fn vertexMain(inputs: VertexInputs) -> FragmentInputs { | ||
var outputs: FragmentInputs; | ||
outputs.Position = vec4(inputs.clipSpacePosition, 0., 1.); | ||
outputs.position = inputs.clipSpacePosition; | ||
outputs.coordinate = inputs.coordinate; | ||
outputs.uv = inputs.texCoord; | ||
return outputs; | ||
} | ||
@@ -32,5 +34,5 @@ `; | ||
#version 300 es | ||
in vec2 aClipSpacePosition; | ||
in vec2 aTexCoord; | ||
in vec2 aCoordinate; | ||
in vec2 clipSpacePosition; | ||
in vec2 texCoord; | ||
in vec2 coordinate; | ||
@@ -42,6 +44,6 @@ out vec2 position; | ||
void main(void) { | ||
gl_Position = vec4(aClipSpacePosition, 0., 1.); | ||
position = aClipSpacePosition; | ||
coordinate = aCoordinate; | ||
uv = aTexCoord; | ||
gl_Position = vec4(clipSpacePosition, 0., 1.); | ||
position = clipSpacePosition; | ||
coordinate = coordinate; | ||
uv = texCoord; | ||
} | ||
@@ -59,7 +61,7 @@ `; | ||
if (props.source) { | ||
props = { ...props, source: `${CLIPSPACE_VERTEX_SHADER_WGSL}\m${props.source}` }; | ||
props = { ...props, source: `${CLIPSPACE_VERTEX_SHADER_WGSL}\n${props.source}` }; | ||
} | ||
super(device, { | ||
id: props.id || uid('clip-space'), | ||
...props, | ||
source: CLIPSPACE_VERTEX_SHADER_WGSL, | ||
vs: CLIPSPACE_VERTEX_SHADER, | ||
@@ -71,5 +73,5 @@ vertexCount: 4, | ||
attributes: { | ||
aClipSpacePosition: { size: 2, value: new Float32Array(POSITIONS) }, | ||
aTexCoord: { size: 2, value: new Float32Array(TEX_COORDS) }, | ||
aCoordinate: { size: 2, value: new Float32Array(TEX_COORDS) } | ||
clipSpacePosition: { size: 2, value: new Float32Array(POSITIONS) }, | ||
texCoord: { size: 2, value: new Float32Array(TEX_COORDS) }, | ||
coordinate: { size: 2, value: new Float32Array(TEX_COORDS) } | ||
} | ||
@@ -80,1 +82,2 @@ }) | ||
} | ||
//# sourceMappingURL=clip-space.js.map |
@@ -177,1 +177,2 @@ // luma.gl | ||
// } | ||
//# sourceMappingURL=color-picking.js.map |
@@ -148,1 +148,2 @@ // luma.gl | ||
}; | ||
//# sourceMappingURL=index-picking.js.map |
@@ -76,1 +76,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=legacy-picking-manager.js.map |
@@ -101,1 +101,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=picking-manager.js.map |
@@ -109,1 +109,2 @@ // luma.gl | ||
}; | ||
//# sourceMappingURL=picking-uniforms.js.map |
@@ -117,1 +117,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=get-fragment-shader.js.map |
@@ -34,3 +34,13 @@ // luma.gl | ||
this.clipSpace = new ClipSpace(device, { | ||
fs: `\ | ||
source: /* wgsl */ `\ | ||
@group(0) @binding(0) var sourceTexture: texture_2d<f32>; | ||
@group(0) @binding(1) var sourceTextureSampler: sampler; | ||
@fragment | ||
fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4<f32> { | ||
let texCoord: vec2<f32> = inputs.coordinate; | ||
return textureSample(sourceTexture, sourceTextureSampler, texCoord); | ||
} | ||
`, | ||
fs: /* glsl */ `\ | ||
#version 300 es | ||
@@ -108,6 +118,5 @@ | ||
first = false; | ||
// eslint-disable-next-line no-shadow | ||
const sourceTexture = this.swapFramebuffers.current.colorAttachments[0].texture; | ||
const swapBufferTexture = this.swapFramebuffers.current.colorAttachments[0].texture; | ||
const bindings = { | ||
sourceTexture | ||
sourceTexture: swapBufferTexture | ||
// texSize: [sourceTextures.width, sourceTextures.height] | ||
@@ -190,1 +199,2 @@ }; | ||
} | ||
//# sourceMappingURL=shader-pass-renderer.js.map |
@@ -88,1 +88,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=group-node.js.map |
@@ -39,1 +39,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=model-node.js.map |
@@ -157,1 +157,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=scenegraph-node.js.map |
@@ -119,1 +119,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=shader-inputs.js.map |
@@ -51,1 +51,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=deep-equal.js.map |
@@ -15,1 +15,2 @@ // luma.gl | ||
} | ||
//# sourceMappingURL=uid.js.map |
{ | ||
"name": "@luma.gl/engine", | ||
"version": "9.1.0-alpha.19", | ||
"version": "9.1.0-beta.1", | ||
"description": "3D Engine Components for luma.gl", | ||
@@ -43,12 +43,12 @@ "type": "module", | ||
"peerDependencies": { | ||
"@luma.gl/core": "9.1.0-alpha.17", | ||
"@luma.gl/shadertools": "9.1.0-alpha.17" | ||
"@luma.gl/core": "9.1.0-alpha.19", | ||
"@luma.gl/shadertools": "9.1.0-alpha.19" | ||
}, | ||
"dependencies": { | ||
"@math.gl/core": "4.1.0-alpha.3", | ||
"@math.gl/types": "4.1.0-alpha.3", | ||
"@math.gl/core": "^4.1.0", | ||
"@math.gl/types": "^4.1.0", | ||
"@probe.gl/log": "^4.0.8", | ||
"@probe.gl/stats": "^4.0.8" | ||
}, | ||
"gitHead": "c836c09a5105e2515a036abff4a1b926d245a152" | ||
"gitHead": "e04bfb138f96d690992f0e0a67b358cbe23bd59f" | ||
} |
@@ -19,2 +19,3 @@ // luma.gl, MIT license | ||
import {loadImageBitmap} from '../application-utils/load-file'; | ||
import {uid} from '../utils/uid'; | ||
@@ -58,2 +59,3 @@ export type AsyncTextureProps = Omit<TextureProps, 'data'> & AsyncTextureDataProps; | ||
readonly device: Device; | ||
readonly id: string; | ||
@@ -75,4 +77,14 @@ // TODO - should we type these as possibly `null`? It will make usage harder? | ||
get [Symbol.toStringTag]() { | ||
return 'AsyncTexture'; | ||
} | ||
toString(): string { | ||
return `AsyncTexture:"${this.id}"(${this.isReady ? 'ready' : 'loading'})`; | ||
} | ||
constructor(device: Device, props: AsyncTextureProps) { | ||
this.device = device; | ||
this.id = props.id || uid('async-texture'); | ||
// this.id = typeof props?.data === 'string' ? props.data.slice(-20) : uid('async-texture'); | ||
@@ -79,0 +91,0 @@ // Signature: new AsyncTexture(device, {data: url}) |
@@ -6,3 +6,3 @@ // luma.gl | ||
import type {BufferProps, FramebufferProps} from '@luma.gl/core'; | ||
import {Device, Resource, Buffer, Framebuffer} from '@luma.gl/core'; | ||
import {Device, Resource, Buffer, Framebuffer, Texture} from '@luma.gl/core'; | ||
@@ -43,3 +43,27 @@ /** | ||
constructor(device: Device, props: FramebufferProps) { | ||
super({current: device.createFramebuffer(props), next: device.createFramebuffer(props)}); | ||
props = {...props}; | ||
let colorAttachments = props.colorAttachments?.map(colorAttachment => | ||
typeof colorAttachment !== 'string' | ||
? colorAttachment | ||
: device.createTexture({ | ||
format: colorAttachment, | ||
usage: Texture.COPY_DST | Texture.RENDER_ATTACHMENT | ||
}) | ||
); | ||
const current = device.createFramebuffer({...props, colorAttachments}); | ||
colorAttachments = props.colorAttachments?.map(colorAttachment => | ||
typeof colorAttachment !== 'string' | ||
? colorAttachment | ||
: device.createTexture({ | ||
format: colorAttachment, | ||
usage: Texture.COPY_DST | Texture.RENDER_ATTACHMENT | ||
}) | ||
); | ||
const next = device.createFramebuffer({...props, colorAttachments}); | ||
super({current, next}); | ||
} | ||
@@ -46,0 +70,0 @@ |
@@ -6,4 +6,5 @@ // luma.gl | ||
import {Buffer, Device, Framebuffer, RenderPassProps, Sampler, Texture} from '@luma.gl/core'; | ||
import {getPassthroughFS} from '@luma.gl/shadertools'; | ||
import {Model, ModelProps} from '../model/model'; | ||
import {getPassthroughFS} from '@luma.gl/shadertools'; | ||
import {uid} from '../utils/uid'; | ||
@@ -64,3 +65,3 @@ /** | ||
this.model = new Model(this.device, { | ||
id: props.id || 'texture-transform-model', | ||
id: props.id || uid('texture-transform-model'), | ||
fs: | ||
@@ -67,0 +68,0 @@ props.fs || |
@@ -6,3 +6,4 @@ // luma.gl | ||
import type {RenderPipelineProps, ComputePipelineProps} from '@luma.gl/core'; | ||
import {Device, RenderPipeline, ComputePipeline} from '@luma.gl/core'; | ||
import {Device, RenderPipeline, ComputePipeline, log} from '@luma.gl/core'; | ||
import {uid} from '../utils/uid'; | ||
@@ -28,3 +29,5 @@ export type PipelineFactoryProps = RenderPipelineProps; | ||
readonly device: Device; | ||
readonly cachingEnabled: boolean; | ||
readonly destroyPolicy: 'unused' | 'never'; | ||
readonly debug: boolean; | ||
@@ -36,33 +39,65 @@ private _hashCounter: number = 0; | ||
get [Symbol.toStringTag](): string { | ||
return 'PipelineFactory'; | ||
} | ||
toString(): string { | ||
return `PipelineFactory(${this.device.id})`; | ||
} | ||
constructor(device: Device) { | ||
this.device = device; | ||
this.destroyPolicy = device.props._factoryDestroyPolicy; | ||
this.cachingEnabled = device.props._cachePipelines; | ||
this.destroyPolicy = device.props._cacheDestroyPolicy; | ||
this.debug = device.props.debugFactories; | ||
} | ||
/** Return a RenderPipeline matching props. Reuses a similar pipeline if already created. */ | ||
/** Return a RenderPipeline matching supplied props. Reuses an equivalent pipeline if already created. */ | ||
createRenderPipeline(props: RenderPipelineProps): RenderPipeline { | ||
if (!this.cachingEnabled) { | ||
return this.device.createRenderPipeline(props); | ||
} | ||
const allProps: Required<RenderPipelineProps> = {...RenderPipeline.defaultProps, ...props}; | ||
const cache = this._renderPipelineCache; | ||
const hash = this._hashRenderPipeline(allProps); | ||
if (!this._renderPipelineCache[hash]) { | ||
const pipeline = this.device.createRenderPipeline({ | ||
let pipeline: RenderPipeline = cache[hash]?.pipeline; | ||
if (!pipeline) { | ||
pipeline = this.device.createRenderPipeline({ | ||
...allProps, | ||
id: allProps.id ? `${allProps.id}-cached` : undefined | ||
id: allProps.id ? `${allProps.id}-cached` : uid('unnamed-cached') | ||
}); | ||
pipeline.hash = hash; | ||
this._renderPipelineCache[hash] = {pipeline, useCount: 0}; | ||
cache[hash] = {pipeline, useCount: 1}; | ||
if (this.debug) { | ||
log.warn(`${this}: ${pipeline} created, count=${cache[hash].useCount}`)(); | ||
} | ||
} else { | ||
cache[hash].useCount++; | ||
if (this.debug) { | ||
log.warn( | ||
`${this}: ${cache[hash].pipeline} reused, count=${cache[hash].useCount}, (id=${props.id})` | ||
)(); | ||
} | ||
} | ||
this._renderPipelineCache[hash].useCount++; | ||
return this._renderPipelineCache[hash].pipeline; | ||
return pipeline; | ||
} | ||
/** Return a ComputePipeline matching supplied props. Reuses an equivalent pipeline if already created. */ | ||
createComputePipeline(props: ComputePipelineProps): ComputePipeline { | ||
if (!this.cachingEnabled) { | ||
return this.device.createComputePipeline(props); | ||
} | ||
const allProps: Required<ComputePipelineProps> = {...ComputePipeline.defaultProps, ...props}; | ||
const cache = this._computePipelineCache; | ||
const hash = this._hashComputePipeline(allProps); | ||
if (!this._computePipelineCache[hash]) { | ||
const pipeline = this.device.createComputePipeline({ | ||
let pipeline: ComputePipeline = cache[hash]?.pipeline; | ||
if (!pipeline) { | ||
pipeline = this.device.createComputePipeline({ | ||
...allProps, | ||
@@ -72,19 +107,38 @@ id: allProps.id ? `${allProps.id}-cached` : undefined | ||
pipeline.hash = hash; | ||
this._computePipelineCache[hash] = {pipeline, useCount: 0}; | ||
cache[hash] = {pipeline, useCount: 1}; | ||
if (this.debug) { | ||
log.warn(`${this}: ${pipeline} created, count=${cache[hash].useCount}`)(); | ||
} | ||
} else { | ||
cache[hash].useCount++; | ||
if (this.debug) { | ||
log.warn( | ||
`${this}: ${cache[hash].pipeline} reused, count=${cache[hash].useCount}, (id=${props.id})` | ||
)(); | ||
} | ||
} | ||
this._computePipelineCache[hash].useCount++; | ||
return this._computePipelineCache[hash].pipeline; | ||
return pipeline; | ||
} | ||
release(pipeline: RenderPipeline | ComputePipeline): void { | ||
if (!this.cachingEnabled) { | ||
pipeline.destroy(); | ||
return; | ||
} | ||
const cache = this._getCache(pipeline); | ||
const hash = pipeline.hash; | ||
const cache = | ||
pipeline instanceof ComputePipeline ? this._computePipelineCache : this._renderPipelineCache; | ||
cache[hash].useCount--; | ||
if (cache[hash].useCount === 0) { | ||
if (this.destroyPolicy === 'unused') { | ||
cache[hash].pipeline.destroy(); | ||
delete cache[hash]; | ||
this._destroyPipeline(pipeline); | ||
if (this.debug) { | ||
log.warn(`${this}: ${pipeline} released and destroyed`)(); | ||
} | ||
} else if (cache[hash].useCount < 0) { | ||
log.error(`${this}: ${pipeline} released, useCount < 0, resetting`)(); | ||
cache[hash].useCount = 0; | ||
} else if (this.debug) { | ||
log.warn(`${this}: ${pipeline} released, count=${cache[hash].useCount}`)(); | ||
} | ||
@@ -94,5 +148,45 @@ } | ||
// PRIVATE | ||
/** Destroy a cached pipeline, removing it from the cache (depending on destroy policy) */ | ||
private _destroyPipeline(pipeline: RenderPipeline | ComputePipeline): boolean { | ||
const cache = this._getCache(pipeline); | ||
switch (this.destroyPolicy) { | ||
case 'never': | ||
return false; | ||
case 'unused': | ||
delete cache[pipeline.hash]; | ||
pipeline.destroy(); | ||
return true; | ||
} | ||
} | ||
/** Get the appropriate cache for the type of pipeline */ | ||
private _getCache( | ||
pipeline: RenderPipeline | ComputePipeline | ||
): Record<string, RenderPipelineCacheItem> | Record<string, ComputePipelineCacheItem> { | ||
let cache: | ||
| Record<string, RenderPipelineCacheItem> | ||
| Record<string, ComputePipelineCacheItem> | ||
| undefined; | ||
if (pipeline instanceof ComputePipeline) { | ||
cache = this._computePipelineCache; | ||
} | ||
if (pipeline instanceof RenderPipeline) { | ||
cache = this._renderPipelineCache; | ||
} | ||
if (!cache) { | ||
throw new Error(`${this}`); | ||
} | ||
if (!cache[pipeline.hash]) { | ||
throw new Error(`${this}: ${pipeline} matched incorrect entry`); | ||
} | ||
return cache; | ||
} | ||
/** Calculate a hash based on all the inputs for a compute pipeline */ | ||
private _hashComputePipeline(props: ComputePipelineProps): string { | ||
const {type} = this.device; | ||
const shaderHash = this._getHash(props.shader.source); | ||
return `${shaderHash}`; | ||
return `${type}/C/${shaderHash}`; | ||
} | ||
@@ -111,7 +205,9 @@ | ||
switch (this.device.type) { | ||
const {type} = this.device; | ||
switch (type) { | ||
case 'webgl': | ||
// WebGL is more dynamic | ||
return `${vsHash}/${fsHash}V${varyingHash}BL${bufferLayoutHash}`; | ||
return `${type}/R/${vsHash}/${fsHash}V${varyingHash}BL${bufferLayoutHash}`; | ||
case 'webgpu': | ||
default: | ||
@@ -122,3 +218,3 @@ // On WebGPU we need to rebuild the pipeline if topology, parameters or bufferLayout change | ||
// create a deepHash() to deduplicate? | ||
return `${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${parameterHash}BL${bufferLayoutHash}`; | ||
return `${type}/R/${vsHash}/${fsHash}V${varyingHash}T${props.topology}P${parameterHash}BL${bufferLayoutHash}`; | ||
} | ||
@@ -125,0 +221,0 @@ } |
@@ -5,3 +5,3 @@ // luma.gl | ||
import {Device, Shader, ShaderProps} from '@luma.gl/core'; | ||
import {Device, Shader, ShaderProps, log} from '@luma.gl/core'; | ||
@@ -19,9 +19,22 @@ /** Manages a cached pool of Shaders for reuse. */ | ||
public readonly device: Device; | ||
readonly cachingEnabled: boolean; | ||
readonly destroyPolicy: 'unused' | 'never'; | ||
readonly debug: boolean; | ||
private readonly _cache: Record<string, {shader: Shader; useCount: number}> = {}; | ||
get [Symbol.toStringTag](): string { | ||
return 'ShaderFactory'; | ||
} | ||
toString(): string { | ||
return `${this[Symbol.toStringTag]}(${this.device.id})`; | ||
} | ||
/** @internal */ | ||
constructor(device: Device) { | ||
this.device = device; | ||
this.destroyPolicy = device.props._factoryDestroyPolicy; | ||
this.cachingEnabled = device.props._cacheShaders; | ||
this.destroyPolicy = device.props._cacheDestroyPolicy; | ||
this.debug = true; // device.props.debugFactories; | ||
} | ||
@@ -31,2 +44,6 @@ | ||
createShader(props: ShaderProps): Shader { | ||
if (!this.cachingEnabled) { | ||
return this.device.createShader(props); | ||
} | ||
const key = this._hashShader(props); | ||
@@ -40,6 +57,13 @@ | ||
}); | ||
this._cache[key] = cacheEntry = {shader, useCount: 0}; | ||
this._cache[key] = cacheEntry = {shader, useCount: 1}; | ||
if (this.debug) { | ||
log.warn(`${this}: Created new shader ${shader.id}`)(); | ||
} | ||
} else { | ||
cacheEntry.useCount++; | ||
if (this.debug) { | ||
log.warn(`${this}: Reusing shader ${cacheEntry.shader.id} count=${cacheEntry.useCount}`)(); | ||
} | ||
} | ||
cacheEntry.useCount++; | ||
return cacheEntry.shader; | ||
@@ -50,2 +74,7 @@ } | ||
release(shader: Shader): void { | ||
if (!this.cachingEnabled) { | ||
shader.destroy(); | ||
return; | ||
} | ||
const key = this._hashShader(shader); | ||
@@ -59,3 +88,10 @@ const cacheEntry = this._cache[key]; | ||
cacheEntry.shader.destroy(); | ||
if (this.debug) { | ||
log.warn(`${this}: Releasing shader ${shader.id}, destroyed`)(); | ||
} | ||
} | ||
} else if (cacheEntry.useCount < 0) { | ||
throw new Error(`ShaderFactory: Shader ${shader.id} released too many times`); | ||
} else if (this.debug) { | ||
log.warn(`${this}: Releasing shader ${shader.id} count=${cacheEntry.useCount}`)(); | ||
} | ||
@@ -67,5 +103,5 @@ } | ||
private _hashShader(value: Shader | ShaderProps): string { | ||
protected _hashShader(value: Shader | ShaderProps): string { | ||
return `${value.stage}:${value.source}`; | ||
} | ||
} |
@@ -224,2 +224,10 @@ // luma.gl | ||
get [Symbol.toStringTag](): string { | ||
return 'Model'; | ||
} | ||
toString(): string { | ||
return `Model(${this.id})`; | ||
} | ||
constructor(device: Device, props: ModelProps) { | ||
@@ -299,3 +307,4 @@ this.props = {...Model.defaultProps, ...props}; | ||
this.vertexArray = device.createVertexArray({ | ||
renderPipeline: this.pipeline | ||
shaderLayout: this.pipeline.shaderLayout, | ||
bufferLayout: this.pipeline.bufferLayout | ||
}); | ||
@@ -332,7 +341,7 @@ | ||
if (props.uniforms) { | ||
this.setUniforms(props.uniforms); | ||
this.setUniformsWebGL(props.uniforms); | ||
} | ||
if (props.moduleSettings) { | ||
// log.warn('Model.props.moduleSettings is deprecated. Use Model.shaderInputs.setProps()')(); | ||
this.updateModuleSettings(props.moduleSettings); | ||
this.updateModuleSettingsWebGL(props.moduleSettings); | ||
} | ||
@@ -348,12 +357,15 @@ if (props.transformFeedback) { | ||
destroy(): void { | ||
if (this._destroyed) return; | ||
this.pipelineFactory.release(this.pipeline); | ||
this.shaderFactory.release(this.pipeline.vs); | ||
if (this.pipeline.fs) { | ||
this.shaderFactory.release(this.pipeline.fs); | ||
if (!this._destroyed) { | ||
// Release pipeline before we destroy the shaders used by the pipeline | ||
this.pipelineFactory.release(this.pipeline); | ||
// Release the shaders | ||
this.shaderFactory.release(this.pipeline.vs); | ||
if (this.pipeline.fs) { | ||
this.shaderFactory.release(this.pipeline.fs); | ||
} | ||
this._uniformStore.destroy(); | ||
// TODO - mark resource as managed and destroyIfManaged() ? | ||
this._gpuGeometry?.destroy(); | ||
this._destroyed = true; | ||
} | ||
this._uniformStore.destroy(); | ||
// TODO - mark resource as managed and destroyIfManaged() ? | ||
this._gpuGeometry?.destroy(); | ||
this._destroyed = true; | ||
} | ||
@@ -387,6 +399,18 @@ | ||
draw(renderPass: RenderPass): boolean { | ||
this.predraw(); | ||
const loadingBinding = this._areBindingsLoading(); | ||
if (loadingBinding) { | ||
log.info(LOG_DRAW_PRIORITY, `>>> DRAWING ABORTED ${this.id}: ${loadingBinding} not loaded`)(); | ||
return false; | ||
} | ||
try { | ||
renderPass.pushDebugGroup(`${this}.predraw(${renderPass})`); | ||
this.predraw(); | ||
} finally { | ||
renderPass.popDebugGroup(); | ||
} | ||
let drawSuccess: boolean; | ||
try { | ||
renderPass.pushDebugGroup(`${this}.draw(${renderPass})`); | ||
this._logDrawCallStart(); | ||
@@ -402,2 +426,3 @@ | ||
// TODO this is a busy initialized check for all bindings every frame | ||
const syncBindings = this._getBindings(); | ||
@@ -431,2 +456,3 @@ this.pipeline.setBindings(syncBindings, { | ||
} finally { | ||
renderPass.popDebugGroup(); | ||
this._logDrawCallEnd(); | ||
@@ -498,3 +524,4 @@ } | ||
this.vertexArray = this.device.createVertexArray({ | ||
renderPipeline: this.pipeline | ||
shaderLayout: this.pipeline.shaderLayout, | ||
bufferLayout: this.pipeline.bufferLayout | ||
}); | ||
@@ -667,3 +694,3 @@ | ||
*/ | ||
setUniforms(uniforms: Record<string, UniformValue>): void { | ||
setUniformsWebGL(uniforms: Record<string, UniformValue>): void { | ||
if (!isObjectEmpty(uniforms)) { | ||
@@ -679,3 +706,3 @@ this.pipeline.setUniformsWebGL(uniforms); | ||
*/ | ||
updateModuleSettings(props: Record<string, any>): void { | ||
updateModuleSettingsWebGL(props: Record<string, any>): void { | ||
// log.warn('Model.updateModuleSettings is deprecated. Use Model.shaderInputs.setProps()')(); | ||
@@ -690,15 +717,28 @@ const {bindings, uniforms} = splitUniformsAndBindings(this._getModuleUniforms(props)); | ||
/** Get texture / texture view from any async textures */ | ||
/** Check that bindings are loaded. Returns id of first binding that is still loading. */ | ||
_areBindingsLoading(): string | false { | ||
for (const binding of Object.values(this.bindings)) { | ||
if (binding instanceof AsyncTexture && !binding.isReady) { | ||
return binding.id; | ||
} | ||
} | ||
return false; | ||
} | ||
/** Extracts texture view from loaded async textures. Returns null if any textures have not yet been loaded. */ | ||
_getBindings(): Record<string, Binding> { | ||
// Extract actual textures from async textures. If not loaded, null | ||
return Object.entries(this.bindings).reduce<Record<string, Binding>>((acc, [name, binding]) => { | ||
const validBindings: Record<string, Binding> = {}; | ||
for (const [name, binding] of Object.entries(this.bindings)) { | ||
if (binding instanceof AsyncTexture) { | ||
// Check that async textures are loaded | ||
if (binding.isReady) { | ||
acc[name] = binding.texture; | ||
validBindings[name] = binding.texture; | ||
} | ||
} else { | ||
acc[name] = binding; | ||
validBindings[name] = binding; | ||
} | ||
return acc; | ||
}, {}); | ||
} | ||
return validBindings; | ||
} | ||
@@ -705,0 +745,0 @@ |
@@ -9,6 +9,24 @@ // luma.gl | ||
const BACKGROUND_FS_WGSL = /* wgsl */ `\ | ||
@group(0) @binding(0) var backgroundTexture: texture_2d<f32>; | ||
@group(0) @binding(1) var backgroundTextureSampler: sampler; | ||
fn billboardTexture_getTextureUV(coordinates: vec2<f32>) -> vec2<f32> { | ||
let iTexSize: vec2<u32> = textureDimensions(backgroundTexture, 0) * 2; | ||
let texSize: vec2<f32> = vec2<f32>(f32(iTexSize.x), f32(iTexSize.y)); | ||
var position: vec2<f32> = coordinates.xy / texSize; | ||
return position; | ||
} | ||
@fragment | ||
fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4<f32> { | ||
let position: vec2<f32> = billboardTexture_getTextureUV(inputs.coordinate); | ||
return textureSample(backgroundTexture, backgroundTextureSampler, position); | ||
} | ||
`; | ||
const BACKGROUND_FS = /* glsl */ `\ | ||
#version 300 es | ||
precision highp float; | ||
precision highp float; | ||
uniform sampler2D backgroundTexture; | ||
@@ -18,3 +36,3 @@ out vec4 fragColor; | ||
vec2 billboardTexture_getTextureUV() { | ||
ivec2 iTexSize = textureSize(backgroundTexture, 0) * 2; | ||
ivec2 iTexSize = textureDimensions(backgroundTexture, 0) * 2; | ||
vec2 texSize = vec2(float(iTexSize.x), float(iTexSize.y)); | ||
@@ -50,2 +68,3 @@ vec2 position = gl_FragCoord.xy / texSize; | ||
id: props.id || 'background-texture-model', | ||
source: BACKGROUND_FS_WGSL, | ||
fs: BACKGROUND_FS, | ||
@@ -52,0 +71,0 @@ parameters: { |
@@ -9,24 +9,26 @@ // luma.gl | ||
import {Geometry} from '../geometry/geometry'; | ||
import {uid} from '../utils/uid'; | ||
const CLIPSPACE_VERTEX_SHADER_WGSL = /* wgsl */ `\ | ||
struct VertexInput { | ||
aClipSpacePosition: vec2<f32>; | ||
aTexCoord: vec2<f32>; | ||
aCoordinate: vec2<f32>; | ||
struct VertexInputs { | ||
@location(0) clipSpacePosition: vec2<f32>, | ||
@location(1) texCoord: vec2<f32>, | ||
@location(2) coordinate: vec2<f32> | ||
} | ||
struct FragmentInput { | ||
@builtin(position) Position : vec4<f32>; | ||
@location(0) position : vec2<f32>; | ||
@location(1) coordinate : vec2<f32>; | ||
@location(2) uv : vec2<f32>; | ||
struct FragmentInputs { | ||
@builtin(position) Position : vec4<f32>, | ||
@location(0) position : vec2<f32>, | ||
@location(1) coordinate : vec2<f32>, | ||
@location(2) uv : vec2<f32> | ||
}; | ||
@stage(vertex) | ||
fn vertexMain(input: VertexInput) -> FragmentInput { | ||
var output: FragmentInput; | ||
output.Position = vec4(aClipSpacePosition, 0., 1.); | ||
output.position = input.aClipSpacePosition; | ||
output.coordinate = input.aCoordinate; | ||
output.uv = aTexCoord; | ||
@vertex | ||
fn vertexMain(inputs: VertexInputs) -> FragmentInputs { | ||
var outputs: FragmentInputs; | ||
outputs.Position = vec4(inputs.clipSpacePosition, 0., 1.); | ||
outputs.position = inputs.clipSpacePosition; | ||
outputs.coordinate = inputs.coordinate; | ||
outputs.uv = inputs.texCoord; | ||
return outputs; | ||
} | ||
@@ -37,5 +39,5 @@ `; | ||
#version 300 es | ||
in vec2 aClipSpacePosition; | ||
in vec2 aTexCoord; | ||
in vec2 aCoordinate; | ||
in vec2 clipSpacePosition; | ||
in vec2 texCoord; | ||
in vec2 coordinate; | ||
@@ -47,6 +49,6 @@ out vec2 position; | ||
void main(void) { | ||
gl_Position = vec4(aClipSpacePosition, 0., 1.); | ||
position = aClipSpacePosition; | ||
coordinate = aCoordinate; | ||
uv = aTexCoord; | ||
gl_Position = vec4(clipSpacePosition, 0., 1.); | ||
position = clipSpacePosition; | ||
coordinate = coordinate; | ||
uv = texCoord; | ||
} | ||
@@ -70,8 +72,8 @@ `; | ||
if (props.source) { | ||
props = {...props, source: `${CLIPSPACE_VERTEX_SHADER_WGSL}\m${props.source}`}; | ||
props = {...props, source: `${CLIPSPACE_VERTEX_SHADER_WGSL}\n${props.source}`}; | ||
} | ||
super(device, { | ||
id: props.id || uid('clip-space'), | ||
...props, | ||
source: CLIPSPACE_VERTEX_SHADER_WGSL, | ||
vs: CLIPSPACE_VERTEX_SHADER, | ||
@@ -83,5 +85,5 @@ vertexCount: 4, | ||
attributes: { | ||
aClipSpacePosition: {size: 2, value: new Float32Array(POSITIONS)}, | ||
aTexCoord: {size: 2, value: new Float32Array(TEX_COORDS)}, | ||
aCoordinate: {size: 2, value: new Float32Array(TEX_COORDS)} | ||
clipSpacePosition: {size: 2, value: new Float32Array(POSITIONS)}, | ||
texCoord: {size: 2, value: new Float32Array(TEX_COORDS)}, | ||
coordinate: {size: 2, value: new Float32Array(TEX_COORDS)} | ||
} | ||
@@ -88,0 +90,0 @@ }) |
@@ -59,3 +59,14 @@ // luma.gl | ||
this.clipSpace = new ClipSpace(device, { | ||
fs: `\ | ||
source: /* wgsl */ `\ | ||
@group(0) @binding(0) var sourceTexture: texture_2d<f32>; | ||
@group(0) @binding(1) var sourceTextureSampler: sampler; | ||
@fragment | ||
fn fragmentMain(inputs: FragmentInputs) -> @location(0) vec4<f32> { | ||
let texCoord: vec2<f32> = inputs.coordinate; | ||
return textureSample(sourceTexture, sourceTextureSampler, texCoord); | ||
} | ||
`, | ||
fs: /* glsl */ `\ | ||
#version 300 es | ||
@@ -148,7 +159,6 @@ | ||
// eslint-disable-next-line no-shadow | ||
const sourceTexture = this.swapFramebuffers.current.colorAttachments[0].texture; | ||
const swapBufferTexture = this.swapFramebuffers.current.colorAttachments[0].texture; | ||
const bindings = { | ||
sourceTexture | ||
sourceTexture: swapBufferTexture | ||
// texSize: [sourceTextures.width, sourceTextures.height] | ||
@@ -155,0 +165,0 @@ }; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1401875
262
25215
+ Added@luma.gl/core@9.1.0-alpha.19(transitive)
+ Added@luma.gl/shadertools@9.1.0-alpha.19(transitive)
+ Added@math.gl/core@4.1.0(transitive)
+ Added@math.gl/types@4.1.0(transitive)
- Removed@luma.gl/core@9.1.0-alpha.17(transitive)
- Removed@luma.gl/shadertools@9.1.0-alpha.17(transitive)
- Removed@math.gl/core@4.1.0-alpha.3(transitive)
- Removed@math.gl/types@4.1.0-alpha.3(transitive)
Updated@math.gl/core@^4.1.0
Updated@math.gl/types@^4.1.0