From 472d4ec8e8528220a1ae91329f45c946ee5c42aa Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 30 Dec 2024 14:49:41 +0800 Subject: [PATCH 01/53] prepare Abstract Classes for webgpu --- packages/reshader.gl/src/AbstractTexture.ts | 25 +- packages/reshader.gl/src/EdgeGeometry.ts | 2 +- packages/reshader.gl/src/Geometry.ts | 468 ++++++++---------- packages/reshader.gl/src/InstancedMesh.ts | 4 +- packages/reshader.gl/src/Material.ts | 90 ++-- packages/reshader.gl/src/Mesh.ts | 48 +- packages/reshader.gl/src/Texture2D.ts | 47 +- packages/reshader.gl/src/TextureCube.ts | 2 +- packages/reshader.gl/src/ToonMaterial.js | 13 - packages/reshader.gl/src/common/Constants.ts | 1 + .../reshader.gl/src/shader/ImageShader.js | 2 +- packages/reshader.gl/src/shader/MeshShader.js | 6 +- 12 files changed, 324 insertions(+), 384 deletions(-) delete mode 100644 packages/reshader.gl/src/ToonMaterial.js diff --git a/packages/reshader.gl/src/AbstractTexture.ts b/packages/reshader.gl/src/AbstractTexture.ts index 2940d48f50..88032c0818 100644 --- a/packages/reshader.gl/src/AbstractTexture.ts +++ b/packages/reshader.gl/src/AbstractTexture.ts @@ -1,6 +1,6 @@ -import { isFunction, hasOwn, getTextureByteWidth, getTextureChannels, supportNPOT } from './common/Util.js'; +import { isFunction, hasOwn, getTextureByteWidth, getTextureChannels } from './common/Util.js'; import Eventable from './common/Eventable'; -import { KEY_DISPOSED } from './common/Constants.js'; +import { ERROR_NOT_IMPLEMENTED, KEY_DISPOSED } from './common/Constants.js'; import ResourceLoader from './ResourceLoader'; import REGL, { Regl, Texture2D } from '@maptalks/regl'; import { TextureConfig } from './types/typings'; @@ -129,7 +129,7 @@ class AbstractTexture extends Eventable(Base) { } } if (this.dirty) { - this._updateREGL(); + this._update(); } return this._texture; } @@ -149,12 +149,8 @@ class AbstractTexture extends Eventable(Base) { // } } - //@internal - _updateREGL() { - if (this._texture && !this._texture[KEY_DISPOSED]) { - this._texture(this.config as any); - } - this.dirty = false; + _update() { + throw new Error(ERROR_NOT_IMPLEMENTED); } dispose() { @@ -185,16 +181,7 @@ class AbstractTexture extends Eventable(Base) { } } - //@internal - _needPowerOf2(regl) { - if (supportNPOT(regl)) { - return false; - } - const config = this.config; - const isRepeat = config.wrap && config.wrap !== 'clamp' || config.wrapS && config.wrapS !== 'clamp' || - config.wrapT && config.wrapT !== 'clamp'; - return isRepeat || config.min && config.min !== 'nearest' && config.min !== 'linear'; - } + } export default AbstractTexture; diff --git a/packages/reshader.gl/src/EdgeGeometry.ts b/packages/reshader.gl/src/EdgeGeometry.ts index 08ad9a5f73..e7555ccf63 100644 --- a/packages/reshader.gl/src/EdgeGeometry.ts +++ b/packages/reshader.gl/src/EdgeGeometry.ts @@ -17,7 +17,7 @@ export default class EdgeGeometry extends Geometry { } //@internal - _getPosAttritute(): number[] { + _getPosAttribute(): number[] { const pos = this.data[this.desc.positionAttribute]; if (!pos.length) { return []; diff --git a/packages/reshader.gl/src/Geometry.ts b/packages/reshader.gl/src/Geometry.ts index 93faa97931..23053b930f 100644 --- a/packages/reshader.gl/src/Geometry.ts +++ b/packages/reshader.gl/src/Geometry.ts @@ -50,7 +50,7 @@ function GUID() { const REF_COUNT_KEY = '_reshader_refCount'; -export default class Geometry { +export class AbstractGeometry { data: Record elements: any desc: GeometryDesc @@ -87,7 +87,7 @@ export default class Geometry { this.elements = elements; this.desc = extend({}, DEFAULT_DESC, desc); - const pos = this._getPosAttritute(); + const pos = this._getPosAttribute(); this.data[this.desc.positionAttribute] = pos; if (!count) { if (this.elements) { @@ -125,7 +125,7 @@ export default class Geometry { // } //@internal - _getPosAttritute() { + _getPosAttribute() { return this.data[this.desc.positionAttribute]; } @@ -221,83 +221,7 @@ export default class Geometry { return this._reglData[key]; } - getREGLData(regl: any, activeAttributes: ActiveAttributes, disableVAO: boolean): AttributeData { - this.getAttrData(activeAttributes); - const updated = !this._reglData || !this._reglData[activeAttributes.key]; - //support vao - if (isSupportVAO(regl) && !disableVAO) { - const key = activeAttributes && activeAttributes.key || 'default'; - if (!this._vao[key] || updated || this._vao[key].dirty) { - const reglData = this._reglData[activeAttributes.key]; - const vertexCount = this._vertexCount; - const buffers = []; - for (let i = 0; i < activeAttributes.length; i++) { - const p = activeAttributes[i]; - const attr = p.name; - const buffer = reglData[attr] && reglData[attr].buffer; - if (!buffer || !buffer.destroy) { - const data = reglData[attr]; - if (!data) { - if (this.desc.fillEmptyDataInMissingAttribute) { - // 某些老版本浏览器(例如3dtiles中的electron),数据不能传空字符串,否则会报错 - // glDrawElements: attempt to access out of range vertices in attribute 1 - buffers.push(new Uint8Array(vertexCount * 4)); - } else { - buffers.push(EMPTY_VAO_BUFFER); - } - continue; - } - const dimension = (data.data && isArray(data.data) ? data.data.length : data.length) / vertexCount; - if (data.data) { - data.dimension = dimension; - buffers.push(data); - } else { - buffers.push({ - data, - dimension - }); - } - } else if (reglData[attr].stride !== undefined) { - buffers.push( - reglData[attr] - ); - } else { - buffers.push(buffer); - } - } - - const vaoData = { - attributes: buffers, - primitive: this.getPrimitive() - } as any; - if (this.elements && !isNumber(this.elements)) { - if (this.elements.destroy) { - vaoData.elements = this.elements; - } else { - vaoData.elements = { - primitive: this.getPrimitive(), - data: this.elements - }; - const type = this.getElementsType(this.elements); - if (type) { - vaoData.elements.type = type; - } - } - } - if (!this._vao[key]) { - this._vao[key] = { - vao: regl.vao(vaoData) - }; - } else { - this._vao[key].vao(vaoData); - } - } - delete this._vao[key].dirty; - return this._vao[key]; - } - return this._reglData[activeAttributes.key]; - } //@internal _isAttrChanged(activeAttributes: ActiveAttributes): boolean { @@ -315,84 +239,6 @@ export default class Geometry { return false; } - generateBuffers(regl: Regl) { - //generate regl buffers beforehand to avoid repeated bufferData - //提前处理addBuffer插入的arraybuffer - const allocatedBuffers = this._buffers; - for (const p in allocatedBuffers) { - if (!allocatedBuffers[p].buffer) { - allocatedBuffers[p].buffer = regl.buffer(allocatedBuffers[p].data); - } - delete allocatedBuffers[p].data; - } - const positionName = this.desc.positionAttribute; - const altitudeName = this.desc.altitudeAttribute; - const data = this.data; - const vertexCount = this._vertexCount; - const buffers = {}; - for (const key in data) { - if (!data[key]) { - continue; - } - //如果调用过addBuffer,buffer有可能是ArrayBuffer - if (data[key].buffer !== undefined && !(data[key].buffer instanceof ArrayBuffer)) { - if (data[key].buffer.destroy) { - buffers[key] = data[key]; - } else if (allocatedBuffers[data[key].buffer]) { - //多个属性共用同一个ArrayBuffer(interleaved) - buffers[key] = extend({}, data[key]); - buffers[key].buffer = allocatedBuffers[data[key].buffer].buffer; - } - } else { - const arr = data[key].data ? data[key].data : data[key]; - const dimension = arr.length / vertexCount; - const info = data[key].data ? data[key] : { data: data[key] }; - info.dimension = dimension; - const buffer = regl.buffer(info); - buffer[REF_COUNT_KEY] = 1; - buffers[key] = { - buffer - }; - if (key === positionName || key === altitudeName) {//vt中positionSize=2,z存在altitude中,也需要一并保存 - buffers[key].array = data[key]; - } - - } - if (this.desc.static || key !== positionName) {//保存POSITION原始数据,用来做额外计算 - delete data[key].array; - } - } - this.data = buffers; - delete this._reglData; - - // const supportVAO = isSupportVAO(regl); - // const excludeElementsInVAO = options && options.excludeElementsInVAO; - if (this.elements && !isNumber(this.elements)) { - const info = { - primitive: this.getPrimitive(), - data: this.elements - } as any; - const type = this.getElementsType(this.elements); - if (type) { - info.type = type; - } - if (!this.desc.static && !this.elements.destroy) { - const elements = this.elements; - this.indices = new Uint16Array(elements.length); - for (let i = 0; i < elements.length; i++) { - this.indices[i] = elements[i]; - } - } - this.elements = this.elements.destroy ? this.elements : regl.elements(info); - const elements = this.elements; - if (!elements[REF_COUNT_KEY]) { - elements[REF_COUNT_KEY] = 0; - } - elements[REF_COUNT_KEY]++; - - } - } - getVertexCount(): number { const { positionAttribute, positionSize, color0Attribute } = this.desc; let data = this.data[positionAttribute]; @@ -432,29 +278,29 @@ export default class Geometry { * @param {String} key - 属性 * @param {ArrayBuffer|REGLBuffer} data - 数据 */ - addBuffer(key: string, data: ArrayBuffer | REGL.Buffer): this { - this._buffers[key] = { - data - }; - delete this._reglData; - this._deleteVAO(); - return this; - } + // addBuffer(key: string, data: ArrayBuffer | REGL.Buffer): this { + // this._buffers[key] = { + // data + // }; + // delete this._reglData; + // this._deleteVAO(); + // return this; + // } - updateBuffer(key: string, data: ArrayBuffer | REGL.Buffer): this { - if (!this._buffers[key]) { - throw new Error(`invalid buffer ${key} in geometry`); - } - // this._buffers[key].data = data; - if (this._buffers[key].buffer) { - this._buffers[key].buffer.subdata(data); - } else { - this._buffers[key].data = data; - } - delete this._reglData; - this._deleteVAO(); - return this; - } + // updateBuffer(key: string, data: ArrayBuffer | REGL.Buffer): this { + // if (!this._buffers[key]) { + // throw new Error(`invalid buffer ${key} in geometry`); + // } + // // this._buffers[key].data = data; + // if (this._buffers[key].buffer) { + // this._buffers[key].buffer.subdata(data); + // } else { + // this._buffers[key].data = data; + // } + // delete this._reglData; + // this._deleteVAO(); + // return this; + // } deleteData(name: string): this { const buf = this.data[name]; @@ -616,7 +462,6 @@ export default class Geometry { } dispose() { - this._deleteVAO(); this._forEachBuffer(buffer => { if (!buffer[KEY_DISPOSED]) { let refCount = buffer[REF_COUNT_KEY]; @@ -896,14 +741,6 @@ export default class Geometry { return size; } - //@internal - _deleteVAO() { - for (const p in this._vao) { - this._vao[p].vao.destroy(); - } - this._vao = {}; - } - //@internal _forEachBuffer(fn: (buffer: any) => void) { if (this.elements && this.elements.destroy) { @@ -974,86 +811,175 @@ function getTypeCtor(arr: NumberArray, byteWidth: number) { return null; } -// function buildTangents2(vertices, normals, uvs, indices) { -// const vtxCount = vertices.length / 3; -// const tangent = new Array(vtxCount * 4); -// const tanA = new Array(vertices.length); -// const tanB = new Array(vertices.length); - -// // (1) -// const indexCount = indices.length; -// for (let i = 0; i < indexCount; i += 3) { -// const i0 = indices[i]; -// const i1 = indices[i + 1]; -// const i2 = indices[i + 2]; - -// const pos0 = vec3.set([], vertices[i0 * 3], vertices[i0 * 3 + 1], vertices[i0 * 3 + 2]); -// const pos1 = vec3.set([], vertices[i1 * 3], vertices[i1 * 3 + 1], vertices[i1 * 3 + 2]); -// const pos2 = vec3.set([], vertices[i2 * 3], vertices[i2 * 3 + 1], vertices[i2 * 3 + 2]); - -// const tex0 = vec2.set([], uvs[i0 * 2], uvs[i0 * 2 + 1]); -// const tex1 = vec2.set([], uvs[i1 * 2], uvs[i1 * 2 + 1]); -// const tex2 = vec2.set([], uvs[i2 * 2], uvs[i2 * 2 + 1]); - -// const edge1 = vec3.sub([], pos1, pos0); -// const edge2 = vec3.sub([], pos2, pos0); - -// const uv1 = vec2.sub([], tex1, tex0); -// const uv2 = vec2.sub([], tex2, tex0); - -// const r = 1.0 / (uv1[0] * uv2[1] - uv1[1] * uv2[0]); - -// const tangent = [ -// ((edge1[0] * uv2[1]) - (edge2[0] * uv1[1])) * r, -// ((edge1[1] * uv2[1]) - (edge2[1] * uv1[1])) * r, -// ((edge1[2] * uv2[1]) - (edge2[2] * uv1[1])) * r -// ]; - -// const bitangent = [ -// ((edge1[0] * uv2[0]) - (edge2[0] * uv1[0])) * r, -// ((edge1[1] * uv2[0]) - (edge2[1] * uv1[0])) * r, -// ((edge1[2] * uv2[0]) - (edge2[2] * uv1[0])) * r -// ]; - -// tanA[i0] = tanA[i0] || [0, 0, 0]; -// tanA[i1] = tanA[i1] || [0, 0, 0]; -// tanA[i2] = tanA[i2] || [0, 0, 0]; -// vec3.add(tanA[i0], tanA[i0], tangent); -// vec3.add(tanA[i1], tanA[i1], tangent); -// vec3.add(tanA[i2], tanA[i2], tangent); -// // tanA[i0] += tangent; -// // tanA[i1] += tangent; -// // tanA[i2] += tangent; - -// tanB[i0] = tanB[i0] || [0, 0, 0]; -// tanB[i1] = tanB[i1] || [0, 0, 0]; -// tanB[i2] = tanB[i2] || [0, 0, 0]; -// vec3.add(tanB[i0], tanB[i0], bitangent); -// vec3.add(tanB[i1], tanB[i1], bitangent); -// vec3.add(tanB[i2], tanB[i2], bitangent); -// // tanB[i0] += bitangent; -// // tanB[i1] += bitangent; -// // tanB[i2] += bitangent; -// } - -// // (2) -// for (let j = 0; j < vtxCount; j++) { -// const n = vec3.set([], normals[j * 3], normals[j * 3 + 1], normals[j * 3 + 2]); -// const t0 = tanA[j]; -// const t1 = tanB[j]; - -// const n1 = vec3.scale([], n, vec3.dot(n, t0)); -// const t = vec3.sub([], t0, n1); -// vec3.normalize(t, t); -// // const t = t0 - (n * dot(n, t0)); -// // t = normaljze(t); - -// const c = vec3.cross(n, n, t0); -// const w = (vec3.dot(c, t1) < 0) ? -1.0 : 1.0; -// tangent[j * 4] = t[0]; -// tangent[j * 4 + 1] = t[1]; -// tangent[j * 4 + 2] = t[2]; -// tangent[j * 4 + 3] = w; -// } -// return tangent; -// } +export default class Geometry extends AbstractGeometry { + getREGLData(regl: any, activeAttributes: ActiveAttributes, disableVAO: boolean): AttributeData { + this.getAttrData(activeAttributes); + const updated = !this._reglData || !this._reglData[activeAttributes.key]; + //support vao + if (isSupportVAO(regl) && !disableVAO) { + const key = activeAttributes && activeAttributes.key || 'default'; + if (!this._vao[key] || updated || this._vao[key].dirty) { + const reglData = this._reglData[activeAttributes.key]; + const vertexCount = this._vertexCount; + const buffers = []; + + for (let i = 0; i < activeAttributes.length; i++) { + const p = activeAttributes[i]; + const attr = p.name; + const buffer = reglData[attr] && reglData[attr].buffer; + if (!buffer || !buffer.destroy) { + const data = reglData[attr]; + if (!data) { + if (this.desc.fillEmptyDataInMissingAttribute) { + // 某些老版本浏览器(例如3dtiles中的electron),数据不能传空字符串,否则会报错 + // glDrawElements: attempt to access out of range vertices in attribute 1 + buffers.push(new Uint8Array(vertexCount * 4)); + } else { + buffers.push(EMPTY_VAO_BUFFER); + } + continue; + } + const dimension = (data.data && isArray(data.data) ? data.data.length : data.length) / vertexCount; + if (data.data) { + data.dimension = dimension; + buffers.push(data); + } else { + buffers.push({ + data, + dimension + }); + } + } else if (reglData[attr].stride !== undefined) { + buffers.push( + reglData[attr] + ); + } else { + buffers.push(buffer); + } + } + + const vaoData = { + attributes: buffers, + primitive: this.getPrimitive() + } as any; + if (this.elements && !isNumber(this.elements)) { + if (this.elements.destroy) { + vaoData.elements = this.elements; + } else { + vaoData.elements = { + primitive: this.getPrimitive(), + data: this.elements + }; + const type = this.getElementsType(this.elements); + if (type) { + vaoData.elements.type = type; + } + } + } + if (!this._vao[key]) { + this._vao[key] = { + vao: regl.vao(vaoData) + }; + } else { + this._vao[key].vao(vaoData); + } + } + delete this._vao[key].dirty; + return this._vao[key]; + } + return this._reglData[activeAttributes.key]; + } + + generateBuffers(regl: Regl) { + //generate regl buffers beforehand to avoid repeated bufferData + //提前处理addBuffer插入的arraybuffer + const allocatedBuffers = this._buffers; + for (const p in allocatedBuffers) { + if (!allocatedBuffers[p].buffer) { + allocatedBuffers[p].buffer = regl.buffer(allocatedBuffers[p].data); + } + delete allocatedBuffers[p].data; + } + const positionName = this.desc.positionAttribute; + const altitudeName = this.desc.altitudeAttribute; + const data = this.data; + const vertexCount = this._vertexCount; + const buffers = {}; + for (const key in data) { + if (!data[key]) { + continue; + } + //如果调用过addBuffer,buffer有可能是ArrayBuffer + if (data[key].buffer !== undefined && !(data[key].buffer instanceof ArrayBuffer)) { + if (data[key].buffer.destroy) { + buffers[key] = data[key]; + } else if (allocatedBuffers[data[key].buffer]) { + //多个属性共用同一个ArrayBuffer(interleaved) + buffers[key] = extend({}, data[key]); + buffers[key].buffer = allocatedBuffers[data[key].buffer].buffer; + } + } else { + const arr = data[key].data ? data[key].data : data[key]; + const dimension = arr.length / vertexCount; + const info = data[key].data ? data[key] : { data: data[key] }; + info.dimension = dimension; + const buffer = regl.buffer(info); + buffer[REF_COUNT_KEY] = 1; + buffers[key] = { + buffer + }; + if (key === positionName || key === altitudeName) {//vt中positionSize=2,z存在altitude中,也需要一并保存 + buffers[key].array = data[key]; + } + + } + if (this.desc.static || key !== positionName) {//保存POSITION原始数据,用来做额外计算 + delete data[key].array; + } + } + this.data = buffers; + delete this._reglData; + + // const supportVAO = isSupportVAO(regl); + // const excludeElementsInVAO = options && options.excludeElementsInVAO; + if (this.elements && !isNumber(this.elements)) { + const info = { + primitive: this.getPrimitive(), + data: this.elements + } as any; + const type = this.getElementsType(this.elements); + if (type) { + info.type = type; + } + if (!this.desc.static && !this.elements.destroy) { + const elements = this.elements; + this.indices = new Uint16Array(elements.length); + for (let i = 0; i < elements.length; i++) { + this.indices[i] = elements[i]; + } + } + this.elements = this.elements.destroy ? this.elements : regl.elements(info); + const elements = this.elements; + if (!elements[REF_COUNT_KEY]) { + elements[REF_COUNT_KEY] = 0; + } + elements[REF_COUNT_KEY]++; + + } + } + + + dispose() { + this._deleteVAO(); + super.dispose(); + } + + + //@internal + _deleteVAO() { + for (const p in this._vao) { + this._vao[p].vao.destroy(); + } + this._vao = {}; + } +} diff --git a/packages/reshader.gl/src/InstancedMesh.ts b/packages/reshader.gl/src/InstancedMesh.ts index 14da18bb10..7e83c9211a 100644 --- a/packages/reshader.gl/src/InstancedMesh.ts +++ b/packages/reshader.gl/src/InstancedMesh.ts @@ -107,8 +107,8 @@ export default class InstancedMesh extends Mesh { return defines; } - getCommandKey(regl: Regl) { - return 'i_' + super.getCommandKey(regl); + getCommandKey() { + return 'i_' + super.getCommandKey(); } //因为 updateBoundingBox 需要, 不再自动生成buffer,而是把原有的buffer销毁 diff --git a/packages/reshader.gl/src/Material.ts b/packages/reshader.gl/src/Material.ts index 0420152537..0fab8a1cae 100644 --- a/packages/reshader.gl/src/Material.ts +++ b/packages/reshader.gl/src/Material.ts @@ -1,14 +1,14 @@ import Eventable from './common/Eventable'; import { isNil, extendWithoutNil, hasOwn, getTexMemorySize } from './common/Util'; import AbstractTexture from './AbstractTexture'; -import { KEY_DISPOSED } from './common/Constants'; +import { ERROR_NOT_IMPLEMENTED, KEY_DISPOSED } from './common/Constants'; import { ShaderUniforms, ShaderUniformValue, ShaderDefines } from './types/typings'; import { Regl, Texture } from '@maptalks/regl'; import Geometry from './Geometry'; class Base {} -class Material extends Eventable(Base) { +export class AbstractMaterial extends Eventable(Base) { uniforms: ShaderUniforms refCount: number // 如果unlit,则不产生阴影(但接受阴影) @@ -141,7 +141,8 @@ class Material extends Eventable(Base) { * Get shader defines * @return {Object} */ - appendDefines(defines: ShaderDefines, geometry: Geometry) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + appendDefines(defines: ShaderDefines, _geometry: Geometry) { const uniforms = this.uniforms; if (!uniforms) { return defines; @@ -162,40 +163,6 @@ class Material extends Eventable(Base) { return !!(this.uniforms && this.uniforms['jointTexture'] && this.uniforms['skinAnimation']); } - getUniforms(regl: Regl) { - if (this._reglUniforms && !this.isDirty()) { - return this._reglUniforms; - } - const uniforms = this.uniforms; - const realUniforms: ShaderUniforms = {}; - for (const p in uniforms) { - if (this.isTexture(p)) { - Object.defineProperty(realUniforms, p, { - enumerable: true, - configurable: true, - get: function () { - return (uniforms[p] as AbstractTexture).getREGLTexture(regl); - } - }); - } else { - const descriptor = Object.getOwnPropertyDescriptor(uniforms, p); - if (descriptor.get) { - Object.defineProperty(realUniforms, p, { - enumerable: true, - configurable: true, - get: function () { - return uniforms[p]; - } - }); - } else { - realUniforms[p] = uniforms[p]; - } - } - } - this._reglUniforms = realUniforms; - this._uniformVer = this.version; - return realUniforms; - } isTexture(k: string) { const v = this.uniforms[k]; @@ -273,6 +240,53 @@ class Material extends Eventable(Base) { this._version++; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getUniforms(_regl?: any) { + throw new Error(ERROR_NOT_IMPLEMENTED); + } + + getMemorySize() { + throw new Error(ERROR_NOT_IMPLEMENTED); + } + +} + +export default class Material extends AbstractMaterial { + getUniforms(regl: Regl) { + if (this._reglUniforms && !this.isDirty()) { + return this._reglUniforms; + } + const uniforms = this.uniforms; + const realUniforms: ShaderUniforms = {}; + for (const p in uniforms) { + if (this.isTexture(p)) { + Object.defineProperty(realUniforms, p, { + enumerable: true, + configurable: true, + get: function () { + return (uniforms[p] as AbstractTexture).getREGLTexture(regl); + } + }); + } else { + const descriptor = Object.getOwnPropertyDescriptor(uniforms, p); + if (descriptor.get) { + Object.defineProperty(realUniforms, p, { + enumerable: true, + configurable: true, + get: function () { + return uniforms[p]; + } + }); + } else { + realUniforms[p] = uniforms[p]; + } + } + } + this._reglUniforms = realUniforms; + this._uniformVer = this.version; + return realUniforms; + } + getMemorySize() { const uniforms = this.uniforms; let size = 0; @@ -286,5 +300,3 @@ class Material extends Eventable(Base) { return size; } } - -export default Material; diff --git a/packages/reshader.gl/src/Mesh.ts b/packages/reshader.gl/src/Mesh.ts index e35673f294..a952249eb3 100644 --- a/packages/reshader.gl/src/Mesh.ts +++ b/packages/reshader.gl/src/Mesh.ts @@ -15,7 +15,7 @@ let uuid = 0; * Config: * transparent, castShadow */ -class Mesh { +export class AbstractMesh { //@internal _version: number //@internal @@ -299,7 +299,7 @@ class Mesh { } //eslint-disable-next-line - getCommandKey(regl: Regl): string { + getCommandKey(): string { if (!this._commandKey || this.dirtyDefines || (this._material && this._materialKeys !== this._material.getUniformKeys())) { //TODO geometry的data变动也可能会改变commandKey,但鉴于geometry一般不会发生变化,暂时不管 let dKey = this._getDefinesKey(); @@ -439,26 +439,6 @@ class Mesh { return this._geometry.getElements(); } - //@internal - _getREGLAttrData(regl, activeAttributes) { - return this._geometry.getREGLData(regl, activeAttributes, this.disableVAO); - } - - getREGLProps(regl: Regl, activeAttributes: ActiveAttributes) { - const props = this.getUniforms(regl); - extend(props, this._getREGLAttrData(regl, activeAttributes)); - if (!isSupportVAO(regl) || this.disableVAO) { - props.elements = this._geometry.getElements(); - } - props.meshProperties = this.properties; - props.geometryProperties = this._geometry.properties; - props.meshConfig = this.config; - props.count = this._geometry.getDrawCount(); - props.offset = this._geometry.getDrawOffset(); - // command primitive : triangle, triangle strip, etc - props.primitive = this._geometry.getPrimitive(); - return props; - } dispose() { delete this._geometry; @@ -539,7 +519,6 @@ class Mesh { } } -export default Mesh; function equalDefine(obj0, obj1) { if (!obj0 && !obj1) { @@ -557,3 +536,26 @@ function equalDefine(obj0, obj1) { } return true; } + +export default class Mesh extends AbstractMesh { + //@internal + _getREGLAttrData(regl, activeAttributes) { + return this._geometry.getREGLData(regl, activeAttributes, this.disableVAO); + } + + getREGLProps(regl: Regl, activeAttributes: ActiveAttributes) { + const props = this.getUniforms(regl); + extend(props, this._getREGLAttrData(regl, activeAttributes)); + if (!isSupportVAO(regl) || this.disableVAO) { + props.elements = this._geometry.getElements(); + } + props.meshProperties = this.properties; + props.geometryProperties = this._geometry.properties; + props.meshConfig = this.config; + props.count = this._geometry.getDrawCount(); + props.offset = this._geometry.getDrawOffset(); + // command primitive : triangle, triangle strip, etc + props.primitive = this._geometry.getPrimitive(); + return props; + } +} diff --git a/packages/reshader.gl/src/Texture2D.ts b/packages/reshader.gl/src/Texture2D.ts index bb0fd5de65..7fadb4476c 100644 --- a/packages/reshader.gl/src/Texture2D.ts +++ b/packages/reshader.gl/src/Texture2D.ts @@ -1,21 +1,16 @@ import parseRGBE from './common/HDR'; -import { isArray, isPowerOfTwo, resizeToPowerOfTwo } from './common/Util'; +import { isArray, isPowerOfTwo, resizeToPowerOfTwo, supportNPOT } from './common/Util'; import { default as Texture, REF_COUNT_KEY } from './AbstractTexture'; import { getUniqueTexture } from './common/REGLHelper'; import REGL, { Regl } from '@maptalks/regl'; import DataUtils from './common/DataUtils'; +import { ERROR_NOT_IMPLEMENTED, KEY_DISPOSED } from './common/Constants'; /** * config properties: * https://github.com/regl-project/regl/blob/gh-pages/API.md#textures */ -class Texture2D extends Texture { - - texParameteri(key: number, value: number) { - if (this._texture) { - (this._texture as any).texParameteri(key, value); - } - } +export class AbstractTexture2D extends Texture { onLoad({ data }) { const config = this.config; @@ -47,7 +42,28 @@ class Texture2D extends Texture { if (this._regl) { this._checkNPOT(this._regl); } - this._updateREGL(); + this._update(); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _checkNPOT(_regl?: any) { + throw new Error(ERROR_NOT_IMPLEMENTED); + } +} + +export default class Texture2D extends AbstractTexture2D { + //@internal + _update() { + if (this._texture && !this._texture[KEY_DISPOSED]) { + this._texture(this.config as any); + } + this.dirty = false; + } + + texParameteri(key: number, value: number) { + if (this._texture) { + (this._texture as any).texParameteri(key, value); + } } createREGLTexture(regl: Regl): REGL.Texture2D { @@ -78,6 +94,15 @@ class Texture2D extends Texture { } } } -} -export default Texture2D; + //@internal + _needPowerOf2(regl) { + if (supportNPOT(regl)) { + return false; + } + const config = this.config; + const isRepeat = config.wrap && config.wrap !== 'clamp' || config.wrapS && config.wrapS !== 'clamp' || + config.wrapT && config.wrapT !== 'clamp'; + return isRepeat || config.min && config.min !== 'nearest' && config.min !== 'linear'; + } +} diff --git a/packages/reshader.gl/src/TextureCube.ts b/packages/reshader.gl/src/TextureCube.ts index 2e7ad5f5b5..d845f96c35 100644 --- a/packages/reshader.gl/src/TextureCube.ts +++ b/packages/reshader.gl/src/TextureCube.ts @@ -8,7 +8,7 @@ class TextureCube extends Texture { } // const faces = this._createFaces(images); // config.faces = faces.map(face => face.data); - this._updateREGL(); + this._update(); } createREGLTexture(regl) { diff --git a/packages/reshader.gl/src/ToonMaterial.js b/packages/reshader.gl/src/ToonMaterial.js deleted file mode 100644 index d431cd9b21..0000000000 --- a/packages/reshader.gl/src/ToonMaterial.js +++ /dev/null @@ -1,13 +0,0 @@ -import PhongMaterial from './PhongMaterial.js'; - -const DEFAULT_UNIFORMS = { - 'toons': 4, - 'specularToons': 2, -}; - -class ToonMaterial extends PhongMaterial { - constructor(uniforms) { - super(uniforms, DEFAULT_UNIFORMS); - } -} -export default ToonMaterial; diff --git a/packages/reshader.gl/src/common/Constants.ts b/packages/reshader.gl/src/common/Constants.ts index 0d21bc3ac6..004281e814 100644 --- a/packages/reshader.gl/src/common/Constants.ts +++ b/packages/reshader.gl/src/common/Constants.ts @@ -10,3 +10,4 @@ export const WEBGL_OPTIONAL_EXTENSIONS = [ 'OES_texture_float', 'OES_texture_float_linear', 'WEBGL_depth_texture', /*'WEBGL_draw_buffers', */'EXT_shader_texture_lod', 'EXT_texture_filter_anisotropic' ]; +export const ERROR_NOT_IMPLEMENTED = 'not implemented'; diff --git a/packages/reshader.gl/src/shader/ImageShader.js b/packages/reshader.gl/src/shader/ImageShader.js index d7c670958b..7d983078e9 100644 --- a/packages/reshader.gl/src/shader/ImageShader.js +++ b/packages/reshader.gl/src/shader/ImageShader.js @@ -26,7 +26,7 @@ class ImageShader extends MeshShader { } getMeshCommand(regl, mesh) { - const key = mesh.getCommandKey(regl); + const key = mesh.getCommandKey(); if (!this.commands['image_' + key]) { const defines = mesh.getDefines(); this.commands['image_' + key] = this.createREGLCommand( diff --git a/packages/reshader.gl/src/shader/MeshShader.js b/packages/reshader.gl/src/shader/MeshShader.js index 92e7523751..3482f91f82 100644 --- a/packages/reshader.gl/src/shader/MeshShader.js +++ b/packages/reshader.gl/src/shader/MeshShader.js @@ -118,13 +118,13 @@ class MeshShader extends Shader { if (!storedKeys) { storedKeys = this._cmdKeys[key] = {}; } - const meshKey = mesh.getCommandKey(regl); + const meshKey = mesh.getCommandKey(); if (!storedKeys[meshKey]) { - storedKeys[meshKey] = key + '_' + mesh.getCommandKey(regl); + storedKeys[meshKey] = key + '_' + mesh.getCommandKey(); } const dKey = storedKeys[meshKey]; // const key = this.dkey || ''; - // const dKey = key + '_' + mesh.getCommandKey(regl); + // const dKey = key + '_' + mesh.getCommandKey(); let command = this.commands[dKey]; if (!command) { const defines = mesh.getDefines(); From 9f608d549a1930eb3e1f805e3b7c2266ed2b95bd Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 30 Dec 2024 14:50:46 +0800 Subject: [PATCH 02/53] mv Shader.ts --- packages/reshader.gl/src/shader/{Shader.js => Shader.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/reshader.gl/src/shader/{Shader.js => Shader.ts} (100%) diff --git a/packages/reshader.gl/src/shader/Shader.js b/packages/reshader.gl/src/shader/Shader.ts similarity index 100% rename from packages/reshader.gl/src/shader/Shader.js rename to packages/reshader.gl/src/shader/Shader.ts From 86343870c9e3f751b2e66f58f73de2c102c59239 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Tue, 31 Dec 2024 11:36:07 +0800 Subject: [PATCH 03/53] some method renames for consistency between gl and gpu --- packages/reshader.gl/src/InstancedMesh.ts | 4 +- packages/reshader.gl/src/Mesh.ts | 14 ++- packages/reshader.gl/src/Renderer.ts | 14 +-- .../src/shader/BloomExtractShader.js | 2 +- .../reshader.gl/src/shader/BoxBlurShader.js | 2 +- .../src/shader/BoxShadowBlurShader.js | 2 +- .../reshader.gl/src/shader/CopyDepthShader.js | 2 +- packages/reshader.gl/src/shader/CopyShader.js | 2 +- packages/reshader.gl/src/shader/FxaaShader.js | 2 +- .../reshader.gl/src/shader/ImageShader.js | 2 +- packages/reshader.gl/src/shader/MeshShader.js | 6 +- .../src/shader/PostProcessShader.js | 2 +- packages/reshader.gl/src/shader/QuadShader.js | 2 +- packages/reshader.gl/src/shader/Shader.ts | 92 +++++++++++++------ .../src/shader/SsrCombineShader.js | 2 +- .../reshader.gl/src/shader/SsrMipmapShader.js | 2 +- packages/reshader.gl/src/shader/TaaShader.js | 2 +- .../src/shadow/ShadowDisplayShader.js | 2 +- .../reshader.gl/src/ssao/SsaoBlurShader.js | 2 +- .../reshader.gl/src/ssao/SsaoExtractShader.js | 2 +- .../reshader.gl/src/weather/fog/FogShader.js | 2 +- 21 files changed, 107 insertions(+), 55 deletions(-) diff --git a/packages/reshader.gl/src/InstancedMesh.ts b/packages/reshader.gl/src/InstancedMesh.ts index 7e83c9211a..cdcb1c3e07 100644 --- a/packages/reshader.gl/src/InstancedMesh.ts +++ b/packages/reshader.gl/src/InstancedMesh.ts @@ -174,8 +174,8 @@ export default class InstancedMesh extends Mesh { return this; } - getREGLProps(regl: Regl, activeAttributes: ActiveAttributes) { - const props = super.getREGLProps(regl, activeAttributes); + getRenderProps(regl: Regl, activeAttributes: ActiveAttributes) { + const props = super.getRenderProps(regl, activeAttributes); if (!isSupportVAO(regl)) { extend(props, this.instancedData); } diff --git a/packages/reshader.gl/src/Mesh.ts b/packages/reshader.gl/src/Mesh.ts index a952249eb3..539f118a3a 100644 --- a/packages/reshader.gl/src/Mesh.ts +++ b/packages/reshader.gl/src/Mesh.ts @@ -537,13 +537,25 @@ function equalDefine(obj0, obj1) { return true; } +export class GPUMesh extends AbstractMesh { + // 实现webgpu相关的逻辑 + getRenderProps(device, activeAttributes) { + // 运行时 + // 1. 根据参数中的 bind group layout,负责生成 mesh 自身 uniform 的 bind group + // 1.1 如果uniform buffer是全新的,需要重新生成一个bind group + // 2. 负责从dynamic buffers 中请求uniform buffer + // 3. 负责从geometry中手机 vertex buffer 相关的信息 + + } +} + export default class Mesh extends AbstractMesh { //@internal _getREGLAttrData(regl, activeAttributes) { return this._geometry.getREGLData(regl, activeAttributes, this.disableVAO); } - getREGLProps(regl: Regl, activeAttributes: ActiveAttributes) { + getRenderProps(regl: Regl, activeAttributes: ActiveAttributes) { const props = this.getUniforms(regl); extend(props, this._getREGLAttrData(regl, activeAttributes)); if (!isSupportVAO(regl) || this.disableVAO) { diff --git a/packages/reshader.gl/src/Renderer.ts b/packages/reshader.gl/src/Renderer.ts index b50a422b29..9170ea40c1 100644 --- a/packages/reshader.gl/src/Renderer.ts +++ b/packages/reshader.gl/src/Renderer.ts @@ -6,10 +6,10 @@ const EMPTY_UNIFORMS = {}; * A basic renderer to render meshes in fashion of forward rendering */ class Renderer { - regl: Regl + device: any - constructor(regl: Regl) { - this.regl = regl; + constructor(device: any) { + this.device = device; } render(shader, uniforms: Uniforms, scene: Scene, framebuffer: REGL.Framebuffer) { @@ -21,16 +21,16 @@ class Renderer { let count = 0; if (scene) { const { opaques, transparents } = scene.getSortedMeshes(); - count += shader.draw(this.regl, opaques); - count += shader.draw(this.regl, transparents); + count += shader.draw(this.device, opaques); + count += shader.draw(this.device, transparents); } else { - count += shader.draw(this.regl); + count += shader.draw(this.device); } return count; } clear(options: REGL.ClearOptions) { - this.regl.clear(options); + this.device.clear(options); } } diff --git a/packages/reshader.gl/src/shader/BloomExtractShader.js b/packages/reshader.gl/src/shader/BloomExtractShader.js index c1939f69b1..ca2daeece4 100644 --- a/packages/reshader.gl/src/shader/BloomExtractShader.js +++ b/packages/reshader.gl/src/shader/BloomExtractShader.js @@ -24,7 +24,7 @@ class BloomExtractShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['bloom_extract']) { - this.commands['bloom_extract'] = this.createREGLCommand( + this.commands['bloom_extract'] = this.createMeshCommand( regl, null, mesh.getElements() diff --git a/packages/reshader.gl/src/shader/BoxBlurShader.js b/packages/reshader.gl/src/shader/BoxBlurShader.js index b77472f7cb..daff9a36e1 100644 --- a/packages/reshader.gl/src/shader/BoxBlurShader.js +++ b/packages/reshader.gl/src/shader/BoxBlurShader.js @@ -29,7 +29,7 @@ class BoxBlurShader extends QuadShader { getMeshCommand(regl, mesh) { const key = 'box_blur_' + this._blurOffset; if (!this.commands[key]) { - this.commands[key] = this.createREGLCommand( + this.commands[key] = this.createMeshCommand( regl, null, mesh.getElements() diff --git a/packages/reshader.gl/src/shader/BoxShadowBlurShader.js b/packages/reshader.gl/src/shader/BoxShadowBlurShader.js index a98fa0478b..0b9026d6cb 100644 --- a/packages/reshader.gl/src/shader/BoxShadowBlurShader.js +++ b/packages/reshader.gl/src/shader/BoxShadowBlurShader.js @@ -17,7 +17,7 @@ class BoxShadowBlurShader extends QuadShader { getMeshCommand(regl, mesh) { const key = 'box_shadow_blur_' + this._blurOffset; if (!this.commands[key]) { - this.commands[key] = this.createREGLCommand( + this.commands[key] = this.createMeshCommand( regl, null, mesh.getElements() diff --git a/packages/reshader.gl/src/shader/CopyDepthShader.js b/packages/reshader.gl/src/shader/CopyDepthShader.js index 9cf843d6a9..cf1cffabfb 100644 --- a/packages/reshader.gl/src/shader/CopyDepthShader.js +++ b/packages/reshader.gl/src/shader/CopyDepthShader.js @@ -37,7 +37,7 @@ class CopyDepthShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['copy_depth']) { - this.commands['copy_depth'] = this.createREGLCommand( + this.commands['copy_depth'] = this.createMeshCommand( regl, null, mesh.getElements() diff --git a/packages/reshader.gl/src/shader/CopyShader.js b/packages/reshader.gl/src/shader/CopyShader.js index ac7881aca1..6e6b5a7cd3 100644 --- a/packages/reshader.gl/src/shader/CopyShader.js +++ b/packages/reshader.gl/src/shader/CopyShader.js @@ -24,7 +24,7 @@ class CopyShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['copy']) { - this.commands['copy'] = this.createREGLCommand( + this.commands['copy'] = this.createMeshCommand( regl, null, mesh.getElements() diff --git a/packages/reshader.gl/src/shader/FxaaShader.js b/packages/reshader.gl/src/shader/FxaaShader.js index ce442f8567..bac892eb64 100644 --- a/packages/reshader.gl/src/shader/FxaaShader.js +++ b/packages/reshader.gl/src/shader/FxaaShader.js @@ -24,7 +24,7 @@ class FxaaShader extends QuadShader { getMeshCommand(regl, mesh) { const key = this.dkey || ''; if (!this.commands[key + '_fxaa']) { - this.commands[key + '_fxaa'] = this.createREGLCommand( + this.commands[key + '_fxaa'] = this.createMeshCommand( regl, null, mesh.getElements() diff --git a/packages/reshader.gl/src/shader/ImageShader.js b/packages/reshader.gl/src/shader/ImageShader.js index 7d983078e9..21092ffc06 100644 --- a/packages/reshader.gl/src/shader/ImageShader.js +++ b/packages/reshader.gl/src/shader/ImageShader.js @@ -29,7 +29,7 @@ class ImageShader extends MeshShader { const key = mesh.getCommandKey(); if (!this.commands['image_' + key]) { const defines = mesh.getDefines(); - this.commands['image_' + key] = this.createREGLCommand( + this.commands['image_' + key] = this.createMeshCommand( regl, defines, mesh.getElements() diff --git a/packages/reshader.gl/src/shader/MeshShader.js b/packages/reshader.gl/src/shader/MeshShader.js index 3482f91f82..a86e701acf 100644 --- a/packages/reshader.gl/src/shader/MeshShader.js +++ b/packages/reshader.gl/src/shader/MeshShader.js @@ -28,7 +28,7 @@ class MeshShader extends Shader { const command = this.getMeshCommand(regl, meshes[i]); //run command one by one, for debug - // const props = extend({}, this.context, meshes[i].getREGLProps()); + // const props = extend({}, this.context, meshes[i].getRenderProps()); // console.log(i); // command(props); @@ -38,7 +38,7 @@ class MeshShader extends Shader { props.length = 0; } - const v = meshes[i].getREGLProps(regl, command.activeAttributes); + const v = meshes[i].getRenderProps(regl, command.activeAttributes); this._ensureContextDefines(v); v.shaderContext = this.context; this.appendDescUniforms(regl, v); @@ -137,7 +137,7 @@ class MeshShader extends Shader { } } command = this.commands[dKey] = - this.createREGLCommand( + this.createMeshCommand( regl, defines, mesh.getElements(), diff --git a/packages/reshader.gl/src/shader/PostProcessShader.js b/packages/reshader.gl/src/shader/PostProcessShader.js index 3c0e78fdb2..e7a9c7f394 100644 --- a/packages/reshader.gl/src/shader/PostProcessShader.js +++ b/packages/reshader.gl/src/shader/PostProcessShader.js @@ -23,7 +23,7 @@ class PostProcessShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['postprocess']) { - this.commands['postprocess'] = this.createREGLCommand( + this.commands['postprocess'] = this.createMeshCommand( regl, null, mesh.getElements() diff --git a/packages/reshader.gl/src/shader/QuadShader.js b/packages/reshader.gl/src/shader/QuadShader.js index 4b636ab1e9..878ffe0c9d 100644 --- a/packages/reshader.gl/src/shader/QuadShader.js +++ b/packages/reshader.gl/src/shader/QuadShader.js @@ -51,7 +51,7 @@ class QuadShader extends MeshShader { getMeshCommand(regl) { const keys = this.dkey || ''; if (!this.commands[keys + '_quad']) { - this.commands[keys + '_quad'] = this.createREGLCommand( + this.commands[keys + '_quad'] = this.createMeshCommand( regl, null, this._quadMesh[0].getElements() diff --git a/packages/reshader.gl/src/shader/Shader.ts b/packages/reshader.gl/src/shader/Shader.ts index 2af5a1b190..49368d99c8 100644 --- a/packages/reshader.gl/src/shader/Shader.ts +++ b/packages/reshader.gl/src/shader/Shader.ts @@ -1,6 +1,7 @@ import { extend, isString, isFunction, isNumber, isSupportVAO, hasOwn, hashCode } from '../common/Util.js'; import ShaderLib from '../shaderlib/ShaderLib.js'; import { KEY_DISPOSED } from '../common/Constants.js'; +import { ShaderUniformValue } from '../types/typings'; const UNIFORM_TYPE = { function : 'function', @@ -11,7 +12,26 @@ let uid = 0; const activeVarsCache = {}; -class Shader { +export class AbstractShader { + vert: string; + frag: string; + uid: number; + //@internal + uniforms: ShaderUniformValue[]; + //@internal + contextDesc: Record; + extraCommandProps: any; + //@internal + commands: any; + //@internal + _shaderDefines: Record; + //@internal + dkey: string; + //@internal + context: any; + //@internal + contextKeys: string; + constructor({ vert, frag, uniforms, defines, extraCommandProps }) { this.vert = vert; this.frag = frag; @@ -146,6 +166,38 @@ class Shader { return this; } + + _compileSource() { + this.vert = ShaderLib.compile(this.vert); + this.frag = ShaderLib.compile(this.frag); + } +} + +function parseArrayName(p) { + const l = p.indexOf('['), r = p.indexOf(']'); + const name = p.substring(0, l), len = +p.substring(l + 1, r); + return { name, len }; +} + +export class GPUShader extends AbstractShader { + createMeshCommand(device, materialDefines, elements, isInstanced, disableVAO, commandProps = {}) { + //TODO + // 生成期: + // 1. 负责对 wgsl 做预处理,生成最终执行的wgsl代码 + // 2. 解析wgsl,生成 bind group layout + // 3. 解析wgsl,获得全局uniform变量名和类型 + // 4. 生成全局 uniform 变量的 bind group + // 执行期: + // 5. 执行时,从 dynamic buffers 中请求uniform buffer + // 6. 负责汇总管理 passEncoder.setBindgroup 方法中的 bind group dynamic offsets + + + } +} + +export default class Shader extends AbstractShader { + version: number; + getVersion(regl, source) { const versionDefined = source.substring(0, 8) === '#version'; if (versionDefined) { @@ -219,7 +271,18 @@ class Shader { return activeVarsCache[cacheKey]; } - createREGLCommand(regl, materialDefines, elements, isInstanced, disableVAO, commandProps = {}) { + + _insertDefines(source, defines) { + const defineHeaders = []; + for (const p in defines) { + if (hasOwn(defines, p) && !isFunction(defines[p])) { + defineHeaders.push(`#define ${p} ${defines[p]}\n`); + } + } + return defineHeaders.join('') + source; + } + + createMeshCommand(regl, materialDefines, elements, isInstanced, disableVAO, commandProps = {}) { const isVAO = isSupportVAO(regl) && !disableVAO; const defines = extend({}, this.shaderDefines || {}, materialDefines || {}); const vertSource = this._insertDefines(this.vert, defines); @@ -265,7 +328,7 @@ class Shader { } } - const command = { + const command: any = { vert, frag, uniforms, attributes }; if (isVAO) { @@ -303,27 +366,4 @@ class Shader { delete this.vert; delete this.frag; } - - _insertDefines(source, defines) { - const defineHeaders = []; - for (const p in defines) { - if (hasOwn(defines, p) && !isFunction(defines[p])) { - defineHeaders.push(`#define ${p} ${defines[p]}\n`); - } - } - return defineHeaders.join('') + source; - } - - _compileSource() { - this.vert = ShaderLib.compile(this.vert); - this.frag = ShaderLib.compile(this.frag); - } } - -function parseArrayName(p) { - const l = p.indexOf('['), r = p.indexOf(']'); - const name = p.substring(0, l), len = +p.substring(l + 1, r); - return { name, len }; -} - -export default Shader; diff --git a/packages/reshader.gl/src/shader/SsrCombineShader.js b/packages/reshader.gl/src/shader/SsrCombineShader.js index b7cf6e736a..6aff6b3d1d 100644 --- a/packages/reshader.gl/src/shader/SsrCombineShader.js +++ b/packages/reshader.gl/src/shader/SsrCombineShader.js @@ -24,7 +24,7 @@ class SsrCombineShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['ssr_combine']) { - this.commands['ssr_combine'] = this.createREGLCommand( + this.commands['ssr_combine'] = this.createMeshCommand( regl, null, mesh.getElements() diff --git a/packages/reshader.gl/src/shader/SsrMipmapShader.js b/packages/reshader.gl/src/shader/SsrMipmapShader.js index 1918546a2a..e149ed735a 100644 --- a/packages/reshader.gl/src/shader/SsrMipmapShader.js +++ b/packages/reshader.gl/src/shader/SsrMipmapShader.js @@ -23,7 +23,7 @@ class SsrMipmapShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['ssr_mimap']) { - this.commands['ssr_mimap'] = this.createREGLCommand( + this.commands['ssr_mimap'] = this.createMeshCommand( regl, null, mesh.getElements() diff --git a/packages/reshader.gl/src/shader/TaaShader.js b/packages/reshader.gl/src/shader/TaaShader.js index 940c7507ab..fc5b1531ff 100644 --- a/packages/reshader.gl/src/shader/TaaShader.js +++ b/packages/reshader.gl/src/shader/TaaShader.js @@ -26,7 +26,7 @@ class TaaShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['taa']) { - this.commands['taa'] = this.createREGLCommand( + this.commands['taa'] = this.createMeshCommand( regl, null, mesh.getElements() diff --git a/packages/reshader.gl/src/shadow/ShadowDisplayShader.js b/packages/reshader.gl/src/shadow/ShadowDisplayShader.js index 5a50d38c19..f2de6051b7 100644 --- a/packages/reshader.gl/src/shadow/ShadowDisplayShader.js +++ b/packages/reshader.gl/src/shadow/ShadowDisplayShader.js @@ -44,7 +44,7 @@ class ShadowDisplayShader extends MeshShader { getMeshCommand(regl, mesh) { if (!this.commands['shadow_display']) { - this.commands['shadow_display'] = this.createREGLCommand( + this.commands['shadow_display'] = this.createMeshCommand( regl, null, mesh.getElements() diff --git a/packages/reshader.gl/src/ssao/SsaoBlurShader.js b/packages/reshader.gl/src/ssao/SsaoBlurShader.js index 43ae398ee4..622d8daf0e 100644 --- a/packages/reshader.gl/src/ssao/SsaoBlurShader.js +++ b/packages/reshader.gl/src/ssao/SsaoBlurShader.js @@ -23,7 +23,7 @@ class SsaoBlurShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['ssao_blur']) { - this.commands['ssao_blur'] = this.createREGLCommand( + this.commands['ssao_blur'] = this.createMeshCommand( regl, null, mesh.getElements() diff --git a/packages/reshader.gl/src/ssao/SsaoExtractShader.js b/packages/reshader.gl/src/ssao/SsaoExtractShader.js index 61b4a5c8b8..ad52642f55 100644 --- a/packages/reshader.gl/src/ssao/SsaoExtractShader.js +++ b/packages/reshader.gl/src/ssao/SsaoExtractShader.js @@ -54,7 +54,7 @@ class SsaoExtactShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['ssao_extract']) { - this.commands['ssao_extract'] = this.createREGLCommand( + this.commands['ssao_extract'] = this.createMeshCommand( regl, null, mesh.getElements() diff --git a/packages/reshader.gl/src/weather/fog/FogShader.js b/packages/reshader.gl/src/weather/fog/FogShader.js index 2d7b558568..da1311266d 100644 --- a/packages/reshader.gl/src/weather/fog/FogShader.js +++ b/packages/reshader.gl/src/weather/fog/FogShader.js @@ -24,7 +24,7 @@ class FogShader extends QuadShader { // getMeshCommand(regl, mesh) { // const key = this.dkey || ''; // if (!this.commands[key + '_fxaa']) { - // this.commands[key + '_fxaa'] = this.createREGLCommand( + // this.commands[key + '_fxaa'] = this.createMeshCommand( // regl, // null, // mesh.getElements() From 531585fa6abbf76b62ada5c2c27ac8b5f9f827cc Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Fri, 3 Jan 2025 17:38:03 +0800 Subject: [PATCH 04/53] simplify some regl command config and change createMeshCommand parameter to mesh --- packages/gl/src/layer/HeatmapProcess.js | 6 ------ packages/gl/src/layer/terrain/TerrainLayerRenderer.js | 3 --- packages/gl/src/layer/terrain/TerrainPainter.js | 3 --- .../src/layer/renderer/TileMeshPainter.js | 7 +------ packages/reshader.gl/src/shader/BloomExtractShader.js | 6 +----- packages/reshader.gl/src/shader/BoxBlurShader.js | 6 +----- .../reshader.gl/src/shader/BoxShadowBlurShader.js | 6 +----- packages/reshader.gl/src/shader/CopyDepthShader.js | 6 +----- packages/reshader.gl/src/shader/CopyShader.js | 6 +----- packages/reshader.gl/src/shader/FxaaShader.js | 6 +----- packages/reshader.gl/src/shader/ImageShader.js | 7 +------ packages/reshader.gl/src/shader/PostProcessShader.js | 6 +----- packages/reshader.gl/src/shader/QuadShader.js | 6 +----- packages/reshader.gl/src/shader/SsrCombineShader.js | 6 +----- packages/reshader.gl/src/shader/SsrMipmapShader.js | 6 +----- packages/reshader.gl/src/shader/TaaShader.js | 6 +----- .../reshader.gl/src/shadow/ShadowDisplayShader.js | 6 +----- packages/reshader.gl/src/ssao/SsaoBlurShader.js | 6 +----- packages/reshader.gl/src/ssao/SsaoExtractShader.js | 6 +----- packages/vt/src/layer/plugins/painters/LinePainter.js | 1 - .../src/layer/plugins/painters/NativelinePainter.js | 4 +--- .../vt/src/layer/plugins/painters/WaterPainter.js | 8 ++------ .../vt/src/layer/plugins/painters/pbr/PBRPainter.js | 3 --- .../src/layer/plugins/painters/pbr/StandardPainter.js | 3 --- .../plugins/painters/util/create_text_painter.js | 11 ++--------- .../src/layer/renderer/stencil/TileStencilRenderer.js | 4 +--- packages/vt/src/layer/renderer/utils/DebugPainter.js | 3 --- 27 files changed, 22 insertions(+), 125 deletions(-) diff --git a/packages/gl/src/layer/HeatmapProcess.js b/packages/gl/src/layer/HeatmapProcess.js index a65fbb7768..e18d8f069f 100644 --- a/packages/gl/src/layer/HeatmapProcess.js +++ b/packages/gl/src/layer/HeatmapProcess.js @@ -185,9 +185,6 @@ export default class HeatmapProcess { } },{ extraCommandProps: { - stencil: { - enable: false - }, depth: { enable: true, range: depthRange || [0, 1], @@ -196,9 +193,6 @@ export default class HeatmapProcess { polygonOffset: { enable: true, offset: this._polygonOffset - }, - scissor: { - enable: false } } }); diff --git a/packages/gl/src/layer/terrain/TerrainLayerRenderer.js b/packages/gl/src/layer/terrain/TerrainLayerRenderer.js index 5ec1f258c3..bbd7b23298 100644 --- a/packages/gl/src/layer/terrain/TerrainLayerRenderer.js +++ b/packages/gl/src/layer/terrain/TerrainLayerRenderer.js @@ -1395,9 +1395,6 @@ class TerrainLayerRenderer extends MaskRendererMixin(maptalks.renderer.TileLayer depth: { enable: false }, - stencil: { - enable: false - }, blend: { enable: true, func: { src: 'one', dst: 'one minus src alpha' }, diff --git a/packages/gl/src/layer/terrain/TerrainPainter.js b/packages/gl/src/layer/terrain/TerrainPainter.js index af4a7bc6cf..7a6695c1f3 100644 --- a/packages/gl/src/layer/terrain/TerrainPainter.js +++ b/packages/gl/src/layer/terrain/TerrainPainter.js @@ -230,9 +230,6 @@ class TerrainPainter { return canvas ? canvas.height : 1; } }, - stencil: { - enable: false - }, cull: { enable: true, face: 'back' diff --git a/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js b/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js index c13376d493..eda46122e4 100644 --- a/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js +++ b/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js @@ -1723,12 +1723,7 @@ export default class TileMeshPainter { }, // mask: 0xff }, - opFront: { - fail: 'keep', - zfail: 'keep', - zpass: 'replace' - }, - opBack: { + op: { fail: 'keep', zfail: 'keep', zpass: 'replace' diff --git a/packages/reshader.gl/src/shader/BloomExtractShader.js b/packages/reshader.gl/src/shader/BloomExtractShader.js index ca2daeece4..79b2d6e94e 100644 --- a/packages/reshader.gl/src/shader/BloomExtractShader.js +++ b/packages/reshader.gl/src/shader/BloomExtractShader.js @@ -24,11 +24,7 @@ class BloomExtractShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['bloom_extract']) { - this.commands['bloom_extract'] = this.createMeshCommand( - regl, - null, - mesh.getElements() - ); + this.commands['bloom_extract'] = this.createMeshCommand(regl, mesh); } return this.commands['bloom_extract']; } diff --git a/packages/reshader.gl/src/shader/BoxBlurShader.js b/packages/reshader.gl/src/shader/BoxBlurShader.js index daff9a36e1..70fe226ef0 100644 --- a/packages/reshader.gl/src/shader/BoxBlurShader.js +++ b/packages/reshader.gl/src/shader/BoxBlurShader.js @@ -29,11 +29,7 @@ class BoxBlurShader extends QuadShader { getMeshCommand(regl, mesh) { const key = 'box_blur_' + this._blurOffset; if (!this.commands[key]) { - this.commands[key] = this.createMeshCommand( - regl, - null, - mesh.getElements() - ); + this.commands[key] = this.createMeshCommand(regl, mesh); } return this.commands[key]; } diff --git a/packages/reshader.gl/src/shader/BoxShadowBlurShader.js b/packages/reshader.gl/src/shader/BoxShadowBlurShader.js index 0b9026d6cb..563410e052 100644 --- a/packages/reshader.gl/src/shader/BoxShadowBlurShader.js +++ b/packages/reshader.gl/src/shader/BoxShadowBlurShader.js @@ -17,11 +17,7 @@ class BoxShadowBlurShader extends QuadShader { getMeshCommand(regl, mesh) { const key = 'box_shadow_blur_' + this._blurOffset; if (!this.commands[key]) { - this.commands[key] = this.createMeshCommand( - regl, - null, - mesh.getElements() - ); + this.commands[key] = this.createMeshCommand(regl, mesh); } return this.commands[key]; } diff --git a/packages/reshader.gl/src/shader/CopyDepthShader.js b/packages/reshader.gl/src/shader/CopyDepthShader.js index cf1cffabfb..3c31862f48 100644 --- a/packages/reshader.gl/src/shader/CopyDepthShader.js +++ b/packages/reshader.gl/src/shader/CopyDepthShader.js @@ -37,11 +37,7 @@ class CopyDepthShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['copy_depth']) { - this.commands['copy_depth'] = this.createMeshCommand( - regl, - null, - mesh.getElements() - ); + this.commands['copy_depth'] = this.createMeshCommand(regl, mesh); } return this.commands['copy_depth']; } diff --git a/packages/reshader.gl/src/shader/CopyShader.js b/packages/reshader.gl/src/shader/CopyShader.js index 6e6b5a7cd3..3a025a7087 100644 --- a/packages/reshader.gl/src/shader/CopyShader.js +++ b/packages/reshader.gl/src/shader/CopyShader.js @@ -24,11 +24,7 @@ class CopyShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['copy']) { - this.commands['copy'] = this.createMeshCommand( - regl, - null, - mesh.getElements() - ); + this.commands['copy'] = this.createMeshCommand(regl, mesh); } return this.commands['copy']; } diff --git a/packages/reshader.gl/src/shader/FxaaShader.js b/packages/reshader.gl/src/shader/FxaaShader.js index bac892eb64..ac2106d181 100644 --- a/packages/reshader.gl/src/shader/FxaaShader.js +++ b/packages/reshader.gl/src/shader/FxaaShader.js @@ -24,11 +24,7 @@ class FxaaShader extends QuadShader { getMeshCommand(regl, mesh) { const key = this.dkey || ''; if (!this.commands[key + '_fxaa']) { - this.commands[key + '_fxaa'] = this.createMeshCommand( - regl, - null, - mesh.getElements() - ); + this.commands[key + '_fxaa'] = this.createMeshCommand(regl, mesh); } return this.commands[key + '_fxaa']; } diff --git a/packages/reshader.gl/src/shader/ImageShader.js b/packages/reshader.gl/src/shader/ImageShader.js index 21092ffc06..14f269b9d0 100644 --- a/packages/reshader.gl/src/shader/ImageShader.js +++ b/packages/reshader.gl/src/shader/ImageShader.js @@ -28,12 +28,7 @@ class ImageShader extends MeshShader { getMeshCommand(regl, mesh) { const key = mesh.getCommandKey(); if (!this.commands['image_' + key]) { - const defines = mesh.getDefines(); - this.commands['image_' + key] = this.createMeshCommand( - regl, - defines, - mesh.getElements() - ); + this.commands['image_' + key] = this.createMeshCommand(regl, mesh); } return this.commands['image_' + key]; } diff --git a/packages/reshader.gl/src/shader/PostProcessShader.js b/packages/reshader.gl/src/shader/PostProcessShader.js index e7a9c7f394..3be434ba45 100644 --- a/packages/reshader.gl/src/shader/PostProcessShader.js +++ b/packages/reshader.gl/src/shader/PostProcessShader.js @@ -23,11 +23,7 @@ class PostProcessShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['postprocess']) { - this.commands['postprocess'] = this.createMeshCommand( - regl, - null, - mesh.getElements() - ); + this.commands['postprocess'] = this.createMeshCommand(regl, mesh); } return this.commands['postprocess']; } diff --git a/packages/reshader.gl/src/shader/QuadShader.js b/packages/reshader.gl/src/shader/QuadShader.js index 878ffe0c9d..61201502e9 100644 --- a/packages/reshader.gl/src/shader/QuadShader.js +++ b/packages/reshader.gl/src/shader/QuadShader.js @@ -51,11 +51,7 @@ class QuadShader extends MeshShader { getMeshCommand(regl) { const keys = this.dkey || ''; if (!this.commands[keys + '_quad']) { - this.commands[keys + '_quad'] = this.createMeshCommand( - regl, - null, - this._quadMesh[0].getElements() - ); + this.commands[keys + '_quad'] = this.createMeshCommand(regl, this._quadMesh[0]); } return this.commands[keys + '_quad']; } diff --git a/packages/reshader.gl/src/shader/SsrCombineShader.js b/packages/reshader.gl/src/shader/SsrCombineShader.js index 6aff6b3d1d..1f2a2eece9 100644 --- a/packages/reshader.gl/src/shader/SsrCombineShader.js +++ b/packages/reshader.gl/src/shader/SsrCombineShader.js @@ -24,11 +24,7 @@ class SsrCombineShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['ssr_combine']) { - this.commands['ssr_combine'] = this.createMeshCommand( - regl, - null, - mesh.getElements() - ); + this.commands['ssr_combine'] = this.createMeshCommand(regl, mesh); } return this.commands['ssr_combine']; } diff --git a/packages/reshader.gl/src/shader/SsrMipmapShader.js b/packages/reshader.gl/src/shader/SsrMipmapShader.js index e149ed735a..adeb212b9a 100644 --- a/packages/reshader.gl/src/shader/SsrMipmapShader.js +++ b/packages/reshader.gl/src/shader/SsrMipmapShader.js @@ -23,11 +23,7 @@ class SsrMipmapShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['ssr_mimap']) { - this.commands['ssr_mimap'] = this.createMeshCommand( - regl, - null, - mesh.getElements() - ); + this.commands['ssr_mimap'] = this.createMeshCommand(regl, mesh); } return this.commands['ssr_mimap']; } diff --git a/packages/reshader.gl/src/shader/TaaShader.js b/packages/reshader.gl/src/shader/TaaShader.js index fc5b1531ff..7f44b3753b 100644 --- a/packages/reshader.gl/src/shader/TaaShader.js +++ b/packages/reshader.gl/src/shader/TaaShader.js @@ -26,11 +26,7 @@ class TaaShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['taa']) { - this.commands['taa'] = this.createMeshCommand( - regl, - null, - mesh.getElements() - ); + this.commands['taa'] = this.createMeshCommand(regl, mesh); } return this.commands['taa']; } diff --git a/packages/reshader.gl/src/shadow/ShadowDisplayShader.js b/packages/reshader.gl/src/shadow/ShadowDisplayShader.js index f2de6051b7..5ac8091b48 100644 --- a/packages/reshader.gl/src/shadow/ShadowDisplayShader.js +++ b/packages/reshader.gl/src/shadow/ShadowDisplayShader.js @@ -44,11 +44,7 @@ class ShadowDisplayShader extends MeshShader { getMeshCommand(regl, mesh) { if (!this.commands['shadow_display']) { - this.commands['shadow_display'] = this.createMeshCommand( - regl, - null, - mesh.getElements() - ); + this.commands['shadow_display'] = this.createMeshCommand(regl, mesh); } return this.commands['shadow_display']; } diff --git a/packages/reshader.gl/src/ssao/SsaoBlurShader.js b/packages/reshader.gl/src/ssao/SsaoBlurShader.js index 622d8daf0e..6f3db3b434 100644 --- a/packages/reshader.gl/src/ssao/SsaoBlurShader.js +++ b/packages/reshader.gl/src/ssao/SsaoBlurShader.js @@ -23,11 +23,7 @@ class SsaoBlurShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['ssao_blur']) { - this.commands['ssao_blur'] = this.createMeshCommand( - regl, - null, - mesh.getElements() - ); + this.commands['ssao_blur'] = this.createMeshCommand(regl, mesh); } return this.commands['ssao_blur']; } diff --git a/packages/reshader.gl/src/ssao/SsaoExtractShader.js b/packages/reshader.gl/src/ssao/SsaoExtractShader.js index ad52642f55..368af5c55a 100644 --- a/packages/reshader.gl/src/ssao/SsaoExtractShader.js +++ b/packages/reshader.gl/src/ssao/SsaoExtractShader.js @@ -54,11 +54,7 @@ class SsaoExtactShader extends QuadShader { getMeshCommand(regl, mesh) { if (!this.commands['ssao_extract']) { - this.commands['ssao_extract'] = this.createMeshCommand( - regl, - null, - mesh.getElements() - ); + this.commands['ssao_extract'] = this.createMeshCommand(regl, mesh); } return this.commands['ssao_extract']; } diff --git a/packages/vt/src/layer/plugins/painters/LinePainter.js b/packages/vt/src/layer/plugins/painters/LinePainter.js index 452c6f7bff..692d87dd5f 100644 --- a/packages/vt/src/layer/plugins/painters/LinePainter.js +++ b/packages/vt/src/layer/plugins/painters/LinePainter.js @@ -529,7 +529,6 @@ class LinePainter extends BasicPainter { enable: () => { return this.isEnableTileStencil(context); }, - mask: 0xff, func: { cmp: () => { return '<='; diff --git a/packages/vt/src/layer/plugins/painters/NativelinePainter.js b/packages/vt/src/layer/plugins/painters/NativelinePainter.js index ef70410c0a..3a4f747c87 100644 --- a/packages/vt/src/layer/plugins/painters/NativelinePainter.js +++ b/packages/vt/src/layer/plugins/painters/NativelinePainter.js @@ -103,15 +103,13 @@ class NativeLinePainter extends BasicPainter { enable: () => { return this.isEnableTileStencil(context); }, - mask: 0xFF, func: { cmp: () => { return this.isOnly2D() ? '=' : '<='; }, ref: (context, props) => { return props.stencilRef; - }, - mask: 0xFF + } }, op: { fail: 'keep', diff --git a/packages/vt/src/layer/plugins/painters/WaterPainter.js b/packages/vt/src/layer/plugins/painters/WaterPainter.js index 1caffb8dbc..f68ce0f04a 100644 --- a/packages/vt/src/layer/plugins/painters/WaterPainter.js +++ b/packages/vt/src/layer/plugins/painters/WaterPainter.js @@ -330,11 +330,9 @@ class WaterPainter extends BasicPainter { colorMask: [false, false, false, false], stencil: { enable: true, - mask: 0xFF, func: { cmp: '<=', - ref: 0xFE, - mask: 0xFF + ref: 0xFE }, op: { fail: 'keep', @@ -357,11 +355,9 @@ class WaterPainter extends BasicPainter { viewport, stencil: { enable: true, - mask: 0xFF, func: { cmp: '==', - ref: 0xFE, - mask: 0xFF + ref: 0xFE }, op: { fail: 'keep', diff --git a/packages/vt/src/layer/plugins/painters/pbr/PBRPainter.js b/packages/vt/src/layer/plugins/painters/pbr/PBRPainter.js index d99ad39200..3870d9d829 100644 --- a/packages/vt/src/layer/plugins/painters/pbr/PBRPainter.js +++ b/packages/vt/src/layer/plugins/painters/pbr/PBRPainter.js @@ -198,9 +198,6 @@ class PBRPainter extends Painter { enable: true, face: 'back' }, - stencil: { - enable: false - }, viewport // polygonOffset: { // enable: true, diff --git a/packages/vt/src/layer/plugins/painters/pbr/StandardPainter.js b/packages/vt/src/layer/plugins/painters/pbr/StandardPainter.js index bd1cdf801b..60d265749e 100644 --- a/packages/vt/src/layer/plugins/painters/pbr/StandardPainter.js +++ b/packages/vt/src/layer/plugins/painters/pbr/StandardPainter.js @@ -223,9 +223,6 @@ class StandardPainter extends MeshPainter { return this.sceneConfig.cullFace || 'back'; } }, - stencil: { - enable: false - }, viewport, depth: { enable: true, diff --git a/packages/vt/src/layer/plugins/painters/util/create_text_painter.js b/packages/vt/src/layer/plugins/painters/util/create_text_painter.js index 53ba53f78b..16dd1d0206 100644 --- a/packages/vt/src/layer/plugins/painters/util/create_text_painter.js +++ b/packages/vt/src/layer/plugins/painters/util/create_text_painter.js @@ -337,25 +337,18 @@ export function createTextShader(canvas, sceneConfig) { viewport, stencil: { //fix #94, intel显卡的崩溃和blending关系比较大,开启stencil来避免blending enable: true, - mask: 0xFF, func: { //halo的stencil ref更大,允许文字填充在halo上绘制 cmp: '<=', //renderer.isEnableWorkAround('win-intel-gpu-crash') ? '<' : '<=', ref: (context, props) => { return props.stencilRef; - }, - mask: 0xFF + } }, op: { fail: 'keep', zfail: 'keep', zpass: 'replace' - }, - // opBack: { - // fail: 'keep', - // zfail: 'keep', - // zpass: 'replace' - // } + } }, blend: { enable: true, diff --git a/packages/vt/src/layer/renderer/stencil/TileStencilRenderer.js b/packages/vt/src/layer/renderer/stencil/TileStencilRenderer.js index 47218a1235..f8d8bf7b11 100644 --- a/packages/vt/src/layer/renderer/stencil/TileStencilRenderer.js +++ b/packages/vt/src/layer/renderer/stencil/TileStencilRenderer.js @@ -104,13 +104,11 @@ export default class TileStencilRenderer { viewport, stencil: { enable: true, - mask: 0xFF, func: { cmp: 'always', ref: (context, props) => { return props.ref; - }, - mask: 0xFF + } }, op: { fail: 'replace', diff --git a/packages/vt/src/layer/renderer/utils/DebugPainter.js b/packages/vt/src/layer/renderer/utils/DebugPainter.js index eeb88c852c..430d3928fe 100644 --- a/packages/vt/src/layer/renderer/utils/DebugPainter.js +++ b/packages/vt/src/layer/renderer/utils/DebugPainter.js @@ -160,9 +160,6 @@ class DebugPainter { }, equation: 'add' }, - stencil: { - enable: false - }, viewport: { x: 0, y: 0, From 329041beb0c4c67f73fee396124d00b3443ca779 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 13 Jan 2025 17:36:05 +0800 Subject: [PATCH 05/53] implement all needed by GPUShader.run --- .../src/layer/renderer/TileMeshPainter.js | 9 +- packages/reshader.gl/package.json | 5 +- packages/reshader.gl/src/AbstractTexture.ts | 2 +- packages/reshader.gl/src/Geometry.ts | 104 ++++++++ packages/reshader.gl/src/Material.ts | 16 +- packages/reshader.gl/src/Mesh.ts | 69 +++-- packages/reshader.gl/src/common/Util.ts | 2 +- packages/reshader.gl/src/shader/MeshShader.js | 51 ++-- packages/reshader.gl/src/shader/Shader.ts | 106 +++++++- .../reshader.gl/src/webgpu/BindGroupFormat.ts | 91 +++++++ .../reshader.gl/src/webgpu/CommandBuilder.ts | 237 ++++++++++++++++++ .../reshader.gl/src/webgpu/DynamicBuffer.ts | 30 +++ .../reshader.gl/src/webgpu/DynamicBuffers.ts | 143 +++++++++++ .../src/webgpu/common/PipelineDesc.ts | 148 +++++++++++ .../src/webgpu/common/ReglTranslator.ts | 72 ++++++ .../reshader.gl/src/webgpu/common/math.ts | 13 + packages/reshader.gl/tsconfig.json | 1 + pnpm-lock.yaml | 27 ++ 18 files changed, 1053 insertions(+), 73 deletions(-) create mode 100644 packages/reshader.gl/src/webgpu/BindGroupFormat.ts create mode 100644 packages/reshader.gl/src/webgpu/CommandBuilder.ts create mode 100644 packages/reshader.gl/src/webgpu/DynamicBuffer.ts create mode 100644 packages/reshader.gl/src/webgpu/DynamicBuffers.ts create mode 100644 packages/reshader.gl/src/webgpu/common/PipelineDesc.ts create mode 100644 packages/reshader.gl/src/webgpu/common/ReglTranslator.ts create mode 100644 packages/reshader.gl/src/webgpu/common/math.ts diff --git a/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js b/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js index eda46122e4..d270dcdd2c 100644 --- a/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js +++ b/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js @@ -1450,14 +1450,11 @@ export default class TileMeshPainter { const buffer = getUniqueREGLBuffer(this._regl, attributes[p], { dimension: attributes[p].itemSize }); // 优先采用 attributeSemantics中定义的属性 const name = attributeSemantics[p] || p; - attrs[name] = { buffer }; - if (attributes[p].quantization) { - attrs[name].quantization = attributes[p].quantization; - } + attrs[name] = extend({}, attributes[p]); + attrs[name].buffer = buffer; + delete attrs[name].array; if (name === attributeSemantics['POSITION']) { attrs[name].array = attributes[p].array; - attrs[name].min = attributes[p].min; - attrs[name].max = attributes[p].max; } } // createColorArray(attrs); diff --git a/packages/reshader.gl/package.json b/packages/reshader.gl/package.json index 8bf7050be7..5f8afc2091 100644 --- a/packages/reshader.gl/package.json +++ b/packages/reshader.gl/package.json @@ -25,8 +25,11 @@ "dependencies": { "@maptalks/regl": "^3.4.0", "@maptalks/tbn-packer": "^1.4.5", + "@webgpu/types": "0.1.52", "earcut": "^3.0.1", - "gl-matrix": "^3.4.0" + "gl-matrix": "^3.4.0", + "wgsl-preprocessor": "1.0.0", + "wgsl_reflect": "^1.0.16" }, "devDependencies": { "@maptalks/rollup-plugin-glsl-minify": "^0.1.7", diff --git a/packages/reshader.gl/src/AbstractTexture.ts b/packages/reshader.gl/src/AbstractTexture.ts index 88032c0818..df92e58eb9 100644 --- a/packages/reshader.gl/src/AbstractTexture.ts +++ b/packages/reshader.gl/src/AbstractTexture.ts @@ -104,7 +104,7 @@ class AbstractTexture extends Eventable(Base) { return null; } - getREGLTexture(regl: Regl) { + getREGLTexture(regl: any): any { if (!this._texture) { this._texture = this.createREGLTexture(regl); if (!this.config.persistent) { diff --git a/packages/reshader.gl/src/Geometry.ts b/packages/reshader.gl/src/Geometry.ts index 23053b930f..30a4004012 100644 --- a/packages/reshader.gl/src/Geometry.ts +++ b/packages/reshader.gl/src/Geometry.ts @@ -779,6 +779,11 @@ export class AbstractGeometry { _incrVersion() { this._version++; } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getBufferDescriptor(_vertexInfo) { + return []; + } } function getElementLength(elements) { @@ -811,7 +816,92 @@ function getTypeCtor(arr: NumberArray, byteWidth: number) { return null; } +export class GPUGeometry extends AbstractGeometry { + getCommandKey() { + // 因为attribute组织方式会影响pipeline的创建,所以attributes组织方式不同时,需要创建不同的command key + // 我们这里暂时不考虑Geometry会更新attributes组织方式的情况,因为在maptalks的使用场景不会出现 + // 可能有些attribute没有被wgsl使用,但组织方式不同,这里也不考虑这种情况 + const keys = []; + for (const p in this.data) { + const attr = this.data[p]; + if (attr.bytesStride) { + keys.push(`${p}(${attr.byteOffset}/${attr.bytesStride}})`); + } else { + keys.push(p); + } + } + return keys.sort().join('-'); + } + + getBufferDescriptor(vertexInfo) { + const data = this.data; + const bufferDesc = []; + const bufferMapping = {}; + for (const p in data) { + const attr = data[p]; + if (!attr) { + continue; + } + const info = vertexInfo[p]; + if (isArray(attr)) { + const itemBytes = getItemBytes(attr); + bufferDesc.push({ + arrayStride: info.itemSize * itemBytes, + attributes: [ + { + shaderLocation: info.location, + format: info.format, + offset: 0 + } + ] + }); + } else { + // a descriptor in gltf accessor style + const accessorName = attr.accessorName; + const byteStride = attr.bytesStride; + if (byteStride && accessorName) { + let desc = bufferMapping[accessorName]; + if (desc) { + desc.attributes.push({ + shaderLocation: info.location, + format: info.format, + offset: attr.byteOffset + }); + } else { + desc = { + arrayStride: byteStride, + attributes: [ + { + shaderLocation: info.location, + format: info.format, + offset: attr.byteOffset + } + ] + } + bufferMapping[accessorName] = desc; + } + } else { + bufferDesc.push({ + arrayStride: info.bytes, + attributes: [ + { + shaderLocation: info.location, + format: info.format, + offset: 0 + } + ] + }); + } + } + } + return bufferDesc; + } +} + export default class Geometry extends AbstractGeometry { + getCommandKey() { + return ''; + } getREGLData(regl: any, activeAttributes: ActiveAttributes, disableVAO: boolean): AttributeData { this.getAttrData(activeAttributes); const updated = !this._reglData || !this._reglData[activeAttributes.key]; @@ -983,3 +1073,17 @@ export default class Geometry extends AbstractGeometry { this._vao = {}; } } + +function getItemBytes(array) { + if (array.BYTES_PER_ELEMENT) { + return array.BYTES_PER_ELEMENT; + } else if (Array.isArray(array)) { + // float + return 4; + } else { + const item = array; + const gltf = getGLTFLoaderBundle(); + const ctor = gltf.GLTFLoader.getTypedArrayCtor(item.componentType); + return ctor.BYTES_PER_ELEMENT; + } +} diff --git a/packages/reshader.gl/src/Material.ts b/packages/reshader.gl/src/Material.ts index 0fab8a1cae..df9de34969 100644 --- a/packages/reshader.gl/src/Material.ts +++ b/packages/reshader.gl/src/Material.ts @@ -79,6 +79,18 @@ export class AbstractMaterial extends Eventable(Base) { return this._loadingCount <= 0; } + hasUniform(k: string) { + return Object.prototype.hasOwnProperty.call(this.uniforms, k); + } + + setUniform(k: string, v: ShaderUniformValue) { + return this.set(k, v); + } + + getUniform(k: string): ShaderUniformValue { + return this.get(k); + } + set(k: string, v: ShaderUniformValue): this { if (this.get(k) === v) { return this; @@ -252,7 +264,7 @@ export class AbstractMaterial extends Eventable(Base) { } export default class Material extends AbstractMaterial { - getUniforms(regl: Regl) { + getUniforms(device: any) { if (this._reglUniforms && !this.isDirty()) { return this._reglUniforms; } @@ -264,7 +276,7 @@ export default class Material extends AbstractMaterial { enumerable: true, configurable: true, get: function () { - return (uniforms[p] as AbstractTexture).getREGLTexture(regl); + return (uniforms[p] as AbstractTexture).getREGLTexture(device); } }); } else { diff --git a/packages/reshader.gl/src/Mesh.ts b/packages/reshader.gl/src/Mesh.ts index 539f118a3a..c3f6c9630f 100644 --- a/packages/reshader.gl/src/Mesh.ts +++ b/packages/reshader.gl/src/Mesh.ts @@ -5,6 +5,7 @@ import Geometry from './Geometry'; import Material from './Material'; import { ActiveAttributes, MatrixFunction, MeshOptions, ShaderDefines, ShaderUniformValue, ShaderUniforms } from './types/typings'; import { Regl } from '@maptalks/regl'; +import DynamicBuffer from './webgpu/DynamicBuffer'; const tempMat4: mat4 = new Array(16) as mat4; @@ -244,6 +245,10 @@ export class AbstractMesh { } } + hasUniform(k: string): boolean { + return this.uniforms[k] !== undefined; + } + getUniform(k: string): ShaderUniformValue { return this.uniforms[k]; } @@ -302,7 +307,7 @@ export class AbstractMesh { getCommandKey(): string { if (!this._commandKey || this.dirtyDefines || (this._material && this._materialKeys !== this._material.getUniformKeys())) { //TODO geometry的data变动也可能会改变commandKey,但鉴于geometry一般不会发生变化,暂时不管 - let dKey = this._getDefinesKey(); + let dKey = this.geometry.getCommandKey() + '_' + this._getDefinesKey(); const elementType = isNumber(this.getElements()) ? 'count' : 'elements'; dKey += '_' + elementType; dKey += '_' + +(!!this.disableVAO); @@ -330,7 +335,28 @@ export class AbstractMesh { // return uniforms; // } - getUniforms(regl: Regl): ShaderUniforms { + getRenderProps(device: any, activeAttributes: ActiveAttributes) { + const props = this.getUniforms(device); + extend(props, this._getGeometryAttributes(device, activeAttributes)); + if (!isSupportVAO(device) || this.disableVAO) { + props.elements = this._geometry.getElements(); + } + props.meshProperties = this.properties; + props.geometryProperties = this._geometry.properties; + props.meshConfig = this.config; + props.count = this._geometry.getDrawCount(); + props.offset = this._geometry.getDrawOffset(); + // command primitive : triangle, triangle strip, etc + props.primitive = this._geometry.getPrimitive(); + return props; + } + + //@internal + _getGeometryAttributes(device, activeAttributes) { + return this._geometry.getREGLData(device, activeAttributes, this.disableVAO); + } + + getUniforms(device: any): ShaderUniforms { if (this._dirtyUniforms || this._dirtyGeometry || this._material && this._materialVer !== this._material.version) { this._uniformDescriptors = new Set(); this._realUniforms = { @@ -355,7 +381,7 @@ export class AbstractMesh { } } if (this._material) { - const materialUniforms = this._material.getUniforms(regl); + const materialUniforms = this._material.getUniforms(device); for (const p in materialUniforms) { if (hasOwn(materialUniforms, p) && !hasOwn(this._realUniforms, p)) { const descriptor = Object.getOwnPropertyDescriptor(materialUniforms, p); @@ -387,7 +413,7 @@ export class AbstractMesh { } } if (this._material && this._material.propVersion !== this._materialPropVer) { - const materialUniforms = this._material.getUniforms(regl); + const materialUniforms = this._material.getUniforms(device); for (const p in materialUniforms) { if (hasOwn(materialUniforms, p) && !this._uniformDescriptors.has(p)) { const descriptor = Object.getOwnPropertyDescriptor(materialUniforms, p); @@ -537,37 +563,28 @@ function equalDefine(obj0, obj1) { return true; } + export class GPUMesh extends AbstractMesh { + _meshBuffer: DynamicBuffer; + _shaderBuffer: DynamicBuffer; // 实现webgpu相关的逻辑 - getRenderProps(device, activeAttributes) { + getDynamicBuffer(device, renderProps, bindGroupMapping) { + if (!this._meshBuffer) { + this._meshBuffer = new DynamicBuffer(device, renderProps, bindGroupMapping); + } else { + this._meshBuffer.fill(renderProps, bindGroupMapping); + } // 运行时 - // 1. 根据参数中的 bind group layout,负责生成 mesh 自身 uniform 的 bind group + // 1. 根据参数中的 bind group mapping,负责生成 mesh 自身 uniform 的 bind group // 1.1 如果uniform buffer是全新的,需要重新生成一个bind group // 2. 负责从dynamic buffers 中请求uniform buffer // 3. 负责从geometry中手机 vertex buffer 相关的信息 - + return this._meshBuffer; } } export default class Mesh extends AbstractMesh { - //@internal - _getREGLAttrData(regl, activeAttributes) { - return this._geometry.getREGLData(regl, activeAttributes, this.disableVAO); - } - getRenderProps(regl: Regl, activeAttributes: ActiveAttributes) { - const props = this.getUniforms(regl); - extend(props, this._getREGLAttrData(regl, activeAttributes)); - if (!isSupportVAO(regl) || this.disableVAO) { - props.elements = this._geometry.getElements(); - } - props.meshProperties = this.properties; - props.geometryProperties = this._geometry.properties; - props.meshConfig = this.config; - props.count = this._geometry.getDrawCount(); - props.offset = this._geometry.getDrawOffset(); - // command primitive : triangle, triangle strip, etc - props.primitive = this._geometry.getPrimitive(); - return props; - } + + } diff --git a/packages/reshader.gl/src/common/Util.ts b/packages/reshader.gl/src/common/Util.ts index c1f05b2d95..b736f1eada 100644 --- a/packages/reshader.gl/src/common/Util.ts +++ b/packages/reshader.gl/src/common/Util.ts @@ -228,7 +228,7 @@ export function clamp(n: number, min: number, max: number) { */ export function isSupportVAO(regl: Regl) { // return false; - return regl && regl.hasExtension('oes_vertex_array_object'); + return regl && regl.hasExtension && regl.hasExtension('oes_vertex_array_object'); } /** diff --git a/packages/reshader.gl/src/shader/MeshShader.js b/packages/reshader.gl/src/shader/MeshShader.js index a86e701acf..985e58595a 100644 --- a/packages/reshader.gl/src/shader/MeshShader.js +++ b/packages/reshader.gl/src/shader/MeshShader.js @@ -1,5 +1,5 @@ -import Shader from './Shader.js'; -import InstancedMesh from '../InstancedMesh.js'; +import Shader, { GPUShader } from './Shader'; +import InstancedMesh from '../InstancedMesh'; class MeshShader extends Shader { @@ -25,7 +25,14 @@ class MeshShader extends Shader { } continue; } - const command = this.getMeshCommand(regl, meshes[i]); + + const v = meshes[i].getRenderProps(regl, command.activeAttributes); + this._ensureContextDefines(v); + v.shaderContext = this.context; + v.meshObject = meshes[i]; + this.appendDescUniforms(regl, v); + + const command = this.getMeshCommand(regl, meshes[i], v); //run command one by one, for debug // const props = extend({}, this.context, meshes[i].getRenderProps()); @@ -34,21 +41,17 @@ class MeshShader extends Shader { if (props.length && preCommand !== command) { //batch mode - preCommand(props); + this.run(preCommand, props); props.length = 0; } - const v = meshes[i].getRenderProps(regl, command.activeAttributes); - this._ensureContextDefines(v); - v.shaderContext = this.context; - this.appendDescUniforms(regl, v); props.push(v); count++; if (i < l - 1) { preCommand = command; } else if (i === l - 1) { - command(props); + this.run(command, props); } } return count; @@ -109,11 +112,16 @@ class MeshShader extends Shader { return filters(m); } - getMeshCommand(regl, mesh) { + getMeshCommand(regl, mesh, uniformValues) { if (!this._cmdKeys) { this._cmdKeys = {}; } - const key = this.dkey || 'default'; + const material = mesh.getMaterial(); + let doubleSided = false; + if (material) { + doubleSided = material.doubleSided; + } + const key = this.getShaderCommandKey(mesh, uniformValues, doubleSided); let storedKeys = this._cmdKeys[key]; if (!storedKeys) { storedKeys = this._cmdKeys[key] = {}; @@ -127,24 +135,13 @@ class MeshShader extends Shader { // const dKey = key + '_' + mesh.getCommandKey(); let command = this.commands[dKey]; if (!command) { - const defines = mesh.getDefines(); - const material = mesh.getMaterial(); + + const commandProps = {}; - if (material) { - const doubleSided = material.doubleSided; - if (doubleSided && this.extraCommandProps) { - commandProps.cull = { enable: false }; - } + if (doubleSided && this.extraCommandProps) { + commandProps.cull = { enable: false }; } - command = this.commands[dKey] = - this.createMeshCommand( - regl, - defines, - mesh.getElements(), - mesh instanceof InstancedMesh, - mesh.disableVAO, - commandProps - ); + command = this.commands[dKey] = this.createMeshCommand(regl, mesh, commandProps); } return command; } diff --git a/packages/reshader.gl/src/shader/Shader.ts b/packages/reshader.gl/src/shader/Shader.ts index 49368d99c8..d435055bea 100644 --- a/packages/reshader.gl/src/shader/Shader.ts +++ b/packages/reshader.gl/src/shader/Shader.ts @@ -1,7 +1,14 @@ import { extend, isString, isFunction, isNumber, isSupportVAO, hasOwn, hashCode } from '../common/Util.js'; + import ShaderLib from '../shaderlib/ShaderLib.js'; import { KEY_DISPOSED } from '../common/Constants.js'; import { ShaderUniformValue } from '../types/typings'; +import PipelineDescriptor from '../webgpu/common/PipelineDesc'; +import InstancedMesh from '../InstancedMesh'; +import Mesh from '../Mesh'; +import DynamicBuffer from '../webgpu/DynamicBuffer'; +import CommandBuilder from '../webgpu/CommandBuilder'; + const UNIFORM_TYPE = { function : 'function', @@ -179,25 +186,100 @@ function parseArrayName(p) { return { name, len }; } +const pipelineDesc = new PipelineDescriptor(); + export class GPUShader extends AbstractShader { - createMeshCommand(device, materialDefines, elements, isInstanced, disableVAO, commandProps = {}) { - //TODO + gpuCommands: any[]; + //@internal + _presentationFormat: GPUTextureFormat; + _bindGroupCache: Record; + _buffers: any; + + getShaderCommandKey(mesh, uniformValues, doubleSided) { + // 获取pipeline所需要的特征变量,即任何变量发生变化后,就需要创建新的pipeline + const commandProps = this.extraCommandProps; + pipelineDesc.readFromREGLCommand(commandProps, mesh, uniformValues, doubleSided); + return pipelineDesc.getSignatureKey(); + } + + createMeshCommand(device: GPUDevice, mesh: Mesh) { // 生成期: // 1. 负责对 wgsl 做预处理,生成最终执行的wgsl代码 - // 2. 解析wgsl,生成 bind group layout - // 3. 解析wgsl,获得全局uniform变量名和类型 - // 4. 生成全局 uniform 变量的 bind group - // 执行期: - // 5. 执行时,从 dynamic buffers 中请求uniform buffer - // 6. 负责汇总管理 passEncoder.setBindgroup 方法中的 bind group dynamic offsets + // 2. 解析wgsl,生成 bind group mapping 信息,用于运行时,mesh生成bind group + // 3. 解析wgsl,获得全局 uniform 变量名和类型 + // 4. 生成 layout 和 pipeline + + // preprocess vert and frag codes + const builder = new CommandBuilder(device, this.vert, this.frag, mesh) + return builder.build(pipelineDesc); + } + + run(device, command, props, context) { + const passEncoder: GPURenderPassEncoder = context.passEncoder; + passEncoder.setPipeline(command.pipeline); + + const { key, bindGroupFormat, pipeline, vertexInfo } = command; + const layout = pipeline.getBindGroupLayout(0); + // 1. 生成shader uniform 需要的dynamic buffer + let shaderBuffer = this._buffers[key]; + if (shaderBuffer) { + shaderBuffer = this._buffers[key] = new DynamicBuffer(device, bindGroupFormat.getShaderUniforms()); + } + // 向buffer中填入shader uniform值 + shaderBuffer.writeBuffer(this.uniforms); + for (let i = 0; i < props.length; i++) { + const mesh = props[i].meshObject; + // 获取mesh的dynamicBuffer + const meshBuffer = mesh.getDynamicBuffer(device, bindGroupFormat.getShaderUniforms()); + const groupKey = meshBuffer.uid + '-' + shaderBuffer.uid; + // 获取或者生成bind group + let bindGroup = this._bindGroupCache[groupKey]; + if (!bindGroup) { + bindGroup = bindGroupFormat.createBindGroup(device, mesh, layout, shaderBuffer, meshBuffer); + // 缓存bind group,只要buffer没有发生变化,即可以重用 + // TODO 可以考虑每帧开始把缓存 bind group 标记为 retire,每帧结束时把不是 current 的 bind group 销毁掉 + this._bindGroupCache[groupKey] = bindGroup; + } + // 向buffer中填入mesh uniform值 + meshBuffer.writeBuffer(props[i]); + // 获取 dynamicOffsets + const dynamicOffsets = shaderBuffer.dynamicOffsets.concat(meshBuffer.dynamicOffsets); + passEncoder.setBindGroup(0, bindGroup, dynamicOffsets); + + for (const vertex of vertexInfo) { + const vertexBuffer = mesh.geometry.getVertexBuffer(vertex.name); + passEncoder.setVertexBuffer(vertex.index, vertexBuffer); + } + + //TODO InstancedMesh 的参数 + const elements = mesh.getElements(); + const drawOffset = mesh.geometry.getDrawOffset(); + const drawCount = mesh.geometry.getDrawCount(); + if (isNumber(elements)) { + passEncoder.draw(drawCount, 1, drawOffset); + } else { + passEncoder.setIndexBuffer(elements.getBuffer(), elements.getFormat()); + passEncoder.drawIndexed(drawCount, 1, drawOffset); + } + } + passEncoder.end(); } } export default class Shader extends AbstractShader { version: number; + run(command, props) { + return command(props); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getShaderCommandKey() { + return this.dkey || 'default'; + } + getVersion(regl, source) { const versionDefined = source.substring(0, 8) === '#version'; if (versionDefined) { @@ -282,7 +364,12 @@ export default class Shader extends AbstractShader { return defineHeaders.join('') + source; } - createMeshCommand(regl, materialDefines, elements, isInstanced, disableVAO, commandProps = {}) { + createMeshCommand(regl, mesh, commandProps = {}) { + const materialDefines = mesh.getDefines(); + const elements = mesh.getElements(); + const isInstanced = mesh instanceof InstancedMesh; + const disableVAO = mesh.disableVAO; + const isVAO = isSupportVAO(regl) && !disableVAO; const defines = extend({}, this.shaderDefines || {}, materialDefines || {}); const vertSource = this._insertDefines(this.vert, defines); @@ -367,3 +454,4 @@ export default class Shader extends AbstractShader { delete this.frag; } } + diff --git a/packages/reshader.gl/src/webgpu/BindGroupFormat.ts b/packages/reshader.gl/src/webgpu/BindGroupFormat.ts new file mode 100644 index 0000000000..e5f4749701 --- /dev/null +++ b/packages/reshader.gl/src/webgpu/BindGroupFormat.ts @@ -0,0 +1,91 @@ +import { ResourceType } from "wgsl_reflect"; +import { toGPUSampler } from "./common/ReglTranslator"; +import DynamicBuffer from "./DynamicBuffer"; +import Mesh from "../Mesh"; +import Texture2D from "../Texture2D"; + +export default class BindGroupFormat { + bytes: number; + //@internal + alignment: number; + //@internal + groups: any; + //@internal + _shaderUniforms: any; + //@internal + _meshUniforms: any; + + constructor(bindGroupMapping, minUniformBufferOffsetAlignment) { + this.groups = bindGroupMapping.groups; + this.alignment = minUniformBufferOffsetAlignment; + this._parse(bindGroupMapping); + } + + getShaderUniforms() { + return this._shaderUniforms; + } + + getMeshUniforms() { + return this._meshUniforms; + } + + _parse(bindGroupMapping) { + this._shaderUniforms = []; + this._shaderUniforms.index = 0; + this._meshUniforms = []; + this._meshUniforms.index = 0; + const groups = bindGroupMapping.groups; + const group = groups[0]; + for (let i = 0; i < group.length; i++) { + const bindedUniform = group[i]; + if (bindedUniform.isGlobal) { + let index = this._shaderUniforms.index; + this._shaderUniforms[index++] = bindedUniform; + this._shaderUniforms.index = index; + } else { + let index = this._shaderUniforms.index; + this._meshUniforms[index++] = bindedUniform; + this._meshUniforms.index = index; + } + } + } + + createBindGroup(device: GPUDevice, mesh: Mesh, layout: GPUBindGroupLayout, shaderBuffer: DynamicBuffer, meshBuffer: DynamicBuffer) { + const groups = this.groups; + const entries = []; + for (let i = 0; i < groups.length; i++) { + const group = groups[i]; + const name = group.name; + if (group.resourceType === ResourceType.Sampler) { + const texture = (mesh.getUniform(name) || mesh.material && mesh.material.getUniform(name)) as Texture2D; + const { min, mag, wrapS, wrapT } = (texture as Texture2D).config; + const filters = toGPUSampler(min, mag, wrapS, wrapT); + const sampler = device.createSampler(filters); + entries.push({ + binding: group.binding, + resource: sampler + }); + } else if (group.resourceType === ResourceType.Texture) { + const texture = (mesh.getUniform(name) || mesh.material && mesh.material.getUniform(name)) as Texture2D; + entries.push({ + binding: group.binding, + resource: (texture.getREGLTexture(device) as GPUTexture).createView() + }); + } else { + entries.push({ + binding: group.binding, + resource: { + buffer: group.isGlobal ? shaderBuffer.gpuBuffer : meshBuffer.gpuBuffer, + offset: 0, + size: Math.max(group.size, this.alignment) + } + }); + } + } + return device.createBindGroup({ + layout, + label: '', + entries + }); + } +} diff --git a/packages/reshader.gl/src/webgpu/CommandBuilder.ts b/packages/reshader.gl/src/webgpu/CommandBuilder.ts new file mode 100644 index 0000000000..01356a1e3b --- /dev/null +++ b/packages/reshader.gl/src/webgpu/CommandBuilder.ts @@ -0,0 +1,237 @@ +import { wgsl } from 'wgsl-preprocessor/wgsl-preprocessor.js'; +import { WgslReflect } from "wgsl_reflect/wgsl_reflect.module.js"; +import BindGroupFormat from '../webgpu/BindGroupFormat'; +import Mesh from '../Mesh'; +import { ResourceType } from 'wgsl_reflect'; +import { extend } from '../common/Util'; + +export default class CommandBuilder { + device: GPUDevice; + vert: string; + frag: string; + mesh: Mesh; + _presentationFormat: any; + constructor(device, vert, frag, mesh) { + this.device = device; + this.vert = vert; + this.frag = frag; + this.mesh = mesh; + } + + build(pipelineDesc) { + const mesh = this.mesh; + const device = this.device; + const defines = this.mesh.getDefines(); + const defined = key => { + return !!defines[key]; + } + const vert = wgsl(this.vert, defined); + const frag = wgsl(this.frag, defined); + + const vertReflect = new WgslReflect(vert); + const vertexInfo = this._formatBufferInfo(vertReflect, mesh); + const fragReflect = new WgslReflect(frag); + + // 生成 bind group layout + const layout = this._createBindGroupLayout(vertReflect, fragReflect, mesh); + const pipeline = this._createPipeline(device, vert, vertexInfo, frag, layout, mesh, pipelineDesc); + + const bindGroupMapping = this._createBindGroupMapping(vertReflect, fragReflect, mesh); + const bindGroupFormat = new BindGroupFormat(bindGroupMapping, device.limits.minUniformBufferOffsetAlignment); + + return { + pipeline, + vertexInfo, + bindGroupMapping, + bindGroupFormat + }; + } + + _createBindGroupLayout(vertReflect: any, fragReflect: any, mesh: Mesh) { + const vertGroups = vertReflect.getBindGroups(); + const fragGroups = fragReflect.getBindGroups(); + const layoutEntries = []; + for (let i = 0; i < vertGroups.length; i++) { + const groupInfo = vertGroups[i]; + const entry = this._createLayoutEntry(i, GPUShaderStage.VERTEX, groupInfo, mesh); + layoutEntries.push(entry); + } + for (let i = 0; i < fragGroups.length; i++) { + const groupInfo = fragGroups[i]; + const entry = this._createLayoutEntry(i, GPUShaderStage.FRAGMENT, groupInfo, mesh); + layoutEntries.push(entry); + } + return layoutEntries; + } + + _createLayoutEntry(binding, visibility, groupInfo, mesh): GPUBindGroupLayoutEntry { + if (groupInfo.resourceType === ResourceType.Sampler) { + return { + binding, + visibility + // sampler 采用默认值 + }; + } else if (groupInfo.resourceType === ResourceType.Texture) { + const name = groupInfo.name; + const texture = mesh.material && mesh.material.get(name); + const type = texture && texture.config.type; + let sampleType: GPUTextureSampleType = 'float';//sint, uint + if (type === 'depth stencil' || type === 'depth') { + sampleType = 'depth'; + } + return { + binding, + visibility, + texture: { + sampleType + } + }; + } else { + return { + binding, + visibility, + buffer: { + type: 'uniform', + hasDynamicOffset: true + } + }; + } + } + + // 从vertex的entry function读出vertex的信息(如location,format等) + _formatBufferInfo(vertReflect: any, mesh: Mesh) { + const vertEntryFuncion = vertReflect.entry.vertex[0]; + const inputs = vertEntryFuncion.inputs; + const inputMapping = {}; + for (let i = 0; i < inputs.length; i++) { + const name = inputs[i].name; + inputMapping[name] = inputs[i]; + } + const vertexInfo = {}; + const data = mesh.geometry.data; + for (const name in data) { + if (inputMapping[name]) { + vertexInfo[name] = { + location: inputMapping[name].location, + format: toBufferFormat(inputMapping[name].type), + bytes: inputMapping[name].type.size + }; + } + } + return vertexInfo; + } + + _createBindGroupMapping(vertReflect: any, fragReflect: any, mesh: Mesh) { + const mapping = {}; + const vertGroups = vertReflect.getBindGroups(); + const fragGroups = fragReflect.getBindGroups(); + // 解析vertInfo和fragInfo,生成一个 bindGroupMapping,用于mesh在运行时,生成bindGroup + // mapping 中包含 uniform 变量名对应的 group index 和 binding index + this._parseGroupMapping(mapping, vertGroups, mesh); + this._parseGroupMapping(mapping, fragGroups, mesh); + return mapping; + } + + _parseGroupMapping(mapping, groupReflect, mesh) { + for (let i = 0; i < groupReflect.length; i++) { + const groupInfo = groupReflect[i]; + const { group, binding } = groupInfo; + const members = groupInfo.members; + let isGlobal = false; + if (members && members.length) { + for (let ii = 0; ii < members.length; ii++) { + const name = members[ii].name; + if (!meshHasUniform(mesh, name)) { + isGlobal = true; + break; + } + } + } else { + const name = groupInfo.name; + if (!meshHasUniform(mesh, name)) { + isGlobal = true; + } + } + mapping.groups = mapping.groups || []; + mapping.groups[group] = mapping.groups[group] || []; + mapping.groups[group][binding] = extend({ + // we assume all the members in the same struct is all global or mesh owned + isGlobal + }, groupInfo); + } + } + // 运行时调用,生成 uniform buffer, 用来存放全局 uniform 变量的值 + _createPipeline(device, vert, vertInfo, frag, layout, mesh, pipelineDesc): GPURenderPipeline { + const vertModule = device.createShaderModule({ + code: vert, + }); + const fragModule = device.createShaderModule({ + code: frag, + }); + if (!this._presentationFormat) { + this._presentationFormat = navigator.gpu.getPreferredCanvasFormat(); + } + const buffers = mesh.geometry.getBufferDescriptor(vertInfo); + const pipelineOptions: GPURenderPipelineDescriptor = { + layout, + vertex: { + module: vertModule, + buffers + }, + fragment: { + module: fragModule, + targets: [ + { + format: this._presentationFormat, + } + ], + }, + primitive: { + topology: pipelineDesc.topology, + cullMode: pipelineDesc.cullMode + }, + depthStencil: { + depthBias: pipelineDesc.depthBias, + depthBiasSlopeScale: pipelineDesc.depthBiasSlopeScale, + depthWriteEnabled: pipelineDesc.depthWriteEnabled, + depthCompare: pipelineDesc.depthCompare, + format: 'depth24plus-stencil8' + } + }; + if (pipelineDesc.stencilFrontCompare) { + pipelineOptions.depthStencil.stencilBack = + pipelineOptions.depthStencil.stencilFront = { + compare: pipelineDesc.stencilFrontCompare, + passOp: pipelineDesc.stencilFrontPassOp + }; + } + if (pipelineDesc.blendAlphaDst) { + const fragTargets = pipelineOptions.fragment.targets; + for (const target of fragTargets) { + target.blend = { + color: { + srcFactor: pipelineDesc.blendColorSrc, + dstFactor: pipelineDesc.blendColorDst, + operation: 'add' + }, + alpha: { + srcFactor: pipelineDesc.blendAlphaSrc, + dstFactor: pipelineDesc.blendAlphaDst, + operation: 'add' + } + }; + } + } + return device.createRenderPipeline(pipelineOptions); + } +} + + +function toBufferFormat(type: any) { + //wgsl中解析的type,转换为buffer的format + throw new Error('Function not implemented.'); +} + +function meshHasUniform(mesh: Mesh, name: string) { + return mesh.hasUniform(name) || (mesh.material && mesh.material.hasUniform(name)); +} diff --git a/packages/reshader.gl/src/webgpu/DynamicBuffer.ts b/packages/reshader.gl/src/webgpu/DynamicBuffer.ts new file mode 100644 index 0000000000..b4879dcc0e --- /dev/null +++ b/packages/reshader.gl/src/webgpu/DynamicBuffer.ts @@ -0,0 +1,30 @@ +import { ShaderUniformValue } from "../types/typings"; + +let uid = 1; + +export default class DynamicBuffer { + device: GPUDevice; + bindgroupMapping: any; + + dynamicOffset: number; + gpuBuffer: GPUBuffer; + stagingBuffer: GPUBuffer; + dirty: boolean; + size: number; + bindGroup: GPUBindGroup; + uid: number; + + constructor(device: GPUDevice, bindgroupMapping) { + this.device = device; + this.bindgroupMapping = bindgroupMapping; + this.dirty = true; + this.uid = uid++; + } + + writeBuffer(uniformValues: Record) { + //TODO 从对象池中申请stagingBuffer + //按照mapping中的顺序将uniform值写入stagingBuffer,写入完成后,记录dynamicOffsets + } + + +} diff --git a/packages/reshader.gl/src/webgpu/DynamicBuffers.ts b/packages/reshader.gl/src/webgpu/DynamicBuffers.ts new file mode 100644 index 0000000000..d4b5f89972 --- /dev/null +++ b/packages/reshader.gl/src/webgpu/DynamicBuffers.ts @@ -0,0 +1,143 @@ +import * as math from './common/math'; + +export default class DynamicBuffers { + + /** + * Allocation size of the underlying buffers. + * + * @type {number} + */ + bufferSize; + + /** + * Internally allocated gpu buffers. + * + * @type {DynamicBuffer[]} + */ + gpuBuffers = []; + + /** + * Internally allocated staging buffers (CPU writable) + * + * @type {DynamicBuffer[]} + */ + stagingBuffers = []; + + /** + * @type {UsedBuffer[]} + */ + usedBuffers = []; + + /** + * @type {UsedBuffer|null} + */ + activeBuffer = null; + device: GPUDevice; + bufferAlignment: any; + + /** + * Create the system of dynamic buffers. + * + * @param {GraphicsDevice} device - The graphics device. + * @param {number} bufferSize - The size of the underlying large buffers. + * @param {number} bufferAlignment - Alignment of each allocation. + */ + constructor(device, bufferSize, bufferAlignment) { + this.device = device; + this.bufferSize = bufferSize; + this.bufferAlignment = bufferAlignment; + } + + /** + * Destroy the system of dynamic buffers. + */ + destroy() { + + this.gpuBuffers.forEach((gpuBuffer) => { + gpuBuffer.destroy(this.device); + }); + this.gpuBuffers = null; + + this.stagingBuffers.forEach((stagingBuffer) => { + stagingBuffer.destroy(this.device); + }); + this.stagingBuffers = null; + + this.usedBuffers = null; + this.activeBuffer = null; + } + + /** + * Allocate an aligned space of the given size from a dynamic buffer. + * + * @param {DynamicBufferAllocation} allocation - The allocation info to fill. + * @param {number} size - The size of the allocation. + */ + alloc(allocation, size) { + + // if we have active buffer without enough space + if (this.activeBuffer) { + const alignedStart = math.roundUp(this.activeBuffer.size, this.bufferAlignment); + const space = this.bufferSize - alignedStart; + if (space < size) { + + // we're done with this buffer, schedule it for submit + this.scheduleSubmit(); + } + } + + // if we don't have an active buffer, allocate new one + if (!this.activeBuffer) { + + // gpu buffer + let gpuBuffer = this.gpuBuffers.pop(); + if (!gpuBuffer) { + gpuBuffer = this.createBuffer(this.device, this.bufferSize, false); + } + + // staging buffer + let stagingBuffer = this.stagingBuffers.pop(); + if (!stagingBuffer) { + stagingBuffer = this.createBuffer(this.device, this.bufferSize, true); + } + + this.activeBuffer = new UsedBuffer(); + this.activeBuffer.stagingBuffer = stagingBuffer; + this.activeBuffer.gpuBuffer = gpuBuffer; + this.activeBuffer.offset = 0; + this.activeBuffer.size = 0; + } + + // allocate from active buffer + const activeBuffer = this.activeBuffer; + const alignedStart = math.roundUp(activeBuffer.size, this.bufferAlignment); + Debug.assert(alignedStart + size <= this.bufferSize, `The allocation size of ${size} is larger than the buffer size of ${this.bufferSize}`); + + allocation.gpuBuffer = activeBuffer.gpuBuffer; + allocation.offset = alignedStart; + allocation.storage = activeBuffer.stagingBuffer.alloc(alignedStart, size); + + // take the allocation from the buffer + activeBuffer.size = alignedStart + size; + } + + createBuffer(device, size, isStaging) { + return new WebgpuDynamicBuffer(device, size, isStaging); + } + + scheduleSubmit() { + + if (this.activeBuffer) { + this.usedBuffers.push(this.activeBuffer); + this.activeBuffer = null; + } + } + + submit() { + + // schedule currently active buffer for submit + this.scheduleSubmit(); + } +} + +export { DynamicBuffers, DynamicBufferAllocation }; diff --git a/packages/reshader.gl/src/webgpu/common/PipelineDesc.ts b/packages/reshader.gl/src/webgpu/common/PipelineDesc.ts new file mode 100644 index 0000000000..117fb78b40 --- /dev/null +++ b/packages/reshader.gl/src/webgpu/common/PipelineDesc.ts @@ -0,0 +1,148 @@ +import { isFunction, isNil } from "../../common/Util"; +import { toGPUCompareFunction, toTopology, toGPUBlendFactor } from "./ReglTranslator"; + +// Pipeline states we cared +export default class PipelineDescriptor { + depthBias?: number; + depthBiasSlopeScale?: number; + depthCompare?: GPUCompareFunction; + depthWriteEnabled?: boolean; + + stencilFrontCompare?: GPUCompareFunction; + stencilFrontPassOp?: GPUStencilOperation; + + blendAlphaSrc?: GPUBlendFactor; + blendAlphaDst?: GPUBlendFactor; + blendColorSrc?: GPUBlendFactor; + blendColorDst?: GPUBlendFactor; + + cullMode?: GPUCullMode; + frontFace?: GPUFrontFace; + topology?: GPUPrimitiveTopology; + + readFromREGLCommand(commandProps: any, mesh, uniformValues, doubleSided) { + const primitive = mesh.geometry.desc.primitive; + this.topology = toTopology(primitive); + + let depthBias, depthBiasSlopeScale; + if (commandProps.polygonOffset && isEnable(commandProps.polygonOffset.enable, uniformValues)) { + depthBias = 0; + depthBiasSlopeScale = 0; + const offsetProps = commandProps.polygonOffset.offset; + if (offsetProps && !isNil(offsetProps.units)) { + depthBias = isFunction(offsetProps.units) && offsetProps.units(null, uniformValues) || offsetProps.units; + } + if (offsetProps && !isNil(offsetProps.factor)) { + depthBiasSlopeScale = isFunction(offsetProps.factor) && offsetProps.factor(null, uniformValues) || offsetProps.factor; + } + } + this.depthBias = depthBias; + this.depthBiasSlopeScale = depthBiasSlopeScale; + + let depthCompare: GPUCompareFunction = 'always'; + let depthWriteEnable = false; + const depthProps = commandProps.depth; + if (depthProps && isEnable(depthProps.enable, uniformValues)) { + depthCompare = 'less'; + if (depthProps.func) { + const depthFunc = isFunction(depthProps.func) && depthProps.func(null, uniformValues) || depthProps.func; + depthCompare = toGPUCompareFunction(depthFunc); + } + + depthWriteEnable = true; + if (!isNil(depthProps.mask)) { + const maskFunc = isFunction(depthProps.mask) && depthProps.mask(null, uniformValues) || depthProps.mask; + depthWriteEnable = !!maskFunc; + } + + //TODO where is depth range? + } + this.depthCompare = depthCompare; + this.depthWriteEnabled = depthWriteEnable; + + let stencilFrontCompare, stencilFrontPassOp; + const stencilProps = commandProps.stencil; + if (stencilProps && isEnable(stencilProps.enable, uniformValues)) { + if (stencilProps.op) { + // 目前还没遇到op是函数的情况,所以可以直接读取 + stencilFrontPassOp = stencilProps.op.zpass; + } + + if (stencilProps.func) { + const stencilFunc = isFunction(stencilProps.func) && stencilProps.func(null, uniformValues) || stencilProps.func; + stencilFrontCompare = toGPUCompareFunction(stencilFunc); + } + } + this.stencilFrontCompare = stencilFrontCompare; + this.stencilFrontPassOp = stencilFrontPassOp; + + let blendAlphaSrc, blendAlphaDst; + let blendColorSrc, blendColorDst; + const blendProps = commandProps.blend; + if (blendProps && isEnable(blendProps.enable, uniformValues)) { + if (blendProps.func) { + if (blendProps.src) { + const blendSrc = isFunction(blendProps.src) && blendProps.src(null, uniformValues) || blendProps.src; + blendAlphaSrc = toGPUBlendFactor(blendSrc); + blendColorSrc = blendAlphaSrc; + } + if (blendProps.dst) { + const blendDst = isFunction(blendProps.dst) && blendProps.dst(null, uniformValues) || blendProps.dst; + blendAlphaDst = toGPUBlendFactor(blendDst); + blendColorDst = blendAlphaDst; + } + if (blendProps.srcAlpha) { + const blendSrcAlpha = isFunction(blendProps.srcAlpha) && blendProps.srcAlpha(null, uniformValues) || blendProps.srcAlpha; + blendAlphaSrc = toGPUBlendFactor(blendSrcAlpha); + } + if (blendProps.srcRGB) { + const blendSrcRGB = isFunction(blendProps.srcRGB) && blendProps.srcRGB(null, uniformValues) || blendProps.srcRGB; + blendColorSrc = toGPUBlendFactor(blendSrcRGB); + } + if (blendProps.dstAlpha) { + const blendDstAlpha = isFunction(blendProps.dstAlpha) && blendProps.dstAlpha(null, uniformValues) || blendProps.dstAlpha; + blendAlphaDst = toGPUBlendFactor(blendDstAlpha); + } + if (blendProps.dstRGB) { + const blendDstRGB = isFunction(blendProps.dstRGB) && blendProps.dstRGB(null, uniformValues) || blendProps.dstRGB; + blendColorDst = toGPUBlendFactor(blendDstRGB); + } + } + } + this.blendAlphaDst = blendAlphaDst; + this.blendAlphaSrc = blendAlphaSrc; + this.blendColorDst = blendColorDst; + this.blendColorSrc = blendColorSrc; + + let cullMode: GPUCullMode = 'none'; + if (!doubleSided) { + const cullProps = commandProps.cull; + if (cullProps && isEnable(cullProps.enable, uniformValues)) { + if (cullProps.face) { + cullMode = isFunction(cullProps.face) && cullProps.face(null, uniformValues) || cullProps.face; + } + } + } + this.cullMode = cullMode; + + // frontFace不存在函数的情况,所以可以直接读取 + this.frontFace = commandProps.frontFace; + + //TODO mesh中buffer的组织方式也需要考虑进来 + } + + getSignatureKey() { + return (this.depthBias || 0) + '-' + (this.depthBiasSlopeScale || 0) + '-' + (this.depthCompare || 0) + '-' + (this.depthWriteEnabled || 0) + + (this.stencilFrontCompare || 0) + '-' + (this.stencilFrontPassOp || 0) + + (this.blendAlphaSrc || 0) + '-' + (this.blendAlphaDst || 0) + '-' + (this.blendColorSrc || 0) + '-' + (this.blendColorDst || 0) + + (this.cullMode || 0) + '-' + (this.frontFace || 0) + '-' + (this.topology || 0); + } +} + +function isEnable(enable, props) { + if (!isFunction(enable)) { + return !!enable; + } + enable = enable(null, props); + return !!enable; +} diff --git a/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts b/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts new file mode 100644 index 0000000000..d3760f170f --- /dev/null +++ b/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts @@ -0,0 +1,72 @@ +const DEPTH_DICTIONARY = { + '=': 'equal', + '<': 'less', + '<=': 'less-equal', + 'lequal': 'less-equal', + '>': 'greater', + '!=': 'not-equal', + 'notequal': 'not-equal', + '>=': 'greater-equal', + 'gequal': 'greater-equal' +} +export function toGPUCompareFunction(func): GPUCompareFunction { + return DEPTH_DICTIONARY[func] || func; +} + +const TOPOLOGY_DICTIONARY = { + 'points': 'point-list', + 'triangles': 'triangle-list', + 'triangle strip': 'triangle-strip', + 'lines': 'line-list', + 'line strip': 'line-strip' +}; +export function toTopology(primitive): GPUPrimitiveTopology { + return TOPOLOGY_DICTIONARY[primitive] || 'triangle-list'; +} + +const BLENDFACTOR_DICTIONARY = { + 0: 'zero', + 1: 'one', + 'src color': 'src', + 'one minus src color': 'one-minus-src', + 'src alpha': 'src-alpha', + 'one minus src alpha': 'one-minus-src-alpha', + 'dst color': 'dst', + 'one minus dst color': 'one-minus-dst', + 'dst alpha': 'dst-alpha', + 'one minus dst alpha': 'one-minus-dst-alpha' + // 其实还有一些别的值,但因为没用到,这里就不用继续翻译了 +} + +export function toGPUBlendFactor(blendFactor): GPUBlendFactor { + return BLENDFACTOR_DICTIONARY[blendFactor] || blendFactor; +} + +const ADDRESS_MODE_DICTIONARY = { + 'clamp': 'clamp-to-edge', + 'mirror': 'mirror-repeat' +}; + +export function toGPUSampler(minFilter: string, magFilter: GPUFilterMode, wrapS: string, wrapT) { + const sampler: GPUSamplerDescriptor = { + magFilter + }; + if (minFilter === 'nearest' || minFilter === 'linear') { + sampler.minFilter = minFilter; + } else if (minFilter === 'linear mipmap linear' || minFilter === 'mipmap') { + sampler.minFilter = 'linear'; + sampler.mipmapFilter = 'linear'; + } else if (minFilter === 'nearest mipmap nearest') { + sampler.minFilter = 'linear'; + sampler.mipmapFilter = 'nearest'; + } else if (minFilter === 'nearest mipmap linear') { + sampler.minFilter = 'nearest'; + sampler.mipmapFilter = 'linear'; + } else if (minFilter === 'linear mipmap nearest') { + sampler.minFilter = 'linear'; + sampler.mipmapFilter = 'nearest'; + } + sampler.addressModeU = ADDRESS_MODE_DICTIONARY[wrapS || 'clamp'] || wrapS; + sampler.addressModeV = ADDRESS_MODE_DICTIONARY[wrapT || 'clamp'] || wrapT; + return sampler; +} diff --git a/packages/reshader.gl/src/webgpu/common/math.ts b/packages/reshader.gl/src/webgpu/common/math.ts new file mode 100644 index 0000000000..e05478f9f1 --- /dev/null +++ b/packages/reshader.gl/src/webgpu/common/math.ts @@ -0,0 +1,13 @@ +/** + * Rounds a number up to nearest multiple. + * + * @param numToRound - The number to round up. + * @param multiple - The multiple to round up to. + * @returns A number rounded up to nearest multiple. + */ +export function roundUp(numToRound: number, multiple: number) { + if (multiple === 0) { + return numToRound; + } + return Math.ceil(numToRound / multiple) * multiple; +} diff --git a/packages/reshader.gl/tsconfig.json b/packages/reshader.gl/tsconfig.json index 67d905bb38..e3d1890a98 100644 --- a/packages/reshader.gl/tsconfig.json +++ b/packages/reshader.gl/tsconfig.json @@ -4,6 +4,7 @@ "outDir": "./dist/", "rootDir": "./src", "baseUrl": "./", + "types": ["@webgpu/types"] }, "include": [ "src/**/*.ts", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2fd872033b..da4418a57c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -669,12 +669,21 @@ importers: '@maptalks/tbn-packer': specifier: ^1.4.5 version: 1.4.5 + '@webgpu/types': + specifier: 0.1.52 + version: 0.1.52 earcut: specifier: ^3.0.1 version: 3.0.1 gl-matrix: specifier: ^3.4.0 version: 3.4.3 + wgsl-preprocessor: + specifier: 1.0.0 + version: 1.0.0 + wgsl_reflect: + specifier: ^1.0.16 + version: 1.0.16 devDependencies: '@maptalks/rollup-plugin-glsl-minify': specifier: ^0.1.7 @@ -688,6 +697,9 @@ importers: '@rollup/plugin-terser': specifier: 0.4.4 version: 0.4.4(rollup@4.17.2) + '@rollup/plugin-typescript': + specifier: ^11.1.6 + version: 11.1.6(rollup@4.17.2)(tslib@2.6.2)(typescript@5.4.5) eslint: specifier: ^8.57.0 version: 8.57.0 @@ -2418,6 +2430,9 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@webgpu/types@0.1.52': + resolution: {integrity: sha512-eI883Nlag2hGIkhXxAnq8s4APpqXWuPL3Gbn2ghiU12UjLvfCbVqHK4XfXl3eLRTatqcMmeK7jws7IwWsGfbzw==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -4807,6 +4822,12 @@ packages: walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + wgsl-preprocessor@1.0.0: + resolution: {integrity: sha512-fpmt1KFFSr5IS1Gc7JYwgNEo2S0dOjUDbTgxc4Zbsp+N8PF+5HH4j2K2RYwz8XCB8kIxURyukNqSJ7yiSCV1jA==} + + wgsl_reflect@1.0.16: + resolution: {integrity: sha512-OE3urfXXbHMD5lhKZwxOxC9SFYynEGEkWXQmvi7B1gzzr5jb9+drh9A8MeBvVqKqznCoBuh8WOzVuSGSZs4CkQ==} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -7273,6 +7294,8 @@ snapshots: '@ungap/structured-clone@1.2.0': {} + '@webgpu/types@0.1.52': {} + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -9769,6 +9792,10 @@ snapshots: dependencies: makeerror: 1.0.12 + wgsl-preprocessor@1.0.0: {} + + wgsl_reflect@1.0.16: {} + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 From e7a6c1d18581f22624d8e0a2be27c00722a3d8e0 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Tue, 14 Jan 2025 17:27:48 +0800 Subject: [PATCH 06/53] implement DynamicBufferPool, DynamicBuffer and GraphicsDevice --- packages/reshader.gl/src/Mesh.ts | 48 +++--- packages/reshader.gl/src/shader/Shader.ts | 44 ++++-- .../reshader.gl/src/webgpu/BindGroupFormat.ts | 22 ++- .../reshader.gl/src/webgpu/DynamicBuffer.ts | 73 +++++++-- .../src/webgpu/DynamicBufferPool.ts | 146 ++++++++++++++++++ .../reshader.gl/src/webgpu/DynamicBuffers.ts | 143 ----------------- .../reshader.gl/src/webgpu/GraphicsDevice.ts | 32 ++++ 7 files changed, 303 insertions(+), 205 deletions(-) create mode 100644 packages/reshader.gl/src/webgpu/DynamicBufferPool.ts delete mode 100644 packages/reshader.gl/src/webgpu/DynamicBuffers.ts create mode 100644 packages/reshader.gl/src/webgpu/GraphicsDevice.ts diff --git a/packages/reshader.gl/src/Mesh.ts b/packages/reshader.gl/src/Mesh.ts index c3f6c9630f..4f8857f782 100644 --- a/packages/reshader.gl/src/Mesh.ts +++ b/packages/reshader.gl/src/Mesh.ts @@ -4,7 +4,6 @@ import BoundingBox from './BoundingBox.js'; import Geometry from './Geometry'; import Material from './Material'; import { ActiveAttributes, MatrixFunction, MeshOptions, ShaderDefines, ShaderUniformValue, ShaderUniforms } from './types/typings'; -import { Regl } from '@maptalks/regl'; import DynamicBuffer from './webgpu/DynamicBuffer'; const tempMat4: mat4 = new Array(16) as mat4; @@ -16,7 +15,7 @@ let uuid = 0; * Config: * transparent, castShadow */ -export class AbstractMesh { +export default class Mesh { //@internal _version: number //@internal @@ -470,6 +469,10 @@ export class AbstractMesh { delete this._geometry; delete this._material; this.uniforms = null; + if (this._meshBuffer) { + this._meshBuffer.dispose(); + delete this._meshBuffer; + } return this; } @@ -543,6 +546,21 @@ export class AbstractMesh { } return this.localTransform; } + + _meshBuffer: DynamicBuffer; + // 实现webgpu相关的逻辑 + writeDynamicBuffer(renderProps, bindGroupMapping, pool) { + if (!this._meshBuffer) { + this._meshBuffer = new DynamicBuffer(bindGroupMapping, pool); + } + this._meshBuffer.writeBuffer(renderProps); + // 运行时 + // 1. 根据参数中的 bind group mapping,负责生成 mesh 自身 uniform 的 bind group + // 1.1 如果uniform buffer是全新的,需要重新生成一个bind group + // 2. 负责从dynamic buffers 中请求uniform buffer + // 3. 负责从geometry中手机 vertex buffer 相关的信息 + return this._meshBuffer; + } } @@ -562,29 +580,3 @@ function equalDefine(obj0, obj1) { } return true; } - - -export class GPUMesh extends AbstractMesh { - _meshBuffer: DynamicBuffer; - _shaderBuffer: DynamicBuffer; - // 实现webgpu相关的逻辑 - getDynamicBuffer(device, renderProps, bindGroupMapping) { - if (!this._meshBuffer) { - this._meshBuffer = new DynamicBuffer(device, renderProps, bindGroupMapping); - } else { - this._meshBuffer.fill(renderProps, bindGroupMapping); - } - // 运行时 - // 1. 根据参数中的 bind group mapping,负责生成 mesh 自身 uniform 的 bind group - // 1.1 如果uniform buffer是全新的,需要重新生成一个bind group - // 2. 负责从dynamic buffers 中请求uniform buffer - // 3. 负责从geometry中手机 vertex buffer 相关的信息 - return this._meshBuffer; - } -} - -export default class Mesh extends AbstractMesh { - - - -} diff --git a/packages/reshader.gl/src/shader/Shader.ts b/packages/reshader.gl/src/shader/Shader.ts index d435055bea..def7fb4d3d 100644 --- a/packages/reshader.gl/src/shader/Shader.ts +++ b/packages/reshader.gl/src/shader/Shader.ts @@ -5,9 +5,10 @@ import { KEY_DISPOSED } from '../common/Constants.js'; import { ShaderUniformValue } from '../types/typings'; import PipelineDescriptor from '../webgpu/common/PipelineDesc'; import InstancedMesh from '../InstancedMesh'; -import Mesh from '../Mesh'; +import Mesh, { GPUMesh } from '../Mesh'; import DynamicBuffer from '../webgpu/DynamicBuffer'; import CommandBuilder from '../webgpu/CommandBuilder'; +import GraphicsDevice from '../webgpu/GraphicsDevice'; const UNIFORM_TYPE = { @@ -193,7 +194,7 @@ export class GPUShader extends AbstractShader { //@internal _presentationFormat: GPUTextureFormat; _bindGroupCache: Record; - _buffers: any; + _buffers: Record; getShaderCommandKey(mesh, uniformValues, doubleSided) { // 获取pipeline所需要的特征变量,即任何变量发生变化后,就需要创建新的pipeline @@ -214,24 +215,25 @@ export class GPUShader extends AbstractShader { return builder.build(pipelineDesc); } - run(device, command, props, context) { + run(device: GraphicsDevice, command, shaderUniforms, props, context) { + const buffersPool = device.dynamicBufferPool; const passEncoder: GPURenderPassEncoder = context.passEncoder; passEncoder.setPipeline(command.pipeline); const { key, bindGroupFormat, pipeline, vertexInfo } = command; const layout = pipeline.getBindGroupLayout(0); // 1. 生成shader uniform 需要的dynamic buffer - let shaderBuffer = this._buffers[key]; + let shaderBuffer = this._buffers[key] as DynamicBuffer; if (shaderBuffer) { - shaderBuffer = this._buffers[key] = new DynamicBuffer(device, bindGroupFormat.getShaderUniforms()); + shaderBuffer = this._buffers[key] = new DynamicBuffer(bindGroupFormat.getShaderUniforms(), buffersPool); } // 向buffer中填入shader uniform值 - shaderBuffer.writeBuffer(this.uniforms); + shaderBuffer.writeBuffer(shaderUniforms); for (let i = 0; i < props.length; i++) { - const mesh = props[i].meshObject; + const mesh = props[i].meshObject as GPUMesh; // 获取mesh的dynamicBuffer - const meshBuffer = mesh.getDynamicBuffer(device, bindGroupFormat.getShaderUniforms()); - const groupKey = meshBuffer.uid + '-' + shaderBuffer.uid; + const meshBuffer = mesh.writeDynamicBuffer(props[i], bindGroupFormat.getMeshUniforms(), buffersPool); + const groupKey = meshBuffer.version + '-' + shaderBuffer.version; // 获取或者生成bind group let bindGroup = this._bindGroupCache[groupKey]; if (!bindGroup) { @@ -241,9 +243,6 @@ export class GPUShader extends AbstractShader { this._bindGroupCache[groupKey] = bindGroup; } - // 向buffer中填入mesh uniform值 - meshBuffer.writeBuffer(props[i]); - // 获取 dynamicOffsets const dynamicOffsets = shaderBuffer.dynamicOffsets.concat(meshBuffer.dynamicOffsets); passEncoder.setBindGroup(0, bindGroup, dynamicOffsets); @@ -266,6 +265,27 @@ export class GPUShader extends AbstractShader { } passEncoder.end(); } + + _deleteCommand(command) { + if (command.bindGroupFormat) { + command.bindGroupFormat.dispose(); + } + } + + dispose() { + for (const key in this._buffers) { + if (this._buffers[key]) { + this._buffers[key].dispose(); + } + } + const commands = this.gpuCommands; + for (let i = 0; i < commands.length; i++) { + if (!commands[i]) { + continue; + } + this._deleteCommand(commands[i]); + } + } } export default class Shader extends AbstractShader { diff --git a/packages/reshader.gl/src/webgpu/BindGroupFormat.ts b/packages/reshader.gl/src/webgpu/BindGroupFormat.ts index e5f4749701..309f1a6ecf 100644 --- a/packages/reshader.gl/src/webgpu/BindGroupFormat.ts +++ b/packages/reshader.gl/src/webgpu/BindGroupFormat.ts @@ -32,20 +32,24 @@ export default class BindGroupFormat { _parse(bindGroupMapping) { this._shaderUniforms = []; this._shaderUniforms.index = 0; + this._shaderUniforms.totalSize = 0; this._meshUniforms = []; this._meshUniforms.index = 0; + this._meshUniforms.totalSize = 0; const groups = bindGroupMapping.groups; const group = groups[0]; for (let i = 0; i < group.length; i++) { - const bindedUniform = group[i]; - if (bindedUniform.isGlobal) { + const uniform = group[i]; + if (uniform.isGlobal) { let index = this._shaderUniforms.index; - this._shaderUniforms[index++] = bindedUniform; + this._shaderUniforms[index++] = uniform; this._shaderUniforms.index = index; + this._shaderUniforms.totalSize += uniform.size; } else { let index = this._shaderUniforms.index; - this._meshUniforms[index++] = bindedUniform; + this._meshUniforms[index++] = uniform; this._meshUniforms.index = index; + this._meshUniforms.totalSize += uniform.size; } } } @@ -72,10 +76,12 @@ export default class BindGroupFormat { resource: (texture.getREGLTexture(device) as GPUTexture).createView() }); } else { + const allocation = group.isGlobal ? shaderBuffer.allocation : meshBuffer.allocation; entries.push({ binding: group.binding, resource: { - buffer: group.isGlobal ? shaderBuffer.gpuBuffer : meshBuffer.gpuBuffer, + buffer: allocation.gpuBuffer, + // offset 永远设为0,在setBindGroup中设置dynamicOffsets offset: 0, size: Math.max(group.size, this.alignment) } @@ -88,4 +94,10 @@ export default class BindGroupFormat { entries }); } + + dispose() { + delete this._shaderUniforms; + delete this._meshUniforms; + delete this.groups; + } } diff --git a/packages/reshader.gl/src/webgpu/DynamicBuffer.ts b/packages/reshader.gl/src/webgpu/DynamicBuffer.ts index b4879dcc0e..e43b4a7641 100644 --- a/packages/reshader.gl/src/webgpu/DynamicBuffer.ts +++ b/packages/reshader.gl/src/webgpu/DynamicBuffer.ts @@ -1,30 +1,69 @@ +import { ResourceType } from "wgsl_reflect"; import { ShaderUniformValue } from "../types/typings"; - -let uid = 1; +import DynamicBufferPool, { DynamicBufferAllocation } from "./DynamicBufferPool"; +import { isArray } from "../common/Util"; export default class DynamicBuffer { - device: GPUDevice; bindgroupMapping: any; + dynamicOffsets: number[]; + pool: DynamicBufferPool; + allocation: DynamicBufferAllocation; + version: number = 0; - dynamicOffset: number; - gpuBuffer: GPUBuffer; - stagingBuffer: GPUBuffer; - dirty: boolean; - size: number; - bindGroup: GPUBindGroup; - uid: number; - - constructor(device: GPUDevice, bindgroupMapping) { - this.device = device; + constructor(bindgroupMapping, pool: DynamicBufferPool) { this.bindgroupMapping = bindgroupMapping; - this.dirty = true; - this.uid = uid++; + this.pool = pool; + this.dynamicOffsets = new Array(bindgroupMapping.length); } writeBuffer(uniformValues: Record) { - //TODO 从对象池中申请stagingBuffer - //按照mapping中的顺序将uniform值写入stagingBuffer,写入完成后,记录dynamicOffsets + this.dynamicOffsets.fill(0); + const totalSize = this.bindgroupMapping.totalSize; + const gpuBuffer = this.allocation.gpuBuffer; + this.pool.alloc(this.allocation, totalSize); + if (gpuBuffer !== this.allocation.gpuBuffer) { + this.version++; + } + let dynamicOffset = this.allocation.offset; + const mapping = this.bindgroupMapping; + const storage = this.allocation.storage; + for (let i = 0; i < mapping.length; i++) { + const uniform = mapping[i]; + if (uniform.members) { + for (let j = 0; j < uniform.members.length; j++) { + const member = uniform.members[j]; + const value = uniformValues[member.name] as number | number[]; + const offset = dynamicOffset + member.offset; + const size = member.size; + this._fillValue(storage, offset, size, value); + } + // size() 返回的值已经考虑过 bufferAlignment + dynamicOffset += mapping[i].size(); + this.dynamicOffsets[i] = dynamicOffset; + } else if (uniform.resourceType === ResourceType.Uniform) { + const value = uniformValues[uniform.name]; + this._fillValue(storage, dynamicOffset, uniform.size(), value); + dynamicOffset += mapping[i].size(); + this.dynamicOffsets[i] = dynamicOffset; + } + } } + _fillValue(buffer, offset, size, value) { + // we always use f32 in WGSL + const view = new Float32Array(buffer, offset, size / 4); + if (isArray(value)) { + for (let i = 0; i < value.length; i++) { + view[i] = value[i]; + } + } else { + view[0] = value; + } + } + dispose() { + delete this.pool; + delete this.allocation; + delete this.dynamicOffsets; + } } diff --git a/packages/reshader.gl/src/webgpu/DynamicBufferPool.ts b/packages/reshader.gl/src/webgpu/DynamicBufferPool.ts new file mode 100644 index 0000000000..40c97dec24 --- /dev/null +++ b/packages/reshader.gl/src/webgpu/DynamicBufferPool.ts @@ -0,0 +1,146 @@ +// contains codes from playcanvas +// https://github.com/playcanvas/engine +// MIT License +import * as math from './common/math'; + +export type DynamicBufferAllocation = { + storage: ArrayBuffer; + gpuBuffer: GPUBuffer; + offset: number; + size: number; +} + +export default class DynamicBufferPool { + + /** + * Allocation size of the underlying buffers. + * + */ + bufferSize: number; + /** + * Internally allocated gpu buffers. + * + */ + poolBuffers: DynamicBufferAllocation[]; + + usedBuffers: DynamicBufferAllocation[]; + + /** + * @type {DynamicBufferAllocation|null} + */ + activeBuffer: DynamicBufferAllocation = null; + device: GPUDevice; + bufferAlignment: number; + + /** + * Create the system of dynamic buffers. + * + * @param device - The graphics device. + * @param bufferSize - The size of the underlying large buffers. + * @param bufferAlignment - Alignment of each allocation. + */ + constructor(device: GPUDevice, bufferSize: number, bufferAlignment: number) { + this.device = device; + this.usedBuffers = []; + this.poolBuffers = []; + this.bufferSize = bufferSize; + this.bufferAlignment = bufferAlignment; + } + + /** + * Destroy the system of dynamic buffers. + */ + destroy() { + this.poolBuffers.forEach((poolBuffer) => { + poolBuffer.gpuBuffer.destroy(); + }); + this.poolBuffers.length = 0; + this.usedBuffers.length = 0; + this.activeBuffer = null; + } + + /** + * Allocate an aligned space of the given size from a dynamic buffer. + * + * @param {DynamicBufferAllocation} allocation - The allocation info to fill. + * @param {number} size - The size of the allocation. + */ + alloc(allocation, size) { + + // if we have active buffer without enough space + if (this.activeBuffer) { + const alignedStart = math.roundUp(this.activeBuffer.size, this.bufferAlignment); + const space = this.bufferSize - alignedStart; + if (space < size) { + + // we're done with this buffer, schedule it for submit + this.scheduleSubmit(); + } + } + + // if we don't have an active buffer, allocate new one + if (!this.activeBuffer) { + + // gpu buffer + this.activeBuffer = this.poolBuffers.pop(); + if (!this.activeBuffer) { + this.activeBuffer = { + gpuBuffer: this.createBuffer(this.device, this.bufferSize), + storage: new ArrayBuffer(this.bufferSize), + offset: 0, + size: 0 + }; + } + this.activeBuffer.offset = 0; + this.activeBuffer.size = 0; + } + + // allocate from active buffer + const activeBuffer = this.activeBuffer; + const alignedStart = math.roundUp(activeBuffer.size, this.bufferAlignment); + // Debug.assert(alignedStart + size <= this.bufferSize, `The allocation size of ${size} is larger than the buffer size of ${this.bufferSize}`); + + allocation.gpuBuffer = activeBuffer.gpuBuffer; + allocation.offset = alignedStart; + allocation.storage = activeBuffer.storage; + + // take the allocation from the buffer + activeBuffer.size = alignedStart + size; + } + + createBuffer(device: GPUDevice, size: number) { + return device.createBuffer({ + size: size, + usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST + }); + } + + scheduleSubmit() { + if (this.activeBuffer) { + this.usedBuffers.push(this.activeBuffer); + this.activeBuffer = null; + } + } + + submit() { + + // schedule currently active buffer for submit + this.scheduleSubmit(); + + // submit all used buffers + const count = this.usedBuffers.length; + if (count) { + const device = this.device; + const poolBuffers = this.poolBuffers; + // run this loop backwards to preserve the order of buffers in gpuBuffers array + for (let i = count - 1; i >= 0; i--) { + const usedBuffer = this.usedBuffers[i]; + const { storage, gpuBuffer, offset, size } = usedBuffer; + device.queue.writeBuffer(gpuBuffer, 0, storage, offset, size); + poolBuffers.push(usedBuffer); + } + + this.usedBuffers.length = 0; + } + } +} diff --git a/packages/reshader.gl/src/webgpu/DynamicBuffers.ts b/packages/reshader.gl/src/webgpu/DynamicBuffers.ts deleted file mode 100644 index d4b5f89972..0000000000 --- a/packages/reshader.gl/src/webgpu/DynamicBuffers.ts +++ /dev/null @@ -1,143 +0,0 @@ -import * as math from './common/math'; - -export default class DynamicBuffers { - - /** - * Allocation size of the underlying buffers. - * - * @type {number} - */ - bufferSize; - - /** - * Internally allocated gpu buffers. - * - * @type {DynamicBuffer[]} - */ - gpuBuffers = []; - - /** - * Internally allocated staging buffers (CPU writable) - * - * @type {DynamicBuffer[]} - */ - stagingBuffers = []; - - /** - * @type {UsedBuffer[]} - */ - usedBuffers = []; - - /** - * @type {UsedBuffer|null} - */ - activeBuffer = null; - device: GPUDevice; - bufferAlignment: any; - - /** - * Create the system of dynamic buffers. - * - * @param {GraphicsDevice} device - The graphics device. - * @param {number} bufferSize - The size of the underlying large buffers. - * @param {number} bufferAlignment - Alignment of each allocation. - */ - constructor(device, bufferSize, bufferAlignment) { - this.device = device; - this.bufferSize = bufferSize; - this.bufferAlignment = bufferAlignment; - } - - /** - * Destroy the system of dynamic buffers. - */ - destroy() { - - this.gpuBuffers.forEach((gpuBuffer) => { - gpuBuffer.destroy(this.device); - }); - this.gpuBuffers = null; - - this.stagingBuffers.forEach((stagingBuffer) => { - stagingBuffer.destroy(this.device); - }); - this.stagingBuffers = null; - - this.usedBuffers = null; - this.activeBuffer = null; - } - - /** - * Allocate an aligned space of the given size from a dynamic buffer. - * - * @param {DynamicBufferAllocation} allocation - The allocation info to fill. - * @param {number} size - The size of the allocation. - */ - alloc(allocation, size) { - - // if we have active buffer without enough space - if (this.activeBuffer) { - const alignedStart = math.roundUp(this.activeBuffer.size, this.bufferAlignment); - const space = this.bufferSize - alignedStart; - if (space < size) { - - // we're done with this buffer, schedule it for submit - this.scheduleSubmit(); - } - } - - // if we don't have an active buffer, allocate new one - if (!this.activeBuffer) { - - // gpu buffer - let gpuBuffer = this.gpuBuffers.pop(); - if (!gpuBuffer) { - gpuBuffer = this.createBuffer(this.device, this.bufferSize, false); - } - - // staging buffer - let stagingBuffer = this.stagingBuffers.pop(); - if (!stagingBuffer) { - stagingBuffer = this.createBuffer(this.device, this.bufferSize, true); - } - - this.activeBuffer = new UsedBuffer(); - this.activeBuffer.stagingBuffer = stagingBuffer; - this.activeBuffer.gpuBuffer = gpuBuffer; - this.activeBuffer.offset = 0; - this.activeBuffer.size = 0; - } - - // allocate from active buffer - const activeBuffer = this.activeBuffer; - const alignedStart = math.roundUp(activeBuffer.size, this.bufferAlignment); - Debug.assert(alignedStart + size <= this.bufferSize, `The allocation size of ${size} is larger than the buffer size of ${this.bufferSize}`); - - allocation.gpuBuffer = activeBuffer.gpuBuffer; - allocation.offset = alignedStart; - allocation.storage = activeBuffer.stagingBuffer.alloc(alignedStart, size); - - // take the allocation from the buffer - activeBuffer.size = alignedStart + size; - } - - createBuffer(device, size, isStaging) { - return new WebgpuDynamicBuffer(device, size, isStaging); - } - - scheduleSubmit() { - - if (this.activeBuffer) { - this.usedBuffers.push(this.activeBuffer); - this.activeBuffer = null; - } - } - - submit() { - - // schedule currently active buffer for submit - this.scheduleSubmit(); - } -} - -export { DynamicBuffers, DynamicBufferAllocation }; diff --git a/packages/reshader.gl/src/webgpu/GraphicsDevice.ts b/packages/reshader.gl/src/webgpu/GraphicsDevice.ts new file mode 100644 index 0000000000..ae1c97bf52 --- /dev/null +++ b/packages/reshader.gl/src/webgpu/GraphicsDevice.ts @@ -0,0 +1,32 @@ +import DynamicBufferPool from "./DynamicBufferPool"; + +export default class GraphicsDevice { + wgpu: GPUDevice; + commandBuffers: GPUCommandBuffer[]; + dynamicBufferPool: DynamicBufferPool; + + constructor(device: GPUDevice) { + this.wgpu = device; + // 1M for each buffer + const bufferSize = 1024 * 1024; + const limits = device.limits; + this.dynamicBufferPool = new DynamicBufferPool(device, bufferSize, limits.minUniformBufferOffsetAlignment); + } + + addCommandBuffer(commandBuffer: GPUCommandBuffer, front: boolean) { + if (front) { + this.commandBuffers.unshift(commandBuffer); + } else { + this.commandBuffers.push(commandBuffer); + } + } + + submit() { + // copy dynamic buffers data to the GPU (this schedules the copy CB to run before all other CBs) + this.dynamicBufferPool.submit(); + if (this.commandBuffers.length > 0) { + this.wgpu.queue.submit(this.commandBuffers); + this.commandBuffers.length = 0; + } + } +} From e973238449d5941e9594937557e07dbd4d3aeeb2 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Wed, 15 Jan 2025 17:24:33 +0800 Subject: [PATCH 07/53] implement Geometry.createBuffer/createElementBuffer and fix Geometry buffer descriptor --- packages/reshader.gl/src/Geometry.ts | 459 ++++++++++-------- packages/reshader.gl/src/Mesh.ts | 5 - .../reshader.gl/src/skybox/SkyboxShader.js | 2 +- .../reshader.gl/src/webgpu/CommandBuilder.ts | 17 +- .../reshader.gl/src/webgpu/common/Types.ts | 41 ++ 5 files changed, 310 insertions(+), 214 deletions(-) create mode 100644 packages/reshader.gl/src/webgpu/common/Types.ts diff --git a/packages/reshader.gl/src/Geometry.ts b/packages/reshader.gl/src/Geometry.ts index 30a4004012..e5e84f048c 100644 --- a/packages/reshader.gl/src/Geometry.ts +++ b/packages/reshader.gl/src/Geometry.ts @@ -5,7 +5,10 @@ import BoundingBox from './BoundingBox'; import { KEY_DISPOSED } from './common/Constants'; import { getGLTFLoaderBundle } from './common/GLTFBundle' import { ActiveAttributes, AttributeData, GeometryDesc, NumberArray } from './types/typings'; -import REGL, { Regl } from '@maptalks/regl'; +import REGL from '@maptalks/regl'; +import GraphicsDevice from './webgpu/GraphicsDevice'; +import { flatten } from 'earcut'; +import { getGPUVertexType, getFormatFromGLTFAccessor, getItemBytesFromGLTFAccessor } from './webgpu/common/Types'; const EMPTY_VAO_BUFFER = []; @@ -51,6 +54,24 @@ function GUID() { const REF_COUNT_KEY = '_reshader_refCount'; export class AbstractGeometry { + static createElementBuffer(device: any, elements: any): any { + if (device instanceof GraphicsDevice) { + return createGPUBuffer(device, elements, GPUBufferUsage.VERTEX, 'index buffer'); + } else { + // regl + return device.elements(elements); + } + } + + static createBuffer(device: any, data: any, name: string) { + if (device instanceof GraphicsDevice) { + return createGPUBuffer(device, data, GPUBufferUsage.VERTEX, name); + } else { + // regl + return device.buffer(data); + } + } + data: Record elements: any desc: GeometryDesc @@ -221,7 +242,83 @@ export class AbstractGeometry { return this._reglData[key]; } + getREGLData(regl: any, activeAttributes: ActiveAttributes, disableVAO: boolean): AttributeData { + this.getAttrData(activeAttributes); + //support vao + if (isSupportVAO(regl) && !disableVAO) { + const updated = !this._reglData || !this._reglData[activeAttributes.key]; + const key = activeAttributes && activeAttributes.key || 'default'; + if (!this._vao[key] || updated || this._vao[key].dirty) { + const reglData = this._reglData[activeAttributes.key]; + const vertexCount = this._vertexCount; + const buffers = []; + for (let i = 0; i < activeAttributes.length; i++) { + const p = activeAttributes[i]; + const attr = p.name; + const buffer = reglData[attr] && reglData[attr].buffer; + if (!buffer || !buffer.destroy) { + const data = reglData[attr]; + if (!data) { + if (this.desc.fillEmptyDataInMissingAttribute) { + // 某些老版本浏览器(例如3dtiles中的electron),数据不能传空字符串,否则会报错 + // glDrawElements: attempt to access out of range vertices in attribute 1 + buffers.push(new Uint8Array(vertexCount * 4)); + } else { + buffers.push(EMPTY_VAO_BUFFER); + } + continue; + } + const dimension = (data.data && isArray(data.data) ? data.data.length : data.length) / vertexCount; + if (data.data) { + data.dimension = dimension; + buffers.push(data); + } else { + buffers.push({ + data, + dimension + }); + } + } else if (reglData[attr].stride !== undefined) { + buffers.push( + reglData[attr] + ); + } else { + buffers.push(buffer); + } + } + + const vaoData = { + attributes: buffers, + primitive: this.getPrimitive() + } as any; + if (this.elements && !isNumber(this.elements)) { + if (this.elements.destroy) { + vaoData.elements = this.elements; + } else { + vaoData.elements = { + primitive: this.getPrimitive(), + data: this.elements + }; + const type = this.getElementsType(this.elements); + if (type) { + vaoData.elements.type = type; + } + } + } + if (!this._vao[key]) { + this._vao[key] = { + vao: regl.vao(vaoData) + }; + } else { + this._vao[key].vao(vaoData); + } + } + delete this._vao[key].dirty; + return this._vao[key]; + } + return this._reglData[activeAttributes.key]; + } //@internal _isAttrChanged(activeAttributes: ActiveAttributes): boolean { @@ -239,6 +336,85 @@ export class AbstractGeometry { return false; } + generateBuffers(device: any) { + //generate regl buffers beforehand to avoid repeated bufferData + //提前处理addBuffer插入的arraybuffer + const allocatedBuffers = this._buffers; + for (const p in allocatedBuffers) { + if (!allocatedBuffers[p].buffer) { + allocatedBuffers[p].buffer = AbstractGeometry.createBuffer(device, allocatedBuffers[p].data, p); + } + delete allocatedBuffers[p].data; + } + const positionName = this.desc.positionAttribute; + const altitudeName = this.desc.altitudeAttribute; + const data = this.data; + const vertexCount = this._vertexCount; + const buffers = {}; + for (const key in data) { + if (!data[key]) { + continue; + } + //如果调用过addBuffer,buffer有可能是ArrayBuffer + if (data[key].buffer !== undefined && !(data[key].buffer instanceof ArrayBuffer)) { + if (data[key].buffer.destroy) { + buffers[key] = data[key]; + } else if (allocatedBuffers[data[key].buffer]) { + //多个属性共用同一个ArrayBuffer(interleaved) + buffers[key] = extend({}, data[key]); + buffers[key].buffer = allocatedBuffers[data[key].buffer].buffer; + } + } else { + const arr = data[key].data ? data[key].data : data[key]; + const dimension = arr.length / vertexCount; + const info = data[key].data ? data[key] : { data: data[key] }; + info.dimension = dimension; + const buffer = AbstractGeometry.createBuffer(device, info, key); + buffer[REF_COUNT_KEY] = 1; + buffers[key] = { + buffer + }; + if (key === positionName || key === altitudeName) {//vt中positionSize=2,z存在altitude中,也需要一并保存 + buffers[key].array = data[key]; + } + + } + if (this.desc.static || key !== positionName) {//保存POSITION原始数据,用来做额外计算 + delete data[key].array; + } + } + this.data = buffers; + delete this._reglData; + + // const supportVAO = isSupportVAO(regl); + // const excludeElementsInVAO = options && options.excludeElementsInVAO; + if (this.elements && !isNumber(this.elements)) { + const info = { + primitive: this.getPrimitive(), + data: this.elements + } as any; + const type = this.getElementsType(this.elements); + if (type) { + info.type = type; + } + if (!this.desc.static && !this.elements.destroy) { + const elements = this.elements; + this.indices = new Uint16Array(elements.length); + for (let i = 0; i < elements.length; i++) { + this.indices[i] = elements[i]; + } + } + this.elements = this.elements.destroy ? this.elements : Geometry.createElementBuffer(device, this.elements); + const elements = this.elements; + if (!elements[REF_COUNT_KEY]) { + elements[REF_COUNT_KEY] = 0; + } + elements[REF_COUNT_KEY]++; + + } + } + + getVertexCount(): number { const { positionAttribute, positionSize, color0Attribute } = this.desc; let data = this.data[positionAttribute]; @@ -793,8 +969,13 @@ function getElementLength(elements) { // object buffer form return elements.count; } else if (elements.destroy) { - // a regl element buffer - return elements['_elements'].vertCount; + if (elements['_elements']) { + // a regl element buffer + return elements['_elements'].vertCount; + } else { + //GPUBuffer + return elements.itemCount; + } } else if (elements.length !== undefined) { return elements.length; } else if (elements.data) { @@ -824,8 +1005,8 @@ export class GPUGeometry extends AbstractGeometry { const keys = []; for (const p in this.data) { const attr = this.data[p]; - if (attr.bytesStride) { - keys.push(`${p}(${attr.byteOffset}/${attr.bytesStride}})`); + if (attr.byteStride) { + keys.push(`${p}(${attr.byteOffset}/${attr.byteStride}})`); } else { keys.push(p); } @@ -843,50 +1024,48 @@ export class GPUGeometry extends AbstractGeometry { continue; } const info = vertexInfo[p]; - if (isArray(attr)) { - const itemBytes = getItemBytes(attr); - bufferDesc.push({ - arrayStride: info.itemSize * itemBytes, - attributes: [ - { - shaderLocation: info.location, - format: info.format, - offset: 0 - } - ] - }); + const accessorName = attr.accessorName; + const byteStride = attr.byteStride; + if (byteStride && accessorName) { + // a GLTF accessor style attribute + const format = getFormatFromGLTFAccessor(attr.componentType, attr.itemSize); + let desc = bufferMapping[accessorName]; + if (desc) { + desc.attributes.push({ + shaderLocation: info.location, + format, + offset: attr.byteOffset + }); + } else { + desc = { + arrayStride: byteStride, + attributes: [ + { + shaderLocation: info.location, + format, + offset: attr.byteOffset + } + ] + } + bufferMapping[accessorName] = desc; + } } else { - // a descriptor in gltf accessor style - const accessorName = attr.accessorName; - const byteStride = attr.bytesStride; - if (byteStride && accessorName) { - let desc = bufferMapping[accessorName]; - if (desc) { - desc.attributes.push({ - shaderLocation: info.location, - format: info.format, - offset: attr.byteOffset - }); + const array = attr.data || attr.array || attr; + if (isArray(array)) { + let format, itemBytes; + if (attr.componentType) { + format = getFormatFromGLTFAccessor(attr.componentType, attr.itemSize); + itemBytes = getItemBytesFromGLTFAccessor(attr.componentType); } else { - desc = { - arrayStride: byteStride, - attributes: [ - { - shaderLocation: info.location, - format: info.format, - offset: attr.byteOffset - } - ] - } - bufferMapping[accessorName] = desc; + format = getItemFormat(array, info.itemSize); + itemBytes = getItemBytes(array); } - } else { bufferDesc.push({ - arrayStride: info.bytes, + arrayStride: info.itemSize * itemBytes, attributes: [ { shaderLocation: info.location, - format: info.format, + format, offset: 0 } ] @@ -902,169 +1081,12 @@ export default class Geometry extends AbstractGeometry { getCommandKey() { return ''; } - getREGLData(regl: any, activeAttributes: ActiveAttributes, disableVAO: boolean): AttributeData { - this.getAttrData(activeAttributes); - const updated = !this._reglData || !this._reglData[activeAttributes.key]; - //support vao - if (isSupportVAO(regl) && !disableVAO) { - const key = activeAttributes && activeAttributes.key || 'default'; - if (!this._vao[key] || updated || this._vao[key].dirty) { - const reglData = this._reglData[activeAttributes.key]; - const vertexCount = this._vertexCount; - const buffers = []; - - for (let i = 0; i < activeAttributes.length; i++) { - const p = activeAttributes[i]; - const attr = p.name; - const buffer = reglData[attr] && reglData[attr].buffer; - if (!buffer || !buffer.destroy) { - const data = reglData[attr]; - if (!data) { - if (this.desc.fillEmptyDataInMissingAttribute) { - // 某些老版本浏览器(例如3dtiles中的electron),数据不能传空字符串,否则会报错 - // glDrawElements: attempt to access out of range vertices in attribute 1 - buffers.push(new Uint8Array(vertexCount * 4)); - } else { - buffers.push(EMPTY_VAO_BUFFER); - } - continue; - } - const dimension = (data.data && isArray(data.data) ? data.data.length : data.length) / vertexCount; - if (data.data) { - data.dimension = dimension; - buffers.push(data); - } else { - buffers.push({ - data, - dimension - }); - } - } else if (reglData[attr].stride !== undefined) { - buffers.push( - reglData[attr] - ); - } else { - buffers.push(buffer); - } - } - - const vaoData = { - attributes: buffers, - primitive: this.getPrimitive() - } as any; - if (this.elements && !isNumber(this.elements)) { - if (this.elements.destroy) { - vaoData.elements = this.elements; - } else { - vaoData.elements = { - primitive: this.getPrimitive(), - data: this.elements - }; - const type = this.getElementsType(this.elements); - if (type) { - vaoData.elements.type = type; - } - } - } - if (!this._vao[key]) { - this._vao[key] = { - vao: regl.vao(vaoData) - }; - } else { - this._vao[key].vao(vaoData); - } - } - delete this._vao[key].dirty; - return this._vao[key]; - } - return this._reglData[activeAttributes.key]; - } - - generateBuffers(regl: Regl) { - //generate regl buffers beforehand to avoid repeated bufferData - //提前处理addBuffer插入的arraybuffer - const allocatedBuffers = this._buffers; - for (const p in allocatedBuffers) { - if (!allocatedBuffers[p].buffer) { - allocatedBuffers[p].buffer = regl.buffer(allocatedBuffers[p].data); - } - delete allocatedBuffers[p].data; - } - const positionName = this.desc.positionAttribute; - const altitudeName = this.desc.altitudeAttribute; - const data = this.data; - const vertexCount = this._vertexCount; - const buffers = {}; - for (const key in data) { - if (!data[key]) { - continue; - } - //如果调用过addBuffer,buffer有可能是ArrayBuffer - if (data[key].buffer !== undefined && !(data[key].buffer instanceof ArrayBuffer)) { - if (data[key].buffer.destroy) { - buffers[key] = data[key]; - } else if (allocatedBuffers[data[key].buffer]) { - //多个属性共用同一个ArrayBuffer(interleaved) - buffers[key] = extend({}, data[key]); - buffers[key].buffer = allocatedBuffers[data[key].buffer].buffer; - } - } else { - const arr = data[key].data ? data[key].data : data[key]; - const dimension = arr.length / vertexCount; - const info = data[key].data ? data[key] : { data: data[key] }; - info.dimension = dimension; - const buffer = regl.buffer(info); - buffer[REF_COUNT_KEY] = 1; - buffers[key] = { - buffer - }; - if (key === positionName || key === altitudeName) {//vt中positionSize=2,z存在altitude中,也需要一并保存 - buffers[key].array = data[key]; - } - - } - if (this.desc.static || key !== positionName) {//保存POSITION原始数据,用来做额外计算 - delete data[key].array; - } - } - this.data = buffers; - delete this._reglData; - - // const supportVAO = isSupportVAO(regl); - // const excludeElementsInVAO = options && options.excludeElementsInVAO; - if (this.elements && !isNumber(this.elements)) { - const info = { - primitive: this.getPrimitive(), - data: this.elements - } as any; - const type = this.getElementsType(this.elements); - if (type) { - info.type = type; - } - if (!this.desc.static && !this.elements.destroy) { - const elements = this.elements; - this.indices = new Uint16Array(elements.length); - for (let i = 0; i < elements.length; i++) { - this.indices[i] = elements[i]; - } - } - this.elements = this.elements.destroy ? this.elements : regl.elements(info); - const elements = this.elements; - if (!elements[REF_COUNT_KEY]) { - elements[REF_COUNT_KEY] = 0; - } - elements[REF_COUNT_KEY]++; - - } - } - dispose() { this._deleteVAO(); super.dispose(); } - //@internal _deleteVAO() { for (const p in this._vao) { @@ -1074,7 +1096,11 @@ export default class Geometry extends AbstractGeometry { } } -function getItemBytes(array) { +function getItemBytes(data) { + const array = data.data || data.buffer || data; + if (array.destroy) { + return array.itemBytes; + } if (array.BYTES_PER_ELEMENT) { return array.BYTES_PER_ELEMENT; } else if (Array.isArray(array)) { @@ -1087,3 +1113,36 @@ function getItemBytes(array) { return ctor.BYTES_PER_ELEMENT; } } + +function getItemFormat(data, itemSize) { + const array = data.data || data.buffer || data; + let format; + if (array.destroy) { + format = array.itemType; + } else { + format = getGPUVertexType(data); + } + return itemSize > 1 ? (format + 'x' + itemSize) : format; +} + +function createGPUBuffer(device, data, usage, label) { + data = data.data || data; + if (Array.isArray(data[0])) { + data = flatten(data); + } + const ctor = data.constructor; + // f32 in default + const size = Array.isArray(data) ? data.length * 4 : data.byteLength; + const buffer = device.wgpu.createBuffer({ + label, + size, + usage, + mappedAtCreation: true + }); + new ctor(buffer.getMappedRange()).set(data); + buffer.unmap(); + buffer.itemCount = data.length; + buffer.itemBytes = getItemBytes(data); + buffer.itemType = getGPUVertexType(data); // uint8, sint8, uint16, sint16, uint32, sint32, float32 + return buffer; +} diff --git a/packages/reshader.gl/src/Mesh.ts b/packages/reshader.gl/src/Mesh.ts index 4f8857f782..ae57f23533 100644 --- a/packages/reshader.gl/src/Mesh.ts +++ b/packages/reshader.gl/src/Mesh.ts @@ -554,11 +554,6 @@ export default class Mesh { this._meshBuffer = new DynamicBuffer(bindGroupMapping, pool); } this._meshBuffer.writeBuffer(renderProps); - // 运行时 - // 1. 根据参数中的 bind group mapping,负责生成 mesh 自身 uniform 的 bind group - // 1.1 如果uniform buffer是全新的,需要重新生成一个bind group - // 2. 负责从dynamic buffers 中请求uniform buffer - // 3. 负责从geometry中手机 vertex buffer 相关的信息 return this._meshBuffer; } } diff --git a/packages/reshader.gl/src/skybox/SkyboxShader.js b/packages/reshader.gl/src/skybox/SkyboxShader.js index 5f9c7e0a34..e3063e780e 100644 --- a/packages/reshader.gl/src/skybox/SkyboxShader.js +++ b/packages/reshader.gl/src/skybox/SkyboxShader.js @@ -58,7 +58,7 @@ class SkyboxShader extends MeshShader { _createSkyboxMesh(regl) { const geometry = new Geometry( { - aPosition : new Int8Array(skyboxData.vertices) + aPosition: new Int8Array(skyboxData.vertices) }, null, skyboxData.vertices.length / 3 diff --git a/packages/reshader.gl/src/webgpu/CommandBuilder.ts b/packages/reshader.gl/src/webgpu/CommandBuilder.ts index 01356a1e3b..a0a6e7fe92 100644 --- a/packages/reshader.gl/src/webgpu/CommandBuilder.ts +++ b/packages/reshader.gl/src/webgpu/CommandBuilder.ts @@ -113,8 +113,7 @@ export default class CommandBuilder { if (inputMapping[name]) { vertexInfo[name] = { location: inputMapping[name].location, - format: toBufferFormat(inputMapping[name].type), - bytes: inputMapping[name].type.size + itemSize: getItemSize(inputMapping[name].type) }; } } @@ -226,12 +225,14 @@ export default class CommandBuilder { } } - -function toBufferFormat(type: any) { - //wgsl中解析的type,转换为buffer的format - throw new Error('Function not implemented.'); -} - function meshHasUniform(mesh: Mesh, name: string) { return mesh.hasUniform(name) || (mesh.material && mesh.material.hasUniform(name)); } + +function getItemSize(type) { + if (type.name.startsWith('vec')) { + return parseInt(type.name[3]); + } else { + return 1; + } +} diff --git a/packages/reshader.gl/src/webgpu/common/Types.ts b/packages/reshader.gl/src/webgpu/common/Types.ts new file mode 100644 index 0000000000..8fb3feebc6 --- /dev/null +++ b/packages/reshader.gl/src/webgpu/common/Types.ts @@ -0,0 +1,41 @@ +export function getGPUVertexType(array) { + let format; + if (Array.isArray(array) || array instanceof Float32Array) { + format === 'float32'; + } else if (array instanceof Uint32Array) { + format === 'uint32'; + } else if (array instanceof Int32Array) { + format === 'sint32'; + } else if (array instanceof Uint16Array) { + format === 'uint16'; + } else if (array instanceof Int16Array) { + format === 'sint16'; + } else if (array instanceof Uint8Array) { + format === 'uint8'; + } else if (array instanceof Int8Array) { + format === 'sint8'; + } + return format; +} + +const component_ctors = { + 5120: { name: 'sint8', bytes: 1 }, + 5122: { name: 'sint16', bytes: 2 }, + 5124: { name: 'sint32', bytes: 4 }, + 5121: { name: 'uint8', bytes: 1 }, + 5123: { name: 'uint16', bytes: 2 }, + 5125: { name: 'uint32', bytes: 4 }, + 5126: { name: 'float32', bytes: 4 } +}; +export function getFormatFromGLTFAccessor(componentType, itemSize) { + const format = component_ctors[componentType].name; + if (itemSize > 1) { + return format + 'x' + itemSize; + } else { + return format; + } +} + +export function getItemBytesFromGLTFAccessor(componentType) { + return component_ctors[componentType].bytes; +} From 0faf3925ad266a61dc00458461695225231a3543 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Wed, 15 Jan 2025 17:26:54 +0800 Subject: [PATCH 08/53] fix typo --- packages/reshader.gl/src/Geometry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/reshader.gl/src/Geometry.ts b/packages/reshader.gl/src/Geometry.ts index e5e84f048c..5c3bd1e0c3 100644 --- a/packages/reshader.gl/src/Geometry.ts +++ b/packages/reshader.gl/src/Geometry.ts @@ -56,7 +56,7 @@ const REF_COUNT_KEY = '_reshader_refCount'; export class AbstractGeometry { static createElementBuffer(device: any, elements: any): any { if (device instanceof GraphicsDevice) { - return createGPUBuffer(device, elements, GPUBufferUsage.VERTEX, 'index buffer'); + return createGPUBuffer(device, elements, GPUBufferUsage.INDEX, 'index buffer'); } else { // regl return device.elements(elements); From 66cac307614311cfabc8143d653d3d0071006b83 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Thu, 16 Jan 2025 14:30:20 +0800 Subject: [PATCH 09/53] merge GL Classes and GPUClasses, add device in getCommandKey and fix bundling --- packages/reshader.gl/src/Geometry.ts | 138 +++--- packages/reshader.gl/src/InstancedMesh.ts | 4 +- packages/reshader.gl/src/Material.ts | 114 +++-- packages/reshader.gl/src/Mesh.ts | 4 +- packages/reshader.gl/src/Renderer.ts | 8 +- packages/reshader.gl/src/Texture2D.ts | 11 +- packages/reshader.gl/src/common/GLTFBundle.ts | 1 - packages/reshader.gl/src/common/Util.ts | 13 + .../reshader.gl/src/shader/ImageShader.js | 2 +- packages/reshader.gl/src/shader/MeshShader.js | 27 +- packages/reshader.gl/src/shader/Shader.ts | 397 +++++++++--------- 11 files changed, 363 insertions(+), 356 deletions(-) diff --git a/packages/reshader.gl/src/Geometry.ts b/packages/reshader.gl/src/Geometry.ts index 5c3bd1e0c3..00e608011c 100644 --- a/packages/reshader.gl/src/Geometry.ts +++ b/packages/reshader.gl/src/Geometry.ts @@ -1,6 +1,6 @@ import { vec3, vec4 } from 'gl-matrix'; import { packTangentFrame, buildTangents, buildNormals } from '@maptalks/tbn-packer'; -import { isNumber, extend, isArray, isSupportVAO, hasOwn, getBufferSize, isInStride, isInterleaved } from './common/Util'; +import { isNumber, extend, isArray, isSupportVAO, hasOwn, getBufferSize, isInStride, isInterleaved, getArrayCtor } from './common/Util'; import BoundingBox from './BoundingBox'; import { KEY_DISPOSED } from './common/Constants'; import { getGLTFLoaderBundle } from './common/GLTFBundle' @@ -53,7 +53,8 @@ function GUID() { const REF_COUNT_KEY = '_reshader_refCount'; -export class AbstractGeometry { +export default class Geometry { + static createElementBuffer(device: any, elements: any): any { if (device instanceof GraphicsDevice) { return createGPUBuffer(device, elements, GPUBufferUsage.INDEX, 'index buffer'); @@ -205,6 +206,10 @@ export class AbstractGeometry { } } + getBuffer(name: string) { + return this.data[name] && this.data[name].buffer; + } + getAttrData(activeAttributes: ActiveAttributes) { const key = activeAttributes.key; const updated = !this._reglData || !this._reglData[key]; @@ -342,7 +347,7 @@ export class AbstractGeometry { const allocatedBuffers = this._buffers; for (const p in allocatedBuffers) { if (!allocatedBuffers[p].buffer) { - allocatedBuffers[p].buffer = AbstractGeometry.createBuffer(device, allocatedBuffers[p].data, p); + allocatedBuffers[p].buffer = Geometry.createBuffer(device, allocatedBuffers[p].data, p); } delete allocatedBuffers[p].data; } @@ -369,7 +374,7 @@ export class AbstractGeometry { const dimension = arr.length / vertexCount; const info = data[key].data ? data[key] : { data: data[key] }; info.dimension = dimension; - const buffer = AbstractGeometry.createBuffer(device, info, key); + const buffer = Geometry.createBuffer(device, info, key); buffer[REF_COUNT_KEY] = 1; buffers[key] = { buffer @@ -638,6 +643,7 @@ export class AbstractGeometry { } dispose() { + this._deleteVAO(); this._forEachBuffer(buffer => { if (!buffer[KEY_DISPOSED]) { let refCount = buffer[REF_COUNT_KEY]; @@ -673,6 +679,14 @@ export class AbstractGeometry { this._disposed = true; } + //@internal + _deleteVAO() { + for (const p in this._vao) { + this._vao[p].vao.destroy(); + } + this._vao = {}; + } + isDisposed() { return !!this._disposed; } @@ -874,7 +888,8 @@ export class AbstractGeometry { throw new Error(name + ' must be array to build unique vertex.'); } oldData[name] = attr; - data[name] = new attr.constructor(l * size); + const ctor = getArrayCtor(attr); + data[name] = new ctor(l * size); } let cursor = 0; @@ -956,62 +971,25 @@ export class AbstractGeometry { this._version++; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getBufferDescriptor(_vertexInfo) { - return []; - } -} - -function getElementLength(elements) { - if (isNumber(elements)) { - return elements; - } else if (elements.count !== undefined) { - // object buffer form - return elements.count; - } else if (elements.destroy) { - if (elements['_elements']) { - // a regl element buffer - return elements['_elements'].vertCount; - } else { - //GPUBuffer - return elements.itemCount; - } - } else if (elements.length !== undefined) { - return elements.length; - } else if (elements.data) { - return elements.data.length; - } - throw new Error('invalid elements length'); -} - -function getTypeCtor(arr: NumberArray, byteWidth: number) { - if (arr instanceof Uint8Array || arr instanceof Uint16Array || arr instanceof Uint32Array || arr instanceof Uint8ClampedArray) { - return byteWidth === 1 ? Uint8Array : byteWidth === 2 ? Uint16Array : Uint32Array; - } - if (arr instanceof Int8Array || arr instanceof Int16Array || arr instanceof Int32Array) { - return byteWidth === 1 ? Int8Array : byteWidth === 2 ? Int16Array : Int32Array; - } - if (arr instanceof Float32Array || arr instanceof Float64Array) { - return byteWidth === 4 ? Float32Array : Float64Array; - } - return null; -} - -export class GPUGeometry extends AbstractGeometry { - getCommandKey() { - // 因为attribute组织方式会影响pipeline的创建,所以attributes组织方式不同时,需要创建不同的command key - // 我们这里暂时不考虑Geometry会更新attributes组织方式的情况,因为在maptalks的使用场景不会出现 - // 可能有些attribute没有被wgsl使用,但组织方式不同,这里也不考虑这种情况 - const keys = []; - for (const p in this.data) { - const attr = this.data[p]; - if (attr.byteStride) { - keys.push(`${p}(${attr.byteOffset}/${attr.byteStride}})`); - } else { - keys.push(p); + getCommandKey(device: any) { + if (device && device.wgpu) { + // 因为attribute组织方式会影响pipeline的创建,所以attributes组织方式不同时,需要创建不同的command key + // 我们这里暂时不考虑Geometry会更新attributes组织方式的情况,因为在maptalks的使用场景不会出现 + // 可能有些attribute没有被wgsl使用,但组织方式不同,这里也不考虑这种情况 + const keys = []; + for (const p in this.data) { + const attr = this.data[p]; + if (attr.byteStride) { + keys.push(`${p}(${attr.byteOffset}/${attr.byteStride}})`); + } else { + keys.push(p); + } } + return keys.sort().join('-'); + } else { + // regl + return ''; } - return keys.sort().join('-'); } getBufferDescriptor(vertexInfo) { @@ -1077,23 +1055,39 @@ export class GPUGeometry extends AbstractGeometry { } } -export default class Geometry extends AbstractGeometry { - getCommandKey() { - return ''; +function getElementLength(elements) { + if (isNumber(elements)) { + return elements; + } else if (elements.count !== undefined) { + // object buffer form + return elements.count; + } else if (elements.destroy) { + if (elements['_elements']) { + // a regl element buffer + return elements['_elements'].vertCount; + } else { + //GPUBuffer + return elements.itemCount; + } + } else if (elements.length !== undefined) { + return elements.length; + } else if (elements.data) { + return elements.data.length; } + throw new Error('invalid elements length'); +} - dispose() { - this._deleteVAO(); - super.dispose(); +function getTypeCtor(arr: NumberArray, byteWidth: number) { + if (arr instanceof Uint8Array || arr instanceof Uint16Array || arr instanceof Uint32Array || arr instanceof Uint8ClampedArray) { + return byteWidth === 1 ? Uint8Array : byteWidth === 2 ? Uint16Array : Uint32Array; } - - //@internal - _deleteVAO() { - for (const p in this._vao) { - this._vao[p].vao.destroy(); - } - this._vao = {}; + if (arr instanceof Int8Array || arr instanceof Int16Array || arr instanceof Int32Array) { + return byteWidth === 1 ? Int8Array : byteWidth === 2 ? Int16Array : Int32Array; } + if (arr instanceof Float32Array || arr instanceof Float64Array) { + return byteWidth === 4 ? Float32Array : Float64Array; + } + return null; } function getItemBytes(data) { diff --git a/packages/reshader.gl/src/InstancedMesh.ts b/packages/reshader.gl/src/InstancedMesh.ts index cdcb1c3e07..b2768a1335 100644 --- a/packages/reshader.gl/src/InstancedMesh.ts +++ b/packages/reshader.gl/src/InstancedMesh.ts @@ -107,8 +107,8 @@ export default class InstancedMesh extends Mesh { return defines; } - getCommandKey() { - return 'i_' + super.getCommandKey(); + getCommandKey(device: any) { + return 'i_' + super.getCommandKey(device); } //因为 updateBoundingBox 需要, 不再自动生成buffer,而是把原有的buffer销毁 diff --git a/packages/reshader.gl/src/Material.ts b/packages/reshader.gl/src/Material.ts index df9de34969..a94bec0010 100644 --- a/packages/reshader.gl/src/Material.ts +++ b/packages/reshader.gl/src/Material.ts @@ -1,14 +1,14 @@ import Eventable from './common/Eventable'; import { isNil, extendWithoutNil, hasOwn, getTexMemorySize } from './common/Util'; import AbstractTexture from './AbstractTexture'; -import { ERROR_NOT_IMPLEMENTED, KEY_DISPOSED } from './common/Constants'; +import { KEY_DISPOSED } from './common/Constants'; import { ShaderUniforms, ShaderUniformValue, ShaderDefines } from './types/typings'; -import { Regl, Texture } from '@maptalks/regl'; +import { Texture } from '@maptalks/regl'; import Geometry from './Geometry'; class Base {} -export class AbstractMaterial extends Eventable(Base) { +export default class Material extends Eventable(Base) { uniforms: ShaderUniforms refCount: number // 如果unlit,则不产生阴影(但接受阴影) @@ -75,6 +75,54 @@ export class AbstractMaterial extends Eventable(Base) { return !!this.uniforms.doubleSided; } + getUniforms(device: any) { + if (this._reglUniforms && !this.isDirty()) { + return this._reglUniforms; + } + const uniforms = this.uniforms; + const realUniforms: ShaderUniforms = {}; + for (const p in uniforms) { + if (this.isTexture(p)) { + Object.defineProperty(realUniforms, p, { + enumerable: true, + configurable: true, + get: function () { + return (uniforms[p] as AbstractTexture).getREGLTexture(device); + } + }); + } else { + const descriptor = Object.getOwnPropertyDescriptor(uniforms, p); + if (descriptor.get) { + Object.defineProperty(realUniforms, p, { + enumerable: true, + configurable: true, + get: function () { + return uniforms[p]; + } + }); + } else { + realUniforms[p] = uniforms[p]; + } + } + } + this._reglUniforms = realUniforms; + this._uniformVer = this.version; + return realUniforms; + } + + getMemorySize() { + const uniforms = this.uniforms; + let size = 0; + for (const p in uniforms) { + if (this.isTexture(p)) { + size += (uniforms[p] as AbstractTexture).getMemorySize(); + } else if (this.uniforms[p] && (this.uniforms[p] as any).destroy) { + size += getTexMemorySize(this.uniforms[p] as Texture); + } + } + return size; + } + isReady() { return this._loadingCount <= 0; } @@ -251,64 +299,4 @@ export class AbstractMaterial extends Eventable(Base) { _incrVersion() { this._version++; } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getUniforms(_regl?: any) { - throw new Error(ERROR_NOT_IMPLEMENTED); - } - - getMemorySize() { - throw new Error(ERROR_NOT_IMPLEMENTED); - } - -} - -export default class Material extends AbstractMaterial { - getUniforms(device: any) { - if (this._reglUniforms && !this.isDirty()) { - return this._reglUniforms; - } - const uniforms = this.uniforms; - const realUniforms: ShaderUniforms = {}; - for (const p in uniforms) { - if (this.isTexture(p)) { - Object.defineProperty(realUniforms, p, { - enumerable: true, - configurable: true, - get: function () { - return (uniforms[p] as AbstractTexture).getREGLTexture(device); - } - }); - } else { - const descriptor = Object.getOwnPropertyDescriptor(uniforms, p); - if (descriptor.get) { - Object.defineProperty(realUniforms, p, { - enumerable: true, - configurable: true, - get: function () { - return uniforms[p]; - } - }); - } else { - realUniforms[p] = uniforms[p]; - } - } - } - this._reglUniforms = realUniforms; - this._uniformVer = this.version; - return realUniforms; - } - - getMemorySize() { - const uniforms = this.uniforms; - let size = 0; - for (const p in uniforms) { - if (this.isTexture(p)) { - size += (uniforms[p] as AbstractTexture).getMemorySize(); - } else if (this.uniforms[p] && (this.uniforms[p] as any).destroy) { - size += getTexMemorySize(this.uniforms[p] as Texture); - } - } - return size; - } } diff --git a/packages/reshader.gl/src/Mesh.ts b/packages/reshader.gl/src/Mesh.ts index ae57f23533..ac7442d0e3 100644 --- a/packages/reshader.gl/src/Mesh.ts +++ b/packages/reshader.gl/src/Mesh.ts @@ -303,10 +303,10 @@ export default class Mesh { } //eslint-disable-next-line - getCommandKey(): string { + getCommandKey(device: any): string { if (!this._commandKey || this.dirtyDefines || (this._material && this._materialKeys !== this._material.getUniformKeys())) { //TODO geometry的data变动也可能会改变commandKey,但鉴于geometry一般不会发生变化,暂时不管 - let dKey = this.geometry.getCommandKey() + '_' + this._getDefinesKey(); + let dKey = this.geometry.getCommandKey(device) + '_' + this._getDefinesKey(); const elementType = isNumber(this.getElements()) ? 'count' : 'elements'; dKey += '_' + elementType; dKey += '_' + +(!!this.disableVAO); diff --git a/packages/reshader.gl/src/Renderer.ts b/packages/reshader.gl/src/Renderer.ts index 9170ea40c1..ef4017270d 100644 --- a/packages/reshader.gl/src/Renderer.ts +++ b/packages/reshader.gl/src/Renderer.ts @@ -1,4 +1,4 @@ -import REGL, { Regl, Uniforms } from "@maptalks/regl"; +import REGL, { Uniforms } from "@maptalks/regl"; import Scene from "./Scene"; const EMPTY_UNIFORMS = {}; @@ -21,10 +21,10 @@ class Renderer { let count = 0; if (scene) { const { opaques, transparents } = scene.getSortedMeshes(); - count += shader.draw(this.device, opaques); - count += shader.draw(this.device, transparents); + count += shader.draw(this.device, uniforms, opaques); + count += shader.draw(this.device, uniforms, transparents); } else { - count += shader.draw(this.device); + count += shader.draw(this.device, uniforms); } return count; } diff --git a/packages/reshader.gl/src/Texture2D.ts b/packages/reshader.gl/src/Texture2D.ts index 7fadb4476c..513a5ec779 100644 --- a/packages/reshader.gl/src/Texture2D.ts +++ b/packages/reshader.gl/src/Texture2D.ts @@ -4,13 +4,13 @@ import { default as Texture, REF_COUNT_KEY } from './AbstractTexture'; import { getUniqueTexture } from './common/REGLHelper'; import REGL, { Regl } from '@maptalks/regl'; import DataUtils from './common/DataUtils'; -import { ERROR_NOT_IMPLEMENTED, KEY_DISPOSED } from './common/Constants'; +import { KEY_DISPOSED } from './common/Constants'; /** * config properties: * https://github.com/regl-project/regl/blob/gh-pages/API.md#textures */ -export class AbstractTexture2D extends Texture { +export default class Texture2D extends Texture { onLoad({ data }) { const config = this.config; @@ -45,13 +45,6 @@ export class AbstractTexture2D extends Texture { this._update(); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - _checkNPOT(_regl?: any) { - throw new Error(ERROR_NOT_IMPLEMENTED); - } -} - -export default class Texture2D extends AbstractTexture2D { //@internal _update() { if (this._texture && !this._texture[KEY_DISPOSED]) { diff --git a/packages/reshader.gl/src/common/GLTFBundle.ts b/packages/reshader.gl/src/common/GLTFBundle.ts index bf2871b02c..ccf5636c2e 100644 --- a/packages/reshader.gl/src/common/GLTFBundle.ts +++ b/packages/reshader.gl/src/common/GLTFBundle.ts @@ -2,7 +2,6 @@ const getGlobal = function () { if (typeof globalThis !== 'undefined') { return globalThis; } if (typeof self !== 'undefined') { return self; } if (typeof window !== 'undefined') { return window; } - if (typeof global !== 'undefined') { return global; } throw new Error('unable to locate global object'); }; diff --git a/packages/reshader.gl/src/common/Util.ts b/packages/reshader.gl/src/common/Util.ts index b736f1eada..6ad509f2a1 100644 --- a/packages/reshader.gl/src/common/Util.ts +++ b/packages/reshader.gl/src/common/Util.ts @@ -160,6 +160,19 @@ export function isArray(arr) { (arr instanceof Float64Array); } +export function getArrayCtor(arr) { + return Array.isArray(arr) && Array || + (arr instanceof Uint8Array) && Uint8Array || + (arr instanceof Int8Array) && Int8Array || + (arr instanceof Uint16Array) && Uint16Array || + (arr instanceof Int16Array) && Int16Array || + (arr instanceof Uint32Array) && Uint32Array || + (arr instanceof Int32Array) && Int32Array || + (arr instanceof Uint8ClampedArray) && Uint8ClampedArray || + (arr instanceof Float32Array) && Float32Array || + (arr instanceof Float64Array) && Float64Array; +} + /** * 对两个矢量执行线性推算 * diff --git a/packages/reshader.gl/src/shader/ImageShader.js b/packages/reshader.gl/src/shader/ImageShader.js index 14f269b9d0..8cdcf89531 100644 --- a/packages/reshader.gl/src/shader/ImageShader.js +++ b/packages/reshader.gl/src/shader/ImageShader.js @@ -26,7 +26,7 @@ class ImageShader extends MeshShader { } getMeshCommand(regl, mesh) { - const key = mesh.getCommandKey(); + const key = mesh.getCommandKey(regl); if (!this.commands['image_' + key]) { this.commands['image_' + key] = this.createMeshCommand(regl, mesh); } diff --git a/packages/reshader.gl/src/shader/MeshShader.js b/packages/reshader.gl/src/shader/MeshShader.js index 985e58595a..bf84d7d392 100644 --- a/packages/reshader.gl/src/shader/MeshShader.js +++ b/packages/reshader.gl/src/shader/MeshShader.js @@ -1,9 +1,8 @@ -import Shader, { GPUShader } from './Shader'; -import InstancedMesh from '../InstancedMesh'; +import Shader from './Shader'; class MeshShader extends Shader { - draw(regl, meshes) { + draw(device, shaderUniforms, meshes) { if (!meshes || !meshes.length) { return 0; } @@ -18,21 +17,21 @@ class MeshShader extends Shader { continue; } if (!meshes[i].geometry.getDrawCount() || !this._runFilter(meshes[i])) { - //此处regl有个潜在的bug: - //如果count为0的geometry不过滤掉,regl生成的函数中,bind的texture不会执行unbind + //此处device有个潜在的bug: + //如果count为0的geometry不过滤掉,device生成的函数中,bind的texture不会执行unbind if (i === l - 1 && preCommand && props.length) { preCommand(props); } continue; } - const v = meshes[i].getRenderProps(regl, command.activeAttributes); + const v = meshes[i].getRenderProps(device, command.activeAttributes); this._ensureContextDefines(v); v.shaderContext = this.context; v.meshObject = meshes[i]; - this.appendDescUniforms(regl, v); + this.appendDescUniforms(device, v); - const command = this.getMeshCommand(regl, meshes[i], v); + const command = this.getMeshCommand(device, meshes[i], v); //run command one by one, for debug // const props = extend({}, this.context, meshes[i].getRenderProps()); @@ -41,7 +40,7 @@ class MeshShader extends Shader { if (props.length && preCommand !== command) { //batch mode - this.run(preCommand, props); + this.run(device, command, shaderUniforms, props); props.length = 0; } @@ -51,7 +50,7 @@ class MeshShader extends Shader { if (i < l - 1) { preCommand = command; } else if (i === l - 1) { - this.run(command, props); + this.run(device, command, shaderUniforms, props); } } return count; @@ -112,7 +111,7 @@ class MeshShader extends Shader { return filters(m); } - getMeshCommand(regl, mesh, uniformValues) { + getMeshCommand(device, mesh, uniformValues) { if (!this._cmdKeys) { this._cmdKeys = {}; } @@ -121,12 +120,12 @@ class MeshShader extends Shader { if (material) { doubleSided = material.doubleSided; } - const key = this.getShaderCommandKey(mesh, uniformValues, doubleSided); + const key = this.getShaderCommandKey(device, mesh, uniformValues, doubleSided); let storedKeys = this._cmdKeys[key]; if (!storedKeys) { storedKeys = this._cmdKeys[key] = {}; } - const meshKey = mesh.getCommandKey(); + const meshKey = mesh.getCommandKey(device); if (!storedKeys[meshKey]) { storedKeys[meshKey] = key + '_' + mesh.getCommandKey(); } @@ -141,7 +140,7 @@ class MeshShader extends Shader { if (doubleSided && this.extraCommandProps) { commandProps.cull = { enable: false }; } - command = this.commands[dKey] = this.createMeshCommand(regl, mesh, commandProps); + command = this.commands[dKey] = this.createMeshCommand(device, mesh, commandProps); } return command; } diff --git a/packages/reshader.gl/src/shader/Shader.ts b/packages/reshader.gl/src/shader/Shader.ts index def7fb4d3d..042762a8b9 100644 --- a/packages/reshader.gl/src/shader/Shader.ts +++ b/packages/reshader.gl/src/shader/Shader.ts @@ -5,10 +5,9 @@ import { KEY_DISPOSED } from '../common/Constants.js'; import { ShaderUniformValue } from '../types/typings'; import PipelineDescriptor from '../webgpu/common/PipelineDesc'; import InstancedMesh from '../InstancedMesh'; -import Mesh, { GPUMesh } from '../Mesh'; +import Mesh from '../Mesh'; import DynamicBuffer from '../webgpu/DynamicBuffer'; import CommandBuilder from '../webgpu/CommandBuilder'; -import GraphicsDevice from '../webgpu/GraphicsDevice'; const UNIFORM_TYPE = { @@ -20,10 +19,11 @@ let uid = 0; const activeVarsCache = {}; -export class AbstractShader { +export class GLShader { vert: string; frag: string; uid: number; + version: number; //@internal uniforms: ShaderUniformValue[]; //@internal @@ -108,195 +108,13 @@ export class AbstractShader { return this; } - /** - * Get shader's context uniforms values - * @param {Object} meshProps - mesh uniforms - */ - appendDescUniforms(regl, meshProps) { - // const context = this.context; - //TODO 这里以前是extend2,需要明确改用extend后是否会有bug - // const props = extend(meshProps, context); - const uniforms = meshProps; - const desc = this.contextDesc; - for (const p in desc) { - if (!desc[p]) { - continue; - } - if (desc[p].type === 'array') { - //an array uniform's value - const name = p, len = desc[p].length; - // change uniform value to the following form as regl requires: - // foo[0]: 'value' - // foo[1]: 'value' - // foo[2]: 'value' - let values = meshProps[p]; - if (desc[p].fn) { - // an array function - values = desc[p].fn(null, meshProps); - } - if (!values) { - continue; - } - if (values.length !== len) { - throw new Error(`${name} uniform's length is not ${len}`); - } - uniforms[name] = uniforms[name] || {}; - for (let i = 0; i < len; i++) { - uniforms[name][`${i}`] = values[i].getREGLTexture ? values[i].getREGLTexture(regl) : values[i]; - } - } else if (desc[p].type === 'function') { - if (!Object.getOwnPropertyDescriptor(uniforms, p)) { - Object.defineProperty(uniforms, p, { - configurable: false, - enumerable: true, - get: function () { - return desc[p].fn(null, meshProps); - } - }); - } - - } - } - - return uniforms; - } - - /** - * Set or get context uniform to or from the shader - * @returns {this} - */ - setUniforms(uniforms) { - if (uniforms['modelMatrix'] || uniforms['positionMatrix']) { - throw new Error('modelMatrix or positionMatrix is reserved uniform name for Mesh, please change to another name'); - } - this.contextKeys = uniforms ? Object.keys(uniforms).join() : null; - this.context = uniforms; - return this; - } - - - _compileSource() { - this.vert = ShaderLib.compile(this.vert); - this.frag = ShaderLib.compile(this.frag); - } -} - -function parseArrayName(p) { - const l = p.indexOf('['), r = p.indexOf(']'); - const name = p.substring(0, l), len = +p.substring(l + 1, r); - return { name, len }; -} - -const pipelineDesc = new PipelineDescriptor(); - -export class GPUShader extends AbstractShader { - gpuCommands: any[]; - //@internal - _presentationFormat: GPUTextureFormat; - _bindGroupCache: Record; - _buffers: Record; - - getShaderCommandKey(mesh, uniformValues, doubleSided) { - // 获取pipeline所需要的特征变量,即任何变量发生变化后,就需要创建新的pipeline - const commandProps = this.extraCommandProps; - pipelineDesc.readFromREGLCommand(commandProps, mesh, uniformValues, doubleSided); - return pipelineDesc.getSignatureKey(); - } - - createMeshCommand(device: GPUDevice, mesh: Mesh) { - // 生成期: - // 1. 负责对 wgsl 做预处理,生成最终执行的wgsl代码 - // 2. 解析wgsl,生成 bind group mapping 信息,用于运行时,mesh生成bind group - // 3. 解析wgsl,获得全局 uniform 变量名和类型 - // 4. 生成 layout 和 pipeline - - // preprocess vert and frag codes - const builder = new CommandBuilder(device, this.vert, this.frag, mesh) - return builder.build(pipelineDesc); - } - - run(device: GraphicsDevice, command, shaderUniforms, props, context) { - const buffersPool = device.dynamicBufferPool; - const passEncoder: GPURenderPassEncoder = context.passEncoder; - passEncoder.setPipeline(command.pipeline); - - const { key, bindGroupFormat, pipeline, vertexInfo } = command; - const layout = pipeline.getBindGroupLayout(0); - // 1. 生成shader uniform 需要的dynamic buffer - let shaderBuffer = this._buffers[key] as DynamicBuffer; - if (shaderBuffer) { - shaderBuffer = this._buffers[key] = new DynamicBuffer(bindGroupFormat.getShaderUniforms(), buffersPool); - } - // 向buffer中填入shader uniform值 - shaderBuffer.writeBuffer(shaderUniforms); - for (let i = 0; i < props.length; i++) { - const mesh = props[i].meshObject as GPUMesh; - // 获取mesh的dynamicBuffer - const meshBuffer = mesh.writeDynamicBuffer(props[i], bindGroupFormat.getMeshUniforms(), buffersPool); - const groupKey = meshBuffer.version + '-' + shaderBuffer.version; - // 获取或者生成bind group - let bindGroup = this._bindGroupCache[groupKey]; - if (!bindGroup) { - bindGroup = bindGroupFormat.createBindGroup(device, mesh, layout, shaderBuffer, meshBuffer); - // 缓存bind group,只要buffer没有发生变化,即可以重用 - // TODO 可以考虑每帧开始把缓存 bind group 标记为 retire,每帧结束时把不是 current 的 bind group 销毁掉 - this._bindGroupCache[groupKey] = bindGroup; - } - - // 获取 dynamicOffsets - const dynamicOffsets = shaderBuffer.dynamicOffsets.concat(meshBuffer.dynamicOffsets); - passEncoder.setBindGroup(0, bindGroup, dynamicOffsets); - - for (const vertex of vertexInfo) { - const vertexBuffer = mesh.geometry.getVertexBuffer(vertex.name); - passEncoder.setVertexBuffer(vertex.index, vertexBuffer); - } - - //TODO InstancedMesh 的参数 - const elements = mesh.getElements(); - const drawOffset = mesh.geometry.getDrawOffset(); - const drawCount = mesh.geometry.getDrawCount(); - if (isNumber(elements)) { - passEncoder.draw(drawCount, 1, drawOffset); - } else { - passEncoder.setIndexBuffer(elements.getBuffer(), elements.getFormat()); - passEncoder.drawIndexed(drawCount, 1, drawOffset); - } - } - passEncoder.end(); - } - - _deleteCommand(command) { - if (command.bindGroupFormat) { - command.bindGroupFormat.dispose(); - } - } - - dispose() { - for (const key in this._buffers) { - if (this._buffers[key]) { - this._buffers[key].dispose(); - } - } - const commands = this.gpuCommands; - for (let i = 0; i < commands.length; i++) { - if (!commands[i]) { - continue; - } - this._deleteCommand(commands[i]); - } - } -} - -export default class Shader extends AbstractShader { - version: number; - - run(command, props) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + run(regl: any, command, shaderUniforms, props) { return command(props); } // eslint-disable-next-line @typescript-eslint/no-unused-vars - getShaderCommandKey() { + getShaderCommandKey(device, mesh, uniformValues, doubleSided) { return this.dkey || 'default'; } @@ -473,5 +291,208 @@ export default class Shader extends AbstractShader { delete this.vert; delete this.frag; } + + /** + * Get shader's context uniforms values + * @param {Object} meshProps - mesh uniforms + */ + appendDescUniforms(regl, meshProps) { + // const context = this.context; + //TODO 这里以前是extend2,需要明确改用extend后是否会有bug + // const props = extend(meshProps, context); + const uniforms = meshProps; + const desc = this.contextDesc; + for (const p in desc) { + if (!desc[p]) { + continue; + } + if (desc[p].type === 'array') { + //an array uniform's value + const name = p, len = desc[p].length; + // change uniform value to the following form as regl requires: + // foo[0]: 'value' + // foo[1]: 'value' + // foo[2]: 'value' + let values = meshProps[p]; + if (desc[p].fn) { + // an array function + values = desc[p].fn(null, meshProps); + } + if (!values) { + continue; + } + if (values.length !== len) { + throw new Error(`${name} uniform's length is not ${len}`); + } + uniforms[name] = uniforms[name] || {}; + for (let i = 0; i < len; i++) { + uniforms[name][`${i}`] = values[i].getREGLTexture ? values[i].getREGLTexture(regl) : values[i]; + } + } else if (desc[p].type === 'function') { + if (!Object.getOwnPropertyDescriptor(uniforms, p)) { + Object.defineProperty(uniforms, p, { + configurable: false, + enumerable: true, + get: function () { + return desc[p].fn(null, meshProps); + } + }); + } + + } + } + + return uniforms; + } + + /** + * Set or get context uniform to or from the shader + * @returns {this} + */ + setUniforms(uniforms) { + if (uniforms['modelMatrix'] || uniforms['positionMatrix']) { + throw new Error('modelMatrix or positionMatrix is reserved uniform name for Mesh, please change to another name'); + } + this.contextKeys = uniforms ? Object.keys(uniforms).join() : null; + this.context = uniforms; + return this; + } + + + _compileSource() { + this.vert = ShaderLib.compile(this.vert); + this.frag = ShaderLib.compile(this.frag); + } +} + +function parseArrayName(p) { + const l = p.indexOf('['), r = p.indexOf(']'); + const name = p.substring(0, l), len = +p.substring(l + 1, r); + return { name, len }; +} + +const pipelineDesc = new PipelineDescriptor(); + +export default class GPUShader extends GLShader { + //@internal + gpuCommands: any[]; + //@internal + isGPU: boolean; + //@internal + _presentationFormat: GPUTextureFormat; + //@internal + _bindGroupCache: Record; + //@internal + _buffers: Record; + //@internal + _passEncoders: Record; + //@internal + _currentPassEncoder: GPURenderPassEncoder + + getShaderCommandKey(device, mesh, uniformValues, doubleSided) { + if (device && device.wgpu) { + // 获取pipeline所需要的特征变量,即任何变量发生变化后,就需要创建新的pipeline + const commandProps = this.extraCommandProps; + pipelineDesc.readFromREGLCommand(commandProps, mesh, uniformValues, doubleSided); + return pipelineDesc.getSignatureKey(); + } else { + // regl + return super.getShaderCommandKey(device, mesh, uniformValues, doubleSided); + } + } + + createMeshCommand(device: any, mesh: Mesh) { + if (device && device.wgpu) { + // 生成期: + // 1. 负责对 wgsl 做预处理,生成最终执行的wgsl代码 + // 2. 解析wgsl,生成 bind group mapping 信息,用于运行时,mesh生成bind group + // 3. 解析wgsl,获得全局 uniform 变量名和类型 + // 4. 生成 layout 和 pipeline + + // preprocess vert and frag codes + const builder = new CommandBuilder(device, this.vert, this.frag, mesh) + return builder.build(pipelineDesc); + } else { + // regl + return super.createMeshCommand(device, mesh); + } + } + + run(device: any, command, shaderUniforms, props) { + if (!device.wgpu) { + // regl command + return super.run(device, command, shaderUniforms, props); + } + this.isGPU = true; + const buffersPool = device.dynamicBufferPool; + const passEncoder: GPURenderPassEncoder = this._currentPassEncoder; + passEncoder.setPipeline(command.pipeline); + + const { key, bindGroupFormat, pipeline, vertexInfo } = command; + const layout = pipeline.getBindGroupLayout(0); + // 1. 生成shader uniform 需要的dynamic buffer + let shaderBuffer = this._buffers[key] as DynamicBuffer; + if (shaderBuffer) { + shaderBuffer = this._buffers[key] = new DynamicBuffer(bindGroupFormat.getShaderUniforms(), buffersPool); + } + // 向buffer中填入shader uniform值 + shaderBuffer.writeBuffer(shaderUniforms); + for (let i = 0; i < props.length; i++) { + const mesh = props[i].meshObject as Mesh; + // 获取mesh的dynamicBuffer + const meshBuffer = mesh.writeDynamicBuffer(props[i], bindGroupFormat.getMeshUniforms(), buffersPool); + const groupKey = meshBuffer.version + '-' + shaderBuffer.version; + // 获取或者生成bind group + let bindGroup = this._bindGroupCache[groupKey]; + if (!bindGroup) { + bindGroup = bindGroupFormat.createBindGroup(device, mesh, layout, shaderBuffer, meshBuffer); + // 缓存bind group,只要buffer没有发生变化,即可以重用 + // TODO 可以考虑每帧开始把缓存 bind group 标记为 retire,每帧结束时把不是 current 的 bind group 销毁掉 + this._bindGroupCache[groupKey] = bindGroup; + } + + // 获取 dynamicOffsets + const dynamicOffsets = shaderBuffer.dynamicOffsets.concat(meshBuffer.dynamicOffsets); + passEncoder.setBindGroup(0, bindGroup, dynamicOffsets); + + for (const vertex of vertexInfo) { + const vertexBuffer = mesh.geometry.getBuffer(vertex.name); + passEncoder.setVertexBuffer(vertex.index, vertexBuffer); + } + + //TODO InstancedMesh 的参数 + const elements = mesh.getElements(); + const drawOffset = mesh.geometry.getDrawOffset(); + const drawCount = mesh.geometry.getDrawCount(); + if (isNumber(elements)) { + passEncoder.draw(drawCount, 1, drawOffset); + } else { + passEncoder.setIndexBuffer(elements.getBuffer(), elements.getFormat()); + passEncoder.drawIndexed(drawCount, 1, drawOffset); + } + } + passEncoder.end(); + } + + dispose() { + if (!this.isGPU) { + super.dispose(); + return; + } + for (const key in this._buffers) { + if (this._buffers[key]) { + this._buffers[key].dispose(); + } + } + const commands = this.gpuCommands; + for (let i = 0; i < commands.length; i++) { + if (!commands[i]) { + continue; + } + if (commands[i].bindGroupFormat) { + commands[i].bindGroupFormat.dispose(); + } + } + } } From bb07942189270dbc421e093d9079db2e0ae2b239 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Thu, 16 Jan 2025 14:46:21 +0800 Subject: [PATCH 10/53] fix reshader.gl --- packages/gl/src/layer/GroundPainter.js | 2 +- packages/gl/src/layer/HeatmapProcess.js | 8 ++++---- packages/gl/src/layer/shadow/ShadowProcess.js | 2 +- packages/gl/src/layer/weather/RainPainter.js | 2 +- packages/reshader.gl/src/InstancedMesh.ts | 4 ++-- packages/reshader.gl/src/Mesh.ts | 13 ++++++++----- packages/reshader.gl/src/shader/ExtentPass.js | 10 +++++----- packages/reshader.gl/src/shader/MeshShader.js | 3 ++- packages/reshader.gl/src/shadow/ShadowPass.js | 2 +- .../vt/src/layer/plugins/painters/WaterPainter.js | 2 +- .../layer/plugins/painters/pbr/StencilShadowPass.js | 2 +- 11 files changed, 27 insertions(+), 23 deletions(-) diff --git a/packages/gl/src/layer/GroundPainter.js b/packages/gl/src/layer/GroundPainter.js index 806c95ff5a..2a5c120fe4 100644 --- a/packages/gl/src/layer/GroundPainter.js +++ b/packages/gl/src/layer/GroundPainter.js @@ -340,7 +340,7 @@ class GroundPainter { ] ); planeGeo.createTangent(); - planeGeo.generateBuffers(this.renderer.regl); + planeGeo.generateBuffers(this.renderer.device); //TODO 还需要构造 tangent this._ground = new reshader.Mesh(planeGeo, null, { castShadow: false }); diff --git a/packages/gl/src/layer/HeatmapProcess.js b/packages/gl/src/layer/HeatmapProcess.js index e18d8f069f..f1cdf8bc09 100644 --- a/packages/gl/src/layer/HeatmapProcess.js +++ b/packages/gl/src/layer/HeatmapProcess.js @@ -24,7 +24,7 @@ export default class HeatmapProcess { render(scene, uniforms, fbo) { this._check(); const map = this._layer.getMap(); - this.renderer.regl.clear({ + this.renderer.device.clear({ color: EMPTY_COLOR, depth: 1, stencil: 0xFF, @@ -86,7 +86,7 @@ export default class HeatmapProcess { if (this._colorRampTex) { this._colorRampTex.destroy(); } - const regl = this.renderer.regl; + const regl = this.renderer.device; this._colorRampTex = regl.texture({ width: 256, height: 1, @@ -116,7 +116,7 @@ export default class HeatmapProcess { _createGround() { const planeGeo = new reshader.Plane(); - planeGeo.generateBuffers(this.renderer.regl); + planeGeo.generateBuffers(this.renderer.device); this._ground = new reshader.Mesh(planeGeo); this._groundScene = new reshader.Scene([this._ground]); } @@ -129,7 +129,7 @@ export default class HeatmapProcess { _createHeatmapTex() { const canvas = this._layer.getRenderer().canvas; - const regl = this.renderer.regl; + const regl = this.renderer.device; const colorType = regl.hasExtension('OES_texture_half_float') ? 'half float' : 'float'; const width = Math.ceil(canvas.width / 4); const height = Math.ceil(canvas.height / 4); diff --git a/packages/gl/src/layer/shadow/ShadowProcess.js b/packages/gl/src/layer/shadow/ShadowProcess.js index bfbd517904..f2f9db8bd2 100644 --- a/packages/gl/src/layer/shadow/ShadowProcess.js +++ b/packages/gl/src/layer/shadow/ShadowProcess.js @@ -219,7 +219,7 @@ class ShadowProcess { _createGround() { const planeGeo = new reshader.Plane(); - planeGeo.generateBuffers(this.renderer.regl); + planeGeo.generateBuffers(this.renderer.device); this._ground = new reshader.Mesh(planeGeo); this._groundScene = new reshader.Scene([this._ground]); } diff --git a/packages/gl/src/layer/weather/RainPainter.js b/packages/gl/src/layer/weather/RainPainter.js index 3645e25865..e30a9603c4 100644 --- a/packages/gl/src/layer/weather/RainPainter.js +++ b/packages/gl/src/layer/weather/RainPainter.js @@ -170,7 +170,7 @@ class RainPainer { uv0Attribute: 'TEXCOORD_0' } ); - geometry.generateBuffers(this.renderer.regl); + geometry.generateBuffers(this.renderer.device); const material = new reshader.Material({ rainMap: this._regl.texture({ width: 2, height: 2 }), diffuse: rainConfig.color || [1, 1, 1], diff --git a/packages/reshader.gl/src/InstancedMesh.ts b/packages/reshader.gl/src/InstancedMesh.ts index b2768a1335..65be2d7018 100644 --- a/packages/reshader.gl/src/InstancedMesh.ts +++ b/packages/reshader.gl/src/InstancedMesh.ts @@ -174,8 +174,8 @@ export default class InstancedMesh extends Mesh { return this; } - getRenderProps(regl: Regl, activeAttributes: ActiveAttributes) { - const props = super.getRenderProps(regl, activeAttributes); + getRenderProps(regl: Regl) { + const props = super.getRenderProps(regl); if (!isSupportVAO(regl)) { extend(props, this.instancedData); } diff --git a/packages/reshader.gl/src/Mesh.ts b/packages/reshader.gl/src/Mesh.ts index ac7442d0e3..923fdcc50a 100644 --- a/packages/reshader.gl/src/Mesh.ts +++ b/packages/reshader.gl/src/Mesh.ts @@ -334,12 +334,8 @@ export default class Mesh { // return uniforms; // } - getRenderProps(device: any, activeAttributes: ActiveAttributes) { + getRenderProps(device: any) { const props = this.getUniforms(device); - extend(props, this._getGeometryAttributes(device, activeAttributes)); - if (!isSupportVAO(device) || this.disableVAO) { - props.elements = this._geometry.getElements(); - } props.meshProperties = this.properties; props.geometryProperties = this._geometry.properties; props.meshConfig = this.config; @@ -355,6 +351,13 @@ export default class Mesh { return this._geometry.getREGLData(device, activeAttributes, this.disableVAO); } + appendGeoAttributes(props, device, activeAttributes) { + extend(props, this._getGeometryAttributes(device, activeAttributes)); + if (!isSupportVAO(device) || this.disableVAO) { + props.elements = this._geometry.getElements(); + } + } + getUniforms(device: any): ShaderUniforms { if (this._dirtyUniforms || this._dirtyGeometry || this._material && this._materialVer !== this._material.version) { this._uniformDescriptors = new Set(); diff --git a/packages/reshader.gl/src/shader/ExtentPass.js b/packages/reshader.gl/src/shader/ExtentPass.js index 1c347aade7..4ff5154fcb 100644 --- a/packages/reshader.gl/src/shader/ExtentPass.js +++ b/packages/reshader.gl/src/shader/ExtentPass.js @@ -17,8 +17,8 @@ class ExtentPass { } _init() { - this._maskColorFbo = this.renderer.regl.framebuffer({ - color: this.renderer.regl.texture({ + this._maskColorFbo = this.renderer.device.framebuffer({ + color: this.renderer.device.texture({ width: 1, height: 1, wrap: 'clamp', @@ -27,8 +27,8 @@ class ExtentPass { }), depth: true }); - this._maskModeFbo = this.renderer.regl.framebuffer({ - color: this.renderer.regl.texture({ + this._maskModeFbo = this.renderer.device.framebuffer({ + color: this.renderer.device.texture({ width: 1, height: 1, wrap: 'clamp', @@ -57,7 +57,7 @@ class ExtentPass { this._maskModeShader = new MeshShader({ vert, frag: maskModeExtent, - uniforms, + uniforms, extraCommandProps: { viewport: this._viewport, } diff --git a/packages/reshader.gl/src/shader/MeshShader.js b/packages/reshader.gl/src/shader/MeshShader.js index bf84d7d392..4d76f0fd3f 100644 --- a/packages/reshader.gl/src/shader/MeshShader.js +++ b/packages/reshader.gl/src/shader/MeshShader.js @@ -25,13 +25,14 @@ class MeshShader extends Shader { continue; } - const v = meshes[i].getRenderProps(device, command.activeAttributes); + const v = meshes[i].getRenderProps(device); this._ensureContextDefines(v); v.shaderContext = this.context; v.meshObject = meshes[i]; this.appendDescUniforms(device, v); const command = this.getMeshCommand(device, meshes[i], v); + meshes[i].appendGeoAttributes(v, device, command.activeAttributes); //run command one by one, for debug // const props = extend({}, this.context, meshes[i].getRenderProps()); diff --git a/packages/reshader.gl/src/shadow/ShadowPass.js b/packages/reshader.gl/src/shadow/ShadowPass.js index dce2d4dc78..d742a93b65 100644 --- a/packages/reshader.gl/src/shadow/ShadowPass.js +++ b/packages/reshader.gl/src/shadow/ShadowPass.js @@ -80,7 +80,7 @@ class ShadowPass { } _init(defines) { - const regl = this.renderer.regl; + const regl = this.renderer.device; const type = 'uint8'; const width = this.width, height = this.height; diff --git a/packages/vt/src/layer/plugins/painters/WaterPainter.js b/packages/vt/src/layer/plugins/painters/WaterPainter.js index f68ce0f04a..3660a66360 100644 --- a/packages/vt/src/layer/plugins/painters/WaterPainter.js +++ b/packages/vt/src/layer/plugins/painters/WaterPainter.js @@ -460,7 +460,7 @@ class WaterPainter extends BasicPainter { planeGeo.data.aTexCoord = new Uint8Array( [0, 1, 1, 1, 0, 0, 1, 0] ); - planeGeo.generateBuffers(this.renderer.regl); + planeGeo.generateBuffers(this.renderer.device); this._water = new reshader.Mesh(planeGeo, null, { castShadow: false }); this._waterScene = new reshader.Scene([this._water]); diff --git a/packages/vt/src/layer/plugins/painters/pbr/StencilShadowPass.js b/packages/vt/src/layer/plugins/painters/pbr/StencilShadowPass.js index fa469e9686..cf3bbf29a3 100644 --- a/packages/vt/src/layer/plugins/painters/pbr/StencilShadowPass.js +++ b/packages/vt/src/layer/plugins/painters/pbr/StencilShadowPass.js @@ -42,7 +42,7 @@ export default class StencilShadowPass { const shadow = new reshader.Geometry({ aPosition: data.vertices }, data.indices); - shadow.generateBuffers(this.renderer.regl); + shadow.generateBuffers(this.renderer.device); return [new reshader.Mesh(shadow)]; } From dfed96eb710ca48a124cf2d04e57fe17f8898fe2 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Thu, 16 Jan 2025 17:34:55 +0800 Subject: [PATCH 11/53] add GraphicsDevice.commandEncoder and Shader._getCurrentRenderPassEncoder --- packages/reshader.gl/src/Renderer.ts | 7 +- packages/reshader.gl/src/shader/Shader.ts | 24 +++++- .../reshader.gl/src/webgpu/GraphicsDevice.ts | 81 ++++++++++++++++++- 3 files changed, 105 insertions(+), 7 deletions(-) diff --git a/packages/reshader.gl/src/Renderer.ts b/packages/reshader.gl/src/Renderer.ts index ef4017270d..b01f1ab238 100644 --- a/packages/reshader.gl/src/Renderer.ts +++ b/packages/reshader.gl/src/Renderer.ts @@ -1,5 +1,6 @@ import REGL, { Uniforms } from "@maptalks/regl"; import Scene from "./Scene"; +import GraphicsDevice from "./webgpu/GraphicsDevice"; const EMPTY_UNIFORMS = {}; /** @@ -9,7 +10,11 @@ class Renderer { device: any constructor(device: any) { - this.device = device; + if (device.device) { + this.device = new GraphicsDevice(device.device, device.context); + } else { + this.device = device; + } } render(shader, uniforms: Uniforms, scene: Scene, framebuffer: REGL.Framebuffer) { diff --git a/packages/reshader.gl/src/shader/Shader.ts b/packages/reshader.gl/src/shader/Shader.ts index 042762a8b9..3f23b56c8c 100644 --- a/packages/reshader.gl/src/shader/Shader.ts +++ b/packages/reshader.gl/src/shader/Shader.ts @@ -8,6 +8,7 @@ import InstancedMesh from '../InstancedMesh'; import Mesh from '../Mesh'; import DynamicBuffer from '../webgpu/DynamicBuffer'; import CommandBuilder from '../webgpu/CommandBuilder'; +import GraphicsDevice from '../webgpu/GraphicsDevice'; const UNIFORM_TYPE = { @@ -418,14 +419,15 @@ export default class GPUShader extends GLShader { } } - run(device: any, command, shaderUniforms, props) { - if (!device.wgpu) { + run(deviceOrRegl: any, command, shaderUniforms, props) { + if (!deviceOrRegl.wgpu) { // regl command - return super.run(device, command, shaderUniforms, props); + return super.run(deviceOrRegl, command, shaderUniforms, props); } + const device = deviceOrRegl as GraphicsDevice; this.isGPU = true; const buffersPool = device.dynamicBufferPool; - const passEncoder: GPURenderPassEncoder = this._currentPassEncoder; + const passEncoder: GPURenderPassEncoder = this._getCurrentRenderPassEncoder(device); passEncoder.setPipeline(command.pipeline); const { key, bindGroupFormat, pipeline, vertexInfo } = command; @@ -474,6 +476,20 @@ export default class GPUShader extends GLShader { passEncoder.end(); } + _getCurrentRenderPassEncoder(device: GraphicsDevice) { + return this._currentPassEncoder || device.getDefaultRenderPassEncoder(); + } + + setFramebuffer(framebuffer) { + if (!framebuffer.isGPU) { + return super.setFramebuffer(framebuffer); + } + // this.context.framebuffer = framebuffer; + // framebuffer => GPURenderPassEncoderDescriptor + // this._currentPassEncoder = passEncoder; + return this; + } + dispose() { if (!this.isGPU) { super.dispose(); diff --git a/packages/reshader.gl/src/webgpu/GraphicsDevice.ts b/packages/reshader.gl/src/webgpu/GraphicsDevice.ts index ae1c97bf52..8397365e25 100644 --- a/packages/reshader.gl/src/webgpu/GraphicsDevice.ts +++ b/packages/reshader.gl/src/webgpu/GraphicsDevice.ts @@ -4,13 +4,80 @@ export default class GraphicsDevice { wgpu: GPUDevice; commandBuffers: GPUCommandBuffer[]; dynamicBufferPool: DynamicBufferPool; + context: GPUCanvasContext; + _defaultRenderTarget: GPURenderPassDescriptor; + commandEncoder: GPUCommandEncoder; + _currentRenderPass: GPURenderPassEncoder; - constructor(device: GPUDevice) { + constructor(device: GPUDevice, context: GPUCanvasContext) { this.wgpu = device; + const presentationFormat = navigator.gpu.getPreferredCanvasFormat(); + context.configure({ + device, + format: presentationFormat, + }); + this.context = context; // 1M for each buffer const bufferSize = 1024 * 1024; const limits = device.limits; - this.dynamicBufferPool = new DynamicBufferPool(device, bufferSize, limits.minUniformBufferOffsetAlignment); + this.dynamicBufferPool = new DynamicBufferPool( + device, + bufferSize, + limits.minUniformBufferOffsetAlignment, + ); + } + + getCommandEncoder(): GPUCommandEncoder { + // use existing or create new encoder + let commandEncoder = this.commandEncoder; + if (!commandEncoder) { + commandEncoder = this.wgpu.createCommandEncoder(); + this.commandEncoder = commandEncoder; + } + + return commandEncoder; + } + + endCommandEncoder() { + const { commandEncoder } = this; + if (commandEncoder) { + commandEncoder.finish(); + // this.addCommandBuffer(cb); + this.commandEncoder = null; + } + } + + getDefaultRenderPassEncoder() { + let rendrTarget = this._defaultRenderTarget + if (!rendrTarget) { + const canvas = this.context.canvas; + const depthTexture = this.wgpu.createTexture({ + size: [canvas.width, canvas.height], + format: 'depth24plus', + usage: GPUTextureUsage.RENDER_ATTACHMENT, + }); + rendrTarget = this._defaultRenderTarget = { + colorAttachments: [ + { + view: undefined, // Assigned later + clearValue: [0, 0, 0, 0], + loadOp: "clear", + storeOp: "store", + }, + ], + depthStencilAttachment: { + view: depthTexture.createView(), + depthClearValue: 1.0, + depthLoadOp: "clear", + depthStoreOp: "store", + }, + }; + } + rendrTarget.colorAttachments[0].view = this.context + .getCurrentTexture() + .createView(); + const commandEncoder = this.getCommandEncoder(); + return commandEncoder.beginRenderPass(rendrTarget); } addCommandBuffer(commandBuffer: GPUCommandBuffer, front: boolean) { @@ -22,6 +89,8 @@ export default class GraphicsDevice { } submit() { + // end the current encoder + this.endCommandEncoder(); // copy dynamic buffers data to the GPU (this schedules the copy CB to run before all other CBs) this.dynamicBufferPool.submit(); if (this.commandBuffers.length > 0) { @@ -29,4 +98,12 @@ export default class GraphicsDevice { this.commandBuffers.length = 0; } } + + framebuffer(reglFBODescriptor) { + // reglDesciprtor => gpu renderPassEncoder + } + + texture(reglTextureDescriptor) { + // regl texture descriptor => GPUTexture + } } From 860ffc232eb74412adf90e01a89210c1924ec020 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Fri, 17 Jan 2025 17:29:43 +0800 Subject: [PATCH 12/53] fix rendering call for example cube.html --- packages/reshader.gl/rollup.config.js | 2 +- packages/reshader.gl/src/Geometry.ts | 10 +++- packages/reshader.gl/src/Renderer.ts | 7 +-- packages/reshader.gl/src/index.ts | 2 + packages/reshader.gl/src/shader/Shader.ts | 17 ++++-- .../reshader.gl/src/webgpu/BindGroupFormat.ts | 11 ++-- .../reshader.gl/src/webgpu/CommandBuilder.ts | 59 +++++++++++++------ .../reshader.gl/src/webgpu/DynamicBuffer.ts | 3 +- .../src/webgpu/DynamicBufferPool.ts | 8 +-- .../reshader.gl/src/webgpu/common/Types.ts | 14 ++--- 10 files changed, 84 insertions(+), 49 deletions(-) diff --git a/packages/reshader.gl/rollup.config.js b/packages/reshader.gl/rollup.config.js index 281e907efe..a302a81998 100644 --- a/packages/reshader.gl/rollup.config.js +++ b/packages/reshader.gl/rollup.config.js @@ -68,7 +68,7 @@ if (production) { module.exports = [ { input: 'src/index.ts', - external : ['gl-matrix', '@maptalks/gltf-loader', '@maptalks/tbn-packer'], + external : production ? ['gl-matrix', '@maptalks/gltf-loader', '@maptalks/tbn-packer'] : [], plugins : plugins, output: [ { diff --git a/packages/reshader.gl/src/Geometry.ts b/packages/reshader.gl/src/Geometry.ts index 00e608011c..47d9e94d3f 100644 --- a/packages/reshader.gl/src/Geometry.ts +++ b/packages/reshader.gl/src/Geometry.ts @@ -1091,7 +1091,7 @@ function getTypeCtor(arr: NumberArray, byteWidth: number) { } function getItemBytes(data) { - const array = data.data || data.buffer || data; + const array = getAttrArray(data); if (array.destroy) { return array.itemBytes; } @@ -1109,16 +1109,20 @@ function getItemBytes(data) { } function getItemFormat(data, itemSize) { - const array = data.data || data.buffer || data; + const array = getAttrArray(data); let format; if (array.destroy) { format = array.itemType; } else { - format = getGPUVertexType(data); + format = getGPUVertexType(array); } return itemSize > 1 ? (format + 'x' + itemSize) : format; } +function getAttrArray(data) { + return data.data || (data.buffer.destroy && data.buffer) || data; +} + function createGPUBuffer(device, data, usage, label) { data = data.data || data; if (Array.isArray(data[0])) { diff --git a/packages/reshader.gl/src/Renderer.ts b/packages/reshader.gl/src/Renderer.ts index b01f1ab238..ef4017270d 100644 --- a/packages/reshader.gl/src/Renderer.ts +++ b/packages/reshader.gl/src/Renderer.ts @@ -1,6 +1,5 @@ import REGL, { Uniforms } from "@maptalks/regl"; import Scene from "./Scene"; -import GraphicsDevice from "./webgpu/GraphicsDevice"; const EMPTY_UNIFORMS = {}; /** @@ -10,11 +9,7 @@ class Renderer { device: any constructor(device: any) { - if (device.device) { - this.device = new GraphicsDevice(device.device, device.context); - } else { - this.device = device; - } + this.device = device; } render(shader, uniforms: Uniforms, scene: Scene, framebuffer: REGL.Framebuffer) { diff --git a/packages/reshader.gl/src/index.ts b/packages/reshader.gl/src/index.ts index e77065b659..aaab8d7209 100644 --- a/packages/reshader.gl/src/index.ts +++ b/packages/reshader.gl/src/index.ts @@ -116,3 +116,5 @@ export { quat, quat2, vec2, vec3, vec4 } from 'gl-matrix'; + +export { default as GraphicsDevice } from './webgpu/GraphicsDevice'; diff --git a/packages/reshader.gl/src/shader/Shader.ts b/packages/reshader.gl/src/shader/Shader.ts index 3f23b56c8c..af0b82cdb6 100644 --- a/packages/reshader.gl/src/shader/Shader.ts +++ b/packages/reshader.gl/src/shader/Shader.ts @@ -433,10 +433,16 @@ export default class GPUShader extends GLShader { const { key, bindGroupFormat, pipeline, vertexInfo } = command; const layout = pipeline.getBindGroupLayout(0); // 1. 生成shader uniform 需要的dynamic buffer + if (!this._buffers) { + this._buffers = {}; + } let shaderBuffer = this._buffers[key] as DynamicBuffer; - if (shaderBuffer) { + if (!shaderBuffer) { shaderBuffer = this._buffers[key] = new DynamicBuffer(bindGroupFormat.getShaderUniforms(), buffersPool); } + if (!this._bindGroupCache) { + this._bindGroupCache = {}; + } // 向buffer中填入shader uniform值 shaderBuffer.writeBuffer(shaderUniforms); for (let i = 0; i < props.length; i++) { @@ -457,9 +463,10 @@ export default class GPUShader extends GLShader { const dynamicOffsets = shaderBuffer.dynamicOffsets.concat(meshBuffer.dynamicOffsets); passEncoder.setBindGroup(0, bindGroup, dynamicOffsets); - for (const vertex of vertexInfo) { - const vertexBuffer = mesh.geometry.getBuffer(vertex.name); - passEncoder.setVertexBuffer(vertex.index, vertexBuffer); + for (const name in vertexInfo) { + const vertex = vertexInfo[name]; + const vertexBuffer = mesh.geometry.getBuffer(name); + passEncoder.setVertexBuffer(vertex.location, vertexBuffer); } //TODO InstancedMesh 的参数 @@ -481,7 +488,7 @@ export default class GPUShader extends GLShader { } setFramebuffer(framebuffer) { - if (!framebuffer.isGPU) { + if (!framebuffer || !framebuffer.isGPU) { return super.setFramebuffer(framebuffer); } // this.context.framebuffer = framebuffer; diff --git a/packages/reshader.gl/src/webgpu/BindGroupFormat.ts b/packages/reshader.gl/src/webgpu/BindGroupFormat.ts index 309f1a6ecf..412f4f2fea 100644 --- a/packages/reshader.gl/src/webgpu/BindGroupFormat.ts +++ b/packages/reshader.gl/src/webgpu/BindGroupFormat.ts @@ -3,6 +3,7 @@ import { toGPUSampler } from "./common/ReglTranslator"; import DynamicBuffer from "./DynamicBuffer"; import Mesh from "../Mesh"; import Texture2D from "../Texture2D"; +import GraphicsDevice from "./GraphicsDevice"; export default class BindGroupFormat { bytes: number; @@ -46,7 +47,7 @@ export default class BindGroupFormat { this._shaderUniforms.index = index; this._shaderUniforms.totalSize += uniform.size; } else { - let index = this._shaderUniforms.index; + let index = this._meshUniforms.index; this._meshUniforms[index++] = uniform; this._meshUniforms.index = index; this._meshUniforms.totalSize += uniform.size; @@ -54,8 +55,8 @@ export default class BindGroupFormat { } } - createBindGroup(device: GPUDevice, mesh: Mesh, layout: GPUBindGroupLayout, shaderBuffer: DynamicBuffer, meshBuffer: DynamicBuffer) { - const groups = this.groups; + createBindGroup(device: GraphicsDevice, mesh: Mesh, layout: GPUBindGroupLayout, shaderBuffer: DynamicBuffer, meshBuffer: DynamicBuffer) { + const groups = this.groups[0]; const entries = []; for (let i = 0; i < groups.length; i++) { const group = groups[i]; @@ -64,7 +65,7 @@ export default class BindGroupFormat { const texture = (mesh.getUniform(name) || mesh.material && mesh.material.getUniform(name)) as Texture2D; const { min, mag, wrapS, wrapT } = (texture as Texture2D).config; const filters = toGPUSampler(min, mag, wrapS, wrapT); - const sampler = device.createSampler(filters); + const sampler = device.wgpu.createSampler(filters); entries.push({ binding: group.binding, resource: sampler @@ -88,7 +89,7 @@ export default class BindGroupFormat { }); } } - return device.createBindGroup({ + return device.wgpu.createBindGroup({ layout, label: '', entries diff --git a/packages/reshader.gl/src/webgpu/CommandBuilder.ts b/packages/reshader.gl/src/webgpu/CommandBuilder.ts index a0a6e7fe92..e1d0abb76f 100644 --- a/packages/reshader.gl/src/webgpu/CommandBuilder.ts +++ b/packages/reshader.gl/src/webgpu/CommandBuilder.ts @@ -4,9 +4,12 @@ import BindGroupFormat from '../webgpu/BindGroupFormat'; import Mesh from '../Mesh'; import { ResourceType } from 'wgsl_reflect'; import { extend } from '../common/Util'; +import GraphicsDevice from './GraphicsDevice'; +import PipelineDescriptor from './common/PipelineDesc'; +import { ActiveAttributes } from '../types/typings'; export default class CommandBuilder { - device: GPUDevice; + device: GraphicsDevice; vert: string; frag: string; mesh: Mesh; @@ -25,8 +28,9 @@ export default class CommandBuilder { const defined = key => { return !!defines[key]; } - const vert = wgsl(this.vert, defined); - const frag = wgsl(this.frag, defined); + //FIXME 如何在wgsl中实现defined + const vert = wgsl(this.vert); + const frag = wgsl(this.frag); const vertReflect = new WgslReflect(vert); const vertexInfo = this._formatBufferInfo(vertReflect, mesh); @@ -37,31 +41,42 @@ export default class CommandBuilder { const pipeline = this._createPipeline(device, vert, vertexInfo, frag, layout, mesh, pipelineDesc); const bindGroupMapping = this._createBindGroupMapping(vertReflect, fragReflect, mesh); - const bindGroupFormat = new BindGroupFormat(bindGroupMapping, device.limits.minUniformBufferOffsetAlignment); + const bindGroupFormat = new BindGroupFormat(bindGroupMapping, device.wgpu.limits.minUniformBufferOffsetAlignment); + const activeAttributes = this._getActiveAttributes(vertexInfo); return { pipeline, vertexInfo, bindGroupMapping, - bindGroupFormat + bindGroupFormat, + activeAttributes }; } + _getActiveAttributes(vertexInfo): ActiveAttributes { + const attributes = [{ name: 'position', type: 1 }]; + (attributes as any).key = attributes.map(attr => attr.name).join(); + return attributes as ActiveAttributes; + } + _createBindGroupLayout(vertReflect: any, fragReflect: any, mesh: Mesh) { const vertGroups = vertReflect.getBindGroups(); const fragGroups = fragReflect.getBindGroups(); - const layoutEntries = []; + const entries = []; for (let i = 0; i < vertGroups.length; i++) { const groupInfo = vertGroups[i]; const entry = this._createLayoutEntry(i, GPUShaderStage.VERTEX, groupInfo, mesh); - layoutEntries.push(entry); + entries.push(entry); } for (let i = 0; i < fragGroups.length; i++) { const groupInfo = fragGroups[i]; const entry = this._createLayoutEntry(i, GPUShaderStage.FRAGMENT, groupInfo, mesh); - layoutEntries.push(entry); + entries.push(entry); } - return layoutEntries; + return this.device.wgpu.createBindGroupLayout({ + label: '', + entries + }); } _createLayoutEntry(binding, visibility, groupInfo, mesh): GPUBindGroupLayoutEntry { @@ -126,12 +141,15 @@ export default class CommandBuilder { const fragGroups = fragReflect.getBindGroups(); // 解析vertInfo和fragInfo,生成一个 bindGroupMapping,用于mesh在运行时,生成bindGroup // mapping 中包含 uniform 变量名对应的 group index 和 binding index - this._parseGroupMapping(mapping, vertGroups, mesh); - this._parseGroupMapping(mapping, fragGroups, mesh); + this._parseGroupMapping(mapping, vertGroups[0], mesh); + this._parseGroupMapping(mapping, fragGroups[0], mesh); return mapping; } _parseGroupMapping(mapping, groupReflect, mesh) { + if (!groupReflect) { + return; + } for (let i = 0; i < groupReflect.length; i++) { const groupInfo = groupReflect[i]; const { group, binding } = groupInfo; @@ -153,14 +171,14 @@ export default class CommandBuilder { } mapping.groups = mapping.groups || []; mapping.groups[group] = mapping.groups[group] || []; - mapping.groups[group][binding] = extend({ - // we assume all the members in the same struct is all global or mesh owned - isGlobal - }, groupInfo); + groupInfo.isGlobal = isGlobal; + // we assume all the members in the same struct is all global or mesh owned + mapping.groups[group][binding] = groupInfo;//extend({ } } // 运行时调用,生成 uniform buffer, 用来存放全局 uniform 变量的值 - _createPipeline(device, vert, vertInfo, frag, layout, mesh, pipelineDesc): GPURenderPipeline { + _createPipeline(graphicsDevice: GraphicsDevice, vert: string, vertInfo, frag: string, layout: GPUBindGroupLayout, mesh:Mesh, pipelineDesc: PipelineDescriptor): GPURenderPipeline { + const device = graphicsDevice.wgpu; const vertModule = device.createShaderModule({ code: vert, }); @@ -171,8 +189,12 @@ export default class CommandBuilder { this._presentationFormat = navigator.gpu.getPreferredCanvasFormat(); } const buffers = mesh.geometry.getBufferDescriptor(vertInfo); + const pipelineLayout = device.createPipelineLayout({ + label: 'label', + bindGroupLayouts: [layout] + }); const pipelineOptions: GPURenderPipelineDescriptor = { - layout, + layout: pipelineLayout, vertex: { module: vertModule, buffers @@ -226,6 +248,9 @@ export default class CommandBuilder { } function meshHasUniform(mesh: Mesh, name: string) { + if (name === 'modelMatrix' || name === 'positionMatrix') { + return true; + } return mesh.hasUniform(name) || (mesh.material && mesh.material.hasUniform(name)); } diff --git a/packages/reshader.gl/src/webgpu/DynamicBuffer.ts b/packages/reshader.gl/src/webgpu/DynamicBuffer.ts index e43b4a7641..578441bafc 100644 --- a/packages/reshader.gl/src/webgpu/DynamicBuffer.ts +++ b/packages/reshader.gl/src/webgpu/DynamicBuffer.ts @@ -14,6 +14,7 @@ export default class DynamicBuffer { this.bindgroupMapping = bindgroupMapping; this.pool = pool; this.dynamicOffsets = new Array(bindgroupMapping.length); + this.allocation = {}; } writeBuffer(uniformValues: Record) { @@ -38,7 +39,7 @@ export default class DynamicBuffer { this._fillValue(storage, offset, size, value); } // size() 返回的值已经考虑过 bufferAlignment - dynamicOffset += mapping[i].size(); + dynamicOffset += mapping[i].size; this.dynamicOffsets[i] = dynamicOffset; } else if (uniform.resourceType === ResourceType.Uniform) { const value = uniformValues[uniform.name]; diff --git a/packages/reshader.gl/src/webgpu/DynamicBufferPool.ts b/packages/reshader.gl/src/webgpu/DynamicBufferPool.ts index 40c97dec24..0a93f7a1ab 100644 --- a/packages/reshader.gl/src/webgpu/DynamicBufferPool.ts +++ b/packages/reshader.gl/src/webgpu/DynamicBufferPool.ts @@ -4,10 +4,10 @@ import * as math from './common/math'; export type DynamicBufferAllocation = { - storage: ArrayBuffer; - gpuBuffer: GPUBuffer; - offset: number; - size: number; + storage?: ArrayBuffer; + gpuBuffer?: GPUBuffer; + offset?: number; + size?: number; } export default class DynamicBufferPool { diff --git a/packages/reshader.gl/src/webgpu/common/Types.ts b/packages/reshader.gl/src/webgpu/common/Types.ts index 8fb3feebc6..d28da16383 100644 --- a/packages/reshader.gl/src/webgpu/common/Types.ts +++ b/packages/reshader.gl/src/webgpu/common/Types.ts @@ -1,19 +1,19 @@ export function getGPUVertexType(array) { let format; if (Array.isArray(array) || array instanceof Float32Array) { - format === 'float32'; + format = 'float32'; } else if (array instanceof Uint32Array) { - format === 'uint32'; + format = 'uint32'; } else if (array instanceof Int32Array) { - format === 'sint32'; + format = 'sint32'; } else if (array instanceof Uint16Array) { - format === 'uint16'; + format = 'uint16'; } else if (array instanceof Int16Array) { - format === 'sint16'; + format = 'sint16'; } else if (array instanceof Uint8Array) { - format === 'uint8'; + format = 'uint8'; } else if (array instanceof Int8Array) { - format === 'sint8'; + format = 'sint8'; } return format; } From ce073fcb436cb1c6d9d068c3dd9a31a2d8982fbb Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Fri, 17 Jan 2025 17:33:55 +0800 Subject: [PATCH 13/53] add webgpu cube.html --- .gitignore | 2 +- debug/webgpu/cube.html | 110 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 debug/webgpu/cube.html diff --git a/.gitignore b/.gitignore index 05378950fe..51aeeb0074 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,6 @@ coverage .env .env.local web.config -debug + *.tsbuildinfo *.map diff --git a/debug/webgpu/cube.html b/debug/webgpu/cube.html new file mode 100644 index 0000000000..fc3669758a --- /dev/null +++ b/debug/webgpu/cube.html @@ -0,0 +1,110 @@ + + + + + Cube Wireframe + + + + + + + + From e39b4bcb1d630254f710bd0235c2564ddbe2b9aa Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 20 Jan 2025 17:09:22 +0800 Subject: [PATCH 14/53] first successful webgpu rendering --- debug/webgpu/cube.html | 40 +- debug/webgpu/gl-matrix/common.js | 51 + debug/webgpu/gl-matrix/index.js | 11 + debug/webgpu/gl-matrix/mat2.js | 432 ++++ debug/webgpu/gl-matrix/mat2d.js | 486 ++++ debug/webgpu/gl-matrix/mat3.js | 778 +++++++ debug/webgpu/gl-matrix/mat4.js | 1987 +++++++++++++++++ debug/webgpu/gl-matrix/quat.js | 760 +++++++ debug/webgpu/gl-matrix/quat2.js | 835 +++++++ debug/webgpu/gl-matrix/vec2.js | 624 ++++++ debug/webgpu/gl-matrix/vec3.js | 805 +++++++ debug/webgpu/gl-matrix/vec4.js | 663 ++++++ debug/webgpu/meshes/cube.js | 124 + packages/reshader.gl/src/shader/Shader.ts | 6 +- .../reshader.gl/src/webgpu/CommandBuilder.ts | 7 +- .../reshader.gl/src/webgpu/DynamicBuffer.ts | 9 +- .../reshader.gl/src/webgpu/GraphicsDevice.ts | 12 +- .../src/webgpu/common/PipelineDesc.ts | 1 + 18 files changed, 7614 insertions(+), 17 deletions(-) create mode 100644 debug/webgpu/gl-matrix/common.js create mode 100644 debug/webgpu/gl-matrix/index.js create mode 100644 debug/webgpu/gl-matrix/mat2.js create mode 100644 debug/webgpu/gl-matrix/mat2d.js create mode 100644 debug/webgpu/gl-matrix/mat3.js create mode 100644 debug/webgpu/gl-matrix/mat4.js create mode 100644 debug/webgpu/gl-matrix/quat.js create mode 100644 debug/webgpu/gl-matrix/quat2.js create mode 100644 debug/webgpu/gl-matrix/vec2.js create mode 100644 debug/webgpu/gl-matrix/vec3.js create mode 100644 debug/webgpu/gl-matrix/vec4.js create mode 100644 debug/webgpu/meshes/cube.js diff --git a/debug/webgpu/cube.html b/debug/webgpu/cube.html index fc3669758a..222ae6a8e6 100644 --- a/debug/webgpu/cube.html +++ b/debug/webgpu/cube.html @@ -62,7 +62,13 @@ const renderer = new Renderer(device); const shader = new MeshShader({ vert, - frag + frag, + name: 'cube', + extraCommandProps: { + cull: { + enable: true + } + } }); const geometry = new Geometry({ @@ -81,17 +87,18 @@ scene.setMeshes([mesh]); renderer.render(shader, null, scene); + device.submit(); requestAnimationFrame(render); } - const aspect = canvas.width / canvas.height; - const projectionMatrix = mat4.perspective([], (2 * Math.PI) / 5, aspect, 1, 100.0); + const projectionMatrix = perspectiveZO([], (2 * Math.PI) / 5, aspect, 1, 100.0); const modelViewProjectionMatrix = mat4.create(); const viewMatrix = mat4.identity([]); const translation = [0, 0, -4]; const axis = []; function getTransformationMatrix() { + mat4.identity(viewMatrix); mat4.translate(viewMatrix, viewMatrix, translation); const now = Date.now() / 1000; mat4.rotate( @@ -104,6 +111,33 @@ return modelViewProjectionMatrix; } + function perspectiveZO(out, fovy, aspect, near, far) { + const f = 1.0 / Math.tan(fovy / 2); + out[0] = f / aspect; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = f; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[11] = -1; + out[12] = 0; + out[13] = 0; + out[15] = 0; + if (far != null && far !== Infinity) { + const nf = 1 / (near - far); + out[10] = far * nf; + out[14] = far * near * nf; + } else { + out[10] = -1; + out[14] = -near; + } + return out; + } + render(); diff --git a/debug/webgpu/gl-matrix/common.js b/debug/webgpu/gl-matrix/common.js new file mode 100644 index 0000000000..fa58cf074c --- /dev/null +++ b/debug/webgpu/gl-matrix/common.js @@ -0,0 +1,51 @@ +/** + * Common utilities + * @module glMatrix + */ +// Configuration Constants +export var EPSILON = 0.000001; +export var ARRAY_TYPE = typeof Float32Array !== "undefined" ? Float32Array : Array; +export var RANDOM = Math.random; +export var ANGLE_ORDER = "zyx"; +/** + * Sets the type of array used when creating new vectors and matrices + * + * @param {Float32ArrayConstructor | ArrayConstructor} type Array type, such as Float32Array or Array + */ + +export function setMatrixArrayType(type) { + ARRAY_TYPE = type; +} +var degree = Math.PI / 180; +/** + * Convert Degree To Radian + * + * @param {Number} a Angle in Degrees + */ + +export function toRadian(a) { + return a * degree; +} +/** + * Tests whether or not the arguments have approximately the same value, within an absolute + * or relative tolerance of glMatrix.EPSILON (an absolute tolerance is used for values less + * than or equal to 1.0, and a relative tolerance is used for larger values) + * + * @param {Number} a The first number to test. + * @param {Number} b The second number to test. + * @returns {Boolean} True if the numbers are approximately equal, false otherwise. + */ + +export function equals(a, b) { + return Math.abs(a - b) <= EPSILON * Math.max(1.0, Math.abs(a), Math.abs(b)); +} +if (!Math.hypot) Math.hypot = function () { + var y = 0, + i = arguments.length; + + while (i--) { + y += arguments[i] * arguments[i]; + } + + return Math.sqrt(y); +}; \ No newline at end of file diff --git a/debug/webgpu/gl-matrix/index.js b/debug/webgpu/gl-matrix/index.js new file mode 100644 index 0000000000..e89dffe691 --- /dev/null +++ b/debug/webgpu/gl-matrix/index.js @@ -0,0 +1,11 @@ +import * as glMatrix from "./common.js"; +import * as mat2 from "./mat2.js"; +import * as mat2d from "./mat2d.js"; +import * as mat3 from "./mat3.js"; +import * as mat4 from "./mat4.js"; +import * as quat from "./quat.js"; +import * as quat2 from "./quat2.js"; +import * as vec2 from "./vec2.js"; +import * as vec3 from "./vec3.js"; +import * as vec4 from "./vec4.js"; +export { glMatrix, mat2, mat2d, mat3, mat4, quat, quat2, vec2, vec3, vec4 }; \ No newline at end of file diff --git a/debug/webgpu/gl-matrix/mat2.js b/debug/webgpu/gl-matrix/mat2.js new file mode 100644 index 0000000000..d3fbcdebc0 --- /dev/null +++ b/debug/webgpu/gl-matrix/mat2.js @@ -0,0 +1,432 @@ +import * as glMatrix from "./common.js"; +/** + * 2x2 Matrix + * @module mat2 + */ + +/** + * Creates a new identity mat2 + * + * @returns {mat2} a new 2x2 matrix + */ + +export function create() { + var out = new glMatrix.ARRAY_TYPE(4); + + if (glMatrix.ARRAY_TYPE != Float32Array) { + out[1] = 0; + out[2] = 0; + } + + out[0] = 1; + out[3] = 1; + return out; +} +/** + * Creates a new mat2 initialized with values from an existing matrix + * + * @param {ReadonlyMat2} a matrix to clone + * @returns {mat2} a new 2x2 matrix + */ + +export function clone(a) { + var out = new glMatrix.ARRAY_TYPE(4); + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + return out; +} +/** + * Copy the values from one mat2 to another + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the source matrix + * @returns {mat2} out + */ + +export function copy(out, a) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + return out; +} +/** + * Set a mat2 to the identity matrix + * + * @param {mat2} out the receiving matrix + * @returns {mat2} out + */ + +export function identity(out) { + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 1; + return out; +} +/** + * Create a new mat2 with the given values + * + * @param {Number} m00 Component in column 0, row 0 position (index 0) + * @param {Number} m01 Component in column 0, row 1 position (index 1) + * @param {Number} m10 Component in column 1, row 0 position (index 2) + * @param {Number} m11 Component in column 1, row 1 position (index 3) + * @returns {mat2} out A new 2x2 matrix + */ + +export function fromValues(m00, m01, m10, m11) { + var out = new glMatrix.ARRAY_TYPE(4); + out[0] = m00; + out[1] = m01; + out[2] = m10; + out[3] = m11; + return out; +} +/** + * Set the components of a mat2 to the given values + * + * @param {mat2} out the receiving matrix + * @param {Number} m00 Component in column 0, row 0 position (index 0) + * @param {Number} m01 Component in column 0, row 1 position (index 1) + * @param {Number} m10 Component in column 1, row 0 position (index 2) + * @param {Number} m11 Component in column 1, row 1 position (index 3) + * @returns {mat2} out + */ + +export function set(out, m00, m01, m10, m11) { + out[0] = m00; + out[1] = m01; + out[2] = m10; + out[3] = m11; + return out; +} +/** + * Transpose the values of a mat2 + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the source matrix + * @returns {mat2} out + */ + +export function transpose(out, a) { + // If we are transposing ourselves we can skip a few steps but have to cache + // some values + if (out === a) { + var a1 = a[1]; + out[1] = a[2]; + out[2] = a1; + } else { + out[0] = a[0]; + out[1] = a[2]; + out[2] = a[1]; + out[3] = a[3]; + } + + return out; +} +/** + * Inverts a mat2 + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the source matrix + * @returns {mat2} out + */ + +export function invert(out, a) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3]; // Calculate the determinant + + var det = a0 * a3 - a2 * a1; + + if (!det) { + return null; + } + + det = 1.0 / det; + out[0] = a3 * det; + out[1] = -a1 * det; + out[2] = -a2 * det; + out[3] = a0 * det; + return out; +} +/** + * Calculates the adjugate of a mat2 + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the source matrix + * @returns {mat2} out + */ + +export function adjoint(out, a) { + // Caching this value is necessary if out == a + var a0 = a[0]; + out[0] = a[3]; + out[1] = -a[1]; + out[2] = -a[2]; + out[3] = a0; + return out; +} +/** + * Calculates the determinant of a mat2 + * + * @param {ReadonlyMat2} a the source matrix + * @returns {Number} determinant of a + */ + +export function determinant(a) { + return a[0] * a[3] - a[2] * a[1]; +} +/** + * Multiplies two mat2's + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the first operand + * @param {ReadonlyMat2} b the second operand + * @returns {mat2} out + */ + +export function multiply(out, a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3]; + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3]; + out[0] = a0 * b0 + a2 * b1; + out[1] = a1 * b0 + a3 * b1; + out[2] = a0 * b2 + a2 * b3; + out[3] = a1 * b2 + a3 * b3; + return out; +} +/** + * Rotates a mat2 by the given angle + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the matrix to rotate + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat2} out + */ + +export function rotate(out, a, rad) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3]; + var s = Math.sin(rad); + var c = Math.cos(rad); + out[0] = a0 * c + a2 * s; + out[1] = a1 * c + a3 * s; + out[2] = a0 * -s + a2 * c; + out[3] = a1 * -s + a3 * c; + return out; +} +/** + * Scales the mat2 by the dimensions in the given vec2 + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the matrix to rotate + * @param {ReadonlyVec2} v the vec2 to scale the matrix by + * @returns {mat2} out + **/ + +export function scale(out, a, v) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3]; + var v0 = v[0], + v1 = v[1]; + out[0] = a0 * v0; + out[1] = a1 * v0; + out[2] = a2 * v1; + out[3] = a3 * v1; + return out; +} +/** + * Creates a matrix from a given angle + * This is equivalent to (but much faster than): + * + * mat2.identity(dest); + * mat2.rotate(dest, dest, rad); + * + * @param {mat2} out mat2 receiving operation result + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat2} out + */ + +export function fromRotation(out, rad) { + var s = Math.sin(rad); + var c = Math.cos(rad); + out[0] = c; + out[1] = s; + out[2] = -s; + out[3] = c; + return out; +} +/** + * Creates a matrix from a vector scaling + * This is equivalent to (but much faster than): + * + * mat2.identity(dest); + * mat2.scale(dest, dest, vec); + * + * @param {mat2} out mat2 receiving operation result + * @param {ReadonlyVec2} v Scaling vector + * @returns {mat2} out + */ + +export function fromScaling(out, v) { + out[0] = v[0]; + out[1] = 0; + out[2] = 0; + out[3] = v[1]; + return out; +} +/** + * Returns a string representation of a mat2 + * + * @param {ReadonlyMat2} a matrix to represent as a string + * @returns {String} string representation of the matrix + */ + +export function str(a) { + return "mat2(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ")"; +} +/** + * Returns Frobenius norm of a mat2 + * + * @param {ReadonlyMat2} a the matrix to calculate Frobenius norm of + * @returns {Number} Frobenius norm + */ + +export function frob(a) { + return Math.hypot(a[0], a[1], a[2], a[3]); +} +/** + * Returns L, D and U matrices (Lower triangular, Diagonal and Upper triangular) by factorizing the input matrix + * @param {ReadonlyMat2} L the lower triangular matrix + * @param {ReadonlyMat2} D the diagonal matrix + * @param {ReadonlyMat2} U the upper triangular matrix + * @param {ReadonlyMat2} a the input matrix to factorize + */ + +export function LDU(L, D, U, a) { + L[2] = a[2] / a[0]; + U[0] = a[0]; + U[1] = a[1]; + U[3] = a[3] - L[2] * U[1]; + return [L, D, U]; +} +/** + * Adds two mat2's + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the first operand + * @param {ReadonlyMat2} b the second operand + * @returns {mat2} out + */ + +export function add(out, a, b) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; + out[3] = a[3] + b[3]; + return out; +} +/** + * Subtracts matrix b from matrix a + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the first operand + * @param {ReadonlyMat2} b the second operand + * @returns {mat2} out + */ + +export function subtract(out, a, b) { + out[0] = a[0] - b[0]; + out[1] = a[1] - b[1]; + out[2] = a[2] - b[2]; + out[3] = a[3] - b[3]; + return out; +} +/** + * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===) + * + * @param {ReadonlyMat2} a The first matrix. + * @param {ReadonlyMat2} b The second matrix. + * @returns {Boolean} True if the matrices are equal, false otherwise. + */ + +export function exactEquals(a, b) { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]; +} +/** + * Returns whether or not the matrices have approximately the same elements in the same position. + * + * @param {ReadonlyMat2} a The first matrix. + * @param {ReadonlyMat2} b The second matrix. + * @returns {Boolean} True if the matrices are equal, false otherwise. + */ + +export function equals(a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3]; + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3]; + return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)); +} +/** + * Multiply each element of the matrix by a scalar. + * + * @param {mat2} out the receiving matrix + * @param {ReadonlyMat2} a the matrix to scale + * @param {Number} b amount to scale the matrix's elements by + * @returns {mat2} out + */ + +export function multiplyScalar(out, a, b) { + out[0] = a[0] * b; + out[1] = a[1] * b; + out[2] = a[2] * b; + out[3] = a[3] * b; + return out; +} +/** + * Adds two mat2's after multiplying each element of the second operand by a scalar value. + * + * @param {mat2} out the receiving vector + * @param {ReadonlyMat2} a the first operand + * @param {ReadonlyMat2} b the second operand + * @param {Number} scale the amount to scale b's elements by before adding + * @returns {mat2} out + */ + +export function multiplyScalarAndAdd(out, a, b, scale) { + out[0] = a[0] + b[0] * scale; + out[1] = a[1] + b[1] * scale; + out[2] = a[2] + b[2] * scale; + out[3] = a[3] + b[3] * scale; + return out; +} +/** + * Alias for {@link mat2.multiply} + * @function + */ + +export var mul = multiply; +/** + * Alias for {@link mat2.subtract} + * @function + */ + +export var sub = subtract; \ No newline at end of file diff --git a/debug/webgpu/gl-matrix/mat2d.js b/debug/webgpu/gl-matrix/mat2d.js new file mode 100644 index 0000000000..ce6988c3ee --- /dev/null +++ b/debug/webgpu/gl-matrix/mat2d.js @@ -0,0 +1,486 @@ +import * as glMatrix from "./common.js"; +/** + * 2x3 Matrix + * @module mat2d + * @description + * A mat2d contains six elements defined as: + *
+ * [a, b,
+ *  c, d,
+ *  tx, ty]
+ * 
+ * This is a short form for the 3x3 matrix: + *
+ * [a, b, 0,
+ *  c, d, 0,
+ *  tx, ty, 1]
+ * 
+ * The last column is ignored so the array is shorter and operations are faster. + */ + +/** + * Creates a new identity mat2d + * + * @returns {mat2d} a new 2x3 matrix + */ + +export function create() { + var out = new glMatrix.ARRAY_TYPE(6); + + if (glMatrix.ARRAY_TYPE != Float32Array) { + out[1] = 0; + out[2] = 0; + out[4] = 0; + out[5] = 0; + } + + out[0] = 1; + out[3] = 1; + return out; +} +/** + * Creates a new mat2d initialized with values from an existing matrix + * + * @param {ReadonlyMat2d} a matrix to clone + * @returns {mat2d} a new 2x3 matrix + */ + +export function clone(a) { + var out = new glMatrix.ARRAY_TYPE(6); + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + out[4] = a[4]; + out[5] = a[5]; + return out; +} +/** + * Copy the values from one mat2d to another + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the source matrix + * @returns {mat2d} out + */ + +export function copy(out, a) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + out[4] = a[4]; + out[5] = a[5]; + return out; +} +/** + * Set a mat2d to the identity matrix + * + * @param {mat2d} out the receiving matrix + * @returns {mat2d} out + */ + +export function identity(out) { + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 1; + out[4] = 0; + out[5] = 0; + return out; +} +/** + * Create a new mat2d with the given values + * + * @param {Number} a Component A (index 0) + * @param {Number} b Component B (index 1) + * @param {Number} c Component C (index 2) + * @param {Number} d Component D (index 3) + * @param {Number} tx Component TX (index 4) + * @param {Number} ty Component TY (index 5) + * @returns {mat2d} A new mat2d + */ + +export function fromValues(a, b, c, d, tx, ty) { + var out = new glMatrix.ARRAY_TYPE(6); + out[0] = a; + out[1] = b; + out[2] = c; + out[3] = d; + out[4] = tx; + out[5] = ty; + return out; +} +/** + * Set the components of a mat2d to the given values + * + * @param {mat2d} out the receiving matrix + * @param {Number} a Component A (index 0) + * @param {Number} b Component B (index 1) + * @param {Number} c Component C (index 2) + * @param {Number} d Component D (index 3) + * @param {Number} tx Component TX (index 4) + * @param {Number} ty Component TY (index 5) + * @returns {mat2d} out + */ + +export function set(out, a, b, c, d, tx, ty) { + out[0] = a; + out[1] = b; + out[2] = c; + out[3] = d; + out[4] = tx; + out[5] = ty; + return out; +} +/** + * Inverts a mat2d + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the source matrix + * @returns {mat2d} out + */ + +export function invert(out, a) { + var aa = a[0], + ab = a[1], + ac = a[2], + ad = a[3]; + var atx = a[4], + aty = a[5]; + var det = aa * ad - ab * ac; + + if (!det) { + return null; + } + + det = 1.0 / det; + out[0] = ad * det; + out[1] = -ab * det; + out[2] = -ac * det; + out[3] = aa * det; + out[4] = (ac * aty - ad * atx) * det; + out[5] = (ab * atx - aa * aty) * det; + return out; +} +/** + * Calculates the determinant of a mat2d + * + * @param {ReadonlyMat2d} a the source matrix + * @returns {Number} determinant of a + */ + +export function determinant(a) { + return a[0] * a[3] - a[1] * a[2]; +} +/** + * Multiplies two mat2d's + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the first operand + * @param {ReadonlyMat2d} b the second operand + * @returns {mat2d} out + */ + +export function multiply(out, a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3], + a4 = a[4], + a5 = a[5]; + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3], + b4 = b[4], + b5 = b[5]; + out[0] = a0 * b0 + a2 * b1; + out[1] = a1 * b0 + a3 * b1; + out[2] = a0 * b2 + a2 * b3; + out[3] = a1 * b2 + a3 * b3; + out[4] = a0 * b4 + a2 * b5 + a4; + out[5] = a1 * b4 + a3 * b5 + a5; + return out; +} +/** + * Rotates a mat2d by the given angle + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the matrix to rotate + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat2d} out + */ + +export function rotate(out, a, rad) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3], + a4 = a[4], + a5 = a[5]; + var s = Math.sin(rad); + var c = Math.cos(rad); + out[0] = a0 * c + a2 * s; + out[1] = a1 * c + a3 * s; + out[2] = a0 * -s + a2 * c; + out[3] = a1 * -s + a3 * c; + out[4] = a4; + out[5] = a5; + return out; +} +/** + * Scales the mat2d by the dimensions in the given vec2 + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the matrix to translate + * @param {ReadonlyVec2} v the vec2 to scale the matrix by + * @returns {mat2d} out + **/ + +export function scale(out, a, v) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3], + a4 = a[4], + a5 = a[5]; + var v0 = v[0], + v1 = v[1]; + out[0] = a0 * v0; + out[1] = a1 * v0; + out[2] = a2 * v1; + out[3] = a3 * v1; + out[4] = a4; + out[5] = a5; + return out; +} +/** + * Translates the mat2d by the dimensions in the given vec2 + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the matrix to translate + * @param {ReadonlyVec2} v the vec2 to translate the matrix by + * @returns {mat2d} out + **/ + +export function translate(out, a, v) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3], + a4 = a[4], + a5 = a[5]; + var v0 = v[0], + v1 = v[1]; + out[0] = a0; + out[1] = a1; + out[2] = a2; + out[3] = a3; + out[4] = a0 * v0 + a2 * v1 + a4; + out[5] = a1 * v0 + a3 * v1 + a5; + return out; +} +/** + * Creates a matrix from a given angle + * This is equivalent to (but much faster than): + * + * mat2d.identity(dest); + * mat2d.rotate(dest, dest, rad); + * + * @param {mat2d} out mat2d receiving operation result + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat2d} out + */ + +export function fromRotation(out, rad) { + var s = Math.sin(rad), + c = Math.cos(rad); + out[0] = c; + out[1] = s; + out[2] = -s; + out[3] = c; + out[4] = 0; + out[5] = 0; + return out; +} +/** + * Creates a matrix from a vector scaling + * This is equivalent to (but much faster than): + * + * mat2d.identity(dest); + * mat2d.scale(dest, dest, vec); + * + * @param {mat2d} out mat2d receiving operation result + * @param {ReadonlyVec2} v Scaling vector + * @returns {mat2d} out + */ + +export function fromScaling(out, v) { + out[0] = v[0]; + out[1] = 0; + out[2] = 0; + out[3] = v[1]; + out[4] = 0; + out[5] = 0; + return out; +} +/** + * Creates a matrix from a vector translation + * This is equivalent to (but much faster than): + * + * mat2d.identity(dest); + * mat2d.translate(dest, dest, vec); + * + * @param {mat2d} out mat2d receiving operation result + * @param {ReadonlyVec2} v Translation vector + * @returns {mat2d} out + */ + +export function fromTranslation(out, v) { + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 1; + out[4] = v[0]; + out[5] = v[1]; + return out; +} +/** + * Returns a string representation of a mat2d + * + * @param {ReadonlyMat2d} a matrix to represent as a string + * @returns {String} string representation of the matrix + */ + +export function str(a) { + return "mat2d(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ", " + a[4] + ", " + a[5] + ")"; +} +/** + * Returns Frobenius norm of a mat2d + * + * @param {ReadonlyMat2d} a the matrix to calculate Frobenius norm of + * @returns {Number} Frobenius norm + */ + +export function frob(a) { + return Math.hypot(a[0], a[1], a[2], a[3], a[4], a[5], 1); +} +/** + * Adds two mat2d's + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the first operand + * @param {ReadonlyMat2d} b the second operand + * @returns {mat2d} out + */ + +export function add(out, a, b) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; + out[3] = a[3] + b[3]; + out[4] = a[4] + b[4]; + out[5] = a[5] + b[5]; + return out; +} +/** + * Subtracts matrix b from matrix a + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the first operand + * @param {ReadonlyMat2d} b the second operand + * @returns {mat2d} out + */ + +export function subtract(out, a, b) { + out[0] = a[0] - b[0]; + out[1] = a[1] - b[1]; + out[2] = a[2] - b[2]; + out[3] = a[3] - b[3]; + out[4] = a[4] - b[4]; + out[5] = a[5] - b[5]; + return out; +} +/** + * Multiply each element of the matrix by a scalar. + * + * @param {mat2d} out the receiving matrix + * @param {ReadonlyMat2d} a the matrix to scale + * @param {Number} b amount to scale the matrix's elements by + * @returns {mat2d} out + */ + +export function multiplyScalar(out, a, b) { + out[0] = a[0] * b; + out[1] = a[1] * b; + out[2] = a[2] * b; + out[3] = a[3] * b; + out[4] = a[4] * b; + out[5] = a[5] * b; + return out; +} +/** + * Adds two mat2d's after multiplying each element of the second operand by a scalar value. + * + * @param {mat2d} out the receiving vector + * @param {ReadonlyMat2d} a the first operand + * @param {ReadonlyMat2d} b the second operand + * @param {Number} scale the amount to scale b's elements by before adding + * @returns {mat2d} out + */ + +export function multiplyScalarAndAdd(out, a, b, scale) { + out[0] = a[0] + b[0] * scale; + out[1] = a[1] + b[1] * scale; + out[2] = a[2] + b[2] * scale; + out[3] = a[3] + b[3] * scale; + out[4] = a[4] + b[4] * scale; + out[5] = a[5] + b[5] * scale; + return out; +} +/** + * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===) + * + * @param {ReadonlyMat2d} a The first matrix. + * @param {ReadonlyMat2d} b The second matrix. + * @returns {Boolean} True if the matrices are equal, false otherwise. + */ + +export function exactEquals(a, b) { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5]; +} +/** + * Returns whether or not the matrices have approximately the same elements in the same position. + * + * @param {ReadonlyMat2d} a The first matrix. + * @param {ReadonlyMat2d} b The second matrix. + * @returns {Boolean} True if the matrices are equal, false otherwise. + */ + +export function equals(a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3], + a4 = a[4], + a5 = a[5]; + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3], + b4 = b[4], + b5 = b[5]; + return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a5), Math.abs(b5)); +} +/** + * Alias for {@link mat2d.multiply} + * @function + */ + +export var mul = multiply; +/** + * Alias for {@link mat2d.subtract} + * @function + */ + +export var sub = subtract; \ No newline at end of file diff --git a/debug/webgpu/gl-matrix/mat3.js b/debug/webgpu/gl-matrix/mat3.js new file mode 100644 index 0000000000..13974ab104 --- /dev/null +++ b/debug/webgpu/gl-matrix/mat3.js @@ -0,0 +1,778 @@ +import * as glMatrix from "./common.js"; +/** + * 3x3 Matrix + * @module mat3 + */ + +/** + * Creates a new identity mat3 + * + * @returns {mat3} a new 3x3 matrix + */ + +export function create() { + var out = new glMatrix.ARRAY_TYPE(9); + + if (glMatrix.ARRAY_TYPE != Float32Array) { + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[5] = 0; + out[6] = 0; + out[7] = 0; + } + + out[0] = 1; + out[4] = 1; + out[8] = 1; + return out; +} +/** + * Copies the upper-left 3x3 values into the given mat3. + * + * @param {mat3} out the receiving 3x3 matrix + * @param {ReadonlyMat4} a the source 4x4 matrix + * @returns {mat3} out + */ + +export function fromMat4(out, a) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[4]; + out[4] = a[5]; + out[5] = a[6]; + out[6] = a[8]; + out[7] = a[9]; + out[8] = a[10]; + return out; +} +/** + * Creates a new mat3 initialized with values from an existing matrix + * + * @param {ReadonlyMat3} a matrix to clone + * @returns {mat3} a new 3x3 matrix + */ + +export function clone(a) { + var out = new glMatrix.ARRAY_TYPE(9); + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + out[4] = a[4]; + out[5] = a[5]; + out[6] = a[6]; + out[7] = a[7]; + out[8] = a[8]; + return out; +} +/** + * Copy the values from one mat3 to another + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the source matrix + * @returns {mat3} out + */ + +export function copy(out, a) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + out[4] = a[4]; + out[5] = a[5]; + out[6] = a[6]; + out[7] = a[7]; + out[8] = a[8]; + return out; +} +/** + * Create a new mat3 with the given values + * + * @param {Number} m00 Component in column 0, row 0 position (index 0) + * @param {Number} m01 Component in column 0, row 1 position (index 1) + * @param {Number} m02 Component in column 0, row 2 position (index 2) + * @param {Number} m10 Component in column 1, row 0 position (index 3) + * @param {Number} m11 Component in column 1, row 1 position (index 4) + * @param {Number} m12 Component in column 1, row 2 position (index 5) + * @param {Number} m20 Component in column 2, row 0 position (index 6) + * @param {Number} m21 Component in column 2, row 1 position (index 7) + * @param {Number} m22 Component in column 2, row 2 position (index 8) + * @returns {mat3} A new mat3 + */ + +export function fromValues(m00, m01, m02, m10, m11, m12, m20, m21, m22) { + var out = new glMatrix.ARRAY_TYPE(9); + out[0] = m00; + out[1] = m01; + out[2] = m02; + out[3] = m10; + out[4] = m11; + out[5] = m12; + out[6] = m20; + out[7] = m21; + out[8] = m22; + return out; +} +/** + * Set the components of a mat3 to the given values + * + * @param {mat3} out the receiving matrix + * @param {Number} m00 Component in column 0, row 0 position (index 0) + * @param {Number} m01 Component in column 0, row 1 position (index 1) + * @param {Number} m02 Component in column 0, row 2 position (index 2) + * @param {Number} m10 Component in column 1, row 0 position (index 3) + * @param {Number} m11 Component in column 1, row 1 position (index 4) + * @param {Number} m12 Component in column 1, row 2 position (index 5) + * @param {Number} m20 Component in column 2, row 0 position (index 6) + * @param {Number} m21 Component in column 2, row 1 position (index 7) + * @param {Number} m22 Component in column 2, row 2 position (index 8) + * @returns {mat3} out + */ + +export function set(out, m00, m01, m02, m10, m11, m12, m20, m21, m22) { + out[0] = m00; + out[1] = m01; + out[2] = m02; + out[3] = m10; + out[4] = m11; + out[5] = m12; + out[6] = m20; + out[7] = m21; + out[8] = m22; + return out; +} +/** + * Set a mat3 to the identity matrix + * + * @param {mat3} out the receiving matrix + * @returns {mat3} out + */ + +export function identity(out) { + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 1; + out[5] = 0; + out[6] = 0; + out[7] = 0; + out[8] = 1; + return out; +} +/** + * Transpose the values of a mat3 + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the source matrix + * @returns {mat3} out + */ + +export function transpose(out, a) { + // If we are transposing ourselves we can skip a few steps but have to cache some values + if (out === a) { + var a01 = a[1], + a02 = a[2], + a12 = a[5]; + out[1] = a[3]; + out[2] = a[6]; + out[3] = a01; + out[5] = a[7]; + out[6] = a02; + out[7] = a12; + } else { + out[0] = a[0]; + out[1] = a[3]; + out[2] = a[6]; + out[3] = a[1]; + out[4] = a[4]; + out[5] = a[7]; + out[6] = a[2]; + out[7] = a[5]; + out[8] = a[8]; + } + + return out; +} +/** + * Inverts a mat3 + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the source matrix + * @returns {mat3} out + */ + +export function invert(out, a) { + var a00 = a[0], + a01 = a[1], + a02 = a[2]; + var a10 = a[3], + a11 = a[4], + a12 = a[5]; + var a20 = a[6], + a21 = a[7], + a22 = a[8]; + var b01 = a22 * a11 - a12 * a21; + var b11 = -a22 * a10 + a12 * a20; + var b21 = a21 * a10 - a11 * a20; // Calculate the determinant + + var det = a00 * b01 + a01 * b11 + a02 * b21; + + if (!det) { + return null; + } + + det = 1.0 / det; + out[0] = b01 * det; + out[1] = (-a22 * a01 + a02 * a21) * det; + out[2] = (a12 * a01 - a02 * a11) * det; + out[3] = b11 * det; + out[4] = (a22 * a00 - a02 * a20) * det; + out[5] = (-a12 * a00 + a02 * a10) * det; + out[6] = b21 * det; + out[7] = (-a21 * a00 + a01 * a20) * det; + out[8] = (a11 * a00 - a01 * a10) * det; + return out; +} +/** + * Calculates the adjugate of a mat3 + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the source matrix + * @returns {mat3} out + */ + +export function adjoint(out, a) { + var a00 = a[0], + a01 = a[1], + a02 = a[2]; + var a10 = a[3], + a11 = a[4], + a12 = a[5]; + var a20 = a[6], + a21 = a[7], + a22 = a[8]; + out[0] = a11 * a22 - a12 * a21; + out[1] = a02 * a21 - a01 * a22; + out[2] = a01 * a12 - a02 * a11; + out[3] = a12 * a20 - a10 * a22; + out[4] = a00 * a22 - a02 * a20; + out[5] = a02 * a10 - a00 * a12; + out[6] = a10 * a21 - a11 * a20; + out[7] = a01 * a20 - a00 * a21; + out[8] = a00 * a11 - a01 * a10; + return out; +} +/** + * Calculates the determinant of a mat3 + * + * @param {ReadonlyMat3} a the source matrix + * @returns {Number} determinant of a + */ + +export function determinant(a) { + var a00 = a[0], + a01 = a[1], + a02 = a[2]; + var a10 = a[3], + a11 = a[4], + a12 = a[5]; + var a20 = a[6], + a21 = a[7], + a22 = a[8]; + return a00 * (a22 * a11 - a12 * a21) + a01 * (-a22 * a10 + a12 * a20) + a02 * (a21 * a10 - a11 * a20); +} +/** + * Multiplies two mat3's + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the first operand + * @param {ReadonlyMat3} b the second operand + * @returns {mat3} out + */ + +export function multiply(out, a, b) { + var a00 = a[0], + a01 = a[1], + a02 = a[2]; + var a10 = a[3], + a11 = a[4], + a12 = a[5]; + var a20 = a[6], + a21 = a[7], + a22 = a[8]; + var b00 = b[0], + b01 = b[1], + b02 = b[2]; + var b10 = b[3], + b11 = b[4], + b12 = b[5]; + var b20 = b[6], + b21 = b[7], + b22 = b[8]; + out[0] = b00 * a00 + b01 * a10 + b02 * a20; + out[1] = b00 * a01 + b01 * a11 + b02 * a21; + out[2] = b00 * a02 + b01 * a12 + b02 * a22; + out[3] = b10 * a00 + b11 * a10 + b12 * a20; + out[4] = b10 * a01 + b11 * a11 + b12 * a21; + out[5] = b10 * a02 + b11 * a12 + b12 * a22; + out[6] = b20 * a00 + b21 * a10 + b22 * a20; + out[7] = b20 * a01 + b21 * a11 + b22 * a21; + out[8] = b20 * a02 + b21 * a12 + b22 * a22; + return out; +} +/** + * Translate a mat3 by the given vector + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the matrix to translate + * @param {ReadonlyVec2} v vector to translate by + * @returns {mat3} out + */ + +export function translate(out, a, v) { + var a00 = a[0], + a01 = a[1], + a02 = a[2], + a10 = a[3], + a11 = a[4], + a12 = a[5], + a20 = a[6], + a21 = a[7], + a22 = a[8], + x = v[0], + y = v[1]; + out[0] = a00; + out[1] = a01; + out[2] = a02; + out[3] = a10; + out[4] = a11; + out[5] = a12; + out[6] = x * a00 + y * a10 + a20; + out[7] = x * a01 + y * a11 + a21; + out[8] = x * a02 + y * a12 + a22; + return out; +} +/** + * Rotates a mat3 by the given angle + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the matrix to rotate + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat3} out + */ + +export function rotate(out, a, rad) { + var a00 = a[0], + a01 = a[1], + a02 = a[2], + a10 = a[3], + a11 = a[4], + a12 = a[5], + a20 = a[6], + a21 = a[7], + a22 = a[8], + s = Math.sin(rad), + c = Math.cos(rad); + out[0] = c * a00 + s * a10; + out[1] = c * a01 + s * a11; + out[2] = c * a02 + s * a12; + out[3] = c * a10 - s * a00; + out[4] = c * a11 - s * a01; + out[5] = c * a12 - s * a02; + out[6] = a20; + out[7] = a21; + out[8] = a22; + return out; +} +/** + * Scales the mat3 by the dimensions in the given vec2 + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the matrix to rotate + * @param {ReadonlyVec2} v the vec2 to scale the matrix by + * @returns {mat3} out + **/ + +export function scale(out, a, v) { + var x = v[0], + y = v[1]; + out[0] = x * a[0]; + out[1] = x * a[1]; + out[2] = x * a[2]; + out[3] = y * a[3]; + out[4] = y * a[4]; + out[5] = y * a[5]; + out[6] = a[6]; + out[7] = a[7]; + out[8] = a[8]; + return out; +} +/** + * Creates a matrix from a vector translation + * This is equivalent to (but much faster than): + * + * mat3.identity(dest); + * mat3.translate(dest, dest, vec); + * + * @param {mat3} out mat3 receiving operation result + * @param {ReadonlyVec2} v Translation vector + * @returns {mat3} out + */ + +export function fromTranslation(out, v) { + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 1; + out[5] = 0; + out[6] = v[0]; + out[7] = v[1]; + out[8] = 1; + return out; +} +/** + * Creates a matrix from a given angle + * This is equivalent to (but much faster than): + * + * mat3.identity(dest); + * mat3.rotate(dest, dest, rad); + * + * @param {mat3} out mat3 receiving operation result + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat3} out + */ + +export function fromRotation(out, rad) { + var s = Math.sin(rad), + c = Math.cos(rad); + out[0] = c; + out[1] = s; + out[2] = 0; + out[3] = -s; + out[4] = c; + out[5] = 0; + out[6] = 0; + out[7] = 0; + out[8] = 1; + return out; +} +/** + * Creates a matrix from a vector scaling + * This is equivalent to (but much faster than): + * + * mat3.identity(dest); + * mat3.scale(dest, dest, vec); + * + * @param {mat3} out mat3 receiving operation result + * @param {ReadonlyVec2} v Scaling vector + * @returns {mat3} out + */ + +export function fromScaling(out, v) { + out[0] = v[0]; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = v[1]; + out[5] = 0; + out[6] = 0; + out[7] = 0; + out[8] = 1; + return out; +} +/** + * Copies the values from a mat2d into a mat3 + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat2d} a the matrix to copy + * @returns {mat3} out + **/ + +export function fromMat2d(out, a) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = 0; + out[3] = a[2]; + out[4] = a[3]; + out[5] = 0; + out[6] = a[4]; + out[7] = a[5]; + out[8] = 1; + return out; +} +/** + * Calculates a 3x3 matrix from the given quaternion + * + * @param {mat3} out mat3 receiving operation result + * @param {ReadonlyQuat} q Quaternion to create matrix from + * + * @returns {mat3} out + */ + +export function fromQuat(out, q) { + var x = q[0], + y = q[1], + z = q[2], + w = q[3]; + var x2 = x + x; + var y2 = y + y; + var z2 = z + z; + var xx = x * x2; + var yx = y * x2; + var yy = y * y2; + var zx = z * x2; + var zy = z * y2; + var zz = z * z2; + var wx = w * x2; + var wy = w * y2; + var wz = w * z2; + out[0] = 1 - yy - zz; + out[3] = yx - wz; + out[6] = zx + wy; + out[1] = yx + wz; + out[4] = 1 - xx - zz; + out[7] = zy - wx; + out[2] = zx - wy; + out[5] = zy + wx; + out[8] = 1 - xx - yy; + return out; +} +/** + * Calculates a 3x3 normal matrix (transpose inverse) from the 4x4 matrix + * + * @param {mat3} out mat3 receiving operation result + * @param {ReadonlyMat4} a Mat4 to derive the normal matrix from + * + * @returns {mat3} out + */ + +export function normalFromMat4(out, a) { + var a00 = a[0], + a01 = a[1], + a02 = a[2], + a03 = a[3]; + var a10 = a[4], + a11 = a[5], + a12 = a[6], + a13 = a[7]; + var a20 = a[8], + a21 = a[9], + a22 = a[10], + a23 = a[11]; + var a30 = a[12], + a31 = a[13], + a32 = a[14], + a33 = a[15]; + var b00 = a00 * a11 - a01 * a10; + var b01 = a00 * a12 - a02 * a10; + var b02 = a00 * a13 - a03 * a10; + var b03 = a01 * a12 - a02 * a11; + var b04 = a01 * a13 - a03 * a11; + var b05 = a02 * a13 - a03 * a12; + var b06 = a20 * a31 - a21 * a30; + var b07 = a20 * a32 - a22 * a30; + var b08 = a20 * a33 - a23 * a30; + var b09 = a21 * a32 - a22 * a31; + var b10 = a21 * a33 - a23 * a31; + var b11 = a22 * a33 - a23 * a32; // Calculate the determinant + + var det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + if (!det) { + return null; + } + + det = 1.0 / det; + out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; + out[1] = (a12 * b08 - a10 * b11 - a13 * b07) * det; + out[2] = (a10 * b10 - a11 * b08 + a13 * b06) * det; + out[3] = (a02 * b10 - a01 * b11 - a03 * b09) * det; + out[4] = (a00 * b11 - a02 * b08 + a03 * b07) * det; + out[5] = (a01 * b08 - a00 * b10 - a03 * b06) * det; + out[6] = (a31 * b05 - a32 * b04 + a33 * b03) * det; + out[7] = (a32 * b02 - a30 * b05 - a33 * b01) * det; + out[8] = (a30 * b04 - a31 * b02 + a33 * b00) * det; + return out; +} +/** + * Generates a 2D projection matrix with the given bounds + * + * @param {mat3} out mat3 frustum matrix will be written into + * @param {number} width Width of your gl context + * @param {number} height Height of gl context + * @returns {mat3} out + */ + +export function projection(out, width, height) { + out[0] = 2 / width; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = -2 / height; + out[5] = 0; + out[6] = -1; + out[7] = 1; + out[8] = 1; + return out; +} +/** + * Returns a string representation of a mat3 + * + * @param {ReadonlyMat3} a matrix to represent as a string + * @returns {String} string representation of the matrix + */ + +export function str(a) { + return "mat3(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ", " + a[4] + ", " + a[5] + ", " + a[6] + ", " + a[7] + ", " + a[8] + ")"; +} +/** + * Returns Frobenius norm of a mat3 + * + * @param {ReadonlyMat3} a the matrix to calculate Frobenius norm of + * @returns {Number} Frobenius norm + */ + +export function frob(a) { + return Math.hypot(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8]); +} +/** + * Adds two mat3's + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the first operand + * @param {ReadonlyMat3} b the second operand + * @returns {mat3} out + */ + +export function add(out, a, b) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; + out[3] = a[3] + b[3]; + out[4] = a[4] + b[4]; + out[5] = a[5] + b[5]; + out[6] = a[6] + b[6]; + out[7] = a[7] + b[7]; + out[8] = a[8] + b[8]; + return out; +} +/** + * Subtracts matrix b from matrix a + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the first operand + * @param {ReadonlyMat3} b the second operand + * @returns {mat3} out + */ + +export function subtract(out, a, b) { + out[0] = a[0] - b[0]; + out[1] = a[1] - b[1]; + out[2] = a[2] - b[2]; + out[3] = a[3] - b[3]; + out[4] = a[4] - b[4]; + out[5] = a[5] - b[5]; + out[6] = a[6] - b[6]; + out[7] = a[7] - b[7]; + out[8] = a[8] - b[8]; + return out; +} +/** + * Multiply each element of the matrix by a scalar. + * + * @param {mat3} out the receiving matrix + * @param {ReadonlyMat3} a the matrix to scale + * @param {Number} b amount to scale the matrix's elements by + * @returns {mat3} out + */ + +export function multiplyScalar(out, a, b) { + out[0] = a[0] * b; + out[1] = a[1] * b; + out[2] = a[2] * b; + out[3] = a[3] * b; + out[4] = a[4] * b; + out[5] = a[5] * b; + out[6] = a[6] * b; + out[7] = a[7] * b; + out[8] = a[8] * b; + return out; +} +/** + * Adds two mat3's after multiplying each element of the second operand by a scalar value. + * + * @param {mat3} out the receiving vector + * @param {ReadonlyMat3} a the first operand + * @param {ReadonlyMat3} b the second operand + * @param {Number} scale the amount to scale b's elements by before adding + * @returns {mat3} out + */ + +export function multiplyScalarAndAdd(out, a, b, scale) { + out[0] = a[0] + b[0] * scale; + out[1] = a[1] + b[1] * scale; + out[2] = a[2] + b[2] * scale; + out[3] = a[3] + b[3] * scale; + out[4] = a[4] + b[4] * scale; + out[5] = a[5] + b[5] * scale; + out[6] = a[6] + b[6] * scale; + out[7] = a[7] + b[7] * scale; + out[8] = a[8] + b[8] * scale; + return out; +} +/** + * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===) + * + * @param {ReadonlyMat3} a The first matrix. + * @param {ReadonlyMat3} b The second matrix. + * @returns {Boolean} True if the matrices are equal, false otherwise. + */ + +export function exactEquals(a, b) { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5] && a[6] === b[6] && a[7] === b[7] && a[8] === b[8]; +} +/** + * Returns whether or not the matrices have approximately the same elements in the same position. + * + * @param {ReadonlyMat3} a The first matrix. + * @param {ReadonlyMat3} b The second matrix. + * @returns {Boolean} True if the matrices are equal, false otherwise. + */ + +export function equals(a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3], + a4 = a[4], + a5 = a[5], + a6 = a[6], + a7 = a[7], + a8 = a[8]; + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3], + b4 = b[4], + b5 = b[5], + b6 = b[6], + b7 = b[7], + b8 = b[8]; + return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a5), Math.abs(b5)) && Math.abs(a6 - b6) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a6), Math.abs(b6)) && Math.abs(a7 - b7) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a7), Math.abs(b7)) && Math.abs(a8 - b8) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a8), Math.abs(b8)); +} +/** + * Alias for {@link mat3.multiply} + * @function + */ + +export var mul = multiply; +/** + * Alias for {@link mat3.subtract} + * @function + */ + +export var sub = subtract; \ No newline at end of file diff --git a/debug/webgpu/gl-matrix/mat4.js b/debug/webgpu/gl-matrix/mat4.js new file mode 100644 index 0000000000..16203051c5 --- /dev/null +++ b/debug/webgpu/gl-matrix/mat4.js @@ -0,0 +1,1987 @@ +import * as glMatrix from "./common.js"; +/** + * 4x4 Matrix
Format: column-major, when typed out it looks like row-major
The matrices are being post multiplied. + * @module mat4 + */ + +/** + * Creates a new identity mat4 + * + * @returns {mat4} a new 4x4 matrix + */ + +export function create() { + var out = new glMatrix.ARRAY_TYPE(16); + + if (glMatrix.ARRAY_TYPE != Float32Array) { + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + } + + out[0] = 1; + out[5] = 1; + out[10] = 1; + out[15] = 1; + return out; +} +/** + * Creates a new mat4 initialized with values from an existing matrix + * + * @param {ReadonlyMat4} a matrix to clone + * @returns {mat4} a new 4x4 matrix + */ + +export function clone(a) { + var out = new glMatrix.ARRAY_TYPE(16); + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + out[4] = a[4]; + out[5] = a[5]; + out[6] = a[6]; + out[7] = a[7]; + out[8] = a[8]; + out[9] = a[9]; + out[10] = a[10]; + out[11] = a[11]; + out[12] = a[12]; + out[13] = a[13]; + out[14] = a[14]; + out[15] = a[15]; + return out; +} +/** + * Copy the values from one mat4 to another + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the source matrix + * @returns {mat4} out + */ + +export function copy(out, a) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + out[4] = a[4]; + out[5] = a[5]; + out[6] = a[6]; + out[7] = a[7]; + out[8] = a[8]; + out[9] = a[9]; + out[10] = a[10]; + out[11] = a[11]; + out[12] = a[12]; + out[13] = a[13]; + out[14] = a[14]; + out[15] = a[15]; + return out; +} +/** + * Create a new mat4 with the given values + * + * @param {Number} m00 Component in column 0, row 0 position (index 0) + * @param {Number} m01 Component in column 0, row 1 position (index 1) + * @param {Number} m02 Component in column 0, row 2 position (index 2) + * @param {Number} m03 Component in column 0, row 3 position (index 3) + * @param {Number} m10 Component in column 1, row 0 position (index 4) + * @param {Number} m11 Component in column 1, row 1 position (index 5) + * @param {Number} m12 Component in column 1, row 2 position (index 6) + * @param {Number} m13 Component in column 1, row 3 position (index 7) + * @param {Number} m20 Component in column 2, row 0 position (index 8) + * @param {Number} m21 Component in column 2, row 1 position (index 9) + * @param {Number} m22 Component in column 2, row 2 position (index 10) + * @param {Number} m23 Component in column 2, row 3 position (index 11) + * @param {Number} m30 Component in column 3, row 0 position (index 12) + * @param {Number} m31 Component in column 3, row 1 position (index 13) + * @param {Number} m32 Component in column 3, row 2 position (index 14) + * @param {Number} m33 Component in column 3, row 3 position (index 15) + * @returns {mat4} A new mat4 + */ + +export function fromValues(m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) { + var out = new glMatrix.ARRAY_TYPE(16); + out[0] = m00; + out[1] = m01; + out[2] = m02; + out[3] = m03; + out[4] = m10; + out[5] = m11; + out[6] = m12; + out[7] = m13; + out[8] = m20; + out[9] = m21; + out[10] = m22; + out[11] = m23; + out[12] = m30; + out[13] = m31; + out[14] = m32; + out[15] = m33; + return out; +} +/** + * Set the components of a mat4 to the given values + * + * @param {mat4} out the receiving matrix + * @param {Number} m00 Component in column 0, row 0 position (index 0) + * @param {Number} m01 Component in column 0, row 1 position (index 1) + * @param {Number} m02 Component in column 0, row 2 position (index 2) + * @param {Number} m03 Component in column 0, row 3 position (index 3) + * @param {Number} m10 Component in column 1, row 0 position (index 4) + * @param {Number} m11 Component in column 1, row 1 position (index 5) + * @param {Number} m12 Component in column 1, row 2 position (index 6) + * @param {Number} m13 Component in column 1, row 3 position (index 7) + * @param {Number} m20 Component in column 2, row 0 position (index 8) + * @param {Number} m21 Component in column 2, row 1 position (index 9) + * @param {Number} m22 Component in column 2, row 2 position (index 10) + * @param {Number} m23 Component in column 2, row 3 position (index 11) + * @param {Number} m30 Component in column 3, row 0 position (index 12) + * @param {Number} m31 Component in column 3, row 1 position (index 13) + * @param {Number} m32 Component in column 3, row 2 position (index 14) + * @param {Number} m33 Component in column 3, row 3 position (index 15) + * @returns {mat4} out + */ + +export function set(out, m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33) { + out[0] = m00; + out[1] = m01; + out[2] = m02; + out[3] = m03; + out[4] = m10; + out[5] = m11; + out[6] = m12; + out[7] = m13; + out[8] = m20; + out[9] = m21; + out[10] = m22; + out[11] = m23; + out[12] = m30; + out[13] = m31; + out[14] = m32; + out[15] = m33; + return out; +} +/** + * Set a mat4 to the identity matrix + * + * @param {mat4} out the receiving matrix + * @returns {mat4} out + */ + +export function identity(out) { + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = 1; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[10] = 1; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + return out; +} +/** + * Transpose the values of a mat4 + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the source matrix + * @returns {mat4} out + */ + +export function transpose(out, a) { + // If we are transposing ourselves we can skip a few steps but have to cache some values + if (out === a) { + var a01 = a[1], + a02 = a[2], + a03 = a[3]; + var a12 = a[6], + a13 = a[7]; + var a23 = a[11]; + out[1] = a[4]; + out[2] = a[8]; + out[3] = a[12]; + out[4] = a01; + out[6] = a[9]; + out[7] = a[13]; + out[8] = a02; + out[9] = a12; + out[11] = a[14]; + out[12] = a03; + out[13] = a13; + out[14] = a23; + } else { + out[0] = a[0]; + out[1] = a[4]; + out[2] = a[8]; + out[3] = a[12]; + out[4] = a[1]; + out[5] = a[5]; + out[6] = a[9]; + out[7] = a[13]; + out[8] = a[2]; + out[9] = a[6]; + out[10] = a[10]; + out[11] = a[14]; + out[12] = a[3]; + out[13] = a[7]; + out[14] = a[11]; + out[15] = a[15]; + } + + return out; +} +/** + * Inverts a mat4 + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the source matrix + * @returns {mat4} out + */ + +export function invert(out, a) { + var a00 = a[0], + a01 = a[1], + a02 = a[2], + a03 = a[3]; + var a10 = a[4], + a11 = a[5], + a12 = a[6], + a13 = a[7]; + var a20 = a[8], + a21 = a[9], + a22 = a[10], + a23 = a[11]; + var a30 = a[12], + a31 = a[13], + a32 = a[14], + a33 = a[15]; + var b00 = a00 * a11 - a01 * a10; + var b01 = a00 * a12 - a02 * a10; + var b02 = a00 * a13 - a03 * a10; + var b03 = a01 * a12 - a02 * a11; + var b04 = a01 * a13 - a03 * a11; + var b05 = a02 * a13 - a03 * a12; + var b06 = a20 * a31 - a21 * a30; + var b07 = a20 * a32 - a22 * a30; + var b08 = a20 * a33 - a23 * a30; + var b09 = a21 * a32 - a22 * a31; + var b10 = a21 * a33 - a23 * a31; + var b11 = a22 * a33 - a23 * a32; // Calculate the determinant + + var det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; + + if (!det) { + return null; + } + + det = 1.0 / det; + out[0] = (a11 * b11 - a12 * b10 + a13 * b09) * det; + out[1] = (a02 * b10 - a01 * b11 - a03 * b09) * det; + out[2] = (a31 * b05 - a32 * b04 + a33 * b03) * det; + out[3] = (a22 * b04 - a21 * b05 - a23 * b03) * det; + out[4] = (a12 * b08 - a10 * b11 - a13 * b07) * det; + out[5] = (a00 * b11 - a02 * b08 + a03 * b07) * det; + out[6] = (a32 * b02 - a30 * b05 - a33 * b01) * det; + out[7] = (a20 * b05 - a22 * b02 + a23 * b01) * det; + out[8] = (a10 * b10 - a11 * b08 + a13 * b06) * det; + out[9] = (a01 * b08 - a00 * b10 - a03 * b06) * det; + out[10] = (a30 * b04 - a31 * b02 + a33 * b00) * det; + out[11] = (a21 * b02 - a20 * b04 - a23 * b00) * det; + out[12] = (a11 * b07 - a10 * b09 - a12 * b06) * det; + out[13] = (a00 * b09 - a01 * b07 + a02 * b06) * det; + out[14] = (a31 * b01 - a30 * b03 - a32 * b00) * det; + out[15] = (a20 * b03 - a21 * b01 + a22 * b00) * det; + return out; +} +/** + * Calculates the adjugate of a mat4 + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the source matrix + * @returns {mat4} out + */ + +export function adjoint(out, a) { + var a00 = a[0], + a01 = a[1], + a02 = a[2], + a03 = a[3]; + var a10 = a[4], + a11 = a[5], + a12 = a[6], + a13 = a[7]; + var a20 = a[8], + a21 = a[9], + a22 = a[10], + a23 = a[11]; + var a30 = a[12], + a31 = a[13], + a32 = a[14], + a33 = a[15]; + var b00 = a00 * a11 - a01 * a10; + var b01 = a00 * a12 - a02 * a10; + var b02 = a00 * a13 - a03 * a10; + var b03 = a01 * a12 - a02 * a11; + var b04 = a01 * a13 - a03 * a11; + var b05 = a02 * a13 - a03 * a12; + var b06 = a20 * a31 - a21 * a30; + var b07 = a20 * a32 - a22 * a30; + var b08 = a20 * a33 - a23 * a30; + var b09 = a21 * a32 - a22 * a31; + var b10 = a21 * a33 - a23 * a31; + var b11 = a22 * a33 - a23 * a32; + out[0] = a11 * b11 - a12 * b10 + a13 * b09; + out[1] = a02 * b10 - a01 * b11 - a03 * b09; + out[2] = a31 * b05 - a32 * b04 + a33 * b03; + out[3] = a22 * b04 - a21 * b05 - a23 * b03; + out[4] = a12 * b08 - a10 * b11 - a13 * b07; + out[5] = a00 * b11 - a02 * b08 + a03 * b07; + out[6] = a32 * b02 - a30 * b05 - a33 * b01; + out[7] = a20 * b05 - a22 * b02 + a23 * b01; + out[8] = a10 * b10 - a11 * b08 + a13 * b06; + out[9] = a01 * b08 - a00 * b10 - a03 * b06; + out[10] = a30 * b04 - a31 * b02 + a33 * b00; + out[11] = a21 * b02 - a20 * b04 - a23 * b00; + out[12] = a11 * b07 - a10 * b09 - a12 * b06; + out[13] = a00 * b09 - a01 * b07 + a02 * b06; + out[14] = a31 * b01 - a30 * b03 - a32 * b00; + out[15] = a20 * b03 - a21 * b01 + a22 * b00; + return out; +} +/** + * Calculates the determinant of a mat4 + * + * @param {ReadonlyMat4} a the source matrix + * @returns {Number} determinant of a + */ + +export function determinant(a) { + var a00 = a[0], + a01 = a[1], + a02 = a[2], + a03 = a[3]; + var a10 = a[4], + a11 = a[5], + a12 = a[6], + a13 = a[7]; + var a20 = a[8], + a21 = a[9], + a22 = a[10], + a23 = a[11]; + var a30 = a[12], + a31 = a[13], + a32 = a[14], + a33 = a[15]; + var b0 = a00 * a11 - a01 * a10; + var b1 = a00 * a12 - a02 * a10; + var b2 = a01 * a12 - a02 * a11; + var b3 = a20 * a31 - a21 * a30; + var b4 = a20 * a32 - a22 * a30; + var b5 = a21 * a32 - a22 * a31; + var b6 = a00 * b5 - a01 * b4 + a02 * b3; + var b7 = a10 * b5 - a11 * b4 + a12 * b3; + var b8 = a20 * b2 - a21 * b1 + a22 * b0; + var b9 = a30 * b2 - a31 * b1 + a32 * b0; // Calculate the determinant + + return a13 * b6 - a03 * b7 + a33 * b8 - a23 * b9; +} +/** + * Multiplies two mat4s + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the first operand + * @param {ReadonlyMat4} b the second operand + * @returns {mat4} out + */ + +export function multiply(out, a, b) { + var a00 = a[0], + a01 = a[1], + a02 = a[2], + a03 = a[3]; + var a10 = a[4], + a11 = a[5], + a12 = a[6], + a13 = a[7]; + var a20 = a[8], + a21 = a[9], + a22 = a[10], + a23 = a[11]; + var a30 = a[12], + a31 = a[13], + a32 = a[14], + a33 = a[15]; // Cache only the current line of the second matrix + + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3]; + out[0] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; + out[1] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; + out[2] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; + out[3] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; + b0 = b[4]; + b1 = b[5]; + b2 = b[6]; + b3 = b[7]; + out[4] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; + out[5] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; + out[6] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; + out[7] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; + b0 = b[8]; + b1 = b[9]; + b2 = b[10]; + b3 = b[11]; + out[8] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; + out[9] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; + out[10] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; + out[11] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; + b0 = b[12]; + b1 = b[13]; + b2 = b[14]; + b3 = b[15]; + out[12] = b0 * a00 + b1 * a10 + b2 * a20 + b3 * a30; + out[13] = b0 * a01 + b1 * a11 + b2 * a21 + b3 * a31; + out[14] = b0 * a02 + b1 * a12 + b2 * a22 + b3 * a32; + out[15] = b0 * a03 + b1 * a13 + b2 * a23 + b3 * a33; + return out; +} +/** + * Translate a mat4 by the given vector + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the matrix to translate + * @param {ReadonlyVec3} v vector to translate by + * @returns {mat4} out + */ + +export function translate(out, a, v) { + var x = v[0], + y = v[1], + z = v[2]; + var a00, a01, a02, a03; + var a10, a11, a12, a13; + var a20, a21, a22, a23; + + if (a === out) { + out[12] = a[0] * x + a[4] * y + a[8] * z + a[12]; + out[13] = a[1] * x + a[5] * y + a[9] * z + a[13]; + out[14] = a[2] * x + a[6] * y + a[10] * z + a[14]; + out[15] = a[3] * x + a[7] * y + a[11] * z + a[15]; + } else { + a00 = a[0]; + a01 = a[1]; + a02 = a[2]; + a03 = a[3]; + a10 = a[4]; + a11 = a[5]; + a12 = a[6]; + a13 = a[7]; + a20 = a[8]; + a21 = a[9]; + a22 = a[10]; + a23 = a[11]; + out[0] = a00; + out[1] = a01; + out[2] = a02; + out[3] = a03; + out[4] = a10; + out[5] = a11; + out[6] = a12; + out[7] = a13; + out[8] = a20; + out[9] = a21; + out[10] = a22; + out[11] = a23; + out[12] = a00 * x + a10 * y + a20 * z + a[12]; + out[13] = a01 * x + a11 * y + a21 * z + a[13]; + out[14] = a02 * x + a12 * y + a22 * z + a[14]; + out[15] = a03 * x + a13 * y + a23 * z + a[15]; + } + + return out; +} +/** + * Scales the mat4 by the dimensions in the given vec3 not using vectorization + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the matrix to scale + * @param {ReadonlyVec3} v the vec3 to scale the matrix by + * @returns {mat4} out + **/ + +export function scale(out, a, v) { + var x = v[0], + y = v[1], + z = v[2]; + out[0] = a[0] * x; + out[1] = a[1] * x; + out[2] = a[2] * x; + out[3] = a[3] * x; + out[4] = a[4] * y; + out[5] = a[5] * y; + out[6] = a[6] * y; + out[7] = a[7] * y; + out[8] = a[8] * z; + out[9] = a[9] * z; + out[10] = a[10] * z; + out[11] = a[11] * z; + out[12] = a[12]; + out[13] = a[13]; + out[14] = a[14]; + out[15] = a[15]; + return out; +} +/** + * Rotates a mat4 by the given angle around the given axis + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the matrix to rotate + * @param {Number} rad the angle to rotate the matrix by + * @param {ReadonlyVec3} axis the axis to rotate around + * @returns {mat4} out + */ + +export function rotate(out, a, rad, axis) { + var x = axis[0], + y = axis[1], + z = axis[2]; + var len = Math.hypot(x, y, z); + var s, c, t; + var a00, a01, a02, a03; + var a10, a11, a12, a13; + var a20, a21, a22, a23; + var b00, b01, b02; + var b10, b11, b12; + var b20, b21, b22; + + if (len < glMatrix.EPSILON) { + return null; + } + + len = 1 / len; + x *= len; + y *= len; + z *= len; + s = Math.sin(rad); + c = Math.cos(rad); + t = 1 - c; + a00 = a[0]; + a01 = a[1]; + a02 = a[2]; + a03 = a[3]; + a10 = a[4]; + a11 = a[5]; + a12 = a[6]; + a13 = a[7]; + a20 = a[8]; + a21 = a[9]; + a22 = a[10]; + a23 = a[11]; // Construct the elements of the rotation matrix + + b00 = x * x * t + c; + b01 = y * x * t + z * s; + b02 = z * x * t - y * s; + b10 = x * y * t - z * s; + b11 = y * y * t + c; + b12 = z * y * t + x * s; + b20 = x * z * t + y * s; + b21 = y * z * t - x * s; + b22 = z * z * t + c; // Perform rotation-specific matrix multiplication + + out[0] = a00 * b00 + a10 * b01 + a20 * b02; + out[1] = a01 * b00 + a11 * b01 + a21 * b02; + out[2] = a02 * b00 + a12 * b01 + a22 * b02; + out[3] = a03 * b00 + a13 * b01 + a23 * b02; + out[4] = a00 * b10 + a10 * b11 + a20 * b12; + out[5] = a01 * b10 + a11 * b11 + a21 * b12; + out[6] = a02 * b10 + a12 * b11 + a22 * b12; + out[7] = a03 * b10 + a13 * b11 + a23 * b12; + out[8] = a00 * b20 + a10 * b21 + a20 * b22; + out[9] = a01 * b20 + a11 * b21 + a21 * b22; + out[10] = a02 * b20 + a12 * b21 + a22 * b22; + out[11] = a03 * b20 + a13 * b21 + a23 * b22; + + if (a !== out) { + // If the source and destination differ, copy the unchanged last row + out[12] = a[12]; + out[13] = a[13]; + out[14] = a[14]; + out[15] = a[15]; + } + + return out; +} +/** + * Rotates a matrix by the given angle around the X axis + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the matrix to rotate + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat4} out + */ + +export function rotateX(out, a, rad) { + var s = Math.sin(rad); + var c = Math.cos(rad); + var a10 = a[4]; + var a11 = a[5]; + var a12 = a[6]; + var a13 = a[7]; + var a20 = a[8]; + var a21 = a[9]; + var a22 = a[10]; + var a23 = a[11]; + + if (a !== out) { + // If the source and destination differ, copy the unchanged rows + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + out[12] = a[12]; + out[13] = a[13]; + out[14] = a[14]; + out[15] = a[15]; + } // Perform axis-specific matrix multiplication + + + out[4] = a10 * c + a20 * s; + out[5] = a11 * c + a21 * s; + out[6] = a12 * c + a22 * s; + out[7] = a13 * c + a23 * s; + out[8] = a20 * c - a10 * s; + out[9] = a21 * c - a11 * s; + out[10] = a22 * c - a12 * s; + out[11] = a23 * c - a13 * s; + return out; +} +/** + * Rotates a matrix by the given angle around the Y axis + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the matrix to rotate + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat4} out + */ + +export function rotateY(out, a, rad) { + var s = Math.sin(rad); + var c = Math.cos(rad); + var a00 = a[0]; + var a01 = a[1]; + var a02 = a[2]; + var a03 = a[3]; + var a20 = a[8]; + var a21 = a[9]; + var a22 = a[10]; + var a23 = a[11]; + + if (a !== out) { + // If the source and destination differ, copy the unchanged rows + out[4] = a[4]; + out[5] = a[5]; + out[6] = a[6]; + out[7] = a[7]; + out[12] = a[12]; + out[13] = a[13]; + out[14] = a[14]; + out[15] = a[15]; + } // Perform axis-specific matrix multiplication + + + out[0] = a00 * c - a20 * s; + out[1] = a01 * c - a21 * s; + out[2] = a02 * c - a22 * s; + out[3] = a03 * c - a23 * s; + out[8] = a00 * s + a20 * c; + out[9] = a01 * s + a21 * c; + out[10] = a02 * s + a22 * c; + out[11] = a03 * s + a23 * c; + return out; +} +/** + * Rotates a matrix by the given angle around the Z axis + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the matrix to rotate + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat4} out + */ + +export function rotateZ(out, a, rad) { + var s = Math.sin(rad); + var c = Math.cos(rad); + var a00 = a[0]; + var a01 = a[1]; + var a02 = a[2]; + var a03 = a[3]; + var a10 = a[4]; + var a11 = a[5]; + var a12 = a[6]; + var a13 = a[7]; + + if (a !== out) { + // If the source and destination differ, copy the unchanged last row + out[8] = a[8]; + out[9] = a[9]; + out[10] = a[10]; + out[11] = a[11]; + out[12] = a[12]; + out[13] = a[13]; + out[14] = a[14]; + out[15] = a[15]; + } // Perform axis-specific matrix multiplication + + + out[0] = a00 * c + a10 * s; + out[1] = a01 * c + a11 * s; + out[2] = a02 * c + a12 * s; + out[3] = a03 * c + a13 * s; + out[4] = a10 * c - a00 * s; + out[5] = a11 * c - a01 * s; + out[6] = a12 * c - a02 * s; + out[7] = a13 * c - a03 * s; + return out; +} +/** + * Creates a matrix from a vector translation + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.translate(dest, dest, vec); + * + * @param {mat4} out mat4 receiving operation result + * @param {ReadonlyVec3} v Translation vector + * @returns {mat4} out + */ + +export function fromTranslation(out, v) { + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = 1; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[10] = 1; + out[11] = 0; + out[12] = v[0]; + out[13] = v[1]; + out[14] = v[2]; + out[15] = 1; + return out; +} +/** + * Creates a matrix from a vector scaling + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.scale(dest, dest, vec); + * + * @param {mat4} out mat4 receiving operation result + * @param {ReadonlyVec3} v Scaling vector + * @returns {mat4} out + */ + +export function fromScaling(out, v) { + out[0] = v[0]; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = v[1]; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[10] = v[2]; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + return out; +} +/** + * Creates a matrix from a given angle around a given axis + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.rotate(dest, dest, rad, axis); + * + * @param {mat4} out mat4 receiving operation result + * @param {Number} rad the angle to rotate the matrix by + * @param {ReadonlyVec3} axis the axis to rotate around + * @returns {mat4} out + */ + +export function fromRotation(out, rad, axis) { + var x = axis[0], + y = axis[1], + z = axis[2]; + var len = Math.hypot(x, y, z); + var s, c, t; + + if (len < glMatrix.EPSILON) { + return null; + } + + len = 1 / len; + x *= len; + y *= len; + z *= len; + s = Math.sin(rad); + c = Math.cos(rad); + t = 1 - c; // Perform rotation-specific matrix multiplication + + out[0] = x * x * t + c; + out[1] = y * x * t + z * s; + out[2] = z * x * t - y * s; + out[3] = 0; + out[4] = x * y * t - z * s; + out[5] = y * y * t + c; + out[6] = z * y * t + x * s; + out[7] = 0; + out[8] = x * z * t + y * s; + out[9] = y * z * t - x * s; + out[10] = z * z * t + c; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + return out; +} +/** + * Creates a matrix from the given angle around the X axis + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.rotateX(dest, dest, rad); + * + * @param {mat4} out mat4 receiving operation result + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat4} out + */ + +export function fromXRotation(out, rad) { + var s = Math.sin(rad); + var c = Math.cos(rad); // Perform axis-specific matrix multiplication + + out[0] = 1; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = c; + out[6] = s; + out[7] = 0; + out[8] = 0; + out[9] = -s; + out[10] = c; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + return out; +} +/** + * Creates a matrix from the given angle around the Y axis + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.rotateY(dest, dest, rad); + * + * @param {mat4} out mat4 receiving operation result + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat4} out + */ + +export function fromYRotation(out, rad) { + var s = Math.sin(rad); + var c = Math.cos(rad); // Perform axis-specific matrix multiplication + + out[0] = c; + out[1] = 0; + out[2] = -s; + out[3] = 0; + out[4] = 0; + out[5] = 1; + out[6] = 0; + out[7] = 0; + out[8] = s; + out[9] = 0; + out[10] = c; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + return out; +} +/** + * Creates a matrix from the given angle around the Z axis + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.rotateZ(dest, dest, rad); + * + * @param {mat4} out mat4 receiving operation result + * @param {Number} rad the angle to rotate the matrix by + * @returns {mat4} out + */ + +export function fromZRotation(out, rad) { + var s = Math.sin(rad); + var c = Math.cos(rad); // Perform axis-specific matrix multiplication + + out[0] = c; + out[1] = s; + out[2] = 0; + out[3] = 0; + out[4] = -s; + out[5] = c; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[10] = 1; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + return out; +} +/** + * Creates a matrix from a quaternion rotation and vector translation + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.translate(dest, vec); + * let quatMat = mat4.create(); + * quat4.toMat4(quat, quatMat); + * mat4.multiply(dest, quatMat); + * + * @param {mat4} out mat4 receiving operation result + * @param {quat4} q Rotation quaternion + * @param {ReadonlyVec3} v Translation vector + * @returns {mat4} out + */ + +export function fromRotationTranslation(out, q, v) { + // Quaternion math + var x = q[0], + y = q[1], + z = q[2], + w = q[3]; + var x2 = x + x; + var y2 = y + y; + var z2 = z + z; + var xx = x * x2; + var xy = x * y2; + var xz = x * z2; + var yy = y * y2; + var yz = y * z2; + var zz = z * z2; + var wx = w * x2; + var wy = w * y2; + var wz = w * z2; + out[0] = 1 - (yy + zz); + out[1] = xy + wz; + out[2] = xz - wy; + out[3] = 0; + out[4] = xy - wz; + out[5] = 1 - (xx + zz); + out[6] = yz + wx; + out[7] = 0; + out[8] = xz + wy; + out[9] = yz - wx; + out[10] = 1 - (xx + yy); + out[11] = 0; + out[12] = v[0]; + out[13] = v[1]; + out[14] = v[2]; + out[15] = 1; + return out; +} +/** + * Creates a new mat4 from a dual quat. + * + * @param {mat4} out Matrix + * @param {ReadonlyQuat2} a Dual Quaternion + * @returns {mat4} mat4 receiving operation result + */ + +export function fromQuat2(out, a) { + var translation = new glMatrix.ARRAY_TYPE(3); + var bx = -a[0], + by = -a[1], + bz = -a[2], + bw = a[3], + ax = a[4], + ay = a[5], + az = a[6], + aw = a[7]; + var magnitude = bx * bx + by * by + bz * bz + bw * bw; //Only scale if it makes sense + + if (magnitude > 0) { + translation[0] = (ax * bw + aw * bx + ay * bz - az * by) * 2 / magnitude; + translation[1] = (ay * bw + aw * by + az * bx - ax * bz) * 2 / magnitude; + translation[2] = (az * bw + aw * bz + ax * by - ay * bx) * 2 / magnitude; + } else { + translation[0] = (ax * bw + aw * bx + ay * bz - az * by) * 2; + translation[1] = (ay * bw + aw * by + az * bx - ax * bz) * 2; + translation[2] = (az * bw + aw * bz + ax * by - ay * bx) * 2; + } + + fromRotationTranslation(out, a, translation); + return out; +} +/** + * Returns the translation vector component of a transformation + * matrix. If a matrix is built with fromRotationTranslation, + * the returned vector will be the same as the translation vector + * originally supplied. + * @param {vec3} out Vector to receive translation component + * @param {ReadonlyMat4} mat Matrix to be decomposed (input) + * @return {vec3} out + */ + +export function getTranslation(out, mat) { + out[0] = mat[12]; + out[1] = mat[13]; + out[2] = mat[14]; + return out; +} +/** + * Returns the scaling factor component of a transformation + * matrix. If a matrix is built with fromRotationTranslationScale + * with a normalized Quaternion paramter, the returned vector will be + * the same as the scaling vector + * originally supplied. + * @param {vec3} out Vector to receive scaling factor component + * @param {ReadonlyMat4} mat Matrix to be decomposed (input) + * @return {vec3} out + */ + +export function getScaling(out, mat) { + var m11 = mat[0]; + var m12 = mat[1]; + var m13 = mat[2]; + var m21 = mat[4]; + var m22 = mat[5]; + var m23 = mat[6]; + var m31 = mat[8]; + var m32 = mat[9]; + var m33 = mat[10]; + out[0] = Math.hypot(m11, m12, m13); + out[1] = Math.hypot(m21, m22, m23); + out[2] = Math.hypot(m31, m32, m33); + return out; +} +/** + * Returns a quaternion representing the rotational component + * of a transformation matrix. If a matrix is built with + * fromRotationTranslation, the returned quaternion will be the + * same as the quaternion originally supplied. + * @param {quat} out Quaternion to receive the rotation component + * @param {ReadonlyMat4} mat Matrix to be decomposed (input) + * @return {quat} out + */ + +export function getRotation(out, mat) { + var scaling = new glMatrix.ARRAY_TYPE(3); + getScaling(scaling, mat); + var is1 = 1 / scaling[0]; + var is2 = 1 / scaling[1]; + var is3 = 1 / scaling[2]; + var sm11 = mat[0] * is1; + var sm12 = mat[1] * is2; + var sm13 = mat[2] * is3; + var sm21 = mat[4] * is1; + var sm22 = mat[5] * is2; + var sm23 = mat[6] * is3; + var sm31 = mat[8] * is1; + var sm32 = mat[9] * is2; + var sm33 = mat[10] * is3; + var trace = sm11 + sm22 + sm33; + var S = 0; + + if (trace > 0) { + S = Math.sqrt(trace + 1.0) * 2; + out[3] = 0.25 * S; + out[0] = (sm23 - sm32) / S; + out[1] = (sm31 - sm13) / S; + out[2] = (sm12 - sm21) / S; + } else if (sm11 > sm22 && sm11 > sm33) { + S = Math.sqrt(1.0 + sm11 - sm22 - sm33) * 2; + out[3] = (sm23 - sm32) / S; + out[0] = 0.25 * S; + out[1] = (sm12 + sm21) / S; + out[2] = (sm31 + sm13) / S; + } else if (sm22 > sm33) { + S = Math.sqrt(1.0 + sm22 - sm11 - sm33) * 2; + out[3] = (sm31 - sm13) / S; + out[0] = (sm12 + sm21) / S; + out[1] = 0.25 * S; + out[2] = (sm23 + sm32) / S; + } else { + S = Math.sqrt(1.0 + sm33 - sm11 - sm22) * 2; + out[3] = (sm12 - sm21) / S; + out[0] = (sm31 + sm13) / S; + out[1] = (sm23 + sm32) / S; + out[2] = 0.25 * S; + } + + return out; +} +/** + * Decomposes a transformation matrix into its rotation, translation + * and scale components. Returns only the rotation component + * @param {quat} out_r Quaternion to receive the rotation component + * @param {vec3} out_t Vector to receive the translation vector + * @param {vec3} out_s Vector to receive the scaling factor + * @param {ReadonlyMat4} mat Matrix to be decomposed (input) + * @returns {quat} out_r + */ + +export function decompose(out_r, out_t, out_s, mat) { + out_t[0] = mat[12]; + out_t[1] = mat[13]; + out_t[2] = mat[14]; + var m11 = mat[0]; + var m12 = mat[1]; + var m13 = mat[2]; + var m21 = mat[4]; + var m22 = mat[5]; + var m23 = mat[6]; + var m31 = mat[8]; + var m32 = mat[9]; + var m33 = mat[10]; + out_s[0] = Math.hypot(m11, m12, m13); + out_s[1] = Math.hypot(m21, m22, m23); + out_s[2] = Math.hypot(m31, m32, m33); + var is1 = 1 / out_s[0]; + var is2 = 1 / out_s[1]; + var is3 = 1 / out_s[2]; + var sm11 = m11 * is1; + var sm12 = m12 * is2; + var sm13 = m13 * is3; + var sm21 = m21 * is1; + var sm22 = m22 * is2; + var sm23 = m23 * is3; + var sm31 = m31 * is1; + var sm32 = m32 * is2; + var sm33 = m33 * is3; + var trace = sm11 + sm22 + sm33; + var S = 0; + + if (trace > 0) { + S = Math.sqrt(trace + 1.0) * 2; + out_r[3] = 0.25 * S; + out_r[0] = (sm23 - sm32) / S; + out_r[1] = (sm31 - sm13) / S; + out_r[2] = (sm12 - sm21) / S; + } else if (sm11 > sm22 && sm11 > sm33) { + S = Math.sqrt(1.0 + sm11 - sm22 - sm33) * 2; + out_r[3] = (sm23 - sm32) / S; + out_r[0] = 0.25 * S; + out_r[1] = (sm12 + sm21) / S; + out_r[2] = (sm31 + sm13) / S; + } else if (sm22 > sm33) { + S = Math.sqrt(1.0 + sm22 - sm11 - sm33) * 2; + out_r[3] = (sm31 - sm13) / S; + out_r[0] = (sm12 + sm21) / S; + out_r[1] = 0.25 * S; + out_r[2] = (sm23 + sm32) / S; + } else { + S = Math.sqrt(1.0 + sm33 - sm11 - sm22) * 2; + out_r[3] = (sm12 - sm21) / S; + out_r[0] = (sm31 + sm13) / S; + out_r[1] = (sm23 + sm32) / S; + out_r[2] = 0.25 * S; + } + + return out_r; +} +/** + * Creates a matrix from a quaternion rotation, vector translation and vector scale + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.translate(dest, vec); + * let quatMat = mat4.create(); + * quat4.toMat4(quat, quatMat); + * mat4.multiply(dest, quatMat); + * mat4.scale(dest, scale) + * + * @param {mat4} out mat4 receiving operation result + * @param {quat4} q Rotation quaternion + * @param {ReadonlyVec3} v Translation vector + * @param {ReadonlyVec3} s Scaling vector + * @returns {mat4} out + */ + +export function fromRotationTranslationScale(out, q, v, s) { + // Quaternion math + var x = q[0], + y = q[1], + z = q[2], + w = q[3]; + var x2 = x + x; + var y2 = y + y; + var z2 = z + z; + var xx = x * x2; + var xy = x * y2; + var xz = x * z2; + var yy = y * y2; + var yz = y * z2; + var zz = z * z2; + var wx = w * x2; + var wy = w * y2; + var wz = w * z2; + var sx = s[0]; + var sy = s[1]; + var sz = s[2]; + out[0] = (1 - (yy + zz)) * sx; + out[1] = (xy + wz) * sx; + out[2] = (xz - wy) * sx; + out[3] = 0; + out[4] = (xy - wz) * sy; + out[5] = (1 - (xx + zz)) * sy; + out[6] = (yz + wx) * sy; + out[7] = 0; + out[8] = (xz + wy) * sz; + out[9] = (yz - wx) * sz; + out[10] = (1 - (xx + yy)) * sz; + out[11] = 0; + out[12] = v[0]; + out[13] = v[1]; + out[14] = v[2]; + out[15] = 1; + return out; +} +/** + * Creates a matrix from a quaternion rotation, vector translation and vector scale, rotating and scaling around the given origin + * This is equivalent to (but much faster than): + * + * mat4.identity(dest); + * mat4.translate(dest, vec); + * mat4.translate(dest, origin); + * let quatMat = mat4.create(); + * quat4.toMat4(quat, quatMat); + * mat4.multiply(dest, quatMat); + * mat4.scale(dest, scale) + * mat4.translate(dest, negativeOrigin); + * + * @param {mat4} out mat4 receiving operation result + * @param {quat4} q Rotation quaternion + * @param {ReadonlyVec3} v Translation vector + * @param {ReadonlyVec3} s Scaling vector + * @param {ReadonlyVec3} o The origin vector around which to scale and rotate + * @returns {mat4} out + */ + +export function fromRotationTranslationScaleOrigin(out, q, v, s, o) { + // Quaternion math + var x = q[0], + y = q[1], + z = q[2], + w = q[3]; + var x2 = x + x; + var y2 = y + y; + var z2 = z + z; + var xx = x * x2; + var xy = x * y2; + var xz = x * z2; + var yy = y * y2; + var yz = y * z2; + var zz = z * z2; + var wx = w * x2; + var wy = w * y2; + var wz = w * z2; + var sx = s[0]; + var sy = s[1]; + var sz = s[2]; + var ox = o[0]; + var oy = o[1]; + var oz = o[2]; + var out0 = (1 - (yy + zz)) * sx; + var out1 = (xy + wz) * sx; + var out2 = (xz - wy) * sx; + var out4 = (xy - wz) * sy; + var out5 = (1 - (xx + zz)) * sy; + var out6 = (yz + wx) * sy; + var out8 = (xz + wy) * sz; + var out9 = (yz - wx) * sz; + var out10 = (1 - (xx + yy)) * sz; + out[0] = out0; + out[1] = out1; + out[2] = out2; + out[3] = 0; + out[4] = out4; + out[5] = out5; + out[6] = out6; + out[7] = 0; + out[8] = out8; + out[9] = out9; + out[10] = out10; + out[11] = 0; + out[12] = v[0] + ox - (out0 * ox + out4 * oy + out8 * oz); + out[13] = v[1] + oy - (out1 * ox + out5 * oy + out9 * oz); + out[14] = v[2] + oz - (out2 * ox + out6 * oy + out10 * oz); + out[15] = 1; + return out; +} +/** + * Calculates a 4x4 matrix from the given quaternion + * + * @param {mat4} out mat4 receiving operation result + * @param {ReadonlyQuat} q Quaternion to create matrix from + * + * @returns {mat4} out + */ + +export function fromQuat(out, q) { + var x = q[0], + y = q[1], + z = q[2], + w = q[3]; + var x2 = x + x; + var y2 = y + y; + var z2 = z + z; + var xx = x * x2; + var yx = y * x2; + var yy = y * y2; + var zx = z * x2; + var zy = z * y2; + var zz = z * z2; + var wx = w * x2; + var wy = w * y2; + var wz = w * z2; + out[0] = 1 - yy - zz; + out[1] = yx + wz; + out[2] = zx - wy; + out[3] = 0; + out[4] = yx - wz; + out[5] = 1 - xx - zz; + out[6] = zy + wx; + out[7] = 0; + out[8] = zx + wy; + out[9] = zy - wx; + out[10] = 1 - xx - yy; + out[11] = 0; + out[12] = 0; + out[13] = 0; + out[14] = 0; + out[15] = 1; + return out; +} +/** + * Generates a frustum matrix with the given bounds + * + * @param {mat4} out mat4 frustum matrix will be written into + * @param {Number} left Left bound of the frustum + * @param {Number} right Right bound of the frustum + * @param {Number} bottom Bottom bound of the frustum + * @param {Number} top Top bound of the frustum + * @param {Number} near Near bound of the frustum + * @param {Number} far Far bound of the frustum + * @returns {mat4} out + */ + +export function frustum(out, left, right, bottom, top, near, far) { + var rl = 1 / (right - left); + var tb = 1 / (top - bottom); + var nf = 1 / (near - far); + out[0] = near * 2 * rl; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = near * 2 * tb; + out[6] = 0; + out[7] = 0; + out[8] = (right + left) * rl; + out[9] = (top + bottom) * tb; + out[10] = (far + near) * nf; + out[11] = -1; + out[12] = 0; + out[13] = 0; + out[14] = far * near * 2 * nf; + out[15] = 0; + return out; +} +/** + * Generates a perspective projection matrix with the given bounds. + * The near/far clip planes correspond to a normalized device coordinate Z range of [-1, 1], + * which matches WebGL/OpenGL's clip volume. + * Passing null/undefined/no value for far will generate infinite projection matrix. + * + * @param {mat4} out mat4 frustum matrix will be written into + * @param {number} fovy Vertical field of view in radians + * @param {number} aspect Aspect ratio. typically viewport width/height + * @param {number} near Near bound of the frustum + * @param {number} far Far bound of the frustum, can be null or Infinity + * @returns {mat4} out + */ + +export function perspectiveNO(out, fovy, aspect, near, far) { + var f = 1.0 / Math.tan(fovy / 2); + out[0] = f / aspect; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = f; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[11] = -1; + out[12] = 0; + out[13] = 0; + out[15] = 0; + + if (far != null && far !== Infinity) { + var nf = 1 / (near - far); + out[10] = (far + near) * nf; + out[14] = 2 * far * near * nf; + } else { + out[10] = -1; + out[14] = -2 * near; + } + + return out; +} +/** + * Alias for {@link mat4.perspectiveNO} + * @function + */ + +export var perspective = perspectiveNO; +/** + * Generates a perspective projection matrix suitable for WebGPU with the given bounds. + * The near/far clip planes correspond to a normalized device coordinate Z range of [0, 1], + * which matches WebGPU/Vulkan/DirectX/Metal's clip volume. + * Passing null/undefined/no value for far will generate infinite projection matrix. + * + * @param {mat4} out mat4 frustum matrix will be written into + * @param {number} fovy Vertical field of view in radians + * @param {number} aspect Aspect ratio. typically viewport width/height + * @param {number} near Near bound of the frustum + * @param {number} far Far bound of the frustum, can be null or Infinity + * @returns {mat4} out + */ + +export function perspectiveZO(out, fovy, aspect, near, far) { + var f = 1.0 / Math.tan(fovy / 2); + out[0] = f / aspect; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = f; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[11] = -1; + out[12] = 0; + out[13] = 0; + out[15] = 0; + + if (far != null && far !== Infinity) { + var nf = 1 / (near - far); + out[10] = far * nf; + out[14] = far * near * nf; + } else { + out[10] = -1; + out[14] = -near; + } + + return out; +} +/** + * Generates a perspective projection matrix with the given field of view. + * This is primarily useful for generating projection matrices to be used + * with the still experiemental WebVR API. + * + * @param {mat4} out mat4 frustum matrix will be written into + * @param {Object} fov Object containing the following values: upDegrees, downDegrees, leftDegrees, rightDegrees + * @param {number} near Near bound of the frustum + * @param {number} far Far bound of the frustum + * @returns {mat4} out + */ + +export function perspectiveFromFieldOfView(out, fov, near, far) { + var upTan = Math.tan(fov.upDegrees * Math.PI / 180.0); + var downTan = Math.tan(fov.downDegrees * Math.PI / 180.0); + var leftTan = Math.tan(fov.leftDegrees * Math.PI / 180.0); + var rightTan = Math.tan(fov.rightDegrees * Math.PI / 180.0); + var xScale = 2.0 / (leftTan + rightTan); + var yScale = 2.0 / (upTan + downTan); + out[0] = xScale; + out[1] = 0.0; + out[2] = 0.0; + out[3] = 0.0; + out[4] = 0.0; + out[5] = yScale; + out[6] = 0.0; + out[7] = 0.0; + out[8] = -((leftTan - rightTan) * xScale * 0.5); + out[9] = (upTan - downTan) * yScale * 0.5; + out[10] = far / (near - far); + out[11] = -1.0; + out[12] = 0.0; + out[13] = 0.0; + out[14] = far * near / (near - far); + out[15] = 0.0; + return out; +} +/** + * Generates a orthogonal projection matrix with the given bounds. + * The near/far clip planes correspond to a normalized device coordinate Z range of [-1, 1], + * which matches WebGL/OpenGL's clip volume. + * + * @param {mat4} out mat4 frustum matrix will be written into + * @param {number} left Left bound of the frustum + * @param {number} right Right bound of the frustum + * @param {number} bottom Bottom bound of the frustum + * @param {number} top Top bound of the frustum + * @param {number} near Near bound of the frustum + * @param {number} far Far bound of the frustum + * @returns {mat4} out + */ + +export function orthoNO(out, left, right, bottom, top, near, far) { + var lr = 1 / (left - right); + var bt = 1 / (bottom - top); + var nf = 1 / (near - far); + out[0] = -2 * lr; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = -2 * bt; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[10] = 2 * nf; + out[11] = 0; + out[12] = (left + right) * lr; + out[13] = (top + bottom) * bt; + out[14] = (far + near) * nf; + out[15] = 1; + return out; +} +/** + * Alias for {@link mat4.orthoNO} + * @function + */ + +export var ortho = orthoNO; +/** + * Generates a orthogonal projection matrix with the given bounds. + * The near/far clip planes correspond to a normalized device coordinate Z range of [0, 1], + * which matches WebGPU/Vulkan/DirectX/Metal's clip volume. + * + * @param {mat4} out mat4 frustum matrix will be written into + * @param {number} left Left bound of the frustum + * @param {number} right Right bound of the frustum + * @param {number} bottom Bottom bound of the frustum + * @param {number} top Top bound of the frustum + * @param {number} near Near bound of the frustum + * @param {number} far Far bound of the frustum + * @returns {mat4} out + */ + +export function orthoZO(out, left, right, bottom, top, near, far) { + var lr = 1 / (left - right); + var bt = 1 / (bottom - top); + var nf = 1 / (near - far); + out[0] = -2 * lr; + out[1] = 0; + out[2] = 0; + out[3] = 0; + out[4] = 0; + out[5] = -2 * bt; + out[6] = 0; + out[7] = 0; + out[8] = 0; + out[9] = 0; + out[10] = nf; + out[11] = 0; + out[12] = (left + right) * lr; + out[13] = (top + bottom) * bt; + out[14] = near * nf; + out[15] = 1; + return out; +} +/** + * Generates a look-at matrix with the given eye position, focal point, and up axis. + * If you want a matrix that actually makes an object look at another object, you should use targetTo instead. + * + * @param {mat4} out mat4 frustum matrix will be written into + * @param {ReadonlyVec3} eye Position of the viewer + * @param {ReadonlyVec3} center Point the viewer is looking at + * @param {ReadonlyVec3} up vec3 pointing up + * @returns {mat4} out + */ + +export function lookAt(out, eye, center, up) { + var x0, x1, x2, y0, y1, y2, z0, z1, z2, len; + var eyex = eye[0]; + var eyey = eye[1]; + var eyez = eye[2]; + var upx = up[0]; + var upy = up[1]; + var upz = up[2]; + var centerx = center[0]; + var centery = center[1]; + var centerz = center[2]; + + if (Math.abs(eyex - centerx) < glMatrix.EPSILON && Math.abs(eyey - centery) < glMatrix.EPSILON && Math.abs(eyez - centerz) < glMatrix.EPSILON) { + return identity(out); + } + + z0 = eyex - centerx; + z1 = eyey - centery; + z2 = eyez - centerz; + len = 1 / Math.hypot(z0, z1, z2); + z0 *= len; + z1 *= len; + z2 *= len; + x0 = upy * z2 - upz * z1; + x1 = upz * z0 - upx * z2; + x2 = upx * z1 - upy * z0; + len = Math.hypot(x0, x1, x2); + + if (!len) { + x0 = 0; + x1 = 0; + x2 = 0; + } else { + len = 1 / len; + x0 *= len; + x1 *= len; + x2 *= len; + } + + y0 = z1 * x2 - z2 * x1; + y1 = z2 * x0 - z0 * x2; + y2 = z0 * x1 - z1 * x0; + len = Math.hypot(y0, y1, y2); + + if (!len) { + y0 = 0; + y1 = 0; + y2 = 0; + } else { + len = 1 / len; + y0 *= len; + y1 *= len; + y2 *= len; + } + + out[0] = x0; + out[1] = y0; + out[2] = z0; + out[3] = 0; + out[4] = x1; + out[5] = y1; + out[6] = z1; + out[7] = 0; + out[8] = x2; + out[9] = y2; + out[10] = z2; + out[11] = 0; + out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez); + out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez); + out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez); + out[15] = 1; + return out; +} +/** + * Generates a matrix that makes something look at something else. + * + * @param {mat4} out mat4 frustum matrix will be written into + * @param {ReadonlyVec3} eye Position of the viewer + * @param {ReadonlyVec3} center Point the viewer is looking at + * @param {ReadonlyVec3} up vec3 pointing up + * @returns {mat4} out + */ + +export function targetTo(out, eye, target, up) { + var eyex = eye[0], + eyey = eye[1], + eyez = eye[2], + upx = up[0], + upy = up[1], + upz = up[2]; + var z0 = eyex - target[0], + z1 = eyey - target[1], + z2 = eyez - target[2]; + var len = z0 * z0 + z1 * z1 + z2 * z2; + + if (len > 0) { + len = 1 / Math.sqrt(len); + z0 *= len; + z1 *= len; + z2 *= len; + } + + var x0 = upy * z2 - upz * z1, + x1 = upz * z0 - upx * z2, + x2 = upx * z1 - upy * z0; + len = x0 * x0 + x1 * x1 + x2 * x2; + + if (len > 0) { + len = 1 / Math.sqrt(len); + x0 *= len; + x1 *= len; + x2 *= len; + } + + out[0] = x0; + out[1] = x1; + out[2] = x2; + out[3] = 0; + out[4] = z1 * x2 - z2 * x1; + out[5] = z2 * x0 - z0 * x2; + out[6] = z0 * x1 - z1 * x0; + out[7] = 0; + out[8] = z0; + out[9] = z1; + out[10] = z2; + out[11] = 0; + out[12] = eyex; + out[13] = eyey; + out[14] = eyez; + out[15] = 1; + return out; +} +/** + * Returns a string representation of a mat4 + * + * @param {ReadonlyMat4} a matrix to represent as a string + * @returns {String} string representation of the matrix + */ + +export function str(a) { + return "mat4(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ", " + a[4] + ", " + a[5] + ", " + a[6] + ", " + a[7] + ", " + a[8] + ", " + a[9] + ", " + a[10] + ", " + a[11] + ", " + a[12] + ", " + a[13] + ", " + a[14] + ", " + a[15] + ")"; +} +/** + * Returns Frobenius norm of a mat4 + * + * @param {ReadonlyMat4} a the matrix to calculate Frobenius norm of + * @returns {Number} Frobenius norm + */ + +export function frob(a) { + return Math.hypot(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9], a[10], a[11], a[12], a[13], a[14], a[15]); +} +/** + * Adds two mat4's + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the first operand + * @param {ReadonlyMat4} b the second operand + * @returns {mat4} out + */ + +export function add(out, a, b) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; + out[3] = a[3] + b[3]; + out[4] = a[4] + b[4]; + out[5] = a[5] + b[5]; + out[6] = a[6] + b[6]; + out[7] = a[7] + b[7]; + out[8] = a[8] + b[8]; + out[9] = a[9] + b[9]; + out[10] = a[10] + b[10]; + out[11] = a[11] + b[11]; + out[12] = a[12] + b[12]; + out[13] = a[13] + b[13]; + out[14] = a[14] + b[14]; + out[15] = a[15] + b[15]; + return out; +} +/** + * Subtracts matrix b from matrix a + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the first operand + * @param {ReadonlyMat4} b the second operand + * @returns {mat4} out + */ + +export function subtract(out, a, b) { + out[0] = a[0] - b[0]; + out[1] = a[1] - b[1]; + out[2] = a[2] - b[2]; + out[3] = a[3] - b[3]; + out[4] = a[4] - b[4]; + out[5] = a[5] - b[5]; + out[6] = a[6] - b[6]; + out[7] = a[7] - b[7]; + out[8] = a[8] - b[8]; + out[9] = a[9] - b[9]; + out[10] = a[10] - b[10]; + out[11] = a[11] - b[11]; + out[12] = a[12] - b[12]; + out[13] = a[13] - b[13]; + out[14] = a[14] - b[14]; + out[15] = a[15] - b[15]; + return out; +} +/** + * Multiply each element of the matrix by a scalar. + * + * @param {mat4} out the receiving matrix + * @param {ReadonlyMat4} a the matrix to scale + * @param {Number} b amount to scale the matrix's elements by + * @returns {mat4} out + */ + +export function multiplyScalar(out, a, b) { + out[0] = a[0] * b; + out[1] = a[1] * b; + out[2] = a[2] * b; + out[3] = a[3] * b; + out[4] = a[4] * b; + out[5] = a[5] * b; + out[6] = a[6] * b; + out[7] = a[7] * b; + out[8] = a[8] * b; + out[9] = a[9] * b; + out[10] = a[10] * b; + out[11] = a[11] * b; + out[12] = a[12] * b; + out[13] = a[13] * b; + out[14] = a[14] * b; + out[15] = a[15] * b; + return out; +} +/** + * Adds two mat4's after multiplying each element of the second operand by a scalar value. + * + * @param {mat4} out the receiving vector + * @param {ReadonlyMat4} a the first operand + * @param {ReadonlyMat4} b the second operand + * @param {Number} scale the amount to scale b's elements by before adding + * @returns {mat4} out + */ + +export function multiplyScalarAndAdd(out, a, b, scale) { + out[0] = a[0] + b[0] * scale; + out[1] = a[1] + b[1] * scale; + out[2] = a[2] + b[2] * scale; + out[3] = a[3] + b[3] * scale; + out[4] = a[4] + b[4] * scale; + out[5] = a[5] + b[5] * scale; + out[6] = a[6] + b[6] * scale; + out[7] = a[7] + b[7] * scale; + out[8] = a[8] + b[8] * scale; + out[9] = a[9] + b[9] * scale; + out[10] = a[10] + b[10] * scale; + out[11] = a[11] + b[11] * scale; + out[12] = a[12] + b[12] * scale; + out[13] = a[13] + b[13] * scale; + out[14] = a[14] + b[14] * scale; + out[15] = a[15] + b[15] * scale; + return out; +} +/** + * Returns whether or not the matrices have exactly the same elements in the same position (when compared with ===) + * + * @param {ReadonlyMat4} a The first matrix. + * @param {ReadonlyMat4} b The second matrix. + * @returns {Boolean} True if the matrices are equal, false otherwise. + */ + +export function exactEquals(a, b) { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5] && a[6] === b[6] && a[7] === b[7] && a[8] === b[8] && a[9] === b[9] && a[10] === b[10] && a[11] === b[11] && a[12] === b[12] && a[13] === b[13] && a[14] === b[14] && a[15] === b[15]; +} +/** + * Returns whether or not the matrices have approximately the same elements in the same position. + * + * @param {ReadonlyMat4} a The first matrix. + * @param {ReadonlyMat4} b The second matrix. + * @returns {Boolean} True if the matrices are equal, false otherwise. + */ + +export function equals(a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3]; + var a4 = a[4], + a5 = a[5], + a6 = a[6], + a7 = a[7]; + var a8 = a[8], + a9 = a[9], + a10 = a[10], + a11 = a[11]; + var a12 = a[12], + a13 = a[13], + a14 = a[14], + a15 = a[15]; + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3]; + var b4 = b[4], + b5 = b[5], + b6 = b[6], + b7 = b[7]; + var b8 = b[8], + b9 = b[9], + b10 = b[10], + b11 = b[11]; + var b12 = b[12], + b13 = b[13], + b14 = b[14], + b15 = b[15]; + return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a5), Math.abs(b5)) && Math.abs(a6 - b6) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a6), Math.abs(b6)) && Math.abs(a7 - b7) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a7), Math.abs(b7)) && Math.abs(a8 - b8) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a8), Math.abs(b8)) && Math.abs(a9 - b9) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a9), Math.abs(b9)) && Math.abs(a10 - b10) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a10), Math.abs(b10)) && Math.abs(a11 - b11) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a11), Math.abs(b11)) && Math.abs(a12 - b12) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a12), Math.abs(b12)) && Math.abs(a13 - b13) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a13), Math.abs(b13)) && Math.abs(a14 - b14) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a14), Math.abs(b14)) && Math.abs(a15 - b15) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a15), Math.abs(b15)); +} +/** + * Alias for {@link mat4.multiply} + * @function + */ + +export var mul = multiply; +/** + * Alias for {@link mat4.subtract} + * @function + */ + +export var sub = subtract; \ No newline at end of file diff --git a/debug/webgpu/gl-matrix/quat.js b/debug/webgpu/gl-matrix/quat.js new file mode 100644 index 0000000000..fd18975135 --- /dev/null +++ b/debug/webgpu/gl-matrix/quat.js @@ -0,0 +1,760 @@ +import * as glMatrix from "./common.js"; +import * as mat3 from "./mat3.js"; +import * as vec3 from "./vec3.js"; +import * as vec4 from "./vec4.js"; +/** + * Quaternion in the format XYZW + * @module quat + */ + +/** + * Creates a new identity quat + * + * @returns {quat} a new quaternion + */ + +export function create() { + var out = new glMatrix.ARRAY_TYPE(4); + + if (glMatrix.ARRAY_TYPE != Float32Array) { + out[0] = 0; + out[1] = 0; + out[2] = 0; + } + + out[3] = 1; + return out; +} +/** + * Set a quat to the identity quaternion + * + * @param {quat} out the receiving quaternion + * @returns {quat} out + */ + +export function identity(out) { + out[0] = 0; + out[1] = 0; + out[2] = 0; + out[3] = 1; + return out; +} +/** + * Sets a quat from the given angle and rotation axis, + * then returns it. + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyVec3} axis the axis around which to rotate + * @param {Number} rad the angle in radians + * @returns {quat} out + **/ + +export function setAxisAngle(out, axis, rad) { + rad = rad * 0.5; + var s = Math.sin(rad); + out[0] = s * axis[0]; + out[1] = s * axis[1]; + out[2] = s * axis[2]; + out[3] = Math.cos(rad); + return out; +} +/** + * Gets the rotation axis and angle for a given + * quaternion. If a quaternion is created with + * setAxisAngle, this method will return the same + * values as providied in the original parameter list + * OR functionally equivalent values. + * Example: The quaternion formed by axis [0, 0, 1] and + * angle -90 is the same as the quaternion formed by + * [0, 0, 1] and 270. This method favors the latter. + * @param {vec3} out_axis Vector receiving the axis of rotation + * @param {ReadonlyQuat} q Quaternion to be decomposed + * @return {Number} Angle, in radians, of the rotation + */ + +export function getAxisAngle(out_axis, q) { + var rad = Math.acos(q[3]) * 2.0; + var s = Math.sin(rad / 2.0); + + if (s > glMatrix.EPSILON) { + out_axis[0] = q[0] / s; + out_axis[1] = q[1] / s; + out_axis[2] = q[2] / s; + } else { + // If s is zero, return any axis (no rotation - axis does not matter) + out_axis[0] = 1; + out_axis[1] = 0; + out_axis[2] = 0; + } + + return rad; +} +/** + * Gets the angular distance between two unit quaternions + * + * @param {ReadonlyQuat} a Origin unit quaternion + * @param {ReadonlyQuat} b Destination unit quaternion + * @return {Number} Angle, in radians, between the two quaternions + */ + +export function getAngle(a, b) { + var dotproduct = dot(a, b); + return Math.acos(2 * dotproduct * dotproduct - 1); +} +/** + * Multiplies two quat's + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a the first operand + * @param {ReadonlyQuat} b the second operand + * @returns {quat} out + */ + +export function multiply(out, a, b) { + var ax = a[0], + ay = a[1], + az = a[2], + aw = a[3]; + var bx = b[0], + by = b[1], + bz = b[2], + bw = b[3]; + out[0] = ax * bw + aw * bx + ay * bz - az * by; + out[1] = ay * bw + aw * by + az * bx - ax * bz; + out[2] = az * bw + aw * bz + ax * by - ay * bx; + out[3] = aw * bw - ax * bx - ay * by - az * bz; + return out; +} +/** + * Rotates a quaternion by the given angle about the X axis + * + * @param {quat} out quat receiving operation result + * @param {ReadonlyQuat} a quat to rotate + * @param {number} rad angle (in radians) to rotate + * @returns {quat} out + */ + +export function rotateX(out, a, rad) { + rad *= 0.5; + var ax = a[0], + ay = a[1], + az = a[2], + aw = a[3]; + var bx = Math.sin(rad), + bw = Math.cos(rad); + out[0] = ax * bw + aw * bx; + out[1] = ay * bw + az * bx; + out[2] = az * bw - ay * bx; + out[3] = aw * bw - ax * bx; + return out; +} +/** + * Rotates a quaternion by the given angle about the Y axis + * + * @param {quat} out quat receiving operation result + * @param {ReadonlyQuat} a quat to rotate + * @param {number} rad angle (in radians) to rotate + * @returns {quat} out + */ + +export function rotateY(out, a, rad) { + rad *= 0.5; + var ax = a[0], + ay = a[1], + az = a[2], + aw = a[3]; + var by = Math.sin(rad), + bw = Math.cos(rad); + out[0] = ax * bw - az * by; + out[1] = ay * bw + aw * by; + out[2] = az * bw + ax * by; + out[3] = aw * bw - ay * by; + return out; +} +/** + * Rotates a quaternion by the given angle about the Z axis + * + * @param {quat} out quat receiving operation result + * @param {ReadonlyQuat} a quat to rotate + * @param {number} rad angle (in radians) to rotate + * @returns {quat} out + */ + +export function rotateZ(out, a, rad) { + rad *= 0.5; + var ax = a[0], + ay = a[1], + az = a[2], + aw = a[3]; + var bz = Math.sin(rad), + bw = Math.cos(rad); + out[0] = ax * bw + ay * bz; + out[1] = ay * bw - ax * bz; + out[2] = az * bw + aw * bz; + out[3] = aw * bw - az * bz; + return out; +} +/** + * Calculates the W component of a quat from the X, Y, and Z components. + * Assumes that quaternion is 1 unit in length. + * Any existing W component will be ignored. + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a quat to calculate W component of + * @returns {quat} out + */ + +export function calculateW(out, a) { + var x = a[0], + y = a[1], + z = a[2]; + out[0] = x; + out[1] = y; + out[2] = z; + out[3] = Math.sqrt(Math.abs(1.0 - x * x - y * y - z * z)); + return out; +} +/** + * Calculate the exponential of a unit quaternion. + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a quat to calculate the exponential of + * @returns {quat} out + */ + +export function exp(out, a) { + var x = a[0], + y = a[1], + z = a[2], + w = a[3]; + var r = Math.sqrt(x * x + y * y + z * z); + var et = Math.exp(w); + var s = r > 0 ? et * Math.sin(r) / r : 0; + out[0] = x * s; + out[1] = y * s; + out[2] = z * s; + out[3] = et * Math.cos(r); + return out; +} +/** + * Calculate the natural logarithm of a unit quaternion. + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a quat to calculate the exponential of + * @returns {quat} out + */ + +export function ln(out, a) { + var x = a[0], + y = a[1], + z = a[2], + w = a[3]; + var r = Math.sqrt(x * x + y * y + z * z); + var t = r > 0 ? Math.atan2(r, w) / r : 0; + out[0] = x * t; + out[1] = y * t; + out[2] = z * t; + out[3] = 0.5 * Math.log(x * x + y * y + z * z + w * w); + return out; +} +/** + * Calculate the scalar power of a unit quaternion. + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a quat to calculate the exponential of + * @param {Number} b amount to scale the quaternion by + * @returns {quat} out + */ + +export function pow(out, a, b) { + ln(out, a); + scale(out, out, b); + exp(out, out); + return out; +} +/** + * Performs a spherical linear interpolation between two quat + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a the first operand + * @param {ReadonlyQuat} b the second operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {quat} out + */ + +export function slerp(out, a, b, t) { + // benchmarks: + // http://jsperf.com/quaternion-slerp-implementations + var ax = a[0], + ay = a[1], + az = a[2], + aw = a[3]; + var bx = b[0], + by = b[1], + bz = b[2], + bw = b[3]; + var omega, cosom, sinom, scale0, scale1; // calc cosine + + cosom = ax * bx + ay * by + az * bz + aw * bw; // adjust signs (if necessary) + + if (cosom < 0.0) { + cosom = -cosom; + bx = -bx; + by = -by; + bz = -bz; + bw = -bw; + } // calculate coefficients + + + if (1.0 - cosom > glMatrix.EPSILON) { + // standard case (slerp) + omega = Math.acos(cosom); + sinom = Math.sin(omega); + scale0 = Math.sin((1.0 - t) * omega) / sinom; + scale1 = Math.sin(t * omega) / sinom; + } else { + // "from" and "to" quaternions are very close + // ... so we can do a linear interpolation + scale0 = 1.0 - t; + scale1 = t; + } // calculate final values + + + out[0] = scale0 * ax + scale1 * bx; + out[1] = scale0 * ay + scale1 * by; + out[2] = scale0 * az + scale1 * bz; + out[3] = scale0 * aw + scale1 * bw; + return out; +} +/** + * Generates a random unit quaternion + * + * @param {quat} out the receiving quaternion + * @returns {quat} out + */ + +export function random(out) { + // Implementation of http://planning.cs.uiuc.edu/node198.html + // TODO: Calling random 3 times is probably not the fastest solution + var u1 = glMatrix.RANDOM(); + var u2 = glMatrix.RANDOM(); + var u3 = glMatrix.RANDOM(); + var sqrt1MinusU1 = Math.sqrt(1 - u1); + var sqrtU1 = Math.sqrt(u1); + out[0] = sqrt1MinusU1 * Math.sin(2.0 * Math.PI * u2); + out[1] = sqrt1MinusU1 * Math.cos(2.0 * Math.PI * u2); + out[2] = sqrtU1 * Math.sin(2.0 * Math.PI * u3); + out[3] = sqrtU1 * Math.cos(2.0 * Math.PI * u3); + return out; +} +/** + * Calculates the inverse of a quat + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a quat to calculate inverse of + * @returns {quat} out + */ + +export function invert(out, a) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3]; + var dot = a0 * a0 + a1 * a1 + a2 * a2 + a3 * a3; + var invDot = dot ? 1.0 / dot : 0; // TODO: Would be faster to return [0,0,0,0] immediately if dot == 0 + + out[0] = -a0 * invDot; + out[1] = -a1 * invDot; + out[2] = -a2 * invDot; + out[3] = a3 * invDot; + return out; +} +/** + * Calculates the conjugate of a quat + * If the quaternion is normalized, this function is faster than quat.inverse and produces the same result. + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a quat to calculate conjugate of + * @returns {quat} out + */ + +export function conjugate(out, a) { + out[0] = -a[0]; + out[1] = -a[1]; + out[2] = -a[2]; + out[3] = a[3]; + return out; +} +/** + * Creates a quaternion from the given 3x3 rotation matrix. + * + * NOTE: The resultant quaternion is not normalized, so you should be sure + * to renormalize the quaternion yourself where necessary. + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyMat3} m rotation matrix + * @returns {quat} out + * @function + */ + +export function fromMat3(out, m) { + // Algorithm in Ken Shoemake's article in 1987 SIGGRAPH course notes + // article "Quaternion Calculus and Fast Animation". + var fTrace = m[0] + m[4] + m[8]; + var fRoot; + + if (fTrace > 0.0) { + // |w| > 1/2, may as well choose w > 1/2 + fRoot = Math.sqrt(fTrace + 1.0); // 2w + + out[3] = 0.5 * fRoot; + fRoot = 0.5 / fRoot; // 1/(4w) + + out[0] = (m[5] - m[7]) * fRoot; + out[1] = (m[6] - m[2]) * fRoot; + out[2] = (m[1] - m[3]) * fRoot; + } else { + // |w| <= 1/2 + var i = 0; + if (m[4] > m[0]) i = 1; + if (m[8] > m[i * 3 + i]) i = 2; + var j = (i + 1) % 3; + var k = (i + 2) % 3; + fRoot = Math.sqrt(m[i * 3 + i] - m[j * 3 + j] - m[k * 3 + k] + 1.0); + out[i] = 0.5 * fRoot; + fRoot = 0.5 / fRoot; + out[3] = (m[j * 3 + k] - m[k * 3 + j]) * fRoot; + out[j] = (m[j * 3 + i] + m[i * 3 + j]) * fRoot; + out[k] = (m[k * 3 + i] + m[i * 3 + k]) * fRoot; + } + + return out; +} +/** + * Creates a quaternion from the given euler angle x, y, z using the provided intrinsic order for the conversion. + * + * @param {quat} out the receiving quaternion + * @param {x} x Angle to rotate around X axis in degrees. + * @param {y} y Angle to rotate around Y axis in degrees. + * @param {z} z Angle to rotate around Z axis in degrees. + * @param {'zyx'|'xyz'|'yxz'|'yzx'|'zxy'|'zyx'} order Intrinsic order for conversion, default is zyx. + * @returns {quat} out + * @function + */ + +export function fromEuler(out, x, y, z) { + var order = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : glMatrix.ANGLE_ORDER; + var halfToRad = Math.PI / 360; + x *= halfToRad; + z *= halfToRad; + y *= halfToRad; + var sx = Math.sin(x); + var cx = Math.cos(x); + var sy = Math.sin(y); + var cy = Math.cos(y); + var sz = Math.sin(z); + var cz = Math.cos(z); + + switch (order) { + case "xyz": + out[0] = sx * cy * cz + cx * sy * sz; + out[1] = cx * sy * cz - sx * cy * sz; + out[2] = cx * cy * sz + sx * sy * cz; + out[3] = cx * cy * cz - sx * sy * sz; + break; + + case "xzy": + out[0] = sx * cy * cz - cx * sy * sz; + out[1] = cx * sy * cz - sx * cy * sz; + out[2] = cx * cy * sz + sx * sy * cz; + out[3] = cx * cy * cz + sx * sy * sz; + break; + + case "yxz": + out[0] = sx * cy * cz + cx * sy * sz; + out[1] = cx * sy * cz - sx * cy * sz; + out[2] = cx * cy * sz - sx * sy * cz; + out[3] = cx * cy * cz + sx * sy * sz; + break; + + case "yzx": + out[0] = sx * cy * cz + cx * sy * sz; + out[1] = cx * sy * cz + sx * cy * sz; + out[2] = cx * cy * sz - sx * sy * cz; + out[3] = cx * cy * cz - sx * sy * sz; + break; + + case "zxy": + out[0] = sx * cy * cz - cx * sy * sz; + out[1] = cx * sy * cz + sx * cy * sz; + out[2] = cx * cy * sz + sx * sy * cz; + out[3] = cx * cy * cz - sx * sy * sz; + break; + + case "zyx": + out[0] = sx * cy * cz - cx * sy * sz; + out[1] = cx * sy * cz + sx * cy * sz; + out[2] = cx * cy * sz - sx * sy * cz; + out[3] = cx * cy * cz + sx * sy * sz; + break; + + default: + throw new Error('Unknown angle order ' + order); + } + + return out; +} +/** + * Returns a string representation of a quaternion + * + * @param {ReadonlyQuat} a vector to represent as a string + * @returns {String} string representation of the vector + */ + +export function str(a) { + return "quat(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ")"; +} +/** + * Creates a new quat initialized with values from an existing quaternion + * + * @param {ReadonlyQuat} a quaternion to clone + * @returns {quat} a new quaternion + * @function + */ + +export var clone = vec4.clone; +/** + * Creates a new quat initialized with the given values + * + * @param {Number} x X component + * @param {Number} y Y component + * @param {Number} z Z component + * @param {Number} w W component + * @returns {quat} a new quaternion + * @function + */ + +export var fromValues = vec4.fromValues; +/** + * Copy the values from one quat to another + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a the source quaternion + * @returns {quat} out + * @function + */ + +export var copy = vec4.copy; +/** + * Set the components of a quat to the given values + * + * @param {quat} out the receiving quaternion + * @param {Number} x X component + * @param {Number} y Y component + * @param {Number} z Z component + * @param {Number} w W component + * @returns {quat} out + * @function + */ + +export var set = vec4.set; +/** + * Adds two quat's + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a the first operand + * @param {ReadonlyQuat} b the second operand + * @returns {quat} out + * @function + */ + +export var add = vec4.add; +/** + * Alias for {@link quat.multiply} + * @function + */ + +export var mul = multiply; +/** + * Scales a quat by a scalar number + * + * @param {quat} out the receiving vector + * @param {ReadonlyQuat} a the vector to scale + * @param {Number} b amount to scale the vector by + * @returns {quat} out + * @function + */ + +export var scale = vec4.scale; +/** + * Calculates the dot product of two quat's + * + * @param {ReadonlyQuat} a the first operand + * @param {ReadonlyQuat} b the second operand + * @returns {Number} dot product of a and b + * @function + */ + +export var dot = vec4.dot; +/** + * Performs a linear interpolation between two quat's + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a the first operand + * @param {ReadonlyQuat} b the second operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {quat} out + * @function + */ + +export var lerp = vec4.lerp; +/** + * Calculates the length of a quat + * + * @param {ReadonlyQuat} a vector to calculate length of + * @returns {Number} length of a + */ + +export var length = vec4.length; +/** + * Alias for {@link quat.length} + * @function + */ + +export var len = length; +/** + * Calculates the squared length of a quat + * + * @param {ReadonlyQuat} a vector to calculate squared length of + * @returns {Number} squared length of a + * @function + */ + +export var squaredLength = vec4.squaredLength; +/** + * Alias for {@link quat.squaredLength} + * @function + */ + +export var sqrLen = squaredLength; +/** + * Normalize a quat + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a quaternion to normalize + * @returns {quat} out + * @function + */ + +export var normalize = vec4.normalize; +/** + * Returns whether or not the quaternions have exactly the same elements in the same position (when compared with ===) + * + * @param {ReadonlyQuat} a The first quaternion. + * @param {ReadonlyQuat} b The second quaternion. + * @returns {Boolean} True if the vectors are equal, false otherwise. + */ + +export var exactEquals = vec4.exactEquals; +/** + * Returns whether or not the quaternions point approximately to the same direction. + * + * Both quaternions are assumed to be unit length. + * + * @param {ReadonlyQuat} a The first unit quaternion. + * @param {ReadonlyQuat} b The second unit quaternion. + * @returns {Boolean} True if the quaternions are equal, false otherwise. + */ + +export function equals(a, b) { + return Math.abs(vec4.dot(a, b)) >= 1 - glMatrix.EPSILON; +} +/** + * Sets a quaternion to represent the shortest rotation from one + * vector to another. + * + * Both vectors are assumed to be unit length. + * + * @param {quat} out the receiving quaternion. + * @param {ReadonlyVec3} a the initial vector + * @param {ReadonlyVec3} b the destination vector + * @returns {quat} out + */ + +export var rotationTo = function () { + var tmpvec3 = vec3.create(); + var xUnitVec3 = vec3.fromValues(1, 0, 0); + var yUnitVec3 = vec3.fromValues(0, 1, 0); + return function (out, a, b) { + var dot = vec3.dot(a, b); + + if (dot < -0.999999) { + vec3.cross(tmpvec3, xUnitVec3, a); + if (vec3.len(tmpvec3) < 0.000001) vec3.cross(tmpvec3, yUnitVec3, a); + vec3.normalize(tmpvec3, tmpvec3); + setAxisAngle(out, tmpvec3, Math.PI); + return out; + } else if (dot > 0.999999) { + out[0] = 0; + out[1] = 0; + out[2] = 0; + out[3] = 1; + return out; + } else { + vec3.cross(tmpvec3, a, b); + out[0] = tmpvec3[0]; + out[1] = tmpvec3[1]; + out[2] = tmpvec3[2]; + out[3] = 1 + dot; + return normalize(out, out); + } + }; +}(); +/** + * Performs a spherical linear interpolation with two control points + * + * @param {quat} out the receiving quaternion + * @param {ReadonlyQuat} a the first operand + * @param {ReadonlyQuat} b the second operand + * @param {ReadonlyQuat} c the third operand + * @param {ReadonlyQuat} d the fourth operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {quat} out + */ + +export var sqlerp = function () { + var temp1 = create(); + var temp2 = create(); + return function (out, a, b, c, d, t) { + slerp(temp1, a, d, t); + slerp(temp2, b, c, t); + slerp(out, temp1, temp2, 2 * t * (1 - t)); + return out; + }; +}(); +/** + * Sets the specified quaternion with values corresponding to the given + * axes. Each axis is a vec3 and is expected to be unit length and + * perpendicular to all other specified axes. + * + * @param {ReadonlyVec3} view the vector representing the viewing direction + * @param {ReadonlyVec3} right the vector representing the local "right" direction + * @param {ReadonlyVec3} up the vector representing the local "up" direction + * @returns {quat} out + */ + +export var setAxes = function () { + var matr = mat3.create(); + return function (out, view, right, up) { + matr[0] = right[0]; + matr[3] = right[1]; + matr[6] = right[2]; + matr[1] = up[0]; + matr[4] = up[1]; + matr[7] = up[2]; + matr[2] = -view[0]; + matr[5] = -view[1]; + matr[8] = -view[2]; + return normalize(out, fromMat3(out, matr)); + }; +}(); \ No newline at end of file diff --git a/debug/webgpu/gl-matrix/quat2.js b/debug/webgpu/gl-matrix/quat2.js new file mode 100644 index 0000000000..a29b804df1 --- /dev/null +++ b/debug/webgpu/gl-matrix/quat2.js @@ -0,0 +1,835 @@ +import * as glMatrix from "./common.js"; +import * as quat from "./quat.js"; +import * as mat4 from "./mat4.js"; +/** + * Dual Quaternion
+ * Format: [real, dual]
+ * Quaternion format: XYZW
+ * Make sure to have normalized dual quaternions, otherwise the functions may not work as intended.
+ * @module quat2 + */ + +/** + * Creates a new identity dual quat + * + * @returns {quat2} a new dual quaternion [real -> rotation, dual -> translation] + */ + +export function create() { + var dq = new glMatrix.ARRAY_TYPE(8); + + if (glMatrix.ARRAY_TYPE != Float32Array) { + dq[0] = 0; + dq[1] = 0; + dq[2] = 0; + dq[4] = 0; + dq[5] = 0; + dq[6] = 0; + dq[7] = 0; + } + + dq[3] = 1; + return dq; +} +/** + * Creates a new quat initialized with values from an existing quaternion + * + * @param {ReadonlyQuat2} a dual quaternion to clone + * @returns {quat2} new dual quaternion + * @function + */ + +export function clone(a) { + var dq = new glMatrix.ARRAY_TYPE(8); + dq[0] = a[0]; + dq[1] = a[1]; + dq[2] = a[2]; + dq[3] = a[3]; + dq[4] = a[4]; + dq[5] = a[5]; + dq[6] = a[6]; + dq[7] = a[7]; + return dq; +} +/** + * Creates a new dual quat initialized with the given values + * + * @param {Number} x1 X component + * @param {Number} y1 Y component + * @param {Number} z1 Z component + * @param {Number} w1 W component + * @param {Number} x2 X component + * @param {Number} y2 Y component + * @param {Number} z2 Z component + * @param {Number} w2 W component + * @returns {quat2} new dual quaternion + * @function + */ + +export function fromValues(x1, y1, z1, w1, x2, y2, z2, w2) { + var dq = new glMatrix.ARRAY_TYPE(8); + dq[0] = x1; + dq[1] = y1; + dq[2] = z1; + dq[3] = w1; + dq[4] = x2; + dq[5] = y2; + dq[6] = z2; + dq[7] = w2; + return dq; +} +/** + * Creates a new dual quat from the given values (quat and translation) + * + * @param {Number} x1 X component + * @param {Number} y1 Y component + * @param {Number} z1 Z component + * @param {Number} w1 W component + * @param {Number} x2 X component (translation) + * @param {Number} y2 Y component (translation) + * @param {Number} z2 Z component (translation) + * @returns {quat2} new dual quaternion + * @function + */ + +export function fromRotationTranslationValues(x1, y1, z1, w1, x2, y2, z2) { + var dq = new glMatrix.ARRAY_TYPE(8); + dq[0] = x1; + dq[1] = y1; + dq[2] = z1; + dq[3] = w1; + var ax = x2 * 0.5, + ay = y2 * 0.5, + az = z2 * 0.5; + dq[4] = ax * w1 + ay * z1 - az * y1; + dq[5] = ay * w1 + az * x1 - ax * z1; + dq[6] = az * w1 + ax * y1 - ay * x1; + dq[7] = -ax * x1 - ay * y1 - az * z1; + return dq; +} +/** + * Creates a dual quat from a quaternion and a translation + * + * @param {ReadonlyQuat2} dual quaternion receiving operation result + * @param {ReadonlyQuat} q a normalized quaternion + * @param {ReadonlyVec3} t translation vector + * @returns {quat2} dual quaternion receiving operation result + * @function + */ + +export function fromRotationTranslation(out, q, t) { + var ax = t[0] * 0.5, + ay = t[1] * 0.5, + az = t[2] * 0.5, + bx = q[0], + by = q[1], + bz = q[2], + bw = q[3]; + out[0] = bx; + out[1] = by; + out[2] = bz; + out[3] = bw; + out[4] = ax * bw + ay * bz - az * by; + out[5] = ay * bw + az * bx - ax * bz; + out[6] = az * bw + ax * by - ay * bx; + out[7] = -ax * bx - ay * by - az * bz; + return out; +} +/** + * Creates a dual quat from a translation + * + * @param {ReadonlyQuat2} dual quaternion receiving operation result + * @param {ReadonlyVec3} t translation vector + * @returns {quat2} dual quaternion receiving operation result + * @function + */ + +export function fromTranslation(out, t) { + out[0] = 0; + out[1] = 0; + out[2] = 0; + out[3] = 1; + out[4] = t[0] * 0.5; + out[5] = t[1] * 0.5; + out[6] = t[2] * 0.5; + out[7] = 0; + return out; +} +/** + * Creates a dual quat from a quaternion + * + * @param {ReadonlyQuat2} dual quaternion receiving operation result + * @param {ReadonlyQuat} q the quaternion + * @returns {quat2} dual quaternion receiving operation result + * @function + */ + +export function fromRotation(out, q) { + out[0] = q[0]; + out[1] = q[1]; + out[2] = q[2]; + out[3] = q[3]; + out[4] = 0; + out[5] = 0; + out[6] = 0; + out[7] = 0; + return out; +} +/** + * Creates a new dual quat from a matrix (4x4) + * + * @param {quat2} out the dual quaternion + * @param {ReadonlyMat4} a the matrix + * @returns {quat2} dual quat receiving operation result + * @function + */ + +export function fromMat4(out, a) { + //TODO Optimize this + var outer = quat.create(); + mat4.getRotation(outer, a); + var t = new glMatrix.ARRAY_TYPE(3); + mat4.getTranslation(t, a); + fromRotationTranslation(out, outer, t); + return out; +} +/** + * Copy the values from one dual quat to another + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the source dual quaternion + * @returns {quat2} out + * @function + */ + +export function copy(out, a) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + out[4] = a[4]; + out[5] = a[5]; + out[6] = a[6]; + out[7] = a[7]; + return out; +} +/** + * Set a dual quat to the identity dual quaternion + * + * @param {quat2} out the receiving quaternion + * @returns {quat2} out + */ + +export function identity(out) { + out[0] = 0; + out[1] = 0; + out[2] = 0; + out[3] = 1; + out[4] = 0; + out[5] = 0; + out[6] = 0; + out[7] = 0; + return out; +} +/** + * Set the components of a dual quat to the given values + * + * @param {quat2} out the receiving quaternion + * @param {Number} x1 X component + * @param {Number} y1 Y component + * @param {Number} z1 Z component + * @param {Number} w1 W component + * @param {Number} x2 X component + * @param {Number} y2 Y component + * @param {Number} z2 Z component + * @param {Number} w2 W component + * @returns {quat2} out + * @function + */ + +export function set(out, x1, y1, z1, w1, x2, y2, z2, w2) { + out[0] = x1; + out[1] = y1; + out[2] = z1; + out[3] = w1; + out[4] = x2; + out[5] = y2; + out[6] = z2; + out[7] = w2; + return out; +} +/** + * Gets the real part of a dual quat + * @param {quat} out real part + * @param {ReadonlyQuat2} a Dual Quaternion + * @return {quat} real part + */ + +export var getReal = quat.copy; +/** + * Gets the dual part of a dual quat + * @param {quat} out dual part + * @param {ReadonlyQuat2} a Dual Quaternion + * @return {quat} dual part + */ + +export function getDual(out, a) { + out[0] = a[4]; + out[1] = a[5]; + out[2] = a[6]; + out[3] = a[7]; + return out; +} +/** + * Set the real component of a dual quat to the given quaternion + * + * @param {quat2} out the receiving quaternion + * @param {ReadonlyQuat} q a quaternion representing the real part + * @returns {quat2} out + * @function + */ + +export var setReal = quat.copy; +/** + * Set the dual component of a dual quat to the given quaternion + * + * @param {quat2} out the receiving quaternion + * @param {ReadonlyQuat} q a quaternion representing the dual part + * @returns {quat2} out + * @function + */ + +export function setDual(out, q) { + out[4] = q[0]; + out[5] = q[1]; + out[6] = q[2]; + out[7] = q[3]; + return out; +} +/** + * Gets the translation of a normalized dual quat + * @param {vec3} out translation + * @param {ReadonlyQuat2} a Dual Quaternion to be decomposed + * @return {vec3} translation + */ + +export function getTranslation(out, a) { + var ax = a[4], + ay = a[5], + az = a[6], + aw = a[7], + bx = -a[0], + by = -a[1], + bz = -a[2], + bw = a[3]; + out[0] = (ax * bw + aw * bx + ay * bz - az * by) * 2; + out[1] = (ay * bw + aw * by + az * bx - ax * bz) * 2; + out[2] = (az * bw + aw * bz + ax * by - ay * bx) * 2; + return out; +} +/** + * Translates a dual quat by the given vector + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the dual quaternion to translate + * @param {ReadonlyVec3} v vector to translate by + * @returns {quat2} out + */ + +export function translate(out, a, v) { + var ax1 = a[0], + ay1 = a[1], + az1 = a[2], + aw1 = a[3], + bx1 = v[0] * 0.5, + by1 = v[1] * 0.5, + bz1 = v[2] * 0.5, + ax2 = a[4], + ay2 = a[5], + az2 = a[6], + aw2 = a[7]; + out[0] = ax1; + out[1] = ay1; + out[2] = az1; + out[3] = aw1; + out[4] = aw1 * bx1 + ay1 * bz1 - az1 * by1 + ax2; + out[5] = aw1 * by1 + az1 * bx1 - ax1 * bz1 + ay2; + out[6] = aw1 * bz1 + ax1 * by1 - ay1 * bx1 + az2; + out[7] = -ax1 * bx1 - ay1 * by1 - az1 * bz1 + aw2; + return out; +} +/** + * Rotates a dual quat around the X axis + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the dual quaternion to rotate + * @param {number} rad how far should the rotation be + * @returns {quat2} out + */ + +export function rotateX(out, a, rad) { + var bx = -a[0], + by = -a[1], + bz = -a[2], + bw = a[3], + ax = a[4], + ay = a[5], + az = a[6], + aw = a[7], + ax1 = ax * bw + aw * bx + ay * bz - az * by, + ay1 = ay * bw + aw * by + az * bx - ax * bz, + az1 = az * bw + aw * bz + ax * by - ay * bx, + aw1 = aw * bw - ax * bx - ay * by - az * bz; + quat.rotateX(out, a, rad); + bx = out[0]; + by = out[1]; + bz = out[2]; + bw = out[3]; + out[4] = ax1 * bw + aw1 * bx + ay1 * bz - az1 * by; + out[5] = ay1 * bw + aw1 * by + az1 * bx - ax1 * bz; + out[6] = az1 * bw + aw1 * bz + ax1 * by - ay1 * bx; + out[7] = aw1 * bw - ax1 * bx - ay1 * by - az1 * bz; + return out; +} +/** + * Rotates a dual quat around the Y axis + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the dual quaternion to rotate + * @param {number} rad how far should the rotation be + * @returns {quat2} out + */ + +export function rotateY(out, a, rad) { + var bx = -a[0], + by = -a[1], + bz = -a[2], + bw = a[3], + ax = a[4], + ay = a[5], + az = a[6], + aw = a[7], + ax1 = ax * bw + aw * bx + ay * bz - az * by, + ay1 = ay * bw + aw * by + az * bx - ax * bz, + az1 = az * bw + aw * bz + ax * by - ay * bx, + aw1 = aw * bw - ax * bx - ay * by - az * bz; + quat.rotateY(out, a, rad); + bx = out[0]; + by = out[1]; + bz = out[2]; + bw = out[3]; + out[4] = ax1 * bw + aw1 * bx + ay1 * bz - az1 * by; + out[5] = ay1 * bw + aw1 * by + az1 * bx - ax1 * bz; + out[6] = az1 * bw + aw1 * bz + ax1 * by - ay1 * bx; + out[7] = aw1 * bw - ax1 * bx - ay1 * by - az1 * bz; + return out; +} +/** + * Rotates a dual quat around the Z axis + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the dual quaternion to rotate + * @param {number} rad how far should the rotation be + * @returns {quat2} out + */ + +export function rotateZ(out, a, rad) { + var bx = -a[0], + by = -a[1], + bz = -a[2], + bw = a[3], + ax = a[4], + ay = a[5], + az = a[6], + aw = a[7], + ax1 = ax * bw + aw * bx + ay * bz - az * by, + ay1 = ay * bw + aw * by + az * bx - ax * bz, + az1 = az * bw + aw * bz + ax * by - ay * bx, + aw1 = aw * bw - ax * bx - ay * by - az * bz; + quat.rotateZ(out, a, rad); + bx = out[0]; + by = out[1]; + bz = out[2]; + bw = out[3]; + out[4] = ax1 * bw + aw1 * bx + ay1 * bz - az1 * by; + out[5] = ay1 * bw + aw1 * by + az1 * bx - ax1 * bz; + out[6] = az1 * bw + aw1 * bz + ax1 * by - ay1 * bx; + out[7] = aw1 * bw - ax1 * bx - ay1 * by - az1 * bz; + return out; +} +/** + * Rotates a dual quat by a given quaternion (a * q) + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the dual quaternion to rotate + * @param {ReadonlyQuat} q quaternion to rotate by + * @returns {quat2} out + */ + +export function rotateByQuatAppend(out, a, q) { + var qx = q[0], + qy = q[1], + qz = q[2], + qw = q[3], + ax = a[0], + ay = a[1], + az = a[2], + aw = a[3]; + out[0] = ax * qw + aw * qx + ay * qz - az * qy; + out[1] = ay * qw + aw * qy + az * qx - ax * qz; + out[2] = az * qw + aw * qz + ax * qy - ay * qx; + out[3] = aw * qw - ax * qx - ay * qy - az * qz; + ax = a[4]; + ay = a[5]; + az = a[6]; + aw = a[7]; + out[4] = ax * qw + aw * qx + ay * qz - az * qy; + out[5] = ay * qw + aw * qy + az * qx - ax * qz; + out[6] = az * qw + aw * qz + ax * qy - ay * qx; + out[7] = aw * qw - ax * qx - ay * qy - az * qz; + return out; +} +/** + * Rotates a dual quat by a given quaternion (q * a) + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat} q quaternion to rotate by + * @param {ReadonlyQuat2} a the dual quaternion to rotate + * @returns {quat2} out + */ + +export function rotateByQuatPrepend(out, q, a) { + var qx = q[0], + qy = q[1], + qz = q[2], + qw = q[3], + bx = a[0], + by = a[1], + bz = a[2], + bw = a[3]; + out[0] = qx * bw + qw * bx + qy * bz - qz * by; + out[1] = qy * bw + qw * by + qz * bx - qx * bz; + out[2] = qz * bw + qw * bz + qx * by - qy * bx; + out[3] = qw * bw - qx * bx - qy * by - qz * bz; + bx = a[4]; + by = a[5]; + bz = a[6]; + bw = a[7]; + out[4] = qx * bw + qw * bx + qy * bz - qz * by; + out[5] = qy * bw + qw * by + qz * bx - qx * bz; + out[6] = qz * bw + qw * bz + qx * by - qy * bx; + out[7] = qw * bw - qx * bx - qy * by - qz * bz; + return out; +} +/** + * Rotates a dual quat around a given axis. Does the normalisation automatically + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the dual quaternion to rotate + * @param {ReadonlyVec3} axis the axis to rotate around + * @param {Number} rad how far the rotation should be + * @returns {quat2} out + */ + +export function rotateAroundAxis(out, a, axis, rad) { + //Special case for rad = 0 + if (Math.abs(rad) < glMatrix.EPSILON) { + return copy(out, a); + } + + var axisLength = Math.hypot(axis[0], axis[1], axis[2]); + rad = rad * 0.5; + var s = Math.sin(rad); + var bx = s * axis[0] / axisLength; + var by = s * axis[1] / axisLength; + var bz = s * axis[2] / axisLength; + var bw = Math.cos(rad); + var ax1 = a[0], + ay1 = a[1], + az1 = a[2], + aw1 = a[3]; + out[0] = ax1 * bw + aw1 * bx + ay1 * bz - az1 * by; + out[1] = ay1 * bw + aw1 * by + az1 * bx - ax1 * bz; + out[2] = az1 * bw + aw1 * bz + ax1 * by - ay1 * bx; + out[3] = aw1 * bw - ax1 * bx - ay1 * by - az1 * bz; + var ax = a[4], + ay = a[5], + az = a[6], + aw = a[7]; + out[4] = ax * bw + aw * bx + ay * bz - az * by; + out[5] = ay * bw + aw * by + az * bx - ax * bz; + out[6] = az * bw + aw * bz + ax * by - ay * bx; + out[7] = aw * bw - ax * bx - ay * by - az * bz; + return out; +} +/** + * Adds two dual quat's + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the first operand + * @param {ReadonlyQuat2} b the second operand + * @returns {quat2} out + * @function + */ + +export function add(out, a, b) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; + out[3] = a[3] + b[3]; + out[4] = a[4] + b[4]; + out[5] = a[5] + b[5]; + out[6] = a[6] + b[6]; + out[7] = a[7] + b[7]; + return out; +} +/** + * Multiplies two dual quat's + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a the first operand + * @param {ReadonlyQuat2} b the second operand + * @returns {quat2} out + */ + +export function multiply(out, a, b) { + var ax0 = a[0], + ay0 = a[1], + az0 = a[2], + aw0 = a[3], + bx1 = b[4], + by1 = b[5], + bz1 = b[6], + bw1 = b[7], + ax1 = a[4], + ay1 = a[5], + az1 = a[6], + aw1 = a[7], + bx0 = b[0], + by0 = b[1], + bz0 = b[2], + bw0 = b[3]; + out[0] = ax0 * bw0 + aw0 * bx0 + ay0 * bz0 - az0 * by0; + out[1] = ay0 * bw0 + aw0 * by0 + az0 * bx0 - ax0 * bz0; + out[2] = az0 * bw0 + aw0 * bz0 + ax0 * by0 - ay0 * bx0; + out[3] = aw0 * bw0 - ax0 * bx0 - ay0 * by0 - az0 * bz0; + out[4] = ax0 * bw1 + aw0 * bx1 + ay0 * bz1 - az0 * by1 + ax1 * bw0 + aw1 * bx0 + ay1 * bz0 - az1 * by0; + out[5] = ay0 * bw1 + aw0 * by1 + az0 * bx1 - ax0 * bz1 + ay1 * bw0 + aw1 * by0 + az1 * bx0 - ax1 * bz0; + out[6] = az0 * bw1 + aw0 * bz1 + ax0 * by1 - ay0 * bx1 + az1 * bw0 + aw1 * bz0 + ax1 * by0 - ay1 * bx0; + out[7] = aw0 * bw1 - ax0 * bx1 - ay0 * by1 - az0 * bz1 + aw1 * bw0 - ax1 * bx0 - ay1 * by0 - az1 * bz0; + return out; +} +/** + * Alias for {@link quat2.multiply} + * @function + */ + +export var mul = multiply; +/** + * Scales a dual quat by a scalar number + * + * @param {quat2} out the receiving dual quat + * @param {ReadonlyQuat2} a the dual quat to scale + * @param {Number} b amount to scale the dual quat by + * @returns {quat2} out + * @function + */ + +export function scale(out, a, b) { + out[0] = a[0] * b; + out[1] = a[1] * b; + out[2] = a[2] * b; + out[3] = a[3] * b; + out[4] = a[4] * b; + out[5] = a[5] * b; + out[6] = a[6] * b; + out[7] = a[7] * b; + return out; +} +/** + * Calculates the dot product of two dual quat's (The dot product of the real parts) + * + * @param {ReadonlyQuat2} a the first operand + * @param {ReadonlyQuat2} b the second operand + * @returns {Number} dot product of a and b + * @function + */ + +export var dot = quat.dot; +/** + * Performs a linear interpolation between two dual quats's + * NOTE: The resulting dual quaternions won't always be normalized (The error is most noticeable when t = 0.5) + * + * @param {quat2} out the receiving dual quat + * @param {ReadonlyQuat2} a the first operand + * @param {ReadonlyQuat2} b the second operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {quat2} out + */ + +export function lerp(out, a, b, t) { + var mt = 1 - t; + if (dot(a, b) < 0) t = -t; + out[0] = a[0] * mt + b[0] * t; + out[1] = a[1] * mt + b[1] * t; + out[2] = a[2] * mt + b[2] * t; + out[3] = a[3] * mt + b[3] * t; + out[4] = a[4] * mt + b[4] * t; + out[5] = a[5] * mt + b[5] * t; + out[6] = a[6] * mt + b[6] * t; + out[7] = a[7] * mt + b[7] * t; + return out; +} +/** + * Calculates the inverse of a dual quat. If they are normalized, conjugate is cheaper + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a dual quat to calculate inverse of + * @returns {quat2} out + */ + +export function invert(out, a) { + var sqlen = squaredLength(a); + out[0] = -a[0] / sqlen; + out[1] = -a[1] / sqlen; + out[2] = -a[2] / sqlen; + out[3] = a[3] / sqlen; + out[4] = -a[4] / sqlen; + out[5] = -a[5] / sqlen; + out[6] = -a[6] / sqlen; + out[7] = a[7] / sqlen; + return out; +} +/** + * Calculates the conjugate of a dual quat + * If the dual quaternion is normalized, this function is faster than quat2.inverse and produces the same result. + * + * @param {quat2} out the receiving quaternion + * @param {ReadonlyQuat2} a quat to calculate conjugate of + * @returns {quat2} out + */ + +export function conjugate(out, a) { + out[0] = -a[0]; + out[1] = -a[1]; + out[2] = -a[2]; + out[3] = a[3]; + out[4] = -a[4]; + out[5] = -a[5]; + out[6] = -a[6]; + out[7] = a[7]; + return out; +} +/** + * Calculates the length of a dual quat + * + * @param {ReadonlyQuat2} a dual quat to calculate length of + * @returns {Number} length of a + * @function + */ + +export var length = quat.length; +/** + * Alias for {@link quat2.length} + * @function + */ + +export var len = length; +/** + * Calculates the squared length of a dual quat + * + * @param {ReadonlyQuat2} a dual quat to calculate squared length of + * @returns {Number} squared length of a + * @function + */ + +export var squaredLength = quat.squaredLength; +/** + * Alias for {@link quat2.squaredLength} + * @function + */ + +export var sqrLen = squaredLength; +/** + * Normalize a dual quat + * + * @param {quat2} out the receiving dual quaternion + * @param {ReadonlyQuat2} a dual quaternion to normalize + * @returns {quat2} out + * @function + */ + +export function normalize(out, a) { + var magnitude = squaredLength(a); + + if (magnitude > 0) { + magnitude = Math.sqrt(magnitude); + var a0 = a[0] / magnitude; + var a1 = a[1] / magnitude; + var a2 = a[2] / magnitude; + var a3 = a[3] / magnitude; + var b0 = a[4]; + var b1 = a[5]; + var b2 = a[6]; + var b3 = a[7]; + var a_dot_b = a0 * b0 + a1 * b1 + a2 * b2 + a3 * b3; + out[0] = a0; + out[1] = a1; + out[2] = a2; + out[3] = a3; + out[4] = (b0 - a0 * a_dot_b) / magnitude; + out[5] = (b1 - a1 * a_dot_b) / magnitude; + out[6] = (b2 - a2 * a_dot_b) / magnitude; + out[7] = (b3 - a3 * a_dot_b) / magnitude; + } + + return out; +} +/** + * Returns a string representation of a dual quaternion + * + * @param {ReadonlyQuat2} a dual quaternion to represent as a string + * @returns {String} string representation of the dual quat + */ + +export function str(a) { + return "quat2(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ", " + a[4] + ", " + a[5] + ", " + a[6] + ", " + a[7] + ")"; +} +/** + * Returns whether or not the dual quaternions have exactly the same elements in the same position (when compared with ===) + * + * @param {ReadonlyQuat2} a the first dual quaternion. + * @param {ReadonlyQuat2} b the second dual quaternion. + * @returns {Boolean} true if the dual quaternions are equal, false otherwise. + */ + +export function exactEquals(a, b) { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3] && a[4] === b[4] && a[5] === b[5] && a[6] === b[6] && a[7] === b[7]; +} +/** + * Returns whether or not the dual quaternions have approximately the same elements in the same position. + * + * @param {ReadonlyQuat2} a the first dual quat. + * @param {ReadonlyQuat2} b the second dual quat. + * @returns {Boolean} true if the dual quats are equal, false otherwise. + */ + +export function equals(a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3], + a4 = a[4], + a5 = a[5], + a6 = a[6], + a7 = a[7]; + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3], + b4 = b[4], + b5 = b[5], + b6 = b[6], + b7 = b[7]; + return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)) && Math.abs(a4 - b4) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a4), Math.abs(b4)) && Math.abs(a5 - b5) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a5), Math.abs(b5)) && Math.abs(a6 - b6) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a6), Math.abs(b6)) && Math.abs(a7 - b7) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a7), Math.abs(b7)); +} \ No newline at end of file diff --git a/debug/webgpu/gl-matrix/vec2.js b/debug/webgpu/gl-matrix/vec2.js new file mode 100644 index 0000000000..ec22f91e4f --- /dev/null +++ b/debug/webgpu/gl-matrix/vec2.js @@ -0,0 +1,624 @@ +import * as glMatrix from "./common.js"; +/** + * 2 Dimensional Vector + * @module vec2 + */ + +/** + * Creates a new, empty vec2 + * + * @returns {vec2} a new 2D vector + */ + +export function create() { + var out = new glMatrix.ARRAY_TYPE(2); + + if (glMatrix.ARRAY_TYPE != Float32Array) { + out[0] = 0; + out[1] = 0; + } + + return out; +} +/** + * Creates a new vec2 initialized with values from an existing vector + * + * @param {ReadonlyVec2} a vector to clone + * @returns {vec2} a new 2D vector + */ + +export function clone(a) { + var out = new glMatrix.ARRAY_TYPE(2); + out[0] = a[0]; + out[1] = a[1]; + return out; +} +/** + * Creates a new vec2 initialized with the given values + * + * @param {Number} x X component + * @param {Number} y Y component + * @returns {vec2} a new 2D vector + */ + +export function fromValues(x, y) { + var out = new glMatrix.ARRAY_TYPE(2); + out[0] = x; + out[1] = y; + return out; +} +/** + * Copy the values from one vec2 to another + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the source vector + * @returns {vec2} out + */ + +export function copy(out, a) { + out[0] = a[0]; + out[1] = a[1]; + return out; +} +/** + * Set the components of a vec2 to the given values + * + * @param {vec2} out the receiving vector + * @param {Number} x X component + * @param {Number} y Y component + * @returns {vec2} out + */ + +export function set(out, x, y) { + out[0] = x; + out[1] = y; + return out; +} +/** + * Adds two vec2's + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {vec2} out + */ + +export function add(out, a, b) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + return out; +} +/** + * Subtracts vector b from vector a + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {vec2} out + */ + +export function subtract(out, a, b) { + out[0] = a[0] - b[0]; + out[1] = a[1] - b[1]; + return out; +} +/** + * Multiplies two vec2's + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {vec2} out + */ + +export function multiply(out, a, b) { + out[0] = a[0] * b[0]; + out[1] = a[1] * b[1]; + return out; +} +/** + * Divides two vec2's + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {vec2} out + */ + +export function divide(out, a, b) { + out[0] = a[0] / b[0]; + out[1] = a[1] / b[1]; + return out; +} +/** + * Math.ceil the components of a vec2 + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a vector to ceil + * @returns {vec2} out + */ + +export function ceil(out, a) { + out[0] = Math.ceil(a[0]); + out[1] = Math.ceil(a[1]); + return out; +} +/** + * Math.floor the components of a vec2 + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a vector to floor + * @returns {vec2} out + */ + +export function floor(out, a) { + out[0] = Math.floor(a[0]); + out[1] = Math.floor(a[1]); + return out; +} +/** + * Returns the minimum of two vec2's + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {vec2} out + */ + +export function min(out, a, b) { + out[0] = Math.min(a[0], b[0]); + out[1] = Math.min(a[1], b[1]); + return out; +} +/** + * Returns the maximum of two vec2's + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {vec2} out + */ + +export function max(out, a, b) { + out[0] = Math.max(a[0], b[0]); + out[1] = Math.max(a[1], b[1]); + return out; +} +/** + * Math.round the components of a vec2 + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a vector to round + * @returns {vec2} out + */ + +export function round(out, a) { + out[0] = Math.round(a[0]); + out[1] = Math.round(a[1]); + return out; +} +/** + * Scales a vec2 by a scalar number + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the vector to scale + * @param {Number} b amount to scale the vector by + * @returns {vec2} out + */ + +export function scale(out, a, b) { + out[0] = a[0] * b; + out[1] = a[1] * b; + return out; +} +/** + * Adds two vec2's after scaling the second operand by a scalar value + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @param {Number} scale the amount to scale b by before adding + * @returns {vec2} out + */ + +export function scaleAndAdd(out, a, b, scale) { + out[0] = a[0] + b[0] * scale; + out[1] = a[1] + b[1] * scale; + return out; +} +/** + * Calculates the euclidian distance between two vec2's + * + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {Number} distance between a and b + */ + +export function distance(a, b) { + var x = b[0] - a[0], + y = b[1] - a[1]; + return Math.hypot(x, y); +} +/** + * Calculates the squared euclidian distance between two vec2's + * + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {Number} squared distance between a and b + */ + +export function squaredDistance(a, b) { + var x = b[0] - a[0], + y = b[1] - a[1]; + return x * x + y * y; +} +/** + * Calculates the length of a vec2 + * + * @param {ReadonlyVec2} a vector to calculate length of + * @returns {Number} length of a + */ + +export function length(a) { + var x = a[0], + y = a[1]; + return Math.hypot(x, y); +} +/** + * Calculates the squared length of a vec2 + * + * @param {ReadonlyVec2} a vector to calculate squared length of + * @returns {Number} squared length of a + */ + +export function squaredLength(a) { + var x = a[0], + y = a[1]; + return x * x + y * y; +} +/** + * Negates the components of a vec2 + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a vector to negate + * @returns {vec2} out + */ + +export function negate(out, a) { + out[0] = -a[0]; + out[1] = -a[1]; + return out; +} +/** + * Returns the inverse of the components of a vec2 + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a vector to invert + * @returns {vec2} out + */ + +export function inverse(out, a) { + out[0] = 1.0 / a[0]; + out[1] = 1.0 / a[1]; + return out; +} +/** + * Normalize a vec2 + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a vector to normalize + * @returns {vec2} out + */ + +export function normalize(out, a) { + var x = a[0], + y = a[1]; + var len = x * x + y * y; + + if (len > 0) { + //TODO: evaluate use of glm_invsqrt here? + len = 1 / Math.sqrt(len); + } + + out[0] = a[0] * len; + out[1] = a[1] * len; + return out; +} +/** + * Calculates the dot product of two vec2's + * + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {Number} dot product of a and b + */ + +export function dot(a, b) { + return a[0] * b[0] + a[1] * b[1]; +} +/** + * Computes the cross product of two vec2's + * Note that the cross product must by definition produce a 3D vector + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @returns {vec3} out + */ + +export function cross(out, a, b) { + var z = a[0] * b[1] - a[1] * b[0]; + out[0] = out[1] = 0; + out[2] = z; + return out; +} +/** + * Performs a linear interpolation between two vec2's + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the first operand + * @param {ReadonlyVec2} b the second operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {vec2} out + */ + +export function lerp(out, a, b, t) { + var ax = a[0], + ay = a[1]; + out[0] = ax + t * (b[0] - ax); + out[1] = ay + t * (b[1] - ay); + return out; +} +/** + * Generates a random vector with the given scale + * + * @param {vec2} out the receiving vector + * @param {Number} [scale] Length of the resulting vector. If omitted, a unit vector will be returned + * @returns {vec2} out + */ + +export function random(out, scale) { + scale = scale === undefined ? 1.0 : scale; + var r = glMatrix.RANDOM() * 2.0 * Math.PI; + out[0] = Math.cos(r) * scale; + out[1] = Math.sin(r) * scale; + return out; +} +/** + * Transforms the vec2 with a mat2 + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the vector to transform + * @param {ReadonlyMat2} m matrix to transform with + * @returns {vec2} out + */ + +export function transformMat2(out, a, m) { + var x = a[0], + y = a[1]; + out[0] = m[0] * x + m[2] * y; + out[1] = m[1] * x + m[3] * y; + return out; +} +/** + * Transforms the vec2 with a mat2d + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the vector to transform + * @param {ReadonlyMat2d} m matrix to transform with + * @returns {vec2} out + */ + +export function transformMat2d(out, a, m) { + var x = a[0], + y = a[1]; + out[0] = m[0] * x + m[2] * y + m[4]; + out[1] = m[1] * x + m[3] * y + m[5]; + return out; +} +/** + * Transforms the vec2 with a mat3 + * 3rd vector component is implicitly '1' + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the vector to transform + * @param {ReadonlyMat3} m matrix to transform with + * @returns {vec2} out + */ + +export function transformMat3(out, a, m) { + var x = a[0], + y = a[1]; + out[0] = m[0] * x + m[3] * y + m[6]; + out[1] = m[1] * x + m[4] * y + m[7]; + return out; +} +/** + * Transforms the vec2 with a mat4 + * 3rd vector component is implicitly '0' + * 4th vector component is implicitly '1' + * + * @param {vec2} out the receiving vector + * @param {ReadonlyVec2} a the vector to transform + * @param {ReadonlyMat4} m matrix to transform with + * @returns {vec2} out + */ + +export function transformMat4(out, a, m) { + var x = a[0]; + var y = a[1]; + out[0] = m[0] * x + m[4] * y + m[12]; + out[1] = m[1] * x + m[5] * y + m[13]; + return out; +} +/** + * Rotate a 2D vector + * @param {vec2} out The receiving vec2 + * @param {ReadonlyVec2} a The vec2 point to rotate + * @param {ReadonlyVec2} b The origin of the rotation + * @param {Number} rad The angle of rotation in radians + * @returns {vec2} out + */ + +export function rotate(out, a, b, rad) { + //Translate point to the origin + var p0 = a[0] - b[0], + p1 = a[1] - b[1], + sinC = Math.sin(rad), + cosC = Math.cos(rad); //perform rotation and translate to correct position + + out[0] = p0 * cosC - p1 * sinC + b[0]; + out[1] = p0 * sinC + p1 * cosC + b[1]; + return out; +} +/** + * Get the angle between two 2D vectors + * @param {ReadonlyVec2} a The first operand + * @param {ReadonlyVec2} b The second operand + * @returns {Number} The angle in radians + */ + +export function angle(a, b) { + var x1 = a[0], + y1 = a[1], + x2 = b[0], + y2 = b[1], + // mag is the product of the magnitudes of a and b + mag = Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2)), + // mag &&.. short circuits if mag == 0 + cosine = mag && (x1 * x2 + y1 * y2) / mag; // Math.min(Math.max(cosine, -1), 1) clamps the cosine between -1 and 1 + + return Math.acos(Math.min(Math.max(cosine, -1), 1)); +} +/** + * Set the components of a vec2 to zero + * + * @param {vec2} out the receiving vector + * @returns {vec2} out + */ + +export function zero(out) { + out[0] = 0.0; + out[1] = 0.0; + return out; +} +/** + * Returns a string representation of a vector + * + * @param {ReadonlyVec2} a vector to represent as a string + * @returns {String} string representation of the vector + */ + +export function str(a) { + return "vec2(" + a[0] + ", " + a[1] + ")"; +} +/** + * Returns whether or not the vectors exactly have the same elements in the same position (when compared with ===) + * + * @param {ReadonlyVec2} a The first vector. + * @param {ReadonlyVec2} b The second vector. + * @returns {Boolean} True if the vectors are equal, false otherwise. + */ + +export function exactEquals(a, b) { + return a[0] === b[0] && a[1] === b[1]; +} +/** + * Returns whether or not the vectors have approximately the same elements in the same position. + * + * @param {ReadonlyVec2} a The first vector. + * @param {ReadonlyVec2} b The second vector. + * @returns {Boolean} True if the vectors are equal, false otherwise. + */ + +export function equals(a, b) { + var a0 = a[0], + a1 = a[1]; + var b0 = b[0], + b1 = b[1]; + return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)); +} +/** + * Alias for {@link vec2.length} + * @function + */ + +export var len = length; +/** + * Alias for {@link vec2.subtract} + * @function + */ + +export var sub = subtract; +/** + * Alias for {@link vec2.multiply} + * @function + */ + +export var mul = multiply; +/** + * Alias for {@link vec2.divide} + * @function + */ + +export var div = divide; +/** + * Alias for {@link vec2.distance} + * @function + */ + +export var dist = distance; +/** + * Alias for {@link vec2.squaredDistance} + * @function + */ + +export var sqrDist = squaredDistance; +/** + * Alias for {@link vec2.squaredLength} + * @function + */ + +export var sqrLen = squaredLength; +/** + * Perform some operation over an array of vec2s. + * + * @param {Array} a the array of vectors to iterate over + * @param {Number} stride Number of elements between the start of each vec2. If 0 assumes tightly packed + * @param {Number} offset Number of elements to skip at the beginning of the array + * @param {Number} count Number of vec2s to iterate over. If 0 iterates over entire array + * @param {Function} fn Function to call for each vector in the array + * @param {Object} [arg] additional argument to pass to fn + * @returns {Array} a + * @function + */ + +export var forEach = function () { + var vec = create(); + return function (a, stride, offset, count, fn, arg) { + var i, l; + + if (!stride) { + stride = 2; + } + + if (!offset) { + offset = 0; + } + + if (count) { + l = Math.min(count * stride + offset, a.length); + } else { + l = a.length; + } + + for (i = offset; i < l; i += stride) { + vec[0] = a[i]; + vec[1] = a[i + 1]; + fn(vec, vec, arg); + a[i] = vec[0]; + a[i + 1] = vec[1]; + } + + return a; + }; +}(); \ No newline at end of file diff --git a/debug/webgpu/gl-matrix/vec3.js b/debug/webgpu/gl-matrix/vec3.js new file mode 100644 index 0000000000..7f07ef5d95 --- /dev/null +++ b/debug/webgpu/gl-matrix/vec3.js @@ -0,0 +1,805 @@ +import * as glMatrix from "./common.js"; +/** + * 3 Dimensional Vector + * @module vec3 + */ + +/** + * Creates a new, empty vec3 + * + * @returns {vec3} a new 3D vector + */ + +export function create() { + var out = new glMatrix.ARRAY_TYPE(3); + + if (glMatrix.ARRAY_TYPE != Float32Array) { + out[0] = 0; + out[1] = 0; + out[2] = 0; + } + + return out; +} +/** + * Creates a new vec3 initialized with values from an existing vector + * + * @param {ReadonlyVec3} a vector to clone + * @returns {vec3} a new 3D vector + */ + +export function clone(a) { + var out = new glMatrix.ARRAY_TYPE(3); + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + return out; +} +/** + * Calculates the length of a vec3 + * + * @param {ReadonlyVec3} a vector to calculate length of + * @returns {Number} length of a + */ + +export function length(a) { + var x = a[0]; + var y = a[1]; + var z = a[2]; + return Math.hypot(x, y, z); +} +/** + * Creates a new vec3 initialized with the given values + * + * @param {Number} x X component + * @param {Number} y Y component + * @param {Number} z Z component + * @returns {vec3} a new 3D vector + */ + +export function fromValues(x, y, z) { + var out = new glMatrix.ARRAY_TYPE(3); + out[0] = x; + out[1] = y; + out[2] = z; + return out; +} +/** + * Copy the values from one vec3 to another + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the source vector + * @returns {vec3} out + */ + +export function copy(out, a) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + return out; +} +/** + * Set the components of a vec3 to the given values + * + * @param {vec3} out the receiving vector + * @param {Number} x X component + * @param {Number} y Y component + * @param {Number} z Z component + * @returns {vec3} out + */ + +export function set(out, x, y, z) { + out[0] = x; + out[1] = y; + out[2] = z; + return out; +} +/** + * Adds two vec3's + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {vec3} out + */ + +export function add(out, a, b) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; + return out; +} +/** + * Subtracts vector b from vector a + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {vec3} out + */ + +export function subtract(out, a, b) { + out[0] = a[0] - b[0]; + out[1] = a[1] - b[1]; + out[2] = a[2] - b[2]; + return out; +} +/** + * Multiplies two vec3's + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {vec3} out + */ + +export function multiply(out, a, b) { + out[0] = a[0] * b[0]; + out[1] = a[1] * b[1]; + out[2] = a[2] * b[2]; + return out; +} +/** + * Divides two vec3's + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {vec3} out + */ + +export function divide(out, a, b) { + out[0] = a[0] / b[0]; + out[1] = a[1] / b[1]; + out[2] = a[2] / b[2]; + return out; +} +/** + * Math.ceil the components of a vec3 + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a vector to ceil + * @returns {vec3} out + */ + +export function ceil(out, a) { + out[0] = Math.ceil(a[0]); + out[1] = Math.ceil(a[1]); + out[2] = Math.ceil(a[2]); + return out; +} +/** + * Math.floor the components of a vec3 + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a vector to floor + * @returns {vec3} out + */ + +export function floor(out, a) { + out[0] = Math.floor(a[0]); + out[1] = Math.floor(a[1]); + out[2] = Math.floor(a[2]); + return out; +} +/** + * Returns the minimum of two vec3's + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {vec3} out + */ + +export function min(out, a, b) { + out[0] = Math.min(a[0], b[0]); + out[1] = Math.min(a[1], b[1]); + out[2] = Math.min(a[2], b[2]); + return out; +} +/** + * Returns the maximum of two vec3's + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {vec3} out + */ + +export function max(out, a, b) { + out[0] = Math.max(a[0], b[0]); + out[1] = Math.max(a[1], b[1]); + out[2] = Math.max(a[2], b[2]); + return out; +} +/** + * Math.round the components of a vec3 + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a vector to round + * @returns {vec3} out + */ + +export function round(out, a) { + out[0] = Math.round(a[0]); + out[1] = Math.round(a[1]); + out[2] = Math.round(a[2]); + return out; +} +/** + * Scales a vec3 by a scalar number + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the vector to scale + * @param {Number} b amount to scale the vector by + * @returns {vec3} out + */ + +export function scale(out, a, b) { + out[0] = a[0] * b; + out[1] = a[1] * b; + out[2] = a[2] * b; + return out; +} +/** + * Adds two vec3's after scaling the second operand by a scalar value + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @param {Number} scale the amount to scale b by before adding + * @returns {vec3} out + */ + +export function scaleAndAdd(out, a, b, scale) { + out[0] = a[0] + b[0] * scale; + out[1] = a[1] + b[1] * scale; + out[2] = a[2] + b[2] * scale; + return out; +} +/** + * Calculates the euclidian distance between two vec3's + * + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {Number} distance between a and b + */ + +export function distance(a, b) { + var x = b[0] - a[0]; + var y = b[1] - a[1]; + var z = b[2] - a[2]; + return Math.hypot(x, y, z); +} +/** + * Calculates the squared euclidian distance between two vec3's + * + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {Number} squared distance between a and b + */ + +export function squaredDistance(a, b) { + var x = b[0] - a[0]; + var y = b[1] - a[1]; + var z = b[2] - a[2]; + return x * x + y * y + z * z; +} +/** + * Calculates the squared length of a vec3 + * + * @param {ReadonlyVec3} a vector to calculate squared length of + * @returns {Number} squared length of a + */ + +export function squaredLength(a) { + var x = a[0]; + var y = a[1]; + var z = a[2]; + return x * x + y * y + z * z; +} +/** + * Negates the components of a vec3 + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a vector to negate + * @returns {vec3} out + */ + +export function negate(out, a) { + out[0] = -a[0]; + out[1] = -a[1]; + out[2] = -a[2]; + return out; +} +/** + * Returns the inverse of the components of a vec3 + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a vector to invert + * @returns {vec3} out + */ + +export function inverse(out, a) { + out[0] = 1.0 / a[0]; + out[1] = 1.0 / a[1]; + out[2] = 1.0 / a[2]; + return out; +} +/** + * Normalize a vec3 + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a vector to normalize + * @returns {vec3} out + */ + +export function normalize(out, a) { + var x = a[0]; + var y = a[1]; + var z = a[2]; + var len = x * x + y * y + z * z; + + if (len > 0) { + //TODO: evaluate use of glm_invsqrt here? + len = 1 / Math.sqrt(len); + } + + out[0] = a[0] * len; + out[1] = a[1] * len; + out[2] = a[2] * len; + return out; +} +/** + * Calculates the dot product of two vec3's + * + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {Number} dot product of a and b + */ + +export function dot(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} +/** + * Computes the cross product of two vec3's + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @returns {vec3} out + */ + +export function cross(out, a, b) { + var ax = a[0], + ay = a[1], + az = a[2]; + var bx = b[0], + by = b[1], + bz = b[2]; + out[0] = ay * bz - az * by; + out[1] = az * bx - ax * bz; + out[2] = ax * by - ay * bx; + return out; +} +/** + * Performs a linear interpolation between two vec3's + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {vec3} out + */ + +export function lerp(out, a, b, t) { + var ax = a[0]; + var ay = a[1]; + var az = a[2]; + out[0] = ax + t * (b[0] - ax); + out[1] = ay + t * (b[1] - ay); + out[2] = az + t * (b[2] - az); + return out; +} +/** + * Performs a spherical linear interpolation between two vec3's + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {vec3} out + */ + +export function slerp(out, a, b, t) { + var angle = Math.acos(Math.min(Math.max(dot(a, b), -1), 1)); + var sinTotal = Math.sin(angle); + var ratioA = Math.sin((1 - t) * angle) / sinTotal; + var ratioB = Math.sin(t * angle) / sinTotal; + out[0] = ratioA * a[0] + ratioB * b[0]; + out[1] = ratioA * a[1] + ratioB * b[1]; + out[2] = ratioA * a[2] + ratioB * b[2]; + return out; +} +/** + * Performs a hermite interpolation with two control points + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @param {ReadonlyVec3} c the third operand + * @param {ReadonlyVec3} d the fourth operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {vec3} out + */ + +export function hermite(out, a, b, c, d, t) { + var factorTimes2 = t * t; + var factor1 = factorTimes2 * (2 * t - 3) + 1; + var factor2 = factorTimes2 * (t - 2) + t; + var factor3 = factorTimes2 * (t - 1); + var factor4 = factorTimes2 * (3 - 2 * t); + out[0] = a[0] * factor1 + b[0] * factor2 + c[0] * factor3 + d[0] * factor4; + out[1] = a[1] * factor1 + b[1] * factor2 + c[1] * factor3 + d[1] * factor4; + out[2] = a[2] * factor1 + b[2] * factor2 + c[2] * factor3 + d[2] * factor4; + return out; +} +/** + * Performs a bezier interpolation with two control points + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the first operand + * @param {ReadonlyVec3} b the second operand + * @param {ReadonlyVec3} c the third operand + * @param {ReadonlyVec3} d the fourth operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {vec3} out + */ + +export function bezier(out, a, b, c, d, t) { + var inverseFactor = 1 - t; + var inverseFactorTimesTwo = inverseFactor * inverseFactor; + var factorTimes2 = t * t; + var factor1 = inverseFactorTimesTwo * inverseFactor; + var factor2 = 3 * t * inverseFactorTimesTwo; + var factor3 = 3 * factorTimes2 * inverseFactor; + var factor4 = factorTimes2 * t; + out[0] = a[0] * factor1 + b[0] * factor2 + c[0] * factor3 + d[0] * factor4; + out[1] = a[1] * factor1 + b[1] * factor2 + c[1] * factor3 + d[1] * factor4; + out[2] = a[2] * factor1 + b[2] * factor2 + c[2] * factor3 + d[2] * factor4; + return out; +} +/** + * Generates a random vector with the given scale + * + * @param {vec3} out the receiving vector + * @param {Number} [scale] Length of the resulting vector. If omitted, a unit vector will be returned + * @returns {vec3} out + */ + +export function random(out, scale) { + scale = scale === undefined ? 1.0 : scale; + var r = glMatrix.RANDOM() * 2.0 * Math.PI; + var z = glMatrix.RANDOM() * 2.0 - 1.0; + var zScale = Math.sqrt(1.0 - z * z) * scale; + out[0] = Math.cos(r) * zScale; + out[1] = Math.sin(r) * zScale; + out[2] = z * scale; + return out; +} +/** + * Transforms the vec3 with a mat4. + * 4th vector component is implicitly '1' + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the vector to transform + * @param {ReadonlyMat4} m matrix to transform with + * @returns {vec3} out + */ + +export function transformMat4(out, a, m) { + var x = a[0], + y = a[1], + z = a[2]; + var w = m[3] * x + m[7] * y + m[11] * z + m[15]; + w = w || 1.0; + out[0] = (m[0] * x + m[4] * y + m[8] * z + m[12]) / w; + out[1] = (m[1] * x + m[5] * y + m[9] * z + m[13]) / w; + out[2] = (m[2] * x + m[6] * y + m[10] * z + m[14]) / w; + return out; +} +/** + * Transforms the vec3 with a mat3. + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the vector to transform + * @param {ReadonlyMat3} m the 3x3 matrix to transform with + * @returns {vec3} out + */ + +export function transformMat3(out, a, m) { + var x = a[0], + y = a[1], + z = a[2]; + out[0] = x * m[0] + y * m[3] + z * m[6]; + out[1] = x * m[1] + y * m[4] + z * m[7]; + out[2] = x * m[2] + y * m[5] + z * m[8]; + return out; +} +/** + * Transforms the vec3 with a quat + * Can also be used for dual quaternions. (Multiply it with the real part) + * + * @param {vec3} out the receiving vector + * @param {ReadonlyVec3} a the vector to transform + * @param {ReadonlyQuat} q quaternion to transform with + * @returns {vec3} out + */ + +export function transformQuat(out, a, q) { + // benchmarks: https://jsperf.com/quaternion-transform-vec3-implementations-fixed + var qx = q[0], + qy = q[1], + qz = q[2], + qw = q[3]; + var x = a[0], + y = a[1], + z = a[2]; // var qvec = [qx, qy, qz]; + // var uv = vec3.cross([], qvec, a); + + var uvx = qy * z - qz * y, + uvy = qz * x - qx * z, + uvz = qx * y - qy * x; // var uuv = vec3.cross([], qvec, uv); + + var uuvx = qy * uvz - qz * uvy, + uuvy = qz * uvx - qx * uvz, + uuvz = qx * uvy - qy * uvx; // vec3.scale(uv, uv, 2 * w); + + var w2 = qw * 2; + uvx *= w2; + uvy *= w2; + uvz *= w2; // vec3.scale(uuv, uuv, 2); + + uuvx *= 2; + uuvy *= 2; + uuvz *= 2; // return vec3.add(out, a, vec3.add(out, uv, uuv)); + + out[0] = x + uvx + uuvx; + out[1] = y + uvy + uuvy; + out[2] = z + uvz + uuvz; + return out; +} +/** + * Rotate a 3D vector around the x-axis + * @param {vec3} out The receiving vec3 + * @param {ReadonlyVec3} a The vec3 point to rotate + * @param {ReadonlyVec3} b The origin of the rotation + * @param {Number} rad The angle of rotation in radians + * @returns {vec3} out + */ + +export function rotateX(out, a, b, rad) { + var p = [], + r = []; //Translate point to the origin + + p[0] = a[0] - b[0]; + p[1] = a[1] - b[1]; + p[2] = a[2] - b[2]; //perform rotation + + r[0] = p[0]; + r[1] = p[1] * Math.cos(rad) - p[2] * Math.sin(rad); + r[2] = p[1] * Math.sin(rad) + p[2] * Math.cos(rad); //translate to correct position + + out[0] = r[0] + b[0]; + out[1] = r[1] + b[1]; + out[2] = r[2] + b[2]; + return out; +} +/** + * Rotate a 3D vector around the y-axis + * @param {vec3} out The receiving vec3 + * @param {ReadonlyVec3} a The vec3 point to rotate + * @param {ReadonlyVec3} b The origin of the rotation + * @param {Number} rad The angle of rotation in radians + * @returns {vec3} out + */ + +export function rotateY(out, a, b, rad) { + var p = [], + r = []; //Translate point to the origin + + p[0] = a[0] - b[0]; + p[1] = a[1] - b[1]; + p[2] = a[2] - b[2]; //perform rotation + + r[0] = p[2] * Math.sin(rad) + p[0] * Math.cos(rad); + r[1] = p[1]; + r[2] = p[2] * Math.cos(rad) - p[0] * Math.sin(rad); //translate to correct position + + out[0] = r[0] + b[0]; + out[1] = r[1] + b[1]; + out[2] = r[2] + b[2]; + return out; +} +/** + * Rotate a 3D vector around the z-axis + * @param {vec3} out The receiving vec3 + * @param {ReadonlyVec3} a The vec3 point to rotate + * @param {ReadonlyVec3} b The origin of the rotation + * @param {Number} rad The angle of rotation in radians + * @returns {vec3} out + */ + +export function rotateZ(out, a, b, rad) { + var p = [], + r = []; //Translate point to the origin + + p[0] = a[0] - b[0]; + p[1] = a[1] - b[1]; + p[2] = a[2] - b[2]; //perform rotation + + r[0] = p[0] * Math.cos(rad) - p[1] * Math.sin(rad); + r[1] = p[0] * Math.sin(rad) + p[1] * Math.cos(rad); + r[2] = p[2]; //translate to correct position + + out[0] = r[0] + b[0]; + out[1] = r[1] + b[1]; + out[2] = r[2] + b[2]; + return out; +} +/** + * Get the angle between two 3D vectors + * @param {ReadonlyVec3} a The first operand + * @param {ReadonlyVec3} b The second operand + * @returns {Number} The angle in radians + */ + +export function angle(a, b) { + var ax = a[0], + ay = a[1], + az = a[2], + bx = b[0], + by = b[1], + bz = b[2], + mag = Math.sqrt((ax * ax + ay * ay + az * az) * (bx * bx + by * by + bz * bz)), + cosine = mag && dot(a, b) / mag; + return Math.acos(Math.min(Math.max(cosine, -1), 1)); +} +/** + * Set the components of a vec3 to zero + * + * @param {vec3} out the receiving vector + * @returns {vec3} out + */ + +export function zero(out) { + out[0] = 0.0; + out[1] = 0.0; + out[2] = 0.0; + return out; +} +/** + * Returns a string representation of a vector + * + * @param {ReadonlyVec3} a vector to represent as a string + * @returns {String} string representation of the vector + */ + +export function str(a) { + return "vec3(" + a[0] + ", " + a[1] + ", " + a[2] + ")"; +} +/** + * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===) + * + * @param {ReadonlyVec3} a The first vector. + * @param {ReadonlyVec3} b The second vector. + * @returns {Boolean} True if the vectors are equal, false otherwise. + */ + +export function exactEquals(a, b) { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2]; +} +/** + * Returns whether or not the vectors have approximately the same elements in the same position. + * + * @param {ReadonlyVec3} a The first vector. + * @param {ReadonlyVec3} b The second vector. + * @returns {Boolean} True if the vectors are equal, false otherwise. + */ + +export function equals(a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2]; + var b0 = b[0], + b1 = b[1], + b2 = b[2]; + return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)); +} +/** + * Alias for {@link vec3.subtract} + * @function + */ + +export var sub = subtract; +/** + * Alias for {@link vec3.multiply} + * @function + */ + +export var mul = multiply; +/** + * Alias for {@link vec3.divide} + * @function + */ + +export var div = divide; +/** + * Alias for {@link vec3.distance} + * @function + */ + +export var dist = distance; +/** + * Alias for {@link vec3.squaredDistance} + * @function + */ + +export var sqrDist = squaredDistance; +/** + * Alias for {@link vec3.length} + * @function + */ + +export var len = length; +/** + * Alias for {@link vec3.squaredLength} + * @function + */ + +export var sqrLen = squaredLength; +/** + * Perform some operation over an array of vec3s. + * + * @param {Array} a the array of vectors to iterate over + * @param {Number} stride Number of elements between the start of each vec3. If 0 assumes tightly packed + * @param {Number} offset Number of elements to skip at the beginning of the array + * @param {Number} count Number of vec3s to iterate over. If 0 iterates over entire array + * @param {Function} fn Function to call for each vector in the array + * @param {Object} [arg] additional argument to pass to fn + * @returns {Array} a + * @function + */ + +export var forEach = function () { + var vec = create(); + return function (a, stride, offset, count, fn, arg) { + var i, l; + + if (!stride) { + stride = 3; + } + + if (!offset) { + offset = 0; + } + + if (count) { + l = Math.min(count * stride + offset, a.length); + } else { + l = a.length; + } + + for (i = offset; i < l; i += stride) { + vec[0] = a[i]; + vec[1] = a[i + 1]; + vec[2] = a[i + 2]; + fn(vec, vec, arg); + a[i] = vec[0]; + a[i + 1] = vec[1]; + a[i + 2] = vec[2]; + } + + return a; + }; +}(); \ No newline at end of file diff --git a/debug/webgpu/gl-matrix/vec4.js b/debug/webgpu/gl-matrix/vec4.js new file mode 100644 index 0000000000..cd84dcf67d --- /dev/null +++ b/debug/webgpu/gl-matrix/vec4.js @@ -0,0 +1,663 @@ +import * as glMatrix from "./common.js"; +/** + * 4 Dimensional Vector + * @module vec4 + */ + +/** + * Creates a new, empty vec4 + * + * @returns {vec4} a new 4D vector + */ + +export function create() { + var out = new glMatrix.ARRAY_TYPE(4); + + if (glMatrix.ARRAY_TYPE != Float32Array) { + out[0] = 0; + out[1] = 0; + out[2] = 0; + out[3] = 0; + } + + return out; +} +/** + * Creates a new vec4 initialized with values from an existing vector + * + * @param {ReadonlyVec4} a vector to clone + * @returns {vec4} a new 4D vector + */ + +export function clone(a) { + var out = new glMatrix.ARRAY_TYPE(4); + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + return out; +} +/** + * Creates a new vec4 initialized with the given values + * + * @param {Number} x X component + * @param {Number} y Y component + * @param {Number} z Z component + * @param {Number} w W component + * @returns {vec4} a new 4D vector + */ + +export function fromValues(x, y, z, w) { + var out = new glMatrix.ARRAY_TYPE(4); + out[0] = x; + out[1] = y; + out[2] = z; + out[3] = w; + return out; +} +/** + * Copy the values from one vec4 to another + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the source vector + * @returns {vec4} out + */ + +export function copy(out, a) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + out[3] = a[3]; + return out; +} +/** + * Set the components of a vec4 to the given values + * + * @param {vec4} out the receiving vector + * @param {Number} x X component + * @param {Number} y Y component + * @param {Number} z Z component + * @param {Number} w W component + * @returns {vec4} out + */ + +export function set(out, x, y, z, w) { + out[0] = x; + out[1] = y; + out[2] = z; + out[3] = w; + return out; +} +/** + * Adds two vec4's + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {vec4} out + */ + +export function add(out, a, b) { + out[0] = a[0] + b[0]; + out[1] = a[1] + b[1]; + out[2] = a[2] + b[2]; + out[3] = a[3] + b[3]; + return out; +} +/** + * Subtracts vector b from vector a + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {vec4} out + */ + +export function subtract(out, a, b) { + out[0] = a[0] - b[0]; + out[1] = a[1] - b[1]; + out[2] = a[2] - b[2]; + out[3] = a[3] - b[3]; + return out; +} +/** + * Multiplies two vec4's + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {vec4} out + */ + +export function multiply(out, a, b) { + out[0] = a[0] * b[0]; + out[1] = a[1] * b[1]; + out[2] = a[2] * b[2]; + out[3] = a[3] * b[3]; + return out; +} +/** + * Divides two vec4's + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {vec4} out + */ + +export function divide(out, a, b) { + out[0] = a[0] / b[0]; + out[1] = a[1] / b[1]; + out[2] = a[2] / b[2]; + out[3] = a[3] / b[3]; + return out; +} +/** + * Math.ceil the components of a vec4 + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a vector to ceil + * @returns {vec4} out + */ + +export function ceil(out, a) { + out[0] = Math.ceil(a[0]); + out[1] = Math.ceil(a[1]); + out[2] = Math.ceil(a[2]); + out[3] = Math.ceil(a[3]); + return out; +} +/** + * Math.floor the components of a vec4 + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a vector to floor + * @returns {vec4} out + */ + +export function floor(out, a) { + out[0] = Math.floor(a[0]); + out[1] = Math.floor(a[1]); + out[2] = Math.floor(a[2]); + out[3] = Math.floor(a[3]); + return out; +} +/** + * Returns the minimum of two vec4's + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {vec4} out + */ + +export function min(out, a, b) { + out[0] = Math.min(a[0], b[0]); + out[1] = Math.min(a[1], b[1]); + out[2] = Math.min(a[2], b[2]); + out[3] = Math.min(a[3], b[3]); + return out; +} +/** + * Returns the maximum of two vec4's + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {vec4} out + */ + +export function max(out, a, b) { + out[0] = Math.max(a[0], b[0]); + out[1] = Math.max(a[1], b[1]); + out[2] = Math.max(a[2], b[2]); + out[3] = Math.max(a[3], b[3]); + return out; +} +/** + * Math.round the components of a vec4 + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a vector to round + * @returns {vec4} out + */ + +export function round(out, a) { + out[0] = Math.round(a[0]); + out[1] = Math.round(a[1]); + out[2] = Math.round(a[2]); + out[3] = Math.round(a[3]); + return out; +} +/** + * Scales a vec4 by a scalar number + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the vector to scale + * @param {Number} b amount to scale the vector by + * @returns {vec4} out + */ + +export function scale(out, a, b) { + out[0] = a[0] * b; + out[1] = a[1] * b; + out[2] = a[2] * b; + out[3] = a[3] * b; + return out; +} +/** + * Adds two vec4's after scaling the second operand by a scalar value + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @param {Number} scale the amount to scale b by before adding + * @returns {vec4} out + */ + +export function scaleAndAdd(out, a, b, scale) { + out[0] = a[0] + b[0] * scale; + out[1] = a[1] + b[1] * scale; + out[2] = a[2] + b[2] * scale; + out[3] = a[3] + b[3] * scale; + return out; +} +/** + * Calculates the euclidian distance between two vec4's + * + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {Number} distance between a and b + */ + +export function distance(a, b) { + var x = b[0] - a[0]; + var y = b[1] - a[1]; + var z = b[2] - a[2]; + var w = b[3] - a[3]; + return Math.hypot(x, y, z, w); +} +/** + * Calculates the squared euclidian distance between two vec4's + * + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {Number} squared distance between a and b + */ + +export function squaredDistance(a, b) { + var x = b[0] - a[0]; + var y = b[1] - a[1]; + var z = b[2] - a[2]; + var w = b[3] - a[3]; + return x * x + y * y + z * z + w * w; +} +/** + * Calculates the length of a vec4 + * + * @param {ReadonlyVec4} a vector to calculate length of + * @returns {Number} length of a + */ + +export function length(a) { + var x = a[0]; + var y = a[1]; + var z = a[2]; + var w = a[3]; + return Math.hypot(x, y, z, w); +} +/** + * Calculates the squared length of a vec4 + * + * @param {ReadonlyVec4} a vector to calculate squared length of + * @returns {Number} squared length of a + */ + +export function squaredLength(a) { + var x = a[0]; + var y = a[1]; + var z = a[2]; + var w = a[3]; + return x * x + y * y + z * z + w * w; +} +/** + * Negates the components of a vec4 + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a vector to negate + * @returns {vec4} out + */ + +export function negate(out, a) { + out[0] = -a[0]; + out[1] = -a[1]; + out[2] = -a[2]; + out[3] = -a[3]; + return out; +} +/** + * Returns the inverse of the components of a vec4 + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a vector to invert + * @returns {vec4} out + */ + +export function inverse(out, a) { + out[0] = 1.0 / a[0]; + out[1] = 1.0 / a[1]; + out[2] = 1.0 / a[2]; + out[3] = 1.0 / a[3]; + return out; +} +/** + * Normalize a vec4 + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a vector to normalize + * @returns {vec4} out + */ + +export function normalize(out, a) { + var x = a[0]; + var y = a[1]; + var z = a[2]; + var w = a[3]; + var len = x * x + y * y + z * z + w * w; + + if (len > 0) { + len = 1 / Math.sqrt(len); + } + + out[0] = x * len; + out[1] = y * len; + out[2] = z * len; + out[3] = w * len; + return out; +} +/** + * Calculates the dot product of two vec4's + * + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @returns {Number} dot product of a and b + */ + +export function dot(a, b) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; +} +/** + * Returns the cross-product of three vectors in a 4-dimensional space + * + * @param {ReadonlyVec4} result the receiving vector + * @param {ReadonlyVec4} U the first vector + * @param {ReadonlyVec4} V the second vector + * @param {ReadonlyVec4} W the third vector + * @returns {vec4} result + */ + +export function cross(out, u, v, w) { + var A = v[0] * w[1] - v[1] * w[0], + B = v[0] * w[2] - v[2] * w[0], + C = v[0] * w[3] - v[3] * w[0], + D = v[1] * w[2] - v[2] * w[1], + E = v[1] * w[3] - v[3] * w[1], + F = v[2] * w[3] - v[3] * w[2]; + var G = u[0]; + var H = u[1]; + var I = u[2]; + var J = u[3]; + out[0] = H * F - I * E + J * D; + out[1] = -(G * F) + I * C - J * B; + out[2] = G * E - H * C + J * A; + out[3] = -(G * D) + H * B - I * A; + return out; +} +/** + * Performs a linear interpolation between two vec4's + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the first operand + * @param {ReadonlyVec4} b the second operand + * @param {Number} t interpolation amount, in the range [0-1], between the two inputs + * @returns {vec4} out + */ + +export function lerp(out, a, b, t) { + var ax = a[0]; + var ay = a[1]; + var az = a[2]; + var aw = a[3]; + out[0] = ax + t * (b[0] - ax); + out[1] = ay + t * (b[1] - ay); + out[2] = az + t * (b[2] - az); + out[3] = aw + t * (b[3] - aw); + return out; +} +/** + * Generates a random vector with the given scale + * + * @param {vec4} out the receiving vector + * @param {Number} [scale] Length of the resulting vector. If omitted, a unit vector will be returned + * @returns {vec4} out + */ + +export function random(out, scale) { + scale = scale === undefined ? 1.0 : scale; // Marsaglia, George. Choosing a Point from the Surface of a + // Sphere. Ann. Math. Statist. 43 (1972), no. 2, 645--646. + // http://projecteuclid.org/euclid.aoms/1177692644; + + var v1, v2, v3, v4; + var s1, s2; + + do { + v1 = glMatrix.RANDOM() * 2 - 1; + v2 = glMatrix.RANDOM() * 2 - 1; + s1 = v1 * v1 + v2 * v2; + } while (s1 >= 1); + + do { + v3 = glMatrix.RANDOM() * 2 - 1; + v4 = glMatrix.RANDOM() * 2 - 1; + s2 = v3 * v3 + v4 * v4; + } while (s2 >= 1); + + var d = Math.sqrt((1 - s1) / s2); + out[0] = scale * v1; + out[1] = scale * v2; + out[2] = scale * v3 * d; + out[3] = scale * v4 * d; + return out; +} +/** + * Transforms the vec4 with a mat4. + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the vector to transform + * @param {ReadonlyMat4} m matrix to transform with + * @returns {vec4} out + */ + +export function transformMat4(out, a, m) { + var x = a[0], + y = a[1], + z = a[2], + w = a[3]; + out[0] = m[0] * x + m[4] * y + m[8] * z + m[12] * w; + out[1] = m[1] * x + m[5] * y + m[9] * z + m[13] * w; + out[2] = m[2] * x + m[6] * y + m[10] * z + m[14] * w; + out[3] = m[3] * x + m[7] * y + m[11] * z + m[15] * w; + return out; +} +/** + * Transforms the vec4 with a quat + * + * @param {vec4} out the receiving vector + * @param {ReadonlyVec4} a the vector to transform + * @param {ReadonlyQuat} q quaternion to transform with + * @returns {vec4} out + */ + +export function transformQuat(out, a, q) { + var x = a[0], + y = a[1], + z = a[2]; + var qx = q[0], + qy = q[1], + qz = q[2], + qw = q[3]; // calculate quat * vec + + var ix = qw * x + qy * z - qz * y; + var iy = qw * y + qz * x - qx * z; + var iz = qw * z + qx * y - qy * x; + var iw = -qx * x - qy * y - qz * z; // calculate result * inverse quat + + out[0] = ix * qw + iw * -qx + iy * -qz - iz * -qy; + out[1] = iy * qw + iw * -qy + iz * -qx - ix * -qz; + out[2] = iz * qw + iw * -qz + ix * -qy - iy * -qx; + out[3] = a[3]; + return out; +} +/** + * Set the components of a vec4 to zero + * + * @param {vec4} out the receiving vector + * @returns {vec4} out + */ + +export function zero(out) { + out[0] = 0.0; + out[1] = 0.0; + out[2] = 0.0; + out[3] = 0.0; + return out; +} +/** + * Returns a string representation of a vector + * + * @param {ReadonlyVec4} a vector to represent as a string + * @returns {String} string representation of the vector + */ + +export function str(a) { + return "vec4(" + a[0] + ", " + a[1] + ", " + a[2] + ", " + a[3] + ")"; +} +/** + * Returns whether or not the vectors have exactly the same elements in the same position (when compared with ===) + * + * @param {ReadonlyVec4} a The first vector. + * @param {ReadonlyVec4} b The second vector. + * @returns {Boolean} True if the vectors are equal, false otherwise. + */ + +export function exactEquals(a, b) { + return a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]; +} +/** + * Returns whether or not the vectors have approximately the same elements in the same position. + * + * @param {ReadonlyVec4} a The first vector. + * @param {ReadonlyVec4} b The second vector. + * @returns {Boolean} True if the vectors are equal, false otherwise. + */ + +export function equals(a, b) { + var a0 = a[0], + a1 = a[1], + a2 = a[2], + a3 = a[3]; + var b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3]; + return Math.abs(a0 - b0) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a0), Math.abs(b0)) && Math.abs(a1 - b1) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a1), Math.abs(b1)) && Math.abs(a2 - b2) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a2), Math.abs(b2)) && Math.abs(a3 - b3) <= glMatrix.EPSILON * Math.max(1.0, Math.abs(a3), Math.abs(b3)); +} +/** + * Alias for {@link vec4.subtract} + * @function + */ + +export var sub = subtract; +/** + * Alias for {@link vec4.multiply} + * @function + */ + +export var mul = multiply; +/** + * Alias for {@link vec4.divide} + * @function + */ + +export var div = divide; +/** + * Alias for {@link vec4.distance} + * @function + */ + +export var dist = distance; +/** + * Alias for {@link vec4.squaredDistance} + * @function + */ + +export var sqrDist = squaredDistance; +/** + * Alias for {@link vec4.length} + * @function + */ + +export var len = length; +/** + * Alias for {@link vec4.squaredLength} + * @function + */ + +export var sqrLen = squaredLength; +/** + * Perform some operation over an array of vec4s. + * + * @param {Array} a the array of vectors to iterate over + * @param {Number} stride Number of elements between the start of each vec4. If 0 assumes tightly packed + * @param {Number} offset Number of elements to skip at the beginning of the array + * @param {Number} count Number of vec4s to iterate over. If 0 iterates over entire array + * @param {Function} fn Function to call for each vector in the array + * @param {Object} [arg] additional argument to pass to fn + * @returns {Array} a + * @function + */ + +export var forEach = function () { + var vec = create(); + return function (a, stride, offset, count, fn, arg) { + var i, l; + + if (!stride) { + stride = 4; + } + + if (!offset) { + offset = 0; + } + + if (count) { + l = Math.min(count * stride + offset, a.length); + } else { + l = a.length; + } + + for (i = offset; i < l; i += stride) { + vec[0] = a[i]; + vec[1] = a[i + 1]; + vec[2] = a[i + 2]; + vec[3] = a[i + 3]; + fn(vec, vec, arg); + a[i] = vec[0]; + a[i + 1] = vec[1]; + a[i + 2] = vec[2]; + a[i + 3] = vec[3]; + } + + return a; + }; +}(); \ No newline at end of file diff --git a/debug/webgpu/meshes/cube.js b/debug/webgpu/meshes/cube.js new file mode 100644 index 0000000000..0411d80523 --- /dev/null +++ b/debug/webgpu/meshes/cube.js @@ -0,0 +1,124 @@ +export const cubeVertexCount = 36; + +// prettier-ignore +export const cubeVertexArray = new Float32Array([ + 1, -1, 1, 1, + -1, -1, 1, 1, + -1, -1, -1, 1, + 1, -1, -1, 1, + 1, -1, 1, 1, + -1, -1, -1, 1, + + 1, 1, 1, 1, + 1, -1, 1, 1, + 1, -1, -1, 1, + 1, 1, -1, 1, + 1, 1, 1, 1, + 1, -1, -1, 1, + + -1, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, -1, 1, + -1, 1, -1, 1, + -1, 1, 1, 1, + 1, 1, -1, 1, + + -1, -1, 1, 1, + -1, 1, 1, 1, + -1, 1, -1, 1, + -1, -1, -1, 1, + -1, -1, 1, 1, + -1, 1, -1, 1, + + 1, 1, 1, 1, + -1, 1, 1, 1, + -1, -1, 1, 1, + -1, -1, 1, 1, + 1, -1, 1, 1, + 1, 1, 1, 1, + + 1, -1, -1, 1, + -1, -1, -1, 1, + -1, 1, -1, 1, + 1, 1, -1, 1, + 1, -1, -1, 1, + -1, 1, -1, 1 +]); + +export const cubeVertexColors = new Float32Array([ + 1, 0, 1, 1, + 0, 0, 1, 1, + 0, 0, 0, 1, + 1, 0, 0, 1, + 1, 0, 1, 1, + 0, 0, 0, 1, + 1, 1, 1, 1, + 1, 0, 1, 1, + 1, 0, 0, 1, + 1, 1, 0, 1, + 1, 1, 1, 1, + 1, 0, 0, 1, + 0, 1, 1, 1, + 1, 1, 1, 1, + 1, 1, 0, 1, + 0, 1, 0, 1, + 0, 1, 1, 1, + 1, 1, 0, 1, + 0, 0, 1, 1, + 0, 1, 1, 1, + 0, 1, 0, 1, + 0, 0, 0, 1, + 0, 0, 1, 1, + 0, 1, 0, 1, + 1, 1, 1, 1, + 0, 1, 1, 1, + 0, 0, 1, 1, + 0, 0, 1, 1, + 1, 0, 1, 1, + 1, 1, 1, 1, + 1, 0, 0, 1, + 0, 0, 0, 1, + 0, 1, 0, 1, + 1, 1, 0, 1, + 1, 0, 0, 1, + 0, 1, 0, 1 +]); + +export const cubeVertexUV = new Float32Array([ + 0, 1, + 1, 1, + 1, 0, + 0, 0, + 0, 1, + 1, 0, + 0, 1, + 1, 1, + 1, 0, + 0, 0, + 0, 1, + 1, 0, + 0, 1, + 1, 1, + 1, 0, + 0, 0, + 0, 1, + 1, 0, + 0, 1, + 1, 1, + 1, 0, + 0, 0, + 0, 1, + 1, 0, + 0, 1, + 1, 1, + 1, 0, + 1, 0, + 0, 0, + 0, 1, + 0, 1, + 1, 1, + 1, 0, + 0, 0, + 0, 1, + 1, 0 +]); diff --git a/packages/reshader.gl/src/shader/Shader.ts b/packages/reshader.gl/src/shader/Shader.ts index af0b82cdb6..64d266a572 100644 --- a/packages/reshader.gl/src/shader/Shader.ts +++ b/packages/reshader.gl/src/shader/Shader.ts @@ -40,8 +40,10 @@ export class GLShader { context: any; //@internal contextKeys: string; + name: string; - constructor({ vert, frag, uniforms, defines, extraCommandProps }) { + constructor({ vert, frag, uniforms, defines, extraCommandProps, name }) { + this.name = name; this.vert = vert; this.frag = frag; const shaderId = uid++; @@ -411,7 +413,7 @@ export default class GPUShader extends GLShader { // 4. 生成 layout 和 pipeline // preprocess vert and frag codes - const builder = new CommandBuilder(device, this.vert, this.frag, mesh) + const builder = new CommandBuilder(this.name, device, this.vert, this.frag, mesh) return builder.build(pipelineDesc); } else { // regl diff --git a/packages/reshader.gl/src/webgpu/CommandBuilder.ts b/packages/reshader.gl/src/webgpu/CommandBuilder.ts index e1d0abb76f..dca28e382d 100644 --- a/packages/reshader.gl/src/webgpu/CommandBuilder.ts +++ b/packages/reshader.gl/src/webgpu/CommandBuilder.ts @@ -3,7 +3,6 @@ import { WgslReflect } from "wgsl_reflect/wgsl_reflect.module.js"; import BindGroupFormat from '../webgpu/BindGroupFormat'; import Mesh from '../Mesh'; import { ResourceType } from 'wgsl_reflect'; -import { extend } from '../common/Util'; import GraphicsDevice from './GraphicsDevice'; import PipelineDescriptor from './common/PipelineDesc'; import { ActiveAttributes } from '../types/typings'; @@ -14,7 +13,9 @@ export default class CommandBuilder { frag: string; mesh: Mesh; _presentationFormat: any; - constructor(device, vert, frag, mesh) { + name: string; + constructor(name: string, device: GraphicsDevice, vert: string, frag: string, mesh: Mesh) { + this.name = name; this.device = device; this.vert = vert; this.frag = frag; @@ -190,7 +191,7 @@ export default class CommandBuilder { } const buffers = mesh.geometry.getBufferDescriptor(vertInfo); const pipelineLayout = device.createPipelineLayout({ - label: 'label', + label: this.name, bindGroupLayouts: [layout] }); const pipelineOptions: GPURenderPipelineDescriptor = { diff --git a/packages/reshader.gl/src/webgpu/DynamicBuffer.ts b/packages/reshader.gl/src/webgpu/DynamicBuffer.ts index 578441bafc..8c87674607 100644 --- a/packages/reshader.gl/src/webgpu/DynamicBuffer.ts +++ b/packages/reshader.gl/src/webgpu/DynamicBuffer.ts @@ -21,6 +21,7 @@ export default class DynamicBuffer { this.dynamicOffsets.fill(0); const totalSize = this.bindgroupMapping.totalSize; const gpuBuffer = this.allocation.gpuBuffer; + const bufferAlignment = this.pool.bufferAlignment; this.pool.alloc(this.allocation, totalSize); if (gpuBuffer !== this.allocation.gpuBuffer) { this.version++; @@ -30,6 +31,7 @@ export default class DynamicBuffer { const storage = this.allocation.storage; for (let i = 0; i < mapping.length; i++) { const uniform = mapping[i]; + this.dynamicOffsets[i] = dynamicOffset; if (uniform.members) { for (let j = 0; j < uniform.members.length; j++) { const member = uniform.members[j]; @@ -38,14 +40,11 @@ export default class DynamicBuffer { const size = member.size; this._fillValue(storage, offset, size, value); } - // size() 返回的值已经考虑过 bufferAlignment - dynamicOffset += mapping[i].size; - this.dynamicOffsets[i] = dynamicOffset; + dynamicOffset += Math.min(mapping[i].size, bufferAlignment); } else if (uniform.resourceType === ResourceType.Uniform) { const value = uniformValues[uniform.name]; this._fillValue(storage, dynamicOffset, uniform.size(), value); - dynamicOffset += mapping[i].size(); - this.dynamicOffsets[i] = dynamicOffset; + dynamicOffset += Math.min(mapping[i].size(), bufferAlignment); } } } diff --git a/packages/reshader.gl/src/webgpu/GraphicsDevice.ts b/packages/reshader.gl/src/webgpu/GraphicsDevice.ts index 8397365e25..9d7027a44d 100644 --- a/packages/reshader.gl/src/webgpu/GraphicsDevice.ts +++ b/packages/reshader.gl/src/webgpu/GraphicsDevice.ts @@ -2,7 +2,7 @@ import DynamicBufferPool from "./DynamicBufferPool"; export default class GraphicsDevice { wgpu: GPUDevice; - commandBuffers: GPUCommandBuffer[]; + commandBuffers: GPUCommandBuffer[] = []; dynamicBufferPool: DynamicBufferPool; context: GPUCanvasContext; _defaultRenderTarget: GPURenderPassDescriptor; @@ -41,8 +41,8 @@ export default class GraphicsDevice { endCommandEncoder() { const { commandEncoder } = this; if (commandEncoder) { - commandEncoder.finish(); - // this.addCommandBuffer(cb); + const cb = commandEncoder.finish(); + this.addCommandBuffer(cb, false); this.commandEncoder = null; } } @@ -53,7 +53,7 @@ export default class GraphicsDevice { const canvas = this.context.canvas; const depthTexture = this.wgpu.createTexture({ size: [canvas.width, canvas.height], - format: 'depth24plus', + format: 'depth24plus-stencil8', usage: GPUTextureUsage.RENDER_ATTACHMENT, }); rendrTarget = this._defaultRenderTarget = { @@ -70,6 +70,10 @@ export default class GraphicsDevice { depthClearValue: 1.0, depthLoadOp: "clear", depthStoreOp: "store", + stencilReadOnly: false, + stencilClearValue: 255, + stencilLoadOp: 'clear', + stencilStoreOp: 'store' }, }; } diff --git a/packages/reshader.gl/src/webgpu/common/PipelineDesc.ts b/packages/reshader.gl/src/webgpu/common/PipelineDesc.ts index 117fb78b40..f56cc330bb 100644 --- a/packages/reshader.gl/src/webgpu/common/PipelineDesc.ts +++ b/packages/reshader.gl/src/webgpu/common/PipelineDesc.ts @@ -118,6 +118,7 @@ export default class PipelineDescriptor { if (!doubleSided) { const cullProps = commandProps.cull; if (cullProps && isEnable(cullProps.enable, uniformValues)) { + cullMode = 'back'; if (cullProps.face) { cullMode = isFunction(cullProps.face) && cullProps.face(null, uniformValues) || cullProps.face; } From 3ecc84b4002a4ee6ff7da6d7ee550ae57b3c6e62 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Wed, 22 Jan 2025 15:15:32 +0800 Subject: [PATCH 15/53] add GraphicsTexture and fix cube-texture.html --- debug/webgpu/Di-3d.png | Bin 0 -> 243533 bytes debug/webgpu/cube-texture.html | 161 ++++++++++++++++++ packages/reshader.gl/src/Geometry.ts | 16 +- packages/reshader.gl/src/Texture2D.ts | 18 +- packages/reshader.gl/src/common/Util.ts | 2 +- packages/reshader.gl/src/shader/Shader.ts | 2 +- packages/reshader.gl/src/shadow/ShadowPass.js | 4 +- .../reshader.gl/src/webgpu/BindGroupFormat.ts | 16 +- .../reshader.gl/src/webgpu/CommandBuilder.ts | 31 +++- .../reshader.gl/src/webgpu/DynamicBuffer.ts | 6 +- .../reshader.gl/src/webgpu/GraphicsDevice.ts | 24 ++- .../reshader.gl/src/webgpu/GraphicsTexture.ts | 89 ++++++++++ .../src/webgpu/common/ReglTranslator.ts | 19 +++ 13 files changed, 363 insertions(+), 25 deletions(-) create mode 100644 debug/webgpu/Di-3d.png create mode 100644 debug/webgpu/cube-texture.html create mode 100644 packages/reshader.gl/src/webgpu/GraphicsTexture.ts diff --git a/debug/webgpu/Di-3d.png b/debug/webgpu/Di-3d.png new file mode 100644 index 0000000000000000000000000000000000000000..ebbff45ead2a90ccb1cead28fbf25c157df23af9 GIT binary patch literal 243533 zcmXtfWmH?w*L9$Hk<#K`_!XzP2Ps}CZp8w{-7R>FyBCVPL(t-0ybvhvPH+nloHze> zttXkRdov&I%9_lav(Mi9M5?LCN0Kn^a|NEc-GP1~!lbG(Gm1Qy4Uy~6~6EUzgSs|yWZM43+ zOFKC@SU9>PzXAZ#t`^_iEzIe>Y}~EsIL_{m#S(rJ%dEi!SrfVg}rhoO#I%D|g+gUV}Q4Ila0Cj~oE4=i3 z@}W#BIj-G=dVt0IE#+uUp<#^R|~(WTy^A(v*3I-ES(m<0(Z%`53-LX!0xtQ zWYyVEBjZOneI0#bfrFhOr-QZQuGEF&DzM4hW&fp> z`X-CBZQos3MIhUlD|gL(heOQV+#CsX?l}$lLprlnthncHeA`z+KwhWkbH4_wk@-LH zu^Q>z&^THsHH}^SPxdhqyMaB!%Go}90nw|+w$)S9T%Xfc7zu)l47awkGq%^Hw(<7~ z`7+2HVGf(Mg3enb`VoX;1V+Ja68lyjU%(uf;U} zL~b@5+-iF@g8P$)9>jd=-sQhmW-7>WN=^H?GiWO|qd>=B6TV7{X8?pwCH zrVNg&-vOSp~X&3u(Yd zsW^S{kxROnK5p4Cz+Eu@GdJ5Y<@W)YveMsCATxjZfL-8oHk*Bm2k4^Z^7i2F*s$aG zqIccq&0ajrWHIZ^vpw3%7BT7@B`A1%k6h`0R-IPMC!VB~4yDt*k?)vlySn1o$G9@b zNz5E3VkgE@-Z|M-ePq74;qQ_c4{I*ne>$Ro-~6&(wE-aaS<)*WMGkQd2e8- ziuX}D3m2+zu(N~RbJje*)Q;JCHY^=o@PI`eNus*?bl?S2Ynxi&oq0GH;J-K0dK-&OZo-#UOs{=Z&y{_UzUBbMxf&zJ&<;mP~KGkUUiuLfF1&PeO6gc72d9>JvyI%L1G*D@IfdPV!1MEFP=1NxnC6GFoW*nqeZ4 z$x*Q3*c@HJ{BtmDTFKAYo^Pf_Munq$rsrbe0!$RC^uF>qg>|$LzsFs~$d#L6mrltYH9H!_ZJJMrAY^)sd^ zxzIU}@vlJzpQ4^7RLTS~9aKtBxibnuJ!SaX;chBEggF<&tTJZ~SNFB?_m{5ipvDt& zp__#ctP=c06Q^`0RrlPfU^qSf#E~36wv%nSg5rA+Cp7KHvd319soFyC=0H{(Ni3E( zgGp)F<`@1H4>z6McsY&7z*V5;d4{W`w_`c4?*X*q<5 z9_B_7-{VGhiy*?K$#=JGUP!_YSVmS!a{}o;ZG?5$TOUlkcRVl>rOWlt#dUVBW08 z#rUh!cSprwj?iLwJmG5(zGJUFYuWFdZr)d9kAE447GKL9#dKrXf%Kj0B#H$*V{yf# z$%L^yTJN>GBGwC|3nI-3d*6$ocx>6kt>rNjU~fLauqJ4Pz#r8$yL9Iv=4gzMdvvqK_rQM)XD)lV z+-Oj)jsiR7ST3mMhwa5B-J!qHa}1b0a}`T?(D$)(g{S>M`Ckn^Ku+m&@5j!d=xhxr zi*5+L)T(Ch7%NuK`;h4(qc0l;W zo8h{EAdI8%Xf~FyX;Y>Qe_hE4#Oo?k02^R;S!jCO{1R+1g$OY zc=T>>CpruF-O#)8zM&voT7up6;-l<3lWuRH@IL|>P%C2-OWw3nNVB;gi!4Z9K5ny; zRsVPXCYage{N}wFh}CtEwFGN(&+glwshaNjBM#v)AcXAw#T*UPmvhn_Q;M)y8ze8g zBT7h=y;NI>#m5@@d3Wv(-gdVj-N3{1vC|Bd%C|Br^NNnK6JzWfOV$L4DLE`f=1=s$ zQKnXNX{C1_3{4}uvY@Q(>pah~z{|n$OxpfZ^|szolbM6sP%{hmFC>k*Cjp&h#)4Xu zLqI5L=0}GA{)VeTzT&MdEiGv*7Rd%75lx~dXOt-+i)%o}StwragfQnnE=cZ(rG80i ztGE%HNNzp5boaXPj^-lWf|x;SkQgT=6dBJC+}mX6c9bUvL+)tWuxlU?0n_foUggv4SSdnd78xZ|onBeY|tOg~X3} z-*x;J==2|AD>uKSf+(}?T3Gc4G}lXl&IFzX<}wP@J_Z%!*q=W5jzH%{)-#mjA}%lGvFH;rfuR^8TZEI@@OP3kC-@yz|EBYS z*!BgP-p_CT{{AD_Q#@YcIXcYXmuN-K!nYQ(`$XUEq(Emgi5fbe&<-z&3G2A7?->8e z5zwp18WFx9GN=M|R?BB-y6RUne*8g@TDbAai6t(0$kkyB-M0ESjkG#`+znq}#OP@w zi9Y%5+U*?Su=n4TI>>9T(OIAW#Mjg`n@-F5qT~`l;xlp35XkWNxGZ}Dql-y8NMmYt zSlOSk!SHJ|qMYV!l~T>)vttw3Al}~^!%wh(AF{l6Q>EQ9s2wcV?NnyA=e(B))&hD| zgyY4!Rn@UY({w_u7mZlO4&DO^QOi9x*8I8pQY)$=vY<0<-_QzcQX1nVG-3NitSrDQ zsn4b5bhFYp-jMhNZklGTRuiq8sw|Xhy4s8C20M?|Ck3cEu_*Q8;6%d@VhzER`_2Pj z2Fr$|%#61T^hxP303VCn&ZAnC%ut!6R>tP+p?viF3ga6(9w*>}o6coH(ne#7=I4~d zPToJ2D)F$*l2PeG;muisO5VL)9bx^M?(+U^Sw*T?x#%xooZ|~S6(b}ruxukN9yJ!z zl4IbKYa-kHhLy_3F2rG)sFwpH!^c?Y@^~H0*#)REl6KibiwnHOzNe(D@s7(LE09sf zl(8Y=sEZno&gh192*{^_uBuZ}L@a@&S9eDZW-{S;v9S!&!yR(zrFlih{*HdCis`f4 zHgtXWZxiVZM$6&5fqjZ~3EhlG9NW}wS&~cqQ|c;TClvz1y!CQ> zf~Pd}@ed{CrpTa#s&0~+LWxP=w4`YcOv)xuOF!_*ms2)VEZo_Lojf4w5GT#)fzQJF zh|_!_7u40}^}#wgEV;PgFss~bn5)_GrmZHWYS@`wRCmqCG-WVYc@|vvV)wkvR=%56 zKhzuX1D$|hOJ3@Z6U1kyqO6F`OPny14_FJRNF~d_QIXwV^Se1#-yxIpl*3N-_)iW( zHS1R4(J4#(Du>&+spzb1Dv-78uxNBz=QAdsHhWZ7=spl<*=FMJvR20ciLClh(JQJb z5i7=wziY69l^CDI9A_2k|5BpfXucNFM*LlRqyEI7TqdMwZZ>iEsAW^6J4h}YlKA_R zVpJ*vXM75g>>Fh?7Y)w1q+hRkpmX#>V`2>#=_;i)RW4dnYOoMrkL%+iX8l4%hdt&m z$Rp$6WCm+LG%}R@R`Wq2r7hXvdg_cgx9*pqeX&h-tWixWwy(`Q~a#H?8?ZM@j zJX|``Ba>64OR>xHzJzQCXSO`K0BjwSr%@j*ssiTLpDexnig?iq;yIR*htIG49;6AM=iK97jjpQXmuwa%0tMl#N;F#K z76A3xUi>IZnP#Wm=&J~YA8UW2-JAK}Mr^o39vxF3bZ;c)Q`j0(;Jqtfq^B@KA2qI< zmv;ILaFrFZIwF4Mk;qErD9EM6op3c^v96o?fH86wf>5$D7(Pu$B*4p=SNf(|5*D9zx@?FJQ3~DSXoI^&Ojmi z#c5pCr^++im`1|{S$%NzY(u)mgC5S-WW9vYMqR%>Mkg~(1t5&FeC~EX$j8M((r_*Z z6I%&%MoWaBN=7q|?S0K#W}dCwlr(V+@5yAt6j5U)0T!peQ2A+6oxP!0^8(g)t7bD_ z>!si2H?R=(NK+KBGOZZ{9iw#}rbC?eP6$K-Xd*@Ei3Fx*btgi{Rb{cnSkBwD9L((k zaES5>rI=WflC8{vggC04z$Nn*qk@lWVJ{>Jp_&XenZI=hOUQ8GE0WK# z=F{KDS@>#jB5WM^N2r;5&lZ&?@W@jTNiR%Q8Oi6sz8V`FXAR|~j_W;$C4Kpgl^sju z)N^BBs1?^&4f3%Tp)osJ;>Ue{T2EYU`F?sMpr5*FO!ji^VSd&b4yO%>aP00b3lxM^ zBZ223L^W4DcF{iU!aF9JN!3({ipI1P{)o(`O4~xezK{${cCSkiU0i}@&Jby=+{e$c zy5GhPJ3Jc&n8fPojuIBKP=ZFxe|WaXUSD6!7SfZT)-U0 zw(*BdJ=GsmIGJg{!=P;Zi&%}qvBU$%S97>P*qOZtQwc8urwj6TB$tMZlLZbbo#U5snDF4XOWEZhWvg%> z>t72s?{_rGx^6YXcEn#JXNZnk6x2r<2dv}uk9?H7y7#_}<`w#G0$36ZPt=`gM%BFF~sU z;ZsIJ;aWGzof1o3r@ zo#k(0fXt01Nh^i$O;F)nvwr(=;(5UcWSf*fA=0NsUie}dMLnsAIy04mpL8s2$DX0~+{ zvSJM-P80@mScgrD1y|7+$+R?JAA!?BA-VKKHfUP9QbkP&b$D?Zn~qj7*RL->u{@PK zOaf}M-fl(6v+;MZ;{Hs%CVLpI()mGN`QDajhuoZlg$FeYwqP?3ZanR1oh-^v3+Q6T ziNSbfzupnq+x`gyXC?!r~GK#dcJne?>H!b zoR>b%xS=d(Ah9!W;3OuOO=MruZhm3m6?=ot6GTJB6M7DPE)jfhHRh>>g{YyEv#FG_f3TgnozY0a+AP*xj*}W_k!3}- z_yUnTvYoPJlO1wNqkgep&&wmvcEjmS)0lk!4yxd#zg_DP0dtbpGi!&kBrf3hem9tFfc$ znUb;P3a2!jUY)g(J8TXcC(?SF7bu*|<43sf;q*k`_QtLNw_wxj4yeskFB=zr;?+YqW}GqKe2kQp)D|MoDdo$d#Q* zA}?kT?z|C$Ec!FQS;$aEjv`WFcl6NxYeiilKHKojLyn%Q&So2*xL2Z5plF@=qM|rV zc?Bw)l8tjMAC9Q!LVQ0twWT&M0#RLv>E*2Mu;ZGH7oXH?$vw$uC6$BF*Fw*0z6(PB zA}s3$MCkt$J`(dj-LCY_GbFIutsG4bi=oq}aJ_Z{V`>griz{AGz?2!Sn~x;?Ip;ijbmVg7Gxmx1QP3SB%kSDbRbLfSrx^SH zZkn6x{I@Y~#Gi-;5bK3vWOu9GfY76b{ZbK68?P&nOe*Xc?g*~B(aVgh~$=IH!E4_2HFA;Zw>v>I0p{IUv0wMu>crd`F{{op|$4uk!q=h`xpGX{a+ro>G+J1bmR{B*HTSpR@X#KW+T9kNy~ zwH}APHtTK?AfNXKcmW=`mK%Gxq|2{<{?Voiz zUR2*Sh_s?DVr1+BE_S$Zi9tDAXFJ*>8V)m>V~Lc7^3jXi2y4%L_!z3`^nYFe?t#%g zz6f2%qRe0s&YV6ywAtBmKO3!nV;}aj#ag#_vW9zQ$4(C4kc1;{_buw3*ZTYAb@_H? zeAy?KZDULFQKhuni{}R+m(4znRx){Gpp%~Wz@NasLUo@aZ>2o$(Fu=@iO?DQ-`^mk z{b?-aLTdo^ZmXO3rdRi?vMVBs$kv@&<;SwByn8?3puH6)Ts~fHtuuP=Mxg$J>|_LO z^?>M?ZtK>#8^8>ZnR?N!nGZMufU=-dPd5fDU0wv$V`&#Fn6!Duk(%IQ=QW_>bu~n3 zvqVI|HEt!^ogu$m3x}^M?y9XdRp6ac6K9D0^9)X0gxdc5W|l2uqYxvj?!y^&&FW6w z4+-;~BIS4-uBcaR+7p|p8|S)^d4tgh?mIYoop+|6FZoay#Ls*f~Br z?HBLroArY$c$fe41=$X$W-1wfHcTw1pjh5d3v;{%y8LK0&GOrEweRNTshhd$0e^9h zWC`5G^{5;x0Z5^w-0worOmscpzilfpe7&3H%qNkpLaL2Nwac!>iT!=Ff~R$$MHi!; zT`7e)Ie>c)E<6g9M=F10@#P^s(LEm;}E*jk0fIA*g zz>`OokiXAoDS?WOi@m~2mP3;*g{M^N;o)JsG$n}4#GedYFkB-_J!s&t@=q`fL9UruOh>^>eHU7+ow5W|)Zq`n@{iNm)&0RyzIR90Sb(P;>$lL2Ti(|T*c)=}t zuA1V6eHaU9j1VM*Y?qv-3J7&Ow`T}xY5G_Sa=wTbw4*wm!u2| zrTzDx54;5@N17DdYo-b1I>&8>eL#}}o-yvd^WHwiY$ z*>#7J-2F`40(L_|$cNFgY_A{C`JV7+-Hki_RD9M#3(e-O$(f8*)w@j|LGwkNnVl{1 z6Z!fMSL{=Q@xPS5?$6ytt~utOn+Y($)Ex$Y)t?RPvGyH$VZ;r!`73ZW_+r$}%*s-#bAccB|w&csP5u58ptJn;df^+sb)9 zw?)3%IEkfknO2$tKY&JA$~4Igu~Et@m2_WGeM(g~5^wmc=^BS4Dtp(Jq7KgjeB_>< zYxZ6u`nhd^48kS_0?`M0rm6btG{-FeH5s(47q4SKV zJK(U5Cn6V=Mv z5Tft(?X^>2w61X=^-vtOK@y48H#U;SDY2+u_feN(Cu8plH9qudq07$be2pLZVc0Pd zD^Z}{ePUH*KJxG$L90Oc*l2X8j%!<_e-x`ay}iFSDf4v0%q&^@Iqp3xf22&X4q@1o zKn)1ud99#XKzm{G+toH#e0bxevh)L*vzOH~VB9@aVY0%Q`5&*g2KVY3=eM|`FHTH+BI2C?s)NxiJO7KHYxLgN4*gXCQDJv_ zDL_uUF%Yxo71N>o&-}p4|3LmQ>W+&EQK7)iz5*?cDQYF@gRJu##+-NR*P1)*@@^W0 ztWf7=@fwTZ!HdiP0yLS^N`%DFwX02|#B-yDI~(`ZMNVW7f3R5eDr6SaP;s}#m#U!1 zCY$!hJIX>1EB_+q|5cB^AP}I)Uj&z8(E-(!4@co{2wu-pQO8N6R zEE!zLspI!I4=wW#-o;KHWh5^&xe5x#3V_Yj1v zO;B$CzS~SU=7tXX-I?!priDIw^bL%QHz+7p44E=$;

W*TNJA*QIN&aaXx5~FDlOgxPWmx(JG5FO+ZI=&n7Ld-TeBou&K$408I^G)|7seP76lRKXN#2>f6nO?s!FboV9kb$6qvs=a! zreb~DX=fR1oHPaf^VLe#<|_WOwIJ$}Ry*v4CTM?RPBrWQgX`usUxJ}{S_!`9*3Yr2 z-{lf*LOQB`NP@8>aa?9jePXbDY|I#Dz#o1q-%7U`!-bC*vL>#nf<#)fsTpOrb-tf! z@;{er^zJYEOpLPPDbNL)Jg5L^6~B|D8&U(+yd_>x=0*^){+ zESY=TfgR!HRp)y|BBxtp5^&90+ZsvI`=r;GAQ1V80)RmaUpB*q5dV}M`4yv9${u>0 ztuRF;9)qTkk>K`JwDAT(3A>@hUQh{e$isB&p6VCowWu3*G`i^$>1X>2OuK({QlKS4Zmpw&-DDe;PPmEO)w=i z51iY)zsyj`RYZ|mBtw%0^YE?popB3ds9+9EHBDa{Q}{7hvR5`QBXujD?r>Unths#@ z8%fU8j{kH1iUcCgkSVdk|7E~EBgqLE)+m+SQ7hDasO?e9OMaciMfhROJ2mDH;!pHX z^l~jlZS*?iU*;#!fBN*W@ciiZ7Xp&KL6#W@?%!|?@UU$?N}7QlIN1aee@2WSP3t0M zv5Zp=xi7M+GX?7&N+t4J0;m_)X!Jj=`k7BI^S`{b#)_BzCkLR}=IWPl(?A*$Hm{16 zytkhaE)|Eld2hUJUv!4mSLq3-qPY)_6s|W1|$|s@5@%?33HH{~ycEQm2 zD+l$3UydB&dXEMQ=((eU)soYqHToeO7O8LE_V(J}0>P@BL)r8uC9V@R+v3`azkcj3 z^d5S)hj+NZC&!oL>1zJxyXb>YrnR*a?s92+rZK$sRHL7eE1HFlYh3vOjZ+q#2t%@S z2^-5^TePnZdh_+o%^RCtEn4+d8z8wKrQ=&q<(>7Ab4-8*B>cwrY;%h#H_v$Fro=Tr zCzvl=rOxd|+_T28cPE-NXt|jHd(Szi3olQpI}JLl@Io}$jmA}p`$7#MC^YOALLGu; z_d_I}$v8VNM@_4pnNJ-3CILg%`ZR}-dRMs|!5aJQ*=12jQ47Y2fp{UCayOMegYNDVXAhb<1g9W~j2~*`gA? z;~aJ3@upeG>)>DhQN$ir;itl0Ks>SnU(0=VSMxHycsmOCCy3dTbJ=l!_=qR@2%3@j zgVONAh~M~_NwnnQBlftO>9j@a)YZy-4R_sAZ;H7LpvRQBH&CRNuJqDy(?>W%Kt|mS z+hafyPar*P=!Qqu8thLw!Y4(WQds<8r4>MS(7|^g`|kbga@YPgI`-l;4J!#v2``=4 zM@_lUw3gI<)Tsm|z?KMW%L%#vPRdyADnJ7mId~P|o}5jadcff@wFkl4JO7J65(N)? zR9ae;XATrB!4utVkK^Fas!;u*K6=z`8LkU?yf(8}lr+HGc$O0m1PJ+4PRClnY+2K457-rB+OV@)bvr~JRM;akV0#WOnMnoGYS6_we+FD!8kD{_~6w6dly{IK+d2Pwc6dE~;?;bbP53R{O zUp>}BF{iw#0P(9U|Hp3^Mk~w;8KEI^`Je7SF2QC2TobXPQ;(Oo9tdWjzk9amammN|y53 z`G?$(8*ND3@Px8^512T7XNUmF|5-JV)4ZI&l6!%@~oAI<2z? zJE1Z}Cz0L!EztPwP4dQJ!kc*Cf-{)EE+HON4oqxif8I17)-1^Cvmxv@MUy@{F7I#L z!RM@La6(wVrH^1j+lBm18O8SqH>G__xAYaBe^K|e_h@JYeHh4S1B zgiW)~2R4sh6J*4d;vLnQdBY3i&ym6N8&cr5i8pzc!Ld-{-;1Jm;>f+Ghys7{#J6r_ z4V})Sh=n(?j_6mK1JOsApDgU+5FecU=!Km+y*^M?+?NQiQ?$$#t83hhHISF*L9p-j zVUkrz6#G?gpL{VdGf-@7a^!=~H`3bRa*2A1uey=EVN z%O>|c%(D?n>U_B!U*~djoK!Y;e}7+4q?CI%x7xn&I24HXe6M?TgGg3(%^-W}_@ua9 zDsWGAO~6|ALV~Y<^Ao?0^V-%!q~&qR&ikTio`i}tr}=M~s_+C2g8tCE^x+k>b4I*s zpT07wLW&K7xB4Q6X~Of1eTnqwiKmYo+m`$DlG~gZlJHYSVt4hu<9zQd%d6AemmK<6 z@~to0DJq??^`huQ^^*N={vEptws(DxA%?l(Yo~9r>M`TAZ2nri673*1vu$zs(hx|@ zH_q<{fk0X@rywckRipXyN7Vn)H=wph8naXzmS05(1|J^0Zx2eDC0?jjH)|8s4T&#} zV|EuCzCN5dxg7s1mR=%E9x*SEnX7SwuZgWZ8Na+f;p~zO1d7+c7uc47;0BiFzdF|> z4~V|^VxE`yl(Bh6Q#$(4ebI~A;(W2Cu`|{|Kewos-^45iOWiKtCx+pX=o*2}4EU1xOeuhY{-JL}$;m5*4Z)07=%!J0UqSi<95iI!izFfg^pr`Zo z;)PfBUXxWRI7@K}LW~QndXC?MSo^E1^T84- zvi~T)))oG_^Mx2kCsAfb!MuIL=3_6I58#Ch1KM#eWw^C*t)MiLh`a=?gC*9sL7LYy zY_MpWtUZukLAoZf;uc)t1MYOaPJ`E}7B7~Zq}GprcC5X3P9Lg^MFzki$j3aQWg%^x zq6#D2w1&bWDy$Q}{obUiz9FekiT%l2)XW{_$~FZQUN`5$wi?!rH7+T-;uP6VJT%*D zrMafJ;SzbBO|w`!61F?>Dwx^xp#E~i`q0wFcKEObHaiF*wQ!YIuga-5uBZe3G0QGu>_7 z7D7=s(U>2B)WmhO^aswai2^n#R*ePG7d|<(~>4{&WM{tFQI{W zp282u#OT9t{P(@#R*PKqnp@<)b0`Y2sYpo|FOe@p4QV1{*{~s$!7G`u+r#Q(xB%lz z{@FpCejbzSv>MajtMJpA4_q08g8f823S78FO%aRbqR4*0v$Pu6{# z>+rzH^VmdHd3jnlJ6UO!hDv|d1Lq*y>RvZL-{o@O!gHtIn->6o^k(Y{dj>!KxU zdUlYCd!6$8Q0B|{idQ<^&y_{^8b&J%$v0SjZDii{vKD@-sO1y+`v#l-vpc$GrK}(y zW%Y6k8L7yODH&>%PWK4;81o-Fqs&J;GnUe8rV1?5J~7)V8(Mr$T5%bWv`pdhcN@iq z9av*WMJ1HsX-iyj5#zZ}NYe3sn>Nw78(*tWn5H*A_NC~)o;SWyq0QiFW$@jnY!!c-?_HeQ?!aN8xndL?5k%F!QS1xU=2ma~ zOvmEU3J))kWW`PI(+g+UHOYi=$G&In^!3sBQHmj?SsZAk1s0|da(1?|__IX!h4Nm; z=s`n-b8SU;D<6lx9IfRt2AEVdRN+M5rWroW&rvrWBs|+cDBYZiR;sKJ^f`7dB-QBpUe4I~aKP+T@7}wci5rK4^8+SuaIm=-v0cEZ3tCvI}5~ zAkk_P&_|_4HX6*dPrD7|a+#S6-QahHcJo~Ko0v%90b|e29i{49>yu*&M*6lSqnDb$ zGKxJwNS*vSjtT}tq8MaAW57SW&n&yM;O}m3H!{56<-8bcUI+1=P4O6dUY@+jVHxIW zz=bdGsNe@g4Hsk$Q&>;P*1dqs9gWY1Aw}i+GyL26Z!>hv#c&+&iBrja4uWO^P9l@Q zO^=4V8%-kuNewXf%I&x0{=_f;^61~9UQ$`{KdXCaOiPM}4hwvu&3NqImK9b0tPNWA zC;Be$znVGoOP(gt3awxbK=|#iLEdqqE3xFvZ=tB#&bQR6*rUEJIN^&SIuOtY{#niW ztPG|ZB5j)m7b3CEi><5<*H>TRqPAH(v&8UQ>#?2T&&n=(w{49qs@YeJ$z)>T3S-O+ z0jS1scb?v=B$S+SOYieluZ8D+S~Z~eg)+Zw2kYEbu3n0p{EL|a74DN`etU5 zt#n2&5=i8AxV+j8#!SZ#CxgN;?@mT)s^ z<9HsS3;kiTNxl58F@h$8Y2Z+paqrU-6!eaKJgwn9PZ9;pO;|DBN00Y)nwU@6MirX# zX0^a3KAPijT zgr?EA1J)s%CP9vn@TobHFhNI?$5~yZ6j9LGjQORMEbW6F(PlGc-*p3P?z33X^6Y~# zz`4$q&gbi?VY(}7PACHxWb$qFJtDiT`vBa*y)G0;(|x~scHS4wDeiI@y+yanO=BjI zO`kiCRIJT@H-{gbe!M(!F!7}vIQ_`=iw>zE#mX^T@tT9>$Obmm7zez$d$` zyM2bNHh1jR3fYz#(be2etaQS%3vWM#Ahx&I@GM79+?33`2WCuzQKpC(<}@6)o}v&N zNJZG^_KeXcsLO6zv*4^S${^uo)<^sV4F!+Y9M2SY@brtlj7f{CVImgAo z`|a%Pl;rBx&uDoRa!$Ks({r#wVbyGeVS24Mf_61pMCiy4wk@mEU_i{8JIVG3U`sIx@08*VU%iA;qJb!Sb~sTeTPC z8*jY!LK;8%RV4;?1iO3JJMURfUzWSSUGAnyDp!ORBDix>%BW{0N!r`V{2n+M;lj=* z6iZ%b1r6NWno-9+?UJ(arOoVJD|o z+uZewxas3T1(F8pGMJqsmsH-5WOunQG4hDhZ|6tG+qXxM?S;h#A-B^N(^!L^ywPO# z##y9US3jaMxVn?|t7NQ2#q!CGDQ#3yK{aLBNw1|n$0X0st`p6YUwtCjRhdYSAxT82 zj-dAgCB#hm!e`}`EH-_3s02jJ@_6ZMhFDTxv(>h?6N{=Op#omL#`hLdE>5N|dr|-A z1wb_+7)6L56V_)`+)!)3G9iYtT!SI_nY6V(tG(6BFvYn4Owf~clr_S$RKm0<7qo;I z>Dvgg)+hrZxsp)^y=H|6@nvDcQGDsLw1h#xQs$)GSu<}`77^o?9ZmF@85=cpfv0XA zVHu;*4p^fvlFE~K!0o3El!0B(*SR4+{?7!p`~PS<>#wN3E)EafC5Uv0ba$6DNOyxs zry$*pfP{2+cT0E2&>)S((A_of{r>R&0nB3Eb?-U*?EQS6v%fDta}tt&RnWIxHK~XP zeTY5S*T`8eVwJwnl{di?K$qU{kNi)!}XeeU9I4A~f4>FmqlhQt=DXOBp;3e1r9Y z3QN16nY`?!9K0QAr}{roDrE|WJ5b;BKgaN|8?oZJz7v|w=O1l@UVsfM&I#r0qq6F8 zjaqr2)n0V_8YRvfM0{Rf%`2Rz8#{SRNYtMD%d=k6_ir+C5oR_8ah~Tnk?B>F=0}h9 zR(U8zJ>ZiC-sK%NdwV*7bHDo;{>tIyUl{+!n(41q#Fw0>AyFk+SGubx<mR-0@{)6)|rd_gX|H`y>-gj%A?fUUw7d#}!G|>nruU zY(nTyxcHK2f4PCS49QAC%&mnnSvT8RBy%`31JrIxw6k@t7k|)2DI&9V5J+Eu$<0^$=M%(W8zs#_ds0&?FZY)rPx=+pH+6b z1$6NWKV4(hLYB}lZ;29-Cy>kY{8brL`L&wwrOp2tovlR`X292OycLj8#x5nz`dR(% zGq`nhh3}Wp1Q}sHM~Hn`FerH@6ft0Vlv;@1;@u}UHe#MS%@}_pkW7D+zsGJQS);eMQmxC#|3gXICTzk2 zu{KVkX|qZ-(WcyGorBe4N@nGsCBA#3-%tS#570NgIj;GSrLZL22Redk$p@H-jJ0v6hhoJ9pkTPyKRSH(Q6nm5=;6O_i z$79+N4;jU9Co9=-PTRQ!E1a}UJpZWB!~et01%cK5;;4e}F=KOU!nP()y9k}?W^v~S z{qUH+cKLJOC(Cuqzpb&@vRyoWHQFMld2Xsw>6QVpK@=shddd-_RBGhbm|V4GWzs&b zl0DS#a;3+azU1N1JFdBY*!482So<;0&9?A~wg?^)|1A~1bi-(#VbPnM7-3(YjUhKI z>Tk=@w)BwE`HUHi?d}-AP}t4_;YDUy@$|B)I#*fiyGi=k>9iwxf;_}KT~_xIRnh+N zwaMi5bhl<9R(=HX~W=Y`N-f z8bg_Wxhds%YPk$K+$@*%1MdV}I_~+|hqF52nWcV4qyh0%9CT|Ik$-VmR#(u7FBGt( zDloIt!0cI3C-Nj7&-7zaVg2>x6JsJU!fkH-Ihn!B{sdGsUeETKT|iD;^O%h6FyP?j z_Kxl~lLu`M{}2Zy9lVY3o~Vj*cJw(#V9QInt(#ZTkk=LV#0VWhD}*b_LiXoDQF&TN z{h_8BTJ^hJBAl?*f+09Xbia9LUXEq@RFSbuY3cEC9LC?cFE5J@alCh51_ zQUx%dcqdtbEReZ3S$NNQpr3GNkZ(0vFhh-(m~amkhs|z%_@c8xqH3p4#!* z%2LlpML|mgzfKdhVNZWHN{LI35te#8CQ!8F#+Nh}w_7Vww*91;X@|f|!g7|;X)5gf z(N7?vtKPe_nQpZacey1ioGe8-o^%mzmo6yd`iW%gt)yRmYH8Ew^#w@q91+?l9e$gF zZE!plesXsWx_Pw9Hq4qUWshS~tERH>{h>*AgPgN=8($&D6^b|c_&TY0@^+|9S8Xhl z1{q=KZwy=T+RzUX@ekYOZog7Rc!en4Y~mdF$G~x{2ajEdx>4NCY2G+~6|H)Yeyo}m zs2eI~_cI|;b7^{zLv^Mr^1TWo@X(s1!n~ErlC)fXj(_k`4usb94khBC3JwL2!SHGG z!wGY#C{{ke0L+{zbVE2A0E+~>A2C=uUU`&&gP}lG{1?vw?kf#x^>jLhyQ0HythsVY zri@>=zNh7DjT!Mf;-(s%YZHGs^wzLfh3go(D;n^N8H0(K-B(x#9pB7pU$S)|bM1xB zD~c?{-~FgRRs*3vI-i!?yk0{ZC*n=HYKpTy;Ps!9!SOE4m;U7SN;FUli7X`HxSQtg zTRIT;#FKxymdsu5y*mr{6{EFaYm$J)sUR zRM0WFiY|0I+U#1ugGChrGg0!;%uN#Y;cG;D0)L!QeMX;u=vJd7sn~$1ss{4+zBQ!J zi-%_tNx(Z3t!hXVeThpYuedzeMzJM_Uk4-GPv1yr1q&|`8n*o}Z|kw*Pd!%Wdw4bsO|u~c#wpSUd;W@Z%~5z_pW=2fRLTvDMA#Gw zJ#H@$>%3GmNmp$oG{U!}FPJ`Uuh*b% z=$v}1;t>D|H1F9Zd~45L7M(28mNFLu_ij_N9}FoOOyOZ`rK2J@HKpDn9Uf6<};+)@f= z;QES~J?^W)CN-XMvJCumJFnx{H*#p;SAYL_1&Ct1!3@mErJXS$;amN_@bmjMM|A)5 zS6jZ)+is9CtI7MF4ZQcY7db5mbT9Dy2FlM-5>`o87-~s?)(Q?J80sv3b00!BsW`DeJuzLAXM%>loBRnr^Y1(GtU3%=& z&(u7lT^|jpk35;KF`@VFtVl^?zP58orAbA@&Q-@esWu_ozhapgluI_%|IE%x)!^n8 z^KprujvmwpASsJg%xS|@{qyzk9S{zo<}PcJR1tob^^GM(R#Yve8hsro;3svpxiL|q zaaeK9fXl;#{73XW0mJD2aJn&P@n}I*+GRb6$nn8u{vwmOQicX1)Nc>;<=qfe>f<0c z^S%B8;TCuj<&HeB>xNyxn0WjZ`6-E$y@X3#Ziidhz!s^eY>dx+)H4Gma_-}&K)C=} zzHbv$1X>Zpb*QfV(sbg(?O-7pfJ&wj6#V1=DsTr7E#TKPci>F^@9Qh00V&F`BuR~| ztyB^RmimW%mXx1Mt;PAB(#CBcjzkKmL$YyhRp6Be=M^N18x8Ot?bT2>=bJ@ND+-D< zyn@MK>=w{0kb21Y7&i|0j|e@$6yB1kozhy-vp<4(j%HBQmI6j>^Ktn<+F0FWCrR$$ z7~wha!X0RSOT8}GsL*2apmtn&Wn5s7_t!3Bt}%^gk(T!GxUA^iSCn5E;_Ium&6%_a z&v#Rv;W+BqkH>8S2Q!$%_G}c$5WGHuM;zApA}V9VIMgEq{iO#)xx0zQ1|`^WzCEajY7S9&ITMK&ot-#Tr=w`)E+Z=pSs z{t>^>LS%H$k;Be7JXja_{oPDO#vi$c{GbmNE;VMYn|=SdGbp=Vke*jp34ms(nPc~_ z>+_D_gb%FwOXuVO-^Wp}%^X)=A9=?tjoqncBCS|(chju>*BgV9Yj{)r>iTZKl%9YJ zljwwQHvXN)T-ayDSx`kWV$2cKk4EC(+K9`$0cAX4B!K*&lCAb`AZBG#y0f0D|5(a* zL)epaJ%2#$b zvp!e-;@gJan^_E&qefex4nuf6zVh1~6{@G&T)ZA=Tjuc&8gt0B;bTXRUFALk$pEda7lyjiNfAI zl~qUyl+%TSE`=K@%AKNAKv>_6r~@3HvNdNymCBVw-YJ(>Aj`7Pv`Wu2a$b*^xVB=k zGGpV2-1z;X!Y&fdzv{yaoT^yRp!!FsHy+5&Rqa~+B*cegiPqyqZY8oN0hd}-bHAmi zW7sh^ws8Cn!h+Dx%e)EF0q2>fQR8E`@8Ln>!0h#B?~8GZwrW4E_U zBVql678{W!e-TyBQlkgTXM9ORFgZS<{{>f!Jo){)1T(;0e>R8rmQ8uC`hBm|=^hF9 zE`Mo=mzv$c@+O0VohI@}N6;hdJ9zL^tjtw|XAh+AGDjXYSC)dMXy*p+#y66dIaOwX zlY=?T%m>5#r!!m6Nkz@Y!7zEjgx%AezA0s(7`$@)2hYX8?n`vgeda>u z2?nwxf@uP7h0aMqCsgP2xJa|d_vqA|)2%{rw11Y0qCSYL!v|1#P5E-RneMjGMc$Jn zX>2-1olzl0))))E)y@6GG3>w$&CXA?i9Y_`cq2C_v@$R%VcNK>9-2H>Kd{m1J<|R1 zZoR5%(87Tjs)`+u8QnK0`K+t>Nw_-q+09(ct-{AYOJo~`gzi;$LsWv3J*#B&73|dW zuo<#BPs{P53yUE(>H5JVKhC2*=pm;9-Jf(b@2s`)Fe>L+avFq9v{m%`;rcZaEpCqe zB-Me?G`nDJZD6+n>~wx-f8u)q!_om2nd*G_7(*)RCmSO;IHHW8X9UU%G23IeUSV0W zC0O6Nmvp1}eR6l_l=SlJ2lkObjF+rLf)RY#Y!BVC(V&I9+Kd(#UByc^BEOnhl+7CV z*Tuk?>c0sEw<~+nWr+5t#JvN0NxA3B%pj4j=96DUCmz^868mR;gk)+nd9PEOG7ET< zg&{ike6niEUl)@Iq05i0Vs7Y-`SlU|wH{3A*>T8Y7gd<&(5Q~NJK zPotCEPt3@5j&tAtgG|&Lr15Wxvgye<1;qIG_Xa(vsrie$%~V4S?Wyk)vE+hsFlsH# zTA3Pd0-~yaTEyEpe91?b%oq}+y$d?r=So%CY{DA1muBrY`qLs75P+t9n(R;++fqErCCJp z8;8iOYy0Iq&!g=YM`5EK>GoUFq=PxlNaO<3hz8 zo~cYj;P=Y?#OnaMe*EanJ91Aq0ga}(m4n4JTA&}vME(&?EAN-VS8A{9O2n16IsU%z zrO^&bbI!;x0)d*@;a3PKlb&H-|4)xKCcr+&#{Klg?8gLgP;^+hbETYXeDZp*=ZI*Qdyd z%MC($hpV^avRb0YnAXp#w#FF6KVmQCmz!+LP$te1*dys`Z1mO39%-hep8a29p_if= z1Uqe;n#r1>L=Drc!LV4h;n*Hiod9e)bZ`z4an?&<0HoMn5Y>z>_;TmIw2C3U5r5hdH{>*S-r4%YHN&eWV7Dq=}Nr0WkI zay7fc-aP4MOBj7NJ}|iehhYs<%cnf>HzFb&)pn&pnNlQj5y(QF_&+nZ0ppvM=Vt}i zf2UME{b>ZvIr(qKRO8Gf*u#h(pPv*K>WpD8II`K+Gl;rQB9x-aC^m-ccb&2l5*DHz z*KeE34WLraZhDMH&kgdcsl5&foYR8|WN3!l#FEBjDY-qSuwVBc&EB?P!Z!V5Pl$(yFl!SFEi+;4d8@tcfSxg6Cpd0eJ!OgE2VkK#;-S6z03|^z zXR%($jXZXS)ltP=U@=RF8AJNPZ92e6^NSw&cuXmh^5!)}TGq+eY)81(@IM~>JGOaI zw~oa7(Ayz3i6C>oT|~k4?(XXIithAr+19V%{f_h-!-$AT2D^gnmV3XcM=7JqVVm1e z1@_l~hDz&KNzX~U8*4Yt)dXYbnMm9MxHmnN}sfs9|{su^451 z;Ee-l&0jKhq2nM@J|$IuVo@-ocO*nRuK#au!ch=DKfF_!>1-%3-U%Gow-aOC9OO4cy3ngG%y5h;PW_~ zC-|O-=5k};Trr8U_(W%}m6_Q>zNFa+hO4V&9q_`p*Phn*ETlEB*?lR{zjv)6(oJD>!vXDVD67DHes`9A0<5SYx=QFqVYMMVAC5t$# z|F~lUP?;$s%nG|lGU#TT2X5V6;;%xP3Z7kj~1B(XPRZUml5D-?$0H-8ArAxl; zp(=?HPjVf_J~2?&Ph`0?F&;oYNopsK^PdXJU(J7KFZY`Mf{r{aNj)lhzzLJoJ_GGA zxy`4a7!M5(c5*JESwYFy!dTgkJICot@ta-0di%rwcpl@s0pLi=h9{COtrF8u8}`3T zj2R0@6krdj1q8A{opIOqY0)IU%DW>}c`~Q7ov%S~NdoXDNg{%-3y!XPq4BuWWx*-O zUFjZFWxWb}b_Fm>I3z-cdgp%8pDgsAvoB-YWa~`tGeb*T!^#gXH-%@=VombFGW;L` z7BaycVN+`1;x@A~8InTC&0GyFFr(^sI7|7($ zop%op-5y&hmnd+r5~ibVbA5+JN1*fjh}VO+%VcBMOPgTW1A}wsVS=Y1M0{xMcU~I# zQa~f?3g2C99&p$0y3rslP3V`ceTNs;T`7(z7J&z?vP#871`DT|xb2~mZMKKMvUU#) z0YKeLjbCU(3Jdd7v6EP@9&zNv;^Cx|pJBZIvEx^W$!z1Di787$vZ732{$CU=wXU$W`m$PVO1r3L{(ndD(5hI*p zMnnNpmTI5H%Wk51afFe^6V$Z8y0M3cmbRJ_g+C{ee-iq~s^iFAUJaL?-+h3V4_ZF|nkk?YUT4^#@V-7=#Qy+@=T5U}A??azxZ=UDl0$`Dm?|a+fSk_Z!J%#gI^%-FihR0hC9l~4nk2OcIrSo2&*mmWxmQE{h zob|&UysZ;7^IwXs>fAGx%8jpps#(3CR7$4|yo3?9bRhF9mAk zjVS+}xWjgzqn%DT;iGwZAdpf^%474XiNQ&EXLBa<`QtMC?3u9-bPYjej@iJb#C2%# zVR#lx^*v}j&`U`g{?$7}MmIV29J z;S|WdW~1=@EICz5eR?5KPDE}XGQ&^0Gv`LUkaTsVy^uscg!lkBOZOKFIP7m*bTMMO z+(bkoJ`pnu70aGb-%KdbA@A@ZBp!}!53z|&DfA{vIH_z+6L=soDWyx(6tcco+Yc!E z6nk9$OK7`xM79JwASuT(II@GL*ERgP)H~z5WVHzg>C&YwSreuv8`VUlGYPkIB4i;- zh6L_(XyB2e-9f!|$IPvLUFzJr+PDeRi#%YL=;0meJanvNKua)e#Rb%khPkLdQ$RP3t@O2=YT1hGvu<)!*UFlyf+}|C zNE4BXz5V2Asrj%Fp#KZ&-=^#(uQ=Qrv_+db3ON3QBUE$ZsjC^3HEf2t+5mtRfRG%} zd!-t6$Cz#UMST@AvN>*(_FSML_Q6f|*_SL=?uV1jstJeV?hbT&?)NL6uAFB4{bqzEXZ?tY|NPq!s}%?3`7T6a3;E+e+44rmkSkqmzTqQ*LSPS zd@hK<+&k*kk0Xub-Pau-!TV(u()WwFwXU{S4vEe;-$DUNEKz0NfI(pHQEiy?)y7z- z!#w{qMU*;@s@N{S9LW~9INV2#eB3=q1_R21>wc6-0lz^Ft2gec|EcBV(oWNuW?u#^ zK`*R?9%B9%ZQoM69zNQ^Z@i&p>M`iVS}f+?mPia8C(s=L2k((ylL&V=69e7~D53a!(4R~ue# z;~IbF-i+lWLCLyx{YSCfTn%Z})_nkPw1Y+M{sGa|`aCJee9l6wtixA5V$%gyNZReD zp#*JO63v*(iMsl-cmrNuwcv!3T+cE`-hKJDuB7au84_?PX^Frnt-!}|A7ehl?G9}q zA*%2={8%6Qt;jtyCHBeI``(nSesN@Fxl}CtZd)safuUme2E`bOS9m8P-H3sBvAjV14uO_t$uI#I#c z9>-ZCd^_kUKC&F@);D80ju?Y z7^smq2C?QL1>{T^erX563N*SPCr@0@CD&g8<))TM;!W3WdOXHzgo%S}OAP-{FfFs6|CF{W zA#^ly#vk?VTuXY>=6Mz4m&4rU8?@lkc#!bo0oH4>Dj($L9`ApNK(^i<-U*b8hwdA_ zX(>jZTQ;${wbY)wPGd7yG`xF0ogVy2Cz{?_im&ZN4c5d7$k|QfK1E!fmzW`5lI{9w zv-$ONT%8tWx-8f5c=!dkPGJ?B%BSt|FuEe4QrHi6g^3MYbxPdfyYU#E3@A1FNrdcC zi>Ux$Ls$p9un59N~J z`UTki>)_2;QaGg?>Ekv2o2ks+l{C(SyJs{On4Fw;w@Y8iENtPR!eP-Fuw+rCn{cwl zMI!+yx1}p)aWIO`aRXkuV4BYTxVpWh}CW`rN)JB3N&>o0;iC@aBh@s!xWhl{hkOt9)kYw*m@Faa=XY2NT7hKN3Z zN^<IaXp!*2lAkyP2(wbuCD@!ypI3j16jkdGdbW7lhXOgjnn}gg-h&eImA?yM#oAu*1u`b zxPagI70`i^J9nDze)+MORvETnCf;UNH@zvepW59uCZLEwUPxb@LPWVm9^s2q4~mX| zq-?7k{OM$wr}@ha=pWeTZ0|?Xkk0TCbDF=rOkU+<&gA=J$y9E(0chHkSUU`Zi6|EH zLB<#pNMhjY4>(0??!3TtmY)10~;O!2;}~CEwf!?&I;vIv`MLem6CqDE*~CEG!x20ke2u&}nPcyBcXCS| z&Rw-}(gEK0X)i5O+ez1kOO24cb%&k4y>UL@H=!`c&d~4{hwTmj7o;H|)p@o-#xrDQ zxBHk6^;F=MGok>E<%@}A<#T7{udPaWLPa7gwAqDxiGrg(#CKl6wgK((_PRhNpw)|` zqL1d?+4k5fP0i~AWqMz^D~d&^hSZyVzA~DSDg+f8t785(XFZFZBrZ9eeqt|A*a$1r zbF8%Vxf6CxwG~eKcY8=qGh80SEc>uYMDV+w;+gLImL`U*OBcGQDz&>&aFLbMmt#c2 zU#7UHKTEay{P7v4tIn)TRtGO0A>S`QbobN&hP|gW&ODqb__34oA)MJUZbd>by;mC< zF4C>Mgm^RTQ(8srJ5B5d&0HwW@PW`N0ttfKMe4)GP{oYr52LW81ru-Af2bH`AG7&s zCa&&HZr$2D>9{X#^d4VSmF;RcP38;I6(rl%Sp>2$i|4El3av|+1+$YxR$TAAgntN- z-aPqccKtxnk0mp`?}Zb3d-GoF_9Z@ke&z%CNI2z{IJeA*=$RlAu{4MkV3ol_q&r)e zxCF$znf$>sp=fB|OfZ?`iT@I7)0;^1FXQN~Pyny6?#CQ3d(GGb2S}<(jv%jY&Vk&( zGP@v`qn87$y3enYY^BK${mLVui-#1}_GG~DagurrQa3t!ak(kDYtRHs+d~qb-)rHc zftMQ^Jp0EKXv&cJ8GajExiMI@61b(m^C$gVqi*=jKd^!T9;>xv6uT|j0devBp^e-4 zK0$T+&G3G=I~EZ7|I;mibrUuOt!trmLbCg#a(setffqo(2w6Qyh^$*i0n*`6oQb%< zBXy{z+%AdX1%F@CR{B;sGQsfP)Bo){M}al{>u;%YZsrE21!VToG2EF;70?vK`}N=7 zI70pjJ!w6|QeQt{M2cQmf5`uPzq<~%@3rVX2HUcZsfDj1O+wMzqK^?XVKg4PFoIB6 z&4O7o9&!q%G;`Q{HA{zqw+*thwLgJKOEqSY74BSlED}bA%-%qiFT5~tj4mFR_in_G zLx>i@zYJvd_6DO4jQ<4ST7tj6F4s|;*EqDaYcv50MnrFK0%5;sA3mWi`ejZQR6yGn z378^Ml}qR<@64|(`aLS{iFzbxWtFnT3?;ElTaxde)*J!=EFaM9A-ByKpv&}UiY|e@4PZ6x{?VN^&WfFM?%Ciza-}PLpCo<-Ka==#{rIm7r1tv@pV7l0>&2FKci$;+ zAX%)^_jq|=C&LQ=ql3q%FJ-jeLy+i{cmnWa>xMt>?o?7ao$i>Lfo&xCgRvxZjsHh^ zjK5GBq=66-z#OkmTdX$QLZ_@6`HXIh;ouzhynyFThuQyc!CUKRP)DW(yn4#iW<7j} z@$~)YX#I@*e^crN>6)K{y>(b%CSbYp(W74(!c66-W_%F(9hXdOLWg5PkD>|%S+DR) zV)}6fmfLZO%7HYAI%hV=; zP?>yS=}hb$pWG=p`}@Lt$XQ*yif)3BZx)8NSGS@X9OQ)a%B_>BY*2`rp#f;h`tL?`gyF*gK7cFnG89OSYjEu=U1Fj$(4MF)qIjvQ9tW*3^AJTkDe}JU#pB{3@Ua{QYt;5}jp3E#hNI zC3dR%+6KgszTGkC{Owa6PF=U#3| zx==4IeAX$q%{_4;8>_krbKaUZ_g?Wcct+ha!V`&BIjiq+Jmve7U zcFGF~f9B=3B$s+7;_`xV)EGX;DZlm2?yEelpr19F4^WOhjh_?dT6SUh=$lQ=t$p6F zs@SpgiFScw>EoC2LE;>-_6tw1JF?ka28U$ZZ5Zwn3H>2wztv+<32;Y*|Kg^e?jb(6 zJ|;tGvO7 zJ9~6M9-)rM7fbt@ql)6QNC+a^4L_OL=?eD*4KBJ;rWj)>TIVLdtYY}$!?^%>bpP0k`@c&xwx4=?($!VFK?rTl`KH(+bSAGtK zL39&-ZxOHDjqx_8|poDMXE?2SUAA6Ef<$ z%kP@-vU~QzlU`YDKE?**)kZ}eN}VeUvO~#KKUe~<$++N=I}R;UZeMAS$*}$Q8G>$f*I1bA~NTT%oO9(SGmnO z-{HVE(XI?v_Y~8bS+T|O_S*#gS`z(^VZ{p5KKOh@VCTxoIQ?76?x)Q^=^qxIMx9@e zBN%btw4!$!xaR+Yw@hBvVuFgAqwm=#IKBXilPADUx(E1$TT_gU-aSO-uL?XE;{>%K z3z`3*LZX^wk|i``eV-w}$uoK?!|~$k{E{Vb87hD^a9uiYV*Tp&Nh5CTRx+Vy%Wu)# zV(||)7#WMFAGu#D{&1cw23>yX3)cL2A-9e19w7oQ zSXzXVdm68N`s-@ozM-bzIM-l`NB|-UfD3@9R~4U0B-N7DdWiB5dJFOk^N;V(i^U9+ zQ~hTVuEhk))?>n!+Uf0euD5Z7<=Swh;kNO-QE>)C*ZNVwtHSHHxsCREcQgTr1_Anu z_WQH#tp;#yOM6s7{Qv=)s~Z@ul0Nx43<5=yJXkE^T8o>!{*~O{;zj|3NUR{n3QAs*HD39r>Fyjc? zaK6Yd%-2-Mka@M!@O*;f$*61Q%(|5DJQKl6g%i6Jxc>LBn=>30Qk8Sm6?DKG%wN zhMke?+Qx$o{*ND`$wdaHtU`9$hIlOO%@(aW zJ5qTIwnz76VW_AgZr)rlddLrysrIc7>dq^8k}@Ewams{dlg)JJQ3R~|52rSH#lW1v z@%2@{UIVEmVC=c^_;!B@SR8P=ydju8JUq$uzy)%)HsymldK4pf+j*73cp#ng8ineK z&+dotwww{A_9zqs_#W+`2lzuHPh*~H2=_J0;@0x{J!EUsrQb|PgVy)Xi={y`Z0jA& zIWgF@Sz1i*Lw3@6a=v;@u(ccMge>SI43bRhEbf?vU~)m?@?V?4f^3vYa`bHFo=UVF z|NlCPCpVr+V)A5HA|xj46(6N#4KV*MuSq@uqS9_>1XkcPd>bY=yE!4O7aAU5K1it* zvI<)`%R?w~p1&Z7YWofx?kP=ncpB9ZNM0Ha@W0-MCKI|vT8M8-)x%PEy4Qbv=%{oK zxVcm|!Qx2&1AW$e@!RyhW^G%zxaUTW6M>dJ+r_Y5|6cB&*s}iDmE!QrW9Jty@VdOx z`(5;tf3>w8XA-VA$-tPXXUfozPzKh-aEU_eNFlIl@}AFnc{wUEF|qiPsBXU`6LU64 zG;PPe4P~p+){oIN!Qskxq1;G=vzS~-EI!@gArT7b0kNo#3Y5?JY%o&%btfH=KNecAWpZ;pC~f*c*4SvvXKf-|Ec>5+Cs}^DMyp|fwU;Rl}T1> z4i8gdMbl*seMupO_aTM>rKIdOc*1T?!@MmAzS1WPBmU+Z1o-RA1kp!apL0N4vjcb| z%l4FO#4~;Mt2V!rn6VKsQA@5=6qOBu_slw#nlI;D(2v?8URM%PeD(sT?qj%*Kh1x} zCi2-DuXOzt*sB_AX9pKViMl~FU7%d*05`K)g)cT~;Y3joK8e1@r$8=T1e4|p>Vgq} z;mz@kH~;lll3usKiQ1(Fma6l7;`hS}$0Jqpbr+e^s_BZa#P(_vD?@>flc_6)jUS5dWCmT>+Ww<8QJv_obMuq8;b% zW>~=KAjNt2NCySjXM+FTZ&KlIp{W=n9%v`7e}I3IVOd=&$b{HR{oWc|sPKSv-d`a3 z-^l#Ao)QFXyE!%ICy8UnZ7+&qdS*%_!`GnCwDJOQOMi;0f?TV*tGqU8L5VdqRe`Mi zjuOW?NH2T$TzIA-i1=JEEoTKwILTTm`dh>EZdPaY=b!X?Z0KK`E|eS)b51{!8`H^O zTa%{aJ!w%C9YbHB*yCI67L>e96~3yr2uw*;=8hy%I6SO|pP}pSb*_7;Zsn+l>+J`c z3xw+Nv&c;u!hwV7xrAzQJxj_hQvHHMvSJeIslh6)5O@{ZxGU^O>*xj*wAzKH;rf87@y4FZ~7i=B_Z?(UtdL3Qk1EU};taaiw0D z>s`W*St0<^Fg`!auIG}%h~{B)W;TWR>c$QP{sa{^=EzOQo6KaXyptWSRs7Wki*C8T z5UJshJi$w9Le`SorOi#-R!Tu(6aP2mOot{FgI-Uc68V8g9<(Wmja|@LTzE*fMFBKq z^3?AMNegrX7!#YKC`y92f%;)gQL?>fPB(&Z zr9S`WH9~V;b=T)zZ^rW{#xYqkp|FSDE?z{lZLC5N>Z%Un!HH%SyQGJ#F4O5r3v1ns z*acwvRXYE~MD|(7>C#fjX-H&^Kvo5|_0J+CZL58}dP|VTBE$Dp8TLTH{$Pe`7r0L1 zte71CV8 zRs>c5n&=+sjq^6|4Qq)-5=tTOH*+MI6~>MG(ul80PK#DY(53Lq_Ifk^fal9r2Y<1f zH@xP>o)RQE_T#(O(=J-g(5E+FpV#XgDp;qT(-z3S{ffr$(qN&kg3^=6NzEsNN^pI1 zuWfTj7)<-;yhsB7V?fA%}E1ZA&> zG9WV8ciMw zQ}JmBHR#xLpDnleT>x)np(+1jhs9}5jr#+7l0!t(^bppfjtPF1?hOvy$NLd& z%}~GYZak(#{0^q^2_nykdI+>3VnA&Z;(3-uM`s_M93vJhk*r1VhCOn5#XXNMwmEra zF|!Ngm$!i&GH}+W^14ADJftsn6$alanB{q;P5J${)>lWv0oA16H94it;DW$)m40M= z6nf{#Z<&u_V(bH;o7y$enzZP^@gtve7uD=H2nOs*B)0jgTCkG7!JI$X97JGFce-G# z(&HPcivbPk;#eNivaf-F8vdfUkRCTm2R0lC0wW7)z#NKH&Hc3KeLi{UuE00>(7VM7 zhHz`?!Ow9SzC-8PMu}={4bA+XD>pEFcO?*US=ViU@Rp(qTN@NO?;P8p$H3KO`gbnu zz;U4w`UFB8c%Sq9w0G;gGum}& zXVr|Gd>?1!%8@p4{IG_`kSj!hzOUPrdUfIEOGEgtokAQRT$Wq|+;ZXU9As4EPNVV< zYY+&uMs}6V9r`@~g9Cx2vUh-+^oD_k$;;(1UAB-oFc$x6dav&b7ex^)zX>B*14Gy7 zLWaeY2J+Yr@FyR)aeaispN3uN^bmVxfsJ#k3XG*RT5I5P!=un(wV2@|XbSA_M%Q6d zE*u*Kx6FkH0xY9LJsdwoO~u7IvD!S|r|UGWg1U~EYv=!fLwv^~VdvW2FDVpNlc%rB=`fjRSDWjh#o) z+Xkof&?V_$8o&H|@XTxOj|+300_iB<*)vHF8XtVFhDEYHtKWNfP?4u|^XD5BxI%{> zG%X$mPFO)Tf@eeM*b|HuS%gFvm{_QP<}Qu1Z#aKCa6qfTP?1SVzRuYwUVGV@K^DyM z4V#f6H@yN7`T`u(PACSbV=P2yljmznJk~b(%((9F`4!zSngS;Nm|JP%4`@lD3OsG+ z^|@T2hqlUT9%MdNx{^SImyfj1S>Y(yU}$tery@OtGfYo;>{I6OaBpT;$`P7KOJ1(# z(Zg`+!kICjUe36EQ(bq3Na0%J*%XxKMQgI}2L?z|D)11%e0vY+>!DZ10pzd9{Y_0p z&H)x?5`5n<^mcGA^U$-G1UG+(k}-YIvy?!Wpo2TVf=&}TEa) zF~eQc*gsSYD<=*L_)U<%Lf2^!Vim8LJ#b)6j#jZ=2kS$SgKOLIL*G&!$3$LXpUmPZ zCF4SnhlA<^gufoBxaM+R(I0TE7wz;=L#)``9i{fw-Ir}X)nm-`-GhxFUT zJP&mpKHYIh#UYf^V!b7+IZ$kcrVll+dt;_K&3fcS4DGfOw|~snm^MiHvD6W?US5hM zQ+Y#ae=xZ;Je1f@eK*Zpy71iu0YpNlt6towBE5=p4Q=8JRTOM)Zux#1E;_Ii4A%j~ zuU&Ps4>OiD*x^K@=D$95Yn3zvIBwO)k}VM7ru@2z9!%EH`auDDuR~(01yG`2?*fWT z{&sJ{bE+*9{1V|6A;sKnCkMIqyXL3cCa0s`%2MN36fvfn2&qG{ z__}N)fjtcumq>jDOv<05j45E90Au9f)6mfDyw+sI(*^bX8%g>n+oVKait%g9A8LE! zFBvpX+$c{Tn?4?^?=SQ@L0w89w2P|BY|Sgfz=Sf(abG3Ng2fQ=@#17o+q?+LPs4t5 zYGyY5Ag7)lV>R`oTd#v#F9o-xFEtGTU`R}984D@44mo`pi#4r# z5&ob@+y_=^{i_cg8g4b$@dhuvvd8F)~C%hiG z`|Dp}6eeWLOWZWd*(MNwsG}rk<7HHt&A^x!moSU(d$S{Z(MpTCW}MyC9+vrAa?s}3 z^6E=>SxB7?Tig=|ATK?C?o{;cgJots*jhxby89+hYMH-cHv%h+$V_%d(Hnrg`XH>w zifMD!Yw&Fw-o5QK)K+t)>$kO+h`Z1VhsHY-IC5Ju9!qG4qCLUDb8$P`H-COvBFu1) z<1F{MLVA&xQ^(8Q4Pgxoaj{d~${ET)o9W$oSWqz6$F7V64xg|&-I2>sdnL~>Njf6abNS>jCSc7tzC(lvZq%ILq$k1FBh`}IY0CO z891OkZ!0%KEwzL3Aa2+&D-QB(Gg*xtAmFUqWJ#u-liTFVsI)>9BcnZQ%E+!CZuwhI z^W$9G0qS7DcHxSURXiHobxkS0Ir92a^xVA$X9e@Z%J|#@Ta$+a&sH^szx)h;^ZQWQ zRM1Q>CLYq7jDo;Q(j)7{U0fekQy>`>;C)6=-P)6>i+RjBQ(=oID9HDe(cRk#tR!!i z{$Uloj#EqI6))p*@s4UnbT0e183|;DXS-*hiZv2>saN>T2oc~B~(^2}SG)%k8jf9nQs#I1O=?8WX-Uu#rYpR)+( zvtdan+VNGW_Zxova*d!=xzVH=is)eZn3%N_#%Ho-vhSAX?a|DD0WGBXI(r`c%60z^$` zu#iG(G_0DJ?^%{1&vQ&Bllk8*r#e0qWy%%^9uja z;&^!Wg}w(*+zT>sZ628&LYpw@YZeT8J@J*zRkV5q^K4y76duChb&Z5|O8kc;qn%X0I zo9i1J_}(A>A$o%WKKku%vH#==>Z%ITzoFj;0Nw;}i!$x%9H4lBg_7Ee<7F^Wt-hp= zyWv=f?_gnlsxzDO)cr526d(+MWTS^JL+d-5RyrzrSZ-T;nYx80CLr#t1jce;r1&D)Hu((XkP@) zD_Nd=f8w_Ns$2Z~6ar|$P{bZe<>VsWGucjTA=DeX+% zz4yd@ZeDTafh%qw#I8FzJw^2o|A5D3iR#b)9M^8$nv1KoZ;QqbOX$qL{(3$0tzVo3 zSu=7%VD7_%kT1XW+*N~%HiL@*Ams&LXOE$AhE^M()yiOjtMA}>LLFzF9I;vLd7%-* z`a6TZE?rn{aG|Kr5<7BxodvS`^_r%#f+B6`!`s$0yl$i90DuFGrr}PqU1nhTIcMxXU6f^skAL?&96f)&99%Ylzx*x=_Va-0)Mfq8VLQ%AmNdb#@sEH_-!Ty1 z#X^7JGcdZSx_^BEKr5gnKrc%A#IXR_m(T^UBjZKcQ76!{g;2iv!X!# zXMcw4w{I^All2;|sdb&bx6PFK^}6h8y|>kJ-bOU-ODLP%2HP?V7AEWOX-G-_vL7MG zC5gP&*4E4}fVM$~^>?ea&gzHvF$kdvh@r^M+Sl=TJg?C+P;)J+>HPVkSZ&zjbI`IP zUuFTM{-)b&+DzM$_ZSX`xbwa5A&z5w{JY=H$^muV3j76#F1zomf~YL6&k-I!Tw=M4 zKK;Te;)ww z(Qki?gXhmt%OH&a8vvIzZR4z^Y1;)!+WeZpzcv>A$}M8=ILbU^_xo-2`?p~LK%kva z)_*|sbP`}5YL%HvZ@A9*6AB1~DUi7gB{2z!QBH_dpz^{oHq#a$#w7|XsF1066BXoD zI6FnJ@Z1|*4w7v_F(N}OEQ&*pcx$vRIle2HiAu)}_h}Q$HG!><@o6ul-F0on&zLU= zkYPkO-ErM;_uC$Tqa?xBty_5W2S31#*Iq+j6sW5T2L}f;6S*itT@3f9>!&VL`%1$;>OYjR>wQ>dLSQIC43+?VA_r{~+qAg* z>KfLd25Vffes9V2xeo3F@Q`h>21K8TjsegrV_1=)HQ&G*QskqCxXnZ+6Y_RM;i~KY zIykXxFFAP9ZdulchFJ%xn{gW=yBk&=`ug>~G8hc-?)&ef-|yptU;PTZj~}B9Y_FyO zppVsRxF-%ob{p!eto-N2zUQ)jANXH%-T$J?00K7RuqSg8jk}}?9p6{Ctd$D9 z*y_0^%9|}^rAE9)&&x0q{*BOH@6jUwW0Bz0ZKuFlKL`DD)1VvyZ3g&~N=IY&eqy%7 z>_}t;WgKV@+$&;ajKz;pKNGjR|w^_lUsQH$zh zmvO(->2!$=kQjMH~pFJ%)fQeO))|9gahy$v}pE&TS)HSUd0)U9>-crMFJ7&1)80$a~!25Wt z>gpn<9KtrzN$48Ym~}E_E^uud{IJ5`D*Z&ThX8@j*>uG~yXn3?#6oNEDrPnQwYtXE z(GgDm@gMQovu8N?(T{NZ5C0HXuV0_rr}OA_-x{j{(_%?A5xGxU&(->0}25R+CniP-x6KC&!ZQdhFzPxnzWMb5Qy*`ApAa66r3 z%+hbq%tBRFOQ5Ew_Aw1U7})SL8jY5~Shgc|eO9b;>xtYfMoA10c z@6DIBR4^pZ^Q8nuEBe{IhZ8zI$3(iEWDOHf0H0u*4eIzgdhO>?!rJv!my@kYhMHw< z;}@VYm)Ud8mhkh^Q2n}1yF*j7p=}g3WutpL^(bHlCI%8Vr}32qeCl~VNlpK^gn|6L z_4W0o8%roEq40GEZ(St)H~oL6{ds&ZGXy>c$C?3zPQ0wU&S+=03-mwfVK7k87M&N<;03(8z~aj2r$Y z`sFW*1&Aqt5~_bqfxxMwfZGNE(FK7g1K0@5qnEk6pbAjpJi__N-7^;j=Buy()_o}g zMjodtx{HKBTUrILyZh}S!lK8FvX-YI*kgy6AEB7E=(y z&NjXOOXuw*Z8RJFi-`u8-4d`(sw1rT{<-8dnJd ze6KlgKvB|pKc#?dK<9hl%3g0^Az-z}R_E@zNm)m;3V^LwNr@d;!$Jg@c$ka`XQymO z^0WwC&mv$f)As5XF8Zw9Eqy_g-@mWzJ%rtfZ-!62Pou(H2*`^M8uvsc${3t_+VnngC&3 zWYldD&=0UkPEYx(6iA7`!R3}g<3&*Y)r$8(MU?%ND0>!1UZdLlfeIqTlxh>p8jq&q zaq{CISW!UUw&A46t3K7bEi zY=fHN(TYnS3eSjJ9VO`#?VHtXQTP%N2+)PR__?Q410~nQ1*}EmAluBc%xoR8+DsVq zSvw#Mg#sA7FTce2*S|*k^eJMA zwrLSrbBp$lTI2_g&W#(g$U~PU9R%wAktd5{VVN&j&~1T7MB{!NkFJ?mqClP7*7{hs zhCOzHK|<#Tu-y*31OYX%C}YY1bzNV#rwM44iV5&d4m@_-9~#+j-I1+$SuB8coCTKK z9Yw1l-4TZra1>bfTku^M2`YvzP*^9>-@?N6QSXmI{U7OsG@ zKT(#UxO*2zJ3E*@dW7vC{RmgT^Bwe^2Fa?UYi!UNx-Lhb_rV$~i+v6W`h3HRnnZzZ z`u>}e`gt2*NNf-?w6S{Ot=Cu-iFHzZK%=StCcqEX!r4jCH%(Ay&62_L5FdVj0eI=@ z_eG)lB3P}2ZR%Nk6LSqeuZ{?0^0_CcpRv(npUF z7sV1aDT_(ZQJ#IOXG;BlJ+R1>Zee`nGM1_Z80icw3jmP3rc~P&mHLptGQsaJ6Ohn; zmRMGv53nqllZks09F%H1s{kDX{}{G2o%ZBi_oNaKnuo*Xy?BXlsF}H&8v2b=*tKpP`GE4fUY2L3QW zYG4i77|M5XV`7sVIni2>VHwPD?Vm4%&@PrSghj6i1E6md2HPY_Fc=J$#`kLPeWv;) zXyIW#Jb;1&oHivDpSoTT3zD0aZHz-TBs~_H>tch!V5vNzX@83q@s$UdeWEHYfdJi8WF3ZNnL44}vc>_-tCoNK9?lUm}uFc1+fUPQzlnx=HMVmoaYNZh^=xqMd z!|x9xptC#y)tJfy%!Y6&U`Xd6hhG>7qed;g->mG{Qx%~yd#jJV>#}*`z6PH1QRK;D z>kjAA(lHcR4Bp{$QG0+L0oEWgf&jT@6)|M5IneAIWhrqi6lNect|M9m0vl9Dc;UWy zJ_9Zw5D*Q@6L-J7?%sFazL@8j+`Wt4J9n_Yxp}5d+RDVWKv^66SYvy2XPDJ>V3li| zv;bX!K9u#Z(gk4sJ(XHA4V3)b?d+@h~ zHD!7g&bIx9f`HodUTi5VASd7|H0;YP4YXweBAoT(=MDy;l-UaT)Mf1wAk+#xl0e3P zN_GCyljWBxb8eH}cL@M?UG_Tckl9Bq?iq3WJ0W%>ZzvKwSTp9M1j88JFK81R`D3A=E^1>g@I+frO$ARq=vk$ayl`m9yQ$2fiP0Fyg+ z=H4vYCDzLJwZpAWWCX{=XF$APilRWC=X1>A5^TTU$8 zf`iXKL-E_+qWAUJt-xQ`sRwR>x~%H|l9fJlbYa;N_^gFIH(uYg_~-De)N5LBp(tMI zt&h>&Veg`X0BbR3T3Hwi0SN(a5j*l61{Ma!iX9O3_=L7ysq<2*zt=5*a8(QFDZ)a{ zs3GX!5{3Z3jJPctK)>!707RZLRZT3I0P!^k{+Epa(9U~79jSnv0VtbH|lZ5hvrt7Ryp@k@iQ#iq8w z5uM>Tm3q&^E~J_=XJBV9c>#Kgj#Mby--v10ioYo^@PR+KvxLN)Y(K179L>IHnoS?WbUeoXM<1d3^{>%$ zeg0juAW=O}uxv_GoTJ`vEiST9#R`N%S)tU(^}w8*>a8&7*Mcb6^_LU`v?CA zX0)O5SJ7r@%D=DZ_?wO%-@?VUpq7etXcG|h9Q$y|l^|YV(TOuP&QZL=J{@AwCb)rF zi(tO9gZ)oG#g$vPu)euzX89pepvDgYd0985RxtTpzE~V*WraS!W=+2^Fi)q`Gr$rWTdToU_o-|8TL9d$VB}+99Rjz_ zX{D&9j)XqfHod-!=+i6sqCOoU=^Kh$0ez}ZD>}8>B7KHHTIJ90CnCk^bc%z!cTxT7 zS4f{f?+pIpV=h*^1?d3_mt{Obx4|%xrzB7zEQ0a@g9gVo3qJec7o=^7F1R$%h6T`I z!L5HH66<3z^IjMdG3ps8Jw`<}{)wXTb)CPU07~mC7$9O|OA-nIpcn(Kz_)^fsk?8k zsNkjPTAex&J0{TAZJ>1^>yHBWUt9K~z_K+ngZ~=@{x9Rc(dV)$AV{e+aLt|bM-IHT z$aa;{#$pb3Q#1ithSA--IDG$oY`pvKnKG)b>-!i2#sOZpCGiRM=!d4`T0RAO?e#GM zp>_H^^Lc!MKpGM^NS#BsiSZ7yikzb!2FTD59}DohOhXDy-qST9u48i@bh3}i$z~_oHe@hEujX>68psT9FbUeo1#~-8o z&2Kt`zbr0OEX+_Y6Hv0!UxR-XpiiT~f>Ik9Wz<07Mh%Nph2|EylCnuLV(H`J<_I(` zuo2F-sA-S?Ws#u<|I$-(0kJSVFeg#_=@WNqK<53G_dasx zoSgw{&-D`%p?!BRS{(^i=R#yq`Ekn{cx3EHfwOITR_6arJwgEi2LrJS-q+kd?zt~6 z4b*{>ROtOr-L;7@vkflx_HgjU7r1fzHiql#OL(9YISqPND>O5}ydI07Tek=DusY3% z`o1jiw|=$~M_=oF7x1?{2U5Ke4LBBE!2to61FXn}Rp;_us1?bco5T&ik%^j7u*8g7 z@WCgKU#o)vU883aAs?r%p=KjCt_l-=tz*&)< zFW||j&MwOA0WM|=TPO$+;~zf%8ayktz}LhAh#p`-fI0R6W2u1O=OhM33DJmn#sSOq*SpodHPM;VV_DulaL9;2=HysdI?0|fR zH(hC9$JO&sRcWBh^{pJEAWXn;JjT&SA7TFoKfukm-tv}_t*oDamJ+Bn55OvCYs+AZ zCGd@&x0v0KKY*|7+NBiG)IpG1kp_dE9%duiqV~!6F$tEfudbW>E|vv4z7ylifUQ{o zT&uB+boj#a2w=INW~}G4r|y4GT=tfu-AsDDeXFM0G)@+QdaTcYrV8i)n#vv{cWhIaIjv!J z+ub%L5eZUsJzl^%$FhPuR$16Jx3G^LXtF;-M8Ip+%Oiu05dZs#0<%@XbEzzbMGrej zpP<_^#nxHeBX7E6V~`V8DtFtFFVO_#SavI1Md-=FL|>JLdanL`3(Hd|Uly=O=LiOX zvwjhMcV4$$fVJaTfkH7rZP$!rEM^|WdwY2C`RBOu+H2_b`=%E_s6|>5CZ2{WzgysG zt*@4D%_iG^ZnDF1`-7506~A(Ef%~D%|KW;m=$2jYki#% zZEgfB+SS_$bphxgMYluxbO})uA)8LI|F8dw>Q}!)@6n^q;9mr2;leG-2QBLBK3dqv z0O)LWl_0=(O!bP3F1HX?6a>Tu^t>GBqv)GO|9K&+o0sRJ+?Ts^cPTru5wFldZm`fSb<|qt^ltPZ&_p*VN zvRDXE`9=PhIPib-8vuU<_SM%_U^xitk@ys^ z|30q0`l>gPYqH6;z+8UUj5v1)_)K>!rq)`2+hp<@124izh##pQmSbhXx2%(b$o=}>I-_ScgdO7q zjQUivC|o9Y6N}c&E%&=vxqj+EuxOb^7XB5)j7Wg!3$WV+0+|Z}qPj-%=n-~5`2?4)UBmkN`V#ZW^gIJL zcWm|h{rOtRJ^*aI#_KEp&7$$OPK-5Y-2Tp;0=}*QDN(fPZu4dRmV@C^0MJ%U&?IDN z+rk&hZbORA;KHd6a_In-Z7v4~Y&;YiQS9sCXN^Lrc4L(_eC>%A`_YCK(B1-}2(YPq zU>%2?-^pl%7oUBG{BQrZ6ZkVNT(!uUeRid*fPctqzDxGcec8VKJ@2?wuAPtE2XqY! z1^#)q9VxoKRA3(&%M|D&0nI^V1X8s+E*2Ha(5fwWyuvyZSQb}W`}x=DO(gPNuB^`VB!Extid zbzNgJ9%J`|50L-e-ywbSq!suJ&}1F5I9+d#8Zz||;G-k>Tb8Z0qK$ou{{rp00ahsu zjBzo}g4&R$uL28eyQu@C*RW_|)by{I!pLBuA%N>nGjJITulhACYbAW$8&VJ7asYna zfs_E1nEY5Q(VBZrN`cHZET-yp>A0|jrj-YT6Su!_yI|m%V;=VDy(5DO(C@QB!B!mD zuX@GnFRaek0sfvlr&A-qjXVIKDxk*&l_nV*$F)CfEuXd_D;Lx3rCTpe+-ig?`rWK1U>9 zfS_wQ`aOkO@jaJa0l9NQ&waLng-{f=^uc%gpld!%9W}LqSV2apwb2R?v|Cl10Kn3B z=zyZGYfQ&uy!hxN6o31-NS{0bT7bXK`r9s8g{mXRu+3UR8AR>LcB&2wC_BMbRGRQH z0HSNSXb~UN@EOt8O<9s=BV^!Gk23o<`E$WtFANpJDxzB2PubVCy+H`@yULaf6@3Dn; zlMK7eE+{C|7aLQo& zDz~BML)+$)$eIW%OJ`jHJ1J_PetCUamP-MQzNs{wZ)NGe{!`f>)=T&v+B}KhOMdC3H}vqyHvEGFe_D`Zc++|z`yon_HF6W%%DTdhS$_DX@Qi^i4qI-d{gIv z&N8{ZEN)4sU-nSZ3Y z{uV2s{Rkp=E-ur)UUAv>v2l;+_YwEO6w7U!U4np`N=Y)_`2`m93rd5@uow;P%TVH* zeKdWRY5*s7ji)<1xc1doxN`F*lC`z@8p!H?&aUN4G;ATJ*usdmV~DMFyGz1tQTHLx z_u-@Of2r?@)|*(_r)A(1+8NCXw5g?pf(b1G)Io_1^Es1cI%dEEKi@^sodJB4HO&>! zLP-&W=vtF6`t*T4*St7*(EINTUaYniz28NaVfxiqh+EqGRt;Hy)6kd9U;sjyM+opW zjo)Sg<(4>o3?fMLJ<1C#1BA|xFbzWjinh}S>kf>J)bNuAFci>>9pGH^fPNS7*Fiw7 z$}FNk5RJi2cV5;5`^()9P0UBvj5Rib0AVqtoU!kKcnaH02#(Eo=zhLRffUbOs66+u z)bu6Gm#-0)efmQTno#_f05wL~JnN#e|1Lp51>nWW3GROI0j|IOHg3K7rim*)F`e&u zmU&J{17I-@4<^i4fJK1TKE5u!YA{4k2SPZh{6 zqZS|N63b>ZI%|s>z#m3t)~5bnVRpl^vA(PTK-0RXSWHq}C#p8` zETF;~v<(16AlRmEE`f-&d8IAEzYQqp(nGC)SK7?lWQ_VgXzL=`WW+aCfUjLZv+aT$F-s~`S*j)`?yN4UEz2bzJAcA0INv^Xbll+a}C;1L=wZ&DfwoU~B{da3KBw3-RO)$8v}fj(pZm-F9}c7{tRFy6-cg z_q*3J*t^J1gtqrVn;;;cOz`Z%1DyQmN7%l6d5P}V>!JYtWHMPw7FoB&C0JjH*eV-@ z7yzA=HDPZ^i)I0S+ilJR05QtZCr2oCJqb{L*EM$;o*+NV3}rp6$1-A&C~PnoAdX{X zS+*3Jv7k)^eA@4sjfOO>tzGS`ebLb#TR~dPft*a~;|CTkhX5dBmU-rY>?4mS<`RIM zJ1yXAavtQ~e!kc|)rzev#kWBEUPTBgfs>q%JkNo;#&l-~7>$-P^ms+^M~mzsR64NS z;_8;)Lz#3&3+Et;a5;`}IZ3gRCK#p(k~l`+eN|P7aap3M3UBST#5Zf%$`-x_{H=pl zmKs`+Wju2mpp=?%hFQ&7jCEF}{$VgbG0qMV>iUp=_W{n<*rRjayPENk=dsPPxIVt> z$^tS<*J`bkpj2A)#+VrcZD7$+A=kf$nbttu3MHtW;;a`xf7gzTT{$kAmamf9klqwe`gqgHUkWNYDKO1_gMh50fH4-Sr^FK7jCQ2HE?-7kMRxJ zFos$neZwR|bsrxi;Co(ZRIrX3t?lUJ^m!QgtcdlEE9M#VYgtt&pFKk~nVbO}?%B>d zfUoz+adGtFK>+)O=a4e$D)YW@PJH9O$cs_-JzLxfY#c zU8FWfKE9f~eAg|8nz>*nD@$Z4tcn$IXvTwQGe}gMjYg>U_kp7DW>O`(`|JDs;d@xj z7e6F0YnzQlo&{9bIi)>Kdg}*qjJ19b{XrjTuZJj(QAa?v1pYH3J&J)|zmI&Hqc#?p z8ZWOCpi3YinV-~khXySidPq4T>9CN;M0ZbdJ_}o4%iu)-PJx+gpMWTrYKj~cCLO%-upmAkX__KQ671~lp?>iKu~h?8_1G>7bPN0isIIw1 zO!~VIuuw9WEw&%Bbd6p1^g2=G31+sUkt=yrMTsg+X8W2XD3SzK63>DGDS`A-q=O#v zaWzu}s`tq#TeTK0?KkgZ5ww<|G>)}ysP_uxFH`0Y(XRb(y9IzpMs_Y!^hWTT*{ov3 zV*%S56Y*riSL75DWBvu*?NRA8TouIHD_GI56>AbJTw!0G-z?tS%ORLR~AxEauWTE(3E)qvQ~E0F>w@)ShEvL&j7J;B)NTrztTAQeKHW zmItKf9#ODZ)A_sN_Gt%;P5~3>0DS3!fm<$c?l+XaWJ=gh{ZTq{GwQI)N zXItEc>h}c$sSo`5L}BV>;sBN~srwdV4I z>wuPEEp?g6Ww!uJO;ebGQ`qBwV+Z%<0heMnoN9?hI!VZ2m0VD>#S%$x?L_x}bK zj*X#`L?1Ja#+nO$t~*9xmp*sr(Qkz8yDy@Y)5HQyj*jr?^Uv}6cfNxy#R6y`=Yc;w z$W6+xmespXoj5;iuS)^i!=5`3 z`1MUE(^pIJWw&i2BrOEd(Hc+7P@2|W!UklA1obKi7K_yz*r3j`jW2T~(K`~<&6qHef)E(bu z9OGKQkBub3Fp7|58KSxlPFC|#x^Y<{E@$^(FUwK8a=@Ujmn>-|D5J}GwHAmQRn9l$lIPT1}|yp zJ5lV8bYNVkath#|M0tS4;H2-k&q&hZ{I0v_UvuyEz_9_xZX11^PlFK!_WL3pzU>xy zjz^z;h9~d6hmG%lzj1zwo(^l(=k;rvX3c0{8w-0FE1KAHADyq+bf5OX`rTvzv_X${ z-Sdq;w7z%@u+#O$xyFSU53RBblzHWx5_vBduKGPmcxTu4gP_V(GJ6%f90f27L z1b{5dmi8}A(r?~n-6*AS;#{*)yl|V zSnzbkEuNz^0fs}wQH(eO`f-dwoFJ-elv#!<%aF)@1ix0Gk>;#wCkUKnTT)xS;ewJU z0KRg-qK$dZt1e$!&~=Sj3(L3^z;rri<&u5+gmOG%S+Y(BQJHxxl)YvP1eh$9`joO$ z-Z!~;qLSx}Bo|uVOJ4}?SgVq$rtZG z?{AVMSaZJDEdG-lSUeus4_j!h>2!)b&oLYhF_}z|=lK%tRTKp#J3D9U|Kdkqc&&5RZfP4I}YodI>F{n^6q(dZ^=t^scUIlx2ZBnIaa~weG?O$o_~4&sddl5-NS|x@*?t#Q7Wxt-|$|=U&_-uep2p6#(~K z;8CN!CLqKBeA7WdRTS8{e;<3hyV%^?IoIAS>$X;TKT)scD!1>6MKxwg!I*P4PhweY2jsovM} z;n(9FEh}Kn5`-o)>9UAL39%`W6%|^oBo>pP`}F0D>ruU4Z|P^AdL<@8ni-MzbKu@3;KPRl~wfrR0RV`D3fUcaLjpujI`@!C?Ai)s}dTzBVkAML#H58QpX zj`ppX5d}c6VWGQuL=4G!SeI`j2sqi_$Cn>}j8|TL73=HkX0mH#<5q{2c^@fLI$sBv zFFOxq>P-~CulL_KOTg;`z|n;Tf_h_|&2j+1pd;H8M?B3gbg|ymAk5p-krx9z|4$q0 z_(~i)n$j8v-|z@Ib{;kCdp=(@ruyY8P1CvaqD-t3MuGpArs+Jwlyfb|Ai;nyl9Zd5 zH3)n&XQL6yXU~8|JXe26U|D~dm+qT?=YO^tw7C6JM1Hqi03@AXf@Am5|7GCxjDehYp zSgd&f+?v-GxUFr96Jig`MoM)9lOteX(RF1@YH|6Spxw-a7g#9z`&~+0?moQYuGv-0 z42X7MyMTV%EI?2TFrDJjr=Q~a`|sneJ9o|m0J_9#*{tg+u2vaY2MJtK2(^s2q3JC_ zpO5>d6uD=PEnWTyu=WxGDLmOazMzzPy}ADyG3-v zl>|=I`<4W>ve0uoq;>3atURzNF{s|CLk*lCV!qTzc!-kq_kyl#=>Y4g`+mt~l^-Bl{d~3t${yrXm`6b@CeS2>Gt221L4$At;8Z%w`wlew9 zDBw0&r$L_gkAd6*c@8+N6zo1Ved`Y{fh*VP1zfG* zgio&T#PQ!Odt+UGV47ba7eB=B*%FK}1HiOD*Z+m?mmolk1Nlbwu_eBF*ghYCs;Xx7 z|APZFTe-*&Zq$%%wN)UO0LD=~n+P|Zq8N{7H@%S=M=DEgvlv+!c+;$V0orR+J3@bJ@5@!cQ%0Jq+F<4iWLF@h5)&74}vawzGvCbL>| zX)KKM{oLg5&q0GWHR8Y3b$ylr;Eiekv6PW4QnL;`G>W%t0ANw}+}36(DciCPI=a%q zj_(5K+P^m2krIgxUO4;bj9!0P0TVx;ODy_2X(J}?Yt^oad~8LYWBT|p>d9p3gCyWo zos$KqRpxqLwQvPs97U+o6m?Z0pHAoC9~%InN6A(l7kLpvhHAxH7qIP00yTh7j37Xt zn~DInsXpFDILASc9zCnC+0-51rHGkX&3vlat&je}>;aR1X!arOH3xp3M7d_8>5T7#`RyN?A3S;qT3X?(xy za)LFh##a{bb<(9i(Hm3vzDyXHS@E*i&dR1OGauhcYyxSX{#TaeQZ}r`i)ot9+ukfO zhi|N|E(LG|WjPnJk%U(1bF?VI68GHeK)^RoAT&|YqNA+9K>%OWtr8S)fD*?sveQ$P zj~>mean&MFVm09$sQz8Kg_XyTu0q+$VsZ-LFp97lMW~7bm81RpSoRq3Z7VjY?vRm+ zjNTn~$hu=JbO9xRkFn_OZkx?S9nOyc{?IMjn=a!n+yXBB>sA0Dedk^4y_L)M3tHd> zkS9bb%Y8I;Kz4$e+v6pyK0%SYNX~0R+rwhIWe@Fi26YPnm0tf10%G4-<33#r-uA$~ z{1PJ}K0Q~u zFJc#9@3{N11dxqJ`0B$C@y4Ayc=Pt{GrBO{ouw!Gv54KG<9!+cYfeC0_dZ{1x^DoG z1^%Iu#L_GPD*%voF)MwRY-rHNM|6AFA)UpmIHPI9I z!ITMT$ptU~Ic9>ZI7wy;k@NG9J>c)lAnFEMl5Jb3Sa#BjS_T5KJN9?nb3SSr1jGRl z;x4`y!Ylg?ujm? zfLLYXOP@Ic>|u*@&^CiD>AVWNPzTClRw;lzGl~|0VNIogNA9&<1~gR)%*B=S#O=cX z3%>yoj=pf4*`)8Wj z0(V`n))9`>_~re%SF_*mFL@AfZ$Hm)W}%$SVV1s6zF6W@pzR^87C^4&^LC|f&jFI9 z^5E0f2dfJ%% z*MUr#0rS``3Wt~}i#O;)AD;vKW&0U`w5m`{CWu|Hf6t)0+hl{j|Q+ho0Y~Y+9~CLW}t&EC%<87Xka!>-hr%N4-l>cZgY!`2i>}K{EgVAOJ~3 zK~!aR5+HQx|1PoIR+kb`MRcut2FM(_*I&U*bv}H_rGU6Wdk{JBSZgSSbfvG^LRXfc#J3a?%~e+@8ia+ug=#*VX%c!W;ayU!D(S0m%6ncK);3)GG-XW z6M&OMt3<9<{5~^2UB+i2xaGW?`Y2~>%3MgG5X#^~eUDsLurleUvBa&x!jthf^*F`RAYl(5l6iWGu3*3 zh5MszDInC{AlF`W0PA$_En1wLafss7D7nN>p=7-6_#Bj&-;-5+#(iXzpAi*?;bw?@I!1}x`e@S z*vLpr!pX@judh}%Zk43lu;129fzWH(%m`@eKuCQVs8##s9kav@T>=2#jC(5p@BtpH zDXrkeH3^Lq_7vEOS^o=g|`9br53ccS#?Xb|$>H^Sy|6C@J z0YQ8`M{-XE*Ak(kLttc2?C@>ThfA&;9}a!boMoM zi`0ZNipbCr=-->N&=?D&plc4)_Z)Tl!~qbn7z>th;Z2vFBSKs?sg zsx#I6X#$vjo=mKJu!AI&`_Z`4q!@~S14g||Z zfCY%?v%*1uh&$J?+{W1EQMf>J!v*B2asHc18?n14r|SKuR5sb5YcJ)2P4_wCAh)KV zPlEX?icL80b?#v??eTJ5qkjGzM-Lz3>MO6H-|q*7*OpNbTdQk)2_<{h+HXx2Y+|SN zXy7L74Fw%3*RpkQxkW3twMevfwu#;djSRLJ0iS@*Cm7VhLK`7nn;;-`lC2>@dZ3TB z4fYpcx=FhfvemM_PQLC9Yow*T=jhq9rT+i6{ko#Y-*EuOs^A`FMezkN05vVlLlxy) zEq~_;0)){RM!IY+EM^r;@qPSP3POqa|7eEca z@;f1rlWS)!?vcuhXRN|>SFg{{(wP^%>a(`A>XbmK2?NLePfY?E1*mAdiTp`AA zcma$+LQG42dWz#uKgH;scQAbAm0)5gJ}!C<7hRcdi`N}u7R#%R@|qIXP?=i$^fcj^ z{(I_+ICRa}0yS2apC|kGl?tqV@M!{kRJ^5z)aD4G_C%k0GW34FKtPKs^(aV7&|%#G zOfTsy-?}07P07~HJRhLQGK?QSMC}BCRSWR1iCTPwMSn2AZrf!y zyU@Y4XK@~5p)=@XS^U@3f6oG@x*;H8Dt<^=uND!p zhOj+Sa)`1qW9%|Do%u_T@KvG&FCzPwR=fc%jOVHS z!(a_Wly*p&Ll4W{1#L6U*vRNCOMV`fEEo~5@3zdzQt~W z#e}=7XjZUu>A@ESXi(Mf$Jwd%I*SdkT3}zep!lf+GO^W772{C_7$jz9xD+sO*|dOd zg=JUB(EVAuecEs=MY6(u!vTo(m=Xh+s(X9W9nYG(wx{&}TCpidF2Q(($_a7D&vvSU z)$;HV2OoTZ<2!e7<;D%KzrGL8cs7h~8|38J2ii?ky*0w#dYu+|>sEB`&a)nAh+g-# zKD4br$cgY}^x9&Ww`UFH(S7t?jD(uH7}`u_3UvYaE-Vk&(d-HbTs|Wu7W47@lw~;| z_iIu60`$@jX_*?y&vG22gM*m>u&T_df7aqWa+%I^EGviQ`wh9Rb`b%KKb41isoh$+DHUp1<#c$wLRYuQ(9YgO}i8w0M$aNx`*b(wX|~ z@$-H6%$S0JEp#8k)W`B{0$-`l8m=U-zd`}z6n5M07&H6fJuI{p+Z_XeGeJOAVfy$n z4jw+lrEAx`SYnC&w9~3J+E?oYn##kzGPcjIp7DhPgpgUB$pGjnNSaFKh=!Q}`FT2> zntA}=PUtM$qT@L;w#L=+!B~QD9lm zvhD7XQ^n}CaRYpYg(Ch{_xaG&wx07~**42H6HkB~XZ1Q2u>z^_8L8dAJf~7X*D}$D z`%LDZYc9Y(43sv+4th`Rd!j!3$L^f(I%e#8M@E2yfC@l)a)QIpKEvdlcd)s=ea7}( zFipT*rmb2-*z^=wS&Mb+piYFv%SP61pRd1Fx@KI_emab|vTvG!kD>^vrqVUl?$ZD; zGvHIN>kQbUt9{IXynbzM&6}-T>PsFBWR3dubv9Ua8sC(qP(Wh=uXg|A?_x%z-TU;g zC4IaM`g~sHcdlVstc4Dy#>dAPfB7Y97i=_vLFPV)8vJ=d@VboM`0KLxGnYZ1qFX=y1i%9ac&|_(kl^g> z0nIkZy*_pO)+Z3Q%0bL5q>6=-xd+eP@m^nnS{GeR9=Ok?dnOKS<1!YeVJY4FnxPR< zu6T@ySa7T5I>7eAWl`Yt?p^HMyNC5Z`lF?+UDx3?MzGNvyWr~SuReBJ4~Ela_BIy6 zzRowzEQFkIQ=bS;&$+k$rvn1tOaU!YyZ4k{FpOgMCv1lLKR2g!Nuy=o#_YiOTDnN0^Ey2HTQ7Z7U0VUDp z5rAF4(s_d5dEg;aWjufHC~yuFz}-xd&@1 zF`+|QMgTE2N=%gsSbB6#1f*YcfO*pe!OvY_Qnff=Qs+K)_nLGIyy}>ZA|!PfTn9Dp?guCa$%aCVK=?bb~`0KS<4R`;B>A5B*BIw;V; zrxvZPsR|w4bW!vRMJ=jxy*-O%;2=W!)VT*!56#i972$*BU!awiIsmff3g{q*PoOnm z$akJ}5MTu%nnlrVd`<5W>dMeJM_HEGeewhke*SZu{PLHG_xES>AK;(THN|2sc&PRV z_n{mbV7JOcl-WJSD5Ndw?xDq4FOnlHo0ahYjwHcf_(qPF?=_TD zVuJ;U0!)U!j&iThJC)~OaB z1wNCtp^uRF*mVGW6Wt~TN{Q+OXFUnz{7Q@8pn;l~Qby&zj~jvj2?P$@`IGj+tL|8Q zJ_1X6fTF}c>7$1w^NAQR$<(mDm`+V#;KrcF!x1KuC%P||>yMbjlN(NQ#2*oL*!$Oi z#g*4z$NJ{x(kvLR>+8f(cbECv=Jon-CBathUpuvG4FK;KCx2Wr*7&ad1yY?U>-Gj; z_Na#?aBG@;`Re-oy3^CsV8*K3==5YJ2{7cjLfME=L)TbzmTzeoIQp{21alCi>pyxt zvCktw2LL{OfkcRW^H>59iwW@=7ZrJqr(b@Fum9Ko!sH+R0ny=M<3ur03_qn0Is-q? zJuk5^K;da#t1ngvsBM#%c=)v&1Va&eDrZVd3l z@4bn=okKi(eu&n6Cg3ML+ zWSd70JlD}}7?YGCj*WXkn1&PB-2hPf24MjtkvPIxL#j||O}sl~mc?bq)Lb@7kaLrz zEW`MVFL3a~A7Xgtj@fM{Woce_d6tS^mwibo1GuzdYgT%GUdgf58Wa?o_Gel4Th{rh zCC+P0ApATU*MmZW4LMg<8=%$x$j|e&BZgd~_{s z^i=|{T7j3;vSmO|g7&uEB{2wcEni?E%uG7mvtl28>It5O=?Q5JB0BGDSlDn+an}2x zX-%ozc|68KY$>Gy)(D6$WxBJ2{ZBr@wKw0y`o_kYELt#%YyE;BmPX6MKMS8kq^&WS zJIcCXa%i@J2LHOQXXAi<5rySGq^V_TQPV)DYZ#h2kOLkJ21{*zLQiuWg{Cj~ z((zf~Z)LDuDfJ2qLzfajR@Kx=ixZ?mr% z+WFDTIXuO}T&b>S0YC--{Q(BouVa+wIN94nT9z1~p7om73xEp^ieX=h!cp`|OID@^ zki{|bx<(0L?6fc`mW6f^-8bT3CnIOYqe4m+>i}LS2J55&v~)@8h=S8=#5QOqKmwUf zcW#Rg0a-x-gp_0+Vxi1$8@LP8cj}?{W$4ca!VZw)w~eBE6pcp&RGwpST%)d*pAPj?LmcYl$ihOxKZ(F|y`v7pXCi^MpjAhNu?dc|tCjcAMKqNlKJX?+fEU4s${s<;iR z0x&6ZY+bvAo7XO3IOqX2@aV-6zPR@U{i0fFe6Dq@T^=Xy8ntFTX){U zPk;JTy!G9;kq+1J{O}O_d4}V9_8#K@P{KchfdkVFXFE?=Mb81r7(mez)FjA@2TLTTGO^wz290W*)&Z0f;f8@Vg3;zA6n+^#Ak$!oW^ws7mUTiDp##QORc${P6e(@$}Ha)golOdMg+uC_(=yEQCk z;(}Lk4(u7nD3o&)MJU(T5N~W?D@lQ9@!8Gu90xC6U^+UTgKUrPpHflsGNo}s_wOcV zlO9XV20V>glz>EUBEzmq3YIRb&xGvHnmZ?L+7YcX7zprRxJ>y<%lk=0e0`i1J_?H> z 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%0D`Jt#M1EoSdDb}(CMblt! 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

50J_F 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 + + + + Cube Wireframe + + + + + + + + diff --git a/packages/reshader.gl/src/Geometry.ts b/packages/reshader.gl/src/Geometry.ts index 47d9e94d3f..7ffcc4c870 100644 --- a/packages/reshader.gl/src/Geometry.ts +++ b/packages/reshader.gl/src/Geometry.ts @@ -1029,7 +1029,21 @@ export default class Geometry { } } else { const array = attr.data || attr.array || attr; - if (isArray(array)) { + if (attr.buffer && !isArray(attr)) { + const itemBytes = attr.buffer.itemBytes; + const format = getItemFormat(attr, info.itemSize); + bufferDesc.push({ + arrayStride: info.itemSize * itemBytes, + attributes: [ + { + shaderLocation: info.location, + format, + offset: 0 + } + ] + }); + continue; + } else if (isArray(array)) { let format, itemBytes; if (attr.componentType) { format = getFormatFromGLTFAccessor(attr.componentType, attr.itemSize); diff --git a/packages/reshader.gl/src/Texture2D.ts b/packages/reshader.gl/src/Texture2D.ts index 513a5ec779..07e27c95ab 100644 --- a/packages/reshader.gl/src/Texture2D.ts +++ b/packages/reshader.gl/src/Texture2D.ts @@ -1,5 +1,5 @@ import parseRGBE from './common/HDR'; -import { isArray, isPowerOfTwo, resizeToPowerOfTwo, supportNPOT } from './common/Util'; +import { isArray, isFunction, isPowerOfTwo, resizeToPowerOfTwo, supportNPOT } from './common/Util'; import { default as Texture, REF_COUNT_KEY } from './AbstractTexture'; import { getUniqueTexture } from './common/REGLHelper'; import REGL, { Regl } from '@maptalks/regl'; @@ -11,6 +11,15 @@ import { KEY_DISPOSED } from './common/Constants'; * https://github.com/regl-project/regl/blob/gh-pages/API.md#textures */ export default class Texture2D extends Texture { + _version: number = 0; + + get version() { + return this._version; + } + + set version(version) { + throw new Error('Texture2D.version is read only.'); + } onLoad({ data }) { const config = this.config; @@ -48,7 +57,12 @@ export default class Texture2D extends Texture { //@internal _update() { if (this._texture && !this._texture[KEY_DISPOSED]) { - this._texture(this.config as any); + this._version++; + if (isFunction(this._texture)) { + this._texture(this.config as any); + } else { + (this._texture as any).update(this.config); + } } this.dirty = false; } diff --git a/packages/reshader.gl/src/common/Util.ts b/packages/reshader.gl/src/common/Util.ts index 6ad509f2a1..1648b7856c 100644 --- a/packages/reshader.gl/src/common/Util.ts +++ b/packages/reshader.gl/src/common/Util.ts @@ -499,5 +499,5 @@ export function resizeToPowerOfTwo(image: HTMLImageElement | NumberArray, width? } export function supportNPOT(regl: any) { - return regl['_gl'] instanceof WebGL2RenderingContext; + return !regl['_gl'] || (regl['_gl'] instanceof WebGL2RenderingContext); } diff --git a/packages/reshader.gl/src/shader/Shader.ts b/packages/reshader.gl/src/shader/Shader.ts index 64d266a572..f89f6009e5 100644 --- a/packages/reshader.gl/src/shader/Shader.ts +++ b/packages/reshader.gl/src/shader/Shader.ts @@ -454,7 +454,7 @@ export default class GPUShader extends GLShader { const groupKey = meshBuffer.version + '-' + shaderBuffer.version; // 获取或者生成bind group let bindGroup = this._bindGroupCache[groupKey]; - if (!bindGroup) { + if (!bindGroup || (bindGroup as any).outdated) { bindGroup = bindGroupFormat.createBindGroup(device, mesh, layout, shaderBuffer, meshBuffer); // 缓存bind group,只要buffer没有发生变化,即可以重用 // TODO 可以考虑每帧开始把缓存 bind group 标记为 retire,每帧结束时把不是 current 的 bind group 销毁掉 diff --git a/packages/reshader.gl/src/shadow/ShadowPass.js b/packages/reshader.gl/src/shadow/ShadowPass.js index d742a93b65..8c799e6a7f 100644 --- a/packages/reshader.gl/src/shadow/ShadowPass.js +++ b/packages/reshader.gl/src/shadow/ShadowPass.js @@ -86,7 +86,7 @@ class ShadowPass { height = this.height; this.depthTex = regl.texture({ width, height, - format: 'rgb', + format: 'rgba', type, min: 'nearest', mag: 'nearest', @@ -105,7 +105,7 @@ class ShadowPass { this.blurTex = regl.texture({ width, height, - format : 'rgb', + format : 'rgba', type, min : 'linear', mag : 'linear' diff --git a/packages/reshader.gl/src/webgpu/BindGroupFormat.ts b/packages/reshader.gl/src/webgpu/BindGroupFormat.ts index 412f4f2fea..85eb11d7cb 100644 --- a/packages/reshader.gl/src/webgpu/BindGroupFormat.ts +++ b/packages/reshader.gl/src/webgpu/BindGroupFormat.ts @@ -58,11 +58,15 @@ export default class BindGroupFormat { createBindGroup(device: GraphicsDevice, mesh: Mesh, layout: GPUBindGroupLayout, shaderBuffer: DynamicBuffer, meshBuffer: DynamicBuffer) { const groups = this.groups[0]; const entries = []; + const textures = []; for (let i = 0; i < groups.length; i++) { const group = groups[i]; const name = group.name; if (group.resourceType === ResourceType.Sampler) { - const texture = (mesh.getUniform(name) || mesh.material && mesh.material.getUniform(name)) as Texture2D; + // we assume sampler's name always be [textureName]Sampler + const textureName = name.substring(0, name.length - 7); + const texture = (mesh.getUniform(textureName) || mesh.material && mesh.material.getUniform(textureName)) as Texture2D; + //TODO texture是否存在 const { min, mag, wrapS, wrapT } = (texture as Texture2D).config; const filters = toGPUSampler(min, mag, wrapS, wrapT); const sampler = device.wgpu.createSampler(filters); @@ -72,9 +76,11 @@ export default class BindGroupFormat { }); } else if (group.resourceType === ResourceType.Texture) { const texture = (mesh.getUniform(name) || mesh.material && mesh.material.getUniform(name)) as Texture2D; + const graphicsTexture = texture.getREGLTexture(device); + textures.push(graphicsTexture); entries.push({ binding: group.binding, - resource: (texture.getREGLTexture(device) as GPUTexture).createView() + resource: graphicsTexture.getView() }); } else { const allocation = group.isGlobal ? shaderBuffer.allocation : meshBuffer.allocation; @@ -89,11 +95,15 @@ export default class BindGroupFormat { }); } } - return device.wgpu.createBindGroup({ + const bindGroup = device.wgpu.createBindGroup({ layout, label: '', entries }); + for (let i = 0; i < textures.length; i++) { + textures[i].addBindGroup(bindGroup); + } + return bindGroup; } dispose() { diff --git a/packages/reshader.gl/src/webgpu/CommandBuilder.ts b/packages/reshader.gl/src/webgpu/CommandBuilder.ts index dca28e382d..38f2517dae 100644 --- a/packages/reshader.gl/src/webgpu/CommandBuilder.ts +++ b/packages/reshader.gl/src/webgpu/CommandBuilder.ts @@ -66,16 +66,28 @@ export default class CommandBuilder { const entries = []; for (let i = 0; i < vertGroups.length; i++) { const groupInfo = vertGroups[i]; - const entry = this._createLayoutEntry(i, GPUShaderStage.VERTEX, groupInfo, mesh); - entries.push(entry); + for (let ii = 0; ii < groupInfo.length; ii++) { + const uniform = groupInfo[ii]; + if (!uniform) { + continue; + } + const entry = this._createLayoutEntry(uniform.binding, GPUShaderStage.VERTEX, uniform, mesh); + entries.push(entry); + } } for (let i = 0; i < fragGroups.length; i++) { const groupInfo = fragGroups[i]; - const entry = this._createLayoutEntry(i, GPUShaderStage.FRAGMENT, groupInfo, mesh); - entries.push(entry); + for (let ii = 0; ii < groupInfo.length; ii++) { + const uniform = groupInfo[ii]; + if (!uniform) { + continue; + } + const entry = this._createLayoutEntry(uniform.binding, GPUShaderStage.FRAGMENT, uniform, mesh); + entries.push(entry); + } } return this.device.wgpu.createBindGroupLayout({ - label: '', + label: this.name + '-bindgrouplayout', entries }); } @@ -84,7 +96,8 @@ export default class CommandBuilder { if (groupInfo.resourceType === ResourceType.Sampler) { return { binding, - visibility + visibility, + sampler: {} // sampler 采用默认值 }; } else if (groupInfo.resourceType === ResourceType.Texture) { @@ -153,6 +166,9 @@ export default class CommandBuilder { } for (let i = 0; i < groupReflect.length; i++) { const groupInfo = groupReflect[i]; + if (!groupInfo) { + continue; + } const { group, binding } = groupInfo; const members = groupInfo.members; let isGlobal = false; @@ -191,10 +207,11 @@ export default class CommandBuilder { } const buffers = mesh.geometry.getBufferDescriptor(vertInfo); const pipelineLayout = device.createPipelineLayout({ - label: this.name, + label: this.name + '-pipelinelayout', bindGroupLayouts: [layout] }); const pipelineOptions: GPURenderPipelineDescriptor = { + label: this.name + '-pipeline', layout: pipelineLayout, vertex: { module: vertModule, diff --git a/packages/reshader.gl/src/webgpu/DynamicBuffer.ts b/packages/reshader.gl/src/webgpu/DynamicBuffer.ts index 8c87674607..3055d4d936 100644 --- a/packages/reshader.gl/src/webgpu/DynamicBuffer.ts +++ b/packages/reshader.gl/src/webgpu/DynamicBuffer.ts @@ -13,7 +13,7 @@ export default class DynamicBuffer { constructor(bindgroupMapping, pool: DynamicBufferPool) { this.bindgroupMapping = bindgroupMapping; this.pool = pool; - this.dynamicOffsets = new Array(bindgroupMapping.length); + this.dynamicOffsets = new Array(bindgroupMapping.filter(uniform => (uniform.resourceType === ResourceType.Uniform || uniform.members)).length); this.allocation = {}; } @@ -29,10 +29,11 @@ export default class DynamicBuffer { let dynamicOffset = this.allocation.offset; const mapping = this.bindgroupMapping; const storage = this.allocation.storage; + let index = 0; for (let i = 0; i < mapping.length; i++) { const uniform = mapping[i]; - this.dynamicOffsets[i] = dynamicOffset; if (uniform.members) { + this.dynamicOffsets[index++] = dynamicOffset; for (let j = 0; j < uniform.members.length; j++) { const member = uniform.members[j]; const value = uniformValues[member.name] as number | number[]; @@ -42,6 +43,7 @@ export default class DynamicBuffer { } dynamicOffset += Math.min(mapping[i].size, bufferAlignment); } else if (uniform.resourceType === ResourceType.Uniform) { + this.dynamicOffsets[index++] = dynamicOffset; const value = uniformValues[uniform.name]; this._fillValue(storage, dynamicOffset, uniform.size(), value); dynamicOffset += Math.min(mapping[i].size(), bufferAlignment); diff --git a/packages/reshader.gl/src/webgpu/GraphicsDevice.ts b/packages/reshader.gl/src/webgpu/GraphicsDevice.ts index 9d7027a44d..9007c82d4c 100644 --- a/packages/reshader.gl/src/webgpu/GraphicsDevice.ts +++ b/packages/reshader.gl/src/webgpu/GraphicsDevice.ts @@ -1,4 +1,7 @@ +import { isArray } from "../common/Util"; +import { toTextureFormat } from "./common/ReglTranslator"; import DynamicBufferPool from "./DynamicBufferPool"; +import GraphicsTexture from "./GraphicsTexture"; export default class GraphicsDevice { wgpu: GPUDevice; @@ -48,12 +51,12 @@ export default class GraphicsDevice { } getDefaultRenderPassEncoder() { - let rendrTarget = this._defaultRenderTarget + let rendrTarget = this._defaultRenderTarget; if (!rendrTarget) { const canvas = this.context.canvas; const depthTexture = this.wgpu.createTexture({ size: [canvas.width, canvas.height], - format: 'depth24plus-stencil8', + format: "depth24plus-stencil8", usage: GPUTextureUsage.RENDER_ATTACHMENT, }); rendrTarget = this._defaultRenderTarget = { @@ -72,8 +75,8 @@ export default class GraphicsDevice { depthStoreOp: "store", stencilReadOnly: false, stencilClearValue: 255, - stencilLoadOp: 'clear', - stencilStoreOp: 'store' + stencilLoadOp: "clear", + stencilStoreOp: "store", }, }; } @@ -103,11 +106,20 @@ export default class GraphicsDevice { } } + buffer(options) { + + } + + read(options) { + //TODO 从帧缓冲中读取像素值 + } + framebuffer(reglFBODescriptor) { // reglDesciprtor => gpu renderPassEncoder + } - texture(reglTextureDescriptor) { - // regl texture descriptor => GPUTexture + texture(config) { + return new GraphicsTexture(this, config); } } diff --git a/packages/reshader.gl/src/webgpu/GraphicsTexture.ts b/packages/reshader.gl/src/webgpu/GraphicsTexture.ts new file mode 100644 index 0000000000..43a0a3fbf5 --- /dev/null +++ b/packages/reshader.gl/src/webgpu/GraphicsTexture.ts @@ -0,0 +1,89 @@ +import { isArray } from "../common/Util"; +import { toTextureFormat } from "./common/ReglTranslator"; +import GraphicsDevice from "./GraphicsDevice"; + +export default class GraphicsTexture { + texture: GPUTexture; + device: GraphicsDevice; + config: any; + //@internal + _bindGroups: GPUBindGroup[] = []; + + constructor(device: GraphicsDevice, config) { + this.device = device; + this.config = config; + this.update(this.config); + } + + update(config) { + const device = this.device.wgpu; + if (this.texture) { + this.texture.destroy(); + if (this._bindGroups.length) { + for (let i = 0; i < this._bindGroups.length; i++) { + (this._bindGroups[i] as any).outdated = true; + } + this._bindGroups = []; + } + } + let texture: GPUTexture; + { + let width = config.width; + let height = config.height; + if (width === undefined || height === undefined) { + const data = config.data; + if (isArray(config.data)) { + const length = config.data.length; + width = Math.sqrt(length / 4); + height = width; + } else { + width = data.width; + height = data.height; + } + } + texture = device.createTexture({ + size: [width, height, 1], + format: toTextureFormat(config.format, config.type), + usage: + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, + }); + if (isArray(config.data)) { + let data =config.data; + if (Array.isArray(config.data)) { + data = new Float32Array(data); + } + device.queue.writeTexture( + { texture: texture }, + data.buffer, + {}, + [width, height] + ); + } else { + device.queue.copyExternalImageToTexture( + { source: config.data }, + { texture: texture }, + [width, height] + ); + } + } + this.texture = texture; + } + + getView() { + return this.texture.createView(); + } + + addBindGroup(bindGroup) { + this._bindGroups.push(bindGroup); + } + + destroy() { + if (this.texture) { + this.texture.destroy(); + delete this.texture; + } + delete this._bindGroups; + } +} diff --git a/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts b/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts index d3760f170f..a632766931 100644 --- a/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts +++ b/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts @@ -70,3 +70,22 @@ export function toGPUSampler(minFilter: string, magFilter: GPUFilterMode, wrapS: sampler.addressModeV = ADDRESS_MODE_DICTIONARY[wrapT || 'clamp'] || wrapT; return sampler; } + + +export function toTextureFormat(format: string, type: string): GPUTextureFormat { + format = format || 'rgba'; + type = type || 'uint8'; + + if (type === 'uint8') { + if (format === 'rgba') { + return 'rgba8unorm'; + } else { + //TODO 各种压缩纹理的类型 + } + } else if (type === 'float16' || type === 'half float') { + return 'r16float'; + } else if (type === 'float') { + return 'r32float'; + } + return 'rgba8unorm'; +} From 63b9bfc39d164610f943a4a4258308f5e41aeb03 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Wed, 22 Jan 2025 22:09:28 +0800 Subject: [PATCH 16/53] implement InstancedMesh on webgpu --- debug/webgpu/cube-instance.html | 198 ++++++++++++++++++ debug/webgpu/cube-texture.html | 2 +- debug/webgpu/cube.html | 1 + packages/reshader.gl/src/Geometry.ts | 75 +++---- packages/reshader.gl/src/InstancedMesh.ts | 33 ++- packages/reshader.gl/src/shader/Shader.ts | 18 +- .../reshader.gl/src/webgpu/BindGroupFormat.ts | 13 ++ .../reshader.gl/src/webgpu/CommandBuilder.ts | 16 ++ 8 files changed, 311 insertions(+), 45 deletions(-) create mode 100644 debug/webgpu/cube-instance.html diff --git a/debug/webgpu/cube-instance.html b/debug/webgpu/cube-instance.html new file mode 100644 index 0000000000..d349c86618 --- /dev/null +++ b/debug/webgpu/cube-instance.html @@ -0,0 +1,198 @@ + + + + + Cube Wireframe + + + + + + + + diff --git a/debug/webgpu/cube-texture.html b/debug/webgpu/cube-texture.html index b3950ac1d9..6db21ef4fc 100644 --- a/debug/webgpu/cube-texture.html +++ b/debug/webgpu/cube-texture.html @@ -24,6 +24,7 @@ import { cubeVertexArray, cubeVertexUV, cubeVertexCount } from 'meshes'; import { mat4, vec3 } from 'gl-matrix'; + //https://webgpu.github.io/webgpu-samples/?sample=texturedCube const vert = ` struct Uniforms { modelMatrix : mat4x4f, @@ -92,7 +93,6 @@ const material = new Material({ myTexture: new Texture2D({ url: './Di-3d.png', - sampler: 'mySampler' }, resourceLoader) }); const mesh = new Mesh(geometry, material); diff --git a/debug/webgpu/cube.html b/debug/webgpu/cube.html index 222ae6a8e6..6cc02e55d4 100644 --- a/debug/webgpu/cube.html +++ b/debug/webgpu/cube.html @@ -24,6 +24,7 @@ import { cubeVertexArray, cubeVertexColors, cubeVertexCount } from 'meshes'; import { mat4, vec3 } from 'gl-matrix'; + //https://webgpu.github.io/webgpu-samples/?sample=rotatingCube const vert = ` struct Uniforms { modelMatrix : mat4x4f, diff --git a/packages/reshader.gl/src/Geometry.ts b/packages/reshader.gl/src/Geometry.ts index 7ffcc4c870..46f84ff401 100644 --- a/packages/reshader.gl/src/Geometry.ts +++ b/packages/reshader.gl/src/Geometry.ts @@ -1026,43 +1026,11 @@ export default class Geometry { ] } bufferMapping[accessorName] = desc; + bufferDesc.push(desc); } } else { - const array = attr.data || attr.array || attr; - if (attr.buffer && !isArray(attr)) { - const itemBytes = attr.buffer.itemBytes; - const format = getItemFormat(attr, info.itemSize); - bufferDesc.push({ - arrayStride: info.itemSize * itemBytes, - attributes: [ - { - shaderLocation: info.location, - format, - offset: 0 - } - ] - }); - continue; - } else if (isArray(array)) { - let format, itemBytes; - if (attr.componentType) { - format = getFormatFromGLTFAccessor(attr.componentType, attr.itemSize); - itemBytes = getItemBytesFromGLTFAccessor(attr.componentType); - } else { - format = getItemFormat(array, info.itemSize); - itemBytes = getItemBytes(array); - } - bufferDesc.push({ - arrayStride: info.itemSize * itemBytes, - attributes: [ - { - shaderLocation: info.location, - format, - offset: 0 - } - ] - }); - } + const desc = getAttrBufferDescriptor(attr, info); + bufferDesc.push(desc); } } return bufferDesc; @@ -1104,6 +1072,43 @@ function getTypeCtor(arr: NumberArray, byteWidth: number) { return null; } +export function getAttrBufferDescriptor(attr, info): GPUVertexBufferLayout { + const array = attr.data || attr.array || attr; + if (attr.buffer && !isArray(attr)) { + const itemBytes = attr.buffer.itemBytes; + const format = getItemFormat(attr, info.itemSize); + return { + arrayStride: info.itemSize * itemBytes, + attributes: [ + { + shaderLocation: info.location, + format, + offset: 0 + } + ] + }; + } else if (isArray(array)) { + let format, itemBytes; + if (attr.componentType) { + format = getFormatFromGLTFAccessor(attr.componentType, attr.itemSize); + itemBytes = getItemBytesFromGLTFAccessor(attr.componentType); + } else { + format = getItemFormat(array, info.itemSize); + itemBytes = getItemBytes(array); + } + return { + arrayStride: info.itemSize * itemBytes, + attributes: [ + { + shaderLocation: info.location, + format, + offset: 0 + } + ] + }; + } +} + function getItemBytes(data) { const array = getAttrArray(data); if (array.destroy) { diff --git a/packages/reshader.gl/src/InstancedMesh.ts b/packages/reshader.gl/src/InstancedMesh.ts index 65be2d7018..cac251e3b9 100644 --- a/packages/reshader.gl/src/InstancedMesh.ts +++ b/packages/reshader.gl/src/InstancedMesh.ts @@ -4,7 +4,7 @@ import { KEY_DISPOSED } from './common/Constants'; import REGL, { BufferOptions, Regl } from '@maptalks/regl'; import { ActiveAttributes, AttributeBufferData, InstancedAttribute, MeshOptions, NumberArray } from './types/typings'; import Material from './Material'; -import Geometry from './Geometry'; +import Geometry, { getAttrBufferDescriptor } from './Geometry'; export default class InstancedMesh extends Mesh { //@internal @@ -142,7 +142,11 @@ export default class InstancedMesh extends Mesh { return this; } - generateInstancedBuffers(regl: Regl) { + getInstancedBuffer(name: string) { + return this.instancedData[name] && (this.instancedData[name] as any).buffer; + } + + generateInstancedBuffers(device: any) { const data = this.instancedData; const buffers: Record = {}; for (const key in data) { @@ -161,11 +165,12 @@ export default class InstancedMesh extends Mesh { divisor: 1 }; } else { + const bufferOptions = { + data: data[key], + dimension: (data[key] as NumberArray).length / this._instanceCount + } as BufferOptions; buffers[key] = { - buffer: regl.buffer({ - data: data[key], - dimension: (data[key] as NumberArray).length / this._instanceCount - } as BufferOptions), + buffer: Geometry.createBuffer(device, bufferOptions, key), divisor: 1 }; } @@ -205,6 +210,22 @@ export default class InstancedMesh extends Mesh { this._vao = {}; } + getBufferDescriptor(vertexInfo) { + const data = this.instancedData; + const bufferDesc = []; + for (const p in data) { + const attr = data[p]; + if (!attr) { + continue; + } + const info = vertexInfo[p]; + const desc = getAttrBufferDescriptor(attr, info); + desc.stepMode = 'instance'; + bufferDesc.push(desc); + } + return bufferDesc; + } + // getBoundingBox() { // if (!this._bbox) { // this.updateBoundingBox(); diff --git a/packages/reshader.gl/src/shader/Shader.ts b/packages/reshader.gl/src/shader/Shader.ts index f89f6009e5..a3f540189d 100644 --- a/packages/reshader.gl/src/shader/Shader.ts +++ b/packages/reshader.gl/src/shader/Shader.ts @@ -465,9 +465,16 @@ export default class GPUShader extends GLShader { const dynamicOffsets = shaderBuffer.dynamicOffsets.concat(meshBuffer.dynamicOffsets); passEncoder.setBindGroup(0, bindGroup, dynamicOffsets); + let instancedMesh; + if (mesh instanceof InstancedMesh) { + instancedMesh = mesh as InstancedMesh; + } for (const name in vertexInfo) { const vertex = vertexInfo[name]; - const vertexBuffer = mesh.geometry.getBuffer(name); + let vertexBuffer = mesh.geometry.getBuffer(name); + if (!vertexBuffer && instancedMesh) { + vertexBuffer = instancedMesh.getInstancedBuffer(name); + } passEncoder.setVertexBuffer(vertex.location, vertexBuffer); } @@ -475,11 +482,16 @@ export default class GPUShader extends GLShader { const elements = mesh.getElements(); const drawOffset = mesh.geometry.getDrawOffset(); const drawCount = mesh.geometry.getDrawCount(); + let instanceCount = 1; + if (mesh instanceof InstancedMesh) { + const instancedMesh = mesh as InstancedMesh; + instanceCount = instancedMesh.instanceCount; + } if (isNumber(elements)) { - passEncoder.draw(drawCount, 1, drawOffset); + passEncoder.draw(drawCount, instanceCount, drawOffset); } else { passEncoder.setIndexBuffer(elements.getBuffer(), elements.getFormat()); - passEncoder.drawIndexed(drawCount, 1, drawOffset); + passEncoder.drawIndexed(drawCount, instanceCount, drawOffset); } } passEncoder.end(); diff --git a/packages/reshader.gl/src/webgpu/BindGroupFormat.ts b/packages/reshader.gl/src/webgpu/BindGroupFormat.ts index 85eb11d7cb..8e803dbc3d 100644 --- a/packages/reshader.gl/src/webgpu/BindGroupFormat.ts +++ b/packages/reshader.gl/src/webgpu/BindGroupFormat.ts @@ -38,7 +38,13 @@ export default class BindGroupFormat { this._meshUniforms.index = 0; this._meshUniforms.totalSize = 0; const groups = bindGroupMapping.groups; + if (!groups) { + return; + } const group = groups[0]; + if (!group) { + return; + } for (let i = 0; i < group.length; i++) { const uniform = group[i]; if (uniform.isGlobal) { @@ -56,6 +62,13 @@ export default class BindGroupFormat { } createBindGroup(device: GraphicsDevice, mesh: Mesh, layout: GPUBindGroupLayout, shaderBuffer: DynamicBuffer, meshBuffer: DynamicBuffer) { + if (!this.groups) { + return device.wgpu.createBindGroup({ + layout, + label: '', + entries: [] + }); + } const groups = this.groups[0]; const entries = []; const textures = []; diff --git a/packages/reshader.gl/src/webgpu/CommandBuilder.ts b/packages/reshader.gl/src/webgpu/CommandBuilder.ts index 38f2517dae..8e3c27c51d 100644 --- a/packages/reshader.gl/src/webgpu/CommandBuilder.ts +++ b/packages/reshader.gl/src/webgpu/CommandBuilder.ts @@ -6,6 +6,7 @@ import { ResourceType } from 'wgsl_reflect'; import GraphicsDevice from './GraphicsDevice'; import PipelineDescriptor from './common/PipelineDesc'; import { ActiveAttributes } from '../types/typings'; +import InstancedMesh from '../InstancedMesh'; export default class CommandBuilder { device: GraphicsDevice; @@ -146,6 +147,17 @@ export default class CommandBuilder { }; } } + if (mesh instanceof InstancedMesh) { + const data = (mesh as InstancedMesh).instancedData; + for (const name in data) { + if (inputMapping[name]) { + vertexInfo[name] = { + location: inputMapping[name].location, + itemSize: getItemSize(inputMapping[name].type) + }; + } + } + } return vertexInfo; } @@ -206,6 +218,10 @@ export default class CommandBuilder { this._presentationFormat = navigator.gpu.getPreferredCanvasFormat(); } const buffers = mesh.geometry.getBufferDescriptor(vertInfo); + if (mesh instanceof InstancedMesh) { + const instanceBuffers = (mesh as InstancedMesh).getBufferDescriptor(vertInfo); + buffers.push(...instanceBuffers); + } const pipelineLayout = device.createPipelineLayout({ label: this.name + '-pipelinelayout', bindGroupLayouts: [layout] From f03c03c1580da6638d32214f8ca934fbc1cca3ad Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Wed, 5 Feb 2025 12:02:57 +0800 Subject: [PATCH 17/53] fix WGSL preprocess --- debug/webgpu/cube.html | 10 +- packages/reshader.gl/package.json | 1 - .../reshader.gl/src/webgpu/CommandBuilder.ts | 9 +- .../src/webgpu/common/WGSLParseDefines.ts | 150 ++++++++++++++++++ packages/reshader.gl/tsconfig.json | 1 + pnpm-lock.yaml | 8 - 6 files changed, 163 insertions(+), 16 deletions(-) create mode 100644 packages/reshader.gl/src/webgpu/common/WGSLParseDefines.ts diff --git a/debug/webgpu/cube.html b/debug/webgpu/cube.html index 6cc02e55d4..92c45e48e4 100644 --- a/debug/webgpu/cube.html +++ b/debug/webgpu/cube.html @@ -51,7 +51,11 @@ fn main( @location(0) fragPosition: vec4f ) -> @location(0) vec4f { - return fragPosition; + #if USE_VERTEX_COLOR + return VERTEX_COLOR; + #else + return fragPosition; + #endif } `; const adapter = await navigator.gpu?.requestAdapter(); @@ -80,6 +84,10 @@ geometry.generateBuffers(device); const mesh = new Mesh(geometry); + mesh.setDefines({ + 'VERTEX_COLOR': 'vec4(1.0, 0.0, 0.0, 1.0)', + // 'USE_VERTEX_COLOR': 1 + }); const scene = new Scene(); function render() { diff --git a/packages/reshader.gl/package.json b/packages/reshader.gl/package.json index 5f8afc2091..83f77e3f3d 100644 --- a/packages/reshader.gl/package.json +++ b/packages/reshader.gl/package.json @@ -28,7 +28,6 @@ "@webgpu/types": "0.1.52", "earcut": "^3.0.1", "gl-matrix": "^3.4.0", - "wgsl-preprocessor": "1.0.0", "wgsl_reflect": "^1.0.16" }, "devDependencies": { diff --git a/packages/reshader.gl/src/webgpu/CommandBuilder.ts b/packages/reshader.gl/src/webgpu/CommandBuilder.ts index 8e3c27c51d..5f68fd36e8 100644 --- a/packages/reshader.gl/src/webgpu/CommandBuilder.ts +++ b/packages/reshader.gl/src/webgpu/CommandBuilder.ts @@ -1,4 +1,3 @@ -import { wgsl } from 'wgsl-preprocessor/wgsl-preprocessor.js'; import { WgslReflect } from "wgsl_reflect/wgsl_reflect.module.js"; import BindGroupFormat from '../webgpu/BindGroupFormat'; import Mesh from '../Mesh'; @@ -7,6 +6,7 @@ import GraphicsDevice from './GraphicsDevice'; import PipelineDescriptor from './common/PipelineDesc'; import { ActiveAttributes } from '../types/typings'; import InstancedMesh from '../InstancedMesh'; +import { WGSLParseDefines } from "./common/WGSLParseDefines"; export default class CommandBuilder { device: GraphicsDevice; @@ -27,12 +27,9 @@ export default class CommandBuilder { const mesh = this.mesh; const device = this.device; const defines = this.mesh.getDefines(); - const defined = key => { - return !!defines[key]; - } //FIXME 如何在wgsl中实现defined - const vert = wgsl(this.vert); - const frag = wgsl(this.frag); + const vert = WGSLParseDefines(this.vert, defines); + const frag = WGSLParseDefines(this.frag, defines); const vertReflect = new WgslReflect(vert); const vertexInfo = this._formatBufferInfo(vertReflect, mesh); diff --git a/packages/reshader.gl/src/webgpu/common/WGSLParseDefines.ts b/packages/reshader.gl/src/webgpu/common/WGSLParseDefines.ts new file mode 100644 index 0000000000..458d3d1885 --- /dev/null +++ b/packages/reshader.gl/src/webgpu/common/WGSLParseDefines.ts @@ -0,0 +1,150 @@ +/*! + * codes from GEngine + * https://github.com/GEngine-js/GEngine/blob/e9c0e2c4a28cc6b9fec133c75958b80115e53a63/src/shader/WGSLParseDefines.ts + * ISC License + */ +import { ShaderDefines } from "../../types/typings"; + +const preprocessorSymbols = /#([^\s]*)(\s*)/gm; +const defineRexg = /\b[0-9A-Z_&&||]+\b/g; +const isNumeric = (n) => !isNaN(n); +export function WGSLParseDefines(shader: string, defines: ShaderDefines): string { + if (!shader) return undefined; + // parse shader inner const define + const notDefineConstShader = ParseDefinesConst(shader, defines); + // filter "&&","||",number + const rexgDefines = notDefineConstShader + .match(defineRexg) + ?.filter((define) => !["&&", "||", "_"].includes(define) && !isNumeric(define) && define != ""); + // normallize defines + const normalizeDefines = getNormalizeDefines(rexgDefines, defines); + // split Shader + const shaderStrs = splitShaderStrsByDefine(notDefineConstShader, rexgDefines); + // parse conditional macro definition + return shaderStrs.length > 0 ? ParseDefines(shaderStrs, normalizeDefines) : notDefineConstShader; +} +function ParseDefines(strings: Array, values: Array): string { + const stateStack = []; + let state = { frag: "", elseIsValid: false, expression: true }; + let depth = 1; + for (let i = 0; i < strings.length; ++i) { + const frag = strings[i]; + const matchedSymbols = frag.matchAll(preprocessorSymbols); + + let lastIndex = 0; + let valueConsumed = false; + for (const match of matchedSymbols) { + state.frag += frag.substring(lastIndex, match.index); + + switch (match[1]) { + case "if": + if (match.index + match[0].length != frag.length) { + throw new Error("#if must be immediately followed by a template expression (ie: ${value})"); + } + valueConsumed = true; + stateStack.push(state); + depth++; + state = { frag: "", elseIsValid: true, expression: !!values[i] }; + break; + case "elif": + if (match.index + match[0].length != frag.length) { + throw new Error("#elif must be immediately followed by a template expression (ie: ${value})"); + } else if (!state.elseIsValid) { + throw new Error("#elif not preceeded by an #if or #elif"); + } + valueConsumed = true; + if (state.expression && stateStack.length != depth) { + stateStack.push(state); + } + state = { frag: "", elseIsValid: true, expression: !!values[i] }; + break; + case "else": + if (!state.elseIsValid) { + throw new Error("#else not preceeded by an #if or #elif"); + } + if (state.expression && stateStack.length != depth) { + stateStack.push(state); + } + state = { frag: match[2], elseIsValid: false, expression: true }; + break; + case "endif": + if (!stateStack.length) { + throw new Error("#endif not preceeded by an #if"); + } + // eslint-disable-next-line no-case-declarations + const branchState = stateStack.length == depth ? stateStack.pop() : state; + state = stateStack.pop(); + depth--; + if (branchState.expression) { + state.frag += branchState.frag; + } + state.frag += match[2]; + break; + default: + // Unknown preprocessor symbol. Emit it back into the output frag unchanged. + state.frag += match[0]; + break; + } + + lastIndex = match.index + match[0].length; + } + + // If the frag didn't end on one of the preprocessor symbols append the rest of it here. + if (lastIndex != frag.length) { + state.frag += frag.substring(lastIndex, frag.length); + } + + // If the next value wasn't consumed by the preprocessor symbol, append it here. + if (!valueConsumed && values.length > i) { + state.frag += values[i]; + } + } + if (stateStack.length) { + throw new Error("Mismatched #if/#endif count"); + } + return state.frag; +} +function ParseDefinesConst(sourceShader: string, defines: ShaderDefines) { + if (!defines) return sourceShader; + let result = sourceShader; + const constDefineKeys = Object.keys(defines)?.filter?.((key) => key != key.toUpperCase()); + constDefineKeys?.forEach?.((key: string) => { + result = result.replaceAll(key, defines[key] + ''); + }); + return result; +} +function getNormalizeDefines(rexgDefines: Array, defines: any) { + return rexgDefines?.map?.((define) => { + if (define?.includes("&&") || define?.includes("||")) { + if (define.includes("&&")) { + const splitDefines = define.split("&&"); + return getAndDefineValue(splitDefines, defines); + } + const splitDefines = define.split("||"); + return !getOrDefineValue(splitDefines, defines); + } + return defines[define]; + }); +} +function getAndDefineValue(splitDefines: Array, defines: ShaderDefines): boolean { + let total = 0; + splitDefines?.forEach?.((defineKey) => (total += Number(defines[defineKey]) > 1 ? 1 : Number(defines[defineKey]))); + return total === splitDefines.length; +} +function getOrDefineValue(splitDefines: Array, defines: ShaderDefines): boolean { + let total = 0; + splitDefines?.forEach?.((defineKey) => (total += Number(defines[defineKey]) > 1 ? 1 : Number(defines[defineKey]))); + return total === 0; +} +function splitShaderStrsByDefine(shader: string, defines: Array): Array { + let currentShaderStr = shader; + const shaderStrs = + defines?.map((define) => { + const length = currentShaderStr.indexOf(define); + const sliceStr = currentShaderStr.slice(0, length); + currentShaderStr = currentShaderStr.slice(length + define.length); + return sliceStr; + }) || []; + if (shaderStrs?.length) shaderStrs.push(currentShaderStr); + return shaderStrs; +} diff --git a/packages/reshader.gl/tsconfig.json b/packages/reshader.gl/tsconfig.json index e3d1890a98..2a9d1908a9 100644 --- a/packages/reshader.gl/tsconfig.json +++ b/packages/reshader.gl/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "../../tsconfig.json", "compilerOptions": { + "lib": ["ES2021", "DOM"], "outDir": "./dist/", "rootDir": "./src", "baseUrl": "./", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da4418a57c..7151e0697e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -678,9 +678,6 @@ importers: gl-matrix: specifier: ^3.4.0 version: 3.4.3 - wgsl-preprocessor: - specifier: 1.0.0 - version: 1.0.0 wgsl_reflect: specifier: ^1.0.16 version: 1.0.16 @@ -4822,9 +4819,6 @@ packages: walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} - wgsl-preprocessor@1.0.0: - resolution: {integrity: sha512-fpmt1KFFSr5IS1Gc7JYwgNEo2S0dOjUDbTgxc4Zbsp+N8PF+5HH4j2K2RYwz8XCB8kIxURyukNqSJ7yiSCV1jA==} - wgsl_reflect@1.0.16: resolution: {integrity: sha512-OE3urfXXbHMD5lhKZwxOxC9SFYynEGEkWXQmvi7B1gzzr5jb9+drh9A8MeBvVqKqznCoBuh8WOzVuSGSZs4CkQ==} @@ -9792,8 +9786,6 @@ snapshots: dependencies: makeerror: 1.0.12 - wgsl-preprocessor@1.0.0: {} - wgsl_reflect@1.0.16: {} which-boxed-primitive@1.0.2: From 7d6e60d17c23c20c9077389a00b702d5c6626f40 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Thu, 6 Feb 2025 17:31:21 +0800 Subject: [PATCH 18/53] initial commit for GraphicsFramebuffer --- packages/reshader.gl/src/Geometry.ts | 2 +- .../extensions/KHRTechniquesWebglManager.js | 2 +- packages/reshader.gl/src/gltf/GLTFPack.js | 2 +- packages/reshader.gl/src/pbr/PBRHelper.js | 4 +- packages/reshader.gl/src/pbr/old/PBRHelper.js | 4 +- packages/reshader.gl/src/shader/Shader.ts | 23 ++- .../reshader.gl/src/webgpu/GraphicsDevice.ts | 149 +++++++++++++----- .../src/webgpu/GraphicsFramebuffer.ts | 138 ++++++++++++++++ .../reshader.gl/src/webgpu/GraphicsTexture.ts | 14 +- .../src/webgpu/common/ReglTranslator.ts | 17 +- 10 files changed, 292 insertions(+), 63 deletions(-) create mode 100644 packages/reshader.gl/src/webgpu/GraphicsFramebuffer.ts diff --git a/packages/reshader.gl/src/Geometry.ts b/packages/reshader.gl/src/Geometry.ts index 46f84ff401..9c252e3a12 100644 --- a/packages/reshader.gl/src/Geometry.ts +++ b/packages/reshader.gl/src/Geometry.ts @@ -64,7 +64,7 @@ export default class Geometry { } } - static createBuffer(device: any, data: any, name: string) { + static createBuffer(device: any, data: any, name?: string) { if (device instanceof GraphicsDevice) { return createGPUBuffer(device, data, GPUBufferUsage.VERTEX, name); } else { diff --git a/packages/reshader.gl/src/extensions/KHRTechniquesWebglManager.js b/packages/reshader.gl/src/extensions/KHRTechniquesWebglManager.js index 167dfcf1c6..bab2868c88 100644 --- a/packages/reshader.gl/src/extensions/KHRTechniquesWebglManager.js +++ b/packages/reshader.gl/src/extensions/KHRTechniquesWebglManager.js @@ -182,7 +182,7 @@ export default class KHRTechniquesWebglManager { } const attrs = {}; for (const p in attributes) { - const buffer = getUniqueREGLBuffer(this._regl, attributes[p], { dimension: attributes[p].itemSize }); + const buffer = getUniqueREGLBuffer(this._regl, attributes[p], { dimension: attributes[p].itemSize, name: p }); // 优先采用 attributeSemantics中定义的属性 const name = attributeSemantics[p] || p; attrs[name] = { diff --git a/packages/reshader.gl/src/gltf/GLTFPack.js b/packages/reshader.gl/src/gltf/GLTFPack.js index 908172f637..e9b9763cbc 100644 --- a/packages/reshader.gl/src/gltf/GLTFPack.js +++ b/packages/reshader.gl/src/gltf/GLTFPack.js @@ -489,7 +489,7 @@ function createGeometry(primitive, regl, hasAOMap) { // 把原有的array赋给attr,用于计算 bbox、buildUniqueVertex attrs[name] = extend({}, attributes[name]); if (regl) { - attrs[name].buffer = getUniqueREGLBuffer(regl, attributes[name], { dimension: attributes[name].itemSize }); + attrs[name].buffer = getUniqueREGLBuffer(regl, attributes[name], { dimension: attributes[name].itemSize, name }); } } diff --git a/packages/reshader.gl/src/pbr/PBRHelper.js b/packages/reshader.gl/src/pbr/PBRHelper.js index f86e8bd41e..a86d6cb686 100644 --- a/packages/reshader.gl/src/pbr/PBRHelper.js +++ b/packages/reshader.gl/src/pbr/PBRHelper.js @@ -404,8 +404,8 @@ export function generateDFGLUT(regl, size, sampleSize, roughnessLevels) { mag : 'nearest' }); - const quadBuf = regl.buffer(quadVertices); - const quadTexBuf = regl.buffer(quadTexcoords); + const quadBuf = regl.buffer({ data: quadVertices, name: 'aPosition' }); + const quadTexBuf = regl.buffer({ data: quadTexcoords, name: 'aTexCoord' }); const fbo = regl.framebuffer({ radius : size, colorType: 'uint8', diff --git a/packages/reshader.gl/src/pbr/old/PBRHelper.js b/packages/reshader.gl/src/pbr/old/PBRHelper.js index 9335984698..99ea819aca 100644 --- a/packages/reshader.gl/src/pbr/old/PBRHelper.js +++ b/packages/reshader.gl/src/pbr/old/PBRHelper.js @@ -244,8 +244,8 @@ function generateBRDFLUT(regl, size, sampleSize, roughnessLevels) { mag : 'nearest' }); - const quadBuf = regl.buffer(quadVertices); - const quadTexBuf = regl.buffer(quadTexcoords); + const quadBuf = regl.buffer({ data: quadVertices, name: 'aPosition' }); + const quadTexBuf = regl.buffer({ data: quadTexcoords, name: 'aTexCoord' }); const fbo = regl.framebuffer({ radius : size, type : 'float', diff --git a/packages/reshader.gl/src/shader/Shader.ts b/packages/reshader.gl/src/shader/Shader.ts index a3f540189d..4c2bfea527 100644 --- a/packages/reshader.gl/src/shader/Shader.ts +++ b/packages/reshader.gl/src/shader/Shader.ts @@ -9,6 +9,7 @@ import Mesh from '../Mesh'; import DynamicBuffer from '../webgpu/DynamicBuffer'; import CommandBuilder from '../webgpu/CommandBuilder'; import GraphicsDevice from '../webgpu/GraphicsDevice'; +import GraphicsFramebuffer from '../webgpu/GraphicsFramebuffer'; const UNIFORM_TYPE = { @@ -391,6 +392,8 @@ export default class GPUShader extends GLShader { _passEncoders: Record; //@internal _currentPassEncoder: GPURenderPassEncoder + //@internal + _gpuFramebuffer: GraphicsFramebuffer; getShaderCommandKey(device, mesh, uniformValues, doubleSided) { if (device && device.wgpu) { @@ -478,7 +481,6 @@ export default class GPUShader extends GLShader { passEncoder.setVertexBuffer(vertex.location, vertexBuffer); } - //TODO InstancedMesh 的参数 const elements = mesh.getElements(); const drawOffset = mesh.geometry.getDrawOffset(); const drawCount = mesh.geometry.getDrawCount(); @@ -498,16 +500,25 @@ export default class GPUShader extends GLShader { } _getCurrentRenderPassEncoder(device: GraphicsDevice) { - return this._currentPassEncoder || device.getDefaultRenderPassEncoder(); + // stencilLoadOp?: GPULoadOp, + // stencilClearValue?: number, + // colorLoadOp?: GPULoadOp, + // depthLoadOp?: GPULoadOp + return device.getRenderPassEncoder(this._gpuFramebuffer); } setFramebuffer(framebuffer) { - if (!framebuffer || !framebuffer.isGPU) { + if (!framebuffer) { + if (this._gpuFramebuffer) { + this._gpuFramebuffer = null; + return this; + } + return super.setFramebuffer(framebuffer); + } + if (!framebuffer.getRenderPassDescriptor) { return super.setFramebuffer(framebuffer); } - // this.context.framebuffer = framebuffer; - // framebuffer => GPURenderPassEncoderDescriptor - // this._currentPassEncoder = passEncoder; + this._gpuFramebuffer = framebuffer; return this; } diff --git a/packages/reshader.gl/src/webgpu/GraphicsDevice.ts b/packages/reshader.gl/src/webgpu/GraphicsDevice.ts index 9007c82d4c..91116ecd4f 100644 --- a/packages/reshader.gl/src/webgpu/GraphicsDevice.ts +++ b/packages/reshader.gl/src/webgpu/GraphicsDevice.ts @@ -1,16 +1,21 @@ -import { isArray } from "../common/Util"; -import { toTextureFormat } from "./common/ReglTranslator"; +import Geometry from "../Geometry"; import DynamicBufferPool from "./DynamicBufferPool"; +import GraphicsFramebuffer from "./GraphicsFramebuffer"; import GraphicsTexture from "./GraphicsTexture"; export default class GraphicsDevice { wgpu: GPUDevice; + context: GPUCanvasContext; + //@internal commandBuffers: GPUCommandBuffer[] = []; + //@internal dynamicBufferPool: DynamicBufferPool; - context: GPUCanvasContext; - _defaultRenderTarget: GPURenderPassDescriptor; + //@internal commandEncoder: GPUCommandEncoder; - _currentRenderPass: GPURenderPassEncoder; + //@internal + _defaultFramebuffer: GraphicsFramebuffer; + //@internal + _readTargets: Record = {}; constructor(device: GPUDevice, context: GPUCanvasContext) { this.wgpu = device; @@ -50,41 +55,29 @@ export default class GraphicsDevice { } } - getDefaultRenderPassEncoder() { - let rendrTarget = this._defaultRenderTarget; - if (!rendrTarget) { + _getDefaultFramebuffer() { + let fbo = this._defaultFramebuffer; + if (!fbo) { const canvas = this.context.canvas; - const depthTexture = this.wgpu.createTexture({ - size: [canvas.width, canvas.height], - format: "depth24plus-stencil8", - usage: GPUTextureUsage.RENDER_ATTACHMENT, + fbo = this._defaultFramebuffer = new GraphicsFramebuffer(this, { + width: canvas.width, + height: canvas.height, + depthStencil: true }); - rendrTarget = this._defaultRenderTarget = { - colorAttachments: [ - { - view: undefined, // Assigned later - clearValue: [0, 0, 0, 0], - loadOp: "clear", - storeOp: "store", - }, - ], - depthStencilAttachment: { - view: depthTexture.createView(), - depthClearValue: 1.0, - depthLoadOp: "clear", - depthStoreOp: "store", - stencilReadOnly: false, - stencilClearValue: 255, - stencilLoadOp: "clear", - stencilStoreOp: "store", - }, - }; } - rendrTarget.colorAttachments[0].view = this.context - .getCurrentTexture() - .createView(); + return fbo; + } + + getRenderPassEncoder(fbo: GraphicsFramebuffer) { + fbo = fbo || this._getDefaultFramebuffer(); + const desc = fbo.getRenderPassDescriptor(); + if (fbo === this._defaultFramebuffer) { + desc.colorAttachments[0].view = this.context + .getCurrentTexture() + .createView(); + } const commandEncoder = this.getCommandEncoder(); - return commandEncoder.beginRenderPass(rendrTarget); + return commandEncoder.beginRenderPass(desc); } addCommandBuffer(commandBuffer: GPUCommandBuffer, front: boolean) { @@ -106,20 +99,92 @@ export default class GraphicsDevice { } } + // implementation of regl.buffer buffer(options) { - - } - - read(options) { - //TODO 从帧缓冲中读取像素值 + return Geometry.createBuffer(this.wgpu, options, options.name); } + // implementation of regl.framebuffer framebuffer(reglFBODescriptor) { // reglDesciprtor => gpu renderPassEncoder - + return new GraphicsFramebuffer(this, reglFBODescriptor); } + // implementation of regl.texture texture(config) { return new GraphicsTexture(this, config); } + + // implementation of regl.clear + clear(options) { + const fbo = options.fbo || this._getDefaultFramebuffer(); + fbo.setClearOptions(options); + } + + + // implementation of regl.read + async read(options) { + const framebuffer = options.framebuffer || this._defaultFramebuffer; + let { width, height } = options; + if (!width) { + width = framebuffer.width; + } + if (!height) { + height = framebuffer.height; + } + const device = this.wgpu; + const colorTexture = framebuffer.colorTexture; + const { bytesPerTexel } = colorTexture.gpuFormat; + let bytesPerRow = options.width * bytesPerTexel; + bytesPerRow = Math.ceil( bytesPerRow / 256 ) * 256; // Align to 256 bytes + const encoder = device.createCommandEncoder(); + + const bufferSize = width * height * bytesPerTexel; + let readBuffer = this._readTargets[bufferSize]; + if (!readBuffer) { + readBuffer = device.createBuffer( + { + size: width * height * bytesPerTexel, + usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ + } + ); + } + + encoder.copyTextureToBuffer( + { + texture: colorTexture.texture, + origin: { x: options.x, y: options.y, z: 0 }, + }, + { + buffer: readBuffer, + bytesPerRow: bytesPerRow + }, + { + width, + height + } + ); + + const typedArrayType = options.data.constructor; + device.queue.submit([encoder.finish()]); + await readBuffer.mapAsync(GPUMapMode.READ); + const buffer = readBuffer.getMappedRange(); + const result = new typedArrayType(buffer); + options.data.set(result); + readBuffer.unmap(); + } + + destroy() { + if (this._defaultFramebuffer) { + this._defaultFramebuffer.destroy(); + delete this._defaultFramebuffer; + } + for (const p in this._readTargets) { + const buffer = this._readTargets[p]; + if (buffer) { + buffer.destroy(); + } + } + this._readTargets = {}; + } } diff --git a/packages/reshader.gl/src/webgpu/GraphicsFramebuffer.ts b/packages/reshader.gl/src/webgpu/GraphicsFramebuffer.ts new file mode 100644 index 0000000000..cd0160d030 --- /dev/null +++ b/packages/reshader.gl/src/webgpu/GraphicsFramebuffer.ts @@ -0,0 +1,138 @@ +import GraphicsDevice from './GraphicsDevice'; +import GraphicsTexture from './GraphicsTexture'; + +export default class GraphicsFramebuffer { + device: GraphicsDevice; + options: any; + //@internal + colorTexture: GPUTexture; + //@internal + depthTexture: GPUTexture; + //@internal + _renderPass: GPURenderPassDescriptor; + width: number; + height: number; + //@internal + colorLoadOp: GPULoadOp; + //@internal + colorClearValue: number[]; + //@internal + depthLoadOp: GPULoadOp; + //@internal + depthClearValue: number; + //@internal + stencilLoadOp: GPULoadOp; + //@internal + stencilClearValue: number; + + constructor(device, options) { + this.device = device; + this.options = options; + this._update(); + } + + _update() { + let color = this.options.color || this.options.colors && this.options.colors[0]; + const { width, height } = this.options; + this.width = width; + this.height = height; + if (!color) { + color = new GraphicsTexture(this.device, color); + } + let depth = this.options.depth; + if (depth) { + if (depth === true) { + depth = new GraphicsTexture(this.device, { width, height, format: 'depth' }); + } else { + if (!(depth instanceof GraphicsTexture)) { + depth = new GraphicsTexture(this.device, depth); + } + } + } + + const depthStencil = this.options.depthStencil; + if (depthStencil === true) { + depth = new GraphicsTexture(this.device, { width, height, format: 'depth stencil' }); + } else { + if (depthStencil instanceof GraphicsTexture) { + depth = depthStencil; + } else { + depth = new GraphicsTexture(this.device, depthStencil); + } + } + this.colorTexture = color === 'undefined' ? null : color; + this.depthTexture = depth; + this._renderPass = { + colorAttachments: [ + { + view: this.colorTexture && this.colorTexture.createView(), // Assigned later + clearValue: [0, 0, 0, 0], + loadOp: 'load', + storeOp: 'store', + }, + ], + depthStencilAttachment: { + view: this.depthTexture.createView(), + depthClearValue: 1.0, + depthLoadOp: 'load', + depthStoreOp: 'store', + stencilReadOnly: false, + stencilClearValue: 255, + stencilLoadOp: 'load', + stencilStoreOp: 'store', + }, + }; + } + + getRenderPassDescriptor() { + const colorAttachment = this._renderPass.colorAttachments[0]; + const depthStencilAttachment = this._renderPass.depthStencilAttachment; + if (colorAttachment) { + colorAttachment.loadOp = this.colorLoadOp || 'load'; + colorAttachment.clearValue = this.colorClearValue || [0, 0, 0, 0]; + } + if (depthStencilAttachment) { + depthStencilAttachment.depthLoadOp = this.depthLoadOp || 'load'; + depthStencilAttachment.depthClearValue = this.depthClearValue || 1; + depthStencilAttachment.stencilLoadOp = this.stencilLoadOp || 'load'; + depthStencilAttachment.stencilClearValue = this.stencilClearValue || 0; + } + this.resetClearOptions(); + return this._renderPass; + } + + setClearOptions(options) { + if (options.color) { + this.colorLoadOp = 'clear'; + this.colorClearValue = options.color; + } + if (options.depth) { + this.depthLoadOp = 'clear'; + this.depthClearValue = options.depth; + } + if (options.stencil) { + this.stencilLoadOp = 'clear'; + this.stencilClearValue = options.stencil; + } + } + + resetClearOptions() { + this.colorLoadOp = null; + this.colorClearValue = null; + this.depthLoadOp = null; + this.depthClearValue = null; + this.stencilLoadOp = null; + this.stencilClearValue = null; + } + + destroy() { + if (this.colorTexture) { + this.colorTexture.destroy(); + delete this.colorTexture; + } + if (this.depthTexture) { + this.depthTexture.destroy(); + delete this.depthTexture; + } + } +} diff --git a/packages/reshader.gl/src/webgpu/GraphicsTexture.ts b/packages/reshader.gl/src/webgpu/GraphicsTexture.ts index 43a0a3fbf5..09c8deff58 100644 --- a/packages/reshader.gl/src/webgpu/GraphicsTexture.ts +++ b/packages/reshader.gl/src/webgpu/GraphicsTexture.ts @@ -1,5 +1,5 @@ import { isArray } from "../common/Util"; -import { toTextureFormat } from "./common/ReglTranslator"; +import { GPUTexFormat, toTextureFormat } from "./common/ReglTranslator"; import GraphicsDevice from "./GraphicsDevice"; export default class GraphicsTexture { @@ -8,10 +8,13 @@ export default class GraphicsTexture { config: any; //@internal _bindGroups: GPUBindGroup[] = []; + //@internal + gpuFormat: GPUTexFormat; constructor(device: GraphicsDevice, config) { this.device = device; this.config = config; + this.gpuFormat = toTextureFormat(config.format, config.type); this.update(this.config); } @@ -41,10 +44,15 @@ export default class GraphicsTexture { height = data.height; } } + const format = this.gpuFormat.format; + const isDepth = format === 'depth24plus' || format === 'depth24plus-stencil8'; texture = device.createTexture({ size: [width, height, 1], - format: toTextureFormat(config.format, config.type), - usage: + format, + usage: isDepth ? + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.RENDER_ATTACHMENT + : GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT, diff --git a/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts b/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts index a632766931..3d5de7893d 100644 --- a/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts +++ b/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts @@ -71,21 +71,28 @@ export function toGPUSampler(minFilter: string, magFilter: GPUFilterMode, wrapS: return sampler; } +export type GPUTexFormat = { format: GPUTextureFormat, bytesPerTexel: number }; -export function toTextureFormat(format: string, type: string): GPUTextureFormat { +export function toTextureFormat(format: string, type: string): GPUTexFormat { format = format || 'rgba'; type = type || 'uint8'; + if (format === 'depth stencil') { + return { format: 'depth24plus-stencil8', bytesPerTexel: 4 }; + } else if (format === 'depth') { + return { format: 'depth24plus', bytesPerTexel: 4 }; + } + if (type === 'uint8') { if (format === 'rgba') { - return 'rgba8unorm'; + return { format: 'rgba8unorm', bytesPerTexel: 1 }; } else { //TODO 各种压缩纹理的类型 } } else if (type === 'float16' || type === 'half float') { - return 'r16float'; + return { format: 'r16float', bytesPerTexel: 2 }; } else if (type === 'float') { - return 'r32float'; + return { format: 'r32float', bytesPerTexel: 4 }; } - return 'rgba8unorm'; + return { format: 'rgba8unorm', bytesPerTexel: 1 }; } From 079d68e486af270e3339132672e263a9b859bb75 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Wed, 12 Feb 2025 21:02:27 +0800 Subject: [PATCH 19/53] fix depthTexture and incorrect dynamicOffsets --- debug/webgpu/cube.html | 48 ++- debug/webgpu/shadowmapping.html | 307 ++++++++++++++++++ packages/reshader.gl/package.json | 5 +- packages/reshader.gl/src/Geometry.ts | 8 +- packages/reshader.gl/src/Mesh.ts | 5 +- packages/reshader.gl/src/Renderer.ts | 6 +- packages/reshader.gl/src/index.ts | 2 + packages/reshader.gl/src/shader/MeshShader.js | 8 +- packages/reshader.gl/src/shader/Shader.ts | 50 ++- packages/reshader.gl/src/types/typings.ts | 6 +- .../reshader.gl/src/webgpu/BindGroupFormat.ts | 28 +- .../reshader.gl/src/webgpu/CommandBuilder.ts | 120 ++++--- .../reshader.gl/src/webgpu/DynamicBuffer.ts | 25 +- .../reshader.gl/src/webgpu/DynamicOffsets.ts | 37 +++ .../src/webgpu/GraphicsFramebuffer.ts | 79 +++-- .../reshader.gl/src/webgpu/GraphicsTexture.ts | 47 +-- .../src/webgpu/common/PipelineDesc.ts | 12 +- .../src/webgpu/common/ReglTranslator.ts | 20 +- 18 files changed, 641 insertions(+), 172 deletions(-) create mode 100644 debug/webgpu/shadowmapping.html create mode 100644 packages/reshader.gl/src/webgpu/DynamicOffsets.ts diff --git a/debug/webgpu/cube.html b/debug/webgpu/cube.html index 92c45e48e4..b90d432345 100644 --- a/debug/webgpu/cube.html +++ b/debug/webgpu/cube.html @@ -25,11 +25,12 @@ import { mat4, vec3 } from 'gl-matrix'; //https://webgpu.github.io/webgpu-samples/?sample=rotatingCube - const vert = ` + const vert = /* wgsl */` struct Uniforms { modelMatrix : mat4x4f, } @binding(0) @group(0) var uniforms : Uniforms; + @binding(1) @group(0) var cameraProjViewMatrix : mat4x4f; struct VertexOutput { @builtin(position) Position : vec4f, @@ -41,7 +42,7 @@ @location(0) position : vec4f, ) -> VertexOutput { var output : VertexOutput; - output.Position = uniforms.modelMatrix * position; + output.Position = cameraProjViewMatrix * uniforms.modelMatrix * position; output.fragPosition = 0.5 * (position + vec4(1.0, 1.0, 1.0, 1.0)); return output; } @@ -92,10 +93,10 @@ function render() { const matrix = getTransformationMatrix(); - mesh.setLocalTransform(matrix); + scene.setMeshes([mesh]); - renderer.render(shader, null, scene); + renderer.render(shader, { cameraProjViewMatrix: matrix }, scene); device.submit(); requestAnimationFrame(render); } @@ -106,18 +107,37 @@ const viewMatrix = mat4.identity([]); const translation = [0, 0, -4]; const axis = []; + + const upVector = vec3.set([], 0, 1, 0); + const origin = vec3.set([], 0, 0, 0); + const eyePosition = [4, -4, 4]; + const cameraViewProjMatrix = mat4.create(); + function getTransformationMatrix() { + // mat4.identity(viewMatrix); + // mat4.translate(viewMatrix, viewMatrix, translation); + // const now = Date.now() / 1000; + // mat4.rotate( + // viewMatrix, + // viewMatrix, + // 1, + // vec3.set(axis, Math.sin(now), Math.cos(now), 0) + // ); + // mat4.multiply(modelViewProjectionMatrix, projectionMatrix, viewMatrix); + // return modelViewProjectionMatrix; + mat4.identity(viewMatrix); - mat4.translate(viewMatrix, viewMatrix, translation); - const now = Date.now() / 1000; - mat4.rotate( - viewMatrix, - viewMatrix, - 1, - vec3.set(axis, Math.sin(now), Math.cos(now), 0) - ); - mat4.multiply(modelViewProjectionMatrix, projectionMatrix, viewMatrix); - return modelViewProjectionMatrix; + mat4.lookAt(viewMatrix, eyePosition, origin, upVector); + // mat4.translate(viewMatrix, viewMatrix, translation); + // const now = Date.now() / 1000; + // mat4.rotate( + // viewMatrix, + // viewMatrix, + // 1, + // vec3.set(axis, Math.sin(now), Math.cos(now), 0) + // ); + mat4.multiply(cameraViewProjMatrix, projectionMatrix, viewMatrix); + return cameraViewProjMatrix; } function perspectiveZO(out, fovy, aspect, near, far) { diff --git a/debug/webgpu/shadowmapping.html b/debug/webgpu/shadowmapping.html new file mode 100644 index 0000000000..6f7113d16c --- /dev/null +++ b/debug/webgpu/shadowmapping.html @@ -0,0 +1,307 @@ + + + + + Shadow mapping + + + + + + + + diff --git a/packages/reshader.gl/package.json b/packages/reshader.gl/package.json index 83f77e3f3d..7fc921fa98 100644 --- a/packages/reshader.gl/package.json +++ b/packages/reshader.gl/package.json @@ -40,7 +40,10 @@ "eslint-plugin-mocha": "^10.5.0", "mocha": "^10.3.0", "rollup": "^4.17.2", - "rollup-plugin-dts": "^6.1.0" + "rollup-plugin-dts": "^6.1.0", + "tslib": "^2.6.2", + "typescript": "^5.4.2", + "typescript-eslint": "^7.18.0" }, "gitHead": "9eeea4807723d460fa7e09969c6556d53f6bed84" } diff --git a/packages/reshader.gl/src/Geometry.ts b/packages/reshader.gl/src/Geometry.ts index 9c252e3a12..e95b65e8e4 100644 --- a/packages/reshader.gl/src/Geometry.ts +++ b/packages/reshader.gl/src/Geometry.ts @@ -6,7 +6,6 @@ import { KEY_DISPOSED } from './common/Constants'; import { getGLTFLoaderBundle } from './common/GLTFBundle' import { ActiveAttributes, AttributeData, GeometryDesc, NumberArray } from './types/typings'; import REGL from '@maptalks/regl'; -import GraphicsDevice from './webgpu/GraphicsDevice'; import { flatten } from 'earcut'; import { getGPUVertexType, getFormatFromGLTFAccessor, getItemBytesFromGLTFAccessor } from './webgpu/common/Types'; @@ -56,7 +55,7 @@ const REF_COUNT_KEY = '_reshader_refCount'; export default class Geometry { static createElementBuffer(device: any, elements: any): any { - if (device instanceof GraphicsDevice) { + if (device.wgpu) { return createGPUBuffer(device, elements, GPUBufferUsage.INDEX, 'index buffer'); } else { // regl @@ -65,7 +64,7 @@ export default class Geometry { } static createBuffer(device: any, data: any, name?: string) { - if (device instanceof GraphicsDevice) { + if (device.wgpu) { return createGPUBuffer(device, data, GPUBufferUsage.VERTEX, name); } else { // regl @@ -360,6 +359,9 @@ export default class Geometry { if (!data[key]) { continue; } + if (Array.isArray(data[key])) { + data[key] = new Float32Array(data[key]); + } //如果调用过addBuffer,buffer有可能是ArrayBuffer if (data[key].buffer !== undefined && !(data[key].buffer instanceof ArrayBuffer)) { if (data[key].buffer.destroy) { diff --git a/packages/reshader.gl/src/Mesh.ts b/packages/reshader.gl/src/Mesh.ts index 923fdcc50a..c9a8819ef3 100644 --- a/packages/reshader.gl/src/Mesh.ts +++ b/packages/reshader.gl/src/Mesh.ts @@ -5,6 +5,7 @@ import Geometry from './Geometry'; import Material from './Material'; import { ActiveAttributes, MatrixFunction, MeshOptions, ShaderDefines, ShaderUniformValue, ShaderUniforms } from './types/typings'; import DynamicBuffer from './webgpu/DynamicBuffer'; +import DynamicOffsets from './webgpu/DynamicOffsets'; const tempMat4: mat4 = new Array(16) as mat4; @@ -552,11 +553,11 @@ export default class Mesh { _meshBuffer: DynamicBuffer; // 实现webgpu相关的逻辑 - writeDynamicBuffer(renderProps, bindGroupMapping, pool) { + writeDynamicBuffer(renderProps, bindGroupMapping, pool, dynamicOffsets: DynamicOffsets) { if (!this._meshBuffer) { this._meshBuffer = new DynamicBuffer(bindGroupMapping, pool); } - this._meshBuffer.writeBuffer(renderProps); + this._meshBuffer.writeBuffer(renderProps, dynamicOffsets); return this._meshBuffer; } } diff --git a/packages/reshader.gl/src/Renderer.ts b/packages/reshader.gl/src/Renderer.ts index ef4017270d..feb7569b16 100644 --- a/packages/reshader.gl/src/Renderer.ts +++ b/packages/reshader.gl/src/Renderer.ts @@ -21,10 +21,10 @@ class Renderer { let count = 0; if (scene) { const { opaques, transparents } = scene.getSortedMeshes(); - count += shader.draw(this.device, uniforms, opaques); - count += shader.draw(this.device, uniforms, transparents); + count += shader.draw(this.device, opaques); + count += shader.draw(this.device, transparents); } else { - count += shader.draw(this.device, uniforms); + count += shader.draw(this.device); } return count; } diff --git a/packages/reshader.gl/src/index.ts b/packages/reshader.gl/src/index.ts index aaab8d7209..5c45a3ac34 100644 --- a/packages/reshader.gl/src/index.ts +++ b/packages/reshader.gl/src/index.ts @@ -118,3 +118,5 @@ export { } from 'gl-matrix'; export { default as GraphicsDevice } from './webgpu/GraphicsDevice'; +export { default as GraphicsTexture } from './webgpu/GraphicsTexture'; +export { default as GraphicsFramebuffer } from './webgpu/GraphicsFramebuffer'; diff --git a/packages/reshader.gl/src/shader/MeshShader.js b/packages/reshader.gl/src/shader/MeshShader.js index 4d76f0fd3f..1234225a5c 100644 --- a/packages/reshader.gl/src/shader/MeshShader.js +++ b/packages/reshader.gl/src/shader/MeshShader.js @@ -2,7 +2,7 @@ import Shader from './Shader'; class MeshShader extends Shader { - draw(device, shaderUniforms, meshes) { + draw(device, meshes) { if (!meshes || !meshes.length) { return 0; } @@ -41,7 +41,7 @@ class MeshShader extends Shader { if (props.length && preCommand !== command) { //batch mode - this.run(device, command, shaderUniforms, props); + this.run(device, command, props); props.length = 0; } @@ -51,7 +51,7 @@ class MeshShader extends Shader { if (i < l - 1) { preCommand = command; } else if (i === l - 1) { - this.run(device, command, shaderUniforms, props); + this.run(device, command, props); } } return count; @@ -141,7 +141,7 @@ class MeshShader extends Shader { if (doubleSided && this.extraCommandProps) { commandProps.cull = { enable: false }; } - command = this.commands[dKey] = this.createMeshCommand(device, mesh, commandProps); + command = this.commands[dKey] = this.createMeshCommand(device, mesh, commandProps, uniformValues); } return command; } diff --git a/packages/reshader.gl/src/shader/Shader.ts b/packages/reshader.gl/src/shader/Shader.ts index 4c2bfea527..ebac8987ac 100644 --- a/packages/reshader.gl/src/shader/Shader.ts +++ b/packages/reshader.gl/src/shader/Shader.ts @@ -2,7 +2,7 @@ import { extend, isString, isFunction, isNumber, isSupportVAO, hasOwn, hashCode import ShaderLib from '../shaderlib/ShaderLib.js'; import { KEY_DISPOSED } from '../common/Constants.js'; -import { ShaderUniformValue } from '../types/typings'; +import { ShaderUniforms, ShaderUniformValue } from '../types/typings'; import PipelineDescriptor from '../webgpu/common/PipelineDesc'; import InstancedMesh from '../InstancedMesh'; import Mesh from '../Mesh'; @@ -10,6 +10,7 @@ import DynamicBuffer from '../webgpu/DynamicBuffer'; import CommandBuilder from '../webgpu/CommandBuilder'; import GraphicsDevice from '../webgpu/GraphicsDevice'; import GraphicsFramebuffer from '../webgpu/GraphicsFramebuffer'; +import DynamicOffsets from '../webgpu/DynamicOffsets'; const UNIFORM_TYPE = { @@ -113,7 +114,7 @@ export class GLShader { } // eslint-disable-next-line @typescript-eslint/no-unused-vars - run(regl: any, command, shaderUniforms, props) { + run(regl: any, command, props) { return command(props); } @@ -365,7 +366,9 @@ export class GLShader { _compileSource() { this.vert = ShaderLib.compile(this.vert); - this.frag = ShaderLib.compile(this.frag); + if (this.frag) { + this.frag = ShaderLib.compile(this.frag); + } } } @@ -394,20 +397,23 @@ export default class GPUShader extends GLShader { _currentPassEncoder: GPURenderPassEncoder //@internal _gpuFramebuffer: GraphicsFramebuffer; + //@internal + _dynamicOffsets: DynamicOffsets; - getShaderCommandKey(device, mesh, uniformValues, doubleSided) { + getShaderCommandKey(device, mesh, renderValues, doubleSided) { if (device && device.wgpu) { // 获取pipeline所需要的特征变量,即任何变量发生变化后,就需要创建新的pipeline + const fbo = this._gpuFramebuffer; const commandProps = this.extraCommandProps; - pipelineDesc.readFromREGLCommand(commandProps, mesh, uniformValues, doubleSided); + pipelineDesc.readFromREGLCommand(commandProps, mesh, renderValues, doubleSided, fbo); return pipelineDesc.getSignatureKey(); } else { // regl - return super.getShaderCommandKey(device, mesh, uniformValues, doubleSided); + return super.getShaderCommandKey(device, mesh, renderValues, doubleSided); } } - createMeshCommand(device: any, mesh: Mesh) { + createMeshCommand(device: any, mesh: Mesh, commandProps: any) { if (device && device.wgpu) { // 生成期: // 1. 负责对 wgsl 做预处理,生成最终执行的wgsl代码 @@ -416,19 +422,22 @@ export default class GPUShader extends GLShader { // 4. 生成 layout 和 pipeline // preprocess vert and frag codes - const builder = new CommandBuilder(this.name, device, this.vert, this.frag, mesh) - return builder.build(pipelineDesc); + const uniformValues = this.context; + const fbo = this._gpuFramebuffer; + const builder = new CommandBuilder(this.name, device, this.vert, this.frag, mesh, uniformValues); + return builder.build(pipelineDesc, fbo); } else { // regl - return super.createMeshCommand(device, mesh); + return super.createMeshCommand(device, mesh, commandProps); } } - run(deviceOrRegl: any, command, shaderUniforms, props) { + run(deviceOrRegl: any, command, props) { if (!deviceOrRegl.wgpu) { // regl command - return super.run(deviceOrRegl, command, shaderUniforms, props); + return super.run(deviceOrRegl, command, props); } + const shaderUniforms = this.context; const device = deviceOrRegl as GraphicsDevice; this.isGPU = true; const buffersPool = device.dynamicBufferPool; @@ -448,24 +457,33 @@ export default class GPUShader extends GLShader { if (!this._bindGroupCache) { this._bindGroupCache = {}; } + + if (!this._dynamicOffsets) { + this._dynamicOffsets = new DynamicOffsets(); + } + this._dynamicOffsets.reset(); // 向buffer中填入shader uniform值 - shaderBuffer.writeBuffer(shaderUniforms); + shaderBuffer.writeBuffer(shaderUniforms, this._dynamicOffsets); + const shaderDynamicOffsets = this._dynamicOffsets.items.slice(); + for (let i = 0; i < props.length; i++) { + this._dynamicOffsets.reset(); const mesh = props[i].meshObject as Mesh; // 获取mesh的dynamicBuffer - const meshBuffer = mesh.writeDynamicBuffer(props[i], bindGroupFormat.getMeshUniforms(), buffersPool); + const meshBuffer = mesh.writeDynamicBuffer(props[i], bindGroupFormat.getMeshUniforms(), buffersPool, this._dynamicOffsets); const groupKey = meshBuffer.version + '-' + shaderBuffer.version; // 获取或者生成bind group let bindGroup = this._bindGroupCache[groupKey]; if (!bindGroup || (bindGroup as any).outdated) { - bindGroup = bindGroupFormat.createBindGroup(device, mesh, layout, shaderBuffer, meshBuffer); + bindGroup = bindGroupFormat.createBindGroup(device, mesh, shaderUniforms, layout, shaderBuffer, meshBuffer); // 缓存bind group,只要buffer没有发生变化,即可以重用 // TODO 可以考虑每帧开始把缓存 bind group 标记为 retire,每帧结束时把不是 current 的 bind group 销毁掉 this._bindGroupCache[groupKey] = bindGroup; } // 获取 dynamicOffsets - const dynamicOffsets = shaderBuffer.dynamicOffsets.concat(meshBuffer.dynamicOffsets); + this._dynamicOffsets.addItems(shaderDynamicOffsets); + const dynamicOffsets = this._dynamicOffsets.getDynamicOffsets(); passEncoder.setBindGroup(0, bindGroup, dynamicOffsets); let instancedMesh; diff --git a/packages/reshader.gl/src/types/typings.ts b/packages/reshader.gl/src/types/typings.ts index db368f495a..5385d0d8dc 100644 --- a/packages/reshader.gl/src/types/typings.ts +++ b/packages/reshader.gl/src/types/typings.ts @@ -1,6 +1,7 @@ import REGL, { Texture, Texture2DOptions, TextureImageData } from "@maptalks/regl"; import { mat4 } from "gl-matrix"; import AbstractTexture from "../AbstractTexture"; +import GraphicsTexture from "../webgpu/GraphicsTexture"; export type UrlModifierFunction = (url: string) => string @@ -37,7 +38,7 @@ export type GeometryElements = { array: NumberArray } export type AttributeKey = { key: string } export type ActiveAttributes = { name: string, type: number }[] & AttributeKey -export type ShaderUniformValue = number | boolean | string | NumberArray | null | AbstractTexture | Texture +export type ShaderUniformValue = number | boolean | string | NumberArray | null | AbstractTexture | Texture | GraphicsTexture export type ShaderUniforms = { meshConfig?: MeshOptions, @@ -85,5 +86,6 @@ export type TextureConfig = { */ maxRange?: number, promise?: Promise, - persistent?: boolean + persistent?: boolean, + compare?: GPUCompareFunction, } & Texture2DOptions; diff --git a/packages/reshader.gl/src/webgpu/BindGroupFormat.ts b/packages/reshader.gl/src/webgpu/BindGroupFormat.ts index 8e803dbc3d..a4430ec59e 100644 --- a/packages/reshader.gl/src/webgpu/BindGroupFormat.ts +++ b/packages/reshader.gl/src/webgpu/BindGroupFormat.ts @@ -4,6 +4,9 @@ import DynamicBuffer from "./DynamicBuffer"; import Mesh from "../Mesh"; import Texture2D from "../Texture2D"; import GraphicsDevice from "./GraphicsDevice"; +import { ShaderUniforms } from "../types/typings"; +import AbstractTexture from "../AbstractTexture"; +import GraphicsTexture from "./GraphicsTexture"; export default class BindGroupFormat { bytes: number; @@ -47,6 +50,9 @@ export default class BindGroupFormat { } for (let i = 0; i < group.length; i++) { const uniform = group[i]; + if (!uniform) { + continue; + } if (uniform.isGlobal) { let index = this._shaderUniforms.index; this._shaderUniforms[index++] = uniform; @@ -61,7 +67,7 @@ export default class BindGroupFormat { } } - createBindGroup(device: GraphicsDevice, mesh: Mesh, layout: GPUBindGroupLayout, shaderBuffer: DynamicBuffer, meshBuffer: DynamicBuffer) { + createBindGroup(device: GraphicsDevice, mesh: Mesh, shaderUniforms: ShaderUniforms, layout: GPUBindGroupLayout, shaderBuffer: DynamicBuffer, meshBuffer: DynamicBuffer) { if (!this.groups) { return device.wgpu.createBindGroup({ layout, @@ -74,26 +80,32 @@ export default class BindGroupFormat { const textures = []; for (let i = 0; i < groups.length; i++) { const group = groups[i]; + if (!group) { + continue; + } const name = group.name; if (group.resourceType === ResourceType.Sampler) { // we assume sampler's name always be [textureName]Sampler const textureName = name.substring(0, name.length - 7); - const texture = (mesh.getUniform(textureName) || mesh.material && mesh.material.getUniform(textureName)) as Texture2D; + const texture = shaderUniforms && shaderUniforms[textureName] || (mesh.getUniform(textureName) || mesh.material && mesh.material.getUniform(textureName)) as Texture2D; //TODO texture是否存在 - const { min, mag, wrapS, wrapT } = (texture as Texture2D).config; - const filters = toGPUSampler(min, mag, wrapS, wrapT); + const { min, mag, wrapS, wrapT, compare } = (texture as Texture2D).config; + const filters = toGPUSampler(min, mag, wrapS, wrapT, compare); const sampler = device.wgpu.createSampler(filters); entries.push({ binding: group.binding, resource: sampler }); } else if (group.resourceType === ResourceType.Texture) { - const texture = (mesh.getUniform(name) || mesh.material && mesh.material.getUniform(name)) as Texture2D; - const graphicsTexture = texture.getREGLTexture(device); + const texture = shaderUniforms && shaderUniforms[name] || (mesh.getUniform(name) || mesh.material && mesh.material.getUniform(name)) as Texture2D; + let graphicsTexture = texture; + if (texture instanceof AbstractTexture) { + graphicsTexture = (texture as AbstractTexture).getREGLTexture(device); + } textures.push(graphicsTexture); entries.push({ binding: group.binding, - resource: graphicsTexture.getView() + resource: (graphicsTexture as GraphicsTexture).getView() }); } else { const allocation = group.isGlobal ? shaderBuffer.allocation : meshBuffer.allocation; @@ -102,7 +114,7 @@ export default class BindGroupFormat { resource: { buffer: allocation.gpuBuffer, // offset 永远设为0,在setBindGroup中设置dynamicOffsets - offset: 0, + // offset: 0, size: Math.max(group.size, this.alignment) } }); diff --git a/packages/reshader.gl/src/webgpu/CommandBuilder.ts b/packages/reshader.gl/src/webgpu/CommandBuilder.ts index 5f68fd36e8..f79aaf3b8e 100644 --- a/packages/reshader.gl/src/webgpu/CommandBuilder.ts +++ b/packages/reshader.gl/src/webgpu/CommandBuilder.ts @@ -4,26 +4,39 @@ import Mesh from '../Mesh'; import { ResourceType } from 'wgsl_reflect'; import GraphicsDevice from './GraphicsDevice'; import PipelineDescriptor from './common/PipelineDesc'; -import { ActiveAttributes } from '../types/typings'; +import { ActiveAttributes, ShaderUniforms } from '../types/typings'; import InstancedMesh from '../InstancedMesh'; import { WGSLParseDefines } from "./common/WGSLParseDefines"; +import GraphicsFramebuffer from "./GraphicsFramebuffer"; + +const ERROR_INFO = 'global uniform and mesh owned uniform can not be in the same struct'; export default class CommandBuilder { + //@internal device: GraphicsDevice; + //@internal vert: string; + //@internal frag: string; + //@internal mesh: Mesh; + //@internal _presentationFormat: any; + //@internal name: string; - constructor(name: string, device: GraphicsDevice, vert: string, frag: string, mesh: Mesh) { + //@internal + uniformValues: ShaderUniforms; + + constructor(name: string, device: GraphicsDevice, vert: string, frag: string, mesh: Mesh, uniformValues: ShaderUniforms) { this.name = name; this.device = device; this.vert = vert; this.frag = frag; this.mesh = mesh; + this.uniformValues = uniformValues; } - build(pipelineDesc) { + build(pipelineDesc: PipelineDescriptor, fbo: GraphicsFramebuffer) { const mesh = this.mesh; const device = this.device; const defines = this.mesh.getDefines(); @@ -35,11 +48,13 @@ export default class CommandBuilder { const vertexInfo = this._formatBufferInfo(vertReflect, mesh); const fragReflect = new WgslReflect(frag); + const vertGroups = vertReflect.getBindGroups(); + const fragGroups = fragReflect.getBindGroups(); // 生成 bind group layout - const layout = this._createBindGroupLayout(vertReflect, fragReflect, mesh); - const pipeline = this._createPipeline(device, vert, vertexInfo, frag, layout, mesh, pipelineDesc); + const layout = this._createBindGroupLayout(vertGroups, fragGroups, mesh, this.uniformValues); + const pipeline = this._createPipeline(device, vert, vertexInfo, frag, layout, mesh, pipelineDesc, fbo); - const bindGroupMapping = this._createBindGroupMapping(vertReflect, fragReflect, mesh); + const bindGroupMapping = this._createBindGroupMapping(vertGroups, fragGroups, mesh); const bindGroupFormat = new BindGroupFormat(bindGroupMapping, device.wgpu.limits.minUniformBufferOffsetAlignment); const activeAttributes = this._getActiveAttributes(vertexInfo); @@ -53,14 +68,14 @@ export default class CommandBuilder { } _getActiveAttributes(vertexInfo): ActiveAttributes { + //TODO const attributes = [{ name: 'position', type: 1 }]; (attributes as any).key = attributes.map(attr => attr.name).join(); return attributes as ActiveAttributes; } - _createBindGroupLayout(vertReflect: any, fragReflect: any, mesh: Mesh) { - const vertGroups = vertReflect.getBindGroups(); - const fragGroups = fragReflect.getBindGroups(); + _createBindGroupLayout(vertGroups: any, fragGroups: any, mesh: Mesh, uniformValues: ShaderUniforms) { + const entries = []; for (let i = 0; i < vertGroups.length; i++) { const groupInfo = vertGroups[i]; @@ -69,7 +84,7 @@ export default class CommandBuilder { if (!uniform) { continue; } - const entry = this._createLayoutEntry(uniform.binding, GPUShaderStage.VERTEX, uniform, mesh); + const entry = this._createLayoutEntry(uniform.binding, GPUShaderStage.VERTEX, uniform, mesh, uniformValues); entries.push(entry); } } @@ -80,30 +95,36 @@ export default class CommandBuilder { if (!uniform) { continue; } - const entry = this._createLayoutEntry(uniform.binding, GPUShaderStage.FRAGMENT, uniform, mesh); + const entry = this._createLayoutEntry(uniform.binding, GPUShaderStage.FRAGMENT, uniform, mesh, uniformValues); entries.push(entry); } } + // sort by binding + entries.sort(sortByBinding); + return this.device.wgpu.createBindGroupLayout({ label: this.name + '-bindgrouplayout', entries }); } - _createLayoutEntry(binding, visibility, groupInfo, mesh): GPUBindGroupLayoutEntry { + _createLayoutEntry(binding, visibility, groupInfo, mesh, uniformValues): GPUBindGroupLayoutEntry { if (groupInfo.resourceType === ResourceType.Sampler) { + const sampler: GPUSamplerBindingLayout = {}; + if (groupInfo.type && groupInfo.type.name === 'sampler_comparison') { + sampler.type = 'comparison'; + } return { binding, visibility, - sampler: {} - // sampler 采用默认值 + sampler }; } else if (groupInfo.resourceType === ResourceType.Texture) { const name = groupInfo.name; - const texture = mesh.material && mesh.material.get(name); - const type = texture && texture.config.type; + const texture = uniformValues[name] || mesh.material && mesh.material.get(name); + const format = texture && texture.gpuFormat.format; let sampleType: GPUTextureSampleType = 'float';//sint, uint - if (type === 'depth stencil' || type === 'depth') { + if (format && format.startsWith('depth')) { sampleType = 'depth'; } return { @@ -158,10 +179,8 @@ export default class CommandBuilder { return vertexInfo; } - _createBindGroupMapping(vertReflect: any, fragReflect: any, mesh: Mesh) { + _createBindGroupMapping(vertGroups: any, fragGroups: any, mesh: Mesh) { const mapping = {}; - const vertGroups = vertReflect.getBindGroups(); - const fragGroups = fragReflect.getBindGroups(); // 解析vertInfo和fragInfo,生成一个 bindGroupMapping,用于mesh在运行时,生成bindGroup // mapping 中包含 uniform 变量名对应的 group index 和 binding index this._parseGroupMapping(mapping, vertGroups[0], mesh); @@ -185,8 +204,12 @@ export default class CommandBuilder { for (let ii = 0; ii < members.length; ii++) { const name = members[ii].name; if (!meshHasUniform(mesh, name)) { + if (!isGlobal && ii > 0) { + throw new Error(ERROR_INFO + groupInfo); + } isGlobal = true; - break; + } else if (isGlobal) { + throw new Error(ERROR_INFO + groupInfo); } } } else { @@ -198,19 +221,26 @@ export default class CommandBuilder { mapping.groups = mapping.groups || []; mapping.groups[group] = mapping.groups[group] || []; groupInfo.isGlobal = isGlobal; + groupInfo.index = // we assume all the members in the same struct is all global or mesh owned mapping.groups[group][binding] = groupInfo;//extend({ } } // 运行时调用,生成 uniform buffer, 用来存放全局 uniform 变量的值 - _createPipeline(graphicsDevice: GraphicsDevice, vert: string, vertInfo, frag: string, layout: GPUBindGroupLayout, mesh:Mesh, pipelineDesc: PipelineDescriptor): GPURenderPipeline { + _createPipeline(graphicsDevice: GraphicsDevice, + vert: string, vertInfo, frag: string, layout: GPUBindGroupLayout, mesh:Mesh, + pipelineDesc: PipelineDescriptor, fbo: GraphicsFramebuffer): GPURenderPipeline { const device = graphicsDevice.wgpu; const vertModule = device.createShaderModule({ code: vert, }); - const fragModule = device.createShaderModule({ - code: frag, - }); + let fragModule; + if (frag) { + fragModule = device.createShaderModule({ + code: frag, + }); + } + if (!this._presentationFormat) { this._presentationFormat = navigator.gpu.getPreferredCanvasFormat(); } @@ -230,26 +260,33 @@ export default class CommandBuilder { module: vertModule, buffers }, - fragment: { - module: fragModule, - targets: [ - { - format: this._presentationFormat, - } - ], - }, primitive: { topology: pipelineDesc.topology, cullMode: pipelineDesc.cullMode - }, - depthStencil: { + } + }; + const depthEnabled = !fbo || !!fbo.depthTexture; + + if (depthEnabled) { + const depthTexture = fbo && fbo.depthTexture; + pipelineOptions.depthStencil = { depthBias: pipelineDesc.depthBias, depthBiasSlopeScale: pipelineDesc.depthBiasSlopeScale, depthWriteEnabled: pipelineDesc.depthWriteEnabled, depthCompare: pipelineDesc.depthCompare, - format: 'depth24plus-stencil8' - } - }; + format: (!depthTexture || depthTexture.gpuFormat.isDepthStencil) ? 'depth24plus-stencil8' : 'depth24plus' + }; + } + if (fragModule) { + pipelineOptions.fragment = { + module: fragModule, + targets: [ + { + format: this._presentationFormat, + } + ], + }; + } if (pipelineDesc.stencilFrontCompare) { pipelineOptions.depthStencil.stencilBack = pipelineOptions.depthStencil.stencilFront = { @@ -257,7 +294,7 @@ export default class CommandBuilder { passOp: pipelineDesc.stencilFrontPassOp }; } - if (pipelineDesc.blendAlphaDst) { + if (fragModule && pipelineDesc.blendAlphaDst) { const fragTargets = pipelineOptions.fragment.targets; for (const target of fragTargets) { target.blend = { @@ -292,3 +329,8 @@ function getItemSize(type) { return 1; } } + +function sortByBinding(a: GPUBindGroupLayoutEntry, b: GPUBindGroupLayoutEntry): number { + return a.binding - b.binding; +} + diff --git a/packages/reshader.gl/src/webgpu/DynamicBuffer.ts b/packages/reshader.gl/src/webgpu/DynamicBuffer.ts index 3055d4d936..8122c49d2b 100644 --- a/packages/reshader.gl/src/webgpu/DynamicBuffer.ts +++ b/packages/reshader.gl/src/webgpu/DynamicBuffer.ts @@ -1,7 +1,8 @@ import { ResourceType } from "wgsl_reflect"; import { ShaderUniformValue } from "../types/typings"; import DynamicBufferPool, { DynamicBufferAllocation } from "./DynamicBufferPool"; -import { isArray } from "../common/Util"; +import { isArray, isFunction } from "../common/Util"; +import DynamicOffsets from "./DynamicOffsets"; export default class DynamicBuffer { bindgroupMapping: any; @@ -13,12 +14,10 @@ export default class DynamicBuffer { constructor(bindgroupMapping, pool: DynamicBufferPool) { this.bindgroupMapping = bindgroupMapping; this.pool = pool; - this.dynamicOffsets = new Array(bindgroupMapping.filter(uniform => (uniform.resourceType === ResourceType.Uniform || uniform.members)).length); this.allocation = {}; } - writeBuffer(uniformValues: Record) { - this.dynamicOffsets.fill(0); + writeBuffer(uniformValues: Record, dynamicOffsets: DynamicOffsets) { const totalSize = this.bindgroupMapping.totalSize; const gpuBuffer = this.allocation.gpuBuffer; const bufferAlignment = this.pool.bufferAlignment; @@ -26,14 +25,16 @@ export default class DynamicBuffer { if (gpuBuffer !== this.allocation.gpuBuffer) { this.version++; } + let dynamicOffset = this.allocation.offset; + const mapping = this.bindgroupMapping; const storage = this.allocation.storage; - let index = 0; + for (let i = 0; i < mapping.length; i++) { const uniform = mapping[i]; if (uniform.members) { - this.dynamicOffsets[index++] = dynamicOffset; + dynamicOffsets.addItem({ binding: uniform.binding, offset: dynamicOffset }); for (let j = 0; j < uniform.members.length; j++) { const member = uniform.members[j]; const value = uniformValues[member.name] as number | number[]; @@ -41,14 +42,17 @@ export default class DynamicBuffer { const size = member.size; this._fillValue(storage, offset, size, value); } - dynamicOffset += Math.min(mapping[i].size, bufferAlignment); + dynamicOffset += Math.max(mapping[i].size, bufferAlignment); } else if (uniform.resourceType === ResourceType.Uniform) { - this.dynamicOffsets[index++] = dynamicOffset; + dynamicOffsets.addItem({ binding: uniform.binding, offset: dynamicOffset }); const value = uniformValues[uniform.name]; - this._fillValue(storage, dynamicOffset, uniform.size(), value); - dynamicOffset += Math.min(mapping[i].size(), bufferAlignment); + const size = isFunction(uniform.size) ? uniform.size() : uniform.size; + this._fillValue(storage, dynamicOffset, size, value); + dynamicOffset += Math.max(size, bufferAlignment); } } + + // console.log(debugInfo.join()); } _fillValue(buffer, offset, size, value) { @@ -66,6 +70,5 @@ export default class DynamicBuffer { dispose() { delete this.pool; delete this.allocation; - delete this.dynamicOffsets; } } diff --git a/packages/reshader.gl/src/webgpu/DynamicOffsets.ts b/packages/reshader.gl/src/webgpu/DynamicOffsets.ts new file mode 100644 index 0000000000..56140602ff --- /dev/null +++ b/packages/reshader.gl/src/webgpu/DynamicOffsets.ts @@ -0,0 +1,37 @@ +export default class DynamicOffsets { + + items: any[]; + offsets: number[]; + index: number; + + constructor() { + this.items = []; + this.offsets = []; + this.index = 0; + } + + reset() { + this.index = 0; + this.items.length = 0; + this.offsets.length = 0; + } + + addItem(item) { + this.items[this.index++] = item; + } + + addItems(items: any[]) { + for (let i = 0; i < items.length; i++) { + this.addItem(items[i]); + } + } + + getDynamicOffsets() { + const items = this.items; + items.sort((a, b) => a.binding - b.binding); + for (let i = 0; i < items.length; i++) { + this.offsets[i] = items[i].offset; + } + return this.offsets; + } +} diff --git a/packages/reshader.gl/src/webgpu/GraphicsFramebuffer.ts b/packages/reshader.gl/src/webgpu/GraphicsFramebuffer.ts index cd0160d030..b820feb2af 100644 --- a/packages/reshader.gl/src/webgpu/GraphicsFramebuffer.ts +++ b/packages/reshader.gl/src/webgpu/GraphicsFramebuffer.ts @@ -5,9 +5,9 @@ export default class GraphicsFramebuffer { device: GraphicsDevice; options: any; //@internal - colorTexture: GPUTexture; + colorTexture: GraphicsTexture; //@internal - depthTexture: GPUTexture; + depthTexture: GraphicsTexture; //@internal _renderPass: GPURenderPassDescriptor; width: number; @@ -32,11 +32,11 @@ export default class GraphicsFramebuffer { } _update() { - let color = this.options.color || this.options.colors && this.options.colors[0]; + let color = this.options.colors && this.options.colors[0] || this.options.color; const { width, height } = this.options; this.width = width; this.height = height; - if (!color) { + if (color && !(color instanceof GraphicsTexture)) { color = new GraphicsTexture(this.device, color); } let depth = this.options.depth; @@ -49,39 +49,47 @@ export default class GraphicsFramebuffer { } } } - + // we assume depth and depthStencil won't be used at the same time const depthStencil = this.options.depthStencil; - if (depthStencil === true) { - depth = new GraphicsTexture(this.device, { width, height, format: 'depth stencil' }); - } else { - if (depthStencil instanceof GraphicsTexture) { - depth = depthStencil; + if (depthStencil) { + if (depthStencil === true) { + depth = new GraphicsTexture(this.device, { width, height, format: 'depth stencil' }); } else { - depth = new GraphicsTexture(this.device, depthStencil); + if (depthStencil instanceof GraphicsTexture) { + depth = depthStencil; + } else { + depth = new GraphicsTexture(this.device, depthStencil); + } } } - this.colorTexture = color === 'undefined' ? null : color; + + this.colorTexture = color; this.depthTexture = depth; this._renderPass = { - colorAttachments: [ - { - view: this.colorTexture && this.colorTexture.createView(), // Assigned later - clearValue: [0, 0, 0, 0], - loadOp: 'load', - storeOp: 'store', - }, - ], - depthStencilAttachment: { - view: this.depthTexture.createView(), - depthClearValue: 1.0, - depthLoadOp: 'load', - depthStoreOp: 'store', - stencilReadOnly: false, - stencilClearValue: 255, - stencilLoadOp: 'load', - stencilStoreOp: 'store', - }, + colorAttachments: [] }; + if (color !== null) { + this._renderPass.colorAttachments[0] = { + view: this.colorTexture && this.colorTexture.getView(), // Assigned later + clearValue: [0, 0, 0, 0], + loadOp: 'load', + storeOp: 'store', + }; + } + if (depth) { + const depthAttchment = this._renderPass.depthStencilAttachment = { + view: this.depthTexture.getView(), + depthClearValue: 1, + depthLoadOp: 'load', + depthStoreOp: 'store' + } as GPURenderPassDepthStencilAttachment; + if (depth.gpuFormat.isDepthStencil) { + depthAttchment.stencilReadOnly = false; + depthAttchment.stencilClearValue = 0; + depthAttchment.stencilLoadOp = 'load'; + depthAttchment.stencilStoreOp = 'store'; + } + } } getRenderPassDescriptor() { @@ -94,10 +102,13 @@ export default class GraphicsFramebuffer { if (depthStencilAttachment) { depthStencilAttachment.depthLoadOp = this.depthLoadOp || 'load'; depthStencilAttachment.depthClearValue = this.depthClearValue || 1; - depthStencilAttachment.stencilLoadOp = this.stencilLoadOp || 'load'; - depthStencilAttachment.stencilClearValue = this.stencilClearValue || 0; + if (this.depthTexture.gpuFormat.isDepthStencil) { + depthStencilAttachment.stencilLoadOp = this.stencilLoadOp || 'load'; + depthStencilAttachment.stencilClearValue = this.stencilClearValue || 0; + } + } - this.resetClearOptions(); + this._resetClearOptions(); return this._renderPass; } @@ -116,7 +127,7 @@ export default class GraphicsFramebuffer { } } - resetClearOptions() { + _resetClearOptions() { this.colorLoadOp = null; this.colorClearValue = null; this.depthLoadOp = null; diff --git a/packages/reshader.gl/src/webgpu/GraphicsTexture.ts b/packages/reshader.gl/src/webgpu/GraphicsTexture.ts index 09c8deff58..a9d31f65e5 100644 --- a/packages/reshader.gl/src/webgpu/GraphicsTexture.ts +++ b/packages/reshader.gl/src/webgpu/GraphicsTexture.ts @@ -46,34 +46,35 @@ export default class GraphicsTexture { } const format = this.gpuFormat.format; const isDepth = format === 'depth24plus' || format === 'depth24plus-stencil8'; + const usage = isDepth ? + GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT + : + GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT; + texture = device.createTexture({ size: [width, height, 1], format, - usage: isDepth ? - GPUTextureUsage.TEXTURE_BINDING | - GPUTextureUsage.RENDER_ATTACHMENT - : - GPUTextureUsage.TEXTURE_BINDING | - GPUTextureUsage.COPY_DST | - GPUTextureUsage.RENDER_ATTACHMENT, + usage }); - if (isArray(config.data)) { - let data =config.data; - if (Array.isArray(config.data)) { - data = new Float32Array(data); + if (config.data) { + if (isArray(config.data)) { + let data =config.data; + if (Array.isArray(config.data)) { + data = new Float32Array(data); + } + device.queue.writeTexture( + { texture: texture }, + data.buffer, + {}, + [width, height] + ); + } else { + device.queue.copyExternalImageToTexture( + { source: config.data }, + { texture: texture }, + [width, height] + ); } - device.queue.writeTexture( - { texture: texture }, - data.buffer, - {}, - [width, height] - ); - } else { - device.queue.copyExternalImageToTexture( - { source: config.data }, - { texture: texture }, - [width, height] - ); } } this.texture = texture; diff --git a/packages/reshader.gl/src/webgpu/common/PipelineDesc.ts b/packages/reshader.gl/src/webgpu/common/PipelineDesc.ts index f56cc330bb..23532baee4 100644 --- a/packages/reshader.gl/src/webgpu/common/PipelineDesc.ts +++ b/packages/reshader.gl/src/webgpu/common/PipelineDesc.ts @@ -1,4 +1,5 @@ import { isFunction, isNil } from "../../common/Util"; +import GraphicsFramebuffer from "../GraphicsFramebuffer"; import { toGPUCompareFunction, toTopology, toGPUBlendFactor } from "./ReglTranslator"; // Pipeline states we cared @@ -20,12 +21,15 @@ export default class PipelineDescriptor { frontFace?: GPUFrontFace; topology?: GPUPrimitiveTopology; - readFromREGLCommand(commandProps: any, mesh, uniformValues, doubleSided) { + readFromREGLCommand(commandProps: any, mesh, uniformValues, doubleSided, fbo: GraphicsFramebuffer) { const primitive = mesh.geometry.desc.primitive; this.topology = toTopology(primitive); + const depthEnabled = !fbo || !!fbo.depthTexture; + const stencilEnabled = !fbo || fbo.depthTexture.gpuFormat.isDepthStencil; + let depthBias, depthBiasSlopeScale; - if (commandProps.polygonOffset && isEnable(commandProps.polygonOffset.enable, uniformValues)) { + if (depthEnabled && commandProps.polygonOffset && isEnable(commandProps.polygonOffset.enable, uniformValues)) { depthBias = 0; depthBiasSlopeScale = 0; const offsetProps = commandProps.polygonOffset.offset; @@ -42,7 +46,7 @@ export default class PipelineDescriptor { let depthCompare: GPUCompareFunction = 'always'; let depthWriteEnable = false; const depthProps = commandProps.depth; - if (depthProps && isEnable(depthProps.enable, uniformValues)) { + if (depthEnabled && depthProps && isEnable(depthProps.enable, uniformValues)) { depthCompare = 'less'; if (depthProps.func) { const depthFunc = isFunction(depthProps.func) && depthProps.func(null, uniformValues) || depthProps.func; @@ -62,7 +66,7 @@ export default class PipelineDescriptor { let stencilFrontCompare, stencilFrontPassOp; const stencilProps = commandProps.stencil; - if (stencilProps && isEnable(stencilProps.enable, uniformValues)) { + if (stencilEnabled && stencilProps && isEnable(stencilProps.enable, uniformValues)) { if (stencilProps.op) { // 目前还没遇到op是函数的情况,所以可以直接读取 stencilFrontPassOp = stencilProps.op.zpass; diff --git a/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts b/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts index 3d5de7893d..17a9ef7a70 100644 --- a/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts +++ b/packages/reshader.gl/src/webgpu/common/ReglTranslator.ts @@ -47,7 +47,8 @@ const ADDRESS_MODE_DICTIONARY = { 'mirror': 'mirror-repeat' }; -export function toGPUSampler(minFilter: string, magFilter: GPUFilterMode, wrapS: string, wrapT) { +export function toGPUSampler(minFilter: string, magFilter: GPUFilterMode, + wrapS: string, wrapT: string, compare?: GPUCompareFunction) { const sampler: GPUSamplerDescriptor = { magFilter }; @@ -68,31 +69,34 @@ export function toGPUSampler(minFilter: string, magFilter: GPUFilterMode, wrapS: } sampler.addressModeU = ADDRESS_MODE_DICTIONARY[wrapS || 'clamp'] || wrapS; sampler.addressModeV = ADDRESS_MODE_DICTIONARY[wrapT || 'clamp'] || wrapT; + if (compare) { + sampler.compare = compare; + } return sampler; } -export type GPUTexFormat = { format: GPUTextureFormat, bytesPerTexel: number }; +export type GPUTexFormat = { format: GPUTextureFormat, bytesPerTexel: number, isDepthStencil: boolean }; export function toTextureFormat(format: string, type: string): GPUTexFormat { format = format || 'rgba'; type = type || 'uint8'; if (format === 'depth stencil') { - return { format: 'depth24plus-stencil8', bytesPerTexel: 4 }; + return { format: 'depth24plus-stencil8', bytesPerTexel: 4, isDepthStencil: true}; } else if (format === 'depth') { - return { format: 'depth24plus', bytesPerTexel: 4 }; + return { format: 'depth24plus', bytesPerTexel: 4, isDepthStencil: false }; } if (type === 'uint8') { if (format === 'rgba') { - return { format: 'rgba8unorm', bytesPerTexel: 1 }; + return { format: 'rgba8unorm', bytesPerTexel: 1, isDepthStencil: false }; } else { //TODO 各种压缩纹理的类型 } } else if (type === 'float16' || type === 'half float') { - return { format: 'r16float', bytesPerTexel: 2 }; + return { format: 'r16float', bytesPerTexel: 2, isDepthStencil: false }; } else if (type === 'float') { - return { format: 'r32float', bytesPerTexel: 4 }; + return { format: 'r32float', bytesPerTexel: 4, isDepthStencil: false }; } - return { format: 'rgba8unorm', bytesPerTexel: 1 }; + return { format: 'rgba8unorm', bytesPerTexel: 1, isDepthStencil: false }; } From 677efd262c6a1f995c260bebc9f21bf91641e6ec Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Thu, 13 Feb 2025 16:21:22 +0800 Subject: [PATCH 20/53] fix webgpu/shadowmapping.html --- debug/webgpu/index.html | 18 +++++ debug/webgpu/meshes/ground.js | 33 ++++++++ debug/webgpu/shadowmapping.html | 79 ++++++++++--------- .../reshader.gl/src/webgpu/CommandBuilder.ts | 11 ++- .../reshader.gl/src/webgpu/GraphicsDevice.ts | 1 + pnpm-lock.yaml | 9 +++ 6 files changed, 113 insertions(+), 38 deletions(-) create mode 100644 debug/webgpu/index.html create mode 100644 debug/webgpu/meshes/ground.js diff --git a/debug/webgpu/index.html b/debug/webgpu/index.html new file mode 100644 index 0000000000..5570e07211 --- /dev/null +++ b/debug/webgpu/index.html @@ -0,0 +1,18 @@ + + + + + reshader(WebGPU) examples + + +

+ + diff --git a/debug/webgpu/meshes/ground.js b/debug/webgpu/meshes/ground.js new file mode 100644 index 0000000000..75baaeb396 --- /dev/null +++ b/debug/webgpu/meshes/ground.js @@ -0,0 +1,33 @@ +// +export function getGroundVertexes() { + // Push vertex attributes for an additional ground plane + // prettier-ignore + return [ + 3, -3, -3, 1, + 3, 3, -3, 1, + -3, 3, -3, 1, + + 3, -3, -3, 1, + -3, 3, -3, 1, + -3, -3, -3, 1 + ]; + + // if (triangles) { + // // Push indices for an additional ground plane + // triangles.push( + // [positions.length, positions.length + 2, positions.length + 1], + // [positions.length, positions.length + 1, positions.length + 3] + // ); + // } + + + // if (uvs) { + // uvs.push( + // [0, 0], // + // [1, 1], // + // [0, 1], // + // [1, 0] + // ); + // } + +} diff --git a/debug/webgpu/shadowmapping.html b/debug/webgpu/shadowmapping.html index 6f7113d16c..3eb31a75ac 100644 --- a/debug/webgpu/shadowmapping.html +++ b/debug/webgpu/shadowmapping.html @@ -41,32 +41,19 @@ @group(0) @binding(0) var scene : Scene; @group(0) @binding(1) var model : Model; - struct VertexOutput { - @location(0) shadowPos: vec3f, - @location(1) fragPos: vec3f, - - @builtin(position) Position: vec4f, - } - @vertex fn main( - @location(0) position: vec3f - ) -> VertexOutput { - var output : VertexOutput; - - // XY is in (-1, 1) space, Z is in (0, 1) space - let posFromLight = scene.lightViewProjMatrix * model.modelMatrix * vec4(position, 1.0); + @location(0) position: vec4f + ) -> @builtin(position) vec4f { + return scene.lightViewProjMatrix * model.modelMatrix * position; + } - // Convert XY to (0, 1) - // Y is flipped because texture coords are Y-down. - output.shadowPos = vec3( - posFromLight.xy * vec2(0.5, -0.5) + vec2(0.5), - posFromLight.z - ); + `; - output.Position = scene.cameraViewProjMatrix * model.modelMatrix * vec4(position, 1.0); - output.fragPos = output.Position.xyz; - return output; + const fragShadow = /* wgsl */` + @fragment + fn main() -> @location(0) vec4f { + return vec4(1.0, 0.0, 0.0, 1.0); } `; @@ -132,6 +119,7 @@ } + const albedo = vec3f(0.9); const ambientFactor = 0.2; @fragment @@ -147,18 +135,16 @@ visibility += textureSampleCompare( shadowMap, shadowMapSampler, - input.shadowPos.xy + offset, input.shadowPos.z - 0.007 + input.shadowPos.xy + offset, input.shadowPos.z - 0.0007 ); } } visibility /= 9.0; - let lightingFactor = min(ambientFactor + visibility, 1.0); - let albedo = input.fragPos; - // return vec4(lightingFactor, 1.0); + let lightingFactor = min(ambientFactor + visibility * 0.3, 1.0); - // return vec4(vec3f(visibility), 1.0); - return vec4(scene.lightPos + vec3(0.5), 1.0); + // return vec4(vec3(visibility), 1.0); + return vec4(lightingFactor * albedo, 1.0); } `; @@ -171,11 +157,15 @@ const renderer = new Renderer(device); const shadowShader = new MeshShader({ vert: vertShadow, + // frag: fragShadow, name: 'shadow', extraCommandProps: { depth: { enable: true, func: '<' + }, + cull: { + enable: true } } }); @@ -184,6 +174,10 @@ frag, name: 'cube', extraCommandProps: { + depth: { + enable: true, + func: '<=' + }, cull: { enable: true } @@ -215,18 +209,29 @@ format: 'depth', compare: 'less' }); - const shadowFBO = device.framebuffer({ depth: depthTexture, width: 1024, height: 1024 }); + const shadowFBO = device.framebuffer({ + color: null, + depth: depthTexture, + width: 1024, + height: 1024 + }); function render() { + device.clear({ + color: [0, 0, 0, 0], + depth: 1 + }); + const cameraViewProjMatrix = getCameraViewProjMatrix(); const { lightViewProjMatrix, lightPos } = getLightMatrix(); scene.setMeshes([mesh, groundMesh]); const uniforms = { cameraViewProjMatrix, lightViewProjMatrix, lightPos }; - // renderer.render(shadowShader, uniforms, scene, shadowFBO); + renderer.render(shadowShader, uniforms, scene, shadowFBO); renderer.render(shader, { cameraViewProjMatrix, lightViewProjMatrix, lightPos, shadowMap: shadowFBO.depthTexture }, scene); + device.submit(); requestAnimationFrame(render); } @@ -249,15 +254,15 @@ // light related matrices - const lightPosition = vec3.set([], 0, 2, 4); + const lightPosition = vec3.set([], -7, -4, 5); const lightViewMatrix = mat4.lookAt([], lightPosition, origin, upVector); - const lightProjectionMatrix = mat4.create(); + const lightProjectionMatrix = []; { - const left = -20; - const right = 20; - const bottom = -20; - const top = 20; - const near = -20; + const left = -10; + const right = 10; + const bottom = -10; + const top = 10; + const near = 1; const far = 20; mat4.ortho(lightProjectionMatrix, left, right, bottom, top, near, far); } diff --git a/packages/reshader.gl/src/webgpu/CommandBuilder.ts b/packages/reshader.gl/src/webgpu/CommandBuilder.ts index f79aaf3b8e..170d2204bf 100644 --- a/packages/reshader.gl/src/webgpu/CommandBuilder.ts +++ b/packages/reshader.gl/src/webgpu/CommandBuilder.ts @@ -8,6 +8,8 @@ import { ActiveAttributes, ShaderUniforms } from '../types/typings'; import InstancedMesh from '../InstancedMesh'; import { WGSLParseDefines } from "./common/WGSLParseDefines"; import GraphicsFramebuffer from "./GraphicsFramebuffer"; +import Texture2D from "../Texture2D"; +import GraphicsTexture from "./GraphicsTexture"; const ERROR_INFO = 'global uniform and mesh owned uniform can not be in the same struct'; @@ -122,7 +124,14 @@ export default class CommandBuilder { } else if (groupInfo.resourceType === ResourceType.Texture) { const name = groupInfo.name; const texture = uniformValues[name] || mesh.material && mesh.material.get(name); - const format = texture && texture.gpuFormat.format; + let format; + if (texture) { + if (texture instanceof Texture2D) { + format = (texture as Texture2D).config.type; + } else if (texture instanceof GraphicsTexture) { + format = (texture as GraphicsTexture).gpuFormat.format; + } + } let sampleType: GPUTextureSampleType = 'float';//sint, uint if (format && format.startsWith('depth')) { sampleType = 'depth'; diff --git a/packages/reshader.gl/src/webgpu/GraphicsDevice.ts b/packages/reshader.gl/src/webgpu/GraphicsDevice.ts index 91116ecd4f..5dfbb78c41 100644 --- a/packages/reshader.gl/src/webgpu/GraphicsDevice.ts +++ b/packages/reshader.gl/src/webgpu/GraphicsDevice.ts @@ -75,6 +75,7 @@ export default class GraphicsDevice { desc.colorAttachments[0].view = this.context .getCurrentTexture() .createView(); + desc.colorAttachments[0].view.label = 'default canvas view'; } const commandEncoder = this.getCommandEncoder(); return commandEncoder.beginRenderPass(desc); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7151e0697e..e07f595504 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -712,6 +712,15 @@ importers: rollup-plugin-dts: specifier: ^6.1.0 version: 6.1.1(rollup@4.17.2)(typescript@5.4.5) + tslib: + specifier: ^2.6.2 + version: 2.6.2 + typescript: + specifier: ^5.4.2 + version: 5.4.5 + typescript-eslint: + specifier: ^7.18.0 + version: 7.18.0(eslint@8.57.0)(typescript@5.4.5) packages/traffic: dependencies: From bac59030d6841be56ed9834a65478628e618304d Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Fri, 14 Feb 2025 10:51:27 +0800 Subject: [PATCH 21/53] add support of device.framebuffer(width, height) --- packages/reshader.gl/src/webgpu/GraphicsDevice.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/reshader.gl/src/webgpu/GraphicsDevice.ts b/packages/reshader.gl/src/webgpu/GraphicsDevice.ts index 5dfbb78c41..66541806e0 100644 --- a/packages/reshader.gl/src/webgpu/GraphicsDevice.ts +++ b/packages/reshader.gl/src/webgpu/GraphicsDevice.ts @@ -1,3 +1,4 @@ +import { isNumber } from "../common/Util"; import Geometry from "../Geometry"; import DynamicBufferPool from "./DynamicBufferPool"; import GraphicsFramebuffer from "./GraphicsFramebuffer"; @@ -106,8 +107,16 @@ export default class GraphicsDevice { } // implementation of regl.framebuffer - framebuffer(reglFBODescriptor) { - // reglDesciprtor => gpu renderPassEncoder + framebuffer(width, height) { + let reglFBODescriptor; + if (!isNumber(width)) { + reglFBODescriptor = width; + } else { + if (height === undefined) { + height = width; + } + reglFBODescriptor = { color: true, depthStencil: true, width, height }; + } return new GraphicsFramebuffer(this, reglFBODescriptor); } From 4fcc172bcda3dd1caab2090e358620abbda6bd2f Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Fri, 14 Feb 2025 10:51:56 +0800 Subject: [PATCH 22/53] adaption of gl renderer for VectorTileLayer and Vector3DLayer --- debug/terrain.html | 2 - .../src/layer/renderer/TileMeshPainter.js | 5 ++- .../layer/renderer/VectorTileLayerRenderer.js | 37 ++++--------------- .../src/layer/vector/Vector3DLayerRenderer.js | 16 +------- 4 files changed, 12 insertions(+), 48 deletions(-) diff --git a/debug/terrain.html b/debug/terrain.html index 266a4b060a..d432b1e49d 100644 --- a/debug/terrain.html +++ b/debug/terrain.html @@ -127,8 +127,6 @@ //mapbox服务 debug: true, type: 'mapbox', - tileSize: 256, - urlTemplate: 'https://{s}.tiles.mapbox.com/v4/mapbox.terrain-rgb/{z}/{x}/{y}.pngraw?access_token=pk.eyJ1IjoibWFwYm94LWdsLWpzIiwiYSI6ImNram9ybGI1ajExYjQyeGxlemppb2pwYjIifQ.LGy5UGNIsXUZdYMvfYRiAQ', // urlTemplate: 'https://api.mapbox.com/v4/mapbox.terrain-rgb/{z}/{x}/{y}@2x.png?access_token=pk.eyJ1Ijoidmhhd2siLCJhIjoiY2xmbWpqOXBoMGNmZDN2cjJwZXk0MXBzZiJ9.192VNPJG0VV9dGOCOX1gUw', // urlTemplate: 'https://osm-tiles.ams3.cdn.digitaloceanspaces.com/elevation/{z}/{x}/{y}.png', diff --git a/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js b/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js index d270dcdd2c..769066b3d5 100644 --- a/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js +++ b/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js @@ -803,7 +803,8 @@ export default class TileMeshPainter { instanceBuffers[p] = { buffer: this._regl.buffer({ dimension: instanceData[p].length / instanceCount, - data: instanceData[p] + data: instanceData[p], + name: p }), divisor: 1 }; @@ -1447,7 +1448,7 @@ export default class TileMeshPainter { } const attrs = {}; for (const p in attributes) { - const buffer = getUniqueREGLBuffer(this._regl, attributes[p], { dimension: attributes[p].itemSize }); + const buffer = getUniqueREGLBuffer(this._regl, attributes[p], { dimension: attributes[p].itemSize, name: p }); // 优先采用 attributeSemantics中定义的属性 const name = attributeSemantics[p] || p; attrs[name] = extend({}, attributes[p]); diff --git a/packages/vt/src/layer/renderer/VectorTileLayerRenderer.js b/packages/vt/src/layer/renderer/VectorTileLayerRenderer.js index 2e3a8d2c72..565ff1f972 100644 --- a/packages/vt/src/layer/renderer/VectorTileLayerRenderer.js +++ b/packages/vt/src/layer/renderer/VectorTileLayerRenderer.js @@ -248,20 +248,11 @@ class VectorTileLayerRenderer extends maptalks.renderer.TileLayerCanvasRenderer return !!inGroup; } - createContext() { - const inGroup = this.canvas.gl && this.canvas.gl.wrap; - if (inGroup) { - this.gl = this.canvas.gl.wrap(); - this.regl = this.canvas.gl.regl; - } else { - const { gl, regl, attributes } = this._createREGLContext(this.canvas); - this.gl = gl; - this.regl = regl; - this.glOptions = attributes; - } - if (inGroup) { - this.canvas.pickingFBO = this.canvas.pickingFBO || this.regl.framebuffer(this.canvas.width, this.canvas.height); - } + initContext() { + const { regl, reglGL } = this.context; + this.regl = regl; + this.gl = reglGL; + this.canvas.pickingFBO = this.canvas.pickingFBO || this.regl.framebuffer(this.canvas.width, this.canvas.height); this.pickingFBO = this.canvas.pickingFBO || this.regl.framebuffer(this.canvas.width, this.canvas.height); this._debugPainter = new DebugPainter(this.regl, this.getMap()); this._prepareWorker(); @@ -326,19 +317,6 @@ class VectorTileLayerRenderer extends maptalks.renderer.TileLayerCanvasRenderer this._workerCacheIndex++; } - clearCanvas() { - super.clearCanvas(); - if (!this.regl) { - return; - } - //这里必须通过regl来clear,如果直接调用webgl context的clear,则brdf的texture会被设为0 - this.regl.clear({ - color: CLEAR_COLOR, - depth: 1, - stencil: 0 - }); - } - isDrawable() { return true; } @@ -1611,6 +1589,8 @@ class VectorTileLayerRenderer extends maptalks.renderer.TileLayerCanvasRenderer this._groundPainter.dispose(); delete this._groundPainter; } + delete this.gl; + delete this.regl; if (super.onRemove) super.onRemove(); this._clearPlugin(); } @@ -2006,8 +1986,7 @@ class VectorTileLayerRenderer extends maptalks.renderer.TileLayerCanvasRenderer _getLayerOpacity() { const layerOpacity = this.layer.options['opacity']; - // 不在GroupGLLayer中时,MapCanvasRenderer会读取opacity并按照透明度绘制,所以layerOpacity设成1 - return this._isInGroupGLLayer() ? (isNil(layerOpacity) ? 1 : layerOpacity) : 1; + return (isNil(layerOpacity) ? 1 : layerOpacity); } } diff --git a/packages/vt/src/layer/vector/Vector3DLayerRenderer.js b/packages/vt/src/layer/vector/Vector3DLayerRenderer.js index 2df36ca683..3437366233 100644 --- a/packages/vt/src/layer/vector/Vector3DLayerRenderer.js +++ b/packages/vt/src/layer/vector/Vector3DLayerRenderer.js @@ -1530,19 +1530,6 @@ class Vector3DLayerRenderer extends maptalks.renderer.CanvasRenderer { /* eslint-enable no-empty */ } - clearCanvas() { - super.clearCanvas(); - if (!this.regl) { - return; - } - //这里必须通过regl来clear,如果直接调用webgl context的clear,则brdf的texture会被设为0 - this.regl.clear({ - color: [0, 0, 0, 0], - depth: 1, - stencil: 0xFF - }); - } - resizeCanvas(canvasSize) { super.resizeCanvas(canvasSize); const canvas = this.canvas; @@ -1654,8 +1641,7 @@ class Vector3DLayerRenderer extends maptalks.renderer.CanvasRenderer { _getLayerOpacity() { const layerOpacity = this.layer.options['opacity']; - // 不在GroupGLLayer中时,MapCanvasRenderer会读取opacity并按照透明度绘制,所以layerOpacity设成1 - return this._isInGroupGLLayer() ? (isNil(layerOpacity) ? 1 : layerOpacity) : 1; + return (isNil(layerOpacity) ? 1 : layerOpacity); } } From 7fa143970360270733c33cb80f730f6017a84fcd Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 11:10:04 +0800 Subject: [PATCH 23/53] merge maptalks-canvas into @maptalks/map and add LayerGLRenderer, MapGLAbstractRenderer TileLayerRenderable for maptalks-gl --- packages/map/src/core/Canvas.ts | 67 +- packages/map/src/geometry/GeoJSON.ts | 23 +- packages/map/src/geometry/Geometry.ts | 8 +- .../map/src/geometry/ext/Geometry.Drag.ts | 58 +- packages/map/src/layer/DrawToolLayer.ts | 4 +- packages/map/src/layer/ImageLayer.ts | 22 + packages/map/src/layer/OverlayLayer.ts | 2 + packages/map/src/layer/index.ts | 4 + packages/map/src/map/Map.ts | 54 +- packages/map/src/map/tool/DistanceTool.ts | 7 +- packages/map/src/map/tool/DrawTool.ts | 2 + .../map/src/renderer/layer/CanvasRenderer.ts | 197 ++- .../src/renderer/layer/ImageGLRenderable.ts | 29 + .../map/src/renderer/layer/LayerGLRenderer.ts | 961 ++++++++++++ .../layer/canvaslayer/CanvasLayerRenderer.ts | 11 - .../tilelayer/TileLayerCanvasRenderer.ts | 1356 ++--------------- .../layer/tilelayer/TileLayerGLRenderer.ts | 53 +- .../layer/tilelayer/TileLayerRendererable.ts | 1351 ++++++++++++++++ .../map/src/renderer/layer/tilelayer/index.ts | 6 +- .../src/renderer/layer/vectorlayer/index.ts | 4 +- .../map/src/renderer/map/MapCanvasRenderer.ts | 548 +++++-- .../src/renderer/map/MapGLAbstractRenderer.ts | 853 +++++++++++ 22 files changed, 4153 insertions(+), 1467 deletions(-) create mode 100644 packages/map/src/renderer/layer/LayerGLRenderer.ts create mode 100644 packages/map/src/renderer/layer/tilelayer/TileLayerRendererable.ts create mode 100644 packages/map/src/renderer/map/MapGLAbstractRenderer.ts diff --git a/packages/map/src/core/Canvas.ts b/packages/map/src/core/Canvas.ts index 376dd9765d..8140392ce8 100644 --- a/packages/map/src/core/Canvas.ts +++ b/packages/map/src/core/Canvas.ts @@ -116,7 +116,7 @@ const defaultChars = getDefaultCharacterSet(); /** * 文本是否全部是默认字符 * @param chars - * @returns + * @returns */ function textIsDefaultChars(chars: string[]) { for (let i = 0, len = chars.length; i < len; i++) { @@ -141,12 +141,12 @@ function reverseChars(chars: string[]) { /** * 字符的旋转角度 - * @param p1 - * @param p2 - * @param char - * @param direction - * @param isDefaultChars - * @returns + * @param p1 + * @param p2 + * @param char + * @param direction + * @param isDefaultChars + * @returns */ function getCharRotation(p1: Point, p2: Point, char: string, direction: string, isDefaultChars: boolean) { const x0 = p1.x, y0 = p1.y; @@ -176,9 +176,9 @@ function getCharRotation(p1: Point, p2: Point, char: string, direction: string, /** * 测量字符的大小 - * @param char - * @param fontSize - * @returns + * @param char + * @param fontSize + * @returns */ function measureCharSize(char: string, fontSize: number) { let w = fontSize, h = fontSize; @@ -192,9 +192,9 @@ function measureCharSize(char: string, fontSize: number) { /** * 计算文本的长度 - * @param textName - * @param fontSize - * @returns + * @param textName + * @param fontSize + * @returns */ function measureTextLength(textName: string, fontSize: number) { let textLen = 0; @@ -220,9 +220,9 @@ function getPercentPoint(segment: segmentType, dis: number) { /** * path 分割 * https://github.com/deyihu/lineseg - * @param points - * @param options - * @returns + * @param points + * @param options + * @returns */ function lineSeg(points: Array, options: any) { options = Object.assign({ segDistance: 1, isGeo: true }, options); @@ -298,8 +298,8 @@ function lineSeg(points: Array, options: any) { /** * 文本路径方向 - * @param path - * @returns + * @param path + * @returns */ function textPathDirection(path: Array) { const len = path.length; @@ -330,10 +330,10 @@ function textPathDirection(path: Array) { /** * 获取文本沿线路径的点 - * @param chunk - * @param chars - * @param fontSize - * @returns + * @param chunk + * @param chars + * @param fontSize + * @returns */ function getTextPath(chunk: Array, chars: string[], fontSize: number, globalCollisonIndex: CollisionIndex) { const total = pathDistance(chunk); @@ -810,12 +810,11 @@ const Canvas = { // @ts-ignore ctx.canvas._drawn = true; try { - // if (isNumber(width) && isNumber(height)) { - // ctx.drawImage(img, x, y, width, height); - // } else { - // ctx.drawImage(img, x, y); - // } - ctx.drawImage(img, 0, 0, (img as any).width, (img as any).height, x, y, width, height) + if (isNumber(width) && isNumber(height)) { + ctx.drawImage(img, x, y, width, height); + } else { + ctx.drawImage(img, x, y); + } } catch (error) { if (console) { console.warn('error when drawing image on canvas:', error); @@ -1123,12 +1122,12 @@ const Canvas = { /** * mock gradient path * 利用颜色插值来模拟渐变的Path - * @param ctx - * @param points - * @param lineDashArray - * @param lineOpacity - * @param isRing - * @returns + * @param ctx + * @param points + * @param lineDashArray + * @param lineOpacity + * @param isRing + * @returns */ _gradientPath(ctx: CanvasRenderingContext2D, points, lineDashArray, lineOpacity, isRing = false) { if (!isNumber(lineOpacity)) { diff --git a/packages/map/src/geometry/GeoJSON.ts b/packages/map/src/geometry/GeoJSON.ts index 4d3f3ffc93..78038aa2bc 100644 --- a/packages/map/src/geometry/GeoJSON.ts +++ b/packages/map/src/geometry/GeoJSON.ts @@ -7,7 +7,8 @@ import { isNumber, extend, getAbsoluteURL, - GUID + GUID, + isFunction } from '../core/util'; import Marker from './Marker'; import LineString from './LineString'; @@ -148,6 +149,7 @@ const GeoJSON = { * Convert one or more GeoJSON objects to geometry * @param {String|Object|Object[]} geoJSON - GeoJSON objects or GeoJSON string * @param {Function} [foreachFn=undefined] - callback function for each geometry + * @param {Function} [filterFn=undefined] - filter function for each geometry * @return {Geometry|Geometry[]} a geometry array when input is a FeatureCollection * @example * var collection = { @@ -187,12 +189,13 @@ const GeoJSON = { * // A geometry array. * const geometries = GeoJSON.toGeometry(collection, geometry => { geometry.config('draggable', true); }); */ - toGeometry: function (geoJSON: any, foreachFn?: any): any { + toGeometry: function (geoJSON: any, foreachFn?: (geo: Geometry) => void, filterFn?: (geo: Geometry) => boolean): any { if (isString(geoJSON)) { geoJSON = parseJSON(geoJSON); } + let resultGeos; if (Array.isArray(geoJSON)) { - const resultGeos = []; + resultGeos = []; for (let i = 0, len = geoJSON.length; i < len; i++) { const geo = GeoJSON._convert(geoJSON[i], foreachFn); if (Array.isArray(geo)) { @@ -201,10 +204,13 @@ const GeoJSON = { resultGeos.push(geo); } } - return resultGeos; } else { - const resultGeo = GeoJSON._convert(geoJSON, foreachFn); - return resultGeo; + resultGeos = GeoJSON._convert(geoJSON, foreachFn); + } + if (filterFn && isFunction(filterFn) && Array.isArray(resultGeos)) { + return resultGeos.filter(filterFn); + } else { + return resultGeos; } }, @@ -215,13 +221,14 @@ const GeoJSON = { * @param {String|Object|Object[]} geoJSON - GeoJSON objects or GeoJSON string * @param {Function} [foreachFn=undefined] - callback function for each geometry * @param {Number} [countPerTime=2000] - Number of graphics converted per time + * @param {Function} [filterFn=undefined] - filter function for each geometry * @return {Promise} * @example * GeoJSON.toGeometryAsync(geoJSON).then(geos=>{ * console.log(geos); * }) * */ - toGeometryAsync(geoJSON: any, foreachFn: any, countPerTime: number = 2000): any { + toGeometryAsync(geoJSON: any, foreachFn?: (geo: Geometry) => void, countPerTime?: number, filterFn?: (geo: Geometry) => boolean): any { if (isString(geoJSON)) { geoJSON = parseJSON(geoJSON); } @@ -235,7 +242,7 @@ const GeoJSON = { const run = () => { const startIndex = (page - 1) * pageSize, endIndex = (page) * pageSize; const fs = features.slice(startIndex, endIndex); - const geos = GeoJSON.toGeometry(fs, foreachFn); + const geos = GeoJSON.toGeometry(fs, foreachFn, filterFn); page++; return geos; }; diff --git a/packages/map/src/geometry/Geometry.ts b/packages/map/src/geometry/Geometry.ts index 74a365b414..0f47e87590 100644 --- a/packages/map/src/geometry/Geometry.ts +++ b/packages/map/src/geometry/Geometry.ts @@ -1912,9 +1912,13 @@ export class Geometry extends JSONAble(Eventable(Handlerable(Class))) { //update coordinates.z setCoordinatesAlt(coordinates, alt); if (layer) { - // const render = layer.getRenderer(); + const renderer = layer.getRenderer(); //for webgllayer,pointlayer/linestringlayer/polygonlayer - this.setCoordinates(coordinates); + if (renderer && (renderer.gl || (renderer as any).device)) { + this.setCoordinates(coordinates); + } else if (renderer) { + this._repaint(); + } } this._clearAltitudeCache(); this.onPositionChanged(); diff --git a/packages/map/src/geometry/ext/Geometry.Drag.ts b/packages/map/src/geometry/ext/Geometry.Drag.ts index df7f3f04a8..5eb6d76390 100644 --- a/packages/map/src/geometry/ext/Geometry.Drag.ts +++ b/packages/map/src/geometry/ext/Geometry.Drag.ts @@ -5,9 +5,8 @@ import Browser from '../../core/Browser'; import Handler from '../../handler/Handler'; import Geometry from '../Geometry'; import DragHandler from '../../handler/Drag'; -// import VectorLayer from '../../layer/VectorLayer'; import { ConnectorLine } from '../ConnectorLine'; -// import { ResourceCache } from '../../renderer/layer/CanvasRenderer'; +import { ResourceCache } from '../../renderer/layer/CanvasRenderer'; import Point from '../../geo/Point'; import Coordinate from '../../geo/Coordinate'; @@ -110,7 +109,7 @@ class GeometryDragHandler extends Handler { if (!target.options.dragShadow) { return; } - // this._prepareDragStageLayer(); + this._prepareDragStageLayer(); if (this._shadow) { this._shadow.remove(); } @@ -143,7 +142,7 @@ class GeometryDragHandler extends Handler { //copy connectors const target = this.target; const shadow = this._shadow; - // const resources = this._dragStageLayer._getRenderer().resources; + const resources = this._dragStageLayer._getRenderer().resources; const shadowConnectors = []; if (ConnectorLine._hasConnectors(target)) { @@ -160,15 +159,15 @@ class GeometryDragHandler extends Handler { conn = new targetConn.constructor(targetConn.getConnectSource(), shadow, connOptions); } shadowConnectors.push(conn); - // if (targetConn.getLayer() && targetConn.getLayer()._getRenderer()) { - // resources.merge(targetConn.getLayer()._getRenderer().resources); - // } + if (targetConn.getLayer() && targetConn.getLayer()._getRenderer()) { + resources.merge(targetConn.getLayer()._getRenderer().resources); + } } } this._shadowConnectors = shadowConnectors; shadowConnectors.push(shadow); - // this._dragStageLayer.bringToFront().addGeometry(shadowConnectors); + this._dragStageLayer.bringToFront().addGeometry(shadowConnectors); } //@internal @@ -179,22 +178,25 @@ class GeometryDragHandler extends Handler { } //@internal - // _prepareDragStageLayer(): void { - // const map = this.target.getMap(), - // layer = this.target.getLayer(); - // this._dragStageLayer = map.getLayer(DRAG_STAGE_LAYER_ID); - // if (!this._dragStageLayer) { - // this._dragStageLayer = new VectorLayer(DRAG_STAGE_LAYER_ID, { - // enableAltitude: layer.options['enableAltitude'], - // altitudeProperty: layer.options['altitudeProperty'] - // }); - // map.addLayer(this._dragStageLayer); - // } - // //copy resources to avoid repeat resource loading. - // const resources = new ResourceCache(); - // resources.merge(layer._getRenderer().resources); - // this._dragStageLayer._getRenderer().resources = resources; - // } + _prepareDragStageLayer(): void { + const map = this.target.getMap(), + layer = this.target.getLayer(); + const prevLayer = map.getLayer(DRAG_STAGE_LAYER_ID); + if (prevLayer) { + prevLayer.remove(); + } + + const layerClazz = layer.constructor; + this._dragStageLayer = new layerClazz(DRAG_STAGE_LAYER_ID, { + enableAltitude: layer.options['enableAltitude'], + altitudeProperty: layer.options['altitudeProperty'] + }); + map.addLayer(this._dragStageLayer); + //copy resources to avoid repeat resource loading. + const resources = new ResourceCache(); + resources.merge(layer._getRenderer().resources); + this._dragStageLayer._getRenderer().resources = resources; + } //@internal _startDrag(param: any): void { @@ -412,10 +414,10 @@ class GeometryDragHandler extends Handler { map.getLayer(DRAG_STAGE_LAYER_ID).removeGeometry(this._shadowConnectors); delete this._shadowConnectors; } - // if (this._dragStageLayer) { - // this._dragStageLayer._getRenderer().resources = new ResourceCache(); - // this._dragStageLayer.remove(); - // } + if (this._dragStageLayer) { + this._dragStageLayer._getRenderer().resources = new ResourceCache(); + this._dragStageLayer.remove(); + } } //find correct coordinate for coordOffset if geometry has altitude diff --git a/packages/map/src/layer/DrawToolLayer.ts b/packages/map/src/layer/DrawToolLayer.ts index 0b1ac6794b..858d038d0b 100644 --- a/packages/map/src/layer/DrawToolLayer.ts +++ b/packages/map/src/layer/DrawToolLayer.ts @@ -33,7 +33,7 @@ export default class DrawToolLayer extends OverlayLayer { * @param options=null - construct options * @param options.style=null - drawToolLayer's style */ - constructor(id: string, geometries?: OverlayLayerOptionsType | Array, options?: DrawToolLayerOptionsType) { + constructor(id: string, geometries?: DrawToolLayerOptionsType | Array, options?: DrawToolLayerOptionsType) { super(id, geometries, options); const depthFunc = this.options.depthFunc || 'always'; options.sceneConfig = { depthFunc }; @@ -104,4 +104,6 @@ DrawToolLayer.mergeOptions(options); type DrawToolLayerOptionsType = OverlayLayerOptionsType & { depthFunc?: string sceneConfig?: any + enableAltitude?: boolean + enableSimplify?: boolean } diff --git a/packages/map/src/layer/ImageLayer.ts b/packages/map/src/layer/ImageLayer.ts index 99c7548311..2ab53d68d3 100644 --- a/packages/map/src/layer/ImageLayer.ts +++ b/packages/map/src/layer/ImageLayer.ts @@ -298,6 +298,12 @@ export class ImageLayerGLRenderer extends ImageGLRenderable(ImageLayerCanvasRend } } } + + //override to set to always drawable + isDrawable() { + return true; + } + //@internal _drawImage(image: LayerImageType, extent: PointExtent, opacity: number) { this.drawGLImage(image, extent.xmin, extent.ymax, extent.getWidth(), extent.getHeight(), 1, opacity); @@ -307,6 +313,22 @@ export class ImageLayerGLRenderer extends ImageGLRenderable(ImageLayerCanvasRend this.createGLContext(); } + resizeCanvas(canvasSize) { + if (!this.canvas) { + return; + } + super.resizeCanvas(canvasSize); + this.resizeGLCanvas(); + } + + clearCanvas() { + if (!this.canvas) { + return; + } + super.clearCanvas(); + this.clearGLCanvas(); + } + retireImage(image: LayerImageType) { const img = image as ImageBitmap; if (img.close) { diff --git a/packages/map/src/layer/OverlayLayer.ts b/packages/map/src/layer/OverlayLayer.ts index 800527c1f9..6faf8c8907 100644 --- a/packages/map/src/layer/OverlayLayer.ts +++ b/packages/map/src/layer/OverlayLayer.ts @@ -884,6 +884,7 @@ OverlayLayer.mergeOptions(options); export default OverlayLayer; export type OverlayLayerOptionsType = LayerOptionsType & { + drawImmediate?: boolean, geometryEvents?: boolean, geometryEventTolerance?: number, style?: any; @@ -894,6 +895,7 @@ export type addGeometryFitViewOptions = { duration?: number, step?: (frame) => void } + export type LayerIdentifyOptionsType = { onlyVisible?: boolean; tolerance?: number; diff --git a/packages/map/src/layer/index.ts b/packages/map/src/layer/index.ts index 0a137d8202..2b92bf23fc 100644 --- a/packages/map/src/layer/index.ts +++ b/packages/map/src/layer/index.ts @@ -6,7 +6,9 @@ import CanvasTileLayer from './tile/CanvasTileLayer'; import ImageLayer from './ImageLayer'; import OverlayLayer from './OverlayLayer'; import DrawToolLayer from './DrawToolLayer'; +import VectorLayer from './VectorLayer'; import CanvasLayer from './CanvasLayer'; +import ParticleLayer from './ParticleLayer'; import TileSystem from './tile/tileinfo/TileSystem'; import TileConfig from './tile/tileinfo/TileConfig'; @@ -19,7 +21,9 @@ export { ImageLayer, OverlayLayer, DrawToolLayer, + VectorLayer, CanvasLayer, + ParticleLayer, TileSystem, TileConfig }; diff --git a/packages/map/src/map/Map.ts b/packages/map/src/map/Map.ts index e6e0b099f4..b6b5fcddc7 100644 --- a/packages/map/src/map/Map.ts +++ b/packages/map/src/map/Map.ts @@ -26,7 +26,7 @@ import Layer from '../layer/Layer'; import Renderable from '../renderer/Renderable'; import SpatialReference, { type SpatialReferenceType } from './spatial-reference/SpatialReference'; import { computeDomPosition, MOUSEMOVE_THROTTLE_TIME } from '../core/util/dom'; -import EPSG9807, { type EPSG9807ProjectionType } from '../geo/projection/Projection.EPSG9807'; +import EPSG9807, { type EPSG9807ProjectionType } from '../geo/projection/Projection.EPSG9807.js'; import { AnimationOptionsType, EasingType } from '../core/Animation'; import { BBOX, bboxInBBOX, getDefaultBBOX, pointsBBOX } from '../core/util/bbox'; import { Attribution } from '../control'; @@ -34,6 +34,7 @@ import { AttributionOptionsType } from '../control/Control.Attribution'; const TEMP_COORD = new Coordinate(0, 0); const TEMP_POINT = new Point(0, 0); +const REDRAW_OPTIONS_PROPERTIES = ['centerCross', 'fog', 'fogColor', 'debugSky']; /** * @property {Object} options - map's options, options must be updated by config method:
map.config('zoomAnimation', false); * @property {Boolean} [options.centerCross=false] - Display a red cross in the center of map @@ -85,7 +86,10 @@ const TEMP_POINT = new Point(0, 0); * @property {Boolean|Object} [options.scaleControl=false] - display the scale control on the map if set to true or a object as the control construct option. * @property {Boolean|Object} [options.overviewControl=false] - display the overview control on the map if set to true or a object as the control construct option. * - * @property {String} [options.renderer=gl] - renderer type. Don't change it if you are not sure about it. About renderer, see [TODO]{@link tutorial.renderer}. + * @property {Boolean} [options.fog=true] - whether to draw fog in far distance. + * @property {Number[]} [options.fogColor=[233, 233, 233]] - color of fog: [r, g, b] + * + * @property {String | String[]} [options.renderer=['canvas', 'gl', 'gpu']] - renderer type. Don't change it if you are not sure about it. About renderer, see [TODO]{@link tutorial.renderer}. * @property {Number} [options.devicePixelRatio=null] - device pixel ratio to override device's default one * @property {Number} [options.heightFactor=1] - the factor for height/altitude calculation,This affects the height calculation of all layers(vectortilelayer/gllayer/threelayer/3dtilelayer) * @property {Boolean} [options.stopRenderOnOffscreen=true] - whether to stop map rendering when container is offscreen @@ -141,7 +145,7 @@ const options: MapOptionsType = { 'checkSize': true, 'checkSizeInterval': 1000, - 'renderer': 'gl', + 'renderer': ['canvas', 'gl', 'gpu'], 'cascadePitches': [10, 60], 'renderable': true, @@ -206,7 +210,7 @@ const options: MapOptionsType = { * subdomains:['a','b','c'] * }), * layers : [ - * new maptalks.PointLayer('v', [new maptalks.Marker([180, 0])]) + * new maptalks.VectorLayer('v', [new maptalks.Marker([180, 0])]) * ] * }); */ @@ -513,6 +517,19 @@ export class Map extends Handlerable(Eventable(Renderable(Class))) { if (!isNil(ref)) { this._updateSpatialReference(ref, null); } + if (this.options.renderer === 'canvas') { + let needUpdate = false; + for (let i = 0, len = REDRAW_OPTIONS_PROPERTIES.length; i < len; i++) { + const key = REDRAW_OPTIONS_PROPERTIES[i]; + if (!isNil(conf[key])) { + needUpdate = true; + break; + } + } + if (!needUpdate) { + return this; + } + } const renderer = this.getRenderer(); if (renderer) { renderer.setToRedraw(); @@ -1449,7 +1466,11 @@ export class Map extends Handlerable(Eventable(Renderable(Class))) { if (removed.length > 0) { const renderer = this.getRenderer(); if (renderer) { - renderer.setToRedraw(); + if (this.options.renderer === 'canvas') { + renderer.setLayerCanvasUpdated(); + } else { + renderer.setToRedraw(); + } } this.once('frameend', () => { removed.forEach(layer => { @@ -2081,10 +2102,19 @@ export class Map extends Handlerable(Eventable(Renderable(Class))) { //@internal _initRenderer() { - const renderer = this.options['renderer']; - const clazz = Map.getRendererClass(renderer) as any; - this._renderer = new clazz(this); - this._renderer.load(); + let renderer = this.options['renderer']; + if (!Array.isArray(renderer)) { + renderer = [renderer]; + } + for (let i = 0; i < renderer.length; i++) { + const clazz = Map.getRendererClass(renderer[i]) as any; + if (clazz) { + this._renderer = new clazz(this); + this._renderer.load(); + break; + } + } + } //@internal @@ -2728,6 +2758,8 @@ Map.mergeOptions(options); export default Map; +export type MapRendererType = 'canvas' | 'gl' | 'gpu'; + export type MapOptionsType = { // center: Array | Coordinate; // zoom: number; @@ -2754,6 +2786,8 @@ export type MapOptionsType = { zoomControl?: boolean; scaleControl?: boolean; overviewControl?: boolean; + fog?: boolean; + fogColor?: any; // fixme 确认类型 devicePixelRatio?: number; heightFactor?: number; originLatitudeForAltitude?: number; @@ -2785,7 +2819,7 @@ export type MapOptionsType = { fixCenterOnResize?: boolean; checkSize?: boolean; checkSizeInterval?: number; - renderer?: 'gl'; + renderer?: MapRendererType | MapRendererType[]; cascadePitches?: Array; renderable?: boolean; clickTimeThreshold?: number; diff --git a/packages/map/src/map/tool/DistanceTool.ts b/packages/map/src/map/tool/DistanceTool.ts index 22c7eb3842..e30621e181 100644 --- a/packages/map/src/map/tool/DistanceTool.ts +++ b/packages/map/src/map/tool/DistanceTool.ts @@ -318,12 +318,15 @@ class DistanceTool extends DrawTool { const layerId = 'distancetool_' + uid; const markerLayerId = 'distancetool_markers_' + uid; const zIndex = this.options.zIndex; + const enableAltitude = this.options.enableAltitude; if (!map.getLayer(layerId)) { this._measureLineLayer = new DrawToolLayer(layerId, { - zIndex + zIndex, + enableAltitude }).addTo(map); this._measureMarkerLayer = new DrawToolLayer(markerLayerId, { - zIndex + zIndex, + enableAltitude }).addTo(map); } else { this._measureLineLayer = map.getLayer(layerId); diff --git a/packages/map/src/map/tool/DrawTool.ts b/packages/map/src/map/tool/DrawTool.ts index 887aa192c7..8a684fccf2 100644 --- a/packages/map/src/map/tool/DrawTool.ts +++ b/packages/map/src/map/tool/DrawTool.ts @@ -877,6 +877,8 @@ class DrawTool extends MapTool { let drawToolLayer: any = this._map.getLayer(drawLayerId); if (!drawToolLayer) { drawToolLayer = new DrawToolLayer(drawLayerId, { + 'enableSimplify': false, + 'enableAltitude': this.options['enableAltitude'], 'zIndex': this.options.zIndex }); this._map.addLayer(drawToolLayer); diff --git a/packages/map/src/renderer/layer/CanvasRenderer.ts b/packages/map/src/renderer/layer/CanvasRenderer.ts index 7918078f6a..e4471dd7f0 100644 --- a/packages/map/src/renderer/layer/CanvasRenderer.ts +++ b/packages/map/src/renderer/layer/CanvasRenderer.ts @@ -1,16 +1,17 @@ /* eslint-disable @typescript-eslint/ban-types */ -import { now, isNil, isArrayHasData, isSVG, IS_NODE, loadImage, hasOwn, getImageBitMap, isImageBitMap } from '../../core/util'; +import { now, isNil, isArrayHasData, isSVG, IS_NODE, loadImage, hasOwn, getImageBitMap, calCanvasSize, isImageBitMap } from '../../core/util'; import Class from '../../core/Class'; import Browser from '../../core/Browser'; import Canvas2D from '../../core/Canvas'; import Actor from '../../core/worker/Actor'; import Point from '../../geo/Point'; import Extent from '../../geo/Extent'; +import { SizeLike } from '../../geo/Size'; import { imageFetchWorkerKey } from '../../core/worker/CoreWorkers'; import { registerWorkerAdapter } from '../../core/worker/Worker'; import { formatResourceUrl } from '../../core/ResourceProxy'; -import { TileRenderingCanvas, ImageType } from '../types'; +import { TileRenderingCanvas, TileRenderingContext, ImageType } from '../types'; const EMPTY_ARRAY = []; class ResourceWorkerConnection extends Actor { @@ -39,8 +40,9 @@ class CanvasRenderer extends Class { layer: any; resources: ResourceCache; - context: any; + context: CanvasRenderingContext2D; canvas: TileRenderingCanvas; + gl: TileRenderingContext; middleWest: Point; canvasExtent2D: Extent; //@internal @@ -63,6 +65,8 @@ class CanvasRenderer extends Class { _loadingResource: boolean; //@internal _renderComplete: boolean; + //@internal + _canvasUpdated: boolean; //@internal _renderZoom: number; @@ -203,7 +207,7 @@ class CanvasRenderer extends Class { const map = this.getMap(); if (map.isInteracting() || map.getRenderer().isViewChanged()) { // don't redraw when map is moving without any pitch - return true; + return !(!map.getPitch() && map.isMoving() && !map.isZooming() && !map.isRotating() && !this.layer.options['forceRenderOnMoving']); } return false; } @@ -236,6 +240,23 @@ class CanvasRenderer extends Class { return this; } + /** + * Mark layer's canvas updated + */ + setCanvasUpdated() { + this._canvasUpdated = true; + return this; + } + + /** + * Only called by map's renderer to check whether the layer's canvas is updated + * @protected + * @return {Boolean} + */ + isCanvasUpdated(): boolean { + return !!this._canvasUpdated; + } + /** * Remove the renderer, will be called when layer is removed */ @@ -277,6 +298,7 @@ class CanvasRenderer extends Class { */ getCanvasImage(): any { const map = this.getMap(); + this._canvasUpdated = false; if (this._renderZoom !== map.getZoom() || !this.canvas || !this._extent2D) { return null; } @@ -300,6 +322,7 @@ class CanvasRenderer extends Class { * Clear canvas */ clear(): void { + this.clearCanvas(); } /** @@ -417,6 +440,118 @@ class CanvasRenderer extends Class { this.middleWest = map._containerPointToPoint(new Point(0, map.height / 2)); } + /** + * Create renderer's Canvas + */ + createCanvas(): void { + if (this.canvas) { + return; + } + const map = this.getMap(); + const size = map.getSize(); + const r = map.getDevicePixelRatio(), + w = Math.round(r * size.width), + h = Math.round(r * size.height); + if (this.layer._canvas) { + const canvas = this.layer._canvas; + canvas.width = w; + canvas.height = h; + if (canvas.style) { + canvas.style.width = size.width + 'px'; + canvas.style.height = size.height + 'px'; + } + this.canvas = this.layer._canvas; + } else { + this.canvas = Canvas2D.createCanvas(w, h, map.CanvasClass); + } + + this.onCanvasCreate(); + + } + + onCanvasCreate(): void { + + } + + //@internal + _canvasContextScale(context: CanvasRenderingContext2D, dpr: number) { + context.scale(dpr, dpr); + context.dpr = dpr; + return this; + } + + createContext(): void { + //Be compatible with layer renderers that overrides create canvas and create gl/context + if (this.gl && this.gl.canvas === this.canvas || this.context) { + return; + } + this.context = Canvas2D.getCanvas2DContext(this.canvas); + if (!this.context) { + return; + } + this.context.dpr = 1; + if (this.layer.options['globalCompositeOperation']) { + this.context.globalCompositeOperation = this.layer.options['globalCompositeOperation']; + } + const dpr = this.getMap().getDevicePixelRatio(); + if (dpr !== 1) { + this._canvasContextScale(this.context, dpr); + } + } + + resetCanvasTransform(): void { + if (!this.context) { + return; + } + const dpr = this.getMap().getDevicePixelRatio(); + this.context.setTransform(dpr, 0, 0, dpr, 0, 0); + } + + /** + * Resize the canvas + * @param canvasSize the size resizing to + */ + resizeCanvas(canvasSize?: SizeLike): void { + const canvas = this.canvas; + if (!canvas) { + return; + } + const size = canvasSize || this.getMap().getSize(); + const r = this.getMap().getDevicePixelRatio(); + const { width, height, cssWidth, cssHeight } = calCanvasSize(size, r); + // width/height不变并不意味着 css width/height 不变 + if (this.layer._canvas && (canvas.style.width !== cssWidth || canvas.style.height !== cssHeight)) { + canvas.style.width = cssWidth; + canvas.style.height = cssHeight; + } + + if (canvas.width === width && canvas.height === height) { + return; + } + //retina support + canvas.height = height; + canvas.width = width; + if (this.context) { + this.context.dpr = 1; + } + if (r !== 1 && this.context) { + this._canvasContextScale(this.context, r); + } + } + + /** + * Clear the canvas to blank + */ + clearCanvas(): void { + if (!this.context || !this.getMap()) { + return; + } + //fix #1597 + const r = this.getMap().getDevicePixelRatio(); + const rScale = 1 / r; + const w = this.canvas.width * rScale, h = this.canvas.height * rScale; + Canvas2D.clearRect(this.context, 0, 0, Math.max(w, this.canvas.width), Math.max(h, this.canvas.height)); + } /** * @english @@ -427,18 +562,35 @@ class CanvasRenderer extends Class { */ prepareCanvas(): any { if (!this.canvas) { - const map = this.getMap(); - this.canvas = map.getRenderer().canvas; - this.context = map.getRenderer().context; - this.initContext(); + this.createCanvas(); + this.createContext(); + this.layer.onCanvasCreate(); + /** + * canvascreate event, fired when canvas created. + * + * @event Layer#canvascreate + * @type {Object} + * @property {String} type - canvascreate + * @property {Layer} target - layer + * @property {CanvasRenderingContext2D} context - canvas's context + * @property {WebGLRenderingContext2D} gl - canvas's webgl context + */ + this.layer.fire('canvascreate', { + 'context': this.context, + 'gl': this.gl + }); + } else { + this.resetCanvasTransform(); + this.clearCanvas(); + this.resizeCanvas(); } - this.prepareContext(); delete this._maskExtent; const mask = this.layer.getMask(); // this.context may be not available if (!mask) { this.layer.fire('renderstart', { - 'context': this.context + 'context': this.context, + 'gl': this.gl }); return null; } @@ -446,7 +598,8 @@ class CanvasRenderer extends Class { //fix vt _extent2D is null if (maskExtent2D && this._extent2D && !maskExtent2D.intersects(this._extent2D)) { this.layer.fire('renderstart', { - 'context': this.context + 'context': this.context, + 'gl': this.gl }); return maskExtent2D; } @@ -460,23 +613,12 @@ class CanvasRenderer extends Class { * @property {CanvasRenderingContext2D} context - canvas's context */ this.layer.fire('renderstart', { - 'context': this.context + 'context': this.context, + 'gl': this.gl }); return maskExtent2D; } - initContext() { - - } - - prepareContext() { - - } - - clearContext() { - - } - clipCanvas(context: CanvasRenderingContext2D) { const mask = this.layer.getMask(); if (!mask) { @@ -493,7 +635,7 @@ class CanvasRenderer extends Class { const dpr = map.getDevicePixelRatio(); if (dpr !== 1) { context.save(); - // this._canvasContextScale(context, dpr); + this._canvasContextScale(context, dpr); } // Handle MultiPolygon if (mask.getGeometries) { @@ -554,8 +696,10 @@ class CanvasRenderer extends Class { * @property {CanvasRenderingContext2D} context - canvas's context */ this.layer.fire('renderend', { - 'context': this.context + 'context': this.context, + 'gl': this.gl }); + this.setCanvasUpdated(); } } @@ -627,6 +771,7 @@ class CanvasRenderer extends Class { */ onResize(param: any) { delete this._extent2D; + this.resizeCanvas(); this.setToRedraw(); } diff --git a/packages/map/src/renderer/layer/ImageGLRenderable.ts b/packages/map/src/renderer/layer/ImageGLRenderable.ts index f8dcd4e8f0..b4b12eeb82 100644 --- a/packages/map/src/renderer/layer/ImageGLRenderable.ts +++ b/packages/map/src/renderer/layer/ImageGLRenderable.ts @@ -329,6 +329,35 @@ const ImageGLRenderable = function (Base: T) { gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true); } + /** + * Resize GL canvas with renderer's 2D canvas + */ + resizeGLCanvas(): void { + if (this.gl) { + this.gl.viewport(0, 0, this.canvas.width, this.canvas.height); + } + if (!this.canvas2) { + return; + } + if (this.canvas2.width !== this.canvas.width || this.canvas2.height !== this.canvas.height) { + this.canvas2.width = this.canvas.width; + this.canvas2.height = this.canvas.height; + } + } + + /** + * Clear gl canvas + */ + clearGLCanvas(): void { + if (this.gl) { + this.gl.clearStencil(0xFF); + this.gl.clear(this.gl.COLOR_BUFFER_BIT | this.gl.STENCIL_BUFFER_BIT); + } + if (!this.gl.wrap) { + this.gl.clear(this.gl.DEPTH_BUFFER_BIT); + } + } + disposeImage(image: TileImageType): void { if (!image) { return; diff --git a/packages/map/src/renderer/layer/LayerGLRenderer.ts b/packages/map/src/renderer/layer/LayerGLRenderer.ts new file mode 100644 index 0000000000..d967cdff58 --- /dev/null +++ b/packages/map/src/renderer/layer/LayerGLRenderer.ts @@ -0,0 +1,961 @@ +/* eslint-disable @typescript-eslint/ban-types */ + +import { now, isNil, isArrayHasData, isSVG, IS_NODE, loadImage, hasOwn, getImageBitMap, isImageBitMap } from '../../core/util'; +import Class from '../../core/Class'; +import Browser from '../../core/Browser'; +import Canvas2D from '../../core/Canvas'; +import Actor from '../../core/worker/Actor'; +import Point from '../../geo/Point'; +import Extent from '../../geo/Extent'; +import { imageFetchWorkerKey } from '../../core/worker/CoreWorkers'; +import { registerWorkerAdapter } from '../../core/worker/Worker'; +import { formatResourceUrl } from '../../core/ResourceProxy'; +import { TileRenderingCanvas, ImageType } from '../types'; + +const EMPTY_ARRAY = []; +class ResourceWorkerConnection extends Actor { + constructor() { + super(imageFetchWorkerKey); + } + + fetchImage(url: string, cb: Function) { + const data = { + url + }; + this.send(data, EMPTY_ARRAY, cb); + } +} + +/** + * 在 HTMLCanvasElement 上渲染图层的基类 + * @english + * Base Class to render layer on HTMLCanvasElement + * @abstract + * @protected + * @memberOf renderer + * @extends Class + */ +class LayerGLRenderer extends Class { + layer: any; + resources: ResourceCache; + + context: any; + canvas: TileRenderingCanvas; + middleWest: Point; + canvasExtent2D: Extent; + //@internal + _extent2D: Extent; + //@internal + _maskExtent: Extent; + + //@internal + _painted: boolean; + //@internal + _drawTime: number; + //@internal + _frameTime: number; + //@internal + _resWorkerConn: ResourceWorkerConnection; + + //@internal + _toRedraw: boolean; + //@internal + _loadingResource: boolean; + //@internal + _renderComplete: boolean; + + //@internal + _renderZoom: number; + //@internal + _errorThrown: boolean; + //@internal + __zoomTransformMatrix: number[]; + + drawOnInteracting?(...args: any[]): void; + checkResources?(): any[]; + getImageData?(): ImageData; + draw?(...args: any[]): void; + + /** + * @param {Layer} layer the layer to render + */ + constructor(layer: any) { + super(); + this.layer = layer; + this._painted = false; + this._drawTime = 0; + if (Browser.decodeImageInWorker && !Browser.safari && !Browser.iosWeixin) { + this._resWorkerConn = new ResourceWorkerConnection(); + } + this.setToRedraw(); + } + + + /** + * Render the layer. + * Call checkResources + */ + render(framestamp?: number): void { + this.prepareRender(); + if (!this.getMap() || !this.layer.isVisible()) { + return; + } + if (!this.resources) { + /* eslint-disable no-use-before-define */ + this.resources = new ResourceCache(); + /* eslint-enable no-use-before-define */ + } + this.checkAndDraw(this._tryToDraw, framestamp); + this._frameTime = framestamp; + } + + getFrameTimestamp(): number { + return this._frameTime || 0; + } + + checkAndDraw(drawFn, ...args) { + this._toRedraw = false; + if (this.checkResources) { + const resources = this.checkResources(); + if (resources.length > 0) { + this._loadingResource = true; + this.loadResources(resources).then(() => { + this._loadingResource = false; + if (this.layer) { + /** + * resourceload event, fired when external resources of the layer complete loading. + * + * @event Layer#resourceload + * @type {Object} + * @property {String} type - resourceload + * @property {Layer} target - layer + */ + this.layer.fire('resourceload'); + const map = this.layer.getMap(); + this.setToRedraw(); + map.getRenderer().callInNextFrame(() => { + // sometimes renderer still fails to fetch loaded images, an additional frame will solved it + this.setToRedraw(); + }); + } + }); + } else { + drawFn.call(this, ...args); + } + } else { + drawFn.call(this, ...args); + } + } + + /** + * Check if has any external resources to load + * If yes, load the resources before calling draw method + * @abstract + * @method checkResources + * @instance + * @returns {Array[]} an array of resource arrays [ [url1, width, height], [url2, width, height], [url3, width, height] .. ] + * @memberOf renderer.LayerGLRenderer + */ + + /** + * a required abstract method to implement + * draw the layer when map is not interacting + * @abstract + * @instance + * @method draw + * @memberOf renderer.LayerGLRenderer + */ + + /** + * an optional abstract method to implement + * draw the layer when map is interacting (moving/zooming/dragrotating) + * @abstract + * @instance + * @method drawOnInteracting + * @param {Object} eventParam event parameters + * @memberOf renderer.LayerGLRenderer + */ + + /** + * @private + */ + testIfNeedRedraw(): boolean { + const map = this.getMap(); + if (this._loadingResource) { + return false; + } + if (this._toRedraw) { + return true; + } + if (map.isInteracting() && !this.drawOnInteracting) { + return false; + } + if (this.needToRedraw()) { + return true; + } + return false; + } + + /** + * Ask whether the layer renderer needs to redraw + */ + needToRedraw(): boolean { + const map = this.getMap(); + if (map.isInteracting() || map.getRenderer().isViewChanged()) { + // don't redraw when map is moving without any pitch + return true; + } + return false; + } + + /** + * A callback for overriding when drawOnInteracting is skipped due to low fps + */ + onSkipDrawOnInteracting(): void { } + + isLoadingResource(): boolean { + return this._loadingResource; + } + + isRenderComplete(): boolean { + return !!this._renderComplete; + } + + /** + * Whether must call render instead of drawOnInteracting when map is interacting + */ + mustRenderOnInteracting(): boolean { + return !this._painted; + } + + /** + * Set to redraw, ask map to call draw/drawOnInteracting to redraw the layer + */ + setToRedraw() { + this._toRedraw = true; + return this; + } + + /** + * Remove the renderer, will be called when layer is removed + */ + remove(): void { + this.onRemove(); + delete this._loadingResource; + delete this.middleWest; + delete this.canvas; + delete this.context; + delete this.canvasExtent2D; + delete this._extent2D; + if (this.resources) { + this.resources.remove(); + } + delete this.resources; + if (this._resWorkerConn) { + this._resWorkerConn.remove(); + delete this._resWorkerConn; + } + delete this.layer; + } + + onRemove(): void { } + + onAdd(): void { } + + /** + * Get map + */ + getMap(): any { + if (!this.layer) { + return null; + } + return this.layer.getMap(); + } + + /** + * Get renderer's Canvas image object + */ + getCanvasImage(): any { + const map = this.getMap(); + if (this._renderZoom !== map.getZoom() || !this.canvas || !this._extent2D) { + return null; + } + if (this.isBlank()) { + return null; + } + if (this.layer.isEmpty && this.layer.isEmpty()) { + return null; + } + // size = this._extent2D.getSize(), + const containerPoint = map._pointToContainerPoint(this.middleWest)._add(0, -map.height / 2); + return { + 'image': this.canvas, + 'layer': this.layer, + 'point': containerPoint/* , + 'size': size */ + }; + } + + /** + * Clear canvas + */ + clear(): void { + } + + /** + * A method to help improve performance. + * If you are sure that layer's canvas is blank, returns true to save unnecessary layer works of maps. + */ + isBlank(): boolean { + return !this._painted; + } + + /** + * Show the layer + */ + show(): void { + this.setToRedraw(); + } + + /** + * Hide the layer + */ + hide(): void { + this.clear(); + this.setToRedraw(); + } + + /** + * Set z-index of layer + */ + setZIndex(_z?: number): void { + this.setToRedraw(); + } + + /** + * Detect if there is anything painted on the given point + * @param point containerPoint + */ + hitDetect(point: Point): boolean { + if (!this.context || (this.layer.isEmpty && this.layer.isEmpty()) || this.isBlank() || this._errorThrown || (this.layer.isVisible && !this.layer.isVisible())) { + return false; + } + const map = this.getMap(); + const r = map.getDevicePixelRatio(); + const size = map.getSize(); + if (point.x < 0 || point.x > size['width'] * r || point.y < 0 || point.y > size['height'] * r) { + return false; + } + const imageData = this.getImageData && this.getImageData(); + if (imageData) { + const x = Math.round(r * point.x), y = Math.round(r * point.y); + const idx = y * imageData.width * 4 + x * 4; + //索引下标从0开始需要-1 + return imageData.data[idx + 3] > 0; + } + try { + const imgData = this.context.getImageData(r * point.x, r * point.y, 1, 1).data; + if (imgData[3] > 0) { + return true; + } + } catch (error) { + if (!this._errorThrown) { + if (console) { + console.warn('hit detect failed with tainted canvas, some geometries have external resources in another domain:\n', error); + } + this._errorThrown = true; + } + //usually a CORS error will be thrown if the canvas uses resources from other domain. + //this may happen when a geometry is filled with pattern file. + return false; + } + return false; + + } + + /** + * loadResource from resourceUrls + * @param {String[]} resourceUrls - Array of urls to load + * @returns {Promise[]} + */ + loadResources(resourceUrls: string[][]): Promise { + if (!this.resources) { + /* eslint-disable no-use-before-define */ + this.resources = new ResourceCache(); + /* eslint-enable no-use-before-define */ + } + const resources = this.resources, + promises = []; + if (isArrayHasData(resourceUrls)) { + const cache = {}; + for (let i = resourceUrls.length - 1; i >= 0; i--) { + const url = resourceUrls[i]; + if (!url || !url.length || cache[url.join('-')]) { + continue; + } + cache[url.join('-')] = 1; + if (!resources.isResourceLoaded(url, true)) { + //closure it to preserve url's value + promises.push(new Promise(this._promiseResource(url))); + } + } + } + return Promise.all(promises); + } + + /** + * Prepare rendering + * Set necessary properties, like this._renderZoom/ this.canvasExtent2D, this.middleWest + * @private + */ + prepareRender(): void { + delete this._renderComplete; + const map = this.getMap(); + this._renderZoom = map.getZoom(); + this.canvasExtent2D = this._extent2D = map.get2DExtent(); + //change from northWest to middleWest, because northwest's point <=> containerPoint changes when pitch >= 72 + this.middleWest = map._containerPointToPoint(new Point(0, map.height / 2)); + } + + + /** + * @english + * Prepare the canvas for rendering.
+ * 1. Clear the canvas to blank.
+ * 2. Clip the canvas by mask if there is any and return the mask's extent + * @return {PointExtent} mask's extent of current zoom's 2d point. + */ + prepareCanvas(): any { + if (!this.canvas) { + const map = this.getMap(); + this.canvas = map.getRenderer().canvas; + this.context = map.getRenderer().context; + this.initContext(); + } + this.prepareContext(); + delete this._maskExtent; + const mask = this.layer.getMask(); + // this.context may be not available + if (!mask) { + this.layer.fire('renderstart', { + 'context': this.context + }); + return null; + } + const maskExtent2D = this._maskExtent = mask._getMaskPainter().get2DExtent(); + //fix vt _extent2D is null + if (maskExtent2D && this._extent2D && !maskExtent2D.intersects(this._extent2D)) { + this.layer.fire('renderstart', { + 'context': this.context + }); + return maskExtent2D; + } + /** + * renderstart event, fired when layer starts to render. + * + * @event Layer#renderstart + * @type {Object} + * @property {String} type - renderstart + * @property {Layer} target - layer + * @property {CanvasRenderingContext2D} context - canvas's context + */ + this.layer.fire('renderstart', { + 'context': this.context + }); + return maskExtent2D; + } + + initContext() { + + } + + prepareContext() { + + } + + clearContext() { + + } + + clipCanvas(context: CanvasRenderingContext2D) { + const mask = this.layer.getMask(); + if (!mask) { + return false; + } + if (!this.layer.options.maskClip) { + return false; + } + const old = this.middleWest; + const map = this.getMap(); + //when clipping, layer's middleWest needs to be reset for mask's containerPoint conversion + this.middleWest = map._containerPointToPoint(new Point(0, map.height / 2)); + context.save(); + const dpr = map.getDevicePixelRatio(); + if (dpr !== 1) { + context.save(); + // this._canvasContextScale(context, dpr); + } + // Handle MultiPolygon + if (mask.getGeometries) { + context.isMultiClip = true; + const masks = mask.getGeometries() || []; + context.beginPath(); + masks.forEach(_mask => { + const painter = _mask._getMaskPainter(); + painter.paint(null, context); + }); + context.stroke(); + context.isMultiClip = false; + } else { + context.isClip = true; + context.beginPath(); + const painter = mask._getMaskPainter(); + painter.paint(null, context); + context.isClip = false; + } + if (dpr !== 1) { + context.restore(); + } + try { + context.clip('evenodd'); + } catch (error) { + console.error(error); + } + this.middleWest = old; + return true; + } + + /** + * Get renderer's current view extent in 2d point + * @return {Object} view.extent, view.maskExtent, view.zoom, view.middleWest + */ + getViewExtent() { + return { + 'extent': this._extent2D, + 'maskExtent': this._maskExtent, + 'zoom': this._renderZoom, + 'middleWest': this.middleWest + }; + } + + /** + * call when rendering completes, this will fire necessary events and call setCanvasUpdated + */ + completeRender(): void { + if (this.getMap()) { + this._renderComplete = true; + /** + * renderend event, fired when layer ends rendering. + * + * @event Layer#renderend + * @type {Object} + * @property {String} type - renderend + * @property {Layer} target - layer + * @property {CanvasRenderingContext2D} context - canvas's context + */ + this.layer.fire('renderend', { + 'context': this.context + }); + } + } + + /** + * Get renderer's event map registered on the map + * @return {Object} events + */ + getEvents() { + return { + '_zoomstart': this.onZoomStart, + '_zooming': this.onZooming, + '_zoomend': this.onZoomEnd, + '_resize': this.onResize, + '_movestart': this.onMoveStart, + '_moving': this.onMoving, + '_moveend': this.onMoveEnd, + '_dragrotatestart': this.onDragRotateStart, + '_dragrotating': this.onDragRotating, + '_dragrotateend': this.onDragRotateEnd, + '_spatialreferencechange': this.onSpatialReferenceChange + }; + } + + /* eslint-disable @typescript-eslint/no-unused-vars */ + /** + * onZoomStart + * @param {Object} param event parameters + */ + onZoomStart(param: any): void { + } + + /** + * onZoomEnd + * @param {Object} param event parameters + */ + onZoomEnd(param: any): void { + this.setToRedraw(); + } + + /** + * onZooming + * @param {Object} param event parameters + */ + onZooming(param: any) { } + + /** + * onMoveStart + * @param {Object} param event parameters + */ + onMoveStart(param: any) { } + + /** + * onMoving + * @param {Object} param event parameters + */ + onMoving(param: any) { } + + /** + * onMoveEnd + * @param {Object} param event parameters + */ + onMoveEnd(param: any) { + this.setToRedraw(); + } + + /** + * onResize + * @param {Object} param event parameters + */ + onResize(param: any) { + delete this._extent2D; + this.setToRedraw(); + } + + /** + * onDragRotateStart + * @param {Object} param event parameters + */ + onDragRotateStart(param: any) { } + + /** + * onDragRotating + * @param {Object} param event parameters + */ + onDragRotating(param: any) { } + + /** + * onDragRotateEnd + * @param {Object} param event parameters + */ + onDragRotateEnd(param: any) { + this.setToRedraw(); + } + + /** + * onSpatialReferenceChange + * @param {Object} param event parameters + */ + onSpatialReferenceChange(param: any) { + } + + /* eslint-disable @typescript-eslint/no-unused-vars */ + + /** + * Get ellapsed time of previous drawing + * @return {Number} + */ + getDrawTime() { + return this._drawTime; + } + + //@internal + _tryToDraw(framestamp) { + this._toRedraw = false; + if (!this.canvas && this.layer.isEmpty && this.layer.isEmpty()) { + this._renderComplete = true; + // not to create canvas when layer is empty + return; + } + this._drawAndRecord(framestamp); + } + + //@internal + _drawAndRecord(framestamp: number) { + if (!this.getMap()) { + return; + } + const painted = this._painted; + this._painted = true; + let t = now(); + this.draw(framestamp); + t = now() - t; + //reduce some time in the first draw + this._drawTime = painted ? t : t / 2; + if (painted && this.layer && this.layer.options['logDrawTime']) { + console.log(this.layer.getId(), 'frameTimeStamp:', framestamp, 'drawTime:', this._drawTime); + } + } + + //@internal + _promiseResource(url) { + const layer = this.layer; + const resources = this.resources; + const crossOrigin = layer.options['crossOrigin']; + const renderer = layer.options['renderer'] || ''; + return (resolve) => { + if (resources.isResourceLoaded(url, true)) { + resolve(url); + return; + } + const imageURL = formatResourceUrl(url[0]); + + if (isImageBitMap(imageURL)) { + createImageBitmap(imageURL).then(newbitmap => { + //新的数据为layer提供服务 + this._cacheResource(url, newbitmap); + resolve(url); + }).catch(err => { + console.error(err); + resolve(url); + }); + return; + } + const fetchInWorker = !isSVG(url[0]) && this._resWorkerConn && (layer.options['renderer'] !== 'canvas' || layer.options['decodeImageInWorker']); + if (fetchInWorker) { + // const uri = getAbsoluteURL(url[0]); + this._resWorkerConn.fetchImage(imageURL, (err, data) => { + if (err) { + if (err && typeof console !== 'undefined') { + console.warn(err); + } + resolve(url); + return; + } + getImageBitMap(data, bitmap => { + this._cacheResource(url, bitmap); + resolve(url); + }); + }); + } else { + const img = new Image(); + if (!isNil(crossOrigin)) { + img['crossOrigin'] = crossOrigin; + } else if (renderer !== 'canvas') { + img['crossOrigin'] = ''; + } + if (isSVG(url[0]) && !IS_NODE) { + //amplify the svg image to reduce loading. + if (url[1]) { url[1] *= 2; } + if (url[2]) { url[2] *= 2; } + } + img.onload = () => { + this._cacheResource(url, img); + resolve(url); + }; + img.onabort = function (err) { + if (console) { console.warn('image loading aborted: ' + url[0]); } + if (err) { + if (console) { console.warn(err); } + } + resolve(url); + }; + img.onerror = function (err) { + // if (console) { console.warn('image loading failed: ' + url[0]); } + if (err && typeof console !== 'undefined') { + console.warn(err); + } + resources.markErrorResource(url); + resolve(url); + }; + loadImage(img, [imageURL]); + } + }; + + } + + //@internal + _cacheResource(url: [string, number | string, string | number], img: ImageType) { + if (!this.layer || !this.resources) { + return; + } + let w = url[1], h = url[2]; + if (this.layer.options['cacheSvgOnCanvas'] && isSVG(url[0]) === 1 && (Browser.edge || Browser.ie)) { + //opacity of svg img painted on canvas is always 1, so we paint svg on a canvas at first. + if (isNil(w)) { + w = img.width || this.layer.options['defaultIconSize'][0]; + } + if (isNil(h)) { + h = img.height || this.layer.options['defaultIconSize'][1]; + } + const canvas = Canvas2D.createCanvas(w as number, h as number); + Canvas2D.image(canvas.getContext('2d'), img, 0, 0, w as number, h as number); + img = canvas; + } + this.resources.addResource(url, img); + } +} + +export default LayerGLRenderer; + +export type ResourceUrl = string | string[] + +export class ResourceCache { + resources: any; + + //@internal + _errors: any; + + constructor() { + this.resources = {}; + this._errors = {}; + } + + addResource(url: [string, number | string, number | string], img) { + this.resources[url[0]] = { + image: img, + width: +url[1], + height: +url[2], + refCnt: 0 + }; + if (img && img.width && img.height && !img.close && Browser.imageBitMap && !Browser.safari && !Browser.iosWeixin) { + if (img.src && isSVG(img.src)) { + return; + } + createImageBitmap(img).then(imageBitmap => { + if (!this.resources[url[0]]) { + //removed + return; + } + this.resources[url[0]].image = imageBitmap; + }); + } + } + + isResourceLoaded(url: ResourceUrl, checkSVG?: boolean) { + if (!url) { + return false; + } + const imgUrl = this._getImgUrl(url); + if (this._errors[imgUrl]) { + return true; + } + const img = this.resources[imgUrl]; + if (!img) { + return false; + } + if (checkSVG && isSVG(url[0]) && (+url[1] > img.width || +url[2] > img.height)) { + return false; + } + return true; + } + + login(url: string) { + const res = this.resources[url]; + if (res) { + res.refCnt++; + } + } + + logout(url: string) { + const res = this.resources[url]; + if (res && res.refCnt-- <= 0) { + if (res.image && res.image.close) { + res.image.close(); + } + delete this.resources[url]; + } + } + + getImage(url: ResourceUrl) { + const imgUrl = this._getImgUrl(url); + if (!this.isResourceLoaded(url) || this._errors[imgUrl]) { + return null; + } + return this.resources[imgUrl].image; + } + + markErrorResource(url: ResourceUrl) { + this._errors[this._getImgUrl(url)] = 1; + } + + merge(res: any) { + if (!res) { + return this; + } + for (const p in res.resources) { + const img = res.resources[p]; + this.addResource([p, img.width, img.height], img.image); + } + return this; + } + + forEach(fn: Function) { + if (!this.resources) { + return this; + } + for (const p in this.resources) { + if (hasOwn(this.resources, p)) { + fn(p, this.resources[p]); + } + } + return this; + } + + //@internal + _getImgUrl(url: ResourceUrl) { + if (!Array.isArray(url)) { + return url; + } + return url[0]; + } + + remove() { + for (const p in this.resources) { + const res = this.resources[p]; + if (res && res.image && res.image.close) { + // close bitmap + res.image.close(); + } + } + this.resources = {}; + } +} + +const workerSource = ` +function (exports) { + exports.onmessage = function (msg, postResponse) { + var url = msg.data.url; + var fetchOptions = msg.data.fetchOptions; + requestImageOffscreen(url, function (err, data) { + var buffers = []; + if (data && data.data) { + buffers.push(data.data); + } + postResponse(err, data, buffers); + }, fetchOptions); + }; + + function requestImageOffscreen(url, cb, fetchOptions) { + fetch(url, fetchOptions ? fetchOptions : {}) + .then(response => response.arrayBuffer()) + .then(arrayBuffer => { + const blob=new Blob([arrayBuffer]); + return createImageBitmap(blob); + }) + .then(bitmap => { + cb(null, {data:bitmap}); + }).catch(err => { + console.warn('error when loading tile:', url); + console.warn(err); + cb(err); + }); + } +}`; + +function registerWorkerSource() { + if (!Browser.decodeImageInWorker) { + return; + } + registerWorkerAdapter(imageFetchWorkerKey, function () { return workerSource; }); +} + +registerWorkerSource(); diff --git a/packages/map/src/renderer/layer/canvaslayer/CanvasLayerRenderer.ts b/packages/map/src/renderer/layer/canvaslayer/CanvasLayerRenderer.ts index e28a92d51e..c0cfd4563e 100644 --- a/packages/map/src/renderer/layer/canvaslayer/CanvasLayerRenderer.ts +++ b/packages/map/src/renderer/layer/canvaslayer/CanvasLayerRenderer.ts @@ -45,17 +45,6 @@ export default class CanvasLayerRenderer extends CanvasRenderer { this._drawLayerOnInteracting(...args); } - clearCanvas() { - if (!this.context || !this.getMap()) { - return; - } - //fix #1597 - const r = this.getMap().getDevicePixelRatio(); - const rScale = 1 / r; - const w = this.canvas.width * rScale, h = this.canvas.height * rScale; - Canvas.clearRect(this.context, 0, 0, Math.max(w, this.canvas.width), Math.max(h, this.canvas.height)); - } - getCanvasImage() { const canvasImg = super.getCanvasImage(); if (canvasImg && canvasImg.image && this.layer.options['doubleBuffer']) { diff --git a/packages/map/src/renderer/layer/tilelayer/TileLayerCanvasRenderer.ts b/packages/map/src/renderer/layer/tilelayer/TileLayerCanvasRenderer.ts index 692f002d19..984f1b9036 100644 --- a/packages/map/src/renderer/layer/tilelayer/TileLayerCanvasRenderer.ts +++ b/packages/map/src/renderer/layer/tilelayer/TileLayerCanvasRenderer.ts @@ -1,109 +1,15 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { - isNil, - loadImage, - emptyImageUrl, - now, - isFunction, - getImageBitMap, - isString, - getAbsoluteURL, - pushIn -} from '../../../core/util'; -import Browser from '../../../core/Browser'; +import Canvas2D from '../../../core/Canvas'; import { default as TileLayer } from '../../../layer/tile/TileLayer'; import WMSTileLayer from '../../../layer/tile/WMSTileLayer'; import CanvasRenderer from '../CanvasRenderer'; import Point from '../../../geo/Point'; -import Extent from '../../../geo/Extent'; -import LRUCache from '../../../core/util/LRUCache'; -import Canvas from '../../../core/Canvas'; -import Actor from '../../../core/worker/Actor'; -import { imageFetchWorkerKey } from '../../../core/worker/CoreWorkers'; -import { TileImageBuffer, TileImageTexture } from '../../types'; -import type { WithUndef } from '../../../types/typings'; +import TileLayerRenderable, { RenderContext, Tile } from './TileLayerRendererable'; +const TILE_POINT = new Point(0, 0); +const TEMP_POINT = new Point(0, 0); -const TEMP_POINT1 = new Point(0, 0); -const TEMP_POINT2 = new Point(0, 0); - -const EMPTY_ARRAY = []; -class TileWorkerConnection extends Actor { - constructor() { - super(imageFetchWorkerKey); - } - - checkUrl(url: string) { - if (!url || !isString(url)) { - return url; - } - //The URL is processed. Here, only the relative protocol is processed - return getAbsoluteURL(url); - - } - - // eslint-disable-next-line @typescript-eslint/ban-types - fetchImage(url: string, workerId: number, cb: Function, fetchOptions: any) { - url = this.checkUrl(url); - const data = { - url, - fetchOptions - }; - this.send(data, EMPTY_ARRAY, cb, workerId); - } -} - -/** - * 瓦片图层的渲染器抽象类,实现瓦片的遍历功能,可以继承并实现 drawTile 等方法来实现瓦片图层渲染 - * - * @english - * Abstract renderer class for TileLayers - * @class - * @protected - * @group renderer - * @extends {renderer.CanvasRenderer} - */ -class TileLayerCanvasRenderer extends CanvasRenderer { - tilesInView: TilesInViewType; - tilesLoading: { [key: string]: any }; - //@internal - _parentTiles: any[]; - //@internal - _childTiles: any[]; - //@internal - _tileZoom: number; - //@internal - _tileQueue: { - tileInfo: any; - tileData: any; - }[]; - //@internal - _tileQueueIds: Set; - tileCache: typeof LRUCache; - //@internal - _compareTiles: any; - //@internal - _tileImageWorkerConn: TileWorkerConnection; - //@internal - _renderTimestamp: number; - //@internal - _frameTiles: { - empty: boolean; - timestamp: number; - }; - - //@internal - _terrainHelper: TerrainHelper; - //@internal - _tilePlaceHolder: any; - //@internal - _frameTileGrids: TileGrids; - - drawingCurrentTiles: WithUndef; - drawingChildTiles: WithUndef; - drawingParentTiles: WithUndef; - avgMinAltitude: number; - avgMaxAltitude: number; +export default class TileLayerCanvasRenderer extends TileLayerRenderable(CanvasRenderer) { /** * @@ -111,339 +17,9 @@ class TileLayerCanvasRenderer extends CanvasRenderer { */ constructor(layer: TileLayer) { super(layer); - this.tilesInView = {}; - this.tilesLoading = {}; - this._parentTiles = []; - this._childTiles = []; - this._tileQueue = []; - this._tileQueueIds = new Set(); - const tileSize = layer.getTileSize().width; - this.tileCache = new LRUCache(layer.options['maxCacheSize'] * tileSize / 512 * tileSize / 512, (tile: Tile) => { - this.deleteTile(tile); - }); - if (Browser.decodeImageInWorker && this.layer.options['decodeImageInWorker'] && (layer.options['renderer'] === 'gl' || !Browser.safari && !Browser.iosWeixin)) { - this._tileImageWorkerConn = new TileWorkerConnection(); - } - this._compareTiles = compareTiles.bind(this); - } - - getCurrentTileZoom(): number { - return this._tileZoom; - } - - draw(timestamp: number, context): number { - const map = this.getMap(); - const mask2DExtent = this.prepareCanvas(); - if (mask2DExtent) { - if (!mask2DExtent.intersects(this.canvasExtent2D)) { - this.completeRender(); - return; - } - } - - if (this._renderTimestamp !== timestamp) { - // maptalks/issues#10 - // 如果consumeTileQueue方法在每个renderMode都会调用,但多边形只在fxaa mode下才会绘制。 - // 导致可能出现consumeTileQueue在fxaa阶段后调用,之后的阶段就不再绘制。 - // 改为consumeTileQueue只在finalRender时调用即解决问题 - this._consumeTileQueue(); - this._computeAvgTileAltitude(); - this._renderTimestamp = timestamp; - } - - - let currentTiles; - let hasFreshTiles = false; - const frameTiles = this._frameTiles; - if (frameTiles && timestamp === frameTiles.timestamp) { - if (frameTiles.empty) { - return; - } - currentTiles = frameTiles; - } else { - currentTiles = this._getTilesInCurrentFrame(); - if (!currentTiles) { - this._frameTiles = { empty: true, timestamp }; - this.completeRender(); - return; - } - hasFreshTiles = true; - this._frameTiles = currentTiles; - this._frameTiles.timestamp = timestamp; - if (currentTiles.loadingCount) { - this.loadTileQueue(currentTiles.tileQueue); - } - } - const { tiles, childTiles, parentTiles, placeholders, loading, loadingCount, missedTiles, incompleteTiles } = currentTiles; - - this._drawTiles(tiles, parentTiles, childTiles, placeholders, context, missedTiles, incompleteTiles); - if (!loadingCount) { - if (!loading) { - //redraw to remove parent tiles if any left in last paint - if (!map.isAnimating() && (this._parentTiles.length || this._childTiles.length)) { - this._parentTiles = []; - this._childTiles = []; - this.setToRedraw(); - } - this.completeRender(); - } - } - if (hasFreshTiles) { - this.retireTiles(); - } - } - - getTileGridsInCurrentFrame(): TileGrids { - return this._frameTileGrids; - } - - getCurrentTimestamp(): number { - return this._renderTimestamp || 0; - } - - //@internal - _getTilesInCurrentFrame() { - const map = this.getMap(); - const layer = this.layer; - const terrainTileMode = layer._isPyramidMode() && layer.options['terrainTileMode']; - let tileGrids = layer.getTiles(); - this._frameTileGrids = tileGrids; - tileGrids = tileGrids.tileGrids; - if (!tileGrids || !tileGrids.length) { - return null; - } - const count = tileGrids.reduce((acc, curr) => acc + (curr && curr.tiles && curr.tiles.length || 0), 0); - if (count >= (this.tileCache.max / 2)) { - this.tileCache.setMaxSize(count * 2 + 1); - } - let loadingCount = 0; - let loading = false; - const checkedTiles = {}; - const tiles = [], - parentTiles = [], parentKeys = {}, - childTiles = [], childKeys = {}, - placeholders = [], placeholderKeys = {}; - //visit all the tiles - const tileQueue = {}; - const preLoadingCount = this.markTiles(); - const loadingLimit = this._getLoadLimit(); - - const l = tileGrids.length; - // !this._terrainHelper can't be deleted as parent tiles are part of terrain skin, maptalks/issues#608 - const isFirstRender = this._tileZoom === undefined && layer.options['currentTilesFirst'] && !this._terrainHelper; - // main tile grid is the last one (draws on top) - this._tileZoom = tileGrids[0]['zoom']; - - // let dirtyParentTiles = null; - let missingTiles = null; - let incompleteTiles = null; - if (terrainTileMode) { - // dirtyParentTiles = new Set(); - missingTiles = []; - incompleteTiles = new Map(); - } - - for (let i = 0; i < l; i++) { - const tileGrid = tileGrids[i]; - const gridTiles = tileGrid['tiles']; - const parents = tileGrid['parents'] || EMPTY_ARRAY; - const parentCount = parents.length; - const allTiles = isFirstRender ? gridTiles : parents.concat(gridTiles); - - let placeholder; - if (allTiles.length) { - placeholder = this._generatePlaceHolder(allTiles[0].res); - } - - for (let j = 0, l = allTiles.length; j < l; j++) { - const tile = allTiles[j]; - const tileId = tile.id; - const isParentTile = !isFirstRender && j < parentCount; - //load tile in cache at first if it has. - let tileLoading = false; - const tilesCount = tiles.length; - if (this._isLoadingTile(tileId)) { - tileLoading = loading = true; - this.markCurrent(this.tilesLoading[tileId], true); - } else { - const cached = this.getCachedTile(tile, isParentTile); - if (cached) { - if (!isParentTile) { - if (cached.image && this.isTileFadingIn(cached.image)) { - tileLoading = loading = true; - this.setToRedraw(); - } - - if (this.isTileComplete(cached)) { - tiles.push(cached); - } else { - tileLoading = true; - if (terrainTileMode) { - incompleteTiles.set(tileId, cached); - } - } - } - } else { - tileLoading = loading = true; - const hitLimit = loadingLimit && (loadingCount + preLoadingCount[0]) > loadingLimit; - if (!this._tileQueueIds.has(tile.id) && !hitLimit && (!map.isInteracting() || (map.isMoving() || map.isRotating()))) { - loadingCount++; - const key = tileId; - tileQueue[key] = tile; - } - } - } - - if (terrainTileMode && !isParentTile) { - if (tiles.length === tilesCount) { - missingTiles.push(tile); - } else { - checkedTiles[tile.id] = 1; - // if (tile.parent) { - // dirtyParentTiles.add(tile.parent); - // } - } - } - - if (terrainTileMode) continue; - if (isParentTile) continue; - if (!tileLoading) continue; - if (checkedTiles[tileId]) continue; - - checkedTiles[tileId] = 1; - if (placeholder && !placeholderKeys[tileId]) { - //tell gl renderer not to bind gl buffer with image - tile.cache = false; - placeholders.push({ - image: placeholder, - info: tile - }); - - placeholderKeys[tileId] = 1; - } - - const children = this._findChildTiles(tile); - if (children.length) { - children.forEach(c => { - if (!childKeys[c.info.id]) { - childTiles.push(c); - childKeys[c.info.id] = 1; - } - }); - } - // (children.length !== 4) means it's not complete, we still need a parent tile - if (!children.length || children.length !== 4) { - const parentTile = this._findParentTile(tile); - if (parentTile) { - const parentId = parentTile.info.id; - if (parentKeys[parentId] === undefined) { - parentKeys[parentId] = parentTiles.length; - parentTiles.push(parentTile); - }/* else { - //replace with parentTile of above tiles - parentTiles[parentKeys[parentId]] = parentTile; - } */ - } - } - } - } - - // 遍历 missingTiles , - const missedTiles = []; - if (terrainTileMode) { - for (let i = 0; i < missingTiles.length; i++) { - const tile = missingTiles[i].info ? missingTiles[i].info : missingTiles[i]; - if (!tile.parent || checkedTiles[tile.id]) { - continue; - } - - const { tiles: children, missedTiles: childMissedTiles } = this._findChildTiles(tile); - if (children.length) { - pushIn(tiles, children); - pushIn(missedTiles, childMissedTiles); - continue; - } else if (incompleteTiles.has(tile.id)) { - tiles.push(incompleteTiles.get(tile.id)); - incompleteTiles.delete(tile.id); - continue; - } - - checkedTiles[tile.id] = 1; - missedTiles.push(tile); - // continue; - - // // 以下是瓦片合并的优化,但一方面优化效果并不明显,且让渲染逻辑变得复杂,故暂时放弃 - // if (dirtyParentTiles.has(tile.parent) || tile.z < this._tileZoom) { - // // 如果sibling tile已经被加载过,或者是远处的上级瓦片,则直接加入missedTiles - // checkedTiles[tile.id] = 1; - // missedTiles.push(tile); - // } else { - // // 遍历当前级别瓦片,如果四个sibling tile都没有加载,则把parentTile加入到missedTiles,减少要处理的瓦片数量 - // let parentTile = parentKeys[tile.parent]; - // if (parentTile || parentTile === null) { - // // parentTile已被处理过 - // // 1. parentTile存在,则parentTile已经被加入到missedTile,作为parentTile的儿子瓦片的tile可以忽略 - // // 2. parentTile不存在,则把当前瓦片加入到missedTile - // if (parentTile === null) { - // checkedTiles[tile.id] = 1; - // missedTiles.push(tile); - // } - // continue; - // } - // // 只查询上一级的parentTile - // parentTile = this._findParentTile(tile, 1) || undefined; - // if (parentTile && parentTile.image) { - // // 父级瓦片存在,则把parentTile放入到tiles列表直接绘制 - // tiles.push(parentTile); - // parentKeys[tile.parent] = parentTile; - // } else { - // const parentTileInfo = layer.tileInfoCache.get(tile.parent); - // // 根据parentTileInfo是否存在,选择把parentTileInfo或者tile加入到missedTiles - // if (parentTileInfo) { - // if (!checkedTiles[parentTileInfo.id]) { - // checkedTiles[parentTileInfo.id] = 1; - // missedTiles.push(parentTileInfo); - // } - // parentKeys[tile.parent] = parentTileInfo; - // } else { - // checkedTiles[tile.id] = 1; - // missedTiles.push(tile); - // parentKeys[tile.parent] = null; - // } - // } - // } - } - } - - this.tileCache.shrink(); - - // if (parentTiles.length) { - // childTiles.length = 0; - // this._childTiles.length = 0; - // } - return { - childTiles, missedTiles, parentTiles, tiles, incompleteTiles: incompleteTiles && Array.from(incompleteTiles.values()), placeholders, loading, loadingCount, tileQueue - }; + this.init(layer); } - removeTileCache(tileId: TileId) { - delete this.tilesInView[tileId]; - this.tileCache.remove(tileId); - } - - isTileCachedOrLoading(tileId: TileId) { - return this.tileCache.get(tileId) || this.tilesInView[tileId] || this.tilesLoading[tileId]; - } - - isTileCached(tileId: TileId) { - return !!(this.tileCache.get(tileId) || this.tilesInView[tileId]); - } - - isTileFadingIn(tileImage: Tile['image']) { - return this.getTileFadingOpacity(tileImage) < 1; - } - - //@internal _drawTiles(tiles, parentTiles, childTiles, placeholders, parentContext, missedTiles, incompleteTiles) { if (parentTiles.length) { //closer the latter (to draw on top) @@ -467,6 +43,9 @@ class TileLayerCanvasRenderer extends CanvasRenderer { } } + // todo 当为 gl 模式时实例应为 TileLayerGLRenderer + const renderInGL = this.layer.options.renderer === 'gl' && (!(this as any).isGL || (this as any).isGL()); + const context = { tiles, parentTiles: this._parentTiles, childTiles: this._childTiles, parentContext }; this.onDrawTileStart(context, parentContext); @@ -475,8 +54,13 @@ class TileLayerCanvasRenderer extends CanvasRenderer { const fadingAnimation = this.layer.options['fadeAnimation']; this.layer.options['fadeAnimation'] = false; - this._drawChildTiles(childTiles, parentContext); - this._drawParentTiles(this._parentTiles, parentContext); + if (renderInGL) { + this._drawChildTiles(childTiles, parentContext); + this._drawParentTiles(this._parentTiles, parentContext); + } else { + this._drawParentTiles(this._parentTiles, parentContext); + this._drawChildTiles(childTiles, parentContext); + } this.layer.options['fadeAnimation'] = fadingAnimation; this.layer._silentConfig = false; @@ -494,61 +78,24 @@ class TileLayerCanvasRenderer extends CanvasRenderer { const fadingAnimation = this.layer.options['fadeAnimation']; this.layer.options['fadeAnimation'] = false; - this._drawChildTiles(childTiles, parentContext); - this._drawParentTiles(this._parentTiles, parentContext); + if (renderInGL) { + this._drawChildTiles(childTiles, parentContext); + this._drawParentTiles(this._parentTiles, parentContext); + } else { + this._drawParentTiles(this._parentTiles, parentContext); + this._drawChildTiles(childTiles, parentContext); + } this.layer.options['fadeAnimation'] = fadingAnimation; this.layer._silentConfig = false; } - // placeholders.forEach(t => this._drawTile(t.info, t.image, parentContext)); + placeholders.forEach(t => this._drawTile(t.info, t.image, parentContext)); this.onDrawTileEnd(context, parentContext); } - //@internal - _drawChildTiles(childTiles, parentContext) { - // _hasOwnSR 时,瓦片之间会有重叠,会产生z-fighting,所以背景瓦片要后绘制 - this.drawingChildTiles = true; - childTiles.forEach(t => this._drawTile(t.info, t.image, parentContext)); - delete this.drawingChildTiles; - } - - //@internal - _drawParentTiles(parentTiles, parentContext) { - this.drawingParentTiles = true; - parentTiles.forEach(t => this._drawTile(t.info, t.image, parentContext)); - delete this.drawingParentTiles; - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onDrawTileStart(context: RenderContext, parentContext: RenderContext) { } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onDrawTileEnd(context: RenderContext, parentContext: RenderContext) { } - - //@internal - _drawTile(info, image, parentContext) { - if (image) { - this.drawTile(info, image, parentContext); - } - } - - drawTile(tileInfo: Tile['info'], tileImage: Tile['image'], parentContext?: RenderContext) { - } - - //@internal - _drawTileAndCache(tile: Tile, parentContext) { - if (this.isValidCachedTile(tile)) { - this.tilesInView[tile.info.id] = tile; - } - this._drawTile(tile.info, tile.image, parentContext); - } - - drawOnInteracting(event: any, timestamp: number, context) { - this.draw(timestamp, context); - } - needToRedraw(): boolean { const map = this.getMap(); if (this._tileQueue.length) { @@ -557,789 +104,116 @@ class TileLayerCanvasRenderer extends CanvasRenderer { if (map.getPitch()) { return super.needToRedraw(); } - if (map.isInteracting()) { + if (map.isRotating() || map.isZooming()) { return true; } - return super.needToRedraw(); - } - - hitDetect(): boolean { - return false; - } - - /** - * @private - * limit tile number to load when map is interacting - */ - //@internal - _getLoadLimit(): number { - if (this.getMap().isInteracting()) { - return this.layer.options['loadingLimitOnInteracting']; - } - return this.layer.options['loadingLimit'] || 0; - } - - - clear(): void { - this.retireTiles(true); - this.tileCache.reset(); - this.tilesInView = {}; - this.tilesLoading = {}; - this._tileQueue = []; - this._tileQueueIds.clear(); - this._parentTiles = []; - this._childTiles = []; - super.clear(); - } - - //@internal - _isLoadingTile(tileId: TileId): boolean { - return !!this.tilesLoading[tileId]; - } - - clipCanvas(context): boolean { - // const mask = this.layer.getMask(); - // if (!mask) { - // return this._clipByPitch(context); - // } - return super.clipCanvas(context); - } - - loadTileQueue(tileQueue): void { - for (const p in tileQueue) { - if (tileQueue.hasOwnProperty(p)) { - const tile = tileQueue[p]; - const tileImage = this.loadTile(tile); - if (tileImage.loadTime === undefined) { - // tile image's loading may not be async - this.tilesLoading[tile['id']] = { - image: tileImage, - current: true, - info: tile - }; - } - } - } - } - - loadTile(tile: Tile['info']): Tile['image'] { - let tileImage = {} as Tile['image']; - // fixme: 无相关定义,是否实现? - // @ts-expect-error todo - if (this.loadTileBitmap) { - const onLoad = (bitmap) => { - this.onTileLoad(bitmap, tile); - }; - const onError = (error, image) => { - this.onTileError(image, tile, error); - }; - // @ts-expect-error todo - this.loadTileBitmap(tile['url'], tile, onLoad, onError); - } else if (this._tileImageWorkerConn && this.loadTileImage === this.constructor.prototype.loadTileImage) { - this._fetchImage(tileImage, tile); - } else { - const tileSize = this.layer.getTileSize(tile.layer); - tileImage = new Image() as Tile['image']; - - // @ts-expect-error todo - tileImage.width = tileSize['width']; - // @ts-expect-error todo - tileImage.height = tileSize['height']; - - (tileImage as any).onload = this.onTileLoad.bind(this, tileImage, tile); - (tileImage as any).onerror = this.onTileError.bind(this, tileImage, tile); - - this.loadTileImage(tileImage, tile['url']); - } - return tileImage; - } - - //@internal - _fetchImage(image: any, tile: Tile['info']) { - if (image instanceof Image) { - image.src = tile.url; - } else { - const { x, y } = tile; - const workerId = Math.abs(x + y) % this._tileImageWorkerConn.workers.length; - this._tileImageWorkerConn.fetchImage(tile.url, workerId, (err, data) => { - if (err) { - this.onTileError(image, tile, err); - } else { - getImageBitMap(data, (bitmap: Tile['image']) => { - this.onTileLoad(bitmap, tile); - }); - } - }, this.layer.options['fetchOptions'] || { - referrer: document.location.href, - headers: { accept: 'image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8' } - }); - } - } - - loadTileImage(tileImage, url: string) { - const crossOrigin = this.layer.options['crossOrigin']; - if (!isNil(crossOrigin)) { - tileImage.crossOrigin = crossOrigin; - } - return loadImage(tileImage, [url]); - } - - abortTileLoading(tileImage: Tile['image'], tileInfo: Tile['info']): void { - if (tileInfo && tileInfo.id !== undefined) { - this.removeTileLoading(tileInfo); - } - if (!tileImage) return; - if (tileImage instanceof Image) { - tileImage.onload = falseFn; - tileImage.onerror = falseFn; - tileImage.src = emptyImageUrl; + if (map.isMoving()) { + return !!this.layer.options['forceRenderOnMoving']; } + return super.needToRedraw(); } - onTileLoad(tileImage: Tile['image'], tileInfo: Tile['info']): void { - this.removeTileLoading(tileInfo); - this._tileQueue.push({ tileInfo: tileInfo, tileData: tileImage }); - this._tileQueueIds.add(tileInfo.id); - this.setToRedraw(); - } - - removeTileLoading(tileInfo: Tile['info']): void { - delete this.tilesLoading[tileInfo.id]; - // need to setToRedraw to let tiles blocked by loadingLimit continue to load - this.setToRedraw(); - } - - //@internal - _consumeTileQueue(): void { - let count = 0; - const limit = this.layer.options['tileLimitPerFrame']; - const queue = this._tileQueue; - /* eslint-disable no-unmodified-loop-condition */ - while (queue.length && (limit <= 0 || count < limit)) { - const { tileData, tileInfo } = queue.shift(); - if (!this._tileQueueIds.has(tileInfo.id)) { - continue; - } - this._tileQueueIds.delete(tileInfo.id); - if (!this.checkTileInQueue(tileData, tileInfo)) { - continue; - } - this.consumeTile(tileData, tileInfo); - count++; - } - /* eslint-enable no-unmodified-loop-condition */ - } - - //@internal - _computeAvgTileAltitude() { - let sumMin = 0; - let sumMax = 0; - let count = 0; - for (const p in this.tilesInView) { - const info = this.tilesInView[p] && this.tilesInView[p].info; - if (info) { - sumMin += info.minAltitude || 0; - sumMax += info.maxAltitude || 0; - count++; + isDrawable(): boolean { + if (this.getMap().getPitch()) { + if (console) { + console.warn('TileLayer with canvas renderer can\'t be pitched, use gl renderer (\'renderer\' : \'gl\') instead.'); } + this.clear(); + return false; } - this.avgMinAltitude = sumMin / count; - this.avgMaxAltitude = sumMax / count; - } - - // Parameters tileImage and tileInfo are required in VectorTileLayerRenderer - checkTileInQueue(tileImage: Tile['image'], tileInfo: Tile['info']): boolean { return true; } - consumeTile(tileImage: Tile['image'], tileInfo: Tile['info']): void { - if (!this.layer) { - return; - } - if (!this.tilesInView) { - // removed - return; - } - const e = { tile: tileInfo, tileImage: tileImage }; - // let user update tileImage in listener if needed - tileImage = e.tileImage; - this.resetTileLoadTime(tileImage); - this.removeTileLoading(tileInfo); - this._addTileToCache(tileInfo, tileImage); - /** - * tileload event, fired when tile is loaded. - * - * @event TileLayer#tileload - * @type {Object} - * @property {String} type - tileload - * @property {TileLayer} target - tile layer - * @property {Object} tileInfo - tile info - * @property {Image} tileImage - tile image - */ - this.layer.fire('tileload', e); - this.setToRedraw(); - } - - resetTileLoadTime(tileImage: Tile['image']): void { - // loadTime = 0 means a tile from onTileError - if (tileImage.loadTime !== 0) { - tileImage.loadTime = now(); - } - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onTileError(tileImage: Tile['image'], tileInfo: Tile['info'], error?: any) { - if (!this.layer) { - return; - } - // example: - /* reloadErrorTileFunction: (layer, renderer, tileInfo, tileImage) => { - const url = tileInfo.url; - // check if need to reload, e.g. server return 500 status code temporarily - if (needReload) { - renderer.loadTile(tileInfo, tileImage); - } - } */ - const reloadErrorTileFunction = this.layer.options['reloadErrorTileFunction']; - if (reloadErrorTileFunction) { - reloadErrorTileFunction.call(this, this.layer, this, tileInfo, tileImage); - return; - } - // tileImage.onerrorTick = tileImage.onerrorTick || 0; - // const tileRetryCount = this.layer.options['tileRetryCount']; - // if (tileRetryCount > tileImage.onerrorTick) { - // tileImage.onerrorTick++; - // this._fetchImage(tileImage, tileInfo); - // this.removeTileLoading(tileInfo); - // return; - // } - const errorUrl = this.layer.options['errorUrl']; - if (errorUrl) { - if ((tileImage instanceof Image) && tileImage.src !== errorUrl) { - tileImage.src = errorUrl; - this.removeTileLoading(tileInfo); - return; - } else { - tileImage = new Image() as any; - (tileImage as HTMLImageElement).src = errorUrl; - } - } - this.abortTileLoading(tileImage, tileInfo); - - tileImage.loadTime = 0; - this.removeTileLoading(tileInfo); - this._addTileToCache(tileInfo, tileImage); - this.setToRedraw(); - /** - * tileerror event, fired when tile loading has error. - * - * @event TileLayer#tileerror - * @type {Object} - * @property {String} type - tileerror - * @property {TileLayer} target - tile layer - * @property {Object} tileInfo - tile info - */ - this.layer.fire('tileerror', { tile: tileInfo }); - } - - getDebugInfo(tileId: TileId): string { - const xyz = tileId.split('_'); - const length = xyz.length; - return xyz[length - 3] + '/' + xyz[length - 2] + '/' + xyz[length - 1]; - } - - findChildTiles(info: Tile['info']) { - return this._findChildTiles(info); - } + // clip canvas to avoid rough edge of tiles //@internal - _findChildTiles(info: Tile['info']): Tile[] | any { - const layer = this._getLayerOfTile(info.layer); - const terrainTileMode = layer && layer.options['terrainTileMode'] && layer._isPyramidMode(); - if (!layer || !layer.options['background'] && !terrainTileMode || info.z > this.layer.getMaxZoom()) { - return EMPTY_ARRAY; - } + _clipByPitch(ctx: CanvasRenderingContext2D): boolean { const map = this.getMap(); - const children = []; - if (layer._isPyramidMode()) { - if (!terrainTileMode) { - // a faster one - const layer = this._getLayerOfTile(info.layer); - const zoomDiff = 2; - const cx = info.x * 2; - const cy = info.y * 2; - const cz = info.z + 1; - const queue = []; - for (let j = 0; j < 2; j++) { - for (let jj = 0; jj < 2; jj++) { - queue.push(cx + j, cy + jj, cz); - } - } - while (queue.length) { - const z = queue.pop(); - const y = queue.pop(); - const x = queue.pop(); - const id = layer._getTileId(x, y, z, info.layer); - const canVisit = z + 1 <= info.z + zoomDiff; - const tile = this.tileCache.getAndRemove(id); - if (tile) { - if (this.isValidCachedTile(tile)) { - children.push(tile); - this.tileCache.add(id, tile); - } else if (canVisit) { - for (let j = 0; j < 2; j++) { - for (let jj = 0; jj < 2; jj++) { - queue.push(x * 2 + j, y * 2 + jj, z + 1); - } - } - } - } else if (canVisit) { - for (let j = 0; j < 2; j++) { - for (let jj = 0; jj < 2; jj++) { - queue.push(x * 2 + j, y * 2 + jj, z + 1); - } - } - } - } - return children; - } - let missedTiles; - if (terrainTileMode) { - missedTiles = []; - } - // const zoomDiff = 2; - const cx = info.x * 2; - const cy = info.y * 2; - const cz = info.z + 1; - // const queue = []; - // for the sake of performance, we only traverse next 2 levels of children tiles - const candidates = []; - for (let i = 0; i < 2; i++) { - for (let ii = 0; ii < 2; ii++) { - const x = cx + i; - const y = cy + ii; - const z = cz; - const id = layer._getTileId(x, y, z, info.layer); - const tile = this.tileCache.getAndRemove(id); - if (tile && this.isValidCachedTile(tile)) { - children.push(tile); - this.tileCache.add(id, tile); - candidates.push(null); - } else { - // 缺少offset - candidates.push(id); - } - } - } - - // children.length等于4时,说明4个一级子瓦片都放入了children中 - if (children.length < 4) { - let index = 0; - for (let i = 0; i < 2; i++) { - for (let ii = 0; ii < 2; ii++) { - const id = candidates[index++]; - if (!id) { - continue; - } - const x = cx + i; - const y = cy + ii; - const z = cz; - const childrenCount = children.length; - const childCandidates = []; - for (let j = 0; j < 2; j++) { - for (let jj = 0; jj < 2; jj++) { - const xx = x * 2 + j; - const yy = y * 2 + jj; - const zz = z + 1; - const id = layer._getTileId(xx, yy, zz, info.layer); - const childTile = this.tileCache.getAndRemove(id); - if (childTile && this.isValidCachedTile(childTile)) { - children.push(childTile); - this.tileCache.add(id, childTile); - childCandidates.push(null); - } else { - childCandidates.push(id); - } - } - } - if (!terrainTileMode) { - continue; - } - if (children.length - childrenCount < 4) { - const childTileInfo = layer.tileInfoCache.get(id) || layer._createChildNode(info, i, ii, [0, 0], id); - if (children.length - childrenCount === 0) { - // 四个二级子瓦片都没有被缓存,直接将当前的一级子瓦片tileInfo放入missedTiles - missedTiles.push(childTileInfo); - } else { - // 四个二级子瓦片有被缓存的,将没有被缓存的tileInfo加入missedTiles - let index = 0; - for (let j = 0; j < 2; j++) { - for (let jj = 0; jj < 2; jj++) { - const id = childCandidates[index++]; - if (!id) { - // 这个二级子瓦片已经被加入到了children - continue; - } - const grandsonTileInfo = this.layer.tileInfoCache.get(id) || layer._createChildNode(childTileInfo, j, jj, [0, 0], id); - missedTiles.push(grandsonTileInfo); - } - } - } - } - - } - } - } - return terrainTileMode ? { tiles: children, missedTiles } : children; - } - const zoomDiff = 1; - const res = info.res; - const min = info.extent2d.getMin(), - max = info.extent2d.getMax(), - pmin = layer._project(map._pointToPrjAtRes(min, res, TEMP_POINT1), TEMP_POINT1), - pmax = layer._project(map._pointToPrjAtRes(max, res, TEMP_POINT2), TEMP_POINT2); - - for (let i = 1; i < zoomDiff; i++) { - this._findChildTilesAt(children, pmin, pmax, layer, info.z + i); - } - - return children; - } - - //@internal - _findChildTilesAt(children: Tile[], pmin: number, pmax: number, layer: any, childZoom: number) { - const layerId = layer.getId(), - res = layer.getSpatialReference().getResolution(childZoom); - if (!res) { - return; - } - const dmin = layer._getTileConfig().getTileIndex(pmin, res), - dmax = layer._getTileConfig().getTileIndex(pmax, res); - const sx = Math.min(dmin.idx, dmax.idx), ex = Math.max(dmin.idx, dmax.idx); - const sy = Math.min(dmin.idy, dmax.idy), ey = Math.max(dmin.idy, dmax.idy); - let id, tile; - for (let i = sx; i < ex; i++) { - for (let ii = sy; ii < ey; ii++) { - id = layer._getTileId(i, ii, childZoom, layerId); - tile = this.tileCache.getAndRemove(id); - if (tile) { - if (this.isValidCachedTile(tile)) { - children.push(tile); - this.tileCache.add(id, tile); - } - } - } - } - } - - findParentTile(info: Tile['info'], targetDiff?: number): Tile { - return this._findParentTile(info, targetDiff); - } - - //@internal - _findParentTile(info: Tile['info'], targetDiff?: number): Tile { - const map = this.getMap(), - layer = this._getLayerOfTile(info.layer); - if (!layer || !layer.options['background'] && !layer.options['terrainTileMode']) { - return null; - } - const minZoom = layer.getMinZoom(); - const zoomDiff: number = targetDiff || info.z - minZoom; - if (layer._isPyramidMode()) { - const endZoom = info.z - zoomDiff; - for (let z = info.z - 1; z >= endZoom; z--) { - const diff = info.z - z; - const scale = Math.pow(2, diff); - const x = Math.floor(info.x / scale); - const y = Math.floor(info.y / scale); - let id; - if (z === info.z - 1) { - id = info.parent; - } else { - id = layer._getTileId(x, y, z, info.layer); - } - const tile = this.tileCache.getAndRemove(id); - if (tile) { - if (this.isValidCachedTile(tile)) { - this.tileCache.add(id, tile); - return tile; - } - } - } - return null; - } - const sr = layer.getSpatialReference(); - // const zoomOffset = layer.options['zoomOffset']; - const d = sr.getZoomDirection(); - const res = info.res; - const center = info.extent2d.getCenter(), - prj = layer._project(map._pointToPrjAtRes(center, res)); - for (let diff = 1; diff <= zoomDiff; diff++) { - const z = info.z - d * diff; - const res = sr.getResolution(z); - if (!res) continue; - const tileIndex = layer._getTileConfig().getTileIndex(prj, res); - const id = layer._getTileId(tileIndex.x, tileIndex.y, z, info.layer); - const tile = this.tileCache.getAndRemove(id); - if (tile) { - this.tileCache.add(id, tile); - return tile; - } - } - return null; - } - - isValidCachedTile(tile: Tile): boolean { - return !!tile.image; - } - - isTileComplete(tile: Tile) { + if (map.getPitch() <= map.options['maxVisualPitch']) { + return false; + } + if (!this.layer.options['clipByPitch']) { + return false; + } + const clipExtent = map.getContainerExtent(); + const r = map.getDevicePixelRatio(); + ctx.save(); + ctx.strokeStyle = 'rgba(0, 0, 0, 0)'; + ctx.beginPath(); + ctx.rect(0, Math.ceil(clipExtent.ymin) * r, Math.ceil(clipExtent.getWidth()) * r, Math.ceil(clipExtent.getHeight()) * r); + ctx.stroke(); + ctx.clip(); return true; } - //@internal - _getLayerOfTile(layerId: LayerId) { - return this.layer.getChildLayer ? this.layer.getChildLayer(layerId) : this.layer; - } - - getCachedTile(tile: Tile, isParent: boolean) { - const tileId = tile.id; - const tilesInView = this.tilesInView; - let cached = this.tileCache.getAndRemove(tileId); - if (cached) { - if (!isParent) { - tilesInView[tileId] = cached; - } - const tilesLoading = this.tilesLoading; - if (tilesLoading && tilesLoading[tileId]) { - this.markCurrent(tilesLoading[tileId], false); - const { image, info } = tilesLoading[tileId]; - this.abortTileLoading(image, info); - delete tilesLoading[tileId]; - } - } else { - cached = tilesInView[tileId]; - } - if (cached) { - cached.current = true; - if (this.isValidCachedTile(cached)) { - this.tileCache.add(tileId, cached); - } - } - return cached; - } - - //@internal - _addTileToCache(tileInfo: Tile['info'], tileImage: Tile['image']) { - if (this.isValidCachedTile({ info: tileInfo, image: tileImage } as Tile)) { - const cached = { - image: tileImage, - info: tileInfo - } as Tile; - this.tileCache.add(tileInfo.id, cached); - } - } - - getTileOpacity(tileImage: Tile['image'], tileInfo: Tile['info']): number { - let opacity = this.getTileFadingOpacity(tileImage); - if (this.layer.getChildLayer) { - // in GroupTileLayer - const childLayer = this.layer.getLayer(tileInfo.layer); - if (childLayer) { - opacity *= childLayer.options['opacity']; - } - } - return opacity; - } - - getTileFadingOpacity(tileImage: Tile['image']): number { - if (!this.layer.options['fadeAnimation'] || !tileImage.loadTime) { - return 1; - } - return Math.min(1, (now() - tileImage.loadTime) / this.layer.options['fadeDuration']); - } - - onRemove(): void { - this.clear(); - delete this.tileCache; - delete this._tilePlaceHolder; - delete this._tileZoom; - super.onRemove(); - } - - markCurrent(tile: Tile, isCurrent?: boolean): void { - tile.current = isCurrent; - } - - markTiles(): number[] { - let a = 0, b = 0; - if (this.tilesLoading) { - for (const p in this.tilesLoading) { - this.markCurrent(this.tilesLoading[p], false); - a++; - } - } - if (this.tilesInView) { - for (const p in this.tilesInView) { - this.markCurrent(this.tilesInView[p], false); - b++; - } - } - return [a, b]; - } - - retireTiles(force?: boolean): void { - for (const i in this.tilesLoading) { - const tile = this.tilesLoading[i]; - if (force || !tile.current) { - // abort loading tiles - if (tile.image) { - this.abortTileLoading(tile.image, tile.info); - } - this.deleteTile(tile); - this.removeTileLoading(tile.info); - } - } - for (const i in this.tilesInView) { - const tile = this.tilesInView[i]; - if (!tile.current) { - delete this.tilesInView[i]; - if (!this.tileCache.has(i)) { - this.deleteTile(tile); - } - } - } - } - - deleteTile(tile: Tile): void { - if (!tile || !tile.image) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + drawTile(tileInfo: Tile['info'], tileImage: Tile['image'], parentContext?: RenderContext) { + if (!tileImage || !this.getMap()) { return; } - const tileId = tile.info.id; - if (this._tileQueueIds.has(tileId)) { - this._tileQueueIds.delete(tileId); - } - if ((tile.image as any).close) { - (tile.image as any).close(); - } - if (tile.image instanceof Image) { - tile.image.onload = null; - tile.image.onerror = null; - } - } - - //@internal - _generatePlaceHolder(res: number): HTMLCanvasElement { - const map = this.getMap(); - const placeholder = this.layer.options['placeholder']; - if (!placeholder || map.getPitch()) { - return null; - } - const tileSize = this.layer.getTileSize(); - const scale = res / map._getResolution(); - const canvas = this._tilePlaceHolder = this._tilePlaceHolder || Canvas.createCanvas(1, 1, map.CanvasClass); - canvas.width = tileSize.width * scale; - canvas.height = tileSize.height * scale; - if (isFunction(placeholder)) { - placeholder(canvas); - } else { - defaultPlaceholder(canvas); - } - return canvas; - } - - setTerrainHelper(helper: TerrainHelper) { - this._terrainHelper = helper; - } -} - -function falseFn(): boolean { return false; } - -function defaultPlaceholder(canvas: HTMLCanvasElement): void { - const ctx = canvas.getContext('2d'), - cw = canvas.width, ch = canvas.height, - w = cw / 16, h = ch / 16; - ctx.beginPath(); - for (let i = 0; i < 16; i++) { - ctx.moveTo(0, i * h); - ctx.lineTo(cw, i * h); - ctx.moveTo(i * w, 0); - ctx.lineTo(i * w, ch); - } - ctx.strokeStyle = 'rgba(180, 180, 180, 0.1)'; - ctx.lineWidth = 1; - ctx.stroke(); - ctx.beginPath(); - const path = [ - [0, 0], [cw, 0], [0, ch], [cw, ch], [0, 0], [0, ch], [cw, 0], [cw, ch], [0, ch / 2], [cw, ch / 2], [cw / 2, 0], [cw / 2, ch] - ]; - for (let i = 1; i < path.length; i += 2) { - ctx.moveTo(path[i - 1][0], path[i - 1][1]); - ctx.lineTo(path[i][0], path[i][1]); + const { extent2d, offset } = tileInfo; + const point = TILE_POINT.set(extent2d.xmin - offset[0], extent2d.ymax - offset[1]), + tileZoom = tileInfo.z, + tileId = tileInfo.id; + const map = this.getMap(), + zoom = map.getZoom(), + ctx = this.context, + cp = map._pointAtResToContainerPoint(point, tileInfo.res, 0, TEMP_POINT), + bearing = map.getBearing(), + transformed = bearing || zoom !== tileZoom; + const opacity = this.getTileOpacity(tileImage, tileInfo); + const alpha = ctx.globalAlpha; + if (opacity < 1) { + ctx.globalAlpha = opacity; + } + if (!transformed) { + cp._round(); + } + let x = cp.x, + y = cp.y; + let w = tileInfo.extent2d.xmax - tileInfo.extent2d.xmin; + let h = tileInfo.extent2d.ymax - tileInfo.extent2d.ymin; + const layer = this.layer; + const bufferPixel = (layer ? layer.options.bufferPixel : 0); + if (transformed) { + ctx.save(); + ctx.translate(x, y); + if (bearing) { + ctx.rotate(-bearing * Math.PI / 180); + } + w += bufferPixel; + h += bufferPixel; + const res = map._getResolution(); + if (res !== tileInfo.res) { + const scale = tileInfo.res / res; + ctx.scale(scale, scale); + } + x = y = 0; + } + Canvas2D.image(ctx, tileImage, x, y, w, h); + if (this.layer.options['debug']) { + const color = this.layer.options['debugOutline']; + ctx.save(); + ctx.strokeStyle = color; + ctx.fillStyle = color; + // ctx.strokeWidth = 10; + // ctx.lineWidth = 10 + ctx.font = '20px monospace'; + const point = new Point(x, y); + Canvas2D.rectangle(ctx, point, { width: w, height: h }, 1, 0); + Canvas2D.fillText(ctx, this.getDebugInfo(tileId), point._add(32, h - 14), color); + Canvas2D.drawCross(ctx, x + w / 2, y + h / 2, 2, color); + ctx.restore(); + } + if (transformed) { + ctx.restore(); + } + if (ctx.globalAlpha !== alpha) { + ctx.globalAlpha = alpha; + } + this.setCanvasUpdated(); } - ctx.lineWidth = 1 * 4; - ctx.stroke(); -} - -export default TileLayerCanvasRenderer; - -function compareTiles(a: Tile, b: Tile): number { - return Math.abs(this._tileZoom - a.info.z) - Math.abs(this._tileZoom - b.info.z); -} -export type TileId = string; -export type LayerId = string | number; -export type TerrainHelper = any; -export type TileImage = (HTMLImageElement | HTMLCanvasElement | ImageBitmap) & { - loadTime: number; - glBuffer?: TileImageBuffer; - texture?: TileImageTexture; - // onerrorTick?: number; } -export interface Tile { - id: TileId; - info: { - x: number; - y: number; - z: number; - idx: number; - idy: number; - id: TileId; - layer: number | string; - children: []; - error: number; - offset: [number, number]; - extent2d: Extent; - res: number; - url: string; - parent: any; - cache?: boolean; - // todo:检查是否存在定义 - minAltitude?: number; - maxAltitude?: number; - }; - - image: TileImage; - current?: boolean; -} - -export type RenderContext = any; - -export type TilesInViewType = { - [key: string]: Tile; -} - -export interface TileGrid { - extent: Extent; - count: number; - tiles: Tile[]; - parents: any[]; - offset: number[]; - zoom: number; -} - -export interface TileGrids { - count: number; - tileGrids: TileGrid[]; -} +TileLayer.registerRenderer('canvas', TileLayerCanvasRenderer); diff --git a/packages/map/src/renderer/layer/tilelayer/TileLayerGLRenderer.ts b/packages/map/src/renderer/layer/tilelayer/TileLayerGLRenderer.ts index ca624e3972..d3dcf321ad 100644 --- a/packages/map/src/renderer/layer/tilelayer/TileLayerGLRenderer.ts +++ b/packages/map/src/renderer/layer/tilelayer/TileLayerGLRenderer.ts @@ -1,8 +1,9 @@ -// import TileLayer from '../../../layer/tile/TileLayer'; +import TileLayer from '../../../layer/tile/TileLayer'; import TileLayerCanvasRenderer from './TileLayerCanvasRenderer'; -import type { Tile, RenderContext} from './TileLayerCanvasRenderer'; +import { RenderContext, Tile } from './TileLayerRendererable'; import ImageGLRenderable from '../ImageGLRenderable'; import Point from '../../../geo/Point'; +import { SizeLike } from '../../../geo/Size'; const TILE_POINT = new Point(0, 0); @@ -21,6 +22,11 @@ const MESH_TO_TEST = { properties: {}}; */ class TileLayerGLRenderer extends ImageGLRenderable(TileLayerCanvasRenderer) { + //override to set to always drawable + isDrawable(): boolean { + return true; + } + needToRedraw(): boolean { const map = this.getMap(); if (this.isGL() && !map.getPitch() && map.isZooming() && !map.isMoving() && !map.isRotating()) { @@ -69,13 +75,17 @@ class TileLayerGLRenderer extends ImageGLRenderable(TileLayerCanvasRenderer) { return; } - const scale = tileInfo.res / map.getGLRes(); + const scale = tileInfo._glScale = tileInfo._glScale || tileInfo.res / map.getGLRes(); const w = tileInfo.extent2d.xmax - tileInfo.extent2d.xmin; const h = tileInfo.extent2d.ymax - tileInfo.extent2d.ymin; if (tileInfo.cache !== false) { this._bindGLBuffer(tileImage, w, h); } - + if (!this.isGL()) { + // fall back to canvas 2D, which is faster + super.drawTile(tileInfo, tileImage); + return; + } const { extent2d, offset } = tileInfo; const point = TILE_POINT.set(extent2d.xmin - offset[0], tileInfo.extent2d.ymax - offset[1]); const x = point.x * scale, @@ -92,8 +102,10 @@ class TileLayerGLRenderer extends ImageGLRenderable(TileLayerCanvasRenderer) { gl.polygonOffset(polygonOffset, polygonOffset); this.drawGLImage(tileImage as any, x, y, w, h, scale, opacity, debugInfo); - if (this.getTileFadingOpacity(tileImage) < 1) { + if (this._getTileFadingOpacity(tileImage) < 1) { this.setToRedraw(); + } else { + this.setCanvasUpdated(); } } @@ -112,6 +124,36 @@ class TileLayerGLRenderer extends ImageGLRenderable(TileLayerCanvasRenderer) { return; } + /** + * prepare gl, create program, create buffers and fill unchanged data: image samplers, texture coordinates + */ + onCanvasCreate() { + //not in a GroupGLLayer + if (!this.canvas.gl || !this.canvas.gl.wrap) { + this.createCanvas2(); + } + } + + createContext(): void { + super.createContext(); + this.createGLContext(); + } + + resizeCanvas(canvasSize: SizeLike) { + if (!this.canvas) { + return; + } + super.resizeCanvas(canvasSize); + this.resizeGLCanvas(); + } + + clearCanvas(): void { + if (!this.canvas) { + return; + } + super.clearCanvas(); + this.clearGLCanvas(); + } getCanvasImage() { if (!this.isGL() || !this.canvas2) { @@ -152,6 +194,7 @@ class TileLayerGLRenderer extends ImageGLRenderable(TileLayerCanvasRenderer) { } } +TileLayer.registerRenderer('gl', TileLayerGLRenderer); export default TileLayerGLRenderer; diff --git a/packages/map/src/renderer/layer/tilelayer/TileLayerRendererable.ts b/packages/map/src/renderer/layer/tilelayer/TileLayerRendererable.ts new file mode 100644 index 0000000000..50f0e2548b --- /dev/null +++ b/packages/map/src/renderer/layer/tilelayer/TileLayerRendererable.ts @@ -0,0 +1,1351 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { + isNil, + loadImage, + emptyImageUrl, + now, + isFunction, + getImageBitMap, + isString, + getAbsoluteURL, + pushIn +} from '../../../core/util'; +import Browser from '../../../core/Browser'; +import { default as TileLayer } from '../../../layer/tile/TileLayer'; +import WMSTileLayer from '../../../layer/tile/WMSTileLayer'; +import LayerGLRenderer from '../LayerGLRenderer'; +import Point from '../../../geo/Point'; +import Extent from '../../../geo/Extent'; +import LRUCache from '../../../core/util/LRUCache'; +import Canvas from '../../../core/Canvas'; +import Actor from '../../../core/worker/Actor'; +import { imageFetchWorkerKey } from '../../../core/worker/CoreWorkers'; +import { TileImageBuffer, TileImageTexture } from '../../types'; +import type { WithUndef } from '../../../types/typings'; +import { MixinConstructor } from '../../../core/Mixin'; + + +const TEMP_POINT1 = new Point(0, 0); +const TEMP_POINT2 = new Point(0, 0); + +const EMPTY_ARRAY = []; +class TileWorkerConnection extends Actor { + constructor() { + super(imageFetchWorkerKey); + } + + checkUrl(url: string) { + if (!url || !isString(url)) { + return url; + } + //The URL is processed. Here, only the relative protocol is processed + return getAbsoluteURL(url); + + } + + // eslint-disable-next-line @typescript-eslint/ban-types + fetchImage(url: string, workerId: number, cb: Function, fetchOptions: any) { + url = this.checkUrl(url); + const data = { + url, + fetchOptions + }; + this.send(data, EMPTY_ARRAY, cb, workerId); + } +} + +/** + * 瓦片图层的渲染器抽象类,实现瓦片的遍历功能,可以继承并实现 drawTile 等方法来实现瓦片图层渲染 + * + * @english + * Abstract renderer class for TileLayers in maptalks-gl + * @class + * @protected + * @group renderer + * @extends {renderer.LayerGLRenderer} + */ +const TileLayerRenderable = function (Base: T) { + const renderable = class extends Base { + [x: string]: any; + tilesInView: TilesInViewType; + tilesLoading: { [key: string]: any }; + //@internal + _parentTiles: any[]; + //@internal + _childTiles: any[]; + //@internal + _tileZoom: number; + //@internal + _tileQueue: { + tileInfo: any; + tileData: any; + }[]; + //@internal + _tileQueueIds: Set; + tileCache: typeof LRUCache; + //@internal + _compareTiles: any; + //@internal + _tileImageWorkerConn: TileWorkerConnection; + //@internal + _renderTimestamp: number; + //@internal + _frameTiles: { + empty: boolean; + timestamp: number; + }; + + //@internal + _terrainHelper: TerrainHelper; + //@internal + _tilePlaceHolder: any; + //@internal + _frameTileGrids: TileGrids; + + drawingCurrentTiles: WithUndef; + drawingChildTiles: WithUndef; + drawingParentTiles: WithUndef; + avgMinAltitude: number; + avgMaxAltitude: number; + + init(layer) { + this.tilesInView = {}; + this.tilesLoading = {}; + this._parentTiles = []; + this._childTiles = []; + this._tileQueue = []; + this._tileQueueIds = new Set(); + const tileSize = layer.getTileSize().width; + this.tileCache = new LRUCache(layer.options['maxCacheSize'] * tileSize / 512 * tileSize / 512, (tile: Tile) => { + this.deleteTile(tile); + }); + if (Browser.decodeImageInWorker && this.layer.options['decodeImageInWorker'] && (layer.options['renderer'] === 'gl' || !Browser.safari && !Browser.iosWeixin)) { + this._tileImageWorkerConn = new TileWorkerConnection(); + } + this._compareTiles = compareTiles.bind(this); + } + + + getCurrentTileZoom(): number { + return this._tileZoom; + } + + draw(timestamp: number, context): number { + const map = this.getMap(); + if ((this as any).isDrawable && !(this as any).isDrawable()) { + return; + } + const mask2DExtent = this.prepareCanvas(); + if (mask2DExtent) { + if (!mask2DExtent.intersects(this.canvasExtent2D)) { + this.completeRender(); + return; + } + } + + if (this._renderTimestamp !== timestamp) { + // maptalks/issues#10 + // 如果consumeTileQueue方法在每个renderMode都会调用,但多边形只在fxaa mode下才会绘制。 + // 导致可能出现consumeTileQueue在fxaa阶段后调用,之后的阶段就不再绘制。 + // 改为consumeTileQueue只在finalRender时调用即解决问题 + this._consumeTileQueue(); + this._computeAvgTileAltitude(); + this._renderTimestamp = timestamp; + } + + + let currentTiles; + let hasFreshTiles = false; + const frameTiles = this._frameTiles; + if (frameTiles && timestamp === frameTiles.timestamp) { + if (frameTiles.empty) { + return; + } + currentTiles = frameTiles; + } else { + currentTiles = this._getTilesInCurrentFrame(); + if (!currentTiles) { + this._frameTiles = { empty: true, timestamp }; + this.completeRender(); + return; + } + hasFreshTiles = true; + this._frameTiles = currentTiles; + this._frameTiles.timestamp = timestamp; + if (currentTiles.loadingCount) { + this.loadTileQueue(currentTiles.tileQueue); + } + } + const { tiles, childTiles, parentTiles, placeholders, loading, loadingCount, missedTiles, incompleteTiles } = currentTiles; + + this._drawTiles(tiles, parentTiles, childTiles, placeholders, context, missedTiles, incompleteTiles); + if (!loadingCount) { + if (!loading) { + //redraw to remove parent tiles if any left in last paint + if (!map.isAnimating() && (this._parentTiles.length || this._childTiles.length)) { + this._parentTiles = []; + this._childTiles = []; + this.setToRedraw(); + } + this.completeRender(); + } + } + if (hasFreshTiles) { + this.retireTiles(); + } + } + + getTileGridsInCurrentFrame(): TileGrids { + return this._frameTileGrids; + } + + getCurrentTimestamp(): number { + return this._renderTimestamp || 0; + } + + //@internal + _getTilesInCurrentFrame() { + const map = this.getMap(); + const layer = this.layer; + const terrainTileMode = layer._isPyramidMode() && layer.options['terrainTileMode']; + let tileGrids = layer.getTiles(); + this._frameTileGrids = tileGrids; + tileGrids = tileGrids.tileGrids; + if (!tileGrids || !tileGrids.length) { + return null; + } + const count = tileGrids.reduce((acc, curr) => acc + (curr && curr.tiles && curr.tiles.length || 0), 0); + if (count >= (this.tileCache.max / 2)) { + this.tileCache.setMaxSize(count * 2 + 1); + } + let loadingCount = 0; + let loading = false; + const checkedTiles = {}; + const tiles = [], + parentTiles = [], parentKeys = {}, + childTiles = [], childKeys = {}, + placeholders = [], placeholderKeys = {}; + //visit all the tiles + const tileQueue = {}; + const preLoadingCount = this.markTiles(); + const loadingLimit = this._getLoadLimit(); + + const l = tileGrids.length; + // !this._terrainHelper can't be deleted as parent tiles are part of terrain skin, maptalks/issues#608 + const isFirstRender = this._tileZoom === undefined && layer.options['currentTilesFirst'] && !this._terrainHelper; + // main tile grid is the last one (draws on top) + this._tileZoom = tileGrids[0]['zoom']; + + // let dirtyParentTiles = null; + let missingTiles = null; + let incompleteTiles = null; + if (terrainTileMode) { + // dirtyParentTiles = new Set(); + missingTiles = []; + incompleteTiles = new Map(); + } + + for (let i = 0; i < l; i++) { + const tileGrid = tileGrids[i]; + const gridTiles = tileGrid['tiles']; + const parents = tileGrid['parents'] || EMPTY_ARRAY; + const parentCount = parents.length; + const allTiles = isFirstRender ? gridTiles : parents.concat(gridTiles); + + let placeholder; + if (allTiles.length) { + placeholder = this._generatePlaceHolder(allTiles[0].res); + } + + for (let j = 0, l = allTiles.length; j < l; j++) { + const tile = allTiles[j]; + const tileId = tile.id; + const isParentTile = !isFirstRender && j < parentCount; + //load tile in cache at first if it has. + let tileLoading = false; + const tilesCount = tiles.length; + if (this._isLoadingTile(tileId)) { + tileLoading = loading = true; + this.markCurrent(this.tilesLoading[tileId], true); + } else { + const cached = this.getCachedTile(tile, isParentTile); + if (cached) { + if (!isParentTile) { + if (cached.image && this.isTileFadingIn(cached.image)) { + tileLoading = loading = true; + this.setToRedraw(); + } + + if (this.isTileComplete(cached)) { + tiles.push(cached); + } else { + tileLoading = true; + if (terrainTileMode) { + incompleteTiles.set(tileId, cached); + } + } + } + } else { + tileLoading = loading = true; + const hitLimit = loadingLimit && (loadingCount + preLoadingCount[0]) > loadingLimit; + if (!this._tileQueueIds.has(tile.id) && !hitLimit && (!map.isInteracting() || (map.isMoving() || map.isRotating()))) { + loadingCount++; + const key = tileId; + tileQueue[key] = tile; + } + } + } + + if (terrainTileMode && !isParentTile) { + if (tiles.length === tilesCount) { + missingTiles.push(tile); + } else { + checkedTiles[tile.id] = 1; + // if (tile.parent) { + // dirtyParentTiles.add(tile.parent); + // } + } + } + + if (terrainTileMode) continue; + if (isParentTile) continue; + if (!tileLoading) continue; + if (checkedTiles[tileId]) continue; + + checkedTiles[tileId] = 1; + if (placeholder && !placeholderKeys[tileId]) { + //tell gl renderer not to bind gl buffer with image + tile.cache = false; + placeholders.push({ + image: placeholder, + info: tile + }); + + placeholderKeys[tileId] = 1; + } + + const children = this._findChildTiles(tile); + if (children.length) { + children.forEach(c => { + if (!childKeys[c.info.id]) { + childTiles.push(c); + childKeys[c.info.id] = 1; + } + }); + } + // (children.length !== 4) means it's not complete, we still need a parent tile + if (!children.length || children.length !== 4) { + const parentTile = this._findParentTile(tile); + if (parentTile) { + const parentId = parentTile.info.id; + if (parentKeys[parentId] === undefined) { + parentKeys[parentId] = parentTiles.length; + parentTiles.push(parentTile); + }/* else { + //replace with parentTile of above tiles + parentTiles[parentKeys[parentId]] = parentTile; + } */ + } + } + } + } + + // 遍历 missingTiles , + const missedTiles = []; + if (terrainTileMode) { + for (let i = 0; i < missingTiles.length; i++) { + const tile = missingTiles[i].info ? missingTiles[i].info : missingTiles[i]; + if (!tile.parent || checkedTiles[tile.id]) { + continue; + } + + const { tiles: children, missedTiles: childMissedTiles } = this._findChildTiles(tile); + if (children.length) { + pushIn(tiles, children); + pushIn(missedTiles, childMissedTiles); + continue; + } else if (incompleteTiles.has(tile.id)) { + tiles.push(incompleteTiles.get(tile.id)); + incompleteTiles.delete(tile.id); + continue; + } + + checkedTiles[tile.id] = 1; + missedTiles.push(tile); + // continue; + + // // 以下是瓦片合并的优化,但一方面优化效果并不明显,且让渲染逻辑变得复杂,故暂时放弃 + // if (dirtyParentTiles.has(tile.parent) || tile.z < this._tileZoom) { + // // 如果sibling tile已经被加载过,或者是远处的上级瓦片,则直接加入missedTiles + // checkedTiles[tile.id] = 1; + // missedTiles.push(tile); + // } else { + // // 遍历当前级别瓦片,如果四个sibling tile都没有加载,则把parentTile加入到missedTiles,减少要处理的瓦片数量 + // let parentTile = parentKeys[tile.parent]; + // if (parentTile || parentTile === null) { + // // parentTile已被处理过 + // // 1. parentTile存在,则parentTile已经被加入到missedTile,作为parentTile的儿子瓦片的tile可以忽略 + // // 2. parentTile不存在,则把当前瓦片加入到missedTile + // if (parentTile === null) { + // checkedTiles[tile.id] = 1; + // missedTiles.push(tile); + // } + // continue; + // } + // // 只查询上一级的parentTile + // parentTile = this._findParentTile(tile, 1) || undefined; + // if (parentTile && parentTile.image) { + // // 父级瓦片存在,则把parentTile放入到tiles列表直接绘制 + // tiles.push(parentTile); + // parentKeys[tile.parent] = parentTile; + // } else { + // const parentTileInfo = layer.tileInfoCache.get(tile.parent); + // // 根据parentTileInfo是否存在,选择把parentTileInfo或者tile加入到missedTiles + // if (parentTileInfo) { + // if (!checkedTiles[parentTileInfo.id]) { + // checkedTiles[parentTileInfo.id] = 1; + // missedTiles.push(parentTileInfo); + // } + // parentKeys[tile.parent] = parentTileInfo; + // } else { + // checkedTiles[tile.id] = 1; + // missedTiles.push(tile); + // parentKeys[tile.parent] = null; + // } + // } + // } + } + } + + this.tileCache.shrink(); + + // if (parentTiles.length) { + // childTiles.length = 0; + // this._childTiles.length = 0; + // } + return { + childTiles, missedTiles, parentTiles, tiles, incompleteTiles: incompleteTiles && Array.from(incompleteTiles.values()), placeholders, loading, loadingCount, tileQueue + }; + } + + removeTileCache(tileId: TileId) { + delete this.tilesInView[tileId]; + this.tileCache.remove(tileId); + } + + isTileCachedOrLoading(tileId: TileId) { + return this.tileCache.get(tileId) || this.tilesInView[tileId] || this.tilesLoading[tileId]; + } + + isTileCached(tileId: TileId) { + return !!(this.tileCache.get(tileId) || this.tilesInView[tileId]); + } + + isTileFadingIn(tileImage: Tile['image']) { + return this.getTileFadingOpacity(tileImage) < 1; + } + + //@internal + _drawTiles(tiles, parentTiles, childTiles, placeholders, parentContext, missedTiles, incompleteTiles) { + if (parentTiles.length) { + //closer the latter (to draw on top) + // parentTiles.sort((t1, t2) => Math.abs(t2.info.z - this._tileZoom) - Math.abs(t1.info.z - this._tileZoom)); + parentTiles.sort(this._compareTiles); + this._parentTiles = parentTiles; + } + if (childTiles.length) { + this._childTiles = childTiles; + this._childTiles.sort(this._compareTiles); + } + + let drawBackground = true; + const backgroundTimestamp = this.canvas._parentTileTimestamp; + if (this.layer.constructor === TileLayer || this.layer.constructor === WMSTileLayer) { + // background tiles are only painted once for TileLayer and WMSTileLayer per frame. + if (this._renderTimestamp === backgroundTimestamp) { + drawBackground = false; + } else { + this.canvas._parentTileTimestamp = this._renderTimestamp; + } + } + + const context = { tiles, parentTiles: this._parentTiles, childTiles: this._childTiles, parentContext }; + this.onDrawTileStart(context, parentContext); + + if (drawBackground && this.layer.options['opacity'] === 1) { + this.layer._silentConfig = true; + const fadingAnimation = this.layer.options['fadeAnimation']; + this.layer.options['fadeAnimation'] = false; + + this._drawChildTiles(childTiles, parentContext); + this._drawParentTiles(this._parentTiles, parentContext); + + this.layer.options['fadeAnimation'] = fadingAnimation; + this.layer._silentConfig = false; + } + + this.drawingCurrentTiles = true; + tiles.sort(this._compareTiles); + for (let i = 0, l = tiles.length; i < l; i++) { + this._drawTileAndCache(tiles[i], parentContext); + } + delete this.drawingCurrentTiles; + + if (drawBackground && this.layer.options['opacity'] < 1) { + this.layer._silentConfig = true; + const fadingAnimation = this.layer.options['fadeAnimation']; + this.layer.options['fadeAnimation'] = false; + + this._drawChildTiles(childTiles, parentContext); + this._drawParentTiles(this._parentTiles, parentContext); + + this.layer.options['fadeAnimation'] = fadingAnimation; + this.layer._silentConfig = false; + } + + // placeholders.forEach(t => this._drawTile(t.info, t.image, parentContext)); + + this.onDrawTileEnd(context, parentContext); + + } + + //@internal + _drawChildTiles(childTiles, parentContext) { + // _hasOwnSR 时,瓦片之间会有重叠,会产生z-fighting,所以背景瓦片要后绘制 + this.drawingChildTiles = true; + childTiles.forEach(t => this._drawTile(t.info, t.image, parentContext)); + delete this.drawingChildTiles; + } + + //@internal + _drawParentTiles(parentTiles, parentContext) { + this.drawingParentTiles = true; + parentTiles.forEach(t => this._drawTile(t.info, t.image, parentContext)); + delete this.drawingParentTiles; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onDrawTileStart(context: RenderContext, parentContext: RenderContext) { } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onDrawTileEnd(context: RenderContext, parentContext: RenderContext) { } + + //@internal + _drawTile(info, image, parentContext) { + if (image) { + this.drawTile(info, image, parentContext); + } + } + + drawTile(tileInfo: Tile['info'], tileImage: Tile['image'], parentContext?: RenderContext) { + } + + //@internal + _drawTileAndCache(tile: Tile, parentContext) { + if (this.isValidCachedTile(tile)) { + this.tilesInView[tile.info.id] = tile; + } + this._drawTile(tile.info, tile.image, parentContext); + } + + drawOnInteracting(event: any, timestamp: number, context) { + this.draw(timestamp, context); + } + + needToRedraw(): boolean { + const map = this.getMap(); + if (this._tileQueue.length) { + return true; + } + if (map.getPitch()) { + return super.needToRedraw(); + } + if (map.isInteracting()) { + return true; + } + return super.needToRedraw(); + } + + hitDetect(): boolean { + return false; + } + + /** + * @private + * limit tile number to load when map is interacting + */ + //@internal + _getLoadLimit(): number { + if (this.getMap().isInteracting()) { + return this.layer.options['loadingLimitOnInteracting']; + } + return this.layer.options['loadingLimit'] || 0; + } + + + clear(): void { + this.retireTiles(true); + this.tileCache.reset(); + this.tilesInView = {}; + this.tilesLoading = {}; + this._tileQueue = []; + this._tileQueueIds.clear(); + this._parentTiles = []; + this._childTiles = []; + super.clear(); + } + + //@internal + _isLoadingTile(tileId: TileId): boolean { + return !!this.tilesLoading[tileId]; + } + + clipCanvas(context): boolean { + // const mask = this.layer.getMask(); + // if (!mask) { + // return this._clipByPitch(context); + // } + return super.clipCanvas(context); + } + + loadTileQueue(tileQueue): void { + for (const p in tileQueue) { + if (tileQueue.hasOwnProperty(p)) { + const tile = tileQueue[p]; + const tileImage = this.loadTile(tile); + if (tileImage.loadTime === undefined) { + // tile image's loading may not be async + this.tilesLoading[tile['id']] = { + image: tileImage, + current: true, + info: tile + }; + } + } + } + } + + loadTile(tile: Tile['info']): Tile['image'] { + let tileImage = {} as Tile['image']; + // fixme: 无相关定义,是否实现? + // @ts-expect-error todo + if (this.loadTileBitmap) { + const onLoad = (bitmap) => { + this.onTileLoad(bitmap, tile); + }; + const onError = (error, image) => { + this.onTileError(image, tile, error); + }; + // @ts-expect-error todo + this.loadTileBitmap(tile['url'], tile, onLoad, onError); + } else if (this._tileImageWorkerConn && this.loadTileImage === this.constructor.prototype.loadTileImage) { + this._fetchImage(tileImage, tile); + } else { + const tileSize = this.layer.getTileSize(tile.layer); + tileImage = new Image() as Tile['image']; + + // @ts-expect-error todo + tileImage.width = tileSize['width']; + // @ts-expect-error todo + tileImage.height = tileSize['height']; + + (tileImage as any).onload = this.onTileLoad.bind(this, tileImage, tile); + (tileImage as any).onerror = this.onTileError.bind(this, tileImage, tile); + + this.loadTileImage(tileImage, tile['url']); + } + return tileImage; + } + + //@internal + _fetchImage(image: any, tile: Tile['info']) { + if (image instanceof Image) { + image.src = tile.url; + } else { + const { x, y } = tile; + const workerId = Math.abs(x + y) % this._tileImageWorkerConn.workers.length; + this._tileImageWorkerConn.fetchImage(tile.url, workerId, (err, data) => { + if (err) { + this.onTileError(image, tile, err); + } else { + getImageBitMap(data, (bitmap: Tile['image']) => { + this.onTileLoad(bitmap, tile); + }); + } + }, this.layer.options['fetchOptions'] || { + referrer: document.location.href, + headers: { accept: 'image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8' } + }); + } + } + + loadTileImage(tileImage, url: string) { + const crossOrigin = this.layer.options['crossOrigin']; + if (!isNil(crossOrigin)) { + tileImage.crossOrigin = crossOrigin; + } + return loadImage(tileImage, [url]); + } + + abortTileLoading(tileImage: Tile['image'], tileInfo: Tile['info']): void { + if (tileInfo && tileInfo.id !== undefined) { + this.removeTileLoading(tileInfo); + } + if (!tileImage) return; + if (tileImage instanceof Image) { + tileImage.onload = falseFn; + tileImage.onerror = falseFn; + tileImage.src = emptyImageUrl; + } + } + + onTileLoad(tileImage: Tile['image'], tileInfo: Tile['info']): void { + this.removeTileLoading(tileInfo); + this._tileQueue.push({ tileInfo: tileInfo, tileData: tileImage }); + this._tileQueueIds.add(tileInfo.id); + this.setToRedraw(); + } + + removeTileLoading(tileInfo: Tile['info']): void { + delete this.tilesLoading[tileInfo.id]; + // need to setToRedraw to let tiles blocked by loadingLimit continue to load + this.setToRedraw(); + } + + //@internal + _consumeTileQueue(): void { + let count = 0; + const limit = this.layer.options['tileLimitPerFrame']; + const queue = this._tileQueue; + /* eslint-disable no-unmodified-loop-condition */ + while (queue.length && (limit <= 0 || count < limit)) { + const { tileData, tileInfo } = queue.shift(); + if (!this._tileQueueIds.has(tileInfo.id)) { + continue; + } + this._tileQueueIds.delete(tileInfo.id); + if (!this.checkTileInQueue(tileData, tileInfo)) { + continue; + } + this.consumeTile(tileData, tileInfo); + count++; + } + /* eslint-enable no-unmodified-loop-condition */ + } + + //@internal + _computeAvgTileAltitude() { + let sumMin = 0; + let sumMax = 0; + let count = 0; + for (const p in this.tilesInView) { + const info = this.tilesInView[p] && this.tilesInView[p].info; + if (info) { + sumMin += info.minAltitude || 0; + sumMax += info.maxAltitude || 0; + count++; + } + } + this.avgMinAltitude = sumMin / count; + this.avgMaxAltitude = sumMax / count; + } + + // Parameters tileImage and tileInfo are required in VectorTileLayerRenderer + checkTileInQueue(tileImage: Tile['image'], tileInfo: Tile['info']): boolean { + return true; + } + + consumeTile(tileImage: Tile['image'], tileInfo: Tile['info']): void { + if (!this.layer) { + return; + } + if (!this.tilesInView) { + // removed + return; + } + const e = { tile: tileInfo, tileImage: tileImage }; + // let user update tileImage in listener if needed + tileImage = e.tileImage; + this.resetTileLoadTime(tileImage); + this.removeTileLoading(tileInfo); + this._addTileToCache(tileInfo, tileImage); + /** + * tileload event, fired when tile is loaded. + * + * @event TileLayer#tileload + * @type {Object} + * @property {String} type - tileload + * @property {TileLayer} target - tile layer + * @property {Object} tileInfo - tile info + * @property {Image} tileImage - tile image + */ + this.layer.fire('tileload', e); + this.setToRedraw(); + } + + resetTileLoadTime(tileImage: Tile['image']): void { + // loadTime = 0 means a tile from onTileError + if (tileImage.loadTime !== 0) { + tileImage.loadTime = now(); + } + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onTileError(tileImage: Tile['image'], tileInfo: Tile['info'], error?: any) { + if (!this.layer) { + return; + } + // example: + /* reloadErrorTileFunction: (layer, renderer, tileInfo, tileImage) => { + const url = tileInfo.url; + // check if need to reload, e.g. server return 500 status code temporarily + if (needReload) { + renderer.loadTile(tileInfo, tileImage); + } + } */ + const reloadErrorTileFunction = this.layer.options['reloadErrorTileFunction']; + if (reloadErrorTileFunction) { + reloadErrorTileFunction.call(this, this.layer, this, tileInfo, tileImage); + return; + } + // tileImage.onerrorTick = tileImage.onerrorTick || 0; + // const tileRetryCount = this.layer.options['tileRetryCount']; + // if (tileRetryCount > tileImage.onerrorTick) { + // tileImage.onerrorTick++; + // this._fetchImage(tileImage, tileInfo); + // this.removeTileLoading(tileInfo); + // return; + // } + const errorUrl = this.layer.options['errorUrl']; + if (errorUrl) { + if ((tileImage instanceof Image) && tileImage.src !== errorUrl) { + tileImage.src = errorUrl; + this.removeTileLoading(tileInfo); + return; + } else { + tileImage = new Image() as any; + (tileImage as HTMLImageElement).src = errorUrl; + } + } + this.abortTileLoading(tileImage, tileInfo); + + tileImage.loadTime = 0; + this.removeTileLoading(tileInfo); + this._addTileToCache(tileInfo, tileImage); + this.setToRedraw(); + /** + * tileerror event, fired when tile loading has error. + * + * @event TileLayer#tileerror + * @type {Object} + * @property {String} type - tileerror + * @property {TileLayer} target - tile layer + * @property {Object} tileInfo - tile info + */ + this.layer.fire('tileerror', { tile: tileInfo }); + } + + getDebugInfo(tileId: TileId): string { + const xyz = tileId.split('_'); + const length = xyz.length; + return xyz[length - 3] + '/' + xyz[length - 2] + '/' + xyz[length - 1]; + } + + findChildTiles(info: Tile['info']) { + return this._findChildTiles(info); + } + + //@internal + _findChildTiles(info: Tile['info']): Tile[] | any { + const layer = this._getLayerOfTile(info.layer); + const terrainTileMode = layer && layer.options['terrainTileMode'] && layer._isPyramidMode(); + if (!layer || !layer.options['background'] && !terrainTileMode || info.z > this.layer.getMaxZoom()) { + return EMPTY_ARRAY; + } + const map = this.getMap(); + const children = []; + if (layer._isPyramidMode()) { + if (!terrainTileMode) { + // a faster one + const layer = this._getLayerOfTile(info.layer); + const zoomDiff = 2; + const cx = info.x * 2; + const cy = info.y * 2; + const cz = info.z + 1; + const queue = []; + for (let j = 0; j < 2; j++) { + for (let jj = 0; jj < 2; jj++) { + queue.push(cx + j, cy + jj, cz); + } + } + while (queue.length) { + const z = queue.pop(); + const y = queue.pop(); + const x = queue.pop(); + const id = layer._getTileId(x, y, z, info.layer); + const canVisit = z + 1 <= info.z + zoomDiff; + const tile = this.tileCache.getAndRemove(id); + if (tile) { + if (this.isValidCachedTile(tile)) { + children.push(tile); + this.tileCache.add(id, tile); + } else if (canVisit) { + for (let j = 0; j < 2; j++) { + for (let jj = 0; jj < 2; jj++) { + queue.push(x * 2 + j, y * 2 + jj, z + 1); + } + } + } + } else if (canVisit) { + for (let j = 0; j < 2; j++) { + for (let jj = 0; jj < 2; jj++) { + queue.push(x * 2 + j, y * 2 + jj, z + 1); + } + } + } + } + return children; + } + let missedTiles; + if (terrainTileMode) { + missedTiles = []; + } + // const zoomDiff = 2; + const cx = info.x * 2; + const cy = info.y * 2; + const cz = info.z + 1; + // const queue = []; + // for the sake of performance, we only traverse next 2 levels of children tiles + const candidates = []; + for (let i = 0; i < 2; i++) { + for (let ii = 0; ii < 2; ii++) { + const x = cx + i; + const y = cy + ii; + const z = cz; + const id = layer._getTileId(x, y, z, info.layer); + const tile = this.tileCache.getAndRemove(id); + if (tile && this.isValidCachedTile(tile)) { + children.push(tile); + this.tileCache.add(id, tile); + candidates.push(null); + } else { + // 缺少offset + candidates.push(id); + } + } + } + + // children.length等于4时,说明4个一级子瓦片都放入了children中 + if (children.length < 4) { + let index = 0; + for (let i = 0; i < 2; i++) { + for (let ii = 0; ii < 2; ii++) { + const id = candidates[index++]; + if (!id) { + continue; + } + const x = cx + i; + const y = cy + ii; + const z = cz; + const childrenCount = children.length; + const childCandidates = []; + for (let j = 0; j < 2; j++) { + for (let jj = 0; jj < 2; jj++) { + const xx = x * 2 + j; + const yy = y * 2 + jj; + const zz = z + 1; + const id = layer._getTileId(xx, yy, zz, info.layer); + const childTile = this.tileCache.getAndRemove(id); + if (childTile && this.isValidCachedTile(childTile)) { + children.push(childTile); + this.tileCache.add(id, childTile); + childCandidates.push(null); + } else { + childCandidates.push(id); + } + } + } + if (!terrainTileMode) { + continue; + } + if (children.length - childrenCount < 4) { + const childTileInfo = layer.tileInfoCache.get(id) || layer._createChildNode(info, i, ii, [0, 0], id); + if (children.length - childrenCount === 0) { + // 四个二级子瓦片都没有被缓存,直接将当前的一级子瓦片tileInfo放入missedTiles + missedTiles.push(childTileInfo); + } else { + // 四个二级子瓦片有被缓存的,将没有被缓存的tileInfo加入missedTiles + let index = 0; + for (let j = 0; j < 2; j++) { + for (let jj = 0; jj < 2; jj++) { + const id = childCandidates[index++]; + if (!id) { + // 这个二级子瓦片已经被加入到了children + continue; + } + const grandsonTileInfo = this.layer.tileInfoCache.get(id) || layer._createChildNode(childTileInfo, j, jj, [0, 0], id); + missedTiles.push(grandsonTileInfo); + } + } + } + } + + } + } + } + return terrainTileMode ? { tiles: children, missedTiles } : children; + } + const zoomDiff = 1; + const res = info.res; + const min = info.extent2d.getMin(), + max = info.extent2d.getMax(), + pmin = layer._project(map._pointToPrjAtRes(min, res, TEMP_POINT1), TEMP_POINT1), + pmax = layer._project(map._pointToPrjAtRes(max, res, TEMP_POINT2), TEMP_POINT2); + + for (let i = 1; i < zoomDiff; i++) { + this._findChildTilesAt(children, pmin, pmax, layer, info.z + i); + } + + return children; + } + + //@internal + _findChildTilesAt(children: Tile[], pmin: number, pmax: number, layer: any, childZoom: number) { + const layerId = layer.getId(), + res = layer.getSpatialReference().getResolution(childZoom); + if (!res) { + return; + } + const dmin = layer._getTileConfig().getTileIndex(pmin, res), + dmax = layer._getTileConfig().getTileIndex(pmax, res); + const sx = Math.min(dmin.idx, dmax.idx), ex = Math.max(dmin.idx, dmax.idx); + const sy = Math.min(dmin.idy, dmax.idy), ey = Math.max(dmin.idy, dmax.idy); + let id, tile; + for (let i = sx; i < ex; i++) { + for (let ii = sy; ii < ey; ii++) { + id = layer._getTileId(i, ii, childZoom, layerId); + tile = this.tileCache.getAndRemove(id); + if (tile) { + if (this.isValidCachedTile(tile)) { + children.push(tile); + this.tileCache.add(id, tile); + } + } + } + } + } + + findParentTile(info: Tile['info'], targetDiff?: number): Tile { + return this._findParentTile(info, targetDiff); + } + + //@internal + _findParentTile(info: Tile['info'], targetDiff?: number): Tile { + const map = this.getMap(), + layer = this._getLayerOfTile(info.layer); + if (!layer || !layer.options['background'] && !layer.options['terrainTileMode']) { + return null; + } + const minZoom = layer.getMinZoom(); + const zoomDiff: number = targetDiff || info.z - minZoom; + if (layer._isPyramidMode()) { + const endZoom = info.z - zoomDiff; + for (let z = info.z - 1; z >= endZoom; z--) { + const diff = info.z - z; + const scale = Math.pow(2, diff); + const x = Math.floor(info.x / scale); + const y = Math.floor(info.y / scale); + let id; + if (z === info.z - 1) { + id = info.parent; + } else { + id = layer._getTileId(x, y, z, info.layer); + } + const tile = this.tileCache.getAndRemove(id); + if (tile) { + if (this.isValidCachedTile(tile)) { + this.tileCache.add(id, tile); + return tile; + } + } + } + return null; + } + const sr = layer.getSpatialReference(); + // const zoomOffset = layer.options['zoomOffset']; + const d = sr.getZoomDirection(); + const res = info.res; + const center = info.extent2d.getCenter(), + prj = layer._project(map._pointToPrjAtRes(center, res)); + for (let diff = 1; diff <= zoomDiff; diff++) { + const z = info.z - d * diff; + const res = sr.getResolution(z); + if (!res) continue; + const tileIndex = layer._getTileConfig().getTileIndex(prj, res); + const id = layer._getTileId(tileIndex.x, tileIndex.y, z, info.layer); + const tile = this.tileCache.getAndRemove(id); + if (tile) { + this.tileCache.add(id, tile); + return tile; + } + } + return null; + } + + isValidCachedTile(tile: Tile): boolean { + return !!tile.image; + } + + isTileComplete(tile: Tile) { + return true; + } + + //@internal + _getLayerOfTile(layerId: LayerId) { + return this.layer.getChildLayer ? this.layer.getChildLayer(layerId) : this.layer; + } + + getCachedTile(tile: Tile, isParent: boolean) { + const tileId = tile.id; + const tilesInView = this.tilesInView; + let cached = this.tileCache.getAndRemove(tileId); + if (cached) { + if (!isParent) { + tilesInView[tileId] = cached; + } + const tilesLoading = this.tilesLoading; + if (tilesLoading && tilesLoading[tileId]) { + this.markCurrent(tilesLoading[tileId], false); + const { image, info } = tilesLoading[tileId]; + this.abortTileLoading(image, info); + delete tilesLoading[tileId]; + } + } else { + cached = tilesInView[tileId]; + } + if (cached) { + cached.current = true; + if (this.isValidCachedTile(cached)) { + this.tileCache.add(tileId, cached); + } + } + return cached; + } + + //@internal + _addTileToCache(tileInfo: Tile['info'], tileImage: Tile['image']) { + if (this.isValidCachedTile({ info: tileInfo, image: tileImage } as Tile)) { + const cached = { + image: tileImage, + info: tileInfo + } as Tile; + this.tileCache.add(tileInfo.id, cached); + } + } + + getTileOpacity(tileImage: Tile['image'], tileInfo: Tile['info']): number { + let opacity = this.getTileFadingOpacity(tileImage); + if (this.layer.getChildLayer) { + // in GroupTileLayer + const childLayer = this.layer.getLayer(tileInfo.layer); + if (childLayer) { + opacity *= childLayer.options['opacity']; + } + } + return opacity; + } + + getTileFadingOpacity(tileImage: Tile['image']): number { + if (!this.layer.options['fadeAnimation'] || !tileImage.loadTime) { + return 1; + } + return Math.min(1, (now() - tileImage.loadTime) / this.layer.options['fadeDuration']); + } + + onRemove(): void { + this.clear(); + delete this.tileCache; + delete this._tilePlaceHolder; + delete this._tileZoom; + super.onRemove(); + } + + markCurrent(tile: Tile, isCurrent?: boolean): void { + tile.current = isCurrent; + } + + markTiles(): number[] { + let a = 0, b = 0; + if (this.tilesLoading) { + for (const p in this.tilesLoading) { + this.markCurrent(this.tilesLoading[p], false); + a++; + } + } + if (this.tilesInView) { + for (const p in this.tilesInView) { + this.markCurrent(this.tilesInView[p], false); + b++; + } + } + return [a, b]; + } + + retireTiles(force?: boolean): void { + for (const i in this.tilesLoading) { + const tile = this.tilesLoading[i]; + if (force || !tile.current) { + // abort loading tiles + if (tile.image) { + this.abortTileLoading(tile.image, tile.info); + } + this.deleteTile(tile); + this.removeTileLoading(tile.info); + } + } + for (const i in this.tilesInView) { + const tile = this.tilesInView[i]; + if (!tile.current) { + delete this.tilesInView[i]; + if (!this.tileCache.has(i)) { + this.deleteTile(tile); + } + } + } + } + + deleteTile(tile: Tile): void { + if (!tile || !tile.image) { + return; + } + const tileId = tile.info.id; + if (this._tileQueueIds.has(tileId)) { + this._tileQueueIds.delete(tileId); + } + if ((tile.image as any).close) { + (tile.image as any).close(); + } + if (tile.image instanceof Image) { + tile.image.onload = null; + tile.image.onerror = null; + } + } + + //@internal + _generatePlaceHolder(res: number): HTMLCanvasElement { + const map = this.getMap(); + const placeholder = this.layer.options['placeholder']; + if (!placeholder || map.getPitch()) { + return null; + } + const tileSize = this.layer.getTileSize(); + const scale = res / map._getResolution(); + const canvas = this._tilePlaceHolder = this._tilePlaceHolder || Canvas.createCanvas(1, 1, map.CanvasClass); + canvas.width = tileSize.width * scale; + canvas.height = tileSize.height * scale; + if (isFunction(placeholder)) { + placeholder(canvas); + } else { + defaultPlaceholder(canvas); + } + return canvas; + } + + setTerrainHelper(helper: TerrainHelper) { + this._terrainHelper = helper; + } + } + return renderable; +} + +export default TileLayerRenderable; + +function falseFn(): boolean { return false; } + +function defaultPlaceholder(canvas: HTMLCanvasElement): void { + const ctx = canvas.getContext('2d'), + cw = canvas.width, ch = canvas.height, + w = cw / 16, h = ch / 16; + ctx.beginPath(); + for (let i = 0; i < 16; i++) { + ctx.moveTo(0, i * h); + ctx.lineTo(cw, i * h); + ctx.moveTo(i * w, 0); + ctx.lineTo(i * w, ch); + } + ctx.strokeStyle = 'rgba(180, 180, 180, 0.1)'; + ctx.lineWidth = 1; + ctx.stroke(); + ctx.beginPath(); + const path = [ + [0, 0], [cw, 0], [0, ch], [cw, ch], [0, 0], [0, ch], [cw, 0], [cw, ch], [0, ch / 2], [cw, ch / 2], [cw / 2, 0], [cw / 2, ch] + ]; + for (let i = 1; i < path.length; i += 2) { + ctx.moveTo(path[i - 1][0], path[i - 1][1]); + ctx.lineTo(path[i][0], path[i][1]); + } + ctx.lineWidth = 1 * 4; + ctx.stroke(); +} + +function compareTiles(a: Tile, b: Tile): number { + return Math.abs(this._tileZoom - a.info.z) - Math.abs(this._tileZoom - b.info.z); +} + +export type TileId = string; +export type LayerId = string | number; +export type TerrainHelper = any; +export type TileImage = (HTMLImageElement | HTMLCanvasElement | ImageBitmap) & { + loadTime: number; + glBuffer?: TileImageBuffer; + texture?: TileImageTexture; + // onerrorTick?: number; +} + +export interface Tile { + id: TileId; + info: { + x: number; + y: number; + z: number; + idx: number; + idy: number; + id: TileId; + layer: number | string; + children: []; + error: number; + offset: [number, number]; + extent2d: Extent; + res: number; + url: string; + parent: any; + cache?: boolean; + // todo:检查是否存在定义 + minAltitude?: number; + maxAltitude?: number; + //@internal + _glScale: number; + }; + + image: TileImage; + current?: boolean; +} + +export type RenderContext = any; + +export type TilesInViewType = { + [key: string]: Tile; +} + +export interface TileGrid { + extent: Extent; + count: number; + tiles: Tile[]; + parents: any[]; + offset: number[]; + zoom: number; +} + +export interface TileGrids { + count: number; + tileGrids: TileGrid[]; +} diff --git a/packages/map/src/renderer/layer/tilelayer/index.ts b/packages/map/src/renderer/layer/tilelayer/index.ts index d7c9256754..b38a368dc8 100644 --- a/packages/map/src/renderer/layer/tilelayer/index.ts +++ b/packages/map/src/renderer/layer/tilelayer/index.ts @@ -1,11 +1,13 @@ import TileLayerCanvasRenderer from './TileLayerCanvasRenderer'; -// import TileLayerGLRenderer from './TileLayerGLRenderer'; +import TileLayerGLRenderer from './TileLayerGLRenderer'; +import TileLayerRendererable from './TileLayerRendererable'; import { CanvasTileLayerCanvasRenderer, CanvasTileLayerGLRenderer } from './CanvasTileLayerRenderer'; import QuadStencil from './QuadStencil'; export { TileLayerCanvasRenderer, - // TileLayerGLRenderer, + TileLayerGLRenderer, + TileLayerRendererable, CanvasTileLayerCanvasRenderer, CanvasTileLayerGLRenderer, QuadStencil diff --git a/packages/map/src/renderer/layer/vectorlayer/index.ts b/packages/map/src/renderer/layer/vectorlayer/index.ts index f3e431843e..06c9f6d80a 100644 --- a/packages/map/src/renderer/layer/vectorlayer/index.ts +++ b/packages/map/src/renderer/layer/vectorlayer/index.ts @@ -1,5 +1,7 @@ import OverlayLayerCanvasRenderer from './OverlayLayerCanvasRenderer'; +import VectorLayerCanvasRenderer from './VectorLayerCanvasRenderer'; export { - OverlayLayerCanvasRenderer + OverlayLayerCanvasRenderer, + VectorLayerCanvasRenderer }; diff --git a/packages/map/src/renderer/map/MapCanvasRenderer.ts b/packages/map/src/renderer/map/MapCanvasRenderer.ts index 6a1e9e00d6..2f5c13b450 100644 --- a/packages/map/src/renderer/map/MapCanvasRenderer.ts +++ b/packages/map/src/renderer/map/MapCanvasRenderer.ts @@ -1,8 +1,9 @@ -import { IS_NODE, requestAnimFrame, cancelAnimFrame, equalMapView, calCanvasSize } from '../../core/util'; -import { createEl, preventSelection, computeDomPosition, removeDomNode } from '../../core/util/dom'; +import { IS_NODE, isNumber, isFunction, requestAnimFrame, cancelAnimFrame, equalMapView, calCanvasSize, pushIn } from '../../core/util'; +import { createEl, preventSelection, computeDomPosition } from '../../core/util/dom'; import { GlobalEvent, EVENT_DOC_DRAGEND, EVENT_DOC_VISIBILITY_CHANGE, EVENT_DPR_CHANGE, EVENT_DOC_DRAGSTART } from './../../core/GlobalEvent'; import Browser from '../../core/Browser'; import Point from '../../geo/Point'; +import Canvas2D from '../../core/Canvas'; import MapRenderer from './MapRenderer'; import Map, { type PanelDom } from '../../map/Map'; import CollisionIndex from '../../core/CollisionIndex'; @@ -39,6 +40,10 @@ class MapCanvasRenderer extends MapRenderer { //@internal _resizeEventList: ResizeObserverEntry[]; + //@internal + _needClear: boolean; + //@internal + _canvasUpdated: boolean; //@internal _isViewChanged: WithUndef; //@internal @@ -70,7 +75,7 @@ class MapCanvasRenderer extends MapRenderer { //@internal _tops: (EditHandle | EditOutline)[]; - context: any; + context: CanvasRenderingContext2D; canvas: HTMLCanvasElement; topLayer: HTMLCanvasElement; topCtx: CanvasRenderingContext2D; @@ -105,7 +110,7 @@ class MapCanvasRenderer extends MapRenderer { this._handleResizeEventList(framestamp); //not render anything when map container is hide if (map.options['stopRenderOnOffscreen'] && this._containerIsOffscreen()) { - return false; + return true; } this._updateDomPosition(framestamp); delete this._isViewChanged; @@ -113,12 +118,18 @@ class MapCanvasRenderer extends MapRenderer { this.updateMapDOM(); map.clearCollisionIndex(); const layers = this._getAllLayerToRender(); - const updated = this.drawLayers(layers, framestamp); - // const updated = this.drawLayerCanvas(layers); + this.drawLayers(layers, framestamp); + const updated = this.drawLayerCanvas(layers); if (updated) { // when updated is false, should escape drawing tops and centerCross to keep handle's alpha this.drawTops(); + this._drawCenterCross(); + if (map.options['debugSky']) { + this._debugSky(); + } } + this._needClear = false; + // this._drawContainerExtent(); // CAUTION: the order to fire frameend and layerload events // fire frameend before layerload, reason: // 1. frameend is often used internally by maptalks and plugins @@ -129,8 +140,9 @@ class MapCanvasRenderer extends MapRenderer { // It must be before events and frame callback, because map state may be changed in callbacks. this._mapview = this._getMapView(); delete this._spatialRefChanged; - this._fireLayerLoadEvents(layers); + this._fireLayerLoadEvents(); this.executeFrameCallbacks(); + this._canvasUpdated = false; //loop ui Collides map.uiCollides(); return true; @@ -156,42 +168,97 @@ class MapCanvasRenderer extends MapRenderer { //need redraw all layer,cause by collision/crs change/view change etc... //@internal - _checkIfNeedToRedrawLayers(layers: Layer[]) { + _needRedrawAllLayers(layers: Layer[]) { if (this.isSpatialReferenceChanged()) { return true; } - for (let i = 0, len = layers.length; i < len; i++) { - if (this._checkLayerRedraw(layers[i])) { + const needRedrawLayers: Layer[] = []; + layers.forEach(layer => { + if (!layer) { + return; + } + //always check layer need to redraw + const needsRedraw = layer._toRedraw = this._checkLayerRedraw(layer); + if (needsRedraw) { + needRedrawLayers.push(layer); + const childLayers = layer.getLayers && layer.getLayers(); + if (childLayers && Array.isArray(childLayers)) { + pushIn(needRedrawLayers, childLayers); + } + } + }); + for (let i = 0, len = needRedrawLayers.length; i < len; i++) { + const layer = needRedrawLayers[i]; + const layerOptions = layer && layer.options; + if (layerOptions && layerOptions.collision && layerOptions.collisionScope === 'map') { return true; } + //other condition if need } return false; } drawLayers(layers: Layer[], framestamp: number) { - const needRedraw = this._checkIfNeedToRedrawLayers(layers); - if (!needRedraw && !this.map.options['forceRedrawPerFrame']) { - return false; - } - this.clearCanvas(); + const needRedrawAllLayers = this._needRedrawAllLayers(layers); + const map = this.map, isInteracting = map.isInteracting(), + // all the visible canvas layers' ids. + canvasIds: string[] = [], + // all the updated canvas layers's ids. + updatedIds: string[] = [], + fps = map.options['fpsOnInteracting'] || 0, + timeLimit = fps === 0 ? 0 : 1000 / fps, + // time of layer drawing + layerLimit = this.map.options['layerCanvasLimitOnInteracting'], l = layers.length; + const baseLayer = map.getBaseLayer(); + let t = 0; for (let i = 0; i < l; i++) { const layer = layers[i]; if (!layer.isVisible()) { continue; } const isCanvas = layer.isCanvasRender(); + if (isCanvas) { + canvasIds.push(layer.getId()); + } const renderer = layer._getRenderer(); if (!renderer) { continue; } - if (isCanvas) { - this.clearLayerCanvasContext(layer); + // if need to call layer's draw/drawInteracting + const needsRedraw = needRedrawAllLayers || layer._toRedraw; + if (isCanvas && renderer.isCanvasUpdated()) { + // don't need to call layer's draw/drawOnInteracting but need to redraw layer's updated canvas + if (!needsRedraw) { + updatedIds.push(layer.getId()); + } + this.setLayerCanvasUpdated(); } + const transformMatrix = renderer.__zoomTransformMatrix; + delete renderer.__zoomTransformMatrix; + if (!needsRedraw) { + if (isCanvas && isInteracting) { + if (map.isZooming() && !map.getPitch()) { + // transform layer's current canvas when zooming + renderer.prepareRender(); + renderer.__zoomTransformMatrix = this._zoomMatrix; + } else if (map.getPitch() || map.isRotating()) { + // when map is pitching or rotating, clear the layer canvas + // otherwise, leave layer's canvas unchanged + renderer.clearCanvas(); + } + } + continue; + } + if (isInteracting && isCanvas) { - this._drawCanvasLayerOnInteracting(layer, framestamp); + if (layerLimit > 0 && l - 1 - i > layerLimit && layer !== baseLayer) { + layer._getRenderer().clearCanvas(); + continue; + } + t += this._drawCanvasLayerOnInteracting(layer, t, timeLimit, framestamp); } else if (isInteracting && renderer.drawOnInteracting) { // dom layers if (renderer.prepareRender) { @@ -206,9 +273,32 @@ class MapCanvasRenderer extends MapRenderer { } else { // map is not interacting, call layer's render renderer.render(framestamp); + //地图缩放完以后,如果下一次render需要载入资源,仍需要设置transformMatrix + //防止在资源载入完成之前,缺少transformMatrix导致的绘制错误 + if (isCanvas && transformMatrix && renderer.isLoadingResource()) { + renderer.__zoomTransformMatrix = transformMatrix; + } + } + + if (isCanvas) { + updatedIds.push(layer.getId()); + this.setLayerCanvasUpdated(); + } + } + // compare: + // 1. previous drawn layers and current drawn layers + // 2. previous canvas layers and current canvas layers + // set map to redraw if either changed + const preCanvasIds = this._canvasIds || []; + const preUpdatedIds = this._updatedIds || []; + this._canvasIds = canvasIds; + this._updatedIds = updatedIds; + if (!this.isLayerCanvasUpdated()) { + const sep = '---'; + if (preCanvasIds.join(sep) !== canvasIds.join(sep) || preUpdatedIds.join(sep) !== updatedIds.join(sep)) { + this.setLayerCanvasUpdated(); } } - return true; } /** @@ -217,6 +307,9 @@ class MapCanvasRenderer extends MapRenderer { */ //@internal _checkLayerRedraw(layer: Layer): boolean { + if (this.isSpatialReferenceChanged()) { + return true; + } const map = this.map; const renderer = layer._getRenderer(); if (!renderer) { @@ -243,23 +336,47 @@ class MapCanvasRenderer extends MapRenderer { * @private */ //@internal - _drawCanvasLayerOnInteracting(layer: Layer, framestamp: number): number { - const renderer = layer._getRenderer(); + _drawCanvasLayerOnInteracting(layer: Layer, t: number, timeLimit: number, framestamp: number): number { + const map = this.map, + renderer = layer._getRenderer(), + drawTime = renderer.getDrawTime(), + inTime = timeLimit === 0 || timeLimit > 0 && t + drawTime <= timeLimit; if (renderer.mustRenderOnInteracting && renderer.mustRenderOnInteracting()) { renderer.render(framestamp); - } else if (renderer.drawOnInteracting) { + } else if (renderer.drawOnInteracting && + (layer === map.getBaseLayer() || inTime || + map.isZooming() && layer.options['forceRenderOnZooming'] || + map.isMoving() && layer.options['forceRenderOnMoving'] || + map.isRotating() && layer.options['forceRenderOnRotating']) + ) { // call drawOnInteracting to redraw the layer renderer.prepareRender(); - const canvas = renderer.prepareCanvas(); + renderer.prepareCanvas(); if (renderer.checkAndDraw) { // for canvas renderers renderer.checkAndDraw(renderer.drawOnInteracting, this._eventParam, framestamp); } else { renderer.drawOnInteracting(this._eventParam, framestamp); } - return canvas; + return drawTime; + } else if (map.isZooming() && !map.getPitch() && !map.isRotating()) { + // when: + // 1. layer's renderer doesn't have drawOnInteracting + // 2. timeLimit is exceeded + // then: + // transform layer's current canvas when zooming + renderer.prepareRender(); + renderer.__zoomTransformMatrix = this._zoomMatrix; + } else if (map.getPitch() || map.isRotating()) { + // when map is pitching or rotating, clear the layer canvas + // otherwise, leave layer's canvas unchanged + renderer.clearCanvas(); } - return null; + if (renderer.drawOnInteracting && !inTime) { + // @ts-expect-error 我也不知道怎么办,不敢乱动,可能插件里需要? + renderer.onSkipDrawOnInteracting(this._eventParam, framestamp); + } + return 0; } /** @@ -268,29 +385,127 @@ class MapCanvasRenderer extends MapRenderer { * @private */ //@internal - _fireLayerLoadEvents(layers: Layer[]) { - //firing order as FIFO, painting as FILO, so the order needs to be reversed - for (let i = layers.length - 1; i >= 0; i--) { - const layer = layers[i]; - const renderer = layer._getRenderer(); - if (!renderer || !renderer.isRenderComplete()) { + _fireLayerLoadEvents() { + if (this._updatedIds && this._updatedIds.length > 0) { + const map = this.map; + //firing order as FIFO, painting as FILO, so the order needs to be reversed + this._updatedIds.reverse().forEach(id => { + const layer = map.getLayer(id); + if (!layer) { + return; + } + const renderer = layer._getRenderer(); + if (!renderer || !renderer.isRenderComplete()) { + return; + } + /** + * layerload event, fired when layer is loaded. + * + * @event Layer#layerload + * @type {Object} + * @property {String} type - layerload + * @property {Layer} target - layer + */ + layer.fire('layerload'); + }); + } + + } + + isLayerCanvasUpdated() { + return this._canvasUpdated; + } + + setLayerCanvasUpdated() { + this._canvasUpdated = true; + } + + /** + * Renders the layers + */ + drawLayerCanvas(layers: Layer[]) { + const map = this.map; + if (!map) { + return false; + } + if (!this.isLayerCanvasUpdated() && !this.isViewChanged() && this._needClear === false) { + return false; + } + if (!this.canvas) { + this.createCanvas(); + } + + /** + * renderstart event, an event fired when map starts to render. + * @event Map#renderstart + * @type {Object} + * @property {String} type - renderstart + * @property {Map} target - the map fires event + * @property {CanvasRenderingContext2D} context - canvas context + */ + map._fireEvent('renderstart', { + 'context': this.context + }); + + if (!this._updateCanvasSize()) { + this.clearCanvas(); + } + + const interacting = map.isInteracting(), + limit = map.options['layerCanvasLimitOnInteracting']; + let len = layers.length; + + let baseLayerImage; + const images = []; + for (let i = 0; i < len; i++) { + if (!layers[i].isVisible() || !layers[i].isCanvasRender()) { continue; } - /** - * layerload event, fired when layer is loaded. - * - * @event Layer#layerload - * @type {Object} - * @property {String} type - layerload - * @property {Layer} target - layer - */ - layer.fire('layerload'); + const renderer = layers[i]._getRenderer(); + if (!renderer) { + continue; + } + const layerImage = this._getLayerImage(layers[i]); + if (layerImage && layerImage['image']) { + if (layers[i] === map.getBaseLayer()) { + baseLayerImage = [layers[i], layerImage]; + } else { + images.push([layers[i], layerImage]); + } + } + } + + const targetWidth = this.canvas.width; + const targetHeight = this.canvas.height; + if (baseLayerImage) { + this._drawLayerCanvasImage(baseLayerImage[0], baseLayerImage[1], targetWidth, targetHeight); + this._drawFog(); + } + + len = images.length; + const start = interacting && limit >= 0 && len > limit ? len - limit : 0; + for (let i = start; i < len; i++) { + this._drawLayerCanvasImage(images[i][0], images[i][1], targetWidth, targetHeight); } + + /** + * renderend event, an event fired when map ends rendering. + * @event Map#renderend + * @type {Object} + * @property {String} type - renderend + * @property {Map} target - the map fires event + * @property {CanvasRenderingContext2D} context - canvas context + */ + map._fireEvent('renderend', { + 'context': this.context + }); + return true; } setToRedraw() { const layers = this._getAllLayerToRender(); //set maprender for clear canvas + this._needClear = true; for (let i = 0, l = layers.length; i < l; i++) { const renderer = layers[i].getRenderer(); if (renderer && renderer.canvas && renderer.setToRedraw) { @@ -348,6 +563,7 @@ class MapCanvasRenderer extends MapRenderer { if (this._resizeObserver) { this._resizeObserver.disconnect(); } + delete this.context; delete this.canvas; delete this.map; delete this._spatialRefChanged; @@ -398,6 +614,15 @@ class MapCanvasRenderer extends MapRenderer { map._trySetCursor(cursor); } + //@internal + _getLayerImage(layer: Layer) { + const renderer = layer._getRenderer(); + if (renderer.getCanvasImage) { + return renderer.getCanvasImage(); + } + return null; + } + /** * initialize container DOM of panels */ @@ -546,16 +771,177 @@ class MapCanvasRenderer extends MapRenderer { } } + //@internal + _drawLayerCanvasImage(layer: Layer, layerImage: any, targetWidth?: number, targetHeight?: number) { + const ctx = this.context; + const point = layerImage['point'].round(); + const dpr = this.map.getDevicePixelRatio(); + if (dpr !== 1) { + point._multi(dpr); + } + const canvasImage = layerImage['image']; + const width = canvasImage.width, height = canvasImage.height; + if (point.x + width <= 0 || point.y + height <= 0) { + return; + } + //opacity of the layer image + let op = layer.options['opacity']; + if (!isNumber(op)) { + op = 1; + } + if (op <= 0) { + return; + } + let imgOp = layerImage['opacity']; + if (!isNumber(imgOp)) { + imgOp = 1; + } + if (imgOp <= 0) { + return; + } + const alpha = ctx.globalAlpha; + + if (op < 1) { + ctx.globalAlpha *= op; + } + if (imgOp < 1) { + ctx.globalAlpha *= imgOp; + } + if (layer.options['cssFilter']) { + ctx.filter = layer.options['cssFilter']; + } + const renderer = layer.getRenderer(); + const matrix = renderer.__zoomTransformMatrix; + const clipped = renderer.clipCanvas(this.context as any); + if (matrix) { + ctx.save(); + ctx.setTransform(...(matrix as any)); + } + + /*let outlineColor = layer.options['debugOutline']; + if (outlineColor) { + if (outlineColor === true) { + outlineColor = '#0f0'; + } + this.context.strokeStyle = outlineColor; + this.context.fillStyle = outlineColor; + this.context.lineWidth = 10; + Canvas2D.rectangle(ctx, point, layerImage.size, 1, 0); + ctx.fillText([layer.getId(), point.toArray().join(), layerImage.size.toArray().join(), canvasImage.width + ',' + canvasImage.height].join(' '), + point.x + 18, point.y + 18); + }*/ + + ctx.drawImage(canvasImage, 0, 0, width, height, point.x, point.y, targetWidth, targetHeight); + if (matrix) { + ctx.restore(); + } + if (clipped) { + ctx.restore(); + } + if (ctx.filter !== 'none') { + ctx.filter = 'none'; + } + ctx.globalAlpha = alpha; + } + + //@internal + _drawCenterCross() { + const cross = this.map.options['centerCross']; + if (cross) { + const ctx = this.context; + const p = new Point(this.canvas.width / 2, this.canvas.height / 2); + if (isFunction(cross)) { + cross(ctx, p); + } else { + Canvas2D.drawCross(this.context, p.x, p.y, 2, '#f00'); + } + } + } + + //@internal + _drawContainerExtent() { + const { cascadePitches } = this.map.options; + const h30 = this.map.height - this.map._getVisualHeight(cascadePitches[0]); + const h60 = this.map.height - this.map._getVisualHeight(cascadePitches[1]); + + const extent = this.map.getContainerExtent(); + const ctx = this.context; + ctx.beginPath(); + ctx.moveTo(0, extent.ymin); + ctx.lineTo(extent.xmax, extent.ymin); + ctx.stroke(); + + + ctx.beginPath(); + ctx.moveTo(0, h30); + ctx.lineTo(extent.xmax, h30); + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(0, h60); + ctx.lineTo(extent.xmax, h60); + ctx.stroke(); + // console.log(extent.ymin, h30, h60); + } + + //@internal + _drawFog() { + const map = this.map; + if (map.getPitch() <= map.options['maxVisualPitch'] || !map.options['fog']) { + return; + } + const fogThickness = 30; + const r = map.getDevicePixelRatio(); + const ctx = this.context, + clipExtent = map.getContainerExtent(); + let top = (map.height - map._getVisualHeight(75)) * r; + if (top < 0) top = 0; + const bottom = clipExtent.ymin * r, + h = Math.ceil(bottom - top), + color = map.options['fogColor'].join(); + const gradient = ctx.createLinearGradient(0, top, 0, bottom + fogThickness); + const landscape = 1 - fogThickness / (h + fogThickness); + gradient.addColorStop(0, `rgba(${color}, 0)`); + gradient.addColorStop(0.3, `rgba(${color}, 0.3)`); + gradient.addColorStop(landscape, `rgba(${color}, 1)`); + gradient.addColorStop(1, `rgba(${color}, 0)`); + ctx.beginPath(); + ctx.fillStyle = gradient; + ctx.fillRect(0, top, Math.ceil(clipExtent.getWidth()) * r, Math.ceil(h + fogThickness)); + } + + //@internal + _debugSky() { + const map = this.map; + if (!map) { + return this; + } + const height = map.getContainerExtent().ymin; + if (height <= 0) { + return this; + } + const ctx = this.context; + ctx.strokeStyle = 'red'; + ctx.strokeRect(0, 0, map.width, height); + return this; + } + //@internal _getAllLayerToRender() { return this.map._getLayers(); } + clearCanvas() { + if (!this.canvas) { + return; + } + Canvas2D.clearRect(this.context, 0, 0, this.canvas.width, this.canvas.height); + } //@internal _updateCanvasSize() { if (!this.canvas || this._containerIsCanvas) { - return; + return false; } const map = this.map, mapSize = map.getSize(), @@ -567,53 +953,29 @@ class MapCanvasRenderer extends MapRenderer { canvas.style.width = cssWidth; canvas.style.height = cssHeight; } - if (width !== canvas.width || height !== canvas.height) { - canvas.width = width; - canvas.height = height; - } - const topLayer = this.topLayer; - if (topLayer && (width !== topLayer.width || height !== topLayer.height)) { - topLayer.width = width; - topLayer.height = height; - topLayer.style.width = cssWidth; - topLayer.style.height = cssHeight; + if (width === canvas.width && height === canvas.height) { + return false; } - } - - createCanvas() { - this.canvas = createEl('canvas') as HTMLCanvasElement; - const panels = this.map.getPanels(); - const canvasContainer = panels.canvasContainer; - canvasContainer.appendChild(this.canvas); - this._updateCanvasSize(); - this.createContext(); - } - - createContext() { - // should be implemented by child class - } - - clearLayerCanvasContext(_layer) { - // should be implemented by child class - } + //retina屏支持 - clearCanvas() { - // should be implemented by child class + canvas.height = height; + canvas.width = width; + this.topLayer.width = canvas.width; + this.topLayer.height = canvas.height; + return true; } - // canvas for tops - createTopCanvas() { + createCanvas() { this.topLayer = createEl('canvas') as HTMLCanvasElement; - const panels = this.map.getPanels(); - const canvasContainer = panels.canvasContainer; - canvasContainer.insertBefore(this.topLayer, this.canvas); this.topCtx = this.topLayer.getContext('2d'); - } - - removeTopCanvas() { - removeDomNode(this.topLayer); - delete this.topLayer; - delete this.topCtx; + if (this._containerIsCanvas) { + this.canvas = this.map.getContainer() as HTMLCanvasElement; + } else { + this.canvas = createEl('canvas') as HTMLCanvasElement; + this._updateCanvasSize(); + this.map.getPanels().canvasContainer.appendChild(this.canvas); + } + this.context = this.canvas.getContext('2d'); } //@internal @@ -789,24 +1151,13 @@ class MapCanvasRenderer extends MapRenderer { } drawTops() { - const tops = this.getTopElements(); - if (tops.length) { - if (!this.topCtx) { - this.createTopCanvas(); - } - } else { - if (this.topCtx) { - this.removeTopCanvas(); - } - return; - } // clear topLayer this.topCtx.clearRect(0, 0, this.topLayer.width, this.topLayer.height); const collisionIndex = tempCollisionIndex; collisionIndex.clear(); this.map.fire('drawtopstart'); this.map.fire('drawtops'); - + const tops = this.getTopElements(); let updated = false; const dpr = this.map.getDevicePixelRatio(); const geos = []; @@ -838,6 +1189,13 @@ class MapCanvasRenderer extends MapRenderer { } } +Map.registerRenderer('canvas', MapCanvasRenderer); + +Map.mergeOptions({ + 'fog': false, + 'fogColor': [233, 233, 233] +}); + export type MapView = { x: number; y: number; @@ -849,5 +1207,3 @@ export type MapView = { } export default MapCanvasRenderer; - - diff --git a/packages/map/src/renderer/map/MapGLAbstractRenderer.ts b/packages/map/src/renderer/map/MapGLAbstractRenderer.ts new file mode 100644 index 0000000000..ca5eb2af50 --- /dev/null +++ b/packages/map/src/renderer/map/MapGLAbstractRenderer.ts @@ -0,0 +1,853 @@ +import { IS_NODE, requestAnimFrame, cancelAnimFrame, equalMapView, calCanvasSize } from '../../core/util'; +import { createEl, preventSelection, computeDomPosition, removeDomNode } from '../../core/util/dom'; +import { GlobalEvent, EVENT_DOC_DRAGEND, EVENT_DOC_VISIBILITY_CHANGE, EVENT_DPR_CHANGE, EVENT_DOC_DRAGSTART } from './../../core/GlobalEvent'; +import Browser from '../../core/Browser'; +import Point from '../../geo/Point'; +import MapRenderer from './MapRenderer'; +import Map, { type PanelDom } from '../../map/Map'; +import CollisionIndex from '../../core/CollisionIndex'; +import GlobalConfig from '../../GlobalConfig'; +import type EditHandle from '../edit/EditHandle'; +import type EditOutline from '../edit/EditOutline'; +import type { Layer } from '../../layer'; +import type Size from '../../geo/Size'; +import type { WithUndef } from '../../types/typings'; + +const tempCollisionIndex = new CollisionIndex(); + +/** + * 基于 Canvas2D 的 map 渲染器 + * + * @english + * Renderer class based on HTML5 Canvas for maps. + * @class + * @protected + * @extends {renderer.MapRenderer} + * @memberOf renderer + */ +class MapGLAbstractRenderer extends MapRenderer { + //@internal + _containerIsCanvas: boolean; + //@internal + _loopTime: number; + //@internal + _resizeTime: number; + //@internal + _resizeCount: number; + //@internal + _frameCycleRenderCount: number; + //@internal + _resizeEventList: ResizeObserverEntry[]; + + //@internal + _isViewChanged: WithUndef; + //@internal + _spatialRefChanged: WithUndef; + //@internal + _resizeObserver: ResizeObserver; + //@internal + _resizeInterval: number; + //@internal + _checkSizeInterval: number; + //@internal + _hitDetectFrame: number; + //@internal + _animationFrame: number; + //@internal + _mapview: MapView; + //@internal + _zoomMatrix: number[]; + //@internal + _eventParam: any; + //@internal + _canvasIds: string[]; + //@internal + _updatedIds: string[]; + //@internal + _frameTimestamp: number; + //@internal + _checkPositionTime: number; + //@internal + _tops: (EditHandle | EditOutline)[]; + + context: any; + canvas: HTMLCanvasElement; + topLayer: HTMLCanvasElement; + topCtx: CanvasRenderingContext2D; + + /** + * @param map - map for the renderer + */ + constructor(map: Map) { + super(map); + //container is a element + this._containerIsCanvas = !!(map.getContainer() as HTMLCanvasElement).getContext; + this._registerEvents(); + this._loopTime = 0; + this._resizeEventList = []; + this._resizeTime = -Infinity; + this._frameCycleRenderCount = 0; + } + + load() { + this.initContainer(); + } + + /** + * render layers in current frame + * @returns return false to cease frame loop + */ + renderFrame(framestamp: number): boolean { + const map = this.map; + if (!map || !map.options['renderable']) { + return false; + } + this._handleResizeEventList(framestamp); + //not render anything when map container is hide + if (map.options['stopRenderOnOffscreen'] && this._containerIsOffscreen()) { + return false; + } + this._updateDomPosition(framestamp); + delete this._isViewChanged; + map._fireEvent('framestart'); + this.updateMapDOM(); + map.clearCollisionIndex(); + const layers = this._getAllLayerToRender(); + const updated = this.drawLayers(layers, framestamp); + // const updated = this.drawLayerCanvas(layers); + if (updated) { + // when updated is false, should escape drawing tops and centerCross to keep handle's alpha + this.drawTops(); + } + // CAUTION: the order to fire frameend and layerload events + // fire frameend before layerload, reason: + // 1. frameend is often used internally by maptalks and plugins + // 2. layerload is often used externally by tests or user apps + map._fireEvent('frameend'); + this._recordView(); + // refresh map's state + // It must be before events and frame callback, because map state may be changed in callbacks. + this._mapview = this._getMapView(); + delete this._spatialRefChanged; + this._fireLayerLoadEvents(layers); + this.executeFrameCallbacks(); + //loop ui Collides + map.uiCollides(); + return true; + } + + getFrameTimestamp() { + return this._frameTimestamp || 0; + } + + updateMapDOM() { + const map = this.map; + // when map is zooming, container is being transformed with matrix, panel doesn't need to be moved. + if (map.isZooming()) { + return; + } + const offset = map.getViewPointFrameOffset(); + if (offset) { + map.offsetPlatform(offset); + } else if (this.domChanged()) { + this.offsetPlatform(null, true); + } + } + + //need redraw all layer,cause by collision/crs change/view change etc... + //@internal + _checkIfNeedToRedrawLayers(layers: Layer[]) { + if (this.isSpatialReferenceChanged()) { + return true; + } + for (let i = 0, len = layers.length; i < len; i++) { + if (this._checkLayerRedraw(layers[i])) { + return true; + } + } + return false; + } + + drawLayers(layers: Layer[], framestamp: number) { + const needRedraw = this._checkIfNeedToRedrawLayers(layers); + if (!needRedraw && !this.map.options['forceRedrawPerFrame']) { + return false; + } + this.clearCanvas(); + const map = this.map, + isInteracting = map.isInteracting(), + l = layers.length; + for (let i = 0; i < l; i++) { + const layer = layers[i]; + if (!layer.isVisible()) { + continue; + } + const isCanvas = layer.isCanvasRender(); + const renderer = layer._getRenderer(); + if (!renderer) { + continue; + } + if (isCanvas) { + this.clearLayerCanvasContext(layer); + } + if (isInteracting && isCanvas) { + this._drawCanvasLayerOnInteracting(layer, framestamp); + } else if (isInteracting && renderer.drawOnInteracting) { + // dom layers + if (renderer.prepareRender) { + renderer.prepareRender(); + } + if (renderer.checkAndDraw) { + // for canvas renderers + renderer.checkAndDraw(renderer.drawOnInteracting, this._eventParam, framestamp); + } else { + renderer.drawOnInteracting(this._eventParam, framestamp); + } + } else { + // map is not interacting, call layer's render + renderer.render(framestamp); + } + } + return true; + } + + /** + * check if need to call layer's draw/drawInteracting + * @param layer + */ + //@internal + _checkLayerRedraw(layer: Layer): boolean { + const map = this.map; + const renderer = layer._getRenderer(); + if (!renderer) { + return false; + } + if (layer.isCanvasRender()) { + return renderer.testIfNeedRedraw(); + } else { + if (renderer.needToRedraw && renderer.needToRedraw()) { + return true; + } + // dom layers, redraw it if map is interacting or state is changed + return map.isInteracting() || this.isViewChanged(); + } + } + + /** + * Draw canvas rendered layer when map is interacting + * @param layer + * @param t current consumed time of layer drawing + * @param timeLimit time limit for layer drawing + * @param framestamp + * @returns time to draw this layer + * @private + */ + //@internal + _drawCanvasLayerOnInteracting(layer: Layer, framestamp: number): number { + const renderer = layer._getRenderer(); + if (renderer.mustRenderOnInteracting && renderer.mustRenderOnInteracting()) { + renderer.render(framestamp); + } else if (renderer.drawOnInteracting) { + // call drawOnInteracting to redraw the layer + renderer.prepareRender(); + const canvas = renderer.prepareCanvas(); + if (renderer.checkAndDraw) { + // for canvas renderers + renderer.checkAndDraw(renderer.drawOnInteracting, this._eventParam, framestamp); + } else { + renderer.drawOnInteracting(this._eventParam, framestamp); + } + return canvas; + } + return null; + } + + /** + * Fire layerload events. + * Make sure layer are drawn on map when firing the events + * @private + */ + //@internal + _fireLayerLoadEvents(layers: Layer[]) { + //firing order as FIFO, painting as FILO, so the order needs to be reversed + for (let i = layers.length - 1; i >= 0; i--) { + const layer = layers[i]; + const renderer = layer._getRenderer(); + if (!renderer || !renderer.isRenderComplete()) { + continue; + } + /** + * layerload event, fired when layer is loaded. + * + * @event Layer#layerload + * @type {Object} + * @property {String} type - layerload + * @property {Layer} target - layer + */ + layer.fire('layerload'); + } + } + + setToRedraw() { + const layers = this._getAllLayerToRender(); + //set maprender for clear canvas + for (let i = 0, l = layers.length; i < l; i++) { + const renderer = layers[i].getRenderer(); + if (renderer && renderer.canvas && renderer.setToRedraw) { + //to fix lost webgl context + renderer.setToRedraw(); + } + } + } + + updateMapSize(size: Size) { + if (!size || this._containerIsCanvas) { + return; + } + const width = size['width'] + 'px', + height = size['height'] + 'px'; + const panels = this.map.getPanels(); + panels.mapWrapper.style.width = width; + panels.mapWrapper.style.height = height; + this._updateCanvasSize(); + } + + getMainPanel() { + if (!this.map) { + return null; + } + if (this._containerIsCanvas) { + return this.map.getContainer(); + } + if (this.map.getPanels()) { + return this.map.getPanels().mapWrapper; + } + return null; + } + + toDataURL(mimeType: string, quality?: number) { + if (!this.canvas) { + return null; + } + return this.canvas.toDataURL(mimeType, quality); + } + + remove() { + if (Browser.webgl && typeof document !== 'undefined') { + GlobalEvent.off(EVENT_DPR_CHANGE, this._thisDocDPRChange, this); + GlobalEvent.off(EVENT_DOC_VISIBILITY_CHANGE, this._thisDocVisibilitychange, this); + GlobalEvent.off(EVENT_DOC_DRAGSTART, this._thisDocDragStart, this); + GlobalEvent.off(EVENT_DOC_DRAGEND, this._thisDocDragEnd, this); + // removeDomEvent(document, 'visibilitychange', this._thisDocVisibilitychange, this); + // removeDomEvent(document, 'dragstart', this._thisDocDragStart, this); + // removeDomEvent(document, 'dragend', this._thisDocDragEnd, this); + } + if (this._resizeInterval) { + clearInterval(this._resizeInterval); + } + if (this._resizeObserver) { + this._resizeObserver.disconnect(); + } + delete this.canvas; + delete this.map; + delete this._spatialRefChanged; + this._cancelFrameLoop(); + } + + hitDetect(point: Point) { + const map = this.map; + if (!map || !map.options['hitDetect'] || map.isInteracting()) { + return; + } + const layers = map._getLayers(); + let cursor = 'default'; + const limit = map.options['hitDetectLimit'] || 0; + let counter = 0; + if (point && point._round) { + // map size is decimal,containerPoint.x/containerPoint.y is decimal + point._round(); + } + for (let i = layers.length - 1; i >= 0; i--) { + const layer = layers[i]; + // 此处如果未开启,无需执行后面判断 + if (!layer.options['hitDetect'] || (layer.isEmpty && layer.isEmpty()) || !layer.options['geometryEvents']) { + continue; + } + const renderer = layer._getRenderer(); + if (!renderer || !renderer.hitDetect) { + continue; + } + if (renderer.isBlank && renderer.isBlank()) { + continue; + } + // renderer.hitDetect(point)) . This can't ignore the shadows. + /** + * TODO + * This requires a better way to judge + */ + if (layer.options['cursor'] !== 'default' && renderer.hitDetect(point)) { + cursor = layer.options['cursor'] || 'pointer'; + break; + } + counter++; + if (limit > 0 && counter > limit) { + break; + } + } + + map._trySetCursor(cursor); + } + + /** + * initialize container DOM of panels + */ + initContainer() { + const panels = this.map.getPanels(); + + function createContainer(name: string, className: string, cssText: string, enableSelect?: boolean): PanelDom { + const c = createEl('div', className) as PanelDom; + if (cssText) { + c.style.cssText = cssText; + } + panels[name] = c; + if (!enableSelect) { + preventSelection(c); + } + return c; + } + const containerDOM = this.map.getContainer(); + + if (this._containerIsCanvas) { + //container is a element. + return; + } + + containerDOM.innerHTML = ''; + + const POSITION0 = 'position:absolute;top:0px;left:0px;'; + + const mapWrapper = createContainer('mapWrapper', 'maptalks-wrapper', 'position:absolute;overflow:hidden;', true), + mapAllLayers = createContainer('allLayers', 'maptalks-all-layers', POSITION0 + 'padding:0px;margin:0px;z-index:0;overflow:visible;', true), + backStatic = createContainer('backStatic', 'maptalks-back-static', POSITION0 + 'z-index:0;', true), + back = createContainer('back', 'maptalks-back', POSITION0 + 'z-index:1;'), + backLayer = createContainer('backLayer', 'maptalks-back-layer', POSITION0), + canvasContainer = createContainer('canvasContainer', 'maptalks-canvas-layer', POSITION0 + 'border:none;z-index:2;'), + frontStatic = createContainer('frontStatic', 'maptalks-front-static', POSITION0 + 'z-index:3;', true), + front = createContainer('front', 'maptalks-front', POSITION0 + 'z-index:4;', true), + frontLayer = createContainer('frontLayer', 'maptalks-front-layer', POSITION0 + 'z-index:0;'), + // children's zIndex in frontLayer will be set by map.addLayer, ui container's z-index is set to 10000 to make sure it's always on the top. + ui = createContainer('ui', 'maptalks-ui', POSITION0 + 'border:none;z-index:1;', true), + control = createContainer('control', 'maptalks-control', 'z-index:1', true); + + containerDOM.appendChild(mapWrapper); + + mapAllLayers.appendChild(backStatic); + back.appendChild(backLayer); + back.layerDOM = backLayer; + mapAllLayers.appendChild(back); + mapAllLayers.appendChild(canvasContainer); + front.appendChild(frontLayer); + front.layerDOM = frontLayer; + front.uiDOM = ui; + mapAllLayers.appendChild(frontStatic); + mapAllLayers.appendChild(front); + front.appendChild(ui); + + mapWrapper.appendChild(mapAllLayers); + mapWrapper.appendChild(control); + + this.createCanvas(); + + this.resetContainer(); + const mapSize = this.map._getContainerDomSize(); + this.updateMapSize(mapSize); + } + + /** + * Is current map's state changed? + */ + isViewChanged() { + if (this._isViewChanged !== undefined) { + return this._isViewChanged; + } + const previous = this._mapview; + const view = this._getMapView(); + this._isViewChanged = !previous || !equalMapView(previous, view); + return this._isViewChanged; + } + + //@internal + _recordView() { + const map = this.map; + if (!map._onViewChange || map.isInteracting() || map.isAnimating()) { + return; + } + if (!equalMapView(map.getView(), map._getCurrentView())) { + map._onViewChange(map.getView()); + } + } + + isSpatialReferenceChanged() { + return this._spatialRefChanged; + } + + //@internal + _getMapView(): MapView { + const map = this.map; + const center = map._getPrjCenter(); + return { + x: center.x, + y: center.y, + zoom: map.getZoom(), + pitch: map.getPitch(), + bearing: map.getBearing(), + width: map.width, + height: map.height + }; + } + + //@internal + _lockFrameRenderEnable() { + const { maxFPS } = this.map.options || {}; + if (maxFPS <= 0 || GlobalConfig.maxFPS <= maxFPS) { + return true; + } + const count = Math.ceil(GlobalConfig.maxFPS / maxFPS); + return this._frameCycleRenderCount >= count; + } + + /** + * Main frame loop + */ + //@internal + _frameLoop(framestamp: number) { + if (!this.map) { + this._cancelFrameLoop(); + return; + } + this._frameCycleRenderCount++; + if (this._lockFrameRenderEnable()) { + framestamp = framestamp || 0; + this._frameTimestamp = framestamp; + this._resizeCount = 0; + this.renderFrame(framestamp); + this._frameCycleRenderCount = 0; + } else if (this.map.options.debug) { + console.log('skip frame ing,frameCycleRenderCount:', this._frameCycleRenderCount); + } + // Keep registering ourselves for the next animation frame + this._animationFrame = requestAnimFrame((framestamp: number) => { this._frameLoop(framestamp); }); + } + + //@internal + _cancelFrameLoop() { + if (this._animationFrame) { + cancelAnimFrame(this._animationFrame); + } + } + + //@internal + _getAllLayerToRender() { + return this.map._getLayers(); + } + + + //@internal + _updateCanvasSize() { + if (!this.canvas || this._containerIsCanvas) { + return; + } + const map = this.map, + mapSize = map.getSize(), + canvas = this.canvas, + r = map.getDevicePixelRatio(); + // width/height不变并不意味着 css width/height 不变 + const { width, height, cssWidth, cssHeight } = calCanvasSize(mapSize, r); + if (canvas.style && (canvas.style.width !== cssWidth || canvas.style.height !== cssHeight)) { + canvas.style.width = cssWidth; + canvas.style.height = cssHeight; + } + if (width !== canvas.width || height !== canvas.height) { + canvas.width = width; + canvas.height = height; + } + const topLayer = this.topLayer; + if (topLayer && (width !== topLayer.width || height !== topLayer.height)) { + topLayer.width = width; + topLayer.height = height; + topLayer.style.width = cssWidth; + topLayer.style.height = cssHeight; + } + } + + createCanvas() { + this.canvas = createEl('canvas') as HTMLCanvasElement; + const panels = this.map.getPanels(); + const canvasContainer = panels.canvasContainer; + canvasContainer.appendChild(this.canvas); + this._updateCanvasSize(); + this.createContext(); + } + + createContext() { + // should be implemented by child class + } + + clearLayerCanvasContext(_layer) { + // should be implemented by child class + } + + clearCanvas() { + // should be implemented by child class + } + + // canvas for tops + createTopCanvas() { + this.topLayer = createEl('canvas') as HTMLCanvasElement; + const panels = this.map.getPanels(); + const canvasContainer = panels.canvasContainer; + canvasContainer.insertBefore(this.topLayer, this.canvas); + this.topCtx = this.topLayer.getContext('2d'); + } + + removeTopCanvas() { + removeDomNode(this.topLayer); + delete this.topLayer; + delete this.topCtx; + } + + //@internal + _updateDomPosition(framestamp: number) { + if (this._checkPositionTime === undefined) { + this._checkPositionTime = -Infinity; + } + const dTime = Math.abs(framestamp - this._checkPositionTime); + if (dTime >= 500) { + // refresh map's dom position + computeDomPosition(this.map.getContainer()); + this._checkPositionTime = Math.min(framestamp, this._checkPositionTime); + } + return this; + } + + //@internal + _handleResizeEventList(time: number) { + if (!this._resizeEventList) { + return this; + } + const len = this._resizeEventList.length; + if (len === 0) { + return this; + } + if (this._resizeTime && time - this._resizeTime < 60) { + return this; + } + const contentRect = this._resizeEventList[len - 1].contentRect; + this.map.setContainerDomRect(contentRect); + this._resizeEventList = []; + this._checkSize(); + this._resizeCount = this._resizeCount || 0; + //force render all layers,这两句代码不能颠倒,因为要先重置所有图层的size,才能正确的渲染所有图层 + this.renderFrame((this._frameTimestamp || 0) + (++this._resizeCount) / 100); + this._resizeTime = time; + return this; + } + + //@internal + _checkSize() { + if (!this.map) { + return; + } + this.map.checkSize(); + } + + //@internal + _setCheckSizeInterval(interval: number) { + // ResizeObserver priority of use + // https://developer.mozilla.org/zh-CN/docs/Web/API/ResizeObserver + if (Browser.resizeObserver) { + if (this._resizeObserver) { + this._resizeObserver.disconnect(); + } + if (this.map) { + // eslint-disable-next-line no-unused-vars + this._resizeObserver = new ResizeObserver((entries) => { + if (!this.map || this.map.isRemoved()) { + this._resizeObserver.disconnect(); + } else if (entries.length) { + this._resizeEventList = this._resizeEventList || []; + this._resizeEventList.push(entries[0]); + } + }); + this._resizeObserver.observe(this.map.getContainer()); + } + } else { + clearInterval(this._resizeInterval); + this._checkSizeInterval = interval; + this._resizeInterval = setInterval(() => { + if (!this.map || this.map.isRemoved()) { + //is deleted + clearInterval(this._resizeInterval); + } else { + this._checkSize(); + } + }, this._checkSizeInterval) as unknown as number; + } + } + + //@internal + _registerEvents() { + const map = this.map; + + if (map.options['checkSize'] && !IS_NODE && (typeof window !== 'undefined')) { + this._setCheckSizeInterval(map.options['checkSizeInterval']); + } + if (!Browser.mobile) { + map.on('_mousemove', this._onMapMouseMove, this); + } + + map.on('_dragrotatestart _dragrotating _dragrotateend _movestart _moving _moveend _zoomstart', (param: any) => { + this._eventParam = param; + }); + + map.on('_zooming', (param: any) => { + if (!map.getPitch()) { + this._zoomMatrix = param['matrix']['container']; + } + this._eventParam = param; + }); + + map.on('_zoomend', (param: any) => { + this._eventParam = param; + delete this._zoomMatrix; + }); + + map.on('_spatialreferencechange', () => { + this._spatialRefChanged = true; + }); + + if (Browser.webgl && typeof document !== 'undefined') { + GlobalEvent.on(EVENT_DPR_CHANGE, this._thisDocDPRChange, this); + GlobalEvent.on(EVENT_DOC_VISIBILITY_CHANGE, this._thisDocVisibilitychange, this); + GlobalEvent.on(EVENT_DOC_DRAGSTART, this._thisDocDragStart, this); + GlobalEvent.on(EVENT_DOC_DRAGEND, this._thisDocDragEnd, this); + + // addDomEvent(document, 'visibilitychange', this._thisDocVisibilitychange, this); + // addDomEvent(document, 'dragstart', this._thisDocDragStart, this); + // addDomEvent(document, 'dragend', this._thisDocDragEnd, this); + } + } + + //@internal + _onMapMouseMove(param: any) { + const map = this.map; + if (map.isInteracting() || !map.options['hitDetect']) { + return; + } + if (this._hitDetectFrame) { + cancelAnimFrame(this._hitDetectFrame); + } + this._hitDetectFrame = requestAnimFrame(() => { + this.hitDetect(param['containerPoint']); + }); + } + + //@internal + _getCanvasLayers() { + return this.map._getLayers(layer => layer.isCanvasRender()); + } + + //----------- top elements methods ------------- + // edit handles or edit outlines + addTopElement(e: EditHandle | EditOutline) { + if (!this._tops) { + this._tops = []; + } + this._tops.push(e); + } + + removeTopElement(e: EditHandle | EditOutline) { + if (!this._tops) { + return; + } + const idx = this._tops.indexOf(e); + if (idx >= 0) { + this._tops.splice(idx, 1); + } + } + + getTopElements() { + return this._tops || []; + } + + sortTopElements() { + this._tops = this._tops.sort((top1, top2) => { + const zIndex1 = (top1.options || {}).zIndex || 0; + const zIndex2 = (top2.options || {}).zIndex || 0; + return zIndex2 - zIndex1; + }); + } + + drawTops() { + const tops = this.getTopElements(); + if (tops.length) { + if (!this.topCtx) { + this.createTopCanvas(); + } + } else { + if (this.topCtx) { + this.removeTopCanvas(); + } + return; + } + // clear topLayer + this.topCtx.clearRect(0, 0, this.topLayer.width, this.topLayer.height); + const collisionIndex = tempCollisionIndex; + collisionIndex.clear(); + this.map.fire('drawtopstart'); + this.map.fire('drawtops'); + + let updated = false; + const dpr = this.map.getDevicePixelRatio(); + const geos = []; + for (let i = 0; i < tops.length; i++) { + const top = tops[i]; + if (top.needCollision && top.needCollision()) { + const bbox = top.getRenderBBOX(dpr); + if (bbox) { + if (collisionIndex.collides(bbox)) { + const geometry = top.target && top.target._geometry; + if (geometry && geos.indexOf(geometry) === -1) { + geos.push(geometry); + geometry.fire('handlecollision'); + } + continue; + } else { + collisionIndex.insertBox(bbox); + } + } + } + if (top.render(this.topCtx)) { + updated = true; + } + } + if (updated) { + this.context.drawImage(this.topLayer, 0, 0); + } + this.map.fire('drawtopsend'); + } +} + +export type MapView = { + x: number; + y: number; + zoom: number; + pitch: number; + bearing: number; + width: number; + height: number; +} + +export default MapGLAbstractRenderer; + + From d2012f76c1a9a36493580f8bb61d189739b98923 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 11:13:07 +0800 Subject: [PATCH 24/53] @maptalks/maptalks => maptalks --- packages/analysis/package.json | 2 +- packages/analysis/src/Analysis.js | 2 +- packages/analysis/src/CrossCutAnalysis.js | 2 +- packages/analysis/src/ExcavateRenderer.js | 2 +- packages/analysis/src/common/Util.js | 2 +- packages/analysis/src/pass/CrossCutPass.js | 2 +- packages/analysis/src/pass/CutAnalysisController.js | 2 +- packages/analysis/src/pass/CutPass.js | 2 +- packages/analysis/src/pass/ExcavatePass.js | 2 +- packages/analysis/src/pass/ExtentPass.js | 2 +- packages/analysis/src/pass/FloodPass.js | 2 +- packages/analysis/src/pass/HeightmapPass.js | 2 +- packages/analysis/src/pass/InSightPass.js | 2 +- packages/analysis/src/pass/OutlinePass.js | 2 +- packages/analysis/src/pass/ViewShedPass.js | 2 +- packages/gl/package.json | 2 +- packages/gl/src/analysis/AnalysisPainter.js | 2 +- packages/gl/src/analysis/Area3DTool.js | 2 +- packages/gl/src/analysis/Distance3DTool.js | 2 +- packages/gl/src/analysis/Height3DTool.js | 2 +- packages/gl/src/analysis/Measure3DTool.js | 2 +- packages/gl/src/index-dev.js | 2 +- packages/gl/src/layer/GroupGLLayer.ts | 4 ++-- packages/gl/src/layer/GroupGLLayerRenderer.js | 2 +- packages/gl/src/layer/TileLayerGLRenderer.ts | 4 ++-- packages/gl/src/layer/mask/BoxClipMask.js | 2 +- packages/gl/src/layer/mask/Mask.js | 2 +- packages/gl/src/layer/mask/MaskLayerMixin.ts | 4 ++-- packages/gl/src/layer/raycaster/RayCaster.js | 4 ++-- packages/gl/src/layer/terrain/TerrainLayer.js | 2 +- packages/gl/src/layer/terrain/TerrainLayerRenderer.js | 2 +- packages/gl/src/layer/terrain/TerrainTileUtil.js | 2 +- packages/gl/src/layer/terrain/TerrainWorkerConnection.js | 2 +- packages/gl/src/layer/util/util.js | 2 +- packages/gl/src/layer/util/uvUniforms.js | 2 +- packages/gl/src/light/MapLights.js | 2 +- packages/gl/src/map/MapGLRenderer.js | 2 +- packages/gl/src/map/MapPostProcess.js | 2 +- packages/gltf-loader/package.json | 2 +- packages/layer-3dtiles/package.json | 2 +- packages/layer-3dtiles/src/layer/Geo3DTilesLayer.ts | 4 ++-- .../layer-3dtiles/src/layer/Geo3DTilesWorkerConnection.js | 2 +- .../layer-3dtiles/src/layer/renderer/Geo3DTilesRenderer.js | 2 +- .../layer-3dtiles/src/layer/renderer/TileMeshPainter.js | 2 +- packages/layer-gltf/package.json | 2 +- packages/layer-gltf/src/AEMarker.js | 2 +- packages/layer-gltf/src/EffectLine.js | 2 +- packages/layer-gltf/src/EffectRing.js | 2 +- packages/layer-gltf/src/GLTFLayer.js | 2 +- packages/layer-gltf/src/GLTFLayerRenderer.js | 2 +- packages/layer-gltf/src/GLTFLineString.js | 2 +- packages/layer-gltf/src/GLTFMarker.js | 2 +- packages/layer-gltf/src/GLTFMercatorGeometry.js | 2 +- packages/layer-gltf/src/MultiGLTFMarker.js | 2 +- packages/layer-gltf/src/common/AbstractGLTFLayer.js | 2 +- packages/layer-gltf/src/common/GLTFWorkerConnection.js | 2 +- packages/layer-gltf/src/common/GeoJSON.js | 2 +- packages/layer-gltf/src/common/Util.js | 2 +- packages/layer-video/package.json | 2 +- packages/layer-video/src/VideoLayer.ts | 2 +- packages/layer-video/src/VideoLayerRenderer.js | 2 +- packages/layer-video/src/VideoSurface.ts | 2 +- packages/map/package.json | 2 +- packages/maptalks-gl/package.json | 2 +- packages/traffic/package.json | 2 +- packages/traffic/src/TrafficScene.ts | 2 +- packages/transform-control/package.json | 2 +- packages/transform-control/src/TransformControl.js | 2 +- packages/transform-control/src/TransformTarget.js | 2 +- packages/transform-control/src/common/Util.js | 2 +- packages/vt/package.json | 2 +- packages/vt/src/common/IconRequestor.js | 2 +- packages/vt/src/layer/layer/GeojsonVectorTileLayer.ts | 4 ++-- packages/vt/src/layer/layer/VectorTileLayer.ts | 6 +++--- packages/vt/src/layer/plugins/Util.js | 2 +- packages/vt/src/layer/plugins/painters/CollisionPainter.js | 2 +- packages/vt/src/layer/plugins/painters/FillPainter.js | 2 +- packages/vt/src/layer/plugins/painters/IconPainter.js | 2 +- packages/vt/src/layer/plugins/painters/LinePainter.js | 2 +- packages/vt/src/layer/plugins/painters/MeshPainter.js | 2 +- packages/vt/src/layer/plugins/painters/Painter.js | 2 +- packages/vt/src/layer/plugins/painters/PhongPainter.js | 2 +- packages/vt/src/layer/plugins/painters/TubePainter.js | 2 +- .../vt/src/layer/plugins/painters/pbr/StandardPainter.js | 2 +- packages/vt/src/layer/plugins/painters/util/line_offset.js | 2 +- packages/vt/src/layer/renderer/VectorTileLayerRenderer.js | 2 +- .../src/layer/renderer/utils/convert_to_painter_features.js | 2 +- packages/vt/src/layer/renderer/worker/WorkerConnection.js | 2 +- packages/vt/src/layer/vector/ExtrudePolygonLayer.ts | 4 ++-- packages/vt/src/layer/vector/LineStringLayer.ts | 2 +- packages/vt/src/layer/vector/PointLayer.ts | 4 ++-- packages/vt/src/layer/vector/PolygonLayer.ts | 2 +- packages/vt/src/layer/vector/Vector3DLayer.ts | 4 ++-- packages/vt/src/layer/vector/Vector3DLayerRenderer.js | 2 +- packages/vt/src/layer/vector/util/convert_to_feature.js | 2 +- packages/vt/src/layer/vector/util/from_json.js | 2 +- packages/vt/src/packer/IconRequestor.js | 2 +- packages/vt/src/types/index.ts | 2 +- 98 files changed, 109 insertions(+), 109 deletions(-) diff --git a/packages/analysis/package.json b/packages/analysis/package.json index 8d82415132..427da955eb 100644 --- a/packages/analysis/package.json +++ b/packages/analysis/package.json @@ -48,7 +48,7 @@ "karma-expect": "^1.1.3", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", - "@maptalks/map": "workspace:*", + "maptalks": "workspace:*", "mocha": "^10.3.0", "pixelmatch": "^4.0.2", "rollup": "^4.17.2" diff --git a/packages/analysis/src/Analysis.js b/packages/analysis/src/Analysis.js index 1f9ebdd0b8..14504fe01f 100644 --- a/packages/analysis/src/Analysis.js +++ b/packages/analysis/src/Analysis.js @@ -1,5 +1,5 @@ import { reshader, mat4, quat, earcut } from '@maptalks/gl'; -import { Class, Eventable, Handlerable, Polygon } from '@maptalks/map'; +import { Class, Eventable, Handlerable, Polygon } from 'maptalks'; import ExtentPass from './pass/ExtentPass'; import { coordinateToWorld } from './common/Util'; diff --git a/packages/analysis/src/CrossCutAnalysis.js b/packages/analysis/src/CrossCutAnalysis.js index 39e0eb2cba..2e29c654c7 100644 --- a/packages/analysis/src/CrossCutAnalysis.js +++ b/packages/analysis/src/CrossCutAnalysis.js @@ -1,5 +1,5 @@ import Analysis from './Analysis'; -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { reshader, mat4 } from '@maptalks/gl'; import CrossCutPass from './pass/CrossCutPass'; import pickingVert from './pass/glsl/picking.vert'; diff --git a/packages/analysis/src/ExcavateRenderer.js b/packages/analysis/src/ExcavateRenderer.js index fea9c1d33e..756c6f0758 100644 --- a/packages/analysis/src/ExcavateRenderer.js +++ b/packages/analysis/src/ExcavateRenderer.js @@ -1,5 +1,5 @@ import { reshader, mat4, vec4 } from '@maptalks/gl'; -import { Util } from '@maptalks/map'; +import { Util } from 'maptalks'; import HeightmapPass from './pass/HeightmapPass'; import { coordinateToWorld, altitudeToDistance } from './common/Util'; import { ExtrudePolygonLayer } from '@maptalks/vt'; diff --git a/packages/analysis/src/common/Util.js b/packages/analysis/src/common/Util.js index 4d084db405..7aadcd5100 100644 --- a/packages/analysis/src/common/Util.js +++ b/packages/analysis/src/common/Util.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; export function containerPointToWorldPoint(point, map) { diff --git a/packages/analysis/src/pass/CrossCutPass.js b/packages/analysis/src/pass/CrossCutPass.js index 474c2506f2..d44687079b 100644 --- a/packages/analysis/src/pass/CrossCutPass.js +++ b/packages/analysis/src/pass/CrossCutPass.js @@ -1,7 +1,7 @@ import { reshader, mat4 } from '@maptalks/gl'; import vert from './glsl/crosscut.vert'; import frag from './glsl/crosscut.frag'; -import { Util } from '@maptalks/map'; +import { Util } from 'maptalks'; import AnalysisPass from './AnalysisPass'; export default class CrossCutPass extends AnalysisPass { diff --git a/packages/analysis/src/pass/CutAnalysisController.js b/packages/analysis/src/pass/CutAnalysisController.js index 417cc0d5e2..bc0d2dafc3 100644 --- a/packages/analysis/src/pass/CutAnalysisController.js +++ b/packages/analysis/src/pass/CutAnalysisController.js @@ -1,6 +1,6 @@ import { reshader, mat4, quat, vec3, vec2 } from '@maptalks/gl'; import partsModels from '../common/parts'; -import { Util } from '@maptalks/map'; +import { Util } from 'maptalks'; import { defined } from '../common/Util'; import pickingVert from './glsl/picking.vert'; diff --git a/packages/analysis/src/pass/CutPass.js b/packages/analysis/src/pass/CutPass.js index b9bf1b689b..e2b3beb351 100644 --- a/packages/analysis/src/pass/CutPass.js +++ b/packages/analysis/src/pass/CutPass.js @@ -2,7 +2,7 @@ import { mat4 } from '@maptalks/gl'; import { reshader } from '@maptalks/gl'; import vert from './glsl/cut.vert'; import frag from './glsl/cut.frag'; -import { Util } from '@maptalks/map'; +import { Util } from 'maptalks'; import CutAnalysisController from './CutAnalysisController'; import CutShader from './CutShader'; import AnalysisPass from './AnalysisPass'; diff --git a/packages/analysis/src/pass/ExcavatePass.js b/packages/analysis/src/pass/ExcavatePass.js index 30ecd697d9..2ebc67869c 100644 --- a/packages/analysis/src/pass/ExcavatePass.js +++ b/packages/analysis/src/pass/ExcavatePass.js @@ -2,7 +2,7 @@ import { mat4 } from '@maptalks/gl'; import { reshader } from '@maptalks/gl'; import vert from './glsl/excavate.vert'; import frag from './glsl/excavate.frag'; -import { Util } from '@maptalks/map'; +import { Util } from 'maptalks'; import AnalysisPass from './AnalysisPass'; const clearColor = [1.0, 0.0, 0.0, 1]; diff --git a/packages/analysis/src/pass/ExtentPass.js b/packages/analysis/src/pass/ExtentPass.js index f4e3727abf..17df8ce580 100644 --- a/packages/analysis/src/pass/ExtentPass.js +++ b/packages/analysis/src/pass/ExtentPass.js @@ -1,5 +1,5 @@ import { reshader, mat4 } from '@maptalks/gl'; -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import vert from './glsl/excavateExtent.vert'; import frag from './glsl/excavateExtent.frag'; import AnalysisPass from './AnalysisPass'; diff --git a/packages/analysis/src/pass/FloodPass.js b/packages/analysis/src/pass/FloodPass.js index ff0f100e20..4214931bee 100644 --- a/packages/analysis/src/pass/FloodPass.js +++ b/packages/analysis/src/pass/FloodPass.js @@ -1,7 +1,7 @@ import { reshader } from '@maptalks/gl'; import vert from './glsl/flood.vert'; import frag from './glsl/flood.frag'; -import { Util } from '@maptalks/map'; +import { Util } from 'maptalks'; import AnalysisPass from './AnalysisPass'; export default class FloodPass extends AnalysisPass { diff --git a/packages/analysis/src/pass/HeightmapPass.js b/packages/analysis/src/pass/HeightmapPass.js index b17c4333ee..13fcdfa3bb 100644 --- a/packages/analysis/src/pass/HeightmapPass.js +++ b/packages/analysis/src/pass/HeightmapPass.js @@ -1,5 +1,5 @@ import { reshader, mat4 } from '@maptalks/gl'; -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import vert from './glsl/heightmap.vert'; import frag from './glsl/heightmap.frag'; import AnalysisPass from './AnalysisPass'; diff --git a/packages/analysis/src/pass/InSightPass.js b/packages/analysis/src/pass/InSightPass.js index 8fd3e8ecd3..ba431a8cb8 100644 --- a/packages/analysis/src/pass/InSightPass.js +++ b/packages/analysis/src/pass/InSightPass.js @@ -2,7 +2,7 @@ import { mat4 } from '@maptalks/gl'; import { reshader } from '@maptalks/gl'; import vert from './glsl/insight.vert'; import frag from './glsl/insight.frag'; -import { Util } from '@maptalks/map'; +import { Util } from 'maptalks'; import AnalysisPass from './AnalysisPass'; const helperPos = [ diff --git a/packages/analysis/src/pass/OutlinePass.js b/packages/analysis/src/pass/OutlinePass.js index 7218f613ca..ba3b03782b 100644 --- a/packages/analysis/src/pass/OutlinePass.js +++ b/packages/analysis/src/pass/OutlinePass.js @@ -3,7 +3,7 @@ import quadVert from './glsl/quad.vert'; import extentFrag from './glsl/extent.frag'; import outlineFrag from './glsl/outline.frag'; import sceneVert from './glsl/sceneVert.vert'; -import { Util } from '@maptalks/map'; +import { Util } from 'maptalks'; import AnalysisPass from './AnalysisPass'; export default class OutlinePass extends AnalysisPass { diff --git a/packages/analysis/src/pass/ViewShedPass.js b/packages/analysis/src/pass/ViewShedPass.js index b6167ea707..9a8edea866 100644 --- a/packages/analysis/src/pass/ViewShedPass.js +++ b/packages/analysis/src/pass/ViewShedPass.js @@ -2,7 +2,7 @@ import { mat4, quat, vec3 } from '@maptalks/gl'; import { reshader } from '@maptalks/gl'; import vert from './glsl/viewshed.vert'; import frag from './glsl/viewshed.frag'; -import { Util, Point } from '@maptalks/map'; +import { Util, Point } from 'maptalks'; import AnalysisPass from './AnalysisPass'; const helperPos = [ diff --git a/packages/gl/package.json b/packages/gl/package.json index c70a0bece1..98b10c3893 100644 --- a/packages/gl/package.json +++ b/packages/gl/package.json @@ -33,7 +33,7 @@ "dependencies": { "@maptalks/fusiongl": "workspace:*", "@maptalks/gltf-loader": "workspace:*", - "@maptalks/map": "workspace:*", + "maptalks": "workspace:*", "@maptalks/martini": "^0.4.0", "@maptalks/regl": "^3.4.0", "@maptalks/reshader.gl": "workspace:*", diff --git a/packages/gl/src/analysis/AnalysisPainter.js b/packages/gl/src/analysis/AnalysisPainter.js index 115e5fb8e9..fbaa3644c0 100644 --- a/packages/gl/src/analysis/AnalysisPainter.js +++ b/packages/gl/src/analysis/AnalysisPainter.js @@ -1,7 +1,7 @@ import * as reshader from '@maptalks/reshader.gl'; import AnalysisShader from './AnalysisShader.js'; import { extend } from '../layer/util/util.js'; -import { Util } from '@maptalks/map'; +import { Util } from 'maptalks'; class AnalysisPainter { constructor(regl, layer, config) { diff --git a/packages/gl/src/analysis/Area3DTool.js b/packages/gl/src/analysis/Area3DTool.js index 1c17f36953..1fd5e3f75b 100644 --- a/packages/gl/src/analysis/Area3DTool.js +++ b/packages/gl/src/analysis/Area3DTool.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import Measure3DTool from './Measure3DTool'; export default class Area3DTool extends Measure3DTool { diff --git a/packages/gl/src/analysis/Distance3DTool.js b/packages/gl/src/analysis/Distance3DTool.js index 14cf2d6235..490c828ed6 100644 --- a/packages/gl/src/analysis/Distance3DTool.js +++ b/packages/gl/src/analysis/Distance3DTool.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import Measure3DTool from './Measure3DTool'; export default class Distance3DTool extends Measure3DTool { diff --git a/packages/gl/src/analysis/Height3DTool.js b/packages/gl/src/analysis/Height3DTool.js index 155b1dbe7d..9c4a417572 100644 --- a/packages/gl/src/analysis/Height3DTool.js +++ b/packages/gl/src/analysis/Height3DTool.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import Measure3DTool from './Measure3DTool'; const MEASURE_HEIGHT_NAMES = [['直线距离', '垂直高度', '水平距离'], ['spatial distance', 'vertical height', 'horizontal distance']]; diff --git a/packages/gl/src/analysis/Measure3DTool.js b/packages/gl/src/analysis/Measure3DTool.js index c7b4822660..ba6bd88044 100644 --- a/packages/gl/src/analysis/Measure3DTool.js +++ b/packages/gl/src/analysis/Measure3DTool.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; const options = { 'mode': 'LineString', diff --git a/packages/gl/src/index-dev.js b/packages/gl/src/index-dev.js index 40f29013fa..987dcc8967 100644 --- a/packages/gl/src/index-dev.js +++ b/packages/gl/src/index-dev.js @@ -18,6 +18,6 @@ export * from './index.ts'; -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import chunk from '../build/worker.js'; maptalks.registerWorkerAdapter('@maptalks/terrain', chunk); diff --git a/packages/gl/src/layer/GroupGLLayer.ts b/packages/gl/src/layer/GroupGLLayer.ts index a23dde88fc..651ed8d3e0 100644 --- a/packages/gl/src/layer/GroupGLLayer.ts +++ b/packages/gl/src/layer/GroupGLLayer.ts @@ -1,11 +1,11 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import Renderer from './GroupGLLayerRenderer.js'; import { vec3 } from '@maptalks/reshader.gl'; import { isNil, extend } from './util/util.js'; import TerrainLayer from './terrain/TerrainLayer'; import RayCaster from './raycaster/RayCaster.js'; import Mask from './mask/Mask.js'; -import { LayerJSONType } from '@maptalks/map'; +import { LayerJSONType } from 'maptalks'; const options: GroupGLLayerOptions = { renderer : 'gl', diff --git a/packages/gl/src/layer/GroupGLLayerRenderer.js b/packages/gl/src/layer/GroupGLLayerRenderer.js index cd2bee5902..2681eb5ba9 100644 --- a/packages/gl/src/layer/GroupGLLayerRenderer.js +++ b/packages/gl/src/layer/GroupGLLayerRenderer.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { vec2, vec3, mat4 } from '@maptalks/reshader.gl'; import ShadowProcess from './shadow/ShadowProcess'; import * as reshader from '@maptalks/reshader.gl'; diff --git a/packages/gl/src/layer/TileLayerGLRenderer.ts b/packages/gl/src/layer/TileLayerGLRenderer.ts index a320e3aa7a..47f040d82b 100644 --- a/packages/gl/src/layer/TileLayerGLRenderer.ts +++ b/packages/gl/src/layer/TileLayerGLRenderer.ts @@ -1,5 +1,5 @@ -import * as maptalks from '@maptalks/map'; -import { RenderContext, Tile } from '@maptalks/map'; +import * as maptalks from 'maptalks'; +import { RenderContext, Tile } from 'maptalks'; import * as reshader from '@maptalks/reshader.gl'; import { mat4 } from '@maptalks/reshader.gl'; diff --git a/packages/gl/src/layer/mask/BoxClipMask.js b/packages/gl/src/layer/mask/BoxClipMask.js index 1fa0a25ee9..d9f9738baf 100644 --- a/packages/gl/src/layer/mask/BoxClipMask.js +++ b/packages/gl/src/layer/mask/BoxClipMask.js @@ -1,4 +1,4 @@ -import { Point } from '@maptalks/map'; +import { Point } from 'maptalks'; import { vec2 } from '@maptalks/reshader.gl'; import ClipMask from './ClipMask'; diff --git a/packages/gl/src/layer/mask/Mask.js b/packages/gl/src/layer/mask/Mask.js index 21722dda3e..036a0aa068 100644 --- a/packages/gl/src/layer/mask/Mask.js +++ b/packages/gl/src/layer/mask/Mask.js @@ -1,4 +1,4 @@ -import { Coordinate, Polygon, Point } from "@maptalks/map"; +import { Coordinate, Polygon, Point } from "maptalks"; import * as reshader from '@maptalks/reshader.gl'; import { mat4, quat } from '@maptalks/reshader.gl'; import { earcut } from '@maptalks/reshader.gl'; diff --git a/packages/gl/src/layer/mask/MaskLayerMixin.ts b/packages/gl/src/layer/mask/MaskLayerMixin.ts index c4f260cef9..e92ff2674e 100644 --- a/packages/gl/src/layer/mask/MaskLayerMixin.ts +++ b/packages/gl/src/layer/mask/MaskLayerMixin.ts @@ -1,8 +1,8 @@ -import { Coordinate, Extent } from "@maptalks/map"; +import { Coordinate, Extent } from "maptalks"; import { mat4, vec3 } from '@maptalks/reshader.gl'; import Mask from "./Mask"; import { extend } from "../util/util"; -import { MixinConstructor } from "@maptalks/map"; +import { MixinConstructor } from "maptalks"; const maskLayerEvents = ['shapechange', 'heightrangechange', 'flatheightchange']; const COORD_EXTENT = new Coordinate(0, 0); diff --git a/packages/gl/src/layer/raycaster/RayCaster.js b/packages/gl/src/layer/raycaster/RayCaster.js index 026e5e3155..8aaaa1bb2d 100644 --- a/packages/gl/src/layer/raycaster/RayCaster.js +++ b/packages/gl/src/layer/raycaster/RayCaster.js @@ -1,6 +1,6 @@ import { vec3, vec4, mat4 } from '@maptalks/reshader.gl'; -import { Coordinate, Point, Util } from '@maptalks/map'; -import * as maptalks from '@maptalks/map'; +import { Coordinate, Point, Util } from 'maptalks'; +import * as maptalks from 'maptalks'; const TRIANGLE = [], POS_A = [], POS_B = [], POS_C = [], TEMP_POINT = new Point(0, 0), NULL_ALTITUDES = []; const TEMP_VEC_AB = [], TEMP_VEC_AC = []; diff --git a/packages/gl/src/layer/terrain/TerrainLayer.js b/packages/gl/src/layer/terrain/TerrainLayer.js index 9503ed6045..42d2466791 100644 --- a/packages/gl/src/layer/terrain/TerrainLayer.js +++ b/packages/gl/src/layer/terrain/TerrainLayer.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import TerrainLayerRenderer from './TerrainLayerRenderer'; import { getTileIdsAtLevel, getSkinTileScale, getSkinTileRes, getCascadeTileIds } from './TerrainTileUtil'; import { extend } from '../util/util'; diff --git a/packages/gl/src/layer/terrain/TerrainLayerRenderer.js b/packages/gl/src/layer/terrain/TerrainLayerRenderer.js index bbd7b23298..42153ce907 100644 --- a/packages/gl/src/layer/terrain/TerrainLayerRenderer.js +++ b/packages/gl/src/layer/terrain/TerrainLayerRenderer.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import TerrainWorkerConnection from './TerrainWorkerConnection'; import * as reshader from '@maptalks/reshader.gl'; import skinVert from './glsl/terrainSkin.vert'; diff --git a/packages/gl/src/layer/terrain/TerrainTileUtil.js b/packages/gl/src/layer/terrain/TerrainTileUtil.js index 320869f4d2..1680f4671f 100644 --- a/packages/gl/src/layer/terrain/TerrainTileUtil.js +++ b/packages/gl/src/layer/terrain/TerrainTileUtil.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { createMartiniData } from './util/martini.js'; export function getCascadeTileIds(layer, x, y, z, center, offset, terrainTileScaleY, scale, levelLimit) { diff --git a/packages/gl/src/layer/terrain/TerrainWorkerConnection.js b/packages/gl/src/layer/terrain/TerrainWorkerConnection.js index f16f3c94a2..158d0913c5 100644 --- a/packages/gl/src/layer/terrain/TerrainWorkerConnection.js +++ b/packages/gl/src/layer/terrain/TerrainWorkerConnection.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; export default class TerrainWorkerConnection extends maptalks.worker.Actor { constructor(mapId) { diff --git a/packages/gl/src/layer/util/util.js b/packages/gl/src/layer/util/util.js index c336d5b9f9..888d908de7 100644 --- a/packages/gl/src/layer/util/util.js +++ b/packages/gl/src/layer/util/util.js @@ -1,7 +1,7 @@ const supportAssign = typeof Object.assign === 'function'; import { vec3, mat4 } from '@maptalks/reshader.gl'; import Color from 'color'; -import { Coordinate } from '@maptalks/map'; +import { Coordinate } from 'maptalks'; /** * Merges the properties of sources into destination object. diff --git a/packages/gl/src/layer/util/uvUniforms.js b/packages/gl/src/layer/util/uvUniforms.js index ccec194e01..3583235746 100644 --- a/packages/gl/src/layer/util/uvUniforms.js +++ b/packages/gl/src/layer/util/uvUniforms.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { vec2 } from '@maptalks/reshader.gl'; const DEFAULT_TEX_OFFSET = [0, 0]; diff --git a/packages/gl/src/light/MapLights.js b/packages/gl/src/light/MapLights.js index 75bc9539eb..a88cd24339 100644 --- a/packages/gl/src/light/MapLights.js +++ b/packages/gl/src/light/MapLights.js @@ -1,4 +1,4 @@ -import { Map } from '@maptalks/map'; +import { Map } from 'maptalks'; import LightManager from './LightManager.js'; diff --git a/packages/gl/src/map/MapGLRenderer.js b/packages/gl/src/map/MapGLRenderer.js index 5997ac68d9..2f4435ee16 100644 --- a/packages/gl/src/map/MapGLRenderer.js +++ b/packages/gl/src/map/MapGLRenderer.js @@ -1,6 +1,6 @@ import { GLContext } from '@maptalks/fusiongl'; import createREGL from '@maptalks/regl'; -import { Map, renderer } from '@maptalks/map'; +import { Map, renderer } from 'maptalks'; export default class MapGLRenderer extends renderer.MapCanvasRenderer { // createCanvas, createContext, getContextInstance, clearLayerCanvasContext 和 clearCanvas 方法都应该动态注入 diff --git a/packages/gl/src/map/MapPostProcess.js b/packages/gl/src/map/MapPostProcess.js index 1ec9c9ce1b..5761b58c28 100644 --- a/packages/gl/src/map/MapPostProcess.js +++ b/packages/gl/src/map/MapPostProcess.js @@ -1,4 +1,4 @@ -import { Map, renderer } from '@maptalks/map'; +import { Map, renderer } from 'maptalks'; import createREGL from '@maptalks/regl'; import PostProcess from '../layer/postprocess/PostProcess'; diff --git a/packages/gltf-loader/package.json b/packages/gltf-loader/package.json index 116e9275fb..45c05928cf 100644 --- a/packages/gltf-loader/package.json +++ b/packages/gltf-loader/package.json @@ -32,7 +32,7 @@ "karma-expect": "^1.1.3", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", - "@maptalks/map": "workspace:*", + "maptalks": "workspace:*", "mocha": "^10.3.0", "rollup": "^4.17.2" }, diff --git a/packages/layer-3dtiles/package.json b/packages/layer-3dtiles/package.json index 70996f5c0a..3e8df780aa 100644 --- a/packages/layer-3dtiles/package.json +++ b/packages/layer-3dtiles/package.json @@ -40,7 +40,7 @@ "homepage": "https://github.com/fuzhenn/3dtiles-issues/", "dependencies": { "@maptalks/function-type": "^1.4.0", - "@maptalks/map": "workspace:*", + "maptalks": "workspace:*", "@maptalks/tbn-packer": "^1.4.5", "frustum-intersects": "^0.2.4", "gl-matrix": "^3.4.0", diff --git a/packages/layer-3dtiles/src/layer/Geo3DTilesLayer.ts b/packages/layer-3dtiles/src/layer/Geo3DTilesLayer.ts index 1a3fed40f0..22ae9d26ff 100644 --- a/packages/layer-3dtiles/src/layer/Geo3DTilesLayer.ts +++ b/packages/layer-3dtiles/src/layer/Geo3DTilesLayer.ts @@ -2,7 +2,7 @@ // box: http://localhost/3dtiles/debug/xx.html // region: -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { quat, vec2, vec3, mat3, mat4, MaskLayerMixin, ClipOutsideMask } from '@maptalks/gl'; //https://github.com/fuzhenn/frustum-intersects import { intersectsSphere, intersectsOrientedBox } from 'frustum-intersects'; @@ -14,7 +14,7 @@ import { radianToCartesian3, cartesian3ToDegree } from '../common/Transform'; import { distanceToCamera } from '../common/intersects_oriented_box.js'; import TileBoundingRegion from './renderer/TileBoundingRegion'; import { eastNorthUpToFixedFrame } from '../common/TileHelper'; -import { LayerJSONType } from '@maptalks/map'; +import { LayerJSONType } from 'maptalks'; type BBOX = [number, number, number, number]; diff --git a/packages/layer-3dtiles/src/layer/Geo3DTilesWorkerConnection.js b/packages/layer-3dtiles/src/layer/Geo3DTilesWorkerConnection.js index a302cf917e..00f459d864 100644 --- a/packages/layer-3dtiles/src/layer/Geo3DTilesWorkerConnection.js +++ b/packages/layer-3dtiles/src/layer/Geo3DTilesWorkerConnection.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; const canvas = typeof document === 'undefined' ? null : document.createElement('canvas'); diff --git a/packages/layer-3dtiles/src/layer/renderer/Geo3DTilesRenderer.js b/packages/layer-3dtiles/src/layer/renderer/Geo3DTilesRenderer.js index a5973957ca..4e3ca10b65 100644 --- a/packages/layer-3dtiles/src/layer/renderer/Geo3DTilesRenderer.js +++ b/packages/layer-3dtiles/src/layer/renderer/Geo3DTilesRenderer.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { reshader } from '@maptalks/gl'; import { getGLTFLoaderBundle } from '@maptalks/gl/dist/transcoders.js'; import { MaskRendererMixin } from '@maptalks/gl'; diff --git a/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js b/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js index 769066b3d5..917983e40a 100644 --- a/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js +++ b/packages/layer-3dtiles/src/layer/renderer/TileMeshPainter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { reshader, vec3, vec4, mat3, mat4, quat, HighlightUtil, ContextUtil } from '@maptalks/gl'; import { iterateMesh, iterateBufferData, getItemAtBufferData, setInstanceData, } from '../../common/GLTFHelpers'; import pntsVert from './glsl/pnts.vert'; diff --git a/packages/layer-gltf/package.json b/packages/layer-gltf/package.json index 780a7fde2a..3e7c6f4431 100644 --- a/packages/layer-gltf/package.json +++ b/packages/layer-gltf/package.json @@ -47,7 +47,7 @@ "karma-happen": "^0.1.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", - "@maptalks/map": "workspace:*", + "maptalks": "workspace:*", "mocha": "^10.3.0", "rollup": "^4.17.2" }, diff --git a/packages/layer-gltf/src/AEMarker.js b/packages/layer-gltf/src/AEMarker.js index 85974f7fa5..814f209966 100644 --- a/packages/layer-gltf/src/AEMarker.js +++ b/packages/layer-gltf/src/AEMarker.js @@ -1,5 +1,5 @@ import EffectMarker from './EffectMarker'; -import { Util } from '@maptalks/map'; +import { Util } from 'maptalks'; const DEFAULT_SYMBOL = { animation : true, diff --git a/packages/layer-gltf/src/EffectLine.js b/packages/layer-gltf/src/EffectLine.js index 6efc785bae..3304d3685e 100644 --- a/packages/layer-gltf/src/EffectLine.js +++ b/packages/layer-gltf/src/EffectLine.js @@ -1,5 +1,5 @@ import EffectMarker from './EffectMarker'; -import { Util } from '@maptalks/map'; +import { Util } from 'maptalks'; const DEFAULT_SYMBOL = { url: 'plane', diff --git a/packages/layer-gltf/src/EffectRing.js b/packages/layer-gltf/src/EffectRing.js index f9b5568134..4a05d1740a 100644 --- a/packages/layer-gltf/src/EffectRing.js +++ b/packages/layer-gltf/src/EffectRing.js @@ -1,5 +1,5 @@ import GLTFMarker from './GLTFMarker'; -import { Util } from '@maptalks/map'; +import { Util } from 'maptalks'; const DEFAULT_UNIFORM = { offsetX: 0, diff --git a/packages/layer-gltf/src/GLTFLayer.js b/packages/layer-gltf/src/GLTFLayer.js index 72ee76b477..c154297d99 100644 --- a/packages/layer-gltf/src/GLTFLayer.js +++ b/packages/layer-gltf/src/GLTFLayer.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { reshader, MaskLayerMixin } from '@maptalks/gl'; import GLTFLayerRenderer from './GLTFLayerRenderer'; import GLTFMarker from './GLTFMarker'; diff --git a/packages/layer-gltf/src/GLTFLayerRenderer.js b/packages/layer-gltf/src/GLTFLayerRenderer.js index 3cee75f860..80da2555a2 100644 --- a/packages/layer-gltf/src/GLTFLayerRenderer.js +++ b/packages/layer-gltf/src/GLTFLayerRenderer.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { defined } from './common/Util'; import { createREGL, mat4, vec2, reshader, MaskRendererMixin } from '@maptalks/gl'; import { intersectsBox } from 'frustum-intersects'; diff --git a/packages/layer-gltf/src/GLTFLineString.js b/packages/layer-gltf/src/GLTFLineString.js index 5b7f2515bc..b87de4e9b8 100644 --- a/packages/layer-gltf/src/GLTFLineString.js +++ b/packages/layer-gltf/src/GLTFLineString.js @@ -1,5 +1,5 @@ import MultiGLTFMarker from "./MultiGLTFMarker"; -import { Coordinate, Util } from "@maptalks/map"; +import { Coordinate, Util } from "maptalks"; import { vec3 } from '@maptalks/gl'; const options = { diff --git a/packages/layer-gltf/src/GLTFMarker.js b/packages/layer-gltf/src/GLTFMarker.js index 1cc72f744c..d60e8302c1 100644 --- a/packages/layer-gltf/src/GLTFMarker.js +++ b/packages/layer-gltf/src/GLTFMarker.js @@ -1,4 +1,4 @@ -import { Marker, Util, Point, Extent } from '@maptalks/map'; +import { Marker, Util, Point, Extent } from 'maptalks'; import { mat4, quat, vec3, vec4, reshader } from '@maptalks/gl'; import { defined, coordinateToWorld, getAbsoluteValue, getGLTFAnchorsAlongLine } from './common/Util'; import { loadFunctionTypes, hasFunctionDefinition } from '@maptalks/function-type'; diff --git a/packages/layer-gltf/src/GLTFMercatorGeometry.js b/packages/layer-gltf/src/GLTFMercatorGeometry.js index af7c8d4aa3..7993ce49c9 100644 --- a/packages/layer-gltf/src/GLTFMercatorGeometry.js +++ b/packages/layer-gltf/src/GLTFMercatorGeometry.js @@ -1,5 +1,5 @@ import GLTFMarker from './GLTFMarker'; -import { Coordinate, Point, projection } from '@maptalks/map'; +import { Coordinate, Point, projection } from 'maptalks'; const COORD = new Coordinate(0, 0), POINT = new Point(0, 0), TEMP_POINT1 = new Point(0, 0), TEMP_POINT2 = new Point(0, 0); export default class GLTFMercatorGeometry extends GLTFMarker { _calSpatialScale(out) { diff --git a/packages/layer-gltf/src/MultiGLTFMarker.js b/packages/layer-gltf/src/MultiGLTFMarker.js index fde119fa79..8b6a918156 100644 --- a/packages/layer-gltf/src/MultiGLTFMarker.js +++ b/packages/layer-gltf/src/MultiGLTFMarker.js @@ -1,6 +1,6 @@ import GLTFMarker from './GLTFMarker'; -import { Coordinate } from '@maptalks/map'; +import { Coordinate } from 'maptalks'; import { mat4, vec3, quat, reshader } from '@maptalks/gl'; import { coordinateToWorld, defined } from './common/Util'; // The structure of MultiGLTFMarker will like below: diff --git a/packages/layer-gltf/src/common/AbstractGLTFLayer.js b/packages/layer-gltf/src/common/AbstractGLTFLayer.js index 6a01f5648c..0eed4af151 100644 --- a/packages/layer-gltf/src/common/AbstractGLTFLayer.js +++ b/packages/layer-gltf/src/common/AbstractGLTFLayer.js @@ -1,6 +1,6 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { compileStyle } from '@maptalks/feature-filter'; import { isNil } from './Util'; import { GeoJSON, GEOJSON_TYPES } from './GeoJSON'; diff --git a/packages/layer-gltf/src/common/GLTFWorkerConnection.js b/packages/layer-gltf/src/common/GLTFWorkerConnection.js index 4058da6f72..82b538261d 100644 --- a/packages/layer-gltf/src/common/GLTFWorkerConnection.js +++ b/packages/layer-gltf/src/common/GLTFWorkerConnection.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { getAbsoluteURL } from './Util'; const canvas = typeof document === 'undefined' ? null : document.createElement('canvas'); diff --git a/packages/layer-gltf/src/common/GeoJSON.js b/packages/layer-gltf/src/common/GeoJSON.js index c9797c329a..ce08002f57 100644 --- a/packages/layer-gltf/src/common/GeoJSON.js +++ b/packages/layer-gltf/src/common/GeoJSON.js @@ -1,4 +1,4 @@ -import { Util, Geometry } from '@maptalks/map'; +import { Util, Geometry } from 'maptalks'; import GLTFMarker from '../GLTFMarker'; import EffectMarker from '../EffectMarker'; diff --git a/packages/layer-gltf/src/common/Util.js b/packages/layer-gltf/src/common/Util.js index 897090b1e2..a034e9166a 100644 --- a/packages/layer-gltf/src/common/Util.js +++ b/packages/layer-gltf/src/common/Util.js @@ -1,4 +1,4 @@ -import { Coordinate, measurer, Util } from '@maptalks/map'; +import { Coordinate, measurer, Util } from 'maptalks'; /** * Whether the object is null or undefined. * @param {Object} obj - object diff --git a/packages/layer-video/package.json b/packages/layer-video/package.json index 548bbdd129..921ce243c6 100644 --- a/packages/layer-video/package.json +++ b/packages/layer-video/package.json @@ -44,7 +44,7 @@ "karma-happen": "^0.1.0", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", - "@maptalks/map": "workspace:*", + "maptalks": "workspace:*", "mocha": "^10.3.0", "rollup": "^4.17.2" }, diff --git a/packages/layer-video/src/VideoLayer.ts b/packages/layer-video/src/VideoLayer.ts index 4fbb4e3e75..86a6a67fdb 100644 --- a/packages/layer-video/src/VideoLayer.ts +++ b/packages/layer-video/src/VideoLayer.ts @@ -1,4 +1,4 @@ -import { Layer } from "@maptalks/map"; +import { Layer } from "maptalks"; import VideoLayerRenderer from "./VideoLayerRenderer"; import VideoSurface from "./VideoSurface"; diff --git a/packages/layer-video/src/VideoLayerRenderer.js b/packages/layer-video/src/VideoLayerRenderer.js index 2477f8a1fa..6954e3e8c9 100644 --- a/packages/layer-video/src/VideoLayerRenderer.js +++ b/packages/layer-video/src/VideoLayerRenderer.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { createREGL, reshader, mat4, quat } from '@maptalks/gl'; import { intersectsBox } from 'frustum-intersects'; diff --git a/packages/layer-video/src/VideoSurface.ts b/packages/layer-video/src/VideoSurface.ts index 5bc393883a..b65853e8f3 100644 --- a/packages/layer-video/src/VideoSurface.ts +++ b/packages/layer-video/src/VideoSurface.ts @@ -5,7 +5,7 @@ import { Handlerable, Polygon, DrawToolLayer, -} from "@maptalks/map"; +} from "maptalks"; interface VideoSurfaceOptions { url?: string; diff --git a/packages/map/package.json b/packages/map/package.json index 6d1ef90863..8a9aaf551e 100644 --- a/packages/map/package.json +++ b/packages/map/package.json @@ -1,5 +1,5 @@ { - "name": "@maptalks/map", + "name": "maptalks", "version": "1.0.0", "description": "A light JavaScript library to create integrated 2D/3D maps.", "license": "BSD-3-Clause", diff --git a/packages/maptalks-gl/package.json b/packages/maptalks-gl/package.json index f429fcdf69..3e39960c0c 100644 --- a/packages/maptalks-gl/package.json +++ b/packages/maptalks-gl/package.json @@ -48,6 +48,6 @@ "@maptalks/transform-control": "workspace:*", "@maptalks/video-layer": "workspace:*", "@maptalks/vt": "workspace:*", - "@maptalks/map": "workspace:*" + "maptalks": "workspace:*" } } diff --git a/packages/traffic/package.json b/packages/traffic/package.json index 67794f4ae2..9ddb6ef599 100644 --- a/packages/traffic/package.json +++ b/packages/traffic/package.json @@ -50,7 +50,7 @@ "karma-expect": "^1.1.3", "karma-mocha": "^2.0.1", "karma-mocha-reporter": "^2.2.5", - "@maptalks/map": "workspace:*", + "maptalks": "workspace:*", "mocha": "^10.3.0", "rollup": "^4.17.2", "rollup-plugin-dts": "^6.1.0" diff --git a/packages/traffic/src/TrafficScene.ts b/packages/traffic/src/TrafficScene.ts index 2afaa14686..622b75dad0 100644 --- a/packages/traffic/src/TrafficScene.ts +++ b/packages/traffic/src/TrafficScene.ts @@ -1,4 +1,4 @@ -import * as maptalks from "@maptalks/map"; +import * as maptalks from "maptalks"; import * as turf from "@turf/turf"; //@ts-expect-error-error import { GLTFLayer, MultiGLTFMarker } from "@maptalks/gltf-layer"; diff --git a/packages/transform-control/package.json b/packages/transform-control/package.json index 595d3adda9..342c857fb0 100644 --- a/packages/transform-control/package.json +++ b/packages/transform-control/package.json @@ -33,7 +33,7 @@ "@rollup/plugin-terser": "0.4.4", "eslint": "^8.57.0", "eslint-plugin-mocha": "^10.5.0", - "@maptalks/map": "workspace:*", + "maptalks": "workspace:*", "pixelmatch": "^4.0.2", "rollup": "^4.17.2" }, diff --git a/packages/transform-control/src/TransformControl.js b/packages/transform-control/src/TransformControl.js index 1047e1a6ba..5bb47be574 100644 --- a/packages/transform-control/src/TransformControl.js +++ b/packages/transform-control/src/TransformControl.js @@ -1,5 +1,5 @@ import { mat4, quat, vec3, vec2, reshader } from '@maptalks/gl'; -import { Handlerable, Eventable, Class, Point, Coordinate, INTERNAL_LAYER_PREFIX } from '@maptalks/map'; +import { Handlerable, Eventable, Class, Point, Coordinate, INTERNAL_LAYER_PREFIX } from 'maptalks'; import TransformHelper from './helper/TransformHelper'; import { calFixedScale, getTranslationPoint } from './common/Util'; import vert from './common/helper.vert'; diff --git a/packages/transform-control/src/TransformTarget.js b/packages/transform-control/src/TransformTarget.js index 9c39cef4ab..f105b272fe 100644 --- a/packages/transform-control/src/TransformTarget.js +++ b/packages/transform-control/src/TransformTarget.js @@ -1,4 +1,4 @@ -import { Coordinate, Point } from "@maptalks/map"; +import { Coordinate, Point } from "maptalks"; import { vec3, mat4, quat } from '@maptalks/gl'; const EMPTY_VEC = [], EMPTY_QUAT = [], EMPTY_MAT = [], TEMP_POINT = new Point(0, 0), TEMP_VEC_1 = [], TEMP_VEC_2 = [], TEMP_SCALE = [], EMPTY_TRANS = [0, 0, 0]; diff --git a/packages/transform-control/src/common/Util.js b/packages/transform-control/src/common/Util.js index 75d1c8e16e..09264a4869 100644 --- a/packages/transform-control/src/common/Util.js +++ b/packages/transform-control/src/common/Util.js @@ -1,6 +1,6 @@ import { mat4, reshader, quat, vec3 } from '@maptalks/gl'; import partsModels from './models'; -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { getGLTFLoaderBundle } from '@maptalks/gl/dist/transcoders'; const gltfloader = getGLTFLoaderBundle(); diff --git a/packages/vt/package.json b/packages/vt/package.json index e63d2d4215..d0c734156b 100644 --- a/packages/vt/package.json +++ b/packages/vt/package.json @@ -61,7 +61,7 @@ "eslint-plugin-mocha": "^10.5.0", "express": "^4.17.1", "happen": "^0.3.1", - "@maptalks/map": "workspace:*", + "maptalks": "workspace:*", "mocha": "^10.3.0", "pixelmatch": "^4.0.2", "rollup": "^4.17.2", diff --git a/packages/vt/src/common/IconRequestor.js b/packages/vt/src/common/IconRequestor.js index b122d7708e..d69d5598e3 100644 --- a/packages/vt/src/common/IconRequestor.js +++ b/packages/vt/src/common/IconRequestor.js @@ -1,4 +1,4 @@ -import { Marker, Util } from '@maptalks/map'; +import { Marker, Util } from 'maptalks'; import LRUCache from '../packer/LRUCache'; export default class IconRequestor { diff --git a/packages/vt/src/layer/layer/GeojsonVectorTileLayer.ts b/packages/vt/src/layer/layer/GeojsonVectorTileLayer.ts index 07ea6ea78c..c6fd688aff 100644 --- a/packages/vt/src/layer/layer/GeojsonVectorTileLayer.ts +++ b/packages/vt/src/layer/layer/GeojsonVectorTileLayer.ts @@ -1,9 +1,9 @@ -import * as maptalks from "@maptalks/map"; +import * as maptalks from "maptalks"; import { isNumber, isObject, isString } from "../../common/Util"; import Ajax from "../../worker/util/Ajax"; -import type { ArrayExtent, Callback, LayerJSONType } from "@maptalks/map"; +import type { ArrayExtent, Callback, LayerJSONType } from "maptalks"; import VectorTileLayer, { VectorTileLayerOptionsType } from "./VectorTileLayer"; const options = { diff --git a/packages/vt/src/layer/layer/VectorTileLayer.ts b/packages/vt/src/layer/layer/VectorTileLayer.ts index 4bd8bfb7b1..90bcd6c9fc 100644 --- a/packages/vt/src/layer/layer/VectorTileLayer.ts +++ b/packages/vt/src/layer/layer/VectorTileLayer.ts @@ -1,4 +1,4 @@ -import * as maptalks from "@maptalks/map"; +import * as maptalks from "maptalks"; import type { BackgroundConfig, @@ -18,8 +18,8 @@ import { extend, hasOwn, isNil, isObject, isString, pushIn } from "../../common/ import Ajax from "../../worker/util/Ajax"; import VectorTileLayerRenderer from "../renderer/VectorTileLayerRenderer"; import { isFunctionDefinition } from "@maptalks/function-type"; -import { LayerIdentifyOptionsType } from "@maptalks/map"; -import { PositionArray, TileLayerOptionsType } from "@maptalks/map"; +import { LayerIdentifyOptionsType } from "maptalks"; +import { PositionArray, TileLayerOptionsType } from "maptalks"; const { PackUtil } = getVectorPacker(); diff --git a/packages/vt/src/layer/plugins/Util.js b/packages/vt/src/layer/plugins/Util.js index 838bf32f63..ba6548451c 100644 --- a/packages/vt/src/layer/plugins/Util.js +++ b/packages/vt/src/layer/plugins/Util.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { isFunctionDefinition } from '@maptalks/function-type'; import Color from 'color'; diff --git a/packages/vt/src/layer/plugins/painters/CollisionPainter.js b/packages/vt/src/layer/plugins/painters/CollisionPainter.js index a8aaf71329..d453ccce75 100644 --- a/packages/vt/src/layer/plugins/painters/CollisionPainter.js +++ b/packages/vt/src/layer/plugins/painters/CollisionPainter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { reshader, vec4 } from '@maptalks/gl'; import collisionVert from './glsl/collision.vert'; import collisionFrag from './glsl/collision.frag'; diff --git a/packages/vt/src/layer/plugins/painters/FillPainter.js b/packages/vt/src/layer/plugins/painters/FillPainter.js index 37562c0874..a0605749b8 100644 --- a/packages/vt/src/layer/plugins/painters/FillPainter.js +++ b/packages/vt/src/layer/plugins/painters/FillPainter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import BasicPainter from './BasicPainter'; import { vec2, reshader, mat4 } from '@maptalks/gl'; import vert from './glsl/fill.vert'; diff --git a/packages/vt/src/layer/plugins/painters/IconPainter.js b/packages/vt/src/layer/plugins/painters/IconPainter.js index eca75673bc..416fd4d59e 100644 --- a/packages/vt/src/layer/plugins/painters/IconPainter.js +++ b/packages/vt/src/layer/plugins/painters/IconPainter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { isFunctionDefinition } from '@maptalks/function-type'; import CollisionPainter from './CollisionPainter'; import { reshader } from '@maptalks/gl'; diff --git a/packages/vt/src/layer/plugins/painters/LinePainter.js b/packages/vt/src/layer/plugins/painters/LinePainter.js index 692d87dd5f..c0dd69cbeb 100644 --- a/packages/vt/src/layer/plugins/painters/LinePainter.js +++ b/packages/vt/src/layer/plugins/painters/LinePainter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import Color from 'color'; import BasicPainter from './BasicPainter'; import { reshader } from '@maptalks/gl'; diff --git a/packages/vt/src/layer/plugins/painters/MeshPainter.js b/packages/vt/src/layer/plugins/painters/MeshPainter.js index d7a6772905..ac7836d80d 100644 --- a/packages/vt/src/layer/plugins/painters/MeshPainter.js +++ b/packages/vt/src/layer/plugins/painters/MeshPainter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { reshader } from '@maptalks/gl'; import { vec2, mat4 } from '@maptalks/gl'; import Painter from './Painter'; diff --git a/packages/vt/src/layer/plugins/painters/Painter.js b/packages/vt/src/layer/plugins/painters/Painter.js index 2c1d38f527..f2394bd5c2 100644 --- a/packages/vt/src/layer/plugins/painters/Painter.js +++ b/packages/vt/src/layer/plugins/painters/Painter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { reshader, vec3, mat4, HighlightUtil } from '@maptalks/gl'; import { getVectorPacker } from '../../../packer/inject'; import { isFunctionDefinition, interpolated, piecewiseConstant } from '@maptalks/function-type'; diff --git a/packages/vt/src/layer/plugins/painters/PhongPainter.js b/packages/vt/src/layer/plugins/painters/PhongPainter.js index e499245784..6541d05db2 100644 --- a/packages/vt/src/layer/plugins/painters/PhongPainter.js +++ b/packages/vt/src/layer/plugins/painters/PhongPainter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { reshader } from '@maptalks/gl'; import { mat4 } from '@maptalks/gl'; import { extend, hasOwn } from '../Util'; diff --git a/packages/vt/src/layer/plugins/painters/TubePainter.js b/packages/vt/src/layer/plugins/painters/TubePainter.js index f84e34d547..15bb094cf3 100644 --- a/packages/vt/src/layer/plugins/painters/TubePainter.js +++ b/packages/vt/src/layer/plugins/painters/TubePainter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import Color from 'color'; import { reshader, mat4, mat3 } from '@maptalks/gl'; import BasicPainter from './BasicPainter'; diff --git a/packages/vt/src/layer/plugins/painters/pbr/StandardPainter.js b/packages/vt/src/layer/plugins/painters/pbr/StandardPainter.js index 60d265749e..4fc596dd45 100644 --- a/packages/vt/src/layer/plugins/painters/pbr/StandardPainter.js +++ b/packages/vt/src/layer/plugins/painters/pbr/StandardPainter.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { reshader, mat4 } from '@maptalks/gl'; import { extend, hasOwn, isNil } from '../../Util'; import MeshPainter from '../MeshPainter'; diff --git a/packages/vt/src/layer/plugins/painters/util/line_offset.js b/packages/vt/src/layer/plugins/painters/util/line_offset.js index 2278749474..90067661bd 100644 --- a/packages/vt/src/layer/plugins/painters/util/line_offset.js +++ b/packages/vt/src/layer/plugins/painters/util/line_offset.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import Point from '@mapbox/point-geometry'; import { projectPoint } from './projection'; import { vec3 } from '@maptalks/gl'; diff --git a/packages/vt/src/layer/renderer/VectorTileLayerRenderer.js b/packages/vt/src/layer/renderer/VectorTileLayerRenderer.js index 565ff1f972..4ee35d3eff 100644 --- a/packages/vt/src/layer/renderer/VectorTileLayerRenderer.js +++ b/packages/vt/src/layer/renderer/VectorTileLayerRenderer.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { mat4, vec3, createREGL, GroundPainter } from '@maptalks/gl'; import WorkerConnection from './worker/WorkerConnection'; import { EMPTY_VECTOR_TILE } from '../core/Constant'; diff --git a/packages/vt/src/layer/renderer/utils/convert_to_painter_features.js b/packages/vt/src/layer/renderer/utils/convert_to_painter_features.js index 76ffe911f4..d0aa35c191 100644 --- a/packages/vt/src/layer/renderer/utils/convert_to_painter_features.js +++ b/packages/vt/src/layer/renderer/utils/convert_to_painter_features.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { KEY_IDX } from '../../../common/Constant'; import { extend } from '../../../common/Util'; diff --git a/packages/vt/src/layer/renderer/worker/WorkerConnection.js b/packages/vt/src/layer/renderer/worker/WorkerConnection.js index 4ea0ff4367..1264f7402e 100644 --- a/packages/vt/src/layer/renderer/worker/WorkerConnection.js +++ b/packages/vt/src/layer/renderer/worker/WorkerConnection.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { extend, uid, toJSON } from '../../../common/Util'; import IconRequestor from '../../../common/IconRequestor'; import { getVectorPacker } from '../../../packer/inject'; diff --git a/packages/vt/src/layer/vector/ExtrudePolygonLayer.ts b/packages/vt/src/layer/vector/ExtrudePolygonLayer.ts index ae11d7865f..79e570ca49 100644 --- a/packages/vt/src/layer/vector/ExtrudePolygonLayer.ts +++ b/packages/vt/src/layer/vector/ExtrudePolygonLayer.ts @@ -1,11 +1,11 @@ -import * as maptalks from "@maptalks/map"; +import * as maptalks from "maptalks"; import type { LitDataConfig, LitMaterial } from "../../types"; import { extend, isNil } from "../../common/Util"; import { getVectorPacker } from "../../packer/inject"; import { ID_PROP } from "./util/convert_to_feature"; -import type { OverlayLayerOptionsType } from "@maptalks/map"; +import type { OverlayLayerOptionsType } from "maptalks"; import { PROP_OMBB } from "../../common/Constant"; import { PolygonLayerRenderer } from "./PolygonLayer"; import Vector3DLayer from "./Vector3DLayer"; diff --git a/packages/vt/src/layer/vector/LineStringLayer.ts b/packages/vt/src/layer/vector/LineStringLayer.ts index 56be13d4b9..c0a8e3dae2 100644 --- a/packages/vt/src/layer/vector/LineStringLayer.ts +++ b/packages/vt/src/layer/vector/LineStringLayer.ts @@ -1,4 +1,4 @@ -import * as maptalks from "@maptalks/map"; +import * as maptalks from "maptalks"; import { LINE_GRADIENT_PROP_KEY, LINE_SYMBOL } from "./util/symbols"; diff --git a/packages/vt/src/layer/vector/PointLayer.ts b/packages/vt/src/layer/vector/PointLayer.ts index 9dffbfba0b..6202e49dbd 100644 --- a/packages/vt/src/layer/vector/PointLayer.ts +++ b/packages/vt/src/layer/vector/PointLayer.ts @@ -1,7 +1,7 @@ -import * as maptalks from "@maptalks/map"; +import * as maptalks from "maptalks"; import { ICON_PAINTER_SCENECONFIG } from "../core/Constant"; -import type { OverlayLayerOptionsType } from "@maptalks/map"; +import type { OverlayLayerOptionsType } from "maptalks"; import Vector3DLayer from "./Vector3DLayer"; import Vector3DLayerRenderer from "./Vector3DLayerRenderer"; import { extend } from "../../common/Util"; diff --git a/packages/vt/src/layer/vector/PolygonLayer.ts b/packages/vt/src/layer/vector/PolygonLayer.ts index aa878112e0..88e8ee0101 100644 --- a/packages/vt/src/layer/vector/PolygonLayer.ts +++ b/packages/vt/src/layer/vector/PolygonLayer.ts @@ -1,4 +1,4 @@ -import * as maptalks from "@maptalks/map"; +import * as maptalks from "maptalks"; import { ID_PROP } from "./util/convert_to_feature"; import { getVectorPacker } from "../../packer/inject"; diff --git a/packages/vt/src/layer/vector/Vector3DLayer.ts b/packages/vt/src/layer/vector/Vector3DLayer.ts index 8d26826246..2d232c1815 100644 --- a/packages/vt/src/layer/vector/Vector3DLayer.ts +++ b/packages/vt/src/layer/vector/Vector3DLayer.ts @@ -1,6 +1,6 @@ -import * as maptalks from "@maptalks/map"; +import * as maptalks from "maptalks"; -import { LayerIdentifyOptionsType } from "@maptalks/map"; +import { LayerIdentifyOptionsType } from "maptalks"; import { extend } from "../../common/Util"; const defaultOptions = { diff --git a/packages/vt/src/layer/vector/Vector3DLayerRenderer.js b/packages/vt/src/layer/vector/Vector3DLayerRenderer.js index 3437366233..efdf3ad3a8 100644 --- a/packages/vt/src/layer/vector/Vector3DLayerRenderer.js +++ b/packages/vt/src/layer/vector/Vector3DLayerRenderer.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { createREGL, reshader, mat4, vec3 } from '@maptalks/gl'; import { convertToFeature, ID_PROP } from './util/convert_to_feature'; import { extend, hasOwn, getCentiMeterScale, isNil } from '../../common/Util'; diff --git a/packages/vt/src/layer/vector/util/convert_to_feature.js b/packages/vt/src/layer/vector/util/convert_to_feature.js index c0b3ec47f8..306002c0c0 100644 --- a/packages/vt/src/layer/vector/util/convert_to_feature.js +++ b/packages/vt/src/layer/vector/util/convert_to_feature.js @@ -1,5 +1,5 @@ import { extend, hasOwn } from '../../../common/Util'; -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; import { KEY_IDX } from '../../../common/Constant'; import { LINE_GRADIENT_PROP_KEY } from './symbols'; import { getVectorPacker } from '../../../packer/inject'; diff --git a/packages/vt/src/layer/vector/util/from_json.js b/packages/vt/src/layer/vector/util/from_json.js index 73cf7eeefb..34f46daad5 100644 --- a/packages/vt/src/layer/vector/util/from_json.js +++ b/packages/vt/src/layer/vector/util/from_json.js @@ -1,4 +1,4 @@ -import * as maptalks from '@maptalks/map'; +import * as maptalks from 'maptalks'; export function fromJSON(json, type, clazz) { if (!json || json['type'] !== type) { diff --git a/packages/vt/src/packer/IconRequestor.js b/packages/vt/src/packer/IconRequestor.js index 2dab3472c9..7cd49d2ad0 100644 --- a/packages/vt/src/packer/IconRequestor.js +++ b/packages/vt/src/packer/IconRequestor.js @@ -1,4 +1,4 @@ -import { Marker, Util } from '@maptalks/map'; +import { Marker, Util } from 'maptalks'; import LRUCache from './LRUCache'; export default class IconRequestor { diff --git a/packages/vt/src/types/index.ts b/packages/vt/src/types/index.ts index 6a025b9ca0..4fbda1549d 100644 --- a/packages/vt/src/types/index.ts +++ b/packages/vt/src/types/index.ts @@ -3,7 +3,7 @@ import type { LineSymbol, MarkerCommonSymbol, TextSymbol, -} from "@maptalks/map"; +} from "maptalks"; export interface FillDataConfig { type?: "fill"; From ade39e0415e33078c5e99719260be520fc49554b Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 14:05:57 +0800 Subject: [PATCH 25/53] fix build --- packages/map/src/layer/ParticleLayer.ts | 135 +++ packages/map/src/layer/VectorLayer.ts | 400 +++++++++ packages/map/src/layer/tile/TileLayer.ts | 2 +- packages/map/src/renderer/index.ts | 2 + .../tilelayer/TileLayerCanvasRenderer.ts | 57 +- .../layer/tilelayer/TileLayerRendererable.ts | 52 +- .../vectorlayer/VectorLayerCanvasRenderer.ts | 773 ++++++++++++++++++ 7 files changed, 1363 insertions(+), 58 deletions(-) create mode 100644 packages/map/src/layer/ParticleLayer.ts create mode 100644 packages/map/src/layer/VectorLayer.ts create mode 100644 packages/map/src/renderer/layer/vectorlayer/VectorLayerCanvasRenderer.ts diff --git a/packages/map/src/layer/ParticleLayer.ts b/packages/map/src/layer/ParticleLayer.ts new file mode 100644 index 0000000000..cd76cf22e6 --- /dev/null +++ b/packages/map/src/layer/ParticleLayer.ts @@ -0,0 +1,135 @@ +import { now } from '../core/util'; +import CanvasLayer, { CanvasLayerOptionsType } from './CanvasLayer'; +import CanvasLayerRenderer from '../renderer/layer/canvaslayer/CanvasLayerRenderer'; +import Point from '../geo/Point'; + +const TEMP_POINT = new Point(0, 0); +/** + * @property {Object} options - configuration options + * @property {Boolean} [options.animation=true] - if the layer is an animated layer + * @memberOf ParticleLayer + * @instance + */ +const options: ParticleLayerOptionsType = { + 'animation': true +}; + +/** + * 粒子图层 + * 提供了一些渲染粒子的接口方法。 + * 你可以直接使用它,但不能以这种方式用JSON序列化/反序列化一个 particelayer + * 更建议使用子类来扩展它 + * + * @english + * @classdesc + * A layer to draw particles.
+ * ParticleLayer provides some interface methods to render particles.
+ * You can use it directly, but can't serialize/deserialize a ParticleLayer with JSON in this way.
+ * It is more recommended to extend it with a subclass. + * @example + * import { ParticleLayer } from 'maptalks'; + * var layer = new ParticleLayer('particle'); + * + * layer.getParticles = function (t) { + * return particles[t]; + * }; + * layer.addTo(map); + * @category layer + * @extends CanvasLayer + * @param {String} id - layer's id + * @param {Object} [options=null] - options defined in [options]{@link ParticleLayer#options} + */ +class ParticleLayer extends CanvasLayer { + options: ParticleLayerOptionsType; + + /** + * 获取t时刻的例子位置 + * + * @english + * Interface method to get particles's position at time t. + * @param t - current time in milliseconds + */ + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getParticles(t?: number) { + } + + draw(context: CanvasRenderingContext2D, view: any) { + const points: any = this.getParticles(now()); + if (!points || points.length === 0) { + const renderer = this._getRenderer(); + if (renderer) { + this._getRenderer()._shouldClear = true; + } + return; + } + const map = this.getMap(); + let extent = view.extent; + if (view.maskExtent) { + extent = view.extent.intersection(view.maskExtent); + } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore 当前 map 接口中目前没有_pointToContainerPoint方法 + extent = extent.convertTo(c => map._pointToContainerPoint(c, undefined, 0, TEMP_POINT)); + const e = 2 * Math.PI; + for (let i = 0, l = points.length; i < l; i++) { + const pos = points[i].point; + if (extent.contains(pos)) { + const color = points[i].color || this.options['lineColor'] || '#fff', + r = points[i].r; + if (context.fillStyle !== color) { + context.fillStyle = color; + } + if (r <= 2) { + context.fillRect(pos.x - r / 2, pos.y - r / 2, r, r); + } else { + context.beginPath(); + context.arc(pos.x, pos.y, r / 2, 0, e); + context.fill(); + } + } + } + this._fillCanvas(context); + } + + //@internal + _fillCanvas(context: CanvasRenderingContext2D) { + const g = context.globalCompositeOperation; + context.globalCompositeOperation = 'destination-out'; + const trail = this.options['trail'] || 30; + context.fillStyle = 'rgba(0, 0, 0, ' + (1 / trail) + ')'; + context.fillRect(0, 0, context.canvas.width, context.canvas.height); + context.globalCompositeOperation = g; + } +} + +ParticleLayer.mergeOptions(options); +ParticleLayer.registerRenderer('canvas', class extends CanvasLayerRenderer { + //@internal + _shouldClear: boolean; + layer: ParticleLayer; + + draw() { + if (!this.canvas || !this.layer.options['animation'] || this._shouldClear) { + this.prepareCanvas(); + this._shouldClear = false; + } + this.prepareDrawContext(); + this._drawLayer(); + } + + drawOnInteracting() { + this.draw(); + this._shouldClear = false; + } + + onSkipDrawOnInteracting() { + this._shouldClear = true; + } +}); + +export default ParticleLayer; + +export type ParticleLayerOptionsType = CanvasLayerOptionsType & { + animation?: boolean; +} diff --git a/packages/map/src/layer/VectorLayer.ts b/packages/map/src/layer/VectorLayer.ts new file mode 100644 index 0000000000..97cb3630de --- /dev/null +++ b/packages/map/src/layer/VectorLayer.ts @@ -0,0 +1,400 @@ +import Browser from '../core/Browser'; +import { isNil, isNumber } from '../core/util'; +import Extent from '../geo/Extent'; +import Geometry from '../geometry/Geometry'; +import OverlayLayer, { LayerIdentifyOptionsType, OverlayLayerOptionsType } from './OverlayLayer'; +import Painter from '../renderer/geometry/Painter'; +import CollectionPainter from '../renderer/geometry/CollectionPainter'; +import Coordinate from '../geo/Coordinate'; +import Point from '../geo/Point'; +import { LineString, Curve, Marker } from '../geometry'; +import PointExtent from '../geo/PointExtent'; +import { VectorLayerCanvasRenderer } from '../renderer'; +import { LayerJSONType } from './Layer'; + +type VectorLayerToJSONOptions = { + geometries: any, + clipExtent: Extent +} + +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore /src/geo/PointExtent.js -> Ts 支持不传参数 +const TEMP_EXTENT = new PointExtent(); +/** + * 配置参数 + * + * @english + * @property {Object} options - VectorLayer's options + * @property {Boolean} options.debug=false - whether the geometries on the layer is in debug mode. + * @property {Boolean} options.enableSimplify=true - whether to simplify geometries before rendering. + * @property {String} options.cursor=default - the cursor style of the layer + * @property {Boolean} options.geometryEvents=true - enable/disable firing geometry events, disable it to improve performance. + * @property {Boolean} options.defaultIconSize=[20,20] - default size of a marker's icon + * @property {Boolean} [options.enableAltitude=false] - whether to enable render geometry with altitude, false by default + * @property {Boolean} [options.altitudeProperty=altitude] - geometry's altitude property name, if enableAltitude is true, "altitude" by default + * @property {Boolean} [options.drawAltitude=false] - whether to draw altitude: a vertical line for marker, a vertical polygon for line + * @property {Boolean} [options.sortByDistanceToCamera=false] - markers Sort by camera distance + * @property {Boolean} [options.roundPoint=false] - round point before painting to improve performance, but will cause geometry shaking in animation + * @property {Number} [options.altitude=0] - layer altitude + * @property {Boolean} [options.debug=false] - whether the geometries on the layer is in debug mode. + * @property {Boolean} [options.collision=false] - whether collision + * @property {Number} [options.collisionBufferSize=2] - collision buffer size + * @property {Number} [options.collisionDelay=250] - collision delay time when map Interacting + * @property {String} [options.collisionScope=layer] - Collision range:layer or map + * @property {Boolean} [options.progressiveRender=false] - progressive Render + * @property {Number} [options.progressiveRenderCount=1000] - progressive Render page size + * @property {Boolean} [options.progressiveRenderDebug=false] - progressive Render debug + * @memberOf VectorLayer + * @instance + */ +const options: VectorLayerOptionsType = { + 'debug': false, + 'enableSimplify': true, + 'defaultIconSize': [20, 20], + 'cacheVectorOnCanvas': true, + 'cacheSvgOnCanvas': Browser.gecko, + 'enableAltitude': true, + 'altitudeProperty': 'altitude', + 'drawAltitude': false, + 'sortByDistanceToCamera': false, + 'roundPoint': false, + 'altitude': 0, + 'clipBBoxBufferSize': 3, + 'collision': false, + 'collisionBufferSize': 2, + 'collisionDelay': 250, + 'collisionScope': 'layer', + 'progressiveRender': false, + 'progressiveRenderCount': 1000, + 'progressiveRenderDebug': false +}; +// Polyline is for custom line geometry +// const TYPES = ['LineString', 'Polyline', 'Polygon', 'MultiLineString', 'MultiPolygon']; +/** + * 用于管理、呈现 geometries 的 layer + * + * @english + * @classdesc + * A layer for managing and rendering geometries. + * @category layer + * @extends OverlayLayer + */ +class VectorLayer extends OverlayLayer { + + options: VectorLayerOptionsType; + isVectorLayer: boolean; + /** + * @param id - layer's id + * @param geometries=null - geometries to add + * @param options=null - construct options + * @param options.style=null - vectorlayer's style + * @param options.*=null - options defined in [VectorLayer]{@link VectorLayer#options} + */ + constructor(id: string, geometries?: VectorLayerOptionsType | Array, options?: VectorLayerOptionsType) { + super(id, geometries, options); + this.isVectorLayer = true; + } + + onConfig(conf: Record) { + super.onConfig(conf); + if (!isNil(conf['enableAltitude'])) { + const geos = this.getGeometries() || []; + for (let i = 0, len = geos.length; i < len; i++) { + const geo = geos[i]; + if (geo) { + geo._clearAltitudeCache(); + geo.fire('positionchange'); + } + } + } + if (conf['enableAltitude'] || conf['drawAltitude'] || conf['altitudeProperty']) { + const renderer = this.getRenderer(); + if (renderer && renderer.setToRedraw) { + renderer.setToRedraw(); + } + } + } + + /** + * 通过给定 coordinate 识别 geometries + * + * @english + * Identify the geometries on the given coordinate + * @param {maptalks.Coordinate} coordinate - coordinate to identify + * @param {Object} [options=null] - options + * @param {Object} [options.tolerance=0] - identify tolerance in pixel + * @param {Object} [options.count=null] - result count + * @return {Geometry[]} geometries identified + */ + identify(coordinate: Coordinate, options?: LayerIdentifyOptionsType): Geometry[] { + options = options || {}; + const renderer = this.getRenderer(); + if (!(coordinate instanceof Coordinate)) { + coordinate = new Coordinate(coordinate); + } + const cp = this.getMap().coordToContainerPoint(coordinate); + // only iterate drawn geometries when onlyVisible is true. + if (options['onlyVisible'] && renderer && renderer.identifyAtPoint) { + return renderer.identifyAtPoint(cp, options); + } + return this._hitGeos(this._geoList, cp, options); + } + + /** + * 通过给定 point 识别 geometries + * + * @english + * Identify the geometries on the given container point + * @param {maptalks.Point} point - container point to identify + * @param {Object} [options=null] - options + * @param {Object} [options.tolerance=0] - identify tolerance in pixel + * @param {Object} [options.count=null] - result count + * @return {Geometry[]} geometries identified + */ + identifyAtPoint(point: Point, options?: LayerIdentifyOptionsType) { + options = options || {}; + const renderer = this.getRenderer(); + if (!(point instanceof Point)) { + point = new Point(point); + } + // only iterate drawn geometries when onlyVisible is true. + if (options['onlyVisible'] && renderer && renderer.identifyAtPoint) { + return renderer.identifyAtPoint(point, options); + } + return this._hitGeos(this._geoList, point, options); + } + + //@internal + _hitGeos(geometries: Array, cp: Point, options: LayerIdentifyOptionsType = {}) { + if (!geometries || !geometries.length) { + return []; + } + const filterGeos = []; + let idx = 0; + for (let i = 0, len = geometries.length; i < len; i++) { + const geo = geometries[i]; + // Ignore collided Points + if (geo.isPoint && (geo as Marker)._collided === true) { + continue; + } + filterGeos[idx] = geo; + idx++; + } + geometries = filterGeos; + + const filter = options['filter'], + hits: Geometry[] = []; + const tolerance = options['tolerance']; + const map = this.getMap(); + const renderer = this.getRenderer(); + const imageData = renderer && renderer.getImageData && renderer.getImageData(); + if (imageData) { + let hitTolerance = 0; + const maxTolerance = renderer.maxTolerance; + //for performance + if (isNumber(maxTolerance)) { + hitTolerance = maxTolerance; + } else { + for (let i = geometries.length - 1; i >= 0; i--) { + const t = geometries[i]._hitTestTolerance() + (tolerance || 0); + if (t > hitTolerance) { + hitTolerance = t; + } + } + } + + const r = map.getDevicePixelRatio(); + (imageData as any).r = r; + let hit = false; + const cpx = cp.x - hitTolerance; + const cpy = cp.y - hitTolerance; + for (let i = -hitTolerance; i <= hitTolerance; i++) { + for (let j = -hitTolerance; j <= hitTolerance; j++) { + const x = Math.round((cpx + i) * r), + y = Math.round((cpy + j) * r); + const idx = y * imageData.width * 4 + x * 4; + if (imageData.data[idx + 3] > 0) { + hit = true; + break; + } + } + if (hit) { + break; + } + } + + //空白的直接返回,避免下面的逻辑,假设有50%的概率不命中(要么命中,要么不命中),可以节省大量的时间 + if (!hit) { + return hits; + } + } + const onlyVisible = options.onlyVisible; + for (let i = geometries.length - 1; i >= 0; i--) { + const geo = geometries[i]; + if (!geo || !geo.options['interactive']) { + continue; + } + //当onlyVisible===false时才需要判断isVisible,因为渲染时已经判断过isVisible的值了 + if (!onlyVisible && (!geo.isVisible())) { + continue; + } + const painter = geo._getPainter(); + if (!painter) { + continue; + } + const bbox = painter.getRenderBBOX && painter.getRenderBBOX(); + if (bbox) { + const { x, y } = cp; + if (x < bbox[0] || y < bbox[1] || x > bbox[2] || y > bbox[3]) { + continue; + } + } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore /src/geometry/LineString.js -> ts LineString 无 _getArrowStyle 属性 + if (!(geo instanceof LineString) || (!geo._getArrowStyle() && !(geo instanceof Curve))) { + // Except for LineString with arrows or curves + let extent = geo.getContainerExtent(TEMP_EXTENT); + if (tolerance) { + extent = extent._expand(tolerance); + } + if (!extent || !extent.contains(cp)) { + continue; + } + } + if (geo._containsPoint(cp, tolerance) && (!filter || filter(geo))) { + hits.push(geo); + if (options['count']) { + if (hits.length >= options['count']) { + break; + } + } + } + } + return hits; + } + + getAltitude() { + return this.options['altitude'] || 0; + } + + /** + * 输出 VectorLayer 的 json + * + * @english + * Export the VectorLayer's JSON.
+ * @param {Object} [options=null] - export options + * @param {Object} [options.geometries=null] - If not null and the layer is a [OverlayerLayer]{@link OverlayLayer}, + * the layer's geometries will be exported with the given "options.geometries" as a parameter of geometry's toJSON. + * @param {Extent} [options.clipExtent=null] - if set, only the geometries intersectes with the extent will be exported. + * @return layer's JSON + */ + + toJSON(options?: VectorLayerToJSONOptions): LayerJSONType { + if (!options) { + options = { + 'clipExtent': null, + 'geometries': null + }; + } + const profile = { + 'type': this.getJSONType(), + 'id': this.getId(), + 'options': this.config() + }; + if (isNil(options['geometries']) || options['geometries']) { + let clipExtent; + if (options['clipExtent']) { + const map = this.getMap(); + const projection = map ? map.getProjection() : null; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore 需/src/geo/Extent.js -> ts 并支持只传两个个参数 + clipExtent = new Extent(options['clipExtent'], projection); + } + const geoJSONs = []; + const geometries = this.getGeometries(); + for (let i = 0, len = geometries.length; i < len; i++) { + const geo = geometries[i]; + const geoExt = geo.getExtent(); + if (!geoExt || (clipExtent && !clipExtent.intersects(geoExt))) { + continue; + } + const json = geo.toJSON(options['geometries']); + geoJSONs.push(json); + } + profile['geometries'] = geoJSONs; + } + return profile; + } + + getRenderer(): VectorLayerCanvasRenderer { + return super.getRenderer() as VectorLayerCanvasRenderer; + } + + /** + * 通过 json 生成 VectorLayer + * + * @english + * Reproduce a VectorLayer from layer's JSON. + * @param {Object} layerJSON - layer's JSON + * @return {VectorLayer} + * @static + * @private + * @function + */ + static fromJSON(json: Record): VectorLayer { + if (!json || json['type'] !== 'VectorLayer') { + return null; + } + const layer = new VectorLayer(json['id'], json['options']); + const geoJSONs = json['geometries']; + const geometries = []; + for (let i = 0; i < geoJSONs.length; i++) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore 未找到fromJSON属性 + const geo = Geometry.fromJSON(geoJSONs[i]); + if (geo) { + geometries.push(geo); + } + } + layer.addGeometry(geometries); + return layer; + } + + static getPainterClass() { + return Painter; + } + + static getCollectionPainterClass() { + return CollectionPainter; + } +} + +VectorLayer.mergeOptions(options); + +VectorLayer.registerJSONType('VectorLayer'); + +export default VectorLayer; + +export type VectorLayerOptionsType = OverlayLayerOptionsType & { + debug?: boolean, + enableSimplify?: boolean, + cursor?: string, + geometryEvents?: boolean + defaultIconSize?: [number, number], + cacheVectorOnCanvas?: boolean, + cacheSvgOnCanvas?: boolean, + enableAltitude?: boolean, + altitudeProperty?: string, + drawAltitude?: boolean, + sortByDistanceToCamera?: boolean, + roundPoint?: boolean, + altitude?: number, + clipBBoxBufferSize?: number, + collision?: boolean, + collisionBufferSize?: number, + collisionDelay?: number, + collisionScope?: 'layer' | 'map', + progressiveRender?: boolean, + progressiveRenderCount?: number, + progressiveRenderDebug?: boolean +}; diff --git a/packages/map/src/layer/tile/TileLayer.ts b/packages/map/src/layer/tile/TileLayer.ts index f83c1aff15..7d5161e7eb 100644 --- a/packages/map/src/layer/tile/TileLayer.ts +++ b/packages/map/src/layer/tile/TileLayer.ts @@ -26,7 +26,7 @@ import * as vec3 from '../../core/util/vec3'; import { formatResourceUrl } from '../../core/ResourceProxy'; import { Coordinate, Extent } from '../../geo'; import { type TileLayerCanvasRenderer } from '../../renderer'; -import { Tile } from '../../renderer/layer/tilelayer/TileLayerCanvasRenderer'; +import { Tile } from '../../renderer/layer/tilelayer/TileLayerRendererable'; import { BBOX, bboxInMask } from '../../core/util/bbox'; const DEFAULT_MAXERROR = 1; diff --git a/packages/map/src/renderer/index.ts b/packages/map/src/renderer/index.ts index 0234a01e06..c06869e8db 100644 --- a/packages/map/src/renderer/index.ts +++ b/packages/map/src/renderer/index.ts @@ -2,6 +2,7 @@ export { ResourceCache } from './layer/CanvasRenderer'; export { default as CanvasRenderer } from './layer/CanvasRenderer'; +export { default as LayerGLRenderer } from './layer/LayerGLRenderer'; export { default as ImageGLRenderable } from './layer/ImageGLRenderable'; export * from './layer/tilelayer'; @@ -9,6 +10,7 @@ export * from './layer/vectorlayer'; export * from './layer/canvaslayer'; export { default as MapRenderer } from './map/MapRenderer'; export { default as MapCanvasRenderer } from './map/MapCanvasRenderer'; +export { default as MapGLAbstractRenderer } from './map/MapGLAbstractRenderer'; export { default as Renderable } from './Renderable'; diff --git a/packages/map/src/renderer/layer/tilelayer/TileLayerCanvasRenderer.ts b/packages/map/src/renderer/layer/tilelayer/TileLayerCanvasRenderer.ts index 984f1b9036..8da3ed0ac8 100644 --- a/packages/map/src/renderer/layer/tilelayer/TileLayerCanvasRenderer.ts +++ b/packages/map/src/renderer/layer/tilelayer/TileLayerCanvasRenderer.ts @@ -98,7 +98,7 @@ export default class TileLayerCanvasRenderer extends TileLayerRenderable(CanvasR needToRedraw(): boolean { const map = this.getMap(); - if (this._tileQueue.length) { + if (this.checkIfNeedRedraw()) { return true; } if (map.getPitch()) { @@ -124,27 +124,46 @@ export default class TileLayerCanvasRenderer extends TileLayerRenderable(CanvasR return true; } + clipCanvas(context): boolean { + // const mask = this.layer.getMask(); + // if (!mask) { + // return this._clipByPitch(context); + // } + return super.clipCanvas(context); + } + + clear() { + this.clearTileCaches(); + super.clear(); + } + + onRemove() { + this.clear(); + this.removeTileCaches(); + super.onRemove(); + } + // clip canvas to avoid rough edge of tiles //@internal - _clipByPitch(ctx: CanvasRenderingContext2D): boolean { - const map = this.getMap(); - if (map.getPitch() <= map.options['maxVisualPitch']) { - return false; - } - if (!this.layer.options['clipByPitch']) { - return false; - } - const clipExtent = map.getContainerExtent(); - const r = map.getDevicePixelRatio(); - ctx.save(); - ctx.strokeStyle = 'rgba(0, 0, 0, 0)'; - ctx.beginPath(); - ctx.rect(0, Math.ceil(clipExtent.ymin) * r, Math.ceil(clipExtent.getWidth()) * r, Math.ceil(clipExtent.getHeight()) * r); - ctx.stroke(); - ctx.clip(); - return true; - } + // _clipByPitch(ctx: CanvasRenderingContext2D): boolean { + // const map = this.getMap(); + // if (map.getPitch() <= map.options['maxVisualPitch']) { + // return false; + // } + // if (!this.layer.options['clipByPitch']) { + // return false; + // } + // const clipExtent = map.getContainerExtent(); + // const r = map.getDevicePixelRatio(); + // ctx.save(); + // ctx.strokeStyle = 'rgba(0, 0, 0, 0)'; + // ctx.beginPath(); + // ctx.rect(0, Math.ceil(clipExtent.ymin) * r, Math.ceil(clipExtent.getWidth()) * r, Math.ceil(clipExtent.getHeight()) * r); + // ctx.stroke(); + // ctx.clip(); + // return true; + // } // eslint-disable-next-line @typescript-eslint/no-unused-vars drawTile(tileInfo: Tile['info'], tileImage: Tile['image'], parentContext?: RenderContext) { diff --git a/packages/map/src/renderer/layer/tilelayer/TileLayerRendererable.ts b/packages/map/src/renderer/layer/tilelayer/TileLayerRendererable.ts index 50f0e2548b..240fdad7e9 100644 --- a/packages/map/src/renderer/layer/tilelayer/TileLayerRendererable.ts +++ b/packages/map/src/renderer/layer/tilelayer/TileLayerRendererable.ts @@ -551,18 +551,8 @@ const TileLayerRenderable = function (Base: T) { this.draw(timestamp, context); } - needToRedraw(): boolean { - const map = this.getMap(); - if (this._tileQueue.length) { - return true; - } - if (map.getPitch()) { - return super.needToRedraw(); - } - if (map.isInteracting()) { - return true; - } - return super.needToRedraw(); + checkIfNeedRedraw(): boolean { + return !!this._tileQueue.length; } hitDetect(): boolean { @@ -581,32 +571,11 @@ const TileLayerRenderable = function (Base: T) { return this.layer.options['loadingLimit'] || 0; } - - clear(): void { - this.retireTiles(true); - this.tileCache.reset(); - this.tilesInView = {}; - this.tilesLoading = {}; - this._tileQueue = []; - this._tileQueueIds.clear(); - this._parentTiles = []; - this._childTiles = []; - super.clear(); - } - //@internal _isLoadingTile(tileId: TileId): boolean { return !!this.tilesLoading[tileId]; } - clipCanvas(context): boolean { - // const mask = this.layer.getMask(); - // if (!mask) { - // return this._clipByPitch(context); - // } - return super.clipCanvas(context); - } - loadTileQueue(tileQueue): void { for (const p in tileQueue) { if (tileQueue.hasOwnProperty(p)) { @@ -627,7 +596,6 @@ const TileLayerRenderable = function (Base: T) { loadTile(tile: Tile['info']): Tile['image'] { let tileImage = {} as Tile['image']; // fixme: 无相关定义,是否实现? - // @ts-expect-error todo if (this.loadTileBitmap) { const onLoad = (bitmap) => { this.onTileLoad(bitmap, tile); @@ -635,7 +603,6 @@ const TileLayerRenderable = function (Base: T) { const onError = (error, image) => { this.onTileError(image, tile, error); }; - // @ts-expect-error todo this.loadTileBitmap(tile['url'], tile, onLoad, onError); } else if (this._tileImageWorkerConn && this.loadTileImage === this.constructor.prototype.loadTileImage) { this._fetchImage(tileImage, tile); @@ -1161,12 +1128,21 @@ const TileLayerRenderable = function (Base: T) { return Math.min(1, (now() - tileImage.loadTime) / this.layer.options['fadeDuration']); } - onRemove(): void { - this.clear(); + clearTileCaches(): void { + this.retireTiles(true); + this.tileCache.reset(); + this.tilesInView = {}; + this.tilesLoading = {}; + this._tileQueue = []; + this._tileQueueIds.clear(); + this._parentTiles = []; + this._childTiles = []; + } + + removeTileCaches() { delete this.tileCache; delete this._tilePlaceHolder; delete this._tileZoom; - super.onRemove(); } markCurrent(tile: Tile, isCurrent?: boolean): void { diff --git a/packages/map/src/renderer/layer/vectorlayer/VectorLayerCanvasRenderer.ts b/packages/map/src/renderer/layer/vectorlayer/VectorLayerCanvasRenderer.ts new file mode 100644 index 0000000000..de93329fd7 --- /dev/null +++ b/packages/map/src/renderer/layer/vectorlayer/VectorLayerCanvasRenderer.ts @@ -0,0 +1,773 @@ +/* eslint-disable @typescript-eslint/ban-types */ +import { getExternalResources, now, getPointsResultPts, type Vector3, isNil } from '../../../core/util'; +import VectorLayer from '../../../layer/VectorLayer'; +import OverlayLayerCanvasRenderer from './OverlayLayerCanvasRenderer'; +import Extent from '../../../geo/Extent'; +import PointExtent from '../../../geo/PointExtent'; +import * as vec3 from '../../../core/util/vec3'; +import Canvas from '../../../core/Canvas'; +import type { Painter, CollectionPainter } from '../../geometry'; +import { Point } from '../../../geo'; +import { Geometries, Marker } from '../../../geometry'; +import type { WithUndef } from '../../../types/typings'; + +const TEMP_EXTENT = new PointExtent(); +const TEMP_VEC3: Vector3 = [] as unknown as Vector3; +const TEMP_FIXEDEXTENT = new PointExtent(); +const PLACEMENT_CENTER = 'center'; + +function clearCanvas(canvas: HTMLCanvasElement): CanvasRenderingContext2D { + if (!canvas) { + return null; + } + const ctx = canvas.getContext('2d'); + ctx.clearRect(0, 0, canvas.width, canvas.height); + return ctx; +} + +function isDebug(layer: any) { + return layer && layer.options.progressiveRender && layer.options.progressiveRenderDebug; +} + +/** + * 基于 `HTML5 Canvas2D` 的渲染器类,用于矢量层 + * + * @english + * Renderer class based on HTML5 Canvas2D for VectorLayers + * @protected + * @group renderer + * @name VectorLayerCanvasRenderer + * @extends renderer.OverlaylayerCanvasRenderer + * @param layer - layer to render + */ +class VectorLayerRenderer extends OverlayLayerCanvasRenderer { + //@internal + _lastRenderTime: number; + //@internal + _lastCollisionTime: number; + //@internal + _imageData: ImageData; + //@internal + _geosToDraw: Geometries[]; + //@internal + _lastGeosToDraw: Geometries[]; + //@internal + _hasPoint: boolean; + //@internal + _onlyHasPoint: WithUndef; + //@internal + _displayExtent: Extent; + //@internal + _drawnRes: number; + + renderEnd: boolean; + pageGeos: Geometries[]; + page: number; + maxTolerance: number; + geoPainterList: (Painter | CollectionPainter)[]; + snapshotCanvas: HTMLCanvasElement; + + setToRedraw(): this { + super.setToRedraw(); + this._resetProgressiveRender(); + return this; + } + + + //@internal + _geoIsCollision(geo: GeoType, collisionIndex: any) { + if (!geo) { + return false; + } + const collision = geo.options.collision; + if (!collision) { + return false; + } + // const type = geo.getType(); + if (geo.isPoint && geo.getContainerExtent) { + if (!geo.bbox) { + geo.bbox = [0, 0, 0, 0]; + } + const bufferSize = this.layer.options['collisionBufferSize']; + const extent = geo.getContainerExtent(); + if (!extent) { + return false; + } + geo.bbox[0] = extent.xmin - bufferSize; + geo.bbox[1] = extent.ymin - bufferSize; + geo.bbox[2] = extent.xmax + bufferSize; + geo.bbox[3] = extent.ymax + bufferSize; + if (collisionIndex.collides(geo.bbox)) { + geo._collided = true; + return true; + } + collisionIndex.insertBox(geo.bbox); + } + return false; + } + + getImageData(): ImageData { + //如果不开启geometry event 或者 渲染频率很高 不要取缓存了,因为getImageData是个很昂贵的操作 + if ((!this._lastRenderTime) || (now() - this._lastRenderTime) < 32) { + return null; + } + if (!this.context || !this.context.canvas) { + return null; + } + if (!this._imageData) { + const { width, height } = this.context.canvas; + try { + this._imageData = this.context.getImageData(0, 0, width, height); + } catch (error) { + console.warn('hit detect failed with tainted canvas, some geometries have external resources in another domain:\n', error); + } + } + return this._imageData; + } + + clearImageData(): void { + //每次渲染完成清除缓存的imageData + this._imageData = null; + delete this._imageData; + this._lastRenderTime = now(); + } + + checkResources(...args: any[]) { + const resources = super.checkResources.apply(this, args); + let style = this.layer.getStyle(); + if (style) { + if (!Array.isArray(style)) { + style = [style]; + } + style.forEach(s => { + const res = getExternalResources(s['symbol'], true); + for (let i = 0, l = res.length; i < l; i++) { + if (!this.resources.isResourceLoaded(res[i])) { + resources.push(res[i]); + } + } + }); + } + return resources; + } + + needToRedraw(): boolean { + if (this.isProgressiveRender() && !this.renderEnd) { + return true; + } + const map = this.getMap(); + if (map.isInteracting() && this.layer.options['enableAltitude']) { + return true; + } + // don't redraw when map is zooming without pitch and layer doesn't have any point symbolizer. + if (map.isZooming() && !map.isRotating() && !map.getPitch() && !this._hasPoint && this.layer.constructor === VectorLayer) { + return false; + } + return super.needToRedraw(); + } + + /** + * render layer + */ + draw(): void { + if (!this.getMap()) { + return; + } + if (!this.layer.isVisible() || this.layer.isEmpty()) { + this.clearCanvas(); + this.completeRender(); + return; + } + + this.prepareCanvas(); + + this.drawGeos(); + + this.completeRender(); + } + + isBlank(): boolean { + if (!this.context) { + return false; + } + if (this.isProgressiveRender()) { + return false; + } + return !(this.context.canvas as any)._drawn; + } + + drawOnInteracting() { + if (!this._geosToDraw) { + return; + } + this._updateMapStateCache(); + this._updateDisplayExtent(); + const map = this.getMap(); + //refresh geometries on zooming + const count = this.layer.getCount(); + const res = this.mapStateCache.resolution; + if (map.isZooming() && + map.options['seamlessZoom'] && this._drawnRes !== undefined && res > this._drawnRes * 1.5 && + this._geosToDraw.length < count || map.isMoving() || map.isInteracting()) { + this.prepareToDraw(); + this._batchConversionMarkers(this.mapStateCache.glRes); + if (!this._onlyHasPoint) { + this._checkGeos(); + } + this._drawnRes = res; + } + this._sortByDistanceToCamera(map.cameraPosition); + const { collision, collisionDelay } = this.layer.options; + if (collision) { + const time = now(); + if (!this._lastCollisionTime) { + this._lastCollisionTime = time; + } + if (time - this._lastCollisionTime <= collisionDelay) { + this._geosToDraw = this._lastGeosToDraw || this._geosToDraw; + } else { + this._collidesGeos(); + this._lastCollisionTime = time; + } + } + + for (let i = 0, l = this._geosToDraw.length; i < l; i++) { + const geo = this._geosToDraw[i]; + if (!geo._isCheck) { + if (!geo.isVisible()) { + delete geo._cPoint; + delete geo._inCurrentView; + continue; + } + } + geo._paint(this._displayExtent); + // https://richardartoul.github.io/jekyll/update/2015/04/26/hidden-classes.html + // https://juejin.cn/post/6972702293636415519 + this._geosToDraw[i]._cPoint = undefined; + this._geosToDraw[i]._inCurrentView = undefined; + } + this.clearImageData(); + this._lastGeosToDraw = this._geosToDraw; + if (isDebug(this.layer)) { + console.log('progressiveRender on drawOnInteracting page:', this.page); + } + } + + /** + * Show and render + * @override + */ + show(...args: any[]) { + this.layer.forEach(function (geo) { + geo._repaint(); + }); + super.show.apply(this, args); + } + + forEachGeo(fn: Function, context?: any) { + this.layer.forEach(fn, context); + } + + //@internal + _checkGeos() { + const geos = this._getCurrentNeedRenderGeos(); + for (let i = 0, len = geos.length; i < len; i++) { + this.checkGeo(geos[i]); + } + return this; + } + + drawGeos() { + this._drawSnapshot(); + this._updateMapStateCache(); + this._drawnRes = this.mapStateCache.resolution; + this._updateDisplayExtent(); + this.prepareToDraw(); + this._batchConversionMarkers(this.mapStateCache.glRes); + if (!this._onlyHasPoint) { + this._checkGeos(); + } + this._sortByDistanceToCamera(this.getMap().cameraPosition); + this._collidesGeos(); + for (let i = 0, len = this._geosToDraw.length; i < len; i++) { + this._geosToDraw[i]._paint(); + this._geosToDraw[i]._cPoint = undefined; + this._geosToDraw[i]._inCurrentView = undefined; + } + this.clearImageData(); + this._lastGeosToDraw = this._geosToDraw; + if (isDebug(this.layer)) { + console.log('progressiveRender drawGeos page:', this.page); + } + this._snapshot(); + this._setDrawGeosDrawTime(); + } + + prepareToDraw() { + this.layer._drawTime = now(); + this._hasPoint = false; + this._geosToDraw = []; + return this; + } + + //@internal + _setDrawGeosDrawTime() { + const time = now(); + const drawTime = this.layer._drawTime; + const painterList = this.getGeoPainterList(); + for (let i = 0, len = painterList.length; i < len; i++) { + const painter = painterList[i]; + if (painter && painter._setDrawTime) { + painter._setDrawTime(drawTime); + } + } + if (isDebug(this.layer)) { + console.log('_setDrawGeosDrawTime time:', (now() - time) + 'ms'); + } + return this; + } + + checkGeo(geo: Geometries) { + //点的话已经在批量处理里判断过了 + if (geo.isPoint && this._onlyHasPoint !== undefined) { + if (geo._inCurrentView || geo.hasAltitude()) { + this._hasPoint = true; + geo._isCheck = true; + this._geosToDraw.push(geo); + } + return; + } + // LineString ,Polygon,Circle etc + geo._isCheck = false; + if (!geo || !geo.isVisible() || !geo.getMap() || + !geo.getLayer() || (!geo.getLayer().isCanvasRender())) { + return; + } + + const painter = geo._getPainter(); + let inCurrentView = true; + if (geo._inCurrentView || !isNil(geo.options.arcDegree) || geo.hasAltitude()) { + inCurrentView = true; + } else if (geo._inCurrentView === false) { + inCurrentView = false; + } else { + const extent2D = painter.get2DExtent(this.resources, TEMP_EXTENT); + if (!extent2D || !extent2D.intersects(this._displayExtent)) { + inCurrentView = false; + } + } + if (!inCurrentView) { + return; + } + if (painter.hasPoint()) { + this._hasPoint = true; + } + geo._isCheck = true; + this._geosToDraw.push(geo); + } + + //@internal + _collidesGeos() { + const geos = this._geosToDraw; + const collision = this.layer.options['collision']; + if (!collision) { + //reset points _collided + for (let i = 0, len = geos.length; i < len; i++) { + const geo = geos[i]; + if (geo.isPoint) { + (geo as Marker)._collided = false; + } + } + return this; + } + const collisionScope = this.layer.options['collisionScope']; + const collisionIndex = this.layer.getCollisionIndex(); + if (collisionScope === 'layer') { + collisionIndex.clear(); + } + this._geosToDraw = []; + for (let i = 0, len = geos.length; i < len; i++) { + const geo = geos[i]; + if (geo.isPoint) { + (geo as Marker)._collided = false; + if (this._geoIsCollision(geo, collisionIndex)) { + (geo as Marker)._collided = true; + continue; + } + } + this._geosToDraw.push(geos[i]); + } + return this; + } + + onZoomEnd(...args: any[]) { + delete this.canvasExtent2D; + super.onZoomEnd.apply(this, args); + } + + onRemove() { + this.forEachGeo(function (g) { + g.onHide(); + }); + delete this._geosToDraw; + delete this.snapshotCanvas; + delete this.pageGeos; + delete this.geoPainterList; + } + + onGeometryPropertiesChange(param: any) { + if (param) { + this.layer._styleGeometry(param['target']); + } + super.onGeometryPropertiesChange(param); + } + + //@internal + _updateDisplayExtent() { + let extent2D = this.canvasExtent2D; + if (this._maskExtent) { + if (!this._maskExtent.intersects(extent2D)) { + this.completeRender(); + return; + } + extent2D = extent2D.intersection(this._maskExtent); + } + this._displayExtent = extent2D; + } + + identifyAtPoint(point: Point, options = {}) { + const geometries = this.getGeosForIdentify(); + if (!geometries) { + return []; + } + return this.layer._hitGeos(geometries, point, options); + } + + //@internal + _updateMapStateCache() { + const map = this.getMap(); + const offset = map._pointToContainerPoint(this.middleWest)._add(0, -map.height / 2); + const resolution = map.getResolution(); + const pitch = map.getPitch(); + const bearing = map.getBearing(); + const glScale = map.getGLScale(); + const glRes = map.getGLRes(); + const containerExtent = map.getContainerExtent(); + const _2DExtent = map.get2DExtent(); + const glExtent = map.get2DExtentAtRes(glRes); + this.mapStateCache = { + resolution, + pitch, + bearing, + glScale, + glRes, + //@internal + _2DExtent, + glExtent, + containerExtent, + offset + }; + return this; + } + + /** + * 使用批量坐标转换提升性能 + * 优化前 11fps + * 优化后 15fps + * Better performance of batch coordinate conversion + * @param glRes + */ + //@internal + _batchConversionMarkers(glRes: number) { + this._onlyHasPoint = undefined; + if (!this._constructorIsThis()) { + return []; + } + const cPoints = []; + const markers = []; + const altitudes = []; + const altitudeCache = {}; + const layer = this.layer; + const layerOpts = layer.options; + const layerAltitude = layer.getAltitude ? layer.getAltitude() : 0; + const isCanvasRender = layer.isCanvasRender(); + this._onlyHasPoint = true; + //Traverse all Geo + let idx = 0; + const geos = this._getCurrentNeedRenderGeos(); + for (let i = 0, len = geos.length; i < len; i++) { + const geo = geos[i]; + // const type = geo.getType(); + if (geo.isPoint) { + let painter = geo._painter as Painter; + if (!painter) { + painter = geo._getPainter(); + } + const point = painter.getRenderPoints(PLACEMENT_CENTER)[0][0]; + const altitude = layerOpts['enableAltitude'] ? geo._getAltitude() : layerAltitude; + //减少方法的调用 + if (altitudeCache[altitude] === undefined) { + altitudeCache[altitude] = painter.getAltitude(); + } + cPoints[idx] = point; + altitudes[idx] = altitudeCache[altitude]; + markers[idx] = geo; + idx++; + } else { + this._onlyHasPoint = false; + } + } + if (idx === 0) { + return []; + } + const map = this.getMap(); + let pts = getPointsResultPts(cPoints, '_pt'); + pts = map._pointsAtResToContainerPoints(cPoints, glRes, altitudes, pts); + const containerExtent = map.getContainerExtent(); + const { xmax, ymax, xmin, ymin } = containerExtent; + const extentCache = {}; + for (let i = 0, len = markers.length; i < len; i++) { + const geo = markers[i]; + geo._cPoint = pts[i]; + if (!geo._cPoint) { + geo._inCurrentView = false; + continue; + } + const { x, y } = pts[i]; + //Is the point in view + geo._inCurrentView = (x >= xmin && y >= ymin && x <= xmax && y <= ymax) || geo.hasAltitude(); + //不在视野内的,再用fixedExtent 精确判断下 + if (!geo._inCurrentView) { + const symbolkey = geo.getSymbolHash(); + let fixedExtent; + if (symbolkey) { + //相同的symbol 不要重复计算 + fixedExtent = extentCache[symbolkey] = (extentCache[symbolkey] || geo._painter.getFixedExtent()); + } else { + fixedExtent = geo._painter.getFixedExtent(); + } + TEMP_FIXEDEXTENT.set(fixedExtent.xmin, fixedExtent.ymin, fixedExtent.xmax, fixedExtent.ymax); + TEMP_FIXEDEXTENT._add(pts[i]); + geo._inCurrentView = TEMP_FIXEDEXTENT.intersects(containerExtent); + } + if (geo._inCurrentView) { + if (!geo.isVisible() || !isCanvasRender) { + geo._inCurrentView = false; + } + //如果当前图层上只有点,整个checkGeo都不用执行了,这里已经把所有的点都判断了 + if (this._onlyHasPoint && geo._inCurrentView) { + this._hasPoint = true; + geo._isCheck = true; + this._geosToDraw.push(geo); + } + } + } + return pts; + } + + //@internal + _sortByDistanceToCamera(cameraPosition: Vector3) { + if (!this.layer.options['sortByDistanceToCamera']) { + return; + } + if (!this._geosToDraw.length) { + return; + } + const map = this.getMap(); + const p = map.distanceToPoint(1000, 0, map.getGLScale()).x; + const meterScale = p / 1000; + const placement = 'center'; + this._geosToDraw.sort((a, b) => { + // const type0 = a.getType(); + // const type1 = b.getType(); + if (!a.isPoint || !b.isPoint) { + return 0; + } + const painter0 = a._painter; + const painter1 = b._painter; + if (!painter0 || !painter1) { + return 0; + } + const point0 = painter0.getRenderPoints(placement)[0][0]; + const point1 = painter1.getRenderPoints(placement)[0][0]; + const alt0 = painter0.getAltitude() * meterScale; + const alt1 = painter1.getAltitude() * meterScale; + vec3.set(TEMP_VEC3, point0.x, point0.y, alt0); + const dist0 = vec3.distance(TEMP_VEC3, cameraPosition); + vec3.set(TEMP_VEC3, point1.x, point1.y, alt1); + const dist1 = vec3.distance(TEMP_VEC3, cameraPosition); + return dist1 - dist0; + }); + } + + //@internal + _constructorIsThis(): boolean { + return this.constructor === VectorLayerRenderer; + } + + isProgressiveRender(): boolean { + const layer = this.layer; + if (!layer) { + return false; + } + const { progressiveRender, collision } = layer.options || {}; + if (collision) { + return false; + } + return progressiveRender; + } + + getGeosForIdentify(): Geometries[] { + if (!this.isProgressiveRender()) { + return this._geosToDraw || []; + } + return this.pageGeos || []; + } + + getGeoPainterList(): (Painter | CollectionPainter)[] { + if (!this.isProgressiveRender()) { + const list = []; + const geos = this._geosToDraw || []; + for (let i = 0, len = geos.length; i < len; i++) { + list.push(geos[i]._painter); + } + return list; + } + return this.geoPainterList || []; + } + + //@internal + _checkSnapshotCanvas() { + if (!this.isProgressiveRender()) { + delete this.snapshotCanvas; + return null; + } + const canvas = this.canvas; + if (!canvas) { + delete this.snapshotCanvas; + return null; + } + if (!this.snapshotCanvas) { + this.snapshotCanvas = Canvas.createCanvas(1, 1); + } + const snapshotCanvas = this.snapshotCanvas; + const { width, height, style } = canvas; + if (snapshotCanvas.width !== width || snapshotCanvas.height !== height) { + snapshotCanvas.width = width; + snapshotCanvas.height = height; + } + if (snapshotCanvas.style.width !== style.width || snapshotCanvas.style.height !== style.height) { + snapshotCanvas.style.width = style.width; + snapshotCanvas.style.height = style.height; + } + + return snapshotCanvas; + + } + + //@internal + _getCurrentNeedRenderGeos(): Geometries[] { + const geos = this.layer._geoList || []; + if (!this.isProgressiveRender()) { + return geos; + } + // if (this.renderEnd) { + // return []; + // } + const layer = this.layer; + const { progressiveRenderCount } = layer.options; + const pageSize = progressiveRenderCount; + const page = this.page; + const start = (page - 1) * pageSize, end = page * pageSize; + const pageGeos = geos.slice(start, end); + return pageGeos; + } + + //@internal + _resetProgressiveRender() { + if (isDebug(this.layer)) { + console.log('progressiveRender resetProgressiveRender'); + } + this.renderEnd = false; + this.page = 1; + this.pageGeos = []; + this.geoPainterList = []; + this.maxTolerance = 0; + this._clearSnapshotCanvas(); + } + + //@internal + _clearSnapshotCanvas() { + const snapshotCanvas = this._checkSnapshotCanvas(); + if (snapshotCanvas) { + clearCanvas(snapshotCanvas); + } + } + + //@internal + _snapshot() { + const progressiveRender = this.isProgressiveRender(); + const geosToDraw = this._geosToDraw || []; + for (let i = 0, len = geosToDraw.length; i < len; i++) { + const geo = geosToDraw[i]; + const t = geo._hitTestTolerance() || 0; + this.maxTolerance = Math.max(this.maxTolerance, t); + if (progressiveRender) { + this.pageGeos.push(geo); + const painter = geo._painter; + this.geoPainterList.push(painter); + } + } + if (!progressiveRender) { + return this; + } + const time = now(); + const snapshotCanvas = this._checkSnapshotCanvas(); + if (snapshotCanvas && this.canvas) { + const ctx = clearCanvas(snapshotCanvas); + ctx.drawImage(this.canvas, 0, 0); + } + const layer = this.layer; + const { progressiveRenderCount } = layer.options; + const geos = layer._geoList || []; + const pages = Math.ceil(geos.length / progressiveRenderCount); + this.renderEnd = this.page >= pages; + if (this.renderEnd) { + this._setDrawGeosDrawTime(); + } + if (isDebug(this.layer)) { + console.log('snapshot time:', (now() - time) + 'ms'); + } + if (!this.renderEnd) { + this.page++; + } + return this; + } + + //@internal + _drawSnapshot() { + if (!this.isProgressiveRender()) { + return this; + } + const { snapshotCanvas, context } = this; + if (!snapshotCanvas || !context) { + return this; + } + const map = this.getMap(); + if (!map) { + return this; + } + const dpr = map.getDevicePixelRatio() || 1; + const rScale = 1 / dpr; + this._canvasContextScale(context, rScale); + context.drawImage(snapshotCanvas, 0, 0); + this._canvasContextScale(context, dpr); + return this; + } +} + +VectorLayer.registerRenderer('canvas', VectorLayerRenderer); + +type GeoType = any; + +export default VectorLayerRenderer; From 85bd373a9b6f5c1aaf1b6f83d0598541fdc69dc1 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 14:14:51 +0800 Subject: [PATCH 26/53] fixing circleci --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 633cb017af..f40b1934aa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,7 +18,7 @@ jobs: # documented at https://circleci.com/docs/2.0/circleci-images/ # - image: circleci/mongo:3.4.4 - working_directory: ~/repo + working_directory: ~/repo/packages/map steps: - browser-tools/install-chrome From b322cb61a898c371cd8022c6992be6bdd64a278d Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 14:18:46 +0800 Subject: [PATCH 27/53] updating circleci --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f40b1934aa..8319058ce6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,7 @@ version: 2.1 orbs: browser-tools: circleci/browser-tools@1.4.8 jobs: - build: + maptalks: docker: # specify the version you desire here - image: cimg/node:20.11-browsers From 34625f2aee8a639c4c72dc7b7f19e111f278574c Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 14:22:35 +0800 Subject: [PATCH 28/53] fixing circleci --- .circleci/config.yml | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8319058ce6..b36b2b865f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,20 +3,20 @@ # Check https://circleci.com/docs/2.0/language-javascript/ for more details # version: 2.1 +executors: + node-browser-executor: + docker: + - image: cimg/node:20.11-browsers + auth: + # ensure you have first added these secrets + # visit app.circleci.com/settings/project/gh/maptalks/maptalks.js/environment-variables + username: $DOCKER_HUB_USER + password: $DOCKER_HUB_PASSWORD orbs: browser-tools: circleci/browser-tools@1.4.8 jobs: - maptalks: - docker: - # specify the version you desire here - - image: cimg/node:20.11-browsers - environment: - CHROME_BIN: "/usr/bin/google-chrome" - - # Specify service dependencies here if necessary - # CircleCI maintains a library of pre-built images - # documented at https://circleci.com/docs/2.0/circleci-images/ - # - image: circleci/mongo:3.4.4 + build: + executor: node-browser-executor working_directory: ~/repo/packages/map @@ -40,5 +40,7 @@ jobs: # run tests! - run: npm test - - +workflows: + my-custom-workflow: + jobs: + - maptalks From cf9625389922cf1c6409ccc47d131183f4e12d19 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 14:26:56 +0800 Subject: [PATCH 29/53] fixing circleci --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index b36b2b865f..a43a1d7b53 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,7 +15,7 @@ executors: orbs: browser-tools: circleci/browser-tools@1.4.8 jobs: - build: + maptalks: executor: node-browser-executor working_directory: ~/repo/packages/map From a52db7a34ea37809ade69117f04d58fe9f4d05df Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 14:32:28 +0800 Subject: [PATCH 30/53] fixing circleci --- .circleci/config.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a43a1d7b53..324ffdd6fe 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,21 +22,22 @@ jobs: steps: - browser-tools/install-chrome + - run: npm install -g pnpm - checkout # Download and cache dependencies - restore_cache: keys: - - v1-dependencies-{{ checksum "package-lock.json" }} + - v1-dependencies-{{ checksum "pnpm-lock.json" }} # fallback to using the latest cache if no exact match is found - v1-dependencies- - - run: npm install + - run: pnpm i - save_cache: paths: - node_modules - key: v1-dependencies-{{ checksum "package-lock.json" }} + key: v1-dependencies-{{ checksum "pnpm-lock.json" }} # run tests! - run: npm test From 1af0c21d8880f0e3ee4ab4628e0ce86715fe3fde Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 14:36:40 +0800 Subject: [PATCH 31/53] fixing circleci --- .circleci/config.yml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 324ffdd6fe..d7ee64cd57 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -22,22 +22,29 @@ jobs: steps: - browser-tools/install-chrome - - run: npm install -g pnpm - checkout # Download and cache dependencies - restore_cache: + name: Restore pnpm Package Cache keys: - - v1-dependencies-{{ checksum "pnpm-lock.json" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- + - pnpm-packages-{{ checksum "pnpm-lock.yaml" }} + + - run: + name: Install pnpm package manager + command: | + npm install --global corepack@latest + corepack enable + corepack prepare pnpm@latest-10 --activate + pnpm config set store-dir .pnpm-store - run: pnpm i - save_cache: + name: Save pnpm Package Cache + key: pnpm-packages-{{ checksum "pnpm-lock.yaml" }} paths: - - node_modules - key: v1-dependencies-{{ checksum "pnpm-lock.json" }} + - .pnpm-store # run tests! - run: npm test From ddf3f2846bf2b3e781925129117b9026bb8e9402 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 14:43:44 +0800 Subject: [PATCH 32/53] fixing circleci --- .circleci/config.yml | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d7ee64cd57..0ee299f1ca 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,11 +7,6 @@ executors: node-browser-executor: docker: - image: cimg/node:20.11-browsers - auth: - # ensure you have first added these secrets - # visit app.circleci.com/settings/project/gh/maptalks/maptalks.js/environment-variables - username: $DOCKER_HUB_USER - password: $DOCKER_HUB_PASSWORD orbs: browser-tools: circleci/browser-tools@1.4.8 jobs: @@ -33,10 +28,10 @@ jobs: - run: name: Install pnpm package manager command: | - npm install --global corepack@latest - corepack enable - corepack prepare pnpm@latest-10 --activate - pnpm config set store-dir .pnpm-store + sudo npm install --global corepack@latest + sudo corepack enable + sudo corepack prepare pnpm@latest-10 --activate + sudo pnpm config set store-dir .pnpm-store - run: pnpm i @@ -49,6 +44,6 @@ jobs: # run tests! - run: npm test workflows: - my-custom-workflow: + maptalks-test: jobs: - maptalks From 782a739a640e8ebbad10ae3d927cf1b009bffdee Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 14:51:06 +0800 Subject: [PATCH 33/53] fixing circleci --- .circleci/config.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0ee299f1ca..2893948a45 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -28,10 +28,7 @@ jobs: - run: name: Install pnpm package manager command: | - sudo npm install --global corepack@latest - sudo corepack enable - sudo corepack prepare pnpm@latest-10 --activate - sudo pnpm config set store-dir .pnpm-store + sudo npm install --global pnpm - run: pnpm i From 8719f67b2adda1f0013bb9bc9b2e411c4ca57521 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 14:53:14 +0800 Subject: [PATCH 34/53] fixing circleci --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2893948a45..8ce47f8287 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,7 @@ version: 2.1 executors: node-browser-executor: docker: - - image: cimg/node:20.11-browsers + - image: cimg/node:22.14-browsers orbs: browser-tools: circleci/browser-tools@1.4.8 jobs: From 331b0172633b45641b4fee04d8c7f81cf66f31b1 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 15:07:02 +0800 Subject: [PATCH 35/53] fixing circleci --- package.json | 1 - pnpm-lock.yaml | 74 +++++++++++++++++++++++++------------------------- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index 17e481a8e1..af9be39e17 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,5 @@ "git add" ] }, - "packageManager": "pnpm@9.1.2", "license": "MIT" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e07f595504..142aae9788 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -54,9 +54,6 @@ importers: '@maptalks/gltf-layer': specifier: workspace:* version: link:../layer-gltf - '@maptalks/map': - specifier: workspace:* - version: link:../map '@maptalks/vt': specifier: workspace:* version: link:../vt @@ -93,6 +90,9 @@ importers: karma-mocha-reporter: specifier: ^2.2.5 version: 2.2.5(karma@6.4.4) + maptalks: + specifier: workspace:* + version: link:../map mocha: specifier: ^10.3.0 version: 10.4.0 @@ -133,9 +133,6 @@ importers: '@maptalks/gltf-loader': specifier: workspace:* version: link:../gltf-loader - '@maptalks/map': - specifier: workspace:* - version: link:../map '@maptalks/martini': specifier: ^0.4.0 version: 0.4.0 @@ -151,6 +148,9 @@ importers: colorin: specifier: ^0.6.0 version: 0.6.0 + maptalks: + specifier: workspace:* + version: link:../map devDependencies: '@rollup/plugin-commonjs': specifier: ^25.0.7 @@ -213,9 +213,6 @@ importers: specifier: ^3.4.0 version: 3.4.3 devDependencies: - '@maptalks/map': - specifier: workspace:* - version: link:../map '@rollup/plugin-commonjs': specifier: ^25.0.7 version: 25.0.7(rollup@4.17.2) @@ -249,6 +246,9 @@ importers: karma-mocha-reporter: specifier: ^2.2.5 version: 2.2.5(karma@6.4.4) + maptalks: + specifier: workspace:* + version: link:../map mocha: specifier: ^10.3.0 version: 10.4.0 @@ -261,9 +261,6 @@ importers: '@maptalks/function-type': specifier: ^1.4.0 version: 1.4.0 - '@maptalks/map': - specifier: workspace:* - version: link:../map '@maptalks/tbn-packer': specifier: ^1.4.5 version: 1.4.5 @@ -276,6 +273,9 @@ importers: gl-matrix: specifier: ^3.4.0 version: 3.4.3 + maptalks: + specifier: workspace:* + version: link:../map pako: specifier: ^2.0.4 version: 2.1.0 @@ -362,9 +362,6 @@ importers: '@maptalks/gl': specifier: workspace:* version: link:../gl - '@maptalks/map': - specifier: workspace:* - version: link:../map '@maptalks/transcoders.draco': specifier: workspace:* version: link:../transcoders.draco @@ -416,6 +413,9 @@ importers: karma-mocha-reporter: specifier: ^2.2.5 version: 2.2.5(karma@6.4.4) + maptalks: + specifier: workspace:* + version: link:../map mocha: specifier: ^10.3.0 version: 10.4.0 @@ -432,9 +432,6 @@ importers: '@maptalks/gl': specifier: workspace:* version: link:../gl - '@maptalks/map': - specifier: workspace:* - version: link:../map '@rollup/plugin-commonjs': specifier: ^25.0.7 version: 25.0.7(rollup@4.17.2) @@ -480,6 +477,9 @@ importers: karma-mocha-reporter: specifier: ^2.2.5 version: 2.2.5(karma@6.4.4) + maptalks: + specifier: workspace:* + version: link:../map mocha: specifier: ^10.3.0 version: 10.4.0 @@ -510,13 +510,6 @@ importers: simplify-js: specifier: ^1.2.1 version: 1.2.4 - optionalDependencies: - '@rollup/rollup-darwin-x64': - specifier: ^4.13.0 - version: 4.17.2 - '@rollup/rollup-linux-x64-gnu': - specifier: ^4.13.0 - version: 4.17.2 devDependencies: '@babel/preset-env': specifier: ^7.24.1 @@ -620,6 +613,13 @@ importers: typescript-eslint: specifier: ^7.18.0 version: 7.18.0(eslint@8.57.0)(typescript@5.4.5) + optionalDependencies: + '@rollup/rollup-darwin-x64': + specifier: ^4.13.0 + version: 4.17.2 + '@rollup/rollup-linux-x64-gnu': + specifier: ^4.13.0 + version: 4.17.2 packages/maptalks-gl: dependencies: @@ -632,9 +632,6 @@ importers: '@maptalks/gltf-layer': specifier: workspace:* version: link:../layer-gltf - '@maptalks/map': - specifier: workspace:* - version: link:../map '@maptalks/transform-control': specifier: workspace:* version: link:../transform-control @@ -644,6 +641,9 @@ importers: '@maptalks/vt': specifier: workspace:* version: link:../vt + maptalks: + specifier: workspace:* + version: link:../map devDependencies: '@rollup/plugin-commonjs': specifier: ^25.0.7 @@ -728,9 +728,6 @@ importers: specifier: workspace:* version: link:../layer-gltf devDependencies: - '@maptalks/map': - specifier: workspace:* - version: link:../map '@rollup/plugin-commonjs': specifier: ^25.0.7 version: 25.0.7(rollup@4.17.2) @@ -773,6 +770,9 @@ importers: karma-mocha-reporter: specifier: ^2.2.5 version: 2.2.5(karma@6.4.4) + maptalks: + specifier: workspace:* + version: link:../map mocha: specifier: ^10.3.0 version: 10.4.0 @@ -879,9 +879,6 @@ importers: specifier: workspace:* version: link:../layer-gltf devDependencies: - '@maptalks/map': - specifier: workspace:* - version: link:../map '@rollup/plugin-commonjs': specifier: ^25.0.7 version: 25.0.7(rollup@4.17.2) @@ -900,6 +897,9 @@ importers: eslint-plugin-mocha: specifier: ^10.5.0 version: 10.5.0(eslint@8.57.0) + maptalks: + specifier: workspace:* + version: link:../map pixelmatch: specifier: ^4.0.2 version: 4.0.2 @@ -976,9 +976,6 @@ importers: specifier: ^3.1.0 version: 3.1.3 devDependencies: - '@maptalks/map': - specifier: workspace:* - version: link:../map '@maptalks/rollup-plugin-glsl-minify': specifier: ^0.1.7 version: 0.1.7 @@ -1018,6 +1015,9 @@ importers: happen: specifier: ^0.3.1 version: 0.3.2 + maptalks: + specifier: workspace:* + version: link:../map mocha: specifier: ^10.3.0 version: 10.4.0 From 5a99630ee48507778571e0b129ab29a3291b14b9 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 15:18:43 +0800 Subject: [PATCH 36/53] fixing circleci --- .circleci/config.yml | 14 +++++++++----- package.json | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8ce47f8287..04b757701a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,10 +10,10 @@ executors: orbs: browser-tools: circleci/browser-tools@1.4.8 jobs: - maptalks: + install: executor: node-browser-executor - working_directory: ~/repo/packages/map + working_directory: ~/repo/ steps: - browser-tools/install-chrome @@ -37,10 +37,14 @@ jobs: key: pnpm-packages-{{ checksum "pnpm-lock.yaml" }} paths: - .pnpm-store - + maptalks-test: # run tests! - - run: npm test + - run: npm run maptalks-test + workflows: maptalks-test: jobs: - - maptalks + - install + - maptalks-test + requires: + - install diff --git a/package.json b/package.json index af9be39e17..a053eaac74 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build-dev": "turbo run build-dev*", "lint": "turbo run lint", "prettier": "prettier --parser=typescript --write **/*.{js,ts,md}", - "test": "turbo run test", + "maptalks-test": "turbo run test --filter=maptalks", "changeset": "changeset", "changeset-version": "changeset version", "release": "pnpm build && changeset publish" From 019773ef6fc2ca69ffa1094df9f3349820015af4 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 15:24:55 +0800 Subject: [PATCH 37/53] fixing circleci --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 04b757701a..ae3b7d0025 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -45,6 +45,6 @@ workflows: maptalks-test: jobs: - install - - maptalks-test - requires: - - install + - maptalks-test: + requires: + - install From bab38954cba919366edeae75945e9d257c61f493 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 15:25:52 +0800 Subject: [PATCH 38/53] fixing --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ae3b7d0025..bf2a29a72a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -42,7 +42,7 @@ jobs: - run: npm run maptalks-test workflows: - maptalks-test: + maptalks: jobs: - install - maptalks-test: From a1502670b444ce4bb5306ee1435858d6b0066d0b Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 15:30:46 +0800 Subject: [PATCH 39/53] fixing --- .circleci/config.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bf2a29a72a..0540d11554 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -10,7 +10,7 @@ executors: orbs: browser-tools: circleci/browser-tools@1.4.8 jobs: - install: + maptalks-test: executor: node-browser-executor working_directory: ~/repo/ @@ -37,14 +37,10 @@ jobs: key: pnpm-packages-{{ checksum "pnpm-lock.yaml" }} paths: - .pnpm-store - maptalks-test: # run tests! - run: npm run maptalks-test workflows: maptalks: jobs: - - install - - maptalks-test: - requires: - - install + - maptalks-test From 871db298df03b5164234756d99e1a33851de8a9b Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 15:34:55 +0800 Subject: [PATCH 40/53] fixing --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index a053eaac74..1313afd1cc 100644 --- a/package.json +++ b/package.json @@ -43,5 +43,6 @@ "git add" ] }, + "packageManager": "pnpm@10.4.1", "license": "MIT" } From 127a19a0436097b8bf9d9cc1879ca31550d54741 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 15:42:28 +0800 Subject: [PATCH 41/53] fixing --- package.json | 2 +- packages/map/{bulid => build}/api-files.js | 0 packages/map/{bulid => build}/karma.config.js | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename packages/map/{bulid => build}/api-files.js (100%) rename packages/map/{bulid => build}/karma.config.js (100%) diff --git a/package.json b/package.json index 1313afd1cc..e72a189169 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,6 @@ "git add" ] }, - "packageManager": "pnpm@10.4.1", + "packageManager": "^pnpm@10.0.0", "license": "MIT" } diff --git a/packages/map/bulid/api-files.js b/packages/map/build/api-files.js similarity index 100% rename from packages/map/bulid/api-files.js rename to packages/map/build/api-files.js diff --git a/packages/map/bulid/karma.config.js b/packages/map/build/karma.config.js similarity index 100% rename from packages/map/bulid/karma.config.js rename to packages/map/build/karma.config.js From d71bfea74542f525c2c4aa167f2c623279d49c55 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 15:59:34 +0800 Subject: [PATCH 42/53] fixing --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e72a189169..1313afd1cc 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,6 @@ "git add" ] }, - "packageManager": "^pnpm@10.0.0", + "packageManager": "pnpm@10.4.1", "license": "MIT" } From 074a3a1de14956811ca112cb55f4dc446d95ddc7 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 16:16:33 +0800 Subject: [PATCH 43/53] fixing --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1313afd1cc..a38a71fdac 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build-dev": "turbo run build-dev*", "lint": "turbo run lint", "prettier": "prettier --parser=typescript --write **/*.{js,ts,md}", - "maptalks-test": "turbo run test --filter=maptalks", + "maptalks-test": "npm test --filter=maptalks", "changeset": "changeset", "changeset-version": "changeset version", "release": "pnpm build && changeset publish" From 16c016b19c43218be34ee33eb576ea3e97616e06 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 16:20:36 +0800 Subject: [PATCH 44/53] fixing --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a38a71fdac..b209d5d5db 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build-dev": "turbo run build-dev*", "lint": "turbo run lint", "prettier": "prettier --parser=typescript --write **/*.{js,ts,md}", - "maptalks-test": "npm test --filter=maptalks", + "maptalks-test": "pnpm run test --filter=maptalks", "changeset": "changeset", "changeset-version": "changeset version", "release": "pnpm build && changeset publish" From e7a7ed1cf4eec6592ede84688633dee4a1bff0a1 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 16:25:01 +0800 Subject: [PATCH 45/53] fixing --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b209d5d5db..168eacc787 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build-dev": "turbo run build-dev*", "lint": "turbo run lint", "prettier": "prettier --parser=typescript --write **/*.{js,ts,md}", - "maptalks-test": "pnpm run test --filter=maptalks", + "maptalks-test": "pnpm --filter=maptalks run test", "changeset": "changeset", "changeset-version": "changeset version", "release": "pnpm build && changeset publish" From cd42aea964f7cb554f7f33cdb71314becf350692 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 16:34:06 +0800 Subject: [PATCH 46/53] fixing specs --- packages/map/src/layer/DrawToolLayer.ts | 10 +++++++++- packages/map/src/layer/VectorLayer.ts | 3 +++ .../renderer/layer/tilelayer/TileLayerGLRenderer.ts | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/map/src/layer/DrawToolLayer.ts b/packages/map/src/layer/DrawToolLayer.ts index 858d038d0b..bceccd25b9 100644 --- a/packages/map/src/layer/DrawToolLayer.ts +++ b/packages/map/src/layer/DrawToolLayer.ts @@ -33,7 +33,7 @@ export default class DrawToolLayer extends OverlayLayer { * @param options=null - construct options * @param options.style=null - drawToolLayer's style */ - constructor(id: string, geometries?: DrawToolLayerOptionsType | Array, options?: DrawToolLayerOptionsType) { + constructor(id: string, geometries?: DrawToolLayerOptionsType | Array, options: DrawToolLayerOptionsType = {}) { super(id, geometries, options); const depthFunc = this.options.depthFunc || 'always'; options.sceneConfig = { depthFunc }; @@ -54,6 +54,10 @@ export default class DrawToolLayer extends OverlayLayer { geometries = [geometries]; } for (let i = 0; i < geometries.length; i++) { + if (this._markerLayer.isVectorLayer) { + this._markerLayer.addGeometry(geometries[i]); + continue; + } if (geometries[i] instanceof Marker || geometries[i] instanceof MultiPoint) { this._markerLayer.addGeometry(geometries[i]); } else if (geometries[i] instanceof LineString || geometries[i] instanceof MultiLineString) { @@ -69,6 +73,10 @@ export default class DrawToolLayer extends OverlayLayer { geometries = [geometries]; } for (let i = 0; i < geometries.length; i++) { + if (this._markerLayer.isVectorLayer) { + this._markerLayer.removeGeometry(geometries[i]); + continue; + } if (geometries[i] instanceof Marker || geometries[i] instanceof MultiPoint) { this._markerLayer.removeGeometry(geometries[i]); } else if (geometries[i] instanceof LineString || geometries[i] instanceof MultiLineString) { diff --git a/packages/map/src/layer/VectorLayer.ts b/packages/map/src/layer/VectorLayer.ts index 97cb3630de..46d3326031 100644 --- a/packages/map/src/layer/VectorLayer.ts +++ b/packages/map/src/layer/VectorLayer.ts @@ -11,6 +11,7 @@ import { LineString, Curve, Marker } from '../geometry'; import PointExtent from '../geo/PointExtent'; import { VectorLayerCanvasRenderer } from '../renderer'; import { LayerJSONType } from './Layer'; +import DrawToolLayer from './DrawToolLayer'; type VectorLayerToJSONOptions = { geometries: any, @@ -398,3 +399,5 @@ export type VectorLayerOptionsType = OverlayLayerOptionsType & { progressiveRenderCount?: number, progressiveRenderDebug?: boolean }; + +DrawToolLayer.setLayerClass(VectorLayer, VectorLayer, VectorLayer); diff --git a/packages/map/src/renderer/layer/tilelayer/TileLayerGLRenderer.ts b/packages/map/src/renderer/layer/tilelayer/TileLayerGLRenderer.ts index d3dcf321ad..7e9cac5fa3 100644 --- a/packages/map/src/renderer/layer/tilelayer/TileLayerGLRenderer.ts +++ b/packages/map/src/renderer/layer/tilelayer/TileLayerGLRenderer.ts @@ -102,7 +102,7 @@ class TileLayerGLRenderer extends ImageGLRenderable(TileLayerCanvasRenderer) { gl.polygonOffset(polygonOffset, polygonOffset); this.drawGLImage(tileImage as any, x, y, w, h, scale, opacity, debugInfo); - if (this._getTileFadingOpacity(tileImage) < 1) { + if (this.getTileFadingOpacity(tileImage) < 1) { this.setToRedraw(); } else { this.setCanvasUpdated(); From 301dac91449f80a700c2935a31c4b4dabcfd582b Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 17:04:09 +0800 Subject: [PATCH 47/53] fixing specs --- packages/map/src/layer/Layer.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/map/src/layer/Layer.ts b/packages/map/src/layer/Layer.ts index 506454ffeb..453846e095 100644 --- a/packages/map/src/layer/Layer.ts +++ b/packages/map/src/layer/Layer.ts @@ -139,9 +139,11 @@ class Layer extends JSONAble(Eventable(Renderable(Class))) { this._initRenderer(); const zIndex = this.getZIndex(); if (!isNil(zIndex)) { - this._renderer.setZIndex(zIndex); - if (!this.isCanvasRender()) { - this._renderer.render(); + if (this._renderer) { + this._renderer.setZIndex(zIndex); + if (!this.isCanvasRender()) { + this._renderer.render(); + } } } this.onLoadEnd(); From 8a6ce158177165667363fe342dab8ae7f8e5d37b Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 17:22:33 +0800 Subject: [PATCH 48/53] fixing specs --- packages/map/src/layer/DrawToolLayer.ts | 24 ++++++++++++++++++++++-- packages/map/src/layer/OverlayLayer.ts | 2 +- packages/map/src/map/tool/DrawTool.ts | 2 +- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/packages/map/src/layer/DrawToolLayer.ts b/packages/map/src/layer/DrawToolLayer.ts index bceccd25b9..9afd7a9b33 100644 --- a/packages/map/src/layer/DrawToolLayer.ts +++ b/packages/map/src/layer/DrawToolLayer.ts @@ -1,5 +1,7 @@ +import { GEOJSON_TYPES } from "../core/Constants"; +import { pushIn } from "../core/util/util"; import { Geometry, LineString, Marker, MultiLineString, MultiPoint, MultiPolygon, Polygon } from "../geometry/index"; -import OverlayLayer, { OverlayLayerOptionsType } from "./OverlayLayer"; +import OverlayLayer, { isGeometry, OverlayLayerOptionsType } from "./OverlayLayer"; const options: DrawToolLayerOptionsType = { // disable renderer of DrawToolLayer @@ -34,12 +36,19 @@ export default class DrawToolLayer extends OverlayLayer { * @param options.style=null - drawToolLayer's style */ constructor(id: string, geometries?: DrawToolLayerOptionsType | Array, options: DrawToolLayerOptionsType = {}) { - super(id, geometries, options); + if (geometries && (!isGeometry(geometries) && !Array.isArray(geometries) && GEOJSON_TYPES.indexOf((geometries as any).type) < 0)) { + options = geometries; + geometries = null; + } + super(id, options); const depthFunc = this.options.depthFunc || 'always'; options.sceneConfig = { depthFunc }; this._markerLayer = new DrawToolLayer.markerLayerClazz(id + '_marker', options); this._lineLayer = new DrawToolLayer.lineLayerClazz(id + '_line', options); this._polygonLayer = new DrawToolLayer.polygonLayerClazz(id + '_polygon', options); + if (geometries) { + this.addGeometry(geometries as Array); + } } bringToFront() { @@ -53,6 +62,7 @@ export default class DrawToolLayer extends OverlayLayer { if (!Array.isArray(geometries)) { geometries = [geometries]; } + pushIn(this._geoList, geometries); for (let i = 0; i < geometries.length; i++) { if (this._markerLayer.isVectorLayer) { this._markerLayer.addGeometry(geometries[i]); @@ -73,6 +83,7 @@ export default class DrawToolLayer extends OverlayLayer { geometries = [geometries]; } for (let i = 0; i < geometries.length; i++) { + this._geoList.splice(geometries[i] as any, 1); if (this._markerLayer.isVectorLayer) { this._markerLayer.removeGeometry(geometries[i]); continue; @@ -88,6 +99,7 @@ export default class DrawToolLayer extends OverlayLayer { } onRemove(): void { + this._geoList = []; this._markerLayer.remove(); this._lineLayer.remove(); this._polygonLayer.remove(); @@ -105,6 +117,14 @@ export default class DrawToolLayer extends OverlayLayer { this._markerLayer.addTo(map); return super.onAdd(); } + + getRenderer() { + return this._getRenderer(); + } + + _getRenderer() { + return this._markerLayer.getRenderer(); + } } DrawToolLayer.mergeOptions(options); diff --git a/packages/map/src/layer/OverlayLayer.ts b/packages/map/src/layer/OverlayLayer.ts index 6faf8c8907..7915dc0b85 100644 --- a/packages/map/src/layer/OverlayLayer.ts +++ b/packages/map/src/layer/OverlayLayer.ts @@ -8,7 +8,7 @@ import GeoJSON from '../geometry/GeoJSON'; import { type OverlayLayerCanvasRenderer } from '../renderer'; import { HandlerFnResultType } from '../core/Eventable'; -function isGeometry(geo) { +export function isGeometry(geo) { return geo && (geo instanceof Geometry); } diff --git a/packages/map/src/map/tool/DrawTool.ts b/packages/map/src/map/tool/DrawTool.ts index 8a684fccf2..31db9b8730 100644 --- a/packages/map/src/map/tool/DrawTool.ts +++ b/packages/map/src/map/tool/DrawTool.ts @@ -413,7 +413,7 @@ class DrawTool extends MapTool { const resources = getExternalResources(symbol); if (resources.length > 0) { //load external resources at first - this._drawToolLayer._getRenderer().loadResources(resources); + this._drawToolLayer.getRenderer().loadResources(resources); } } From 1b4f3b3c22254cb581e9dd5b353daa518b677afc Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 17:53:05 +0800 Subject: [PATCH 49/53] fixing specs --- packages/map/src/layer/DrawToolLayer.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/map/src/layer/DrawToolLayer.ts b/packages/map/src/layer/DrawToolLayer.ts index 9afd7a9b33..d771279403 100644 --- a/packages/map/src/layer/DrawToolLayer.ts +++ b/packages/map/src/layer/DrawToolLayer.ts @@ -98,8 +98,22 @@ export default class DrawToolLayer extends OverlayLayer { } } + _onRemoveDrawToolGeo(params) { + const geometries = params.geometries; + for (let i = 0; i < geometries.length; i++) { + if (geometries[i]) { + this._geoList.splice(geometries[i] as any, 1); + } + } + } + onRemove(): void { this._geoList = []; + + this._markerLayer.off('removegeo', this._onRemoveDrawToolGeo, this); + this._lineLayer.off('removegeo', this._onRemoveDrawToolGeo, this); + this._polygonLayer.off('removegeo', this._onRemoveDrawToolGeo, this); + this._markerLayer.remove(); this._lineLayer.remove(); this._polygonLayer.remove(); @@ -112,9 +126,13 @@ export default class DrawToolLayer extends OverlayLayer { onAdd(): void { const map = this.getMap(); // order is important + this._markerLayer.addTo(map); this._polygonLayer.addTo(map); this._lineLayer.addTo(map); - this._markerLayer.addTo(map); + + this._markerLayer.on('removegeo', this._onRemoveDrawToolGeo, this); + this._lineLayer.on('removegeo', this._onRemoveDrawToolGeo, this); + this._polygonLayer.on('removegeo', this._onRemoveDrawToolGeo, this); return super.onAdd(); } From 7a2559a06c287e6a2377e81af84c4b85557a8784 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 17:54:04 +0800 Subject: [PATCH 50/53] fixing specs --- packages/map/src/layer/DrawToolLayer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/map/src/layer/DrawToolLayer.ts b/packages/map/src/layer/DrawToolLayer.ts index d771279403..5737adaea4 100644 --- a/packages/map/src/layer/DrawToolLayer.ts +++ b/packages/map/src/layer/DrawToolLayer.ts @@ -126,9 +126,9 @@ export default class DrawToolLayer extends OverlayLayer { onAdd(): void { const map = this.getMap(); // order is important - this._markerLayer.addTo(map); this._polygonLayer.addTo(map); this._lineLayer.addTo(map); + this._markerLayer.addTo(map); this._markerLayer.on('removegeo', this._onRemoveDrawToolGeo, this); this._lineLayer.on('removegeo', this._onRemoveDrawToolGeo, this); From 66332575761eef594e9b20f0e33ed9d0e12a8243 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 18:03:45 +0800 Subject: [PATCH 51/53] fixing specs --- packages/map/src/layer/OverlayLayer.ts | 17 ----------------- .../vectorlayer/OverlayLayerCanvasRenderer.ts | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/packages/map/src/layer/OverlayLayer.ts b/packages/map/src/layer/OverlayLayer.ts index 7915dc0b85..6e55e6ab25 100644 --- a/packages/map/src/layer/OverlayLayer.ts +++ b/packages/map/src/layer/OverlayLayer.ts @@ -468,23 +468,6 @@ class OverlayLayer extends Layer { if (!geometries[i] || this !== geometries[i].getLayer()) continue; geometries[i].remove(); } - /** - * removegeo 事件 - * - * @english - * removegeo event. - * - * @event OverlayLayer#removegeo - * @type {Object} - * @property {String} type - removegeo - * @property {OverlayLayer} target - layer - * @property {Geometry[]} geometries - the geometries to remove - */ - this.fire('removegeo', { - 'type': 'removegeo', - 'target': this, - 'geometries': geometries - }); return this; } diff --git a/packages/map/src/renderer/layer/vectorlayer/OverlayLayerCanvasRenderer.ts b/packages/map/src/renderer/layer/vectorlayer/OverlayLayerCanvasRenderer.ts index 2b502c1e5b..404bee1ced 100644 --- a/packages/map/src/renderer/layer/vectorlayer/OverlayLayerCanvasRenderer.ts +++ b/packages/map/src/renderer/layer/vectorlayer/OverlayLayerCanvasRenderer.ts @@ -105,6 +105,23 @@ class OverlayLayerRenderer extends CanvasRenderer { } // eslint-disable-next-line @typescript-eslint/no-unused-vars onGeometryRemove(params: any) { + /** + * removegeo 事件 + * + * @english + * removegeo event. + * + * @event OverlayLayer#removegeo + * @type {Object} + * @property {String} type - removegeo + * @property {OverlayLayer} target - layer + * @property {Geometry[]} geometries - the geometries to remove + */ + this.layer.fire('removegeo', { + 'type': 'removegeo', + 'target': this, + 'geometries': params + }); redraw(this); } From e152d79ab9d131b048bf541cebccb7bf2dec8216 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 18:11:38 +0800 Subject: [PATCH 52/53] add OverlayLayerGLRenderer --- .../vectorlayer/OverlayLayerCanvasRenderer.ts | 83 +++++++++++-------- .../vectorlayer/VectorLayerCanvasRenderer.ts | 2 +- .../src/renderer/layer/vectorlayer/index.ts | 3 +- 3 files changed, 51 insertions(+), 37 deletions(-) diff --git a/packages/map/src/renderer/layer/vectorlayer/OverlayLayerCanvasRenderer.ts b/packages/map/src/renderer/layer/vectorlayer/OverlayLayerCanvasRenderer.ts index 404bee1ced..2a27ba8649 100644 --- a/packages/map/src/renderer/layer/vectorlayer/OverlayLayerCanvasRenderer.ts +++ b/packages/map/src/renderer/layer/vectorlayer/OverlayLayerCanvasRenderer.ts @@ -1,8 +1,8 @@ import { isArrayHasData, pushIn } from '../../../core/util'; -import { type Geometry } from '../../../geometry'; import CanvasRenderer from '../CanvasRenderer'; import { Geometries } from '../../../geometry'; import Extent from '../../../geo/Extent'; +import LayerGLRenderer from '../LayerGLRenderer'; interface MapStateCacheType { resolution: number; @@ -17,26 +17,16 @@ interface MapStateCacheType { offset: number; } -/** - * OverlayLayer 的父呈现器类,供 OverlayLayer 的子类继承。 - * - * @english - * - * A parent renderer class for OverlayLayer to inherit by OverlayLayer's subclasses. - * @protected - * @memberOf renderer - * @name OverlayLayerCanvasRenderer - * @extends renderer.CanvasRenderer - */ -class OverlayLayerRenderer extends CanvasRenderer { - //@internal - _geosToCheck: Geometries[]; - //@internal - _resourceChecked: boolean; - clearImageData?(): void; - //@internal - _lastGeosToDraw: Geometry[]; - mapStateCache: MapStateCacheType; +const OverlayLayerIncludes = { + // //@internal + // _geosToCheck: Geometries[]; + // //@internal + // _resourceChecked: boolean; + // clearImageData?(): void; + // //@internal + // _lastGeosToDraw: Geometry[]; + // //@internal + // mapStateCache: MapStateCacheType; /** * @english @@ -78,12 +68,12 @@ class OverlayLayerRenderer extends CanvasRenderer { this._resourceChecked = true; delete this._geosToCheck; return resources; - } + }, render(...args: any[]): void { this.layer._sortGeometries(); return super.render.apply(this, args); - } + }, //@internal _addGeoToCheckRes(res: Geometries | Geometries[]) { @@ -97,12 +87,12 @@ class OverlayLayerRenderer extends CanvasRenderer { this._geosToCheck = []; } pushIn(this._geosToCheck, res); - } + }, onGeometryAdd(geometries: Geometries | Geometries[]) { this._addGeoToCheckRes(geometries); redraw(this); - } + }, // eslint-disable-next-line @typescript-eslint/no-unused-vars onGeometryRemove(params: any) { /** @@ -123,43 +113,66 @@ class OverlayLayerRenderer extends CanvasRenderer { 'geometries': params }); redraw(this); - } + }, onGeometrySymbolChange(e: { target: Geometries; }) { this._addGeoToCheckRes(e.target); redraw(this); - } + }, // eslint-disable-next-line @typescript-eslint/no-unused-vars onGeometryShapeChange(params: any) { redraw(this); - } + }, // eslint-disable-next-line @typescript-eslint/no-unused-vars onGeometryPositionChange(params: any) { redraw(this); - } + }, // eslint-disable-next-line @typescript-eslint/no-unused-vars onGeometryZIndexChange(params: any) { redraw(this); - } + }, // eslint-disable-next-line @typescript-eslint/no-unused-vars onGeometryShow(params: any) { redraw(this); - } + }, // eslint-disable-next-line @typescript-eslint/no-unused-vars onGeometryHide(params: any) { redraw(this); - } + }, onGeometryPropertiesChange(_: any) { redraw(this); } } -function redraw(renderer: OverlayLayerRenderer): void { - if (renderer.layer.options['drawImmediate']) { +/** + * OverlayLayer 的父呈现器类,供 OverlayLayer 的子类继承。 + * + * @english + * + * A parent renderer class for OverlayLayer to inherit by OverlayLayer's subclasses. + * @protected + * @memberOf renderer + * @name OverlayLayerCanvasRenderer + * @extends renderer.CanvasRenderer + */ +class OverlayLayerCanvasRenderer extends CanvasRenderer { + +} + +OverlayLayerCanvasRenderer.include(OverlayLayerIncludes); + +class OverlayLayerGLRenderer extends LayerGLRenderer { + +} + +OverlayLayerGLRenderer.include(OverlayLayerIncludes); + +function redraw(renderer): void { + if (renderer instanceof OverlayLayerCanvasRenderer && renderer.layer.options['drawImmediate']) { renderer.render(); } renderer.setToRedraw(); } -export default OverlayLayerRenderer; +export { OverlayLayerCanvasRenderer, OverlayLayerGLRenderer }; diff --git a/packages/map/src/renderer/layer/vectorlayer/VectorLayerCanvasRenderer.ts b/packages/map/src/renderer/layer/vectorlayer/VectorLayerCanvasRenderer.ts index de93329fd7..1142361b7c 100644 --- a/packages/map/src/renderer/layer/vectorlayer/VectorLayerCanvasRenderer.ts +++ b/packages/map/src/renderer/layer/vectorlayer/VectorLayerCanvasRenderer.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/ban-types */ import { getExternalResources, now, getPointsResultPts, type Vector3, isNil } from '../../../core/util'; import VectorLayer from '../../../layer/VectorLayer'; -import OverlayLayerCanvasRenderer from './OverlayLayerCanvasRenderer'; +import { OverlayLayerCanvasRenderer } from './OverlayLayerCanvasRenderer'; import Extent from '../../../geo/Extent'; import PointExtent from '../../../geo/PointExtent'; import * as vec3 from '../../../core/util/vec3'; diff --git a/packages/map/src/renderer/layer/vectorlayer/index.ts b/packages/map/src/renderer/layer/vectorlayer/index.ts index 06c9f6d80a..e9f490a8ef 100644 --- a/packages/map/src/renderer/layer/vectorlayer/index.ts +++ b/packages/map/src/renderer/layer/vectorlayer/index.ts @@ -1,7 +1,8 @@ -import OverlayLayerCanvasRenderer from './OverlayLayerCanvasRenderer'; +import { OverlayLayerCanvasRenderer, OverlayLayerGLRenderer } from './OverlayLayerCanvasRenderer'; import VectorLayerCanvasRenderer from './VectorLayerCanvasRenderer'; export { OverlayLayerCanvasRenderer, + OverlayLayerGLRenderer, VectorLayerCanvasRenderer }; From 0885497fad6a06d3cef3f84be3362c62abe10749 Mon Sep 17 00:00:00 2001 From: fuzhenn Date: Mon, 17 Feb 2025 18:23:10 +0800 Subject: [PATCH 53/53] fixing OverlayLayerGLRenderer --- .../vectorlayer/OverlayLayerCanvasRenderer.ts | 244 +++++++++--------- 1 file changed, 123 insertions(+), 121 deletions(-) diff --git a/packages/map/src/renderer/layer/vectorlayer/OverlayLayerCanvasRenderer.ts b/packages/map/src/renderer/layer/vectorlayer/OverlayLayerCanvasRenderer.ts index 2a27ba8649..2bad480c2d 100644 --- a/packages/map/src/renderer/layer/vectorlayer/OverlayLayerCanvasRenderer.ts +++ b/packages/map/src/renderer/layer/vectorlayer/OverlayLayerCanvasRenderer.ts @@ -1,8 +1,9 @@ import { isArrayHasData, pushIn } from '../../../core/util'; import CanvasRenderer from '../CanvasRenderer'; -import { Geometries } from '../../../geometry'; +import { Geometries, Geometry } from '../../../geometry'; import Extent from '../../../geo/Extent'; import LayerGLRenderer from '../LayerGLRenderer'; +import { MixinConstructor } from '../../../core/Mixin'; interface MapStateCacheType { resolution: number; @@ -17,132 +18,130 @@ interface MapStateCacheType { offset: number; } -const OverlayLayerIncludes = { - // //@internal - // _geosToCheck: Geometries[]; - // //@internal - // _resourceChecked: boolean; - // clearImageData?(): void; - // //@internal - // _lastGeosToDraw: Geometry[]; - // //@internal - // mapStateCache: MapStateCacheType; +const OverlayLayerRenderable = function (Base: T) { + const renderable = class extends Base { + //@internal + _geosToCheck: Geometries[]; + //@internal + _resourceChecked: boolean; + clearImageData?(): void; + //@internal + _lastGeosToDraw: Geometry[]; + //@internal + mapStateCache: MapStateCacheType; - /** - * @english - * possible memory leaks: - * 1. if geometries' symbols with external resources change frequently, - * resources of old symbols will still be stored. - * 2. removed geometries' resources won't be removed. - */ - checkResources() { - const geometries = this._geosToCheck || []; - if (!this._resourceChecked && this.layer._geoList) { - pushIn(geometries, this.layer._geoList); - } - if (!isArrayHasData(geometries)) { - return []; - } - const resources = []; - const cache = {}; - - for (let i = geometries.length - 1; i >= 0; i--) { - const geo = geometries[i]; - const res = geo._getExternalResources(); - if (!res.length) { - continue; + /** + * @english + * possible memory leaks: + * 1. if geometries' symbols with external resources change frequently, + * resources of old symbols will still be stored. + * 2. removed geometries' resources won't be removed. + */ + checkResources() { + const geometries = this._geosToCheck || []; + if (!this._resourceChecked && (this as any).layer._geoList) { + pushIn(geometries, (this as any).layer._geoList); + } + if (!isArrayHasData(geometries)) { + return []; } - if (!this.resources) { - // @tip 解构会有一定的性能影响,对于少量数据是否可以忽略 - resources.push(...res); - } else { - for (let i = 0; i < res.length; i++) { - const url = res[i][0]; - if (!this.resources.isResourceLoaded(res[i]) && !cache[url]) { - resources.push(res[i]); - cache[url] = 1; + const resources = []; + const cache = {}; + + for (let i = geometries.length - 1; i >= 0; i--) { + const geo = geometries[i]; + const res = geo._getExternalResources(); + if (!res.length) { + continue; + } + if (!(this as any).resources) { + // @tip 解构会有一定的性能影响,对于少量数据是否可以忽略 + resources.push(...res); + } else { + for (let i = 0; i < res.length; i++) { + const url = res[i][0]; + if (!(this as any).resources.isResourceLoaded(res[i]) && !cache[url]) { + resources.push(res[i]); + cache[url] = 1; + } } } } + this._resourceChecked = true; + delete this._geosToCheck; + return resources; } - this._resourceChecked = true; - delete this._geosToCheck; - return resources; - }, - - render(...args: any[]): void { - this.layer._sortGeometries(); - return super.render.apply(this, args); - }, - //@internal - _addGeoToCheckRes(res: Geometries | Geometries[]) { - if (!res) { - return; + //@internal + _addGeoToCheckRes(res: Geometries | Geometries[]) { + if (!res) { + return; + } + if (!Array.isArray(res)) { + res = [res]; + } + if (!this._geosToCheck) { + this._geosToCheck = []; + } + pushIn(this._geosToCheck, res); } - if (!Array.isArray(res)) { - res = [res]; + + onGeometryAdd(geometries: Geometries | Geometries[]) { + this._addGeoToCheckRes(geometries); + redraw(this); } - if (!this._geosToCheck) { - this._geosToCheck = []; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onGeometryRemove(params: any) { + /** + * removegeo 事件 + * + * @english + * removegeo event. + * + * @event OverlayLayer#removegeo + * @type {Object} + * @property {String} type - removegeo + * @property {OverlayLayer} target - layer + * @property {Geometry[]} geometries - the geometries to remove + */ + (this as any).layer.fire('removegeo', { + 'type': 'removegeo', + 'target': this, + 'geometries': params + }); + redraw(this); } - pushIn(this._geosToCheck, res); - }, - - onGeometryAdd(geometries: Geometries | Geometries[]) { - this._addGeoToCheckRes(geometries); - redraw(this); - }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onGeometryRemove(params: any) { - /** - * removegeo 事件 - * - * @english - * removegeo event. - * - * @event OverlayLayer#removegeo - * @type {Object} - * @property {String} type - removegeo - * @property {OverlayLayer} target - layer - * @property {Geometry[]} geometries - the geometries to remove - */ - this.layer.fire('removegeo', { - 'type': 'removegeo', - 'target': this, - 'geometries': params - }); - redraw(this); - }, - onGeometrySymbolChange(e: { target: Geometries; }) { - this._addGeoToCheckRes(e.target); - redraw(this); - }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onGeometryShapeChange(params: any) { - redraw(this); - }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onGeometryPositionChange(params: any) { - redraw(this); - }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onGeometryZIndexChange(params: any) { - redraw(this); - }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onGeometryShow(params: any) { - redraw(this); - }, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onGeometryHide(params: any) { - redraw(this); - }, + onGeometrySymbolChange(e: { target: Geometries; }) { + this._addGeoToCheckRes(e.target); + redraw(this); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onGeometryShapeChange(params: any) { + redraw(this); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onGeometryPositionChange(params: any) { + redraw(this); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onGeometryZIndexChange(params: any) { + redraw(this); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onGeometryShow(params: any) { + redraw(this); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + onGeometryHide(params: any) { + redraw(this); + } - onGeometryPropertiesChange(_: any) { - redraw(this); + onGeometryPropertiesChange(_: any) { + redraw(this); + } } + return renderable; } /** @@ -156,18 +155,21 @@ const OverlayLayerIncludes = { * @name OverlayLayerCanvasRenderer * @extends renderer.CanvasRenderer */ -class OverlayLayerCanvasRenderer extends CanvasRenderer { - +class OverlayLayerCanvasRenderer extends OverlayLayerRenderable(CanvasRenderer) { + render(...args: any[]): void { + (this as any).layer._sortGeometries(); + return super.render.apply(this, args); + } } -OverlayLayerCanvasRenderer.include(OverlayLayerIncludes); - -class OverlayLayerGLRenderer extends LayerGLRenderer { +class OverlayLayerGLRenderer extends OverlayLayerRenderable(LayerGLRenderer) { + render(...args: any[]): void { + (this as any).layer._sortGeometries(); + return super.render.apply(this, args); + } } -OverlayLayerGLRenderer.include(OverlayLayerIncludes); - function redraw(renderer): void { if (renderer instanceof OverlayLayerCanvasRenderer && renderer.layer.options['drawImmediate']) { renderer.render();