From 6ae0d6ca5f95f81ec7379c2048039e3d4b28095f Mon Sep 17 00:00:00 2001 From: unanmed <1319491857@qq.com> Date: Tue, 18 Nov 2025 16:56:44 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=9C=B0=E5=9B=BE=E6=B8=B2=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../client-base/src/material/asset.ts | 13 +- .../client-base/src/material/autotile.ts | 30 +- .../client-base/src/material/builder.ts | 101 +++- .../client-base/src/material/fallback.ts | 7 +- .../client-base/src/material/manager.ts | 58 ++- .../client-base/src/material/types.ts | 30 +- .../src/render/elements/props.ts | 5 +- .../client-modules/src/render/map/asset.ts | 31 -- .../client-modules/src/render/map/block.ts | 14 +- .../client-modules/src/render/map/constant.ts | 2 + .../client-modules/src/render/map/element.ts | 35 +- .../client-modules/src/render/map/renderer.ts | 487 +++++++++++------- .../src/render/map/shader/back.frag | 1 + .../src/render/map/shader/back.vert | 12 +- .../src/render/map/shader/map.frag | 3 +- .../src/render/map/shader/map.vert | 38 +- .../client-modules/src/render/map/types.ts | 138 ++--- .../client-modules/src/render/map/vertex.ts | 401 ++++++-------- .../client-modules/src/render/map/viewport.ts | 130 +++-- .../client-modules/src/render/shared.ts | 8 +- .../client-modules/src/render/ui/main.tsx | 79 +-- .../data-state/src/core/layerState.ts | 4 +- packages-user/data-state/src/map/mapLayer.ts | 3 + packages/common/src/dirtyTracker.ts | 38 +- packages/common/src/logger.json | 2 +- packages/common/src/types.ts | 10 +- packages/render-assets/src/animater.ts | 3 +- packages/render-assets/src/store.ts | 4 + packages/render-assets/src/texture.ts | 11 +- packages/render-assets/src/types.ts | 6 + packages/render-core/src/gl2.ts | 2 +- public/project/floors/MT13.js | 2 +- public/project/functions.js | 39 ++ 33 files changed, 986 insertions(+), 761 deletions(-) delete mode 100644 packages-user/client-modules/src/render/map/asset.ts create mode 100644 packages-user/client-modules/src/render/map/constant.ts diff --git a/packages-user/client-base/src/material/asset.ts b/packages-user/client-base/src/material/asset.ts index 1bbcf50..95b23f6 100644 --- a/packages-user/client-base/src/material/asset.ts +++ b/packages-user/client-base/src/material/asset.ts @@ -1,9 +1,10 @@ import { ITextureComposedData } from '@motajs/render-assets'; import { IMaterialAsset } from './types'; +import { IDirtyMark } from '@motajs/common'; export class MaterialAsset implements IMaterialAsset { /** 标记列表 */ - private readonly marks: WeakMap = new WeakMap(); + private readonly marks: WeakMap = new WeakMap(); /** 脏标记,所有值小于此标记的都视为需要更新 */ private dirtyFlag: number = 0; @@ -13,22 +14,22 @@ export class MaterialAsset implements IMaterialAsset { this.dirtyFlag++; } - mark(): symbol { - const symbol = Symbol(); + mark(): IDirtyMark { + const symbol = {}; this.marks.set(symbol, this.dirtyFlag); return symbol; } - unmark(mark: symbol): void { + unmark(mark: IDirtyMark): void { this.marks.delete(mark); } - dirtySince(mark: symbol): boolean { + dirtySince(mark: IDirtyMark): boolean { const value = this.marks.get(mark) ?? -1; return value < this.dirtyFlag; } - hasMark(symbol: symbol): boolean { + hasMark(symbol: IDirtyMark): boolean { return this.marks.has(symbol); } } diff --git a/packages-user/client-base/src/material/autotile.ts b/packages-user/client-base/src/material/autotile.ts index f7dbbdd..0c50c51 100644 --- a/packages-user/client-base/src/material/autotile.ts +++ b/packages-user/client-base/src/material/autotile.ts @@ -71,21 +71,21 @@ export class AutotileProcessor implements IAutotileProcessor { // 如果地图高度只有 1 if (length === width) { if (index === 0) { - return 0b1100_0111; + return 0b1110_1111; } else if (index === length - 1) { - return 0b0111_1100; + return 0b1111_1110; } else { - return 0b0100_0100; + return 0b1110_1110; } } // 如果地图宽度只有 1 if (width === 1) { if (index === 0) { - return 0b1111_0001; + return 0b1111_1011; } else if (index === length - 1) { - return 0b0001_1111; + return 0b1011_1111; } else { - return 0b0001_0001; + return 0b1011_1011; } } @@ -96,23 +96,23 @@ export class AutotileProcessor implements IAutotileProcessor { // 四个角,左上,右上,右下,左下 if (index === 0) { - return 0b1100_0001; + return 0b1110_0011; } else if (index === width - 1) { - return 0b0111_0000; + return 0b1111_1000; } else if (index === length - 1) { - return 0b0001_1100; + return 0b0011_1110; } else if (index === lastLine) { - return 0b0000_0111; + return 0b1000_1111; } // 四条边,上,右,下,左 else if (index < width) { - return 0b0100_0000; + return 0b1110_0000; } else if (x === width - 1) { - return 0b0001_0000; + return 0b0011_1000; } else if (index > lastLine) { - return 0b0000_0100; + return 0b0000_1110; } else if (x === 0) { - return 0b0000_0001; + return 0b1000_0011; } // 不在边缘 else { @@ -222,7 +222,7 @@ export class AutotileProcessor implements IAutotileProcessor { connection: number ): ITextureRenderable | null { const { texture } = tile; - const size = texture.height === 128 ? 32 : 48; + const size = texture.height === 32 * 48 ? 32 : 48; const index = distinctConnectionMap.get(connection); if (isNil(index)) return null; return { diff --git a/packages-user/client-base/src/material/builder.ts b/packages-user/client-base/src/material/builder.ts index a9473bc..695ef20 100644 --- a/packages-user/client-base/src/material/builder.ts +++ b/packages-user/client-base/src/material/builder.ts @@ -3,10 +3,11 @@ import { ITexture, ITextureComposedData, ITextureStreamComposer, - TextureMaxRectsStreamComposer + TextureMaxRectsStreamComposer, + SizedCanvasImageSource } from '@motajs/render-assets'; -import { IAssetBuilder } from './types'; -import { logger } from '@motajs/common'; +import { IAssetBuilder, IMaterialGetter, ITrackedAssetData } from './types'; +import { logger, PrivateListDirtyTracker } from '@motajs/common'; export class AssetBuilder implements IAssetBuilder { readonly composer: ITextureStreamComposer = @@ -15,6 +16,18 @@ export class AssetBuilder implements IAssetBuilder { private output: ITextureStore | null = null; private started: boolean = false; + private readonly trackedData: TrackedAssetData; + + /** 当前的索引 */ + private index: number = -1; + + /** 贴图更新的 promise */ + private pending: Promise = Promise.resolve(); + + constructor(readonly materials: IMaterialGetter) { + this.trackedData = new TrackedAssetData(materials, this); + } + pipe(store: ITextureStore): void { if (this.started) { logger.warn(76); @@ -29,29 +42,103 @@ export class AssetBuilder implements IAssetBuilder { const data = res[0]; if (this.output) { - if (!this.output.getTexture(data.index)) { + if (data.index > this.index) { this.output.addTexture(data.index, data.texture); + this.index = data.index; } } + + this.pending = this.pending.then(() => + this.trackedData.updateSource(data.index, data.texture.source) + ); + return data; } + private async updateSourceList(source: Set) { + for (const data of source) { + await this.trackedData.updateSource( + data.index, + data.texture.source + ); + } + } + addTextureList( texture: Iterable ): Iterable { this.started = true; const res = [...this.composer.add(texture)]; + const toUpdate = new Set(); if (this.output) { - res.forEach(v => { - if (!this.output!.getTexture(v.index)) { - this.output!.addTexture(v.index, v.texture); + res.forEach(data => { + if (data.index > this.index) { + this.output!.addTexture(data.index, data.texture); + this.index = data.index; + toUpdate.add(data); + } else { + toUpdate.add(data); } }); } + + this.pending = this.pending.then(() => this.updateSourceList(toUpdate)); + return res; } + tracked(): ITrackedAssetData { + return this.trackedData; + } + close(): void { this.composer.close(); } } + +class TrackedAssetData + extends PrivateListDirtyTracker + implements ITrackedAssetData +{ + readonly sourceList: Map = new Map(); + readonly skipRef: Map = new Map(); + + private originSourceMap: Map = new Map(); + + constructor( + readonly materials: IMaterialGetter, + readonly builder: AssetBuilder + ) { + super(0); + } + + markDirty(index: number) { + this.dirty(index); + } + + async updateSource(index: number, source: SizedCanvasImageSource) { + const origin = this.originSourceMap.get(index); + const prev = this.sourceList.get(index); + if (origin) { + this.skipRef.delete(origin); + } + if (prev) { + this.skipRef.delete(prev); + } + if (source instanceof ImageBitmap) { + if (this.skipRef.has(source)) return; + this.sourceList.set(index, source); + this.skipRef.set(source, index); + } else { + const bitmap = await createImageBitmap(source); + this.sourceList.set(index, bitmap); + this.skipRef.set(bitmap, index); + // 要把源也加到映射中,因为这里的 bitmap 与外部源并不同引用 + this.skipRef.set(source, index); + this.originSourceMap.set(index, source); + } + this.dirty(index); + } + + close(): void {} +} diff --git a/packages-user/client-base/src/material/fallback.ts b/packages-user/client-base/src/material/fallback.ts index 7df806c..fb6697b 100644 --- a/packages-user/client-base/src/material/fallback.ts +++ b/packages-user/client-base/src/material/fallback.ts @@ -36,7 +36,7 @@ function addAutotile(set: Set, map?: readonly (readonly number[])[]) { map.forEach(line => { line.forEach(v => { const id = core.maps.blocksInfo[v as keyof NumberToId]; - if (id.cls === 'autotile') set.add(v); + if (id?.cls === 'autotile') set.add(v); }); }); } @@ -118,7 +118,8 @@ export function fallbackLoad() { addAutotile(autotileSet, floor.fg2map); }); - materials.cacheTilesetList(tilesetSet.union(autotileSet)); - materials.buildAssets(); + + materials.cacheAutotileList(autotileSet); + materials.cacheTilesetList(tilesetSet); } diff --git a/packages-user/client-base/src/material/manager.ts b/packages-user/client-base/src/material/manager.ts index e8ba3ac..955acce 100644 --- a/packages-user/client-base/src/material/manager.ts +++ b/packages-user/client-base/src/material/manager.ts @@ -19,16 +19,22 @@ import { BlockCls, IBigImageReturn, IAssetBuilder, - IMaterialAsset, - IMaterialFramedData + IMaterialFramedData, + ITrackedAssetData } from './types'; import { logger } from '@motajs/common'; import { getClsByString, getTextureFrame } from './utils'; import { isNil } from 'lodash-es'; import { AssetBuilder } from './builder'; -import { MaterialAsset } from './asset'; import { AutotileProcessor } from './autotile'; +interface TilesetCache { + /** 是否已经在贴图库中存在 */ + readonly existed: boolean; + /** 贴图对象 */ + readonly texture: ITexture; +} + export class MaterialManager implements IMaterialManager { readonly tileStore: ITextureStore = new TextureStore(); readonly tilesetStore: ITextureStore = new TextureStore(); @@ -40,16 +46,18 @@ export class MaterialManager implements IMaterialManager { readonly autotileSource: Map = new Map(); /** 图集信息存储 */ - readonly assetDataStore: Map = new Map(); + readonly assetDataStore: Map = new Map(); /** 贴图到图集索引的映射 */ readonly assetMap: Map = new Map(); + /** 带有脏标记追踪的图集对象 */ + readonly trackedAsset: ITrackedAssetData; /** 大怪物数据 */ readonly bigImageData: Map = new Map(); /** tileset 中 `Math.floor(id / 10000) + 1` 映射到 tileset 对应索引的映射,用于处理图块超出 10000 的 tileset */ readonly tilesetOffsetMap: Map = new Map(); /** 图集打包器 */ - readonly assetBuilder: IAssetBuilder = new AssetBuilder(); + readonly assetBuilder: IAssetBuilder; /** 图块 id 到图块数字的映射 */ readonly idNumMap: Map = new Map(); @@ -73,7 +81,9 @@ export class MaterialManager implements IMaterialManager { private built: boolean = false; constructor() { + this.assetBuilder = new AssetBuilder(this); this.assetBuilder.pipe(this.assetStore); + this.trackedAsset = this.assetBuilder.tracked(); } /** @@ -268,9 +278,9 @@ export class MaterialManager implements IMaterialManager { return this.imageStore.fromAlias(alias); } - private getTilesetOwnTexture(identifier: number) { + private getTilesetOwnTexture(identifier: number): TilesetCache | null { const texture = this.tileStore.getTexture(identifier); - if (texture) return texture; + if (texture) return { existed: true, texture }; // 如果 tileset 不存在,那么执行缓存操作 const offset = Math.floor(identifier / 10000); const index = this.tilesetOffsetMap.get(offset - 1); @@ -290,7 +300,7 @@ export class MaterialManager implements IMaterialManager { const y = Math.floor(rest / tileWidth); const newTexture = new Texture(tileset.source); newTexture.clip(x * 32, y * 32, 32, 32); - return newTexture; + return { existed: false, texture: newTexture }; } /** @@ -298,17 +308,13 @@ export class MaterialManager implements IMaterialManager { * @param data 图集数据 */ private checkAssetDirty(data: ITextureComposedData) { + if (!this.built) return; const asset = this.assetDataStore.get(data.index); - if (asset) { - // 如果不是新图集,需要标记为脏 - asset.dirty(); - } else { + if (!asset) { // 如果有新图集,需要添加 const alias = `asset-${data.index}`; - const newAsset = new MaterialAsset(data); - newAsset.dirty(); this.assetStore.alias(data.index, alias); - this.assetDataStore.set(data.index, newAsset); + this.assetDataStore.set(data.index, data); } } @@ -335,14 +341,16 @@ export class MaterialManager implements IMaterialManager { cacheTileset(identifier: number): ITexture | null { const newTexture = this.getTilesetOwnTexture(identifier); if (!newTexture) return null; + const { existed, texture } = newTexture; + if (existed) return texture; // 缓存贴图 - this.tileStore.addTexture(identifier, newTexture); + this.tileStore.addTexture(identifier, texture); this.idNumMap.set(`X${identifier}`, identifier); this.numIdMap.set(identifier, `X${identifier}`); - const data = this.assetBuilder.addTexture(newTexture); - newTexture.toAsset(data); + const data = this.assetBuilder.addTexture(texture); + texture.toAsset(data); this.checkAssetDirty(data); - return newTexture; + return texture; } cacheTilesetList( @@ -354,8 +362,10 @@ export class MaterialManager implements IMaterialManager { arr.forEach(v => { const newTexture = this.getTilesetOwnTexture(v); if (!newTexture) return; - toAdd.push(newTexture); - this.tileStore.addTexture(v, newTexture); + const { existed, texture } = newTexture; + if (existed) return; + toAdd.push(texture); + this.tileStore.addTexture(v, texture); this.idNumMap.set(`X${v}`, v); this.numIdMap.set(v, `X${v}`); }); @@ -429,7 +439,7 @@ export class MaterialManager implements IMaterialManager { arr.forEach(v => { const alias = `asset-${v.index}`; this.assetStore.alias(v.index, alias); - this.assetDataStore.set(v.index, new MaterialAsset(v)); + this.assetDataStore.set(v.index, v); const data: IMaterialAssetData = { data: v, identifier: v.index, @@ -444,11 +454,11 @@ export class MaterialManager implements IMaterialManager { return res; } - getAsset(identifier: number): IMaterialAsset | null { + getAsset(identifier: number): ITextureComposedData | null { return this.assetDataStore.get(identifier) ?? null; } - getAssetByAlias(alias: string): IMaterialAsset | null { + getAssetByAlias(alias: string): ITextureComposedData | null { const id = this.assetStore.identifierOf(alias); if (isNil(id)) return null; return this.assetDataStore.get(id) ?? null; diff --git a/packages-user/client-base/src/material/types.ts b/packages-user/client-base/src/material/types.ts index 1f058e4..925ae31 100644 --- a/packages-user/client-base/src/material/types.ts +++ b/packages-user/client-base/src/material/types.ts @@ -234,7 +234,7 @@ export interface IMaterialGetter { * 根据标识符获取图集信息 * @param identifier 图集的标识符 */ - getAsset(identifier: number): IMaterialAsset | null; + getAsset(identifier: number): ITextureComposedData | null; /** * 根据额外素材索引获取额外素材 @@ -272,7 +272,7 @@ export interface IMaterialAliasGetter { * 根据别名获取图集信息 * @param alias 图集的别名 */ - getAssetByAlias(alias: string): IMaterialAsset | null; + getAssetByAlias(alias: string): ITextureComposedData | null; /** * 根据图块别名获取图块类型 @@ -302,7 +302,9 @@ export interface IMaterialManager readonly bigImageStore: ITextureStore; /** 图集信息存储 */ - readonly assetDataStore: Iterable<[number, IMaterialAsset]>; + readonly assetDataStore: Iterable<[number, ITextureComposedData]>; + /** 带有脏标记追踪的图集信息 */ + readonly trackedAsset: ITrackedAssetData; /** 图块类型映射 */ readonly clsMap: Map; @@ -465,8 +467,30 @@ export interface IAssetBuilder { */ addTextureList(texture: Iterable): Iterable; + /** + * 获取可追踪贴图对象 + */ + tracked(): ITrackedAssetData; + /** * 结束此打包器 */ close(): void; } + +export interface ITrackedAssetData extends IDirtyTracker> { + /** 图像源列表 */ + readonly sourceList: Map; + /** + * 贴图引用跳接,`ImageBitmap` 的传递性能远好于其他类型,而贴图图集为了能够动态增加内容会使用画布类型, + * 因此需要把贴图生成为额外的 `ImageBitmap`,并提供引用跳接映射。值代表在 `sourceList` 中的索引。 + */ + readonly skipRef: Map; + /** 贴图数据 */ + readonly materials: IMaterialGetter; + + /** + * 取消使用此图集,释放相关资源 + */ + close(): void; +} diff --git a/packages-user/client-modules/src/render/elements/props.ts b/packages-user/client-modules/src/render/elements/props.ts index 9466d19..fc70e42 100644 --- a/packages-user/client-modules/src/render/elements/props.ts +++ b/packages-user/client-modules/src/render/elements/props.ts @@ -1,5 +1,5 @@ import { BaseProps, TagDefine } from '@motajs/render-vue'; -import { Transform } from '@motajs/render-core'; +import { ERenderItemEvent, Transform } from '@motajs/render-core'; import { CanvasStyle } from '@motajs/render-assets'; import { ILayerGroupRenderExtends, @@ -62,7 +62,7 @@ export interface LayerProps extends BaseProps { } export interface MapRenderProps extends BaseProps { - layerList: IRenderLayerData; + layerList: Iterable; } declare module 'vue/jsx-runtime' { @@ -73,6 +73,7 @@ declare module 'vue/jsx-runtime' { animation: TagDefine; icon: TagDefine; winskin: TagDefine; + 'map-render': TagDefine; } } } diff --git a/packages-user/client-modules/src/render/map/asset.ts b/packages-user/client-modules/src/render/map/asset.ts deleted file mode 100644 index 5d754b8..0000000 --- a/packages-user/client-modules/src/render/map/asset.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { SizedCanvasImageSource } from '@motajs/render-assets'; -import { - IMaterialGetter, - IMaterialManager, - materials -} from '@user/client-base'; -import { IMapAssetData, IMapAssetManager } from './types'; -import { PrivateListDirtyTracker } from '@motajs/common'; - -export class MapAssetManager implements IMapAssetManager { - materials: IMaterialManager = materials; - - generateAsset(): IMapAssetData { - const data = new MapAssetData(); - - return data; - } -} - -class MapAssetData - extends PrivateListDirtyTracker - implements IMapAssetData -{ - sourceList: ImageBitmap[] = []; - skipRef: Map = new Map(); - materials: IMaterialGetter = materials; - - constructor() { - super(0); - } -} diff --git a/packages-user/client-modules/src/render/map/block.ts b/packages-user/client-modules/src/render/map/block.ts index 47a4b90..2d3520e 100644 --- a/packages-user/client-modules/src/render/map/block.ts +++ b/packages-user/client-modules/src/render/map/block.ts @@ -12,8 +12,8 @@ export class BlockSplitter implements IBlockSplitter { blockHeight: number = 0; dataWidth: number = 0; dataHeight: number = 0; - width: number = 0; - height: number = 0; + width: number = 1; + height: number = 1; /** 分块映射 */ readonly blockMap: Map> = new Map(); @@ -33,7 +33,7 @@ export class BlockSplitter implements IBlockSplitter { * @param y 分块纵坐标 */ private checkLocRange(x: number, y: number) { - return x > 0 && y > 0 && x < this.width && y < this.height; + return x >= 0 && y >= 0 && x < this.width && y < this.height; } getBlockByLoc(x: number, y: number): IBlockData | null { @@ -221,10 +221,10 @@ export class BlockSplitter implements IBlockSplitter { } configSplitter(config: IBlockSplitterConfig): void { + this.splitDataWidth = config.dataWidth; + this.splitDataHeight = config.dataHeight; this.splitBlockWidth = config.blockWidth; this.splitBlockHeight = config.blockHeight; - this.splitDataWidth = config.dataWidth; - this.splitBlockHeight = config.dataHeight; } private mapBlock( @@ -252,6 +252,10 @@ export class BlockSplitter implements IBlockSplitter { splitBlocks(mapFn: (block: IBlockInfo) => T): void { this.blockMap.clear(); + this.blockWidth = this.splitBlockWidth; + this.blockHeight = this.splitBlockHeight; + this.dataWidth = this.splitDataWidth; + this.dataHeight = this.splitDataHeight; const restX = this.splitDataWidth % this.splitBlockWidth; const restY = this.splitDataHeight % this.splitBlockHeight; const width = Math.floor(this.splitDataWidth / this.splitBlockWidth); diff --git a/packages-user/client-modules/src/render/map/constant.ts b/packages-user/client-modules/src/render/map/constant.ts new file mode 100644 index 0000000..d29a817 --- /dev/null +++ b/packages-user/client-modules/src/render/map/constant.ts @@ -0,0 +1,2 @@ +/** 单个图块的实例化数据数量 */ +export const INSTANCED_COUNT = 4 + 4 + 4 + 4; diff --git a/packages-user/client-modules/src/render/map/element.ts b/packages-user/client-modules/src/render/map/element.ts index 21c16e6..b55d9ac 100644 --- a/packages-user/client-modules/src/render/map/element.ts +++ b/packages-user/client-modules/src/render/map/element.ts @@ -4,10 +4,16 @@ import { Transform } from '@motajs/render-core'; import { IMapLayer } from '@user/data-state'; -import { IMapRenderer } from './types'; +import { + IMapRenderer, + IMapRendererExtends, + MapTileAlign, + MapTileBehavior +} from './types'; import { MapRenderer } from './renderer'; import { materials } from '@user/client-base'; import { ElementNamespace, ComponentInternalInstance } from 'vue'; +import { CELL_HEIGHT, CELL_WIDTH, MAP_HEIGHT, MAP_WIDTH } from '../shared'; export interface IRenderLayerData { /** 图层对象 */ @@ -41,6 +47,15 @@ export class MapRender extends RenderItem { this.renderer.addLayer(layer.layer, layer.alias); this.renderer.setZIndex(layer.layer, layer.zIndex); } + this.renderer.useAsset(materials.trackedAsset); + this.renderer.addExtends(new MapUpdateExtends(this)); + + this.renderer.setTileBackground(1); + + this.renderer.setCellSize(CELL_WIDTH, CELL_HEIGHT); + this.renderer.setRenderSize(MAP_WIDTH, MAP_HEIGHT); + + gl.viewport(0, 0, this.canvas.width, this.canvas.height); } /** @@ -83,16 +98,12 @@ export class MapRender extends RenderItem { } protected render(canvas: MotaOffscreenCanvas2D): void { + console.log('----- render start -----'); + console.time('map-element-render'); this.renderer.render(this.gl); - canvas.ctx.drawImage( - this.canvas, - 0, - 0, - this.canvas.width, - this.canvas.height - ); + canvas.ctx.drawImage(this.canvas, 0, 0, canvas.width, canvas.height); console.timeEnd('map-element-render'); } @@ -112,3 +123,11 @@ export class MapRender extends RenderItem { super.patchProp(key, prevValue, nextValue, namespace, parentComponent); } } + +class MapUpdateExtends implements IMapRendererExtends { + constructor(readonly element: MapRender) {} + + onUpdate(): void { + this.element.update(); + } +} diff --git a/packages-user/client-modules/src/render/map/renderer.ts b/packages-user/client-modules/src/render/map/renderer.ts index de63eb4..c9713e9 100644 --- a/packages-user/client-modules/src/render/map/renderer.ts +++ b/packages-user/client-modules/src/render/map/renderer.ts @@ -9,14 +9,15 @@ import { BlockCls, IAutotileProcessor, IMaterialFramedData, - IMaterialManager + IMaterialManager, + ITrackedAssetData } from '@user/client-base'; import { IContextData, - IMapAssetData, IMapBackgroundConfig, IMapRenderConfig, IMapRenderer, + IMapRendererExtends, IMapVertexGenerator, IMapViewportController, IMovingBlock, @@ -48,6 +49,7 @@ import { } from '../shared'; import { ITransformUpdatable, Transform } from '@motajs/render-core'; import { MapViewport } from './viewport'; +import { INSTANCED_COUNT } from './constant'; const enum BackgroundType { Static, @@ -94,6 +96,11 @@ export class MapRenderer renderHeight: number = 0; cellWidth: number = CELL_WIDTH; cellHeight: number = CELL_HEIGHT; + assetWidth: number = 4096; + assetHeight: number = 4096; + + /** 拓展列表 */ + private readonly extendList: Set = new Set(); /** 这个渲染器添加的图层 */ readonly layers: Set = new Set(); @@ -109,7 +116,7 @@ export class MapRenderer private layerIndexMap: Map = new Map(); /** 使用的图集数据 */ - private assetData: IMapAssetData | null = null; + private assetData: ITrackedAssetData | null = null; /** 背景图类型 */ private backgroundType: BackgroundType = BackgroundType.Tile; @@ -123,6 +130,8 @@ export class MapRenderer private backFrameSpeed: number = 300; /** 当前背景图帧数 */ private backgroundFrame: number = 0; + /** 是否需要更新背景图帧数 */ + private needUpdateBackgroundFrame: boolean = true; /** 背景图总帧数 */ private backgroundFrameCount: number = 1; /** 背景图上一帧的时刻 */ @@ -141,10 +150,6 @@ export class MapRenderer private backgroundDirty: boolean = false; /** 背景图是否正在更新 */ private backgroundPending: boolean = false; - /** 背景图宽度 */ - private backgroundWidth: number = 0; - /** 背景图高度 */ - private backgroundHeight: number = 0; /** 背景顶点数组 */ private backgroundVertex: Float32Array = new Float32Array(4 * 4); @@ -192,11 +197,15 @@ export class MapRenderer private lastFrameTime: number = 0; /** 当前帧数 */ private frameCounter: number = 0; + /** 是否需要更新当前帧数 */ + private needUpdateFrameCounter: boolean = true; /** 帧动画速率 */ private frameSpeed: number = 300; /** 画布上下文数据 */ private contextData: IContextData; + /** 是否需要更新变换矩阵 */ + private needUpdateTransform: boolean = true; /** 图块动画器 */ private readonly tileAnimater: ITextureAnimater; @@ -218,8 +227,8 @@ export class MapRenderer ) { // 上下文初始化要依赖于 offsetPool,因此提前调用 const offsetPool = this.getOffsetPool(); - const data = this.initContext()!; this.offsetPool = offsetPool; + const data = this.initContext()!; this.normalizedOffsetPool = offsetPool.map( v => v / data.tileTextureWidth ); @@ -228,44 +237,80 @@ export class MapRenderer this.vertex = new MapVertexGenerator(this, data); this.autotile = new AutotileProcessor(manager); this.tick = this.tick.bind(this); - this.transform = new Transform(); this.transform.bind(this); this.viewport = new MapViewport(this); this.viewport.bindTransform(this.transform); this.tileAnimater = new TextureColumnAnimater(); + this.initVertexPointer(gl, data); + } + + /** + * 初始化顶点 pointer + * @param gl 画布 WebGL2 上下文 + * @param data 上下文数据 + */ + private initVertexPointer(gl: WebGL2RenderingContext, data: IContextData) { + // 顶点数组初始化 + const { + backVAO, + tileVAO, + vertexBuffer, + instancedBuffer, + backgroundVertexBuffer, + vertexAttribLocation: vaLocation, + insTilePosAttribLocation: tilePos, + insTexCoordAttribLocation: texCoord, + insTileDataAttribLocation: tileData, + insTexDataAttribLocation: texData, + backVertexAttribLocation: bvaLocation, + backTexCoordAttribLocation: btcaLocation + } = data; // 背景初始化 - const arr = this.backgroundVertex; - // 左下角 - arr[0] = -1; - arr[1] = -1; - // 右下角 - arr[4] = 1; - arr[5] = -1; - // 左上角 - arr[8] = -1; - arr[9] = 1; - // 右上角 - arr[12] = 1; - arr[13] = 1; - gl.bindVertexArray(data.backVAO); - gl.bindBuffer(gl.ARRAY_BUFFER, data.backgroundVertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, arr, gl.DYNAMIC_DRAW); - gl.vertexAttribPointer( - data.backVertexAttribLocation, - 2, - gl.FLOAT, - false, - 4 * 4, - 0 - ); - gl.vertexAttribPointer( - data.backTexCoordAttribLocation, - 2, - gl.FLOAT, - false, - 4 * 4, - 2 * 4 + gl.bindVertexArray(backVAO); + gl.bindBuffer(gl.ARRAY_BUFFER, backgroundVertexBuffer); + gl.bufferData(gl.ARRAY_BUFFER, 16 * 4, gl.DYNAMIC_DRAW); + gl.vertexAttribPointer(bvaLocation, 2, gl.FLOAT, false, 4 * 4, 0); + gl.vertexAttribPointer(btcaLocation, 2, gl.FLOAT, false, 4 * 4, 2 * 4); + gl.enableVertexAttribArray(bvaLocation); + gl.enableVertexAttribArray(btcaLocation); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.bindVertexArray(null); + + // 顶点数组 + gl.bindVertexArray(tileVAO); + gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); + gl.bufferData( + gl.ARRAY_BUFFER, + // prettier-ignore + new Float32Array([ + // 左下,右下,左上,右上,前两个是顶点坐标,后两个是纹理坐标 + // 因为我们已经在数据处理阶段将数据归一化到了 [-1, 1] 的范围,因此顶点坐标应该是 [0, 1] 的范围 + // 同时又因为我们以左上角为原点,因此纵坐标需要取反 + 0, 0, 0, 0, + 1, 0, 1, 0, + 0, -1, 0, 1, + 1, -1, 1, 1 + ]), + gl.STATIC_DRAW ); + gl.vertexAttribPointer(vaLocation, 4, gl.FLOAT, false, 0, 0); + gl.vertexAttribDivisor(vaLocation, 0); + gl.enableVertexAttribArray(vaLocation); + gl.bindBuffer(gl.ARRAY_BUFFER, instancedBuffer); + const stride = INSTANCED_COUNT * 4; + gl.vertexAttribPointer(tilePos, 4, gl.FLOAT, false, stride, 0); + gl.vertexAttribPointer(texCoord, 4, gl.FLOAT, false, stride, 4 * 4); + gl.vertexAttribPointer(tileData, 4, gl.FLOAT, false, stride, 8 * 4); + gl.vertexAttribPointer(texData, 4, gl.FLOAT, false, stride, 12 * 4); + gl.vertexAttribDivisor(tilePos, 1); + gl.vertexAttribDivisor(texCoord, 1); + gl.vertexAttribDivisor(tileData, 1); + gl.vertexAttribDivisor(texData, 1); + gl.enableVertexAttribArray(tilePos); + gl.enableVertexAttribArray(texCoord); + gl.enableVertexAttribArray(tileData); + gl.enableVertexAttribArray(texData); + gl.bindBuffer(gl.ARRAY_BUFFER, null); gl.bindVertexArray(null); } @@ -298,6 +343,7 @@ export class MapRenderer this.sortLayer(); this.layerDirty = true; this.layerCount = this.layers.size; + this.resizeLayer(); } removeLayer(layer: IMapLayer): void { @@ -317,6 +363,7 @@ export class MapRenderer this.sortLayer(); this.layerDirty = true; this.layerCount = this.layers.size; + this.resizeLayer(); } getLayer(identifier: string): IMapLayer | null { @@ -363,6 +410,12 @@ export class MapRenderer this.mapWidth = maxWidth; this.mapHeight = maxHeight; this.layerDirty = true; + this.updateBackgroundVertex( + this.gl, + this.contextData, + this.contextData.backgroundWidth, + this.contextData.backgroundHeight + ); } //#endregion @@ -376,6 +429,7 @@ export class MapRenderer this.tileBack = 0; this.backLastFrame = this.timestamp; this.backgroundFrameCount = 1; + this.backgroundDirty = true; this.checkBackground(this.gl, this.contextData); } @@ -387,6 +441,7 @@ export class MapRenderer this.tileBack = 0; this.backLastFrame = this.timestamp; this.backgroundFrameCount = array.length; + this.backgroundDirty = true; this.checkBackground(this.gl, this.contextData); } @@ -396,6 +451,7 @@ export class MapRenderer this.staticBack = null; this.dynamicBack = null; this.backLastFrame = this.timestamp; + this.backgroundDirty = true; this.checkBackground(this.gl, this.contextData); } @@ -421,8 +477,8 @@ export class MapRenderer this.updateBackgroundVertex( this.gl, this.contextData, - this.backgroundWidth, - this.backgroundHeight + this.contextData.backgroundWidth, + this.contextData.backgroundHeight ); } @@ -441,7 +497,7 @@ export class MapRenderer //#region 渲染设置 - useAsset(asset: IMapAssetData): void { + useAsset(asset: ITrackedAssetData): void { this.assetData = asset; this.sortedLayers.forEach(v => { this.updateLayerArea(v, 0, 0, v.width, v.height); @@ -531,11 +587,7 @@ export class MapRenderer getAssetSourceIndex(source: SizedCanvasImageSource): number { if (!this.assetData) return -1; - if (source instanceof ImageBitmap) { - return this.assetData.sourceList.indexOf(source); - } else { - return -1; - } + return this.assetData.skipRef.get(source) ?? -1; } getOffsetIndex(offset: number): number { @@ -556,17 +608,20 @@ export class MapRenderer return null; } + const { program: tp } = tileProgram; + const { program: bp } = backProgram; + const poolLocation = gl.getUniformLocation( - tileProgram, + tp, // 数组要写 [0] 'u_offsetPool[0]' ); - const frameLocation = gl.getUniformLocation(tileProgram, 'u_nowFrame'); - const tileSampler = gl.getUniformLocation(tileProgram, 'u_sampler'); - const backSampler = gl.getUniformLocation(backProgram, 'u_sampler'); - const tileTrans = gl.getUniformLocation(tileProgram, 'u_transform'); - const backTrans = gl.getUniformLocation(backProgram, 'u_transform'); - const backFrame = gl.getUniformLocation(backProgram, 'u_nowFrame'); + const frameLocation = gl.getUniformLocation(tp, 'u_nowFrame'); + const tileSampler = gl.getUniformLocation(tp, 'u_sampler'); + const backSampler = gl.getUniformLocation(bp, 'u_sampler'); + const tileTrans = gl.getUniformLocation(tp, 'u_transform'); + const backTrans = gl.getUniformLocation(bp, 'u_transform'); + const backFrame = gl.getUniformLocation(bp, 'u_nowFrame'); if ( !poolLocation || !frameLocation || @@ -580,16 +635,16 @@ export class MapRenderer return null; } - const vertexAttrib = gl.getAttribLocation(tileProgram, 'a_position'); - const texCoordAttrib = gl.getAttribLocation(tileProgram, 'a_texCoord'); - const offsetAttrib = gl.getAttribLocation(tileProgram, 'a_offset'); - const alphaAttrib = gl.getAttribLocation(tileProgram, 'a_alpha'); - const backVertex = gl.getAttribLocation(backProgram, 'a_position'); - const backTexCoord = gl.getAttribLocation(backProgram, 'a_texCoord'); + const vertexAttrib = gl.getAttribLocation(tp, 'a_position'); + const insTilePosAttrib = gl.getAttribLocation(tp, 'a_tilePos'); + const insTexCoordAttib = gl.getAttribLocation(tp, 'a_texCoord'); + const insTileDataAttrib = gl.getAttribLocation(tp, 'a_tileData'); + const insTexDataAttib = gl.getAttribLocation(tp, 'a_texData'); + const backVertex = gl.getAttribLocation(bp, 'a_position'); + const backTexCoord = gl.getAttribLocation(bp, 'a_texCoord'); const vertexBuffer = gl.createBuffer(); - const offsetBuffer = gl.createBuffer(); - const alphaBuffer = gl.createBuffer(); + const instancedBuffer = gl.createBuffer(); const backVertexBuffer = gl.createBuffer(); const tileTexture = gl.createTexture(); @@ -600,22 +655,19 @@ export class MapRenderer gl.bindTexture(gl.TEXTURE_2D_ARRAY, tileTexture); gl.texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, 4096, 4096, 1); - - gl.texParameteri( - gl.TEXTURE_2D_ARRAY, - gl.TEXTURE_MAG_FILTER, - gl.NEAREST - ); - gl.texParameteri( - gl.TEXTURE_2D_ARRAY, - gl.TEXTURE_MIN_FILTER, - gl.NEAREST - ); + gl.bindTexture(gl.TEXTURE_2D_ARRAY, null); // 配置清空选项 - gl.clearColor(0, 0, 0, 0); + gl.clearColor(0, 0, 0, 1); gl.clearDepth(1); + // 其他配置 + gl.disable(gl.CULL_FACE); + gl.enable(gl.DEPTH_TEST); + gl.enable(gl.BLEND); + gl.depthFunc(gl.LESS); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + const data: IContextData = { tileProgram: tileProgram.program, tileVertShader: tileProgram.vertexShader, @@ -624,8 +676,7 @@ export class MapRenderer backVertShader: backProgram.vertexShader, backFragShader: backProgram.fragmentShader, vertexBuffer, - offsetBuffer, - alphaBuffer, + instancedBuffer, backgroundVertexBuffer: backVertexBuffer, offsetPoolLocation: poolLocation, nowFrameLocation: frameLocation, @@ -635,9 +686,10 @@ export class MapRenderer backTransformLocation: backTrans, backNowFrameLocation: backFrame, vertexAttribLocation: vertexAttrib, - texCoordAttribLocation: texCoordAttrib, - offsetAttribLocation: offsetAttrib, - alphaAttribLocation: alphaAttrib, + insTilePosAttribLocation: insTilePosAttrib, + insTexCoordAttribLocation: insTexCoordAttib, + insTileDataAttribLocation: insTileDataAttrib, + insTexDataAttribLocation: insTexDataAttib, backVertexAttribLocation: backVertex, backTexCoordAttribLocation: backTexCoord, tileVAO, @@ -647,9 +699,9 @@ export class MapRenderer tileTextureWidth: 4096, tileTextureHeight: 4096, tileTextureDepth: 1, - backgroundWidth: 0, - backgroundHeight: 0, - backgroundDepth: 0, + backgroundWidth: 32, + backgroundHeight: 32, + backgroundDepth: 1, tileTextureMark: Symbol(), vertexMark: Symbol() }; @@ -661,8 +713,9 @@ export class MapRenderer const gl = this.gl; const data = this.contextData; if (!data) return; - gl.deleteBuffer(data.offsetBuffer); gl.deleteBuffer(data.vertexBuffer); + gl.deleteBuffer(data.instancedBuffer); + gl.deleteBuffer(data.backgroundVertexBuffer); gl.deleteProgram(data.tileProgram); gl.deleteProgram(data.backProgram); gl.deleteShader(data.tileVertShader); @@ -671,6 +724,8 @@ export class MapRenderer gl.deleteShader(data.backFragShader); gl.deleteTexture(data.tileTexture); gl.deleteTexture(data.backgroundTexture); + gl.deleteVertexArray(data.tileVAO); + gl.deleteVertexArray(data.backVAO); } //#endregion @@ -708,6 +763,8 @@ export class MapRenderer data.tileTextureWidth = maxWidth; data.tileTextureHeight = maxHeight; data.tileTextureDepth = count; + this.assetWidth = maxWidth; + this.assetHeight = maxHeight; this.normalizedOffsetPool = this.offsetPool.map(v => v / maxWidth); this.needUpdateOffsetPool = true; return true; @@ -725,11 +782,14 @@ export class MapRenderer if (!this.assetData) return; const tile = data.tileTexture; const source = this.assetData.sourceList; + const sourceArray = [...source.values()]; if (!this.assetData.hasMark(data.tileTextureMark)) { // 如果没有标记,那么直接全部重新传递 - gl.bindTexture(gl.TEXTURE_2D_ARRAY, tile); + this.assetData.unmark(data.tileTextureMark); data.tileTextureMark = this.assetData.mark(); - this.checkTextureArraySize(gl, data, source); + gl.bindTexture(gl.TEXTURE_2D_ARRAY, tile); + this.checkTextureArraySize(gl, data, sourceArray); + console.time('texture-upload'); source.forEach((v, i) => { gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, @@ -747,13 +807,29 @@ export class MapRenderer }); gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D_ARRAY, gl.TEXTURE_WRAP_T, gl.REPEAT); + gl.texParameteri( + gl.TEXTURE_2D_ARRAY, + gl.TEXTURE_MAG_FILTER, + gl.NEAREST + ); + gl.texParameteri( + gl.TEXTURE_2D_ARRAY, + gl.TEXTURE_MIN_FILTER, + gl.NEAREST + ); + console.timeEnd('texture-upload'); } else { const dirty = this.assetData.dirtySince(data.tileTextureMark); if (dirty.size === 0) return; this.assetData.unmark(data.tileTextureMark); data.tileTextureMark = this.assetData.mark(); gl.bindTexture(gl.TEXTURE_2D_ARRAY, tile); - const sizeChanged = this.checkTextureArraySize(gl, data, source); + const sizeChanged = this.checkTextureArraySize( + gl, + data, + sourceArray + ); + console.time('texture-upload'); if (sizeChanged) { // 尺寸变化,需要全部重新传递 source.forEach((v, i) => { @@ -774,7 +850,7 @@ export class MapRenderer } else { // 否则只需要传递标记为脏的图像 dirty.forEach(v => { - const img = source[v]; + const img = source.get(v)!; gl.texSubImage3D( gl.TEXTURE_2D_ARRAY, 0, @@ -790,6 +866,7 @@ export class MapRenderer ); }); } + console.timeEnd('texture-upload'); } } @@ -803,34 +880,20 @@ export class MapRenderer data: IContextData ) { if (!this.assetData) return; - const dirty = this.vertex.dirtySince(data.vertexMark); - if (!dirty) return; + this.vertex.checkRebuild(); + const hasDirty = this.vertex.hasMark(data.vertexMark); + if (hasDirty) { + const dirty = this.vertex.dirtySince(data.vertexMark); + if (!dirty) return; + } + this.vertex.unmark(data.vertexMark); + data.vertexMark = this.vertex.mark(); const array = this.vertex.getVertexArray(); - const { - vertexBuffer, - offsetBuffer, - alphaBuffer, - tileVAO, - vertexAttribLocation: vaLocation, - texCoordAttribLocation: tcaLocation, - offsetAttribLocation: oaLocation, - alphaAttribLocation: aaLocation - } = data; - // 顶点数据不需要实例化,偏移和不透明度需要实例化 - gl.bindVertexArray(tileVAO); - gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); - gl.bufferData(gl.ARRAY_BUFFER, array.tileVertex, gl.DYNAMIC_DRAW); - gl.vertexAttribPointer(vaLocation, 3, gl.FLOAT, false, 6 * 4, 0); - gl.vertexAttribPointer(tcaLocation, 3, gl.FLOAT, false, 6 * 4, 3 * 4); - gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); - gl.bufferData(gl.ARRAY_BUFFER, array.tileOffset, gl.DYNAMIC_DRAW); - gl.vertexAttribIPointer(oaLocation, 2, gl.SHORT, 0, 0); - gl.vertexAttribDivisor(oaLocation, 1); - gl.bindBuffer(gl.ARRAY_BUFFER, alphaBuffer); - gl.bufferData(gl.ARRAY_BUFFER, array.tileAlpha, gl.DYNAMIC_DRAW); - gl.vertexAttribPointer(aaLocation, 1, gl.FLOAT, false, 0, 0); - gl.vertexAttribDivisor(aaLocation, 1); - gl.bindVertexArray(null); + const { instancedBuffer } = data; + // 更新实例化缓冲区 + gl.bindBuffer(gl.ARRAY_BUFFER, instancedBuffer); + gl.bufferData(gl.ARRAY_BUFFER, array.tileInstanced, gl.DYNAMIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, null); } /** @@ -844,6 +907,7 @@ export class MapRenderer data: IContextData, source: ImageBitmap ) { + gl.bindTexture(gl.TEXTURE_2D_ARRAY, data.backgroundTexture); const { width: w, height: h } = source; if ( w !== data.backgroundWidth || @@ -868,6 +932,7 @@ export class MapRenderer gl.UNSIGNED_BYTE, source ); + gl.bindTexture(gl.TEXTURE_2D_ARRAY, null); } /** @@ -885,6 +950,7 @@ export class MapRenderer height: number, source: ImageBitmap[] ) { + gl.bindTexture(gl.TEXTURE_2D_ARRAY, data.backgroundTexture); const w = width; const h = height; const depth = source.length; @@ -905,14 +971,15 @@ export class MapRenderer 0, 0, i, - w, - h, + v.width, + v.height, 1, gl.RGBA, gl.UNSIGNED_BYTE, v ); }); + gl.bindTexture(gl.TEXTURE_2D_ARRAY, null); } /** @@ -929,30 +996,41 @@ export class MapRenderer width: number, height: number ) { - this.backgroundWidth = width; - this.backgroundHeight = height; const w = this.backUseImageSize ? width : this.backRenderWidth; const h = this.backUseImageSize ? height : this.backRenderHeight; if (w === 0 || h === 0) return; - const rw = w / this.renderWidth; - const rh = h / this.renderHeight; - const vx = 1 / rw; - const vy = 1 / rh; + const mapRenderWidth = this.mapWidth * this.cellWidth; + const mapRenderHeight = this.mapHeight * this.cellHeight; + const vx = mapRenderWidth / w; + const vy = mapRenderHeight / h; const arr = this.backgroundVertex; + const left = -1; + const right = (mapRenderWidth / this.renderWidth) * 2 - 1; + const top = -1; + const bottom = (mapRenderHeight / this.renderHeight) * 2 - 1; // 左下角 + arr[0] = left; + arr[1] = bottom; arr[2] = 0; arr[3] = vy; // 右下角 + arr[4] = right; + arr[5] = bottom; arr[6] = vx; arr[7] = vy; // 左上角 + arr[8] = left; + arr[9] = top; arr[10] = 0; arr[11] = 0; // 右上角 + arr[12] = right; + arr[13] = top; arr[14] = vx; arr[15] = 0; gl.bindBuffer(gl.ARRAY_BUFFER, data.backgroundVertexBuffer); gl.bufferSubData(gl.ARRAY_BUFFER, 0, arr); + gl.bindBuffer(gl.ARRAY_BUFFER, null); } /** @@ -1015,7 +1093,6 @@ export class MapRenderer ); this.texDynamicBackground(gl, data, w, h, images); this.updateBackgroundVertex(gl, data, w, h); - this.backgroundHeight = renderable.length; } /** @@ -1066,9 +1143,7 @@ export class MapRenderer ) { if (!this.backgroundDirty || this.backgroundPending) return; this.backgroundPending = true; - const { backVAO, backgroundTexture } = data; - gl.bindVertexArray(backVAO); - gl.bindTexture(gl.TEXTURE_2D_ARRAY, backgroundTexture); + const { backgroundTexture } = data; // 根据背景类型使用不同贴图 switch (this.backgroundType) { case BackgroundType.Tile: { @@ -1086,6 +1161,7 @@ export class MapRenderer break; } } + gl.bindTexture(gl.TEXTURE_2D_ARRAY, backgroundTexture); // 重复模式 switch (this.backRepeatModeX) { case MapBackgroundRepeat.Repeat: { @@ -1149,18 +1225,9 @@ export class MapRenderer gl.TEXTURE_MIN_FILTER, gl.NEAREST ); - gl.bindVertexArray(null); this.backgroundPending = false; - } - - /** - * 检查偏移数组是否需要更新 - * @param gl 画布上下文 - * @param data 上下文数据 - */ - private checkOffsetPool(gl: WebGL2RenderingContext, data: IContextData) { - if (!this.needUpdateOffsetPool) return; - gl.uniform1iv(data.offsetPoolLocation, this.normalizedOffsetPool); + this.extendList.forEach(v => v.onUpdate?.()); + gl.bindTexture(gl.TEXTURE_2D_ARRAY, null); } render(): void { @@ -1172,6 +1239,26 @@ export class MapRenderer } console.time('render-map'); + const { + backVAO, + backProgram, + backNowFrameLocation, + backTransformLocation, + backgroundTexture, + tileVAO, + tileProgram, + tileTexture, + instancedBuffer, + offsetPoolLocation, + nowFrameLocation, + tileTransformLocation, + insTilePosAttribLocation: tilePos, + insTexCoordAttribLocation: texCoord, + insTileDataAttribLocation: tileData, + insTexDataAttribLocation: texData + } = data; + + console.time('layer-check'); // 图层检查 if (this.layerDirty) { this.vertex.updateLayerArray(); @@ -1182,68 +1269,106 @@ export class MapRenderer this.layerSizeDirty = false; } this.vertex.checkRebuild(); + console.timeEnd('layer-check'); + + console.time('texture-check'); // 数据检查 this.checkTexture(gl, data); this.checkTileVertexArray(gl, data); - this.checkOffsetPool(gl, data); + + console.timeEnd('texture-check'); + + console.time('update-block-cache'); const area = this.viewport.getRenderArea(); area.blockList.forEach(v => { this.vertex.updateBlockCache(v); + v.data.render(); }); + + console.timeEnd('update-block-cache'); + if (area.dirty.length > 0) { + console.time('upload-block-buffer'); // 如果需要更新顶点数组... - const { vertexBuffer, offsetBuffer, alphaBuffer } = data; const array = this.vertex.getVertexArray(); - gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer); + gl.bindBuffer(gl.ARRAY_BUFFER, instancedBuffer); area.dirty.forEach(v => { gl.bufferSubData( gl.ARRAY_BUFFER, // float32 需要 * 4 - v.startIndex * 6 * 6 * 4, - array.tileVertex, - v.startIndex * 6 * 6, - v.count * 6 * 6 - ); - }); - gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer); - area.dirty.forEach(v => { - gl.bufferSubData( - gl.ARRAY_BUFFER, - // int16 需要 * 2 - v.startIndex * 2 * 2, - array.tileOffset, - v.startIndex * 2, - v.count * 2 - ); - }); - gl.bindBuffer(gl.ARRAY_BUFFER, alphaBuffer); - area.dirty.forEach(v => { - gl.bufferSubData( - gl.ARRAY_BUFFER, - // float32 需要 * 4 - v.startIndex * 4, - array.tileAlpha, - v.startIndex, - v.count + v.startIndex * INSTANCED_COUNT * 4, + array.tileInstanced, + v.startIndex * INSTANCED_COUNT, + v.count * INSTANCED_COUNT ); }); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + console.timeEnd('upload-block-buffer'); } // 背景 console.time('render-call'); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); - gl.useProgram(data.backProgram); - gl.bindVertexArray(data.backVAO); + gl.useProgram(backProgram); + if (this.needUpdateBackgroundFrame) { + this.needUpdateBackgroundFrame = false; + gl.uniform1f(backNowFrameLocation, this.backgroundFrame); + } + if (this.needUpdateTransform) { + gl.uniformMatrix3fv( + backTransformLocation, + false, + this.transform.mat + ); + } + gl.bindTexture(gl.TEXTURE_2D_ARRAY, backgroundTexture); + gl.bindVertexArray(backVAO); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); + gl.bindVertexArray(null); + // 图块 - gl.useProgram(data.tileProgram); - gl.bindVertexArray(data.tileVAO); + gl.useProgram(tileProgram); + if (this.needUpdateOffsetPool) { + this.needUpdateOffsetPool = false; + gl.uniform1fv(offsetPoolLocation, this.normalizedOffsetPool); + } + if (this.needUpdateFrameCounter) { + this.needUpdateFrameCounter = false; + gl.uniform1f(nowFrameLocation, this.frameCounter); + } + if (this.needUpdateTransform) { + gl.uniformMatrix3fv( + tileTransformLocation, + false, + this.transform.mat + ); + } + this.needUpdateTransform = false; + gl.bindTexture(gl.TEXTURE_2D_ARRAY, tileTexture); + gl.bindVertexArray(tileVAO); + + // 由于 WebGL2 没有 glDrawArraysInstancedBaseInstance,只能每次渲染的时候临时修改 VBO 读取方式 + const stride = INSTANCED_COUNT * 4; + gl.bindBuffer(gl.ARRAY_BUFFER, instancedBuffer); + + console.log(area); + area.render.forEach(v => { - gl.drawArraysInstanced(gl.TRIANGLES, v.startIndex, v.count, 6); + const s = v.startIndex * INSTANCED_COUNT; + const o1 = s + 0; + const o2 = o1 + 4 * 4; + const o3 = o2 + 4 * 4; + const o4 = o3 + 4 * 4; + gl.vertexAttribPointer(tilePos, 4, gl.FLOAT, false, stride, o1); + gl.vertexAttribPointer(texCoord, 4, gl.FLOAT, false, stride, o2); + gl.vertexAttribPointer(tileData, 4, gl.FLOAT, false, stride, o3); + gl.vertexAttribPointer(texData, 4, gl.FLOAT, false, stride, o4); + gl.drawArraysInstanced(gl.TRIANGLE_STRIP, 0, 4, v.count); }); gl.bindVertexArray(null); + gl.bindTexture(gl.TEXTURE_2D_ARRAY, null); console.timeEnd('render-call'); console.timeEnd('render-map'); } @@ -1268,6 +1393,7 @@ export class MapRenderer h: number ) { this.vertex.updateArea(layer, x, y, w, h); + this.extendList.forEach(v => v.onUpdate?.()); } /** @@ -1279,6 +1405,7 @@ export class MapRenderer */ updateLayerBlock(layer: IMapLayer, block: number, x: number, y: number) { this.vertex.updateBlock(layer, block, x, y); + this.extendList.forEach(v => v.onUpdate?.()); } //#endregion @@ -1406,7 +1533,6 @@ export class MapRenderer tick(timestamp: number) { this.timestamp = timestamp; - const { backNowFrameLocation, nowFrameLocation } = this.contextData; // 移动数组 const expandDT = timestamp - this.lastExpandTime; @@ -1418,20 +1544,17 @@ export class MapRenderer // 背景 const backgroundDT = timestamp - this.backLastFrame; if (backgroundDT > this.backFrameSpeed) { - const last = this.backgroundFrame; this.backgroundFrame++; this.backgroundFrame %= this.backgroundFrameCount; this.backLastFrame = timestamp; - if (last !== this.backgroundFrame) { - this.gl.uniform1f(backNowFrameLocation, this.backgroundFrame); - } + this.needUpdateBackgroundFrame = true; } // 地图帧动画 const frameDT = timestamp - this.lastFrameTime; if (frameDT > this.frameSpeed) { this.frameCounter++; - this.gl.uniform1ui(nowFrameLocation, this.frameCounter); + this.needUpdateFrameCounter = true; } // 图块移动 @@ -1446,13 +1569,19 @@ export class MapRenderer } updateTransform(): void { - this.render(); + this.needUpdateTransform = true; + this.extendList.forEach(v => v.onUpdate?.()); + } + + addExtends(ex: IMapRendererExtends): void { + this.extendList.add(ex); } close(): void { this.layers.clear(); this.layerAlias.clear(); this.layerZIndex.clear(); + this.extendList.clear(); } //#endregion diff --git a/packages-user/client-modules/src/render/map/shader/back.frag b/packages-user/client-modules/src/render/map/shader/back.frag index 6aac789..6ae77b6 100644 --- a/packages-user/client-modules/src/render/map/shader/back.frag +++ b/packages-user/client-modules/src/render/map/shader/back.frag @@ -1,5 +1,6 @@ #version 300 es precision highp float; +precision highp sampler2DArray; in vec3 v_texCoord; out vec4 outColor; diff --git a/packages-user/client-modules/src/render/map/shader/back.vert b/packages-user/client-modules/src/render/map/shader/back.vert index b5e1a85..c94ec7e 100644 --- a/packages-user/client-modules/src/render/map/shader/back.vert +++ b/packages-user/client-modules/src/render/map/shader/back.vert @@ -1,8 +1,8 @@ #version 300 es precision highp float; -in vec2 a_position; -in vec2 a_texCoord; +layout(location = 0) in vec2 a_position; +layout(location = 1) in vec2 a_texCoord; out vec3 v_texCoord; @@ -10,9 +10,7 @@ uniform float u_nowFrame; uniform mat3 u_transform; void main() { - // 背景图永远是全图都画,因此变换矩阵应该作用于纹理坐标 - vec3 texCoord = vec3(a_texCoord, 1.0); - vec3 transformed = u_transform * texCoord; - v_texCoord = vec3(transformed.xy, u_nowFrame); - gl_Position = vec4(a_position, 0.0, 1.0); + vec3 transformed = u_transform * vec3(a_position, 1.0); + v_texCoord = vec3(a_texCoord, u_nowFrame); + gl_Position = vec4(transformed.xy, 0.95, 1.0); } diff --git a/packages-user/client-modules/src/render/map/shader/map.frag b/packages-user/client-modules/src/render/map/shader/map.frag index e4cc4c3..6b31186 100644 --- a/packages-user/client-modules/src/render/map/shader/map.frag +++ b/packages-user/client-modules/src/render/map/shader/map.frag @@ -1,6 +1,6 @@ #version 300 es precision highp float; -precision mediump uint; +precision highp sampler2DArray; in vec4 v_texCoord; @@ -11,4 +11,5 @@ uniform sampler2DArray u_sampler; void main() { vec4 texColor = texture(u_sampler, v_texCoord.xyz); outColor = vec4(texColor.rgb, texColor.a * v_texCoord.a); + // outColor = vec4(texColor.a * 0.001, v_texCoord.x * 6.0, v_texCoord.y * 0.0, v_texCoord.a); } diff --git a/packages-user/client-modules/src/render/map/shader/map.vert b/packages-user/client-modules/src/render/map/shader/map.vert index 34a6c19..82ab309 100644 --- a/packages-user/client-modules/src/render/map/shader/map.vert +++ b/packages-user/client-modules/src/render/map/shader/map.vert @@ -1,26 +1,36 @@ #version 300 es precision highp float; -precision mediump uint; +precision mediump int; -in vec3 a_position; -in vec3 a_texCoord; -// x: 最大帧数,y: 偏移池索引,实例化绘制传入 -in ivec2 a_offsetData; -// 不透明度,用于前景层虚化 -in float a_alpha; +// 顶点坐标 +layout(location = 0) in vec4 a_position; +// 实例化数据 +// 图块坐标 +layout(location = 1) in vec4 a_tilePos; +// 贴图坐标 +layout(location = 2) in vec4 a_texCoord; +// x: 纵深,y: 不透明度 +layout(location = 3) in vec4 a_tileData; +// x: 当前帧数,负数表示使用 u_nowFrame,y: 最大帧数,z: 偏移池索引,w: 纹理数组索引 +layout(location = 4) in vec4 a_texData; // x,y,z: 纹理坐标,w: 不透明度 out vec4 v_texCoord; -uniform vec2 u_offsetPool[$1]; -uniform uint u_nowFrame; +uniform float u_offsetPool[$1]; +uniform float u_nowFrame; uniform mat3 u_transform; void main() { + // 坐标 + vec2 pos = a_position.xy * a_tilePos.zw + a_tilePos.xy; + vec2 texCoord = a_position.zw * a_texCoord.zw + a_texCoord.xy; // 偏移量 - uint offset = mod(u_nowFrame, a_offsetData.x); - float fOffset = float(offset); - // 贴图坐标 - v_texCoord = vec4(a_texCoord.xy + u_offsetPool[a_offsetData.y] * fOffset, a_texCoord.z, a_alpha); - gl_Position = vec4(u_transform * a_position, 1.0); + float offset = a_texData.x < 0.0 ? mod(u_nowFrame, a_texData.y) : a_texData.x; + int offsetIndex = int(a_texData.z); + // 贴图偏移 + texCoord.x += u_offsetPool[offsetIndex] * offset; + v_texCoord = vec4(texCoord.xy, a_texData.w, a_tileData.y); + vec3 transformed = u_transform * vec3(pos.xy, 1.0); + gl_Position = vec4(transformed.xy, a_tileData.x, 1.0); } diff --git a/packages-user/client-modules/src/render/map/types.ts b/packages-user/client-modules/src/render/map/types.ts index a58c5a1..4e7cb90 100644 --- a/packages-user/client-modules/src/render/map/types.ts +++ b/packages-user/client-modules/src/render/map/types.ts @@ -1,14 +1,11 @@ -import { IDirtyTracker } from '@motajs/common'; -import { - ITextureRenderable, - SizedCanvasImageSource -} from '@motajs/render-assets'; +import { IDirtyMark, IDirtyTracker } from '@motajs/common'; +import { ITextureRenderable } from '@motajs/render-assets'; import { Transform } from '@motajs/render-core'; import { IAutotileProcessor, IMaterialFramedData, - IMaterialGetter, - IMaterialManager + IMaterialManager, + ITrackedAssetData } from '@user/client-base'; import { IMapLayer } from '@user/data-state'; import { TimingFn } from 'mutate-animate'; @@ -45,18 +42,6 @@ export const enum MapTileAlign { End } -export interface IMapAssetData extends IDirtyTracker> { - /** 图像源列表 */ - readonly sourceList: ImageBitmap[]; - /** - * 贴图引用跳接,`ImageBitmap` 的传递性能远好于其他类型,而贴图图集为了能够动态增加内容会使用画布类型, - * 因此需要把贴图生成为额外的 `ImageBitmap`,并提供引用跳接映射。值代表在 `sourceList` 中的索引。 - */ - readonly skipRef: Map; - /** 贴图数据 */ - readonly materials: IMaterialGetter; -} - export interface IMapBackgroundConfig { /** 是否使用图片大小作为背景图渲染大小,如果是 `false`,则使用 `renderWidth` `renderHeight` 作为渲染大小 */ readonly useImageSize: boolean; @@ -87,16 +72,6 @@ export interface IMapRenderConfig { readonly frameSpeed: number; } -export interface IMapAssetManager { - /** 素材管理对象 */ - readonly materials: IMaterialManager; - - /** - * 生成地图渲染图集数据 - */ - generateAsset(): IMapAssetData; -} - export interface IContextData { /** 图块程序 */ readonly tileProgram: WebGLProgram; @@ -126,22 +101,22 @@ export interface IContextData { readonly backNowFrameLocation: WebGLUniformLocation; /** 顶点数组输入 */ readonly vertexAttribLocation: number; - /** 纹理坐标输入输入 */ - readonly texCoordAttribLocation: number; - /** 偏移数组输入 */ - readonly offsetAttribLocation: number; - /** 不透明度数组输入 */ - readonly alphaAttribLocation: number; + /** 图块坐标输入 */ + readonly insTilePosAttribLocation: number; + /** 图块纹理坐标输入 */ + readonly insTexCoordAttribLocation: number; + /** 图块数据输入 */ + readonly insTileDataAttribLocation: number; + /** 图块当前帧数输入 */ + readonly insTexDataAttribLocation: number; /** 背景顶点数组输入 */ readonly backVertexAttribLocation: number; /** 背景纹理数组输入 */ readonly backTexCoordAttribLocation: number; /** 顶点数组 */ readonly vertexBuffer: WebGLBuffer; - /** 偏移数组 */ - readonly offsetBuffer: WebGLBuffer; - /** 不透明度数组 */ - readonly alphaBuffer: WebGLBuffer; + /** 实例化数据数组 */ + readonly instancedBuffer: WebGLBuffer; /** 背景顶点数组 */ readonly backgroundVertexBuffer: WebGLBuffer; /** 图块纹理对象 */ @@ -167,9 +142,9 @@ export interface IContextData { backgroundDepth: number; /** 图块纹理的脏标记 */ - tileTextureMark: symbol; + tileTextureMark: IDirtyMark; /** 顶点数组的脏标记 */ - vertexMark: symbol; + vertexMark: IDirtyMark; } export interface IMovingBlock { @@ -246,6 +221,13 @@ export interface IMovingBlock { destroy(): void; } +export interface IMapRendererExtends { + /** + * 当需要更新画面时执行 + */ + onUpdate?(): void; +} + export interface IMapRenderer { /** 地图渲染器使用的资源管理器 */ readonly manager: IMaterialManager; @@ -275,12 +257,22 @@ export interface IMapRenderer { readonly cellWidth: number; /** 每个格子的高度 */ readonly cellHeight: number; + /** 图集宽度 */ + readonly assetWidth: number; + /** 图集高度 */ + readonly assetHeight: number; /** * 使用指定图集对象 * @param asset 要使用的缓存对象 */ - useAsset(asset: IMapAssetData): void; + useAsset(asset: ITrackedAssetData): void; + + /** + * 添加地图渲染拓展 + * @param ex 拓展对象 + */ + addExtends(ex: IMapRendererExtends): void; /** * 摧毁此地图渲染器,表示当前渲染器不会再被使用到 @@ -379,9 +371,9 @@ export interface IMapRenderer { configRendering(config: Partial): void; /** - * 设置渲染的宽高,单位格子。例如填 13 就表示渲染宽度或高度是 13 个格子。 - * @param width 渲染的格子宽度 - * @param height 渲染的格子高度 + * 设置渲染的宽高,单位像素 + * @param width 渲染的像素宽度 + * @param height 渲染的像素高度 */ setRenderSize(width: number, height: number): void; @@ -466,21 +458,17 @@ export interface IMapRenderer { export interface IMapVertexArray { /** - * 地图渲染顶点数组,结构是一个三维张量 `[B, L, T]`,其中 `B` 代表分块,`L` 代表图层,`T` 代表图块,并按照结构顺序平铺存储。 - * 每个顶点包含两个数据,顶点的 `x,y,z` 坐标,顶点的贴图坐标 `x,y,z`。 + * 地图渲染实例化数组,结构是一个三维张量 `[B, L, T]`,其中 `B` 代表分块,`L` 代表图层,`T` 代表图块,并按照结构顺序平铺存储。 * * 语义解释就是,最内层存储图块,再外面一层存储图层,最外层存储分块。这样的话可以一次性将一个分块的所有图层渲染完毕。 + * + * 依次存储 a_tilePos, a_texCoord, a_tileData, a_texData */ - readonly tileVertex: Float32Array; + readonly tileInstanced: Float32Array; - /** 每个图块的偏移数据,使用实例化绘制,第一项表示这个图块的总帧数,第二项表示每帧的偏移量 */ - readonly tileOffset: Int16Array; - /** 每个图块的不透明度,用于实现前景层虚化效果 */ - readonly tileAlpha: Float32Array; - - /** 动态内容顶点起始索引 */ + /** 动态内容的起始索引,以实例为单位 */ readonly dynamicStart: number; - /** 动态内容顶点数量 */ + /** 动态内容的数量,以实例为单位 */ readonly dynamicCount: number; } @@ -717,21 +705,13 @@ export interface IBlockSplitter extends IBlockSplitterConfig { } export interface IMapVertexData { - /** 这个分块的顶点数组 */ - readonly vertexArray: Float32Array; - /** 这个分块的偏移数组 */ - readonly offsetArray: Int16Array; - /** 这个分块的不透明度数组 */ - readonly alphaArray: Float32Array; + /** 这个分块的实例化数据数组 */ + readonly instancedArray: Float32Array; } export interface IIndexedMapVertexData extends IMapVertexData { - /** 这个分块的顶点数组的起始索引 */ - readonly vertexStart: number; - /** 这个分块的偏移数组的起始索引 */ - readonly offsetStart: number; - /** 这个分块的不透明度数组的起始索引 */ - readonly alphaStart: number; + /** 这个分块的实例化数据的起始索引 */ + readonly instancedStart: number; } export interface ILayerDirtyData { @@ -764,6 +744,11 @@ export interface IMapVertexBlock extends IMapVertexData { */ render(): void; + /** + * 取消数据脏标记 + */ + updated(): void; + /** * 标记指定区域为脏,需要更新 * @param layer 图层对象 @@ -787,22 +772,10 @@ export interface IMapVertexBlock extends IMapVertexData { getDirtyArea(layer: IMapLayer): Readonly | null; /** - * 获取指定图层的顶点数组,是对内部存储的直接引用 + * 获取指定图层的实例化数据数组,是对内部存储的直接引用 * @param layer 图层对象 */ - getLayerVertex(layer: IMapLayer): Float32Array | null; - - /** - * 获取指定图层的偏移数组,是对内部存储的直接引用 - * @param layer 图层对象 - */ - getLayerOffset(layer: IMapLayer): Int16Array | null; - - /** - * 获取指定图层的不透明度数组,是对内部存储的直接引用 - * @param layer 图层对象 - */ - getLayerAlpha(layer: IMapLayer): Float32Array | null; + getLayerInstanced(layer: IMapLayer): Float32Array | null; /** * 获取指定图层的所有顶点数组数据,是对内部存储的直接引用 @@ -820,6 +793,9 @@ export interface IMapBlockUpdateObject { readonly y: number; } +/** + * 脏标记表示顶点数组的长度是否发生变化 + */ export interface IMapVertexGenerator extends IDirtyTracker { /** 地图渲染器 */ readonly renderer: IMapRenderer; diff --git a/packages-user/client-modules/src/render/map/vertex.ts b/packages-user/client-modules/src/render/map/vertex.ts index bd394d7..56feb29 100644 --- a/packages-user/client-modules/src/render/map/vertex.ts +++ b/packages-user/client-modules/src/render/map/vertex.ts @@ -22,6 +22,7 @@ import { BlockSplitter } from './block'; import { clamp, isNil } from 'lodash-es'; import { BlockCls, IMaterialFramedData } from '@user/client-base'; import { IRect, SizedCanvasImageSource } from '@motajs/render-assets'; +import { INSTANCED_COUNT } from './constant'; // todo: 潜在优化点:顶点数组的 z 坐标以及纹理的 z 坐标可以换为实例化绘制 @@ -79,20 +80,9 @@ interface BlockIndex extends IndexedBlockMapPos { readonly mapIndex: number; } -interface TilePosition { - /** 左边缘位置 */ - readonly left: number; - /** 上边缘位置 */ - readonly top: number; - /** 右边缘位置 */ - readonly right: number; - /** 下边缘位置 */ - readonly bottom: number; -} - const enum VertexUpdate { - /** 更新顶点信息 */ - Vertex = 0b01, + /** 更新顶点位置信息 */ + Position = 0b01, /** 更新贴图信息 */ Texture = 0b10, /** 全部更新 */ @@ -112,29 +102,19 @@ export class MapVertexGenerator /** 空顶点数组,因为空顶点很常用,所以直接定义一个全局常量 */ private static readonly EMPTY_VETREX: Float32Array = new Float32Array( - 6 * 6 + INSTANCED_COUNT ); readonly block: IBlockSplitter; - /** 顶点数组 */ - private vertexArray: Float32Array = new Float32Array(); /** 偏移数组 */ - private offsetArray: Int16Array = new Int16Array(); - /** 不透明度数组 */ - private alphaArray: Float32Array = new Float32Array(); - /** 动态内容顶点数组 */ - private dynamicVertexArray: Float32Array = new Float32Array(); + private instancedArray: Float32Array = new Float32Array(); /** 动态内容偏移数组 */ - private dynamicOffsetArray: Int16Array = new Int16Array(); - /** 动态内容不透明度数组 */ - private dynamicAlphaArray: Float32Array = new Float32Array(); + private dynamicInstancedArray: Float32Array = new Float32Array(); /** 图层列表 */ private layers: IMapLayer[] = []; - /** 对应分块的顶点数组 */ - private blockList: Map = new Map(); /** 分块宽度 */ private blockWidth: number = MAP_BLOCK_WIDTH; /** 分块高度 */ @@ -163,6 +143,7 @@ export class MapVertexGenerator readonly data: IContextData ) { super(); + this.resizeMap(); this.block = new BlockSplitter(); } @@ -175,23 +156,13 @@ export class MapVertexGenerator const area = this.renderer.mapWidth * this.renderer.mapHeight; const staticCount = area * this.renderer.layerCount; const count = staticCount + this.dynamicLength; - const vertexSize = count * 6 * 6; - const offsetSize = count * 2; - const alphaSize = count; - this.vertexArray = new Float32Array(vertexSize); - this.offsetArray = new Int16Array(offsetSize); - this.alphaArray = new Float32Array(alphaSize); - this.alphaArray.fill(1); + const offsetSize = count * INSTANCED_COUNT; + this.instancedArray = new Float32Array(offsetSize); this.staticLength = staticCount; - this.dynamicVertexArray = this.vertexArray.subarray( - staticCount * 6 * 6, - count * 6 * 6 + this.dynamicInstancedArray = this.instancedArray.subarray( + staticCount * INSTANCED_COUNT, + count * INSTANCED_COUNT ); - this.dynamicOffsetArray = this.offsetArray.subarray( - staticCount * 2, - count * 2 - ); - this.dynamicAlphaArray = this.alphaArray.subarray(staticCount, count); } private splitBlock() { @@ -206,19 +177,16 @@ export class MapVertexGenerator const lastCount = (this.mapHeight % this.blockHeight) * this.blockWidth; const bh = Math.floor(this.mapHeight / this.blockHeight); const lastStart = bh * lineCount; - const layerCount = this.renderer.layerCount; this.block.splitBlocks(block => { // 最后一行的算法与其他行不同 const startIndex = block.height < this.blockHeight ? lastStart + lastCount * block.x : lineCount * block.y + blockCount * block.x; - const count = block.width * block.height * layerCount; + const count = block.width * block.height; const origin: IMapVertexData = { - vertexArray: this.vertexArray, - offsetArray: this.offsetArray, - alphaArray: this.alphaArray + instancedArray: this.instancedArray }; const data = new MapVertexBlock( this.renderer, @@ -245,22 +213,18 @@ export class MapVertexGenerator this.mapHeight !== this.renderer.mapHeight ) { this.needRebuild = true; + this.mapWidth = this.renderer.mapWidth; + this.mapHeight = this.renderer.mapHeight; } } expandMoving(targetSize: number): void { - const beforeVertex = this.vertexArray; - const beforeOffset = this.offsetArray; - const beforeAlpha = this.alphaArray; + const beforeOffset = this.instancedArray; this.dynamicLength = targetSize; this.mallocVertexArray(); - this.vertexArray.set(beforeVertex); - this.offsetArray.set(beforeOffset); - this.alphaArray.set(beforeAlpha); + this.instancedArray.set(beforeOffset); const array: IMapVertexData = { - vertexArray: this.vertexArray, - offsetArray: this.offsetArray, - alphaArray: this.alphaArray + instancedArray: this.instancedArray }; // 重建一下对应分块就行了,不需要重新分块 for (const block of this.block.iterateBlocks()) { @@ -269,42 +233,25 @@ export class MapVertexGenerator } reduceMoving(targetSize: number, indexMap: Map): void { - const beforeVertexLength = this.vertexArray.length; - const beforeOffsetLength = this.offsetArray.length; - const beforeAlphaLength = this.alphaArray.length; + const beforeOffsetLength = this.instancedArray.length; const deltaLength = this.dynamicLength - targetSize; this.dynamicLength = targetSize; - this.vertexArray = this.vertexArray.subarray( + this.instancedArray = this.instancedArray.subarray( 0, - beforeVertexLength - deltaLength * 6 * 6 - ); - this.offsetArray = this.offsetArray.subarray( - 0, - beforeOffsetLength - deltaLength * 2 - ); - this.alphaArray = this.alphaArray.subarray( - 0, - beforeAlphaLength - deltaLength + beforeOffsetLength - deltaLength * INSTANCED_COUNT ); indexMap.forEach((target, from) => { const next = from + 1; - this.dynamicVertexArray.copyWithin( - target * 6 * 6, - from * 6 * 6, - next * 6 * 6 + this.dynamicInstancedArray.copyWithin( + target * INSTANCED_COUNT, + from * INSTANCED_COUNT, + next * INSTANCED_COUNT ); - this.dynamicOffsetArray.copyWithin(target * 2, from * 2, next * 2); - this.dynamicAlphaArray[target] = this.dynamicAlphaArray[from]; }); - this.dynamicVertexArray = this.dynamicVertexArray.subarray( + this.dynamicInstancedArray = this.dynamicInstancedArray.subarray( 0, - targetSize * 6 * 6 + targetSize * INSTANCED_COUNT ); - this.dynamicOffsetArray = this.dynamicOffsetArray.subarray( - 0, - targetSize * 2 - ); - this.dynamicAlphaArray = this.dynamicAlphaArray.subarray(0, targetSize); // 这个不需要重新分配内存,依然共用同一个 ArrayBuffer,因此不需要重新分块 } @@ -323,6 +270,7 @@ export class MapVertexGenerator this.needRebuild = false; this.mallocVertexArray(); this.splitBlock(); + this.dirty(); } //#endregion @@ -339,7 +287,7 @@ export class MapVertexGenerator pos: BlockMapPos, width: number, height: number - ): TilePosition { + ): Readonly { const { renderWidth, renderHeight, @@ -362,14 +310,14 @@ export class MapVertexGenerator const cw = cwu * 2; // normalized cell width in range [-1, 1] const ch = chu * 2; // normalized cell height in range [-1, 1] const cl = pos.mapX * cw - 1; // cell left - const ct = pos.mapY * ch - 1; // cell top + const ct = 1 - pos.mapY * ch; // cell top if (mode === MapTileBehavior.FitToSize) { // 适应到格子大小 return { - left: cl, - top: ct, - right: cl + cw, - bottom: ct + ch + x: cl, + y: ct, + w: cw, + h: ch }; } else { // 维持大小,需要判断对齐 @@ -382,26 +330,20 @@ export class MapVertexGenerator const th = thu * 2; // normalized texture height in range [-1, 1] let left = 0; let top = 0; - let right = 0; - let bottom = 0; switch (tileAlignX) { case MapTileAlign.Start: { // 左对齐 left = cl; - right = cl + tw; break; } case MapTileAlign.Center: { // 左右居中对齐 - const center = cl + cwu; - left = center - twu; - right = center + twu; + left = cl + cwu - twu; break; } case MapTileAlign.End: { // 右对齐 - right = cl + cw; - left = right - tw; + left = cl + cw - tw; break; } } @@ -409,23 +351,19 @@ export class MapVertexGenerator case MapTileAlign.Start: { // 上对齐 top = ct; - bottom = ct + th; break; } case MapTileAlign.Center: { // 上下居中对齐 - const center = ct + chu; - top = center - thu; - bottom = center + thu; + top = ct + chu - thu; break; } case MapTileAlign.End: { // 下对齐 - bottom = ct + ch; - top = bottom - th; + top = ct + ch - th; } } - return { left, top, right, bottom }; + return { x: left, y: top, w: tw, h: th }; } } @@ -448,69 +386,44 @@ export class MapVertexGenerator frames: number, update: VertexUpdate ) { - const { offsetArray, vertexArray } = vertex; + const { instancedArray } = vertex; // 顶点数组 - const { renderWidth, renderHeight, layerCount } = this.renderer; - const { x, y, w, h } = rect; - const vertexStart = index.blockIndex * 6 * 6; - if (update & VertexUpdate.Texture) { - const texLeft = (x / renderWidth) * 2 - 1; - const texTop = (y / renderHeight) * 2 - 1; - const texRight = ((x + w) / renderWidth) * 2 - 1; - const texBottom = ((y + h) / renderHeight) * 2 - 1; - // 六个顶点分别是 左下,右下,左上,左上,右下,右上 - vertexArray[vertexStart + 3] = texLeft; - vertexArray[vertexStart + 4] = texBottom; - vertexArray[vertexStart + 5] = assetIndex; - vertexArray[vertexStart + 9] = texRight; - vertexArray[vertexStart + 10] = texBottom; - vertexArray[vertexStart + 11] = assetIndex; - vertexArray[vertexStart + 15] = texLeft; - vertexArray[vertexStart + 16] = texTop; - vertexArray[vertexStart + 17] = assetIndex; - vertexArray[vertexStart + 21] = texLeft; - vertexArray[vertexStart + 22] = texTop; - vertexArray[vertexStart + 23] = assetIndex; - vertexArray[vertexStart + 27] = texRight; - vertexArray[vertexStart + 28] = texBottom; - vertexArray[vertexStart + 29] = assetIndex; - vertexArray[vertexStart + 33] = texRight; - vertexArray[vertexStart + 34] = texTop; - vertexArray[vertexStart + 35] = assetIndex; - } - if (update & VertexUpdate.Vertex) { + const { layerCount, assetWidth, assetHeight } = this.renderer; + const { x, y, w: width, h: height } = rect; + const startIndex = index.blockIndex * INSTANCED_COUNT; + if (update & VertexUpdate.Position) { // 如果需要更新顶点坐标 const layerIndex = this.renderer.getLayerIndex(index.layer); - const layerStart = (layerIndex / layerCount) * 2 - 1; - const zIndex = layerStart + index.mapY / this.mapHeight; - const { left, top, right, bottom } = this.getTilePosition( - index, - w, - h - ); - vertexArray[vertexStart] = left; - vertexArray[vertexStart + 1] = bottom; - vertexArray[vertexStart + 2] = zIndex; - vertexArray[vertexStart + 6] = right; - vertexArray[vertexStart + 7] = bottom; - vertexArray[vertexStart + 8] = zIndex; - vertexArray[vertexStart + 12] = left; - vertexArray[vertexStart + 13] = top; - vertexArray[vertexStart + 14] = zIndex; - vertexArray[vertexStart + 18] = left; - vertexArray[vertexStart + 19] = top; - vertexArray[vertexStart + 20] = zIndex; - vertexArray[vertexStart + 24] = right; - vertexArray[vertexStart + 25] = bottom; - vertexArray[vertexStart + 26] = zIndex; - vertexArray[vertexStart + 30] = right; - vertexArray[vertexStart + 31] = top; - vertexArray[vertexStart + 32] = zIndex; + // 避免 z 坐标是 1 的时候被裁剪,因此范围选择 [-0.9, 0.9] + const layerStart = (layerIndex / layerCount) * 1.8 - 0.9; + const zIndex = -layerStart - index.mapY / this.mapHeight; + const { x, y, w, h } = this.getTilePosition(index, width, height); + // 图块位置 + instancedArray[startIndex] = x; + instancedArray[startIndex + 1] = y; + instancedArray[startIndex + 2] = w; + instancedArray[startIndex + 3] = h; + // 图块纵深 + instancedArray[startIndex + 8] = zIndex; + } + if (update & VertexUpdate.Texture) { + const texX = x / assetWidth; + const texY = y / assetHeight; + const texWidth = width / assetWidth; + const texHeight = height / assetHeight; + // 纹理坐标 + instancedArray[startIndex + 4] = texX; + instancedArray[startIndex + 5] = texY; + instancedArray[startIndex + 6] = texWidth; + instancedArray[startIndex + 7] = texHeight; + // 不透明度 + instancedArray[startIndex + 9] = 1; + // 帧数、偏移、纹理索引 + instancedArray[startIndex + 12] = -1; + instancedArray[startIndex + 13] = frames; + instancedArray[startIndex + 14] = offsetIndex; + instancedArray[startIndex + 15] = assetIndex; } - // 偏移数组 - const offsetStart = index.blockIndex * 2; - offsetArray[offsetStart] = frames; - offsetArray[offsetStart + 1] = offsetIndex; } /** @@ -615,13 +528,13 @@ export class MapVertexGenerator if (!tile) { // 不存在可渲染对象,认为是空图块 - vertex.vertexArray.set( - MapVertexGenerator.EMPTY_VETREX, - index.blockIndex * 6 * 6 - ); - const offsetStart = index.blockIndex * 2; - vertex.offsetArray[offsetStart] = 0; - vertex.offsetArray[offsetStart + 1] = 0; + const { instancedArray } = vertex; + const instancedStart = index.blockIndex * INSTANCED_COUNT; + // 只把坐标改成 0 就可以了,其他的保留 + instancedArray[instancedStart] = 0; + instancedArray[instancedStart + 1] = 0; + instancedArray[instancedStart + 2] = 0; + instancedArray[instancedStart + 3] = 0; return; } @@ -738,12 +651,15 @@ export class MapVertexGenerator w: number, h: number ): void { + if (!this.renderer.hasLayer(layer)) return; + this.checkRebuild(); // 这里多一圈是因为要更新这一圈的自动元件 const ax = x - 1; const ay = y - 1; const areaRight = x + w + 1; const areaBottom = y + h + 1; const blocks = this.block.iterateBlocksOfDataArea(ax, ay, w + 2, h + 2); + for (const block of blocks) { const left = ax - block.dataX; const top = ay - block.dataY; @@ -761,6 +677,7 @@ export class MapVertexGenerator if (import.meta.env.DEV) { this.checkUpdateCallPerformance('updateBlock'); } + this.checkRebuild(); this.updateBlockVertex(layer, num, x, y); } @@ -769,6 +686,7 @@ export class MapVertexGenerator if (import.meta.env.DEV) { this.checkUpdateCallPerformance('updateBlockList'); } + this.checkRebuild(); if (blocks.length > 50) { // 对于超出50个的更新操作使用懒更新 blocks.forEach(v => { @@ -864,11 +782,12 @@ export class MapVertexGenerator } updateBlockCache(block: Readonly>): void { - console.time('update-block-cache'); + if (!block.data.dirty) return; const layers = this.renderer.getSortedLayer(); layers.forEach(layer => { const dirty = block.data.getDirtyArea(layer); if (!dirty || !dirty.dirty) return; + block.data.updated(); const vertex = block.data.getLayerData(layer); const mapData = this.renderer.getMapLayerData(layer); if (!vertex || !mapData) return; @@ -897,7 +816,6 @@ export class MapVertexGenerator } } }); - console.timeEnd('update-block-cache'); } //#endregion @@ -908,7 +826,7 @@ export class MapVertexGenerator const data = this.renderer.getMapLayerData(layer); const block = this.block.getBlockByDataLoc(x, y); if (!data || !block) return; - const vertexArray = block.data.getLayerOffset(layer); + const vertexArray = block.data.getLayerInstanced(layer); if (!vertexArray) return; const mapIndex = y * this.mapWidth + x; const num = data.array[mapIndex]; @@ -917,19 +835,19 @@ export class MapVertexGenerator const bx = x - block.dataX; const by = y - block.dataY; const bIndex = by * block.width + bx; - vertexArray[bIndex * 2] = tile.frames; + vertexArray[bIndex * INSTANCED_COUNT + 13] = tile.frames; block.data.markRenderDirty(); } disableStaticFrameAnimate(layer: IMapLayer, x: number, y: number): void { const block = this.block.getBlockByDataLoc(x, y); if (!block) return; - const vertexArray = block.data.getLayerOffset(layer); + const vertexArray = block.data.getLayerInstanced(layer); if (!vertexArray) return; const bx = x - block.dataX; const by = y - block.dataY; const bIndex = by * block.width + bx; - vertexArray[bIndex * 2] = 1; + vertexArray[bIndex * INSTANCED_COUNT + 13] = 1; block.data.markRenderDirty(); } @@ -941,12 +859,12 @@ export class MapVertexGenerator ): void { const block = this.block.getBlockByDataLoc(x, y); if (!block) return; - const vertexArray = block.data.getLayerAlpha(layer); + const vertexArray = block.data.getLayerInstanced(layer); if (!vertexArray) return; const bx = x - block.dataX; const by = y - block.dataY; const bIndex = by * block.width + bx; - vertexArray[bIndex] = alpha; + vertexArray[bIndex * INSTANCED_COUNT + 9] = alpha; block.data.markRenderDirty(); } @@ -958,9 +876,7 @@ export class MapVertexGenerator if (!this.renderer.hasMoving(block)) return; const { cls, frames, offset, texture } = block.texture; const vertex: IMapVertexData = { - vertexArray: this.dynamicVertexArray, - offsetArray: this.dynamicOffsetArray, - alphaArray: this.dynamicAlphaArray + instancedArray: this.dynamicInstancedArray }; const index: IndexedBlockMapPos = { layer: block.layer, @@ -974,7 +890,7 @@ export class MapVertexGenerator logger.error(40, block.tile.toString()); return; } - const update = updateTexture ? VertexUpdate.All : VertexUpdate.Vertex; + const update = updateTexture ? VertexUpdate.All : VertexUpdate.Position; if (cls === BlockCls.Autotile) { // 自动元件使用全部不连接 const renderable = this.renderer.autotile.renderWithoutCheck( @@ -1025,32 +941,33 @@ export class MapVertexGenerator } deleteMoving(moving: IMovingBlock): void { - this.dynamicVertexArray.set( + const instancedStart = moving.index * INSTANCED_COUNT; + // 这个需要全部清空了,因为可能会复用 + this.dynamicInstancedArray.set( MapVertexGenerator.EMPTY_VETREX, - moving.index * 6 * 6 + instancedStart ); - const offsetStart = moving.index * 2; - this.dynamicOffsetArray[offsetStart] = 0; - this.dynamicOffsetArray[offsetStart + 1] = 0; - this.dynamicAlphaArray[moving.index] = 0; this.dynamicRenderDirty = true; } enableDynamicFrameAnimate(block: IMovingBlock): void { if (!this.renderer.hasMoving(block)) return; - this.dynamicOffsetArray[block.index * 2] = 1; + const instancedStart = block.index * INSTANCED_COUNT; + this.dynamicInstancedArray[instancedStart + 13] = 1; this.dynamicRenderDirty = true; } disableDynamicFrameAnimate(block: IMovingBlock): void { if (!this.renderer.hasMoving(block)) return; - this.dynamicOffsetArray[block.index * 2] = block.texture.frames; + const instancedStart = block.index * INSTANCED_COUNT; + this.dynamicInstancedArray[instancedStart + 13] = block.texture.frames; this.dynamicRenderDirty = true; } setDynamicAlpha(block: IMovingBlock, alpha: number): void { if (!this.renderer.hasMoving(block)) return; - this.dynamicAlphaArray[block.index] = alpha; + const instancedStart = block.index * INSTANCED_COUNT; + this.dynamicInstancedArray[instancedStart + 9] = alpha; this.dynamicRenderDirty = true; } @@ -1066,11 +983,9 @@ export class MapVertexGenerator getVertexArray(): IMapVertexArray { this.checkRebuild(); return { - dynamicStart: this.staticLength * 6, - dynamicCount: this.dynamicLength * 6, - tileVertex: this.vertexArray, - tileOffset: this.offsetArray, - tileAlpha: this.alphaArray + dynamicStart: this.staticLength, + dynamicCount: this.dynamicLength, + tileInstanced: this.instancedArray }; } @@ -1080,11 +995,9 @@ export class MapVertexGenerator //#region 分块对象 class MapVertexBlock implements IMapVertexBlock { - vertexArray!: Float32Array; - offsetArray!: Int16Array; - alphaArray!: Float32Array; + instancedArray!: Float32Array; - dirty: boolean = false; + dirty: boolean = true; renderDirty: boolean = true; private readonly layerDirty: Map = new Map(); @@ -1092,22 +1005,21 @@ class MapVertexBlock implements IMapVertexBlock { readonly startIndex: number; readonly endIndex: number; readonly count: number; + readonly layerCount: number; - readonly vertexStart: number; - readonly offsetStart: number; - readonly alphaStart: number; + readonly instancedStart: number; private readonly indexMap: Map = new Map(); - private readonly vertexMap: Map = new Map(); - private readonly offsetMap: Map = new Map(); - private readonly alphaMap: Map = new Map(); + private readonly instancedMap: Map = new Map(); /** * 创建分块的顶点数组对象,此对象不能动态扩展,如果地图变化,需要全部重建 * @param renderer 渲染器对象 * @param originArray 原始顶点数组 * @param startIndex 起始网格索引 - * @param count 分块数量 + * @param count 单个图层的图块数量 + * @param blockWidth 分块宽度 + * @param blockHeight 分块高度 */ constructor( readonly renderer: IMapRenderer, @@ -1117,17 +1029,13 @@ class MapVertexBlock implements IMapVertexBlock { private readonly blockWidth: number, private readonly blockHeight: number ) { - this.startIndex = startIndex; - this.endIndex = startIndex + count; - this.count = count; const layerCount = renderer.layerCount; - const vertexStart = startIndex * layerCount * 6 * 6; - const offsetStart = startIndex * layerCount * 2; - const alphaStart = startIndex * layerCount; - this.vertexStart = vertexStart; - this.offsetStart = offsetStart; - this.alphaStart = alphaStart; - + this.startIndex = startIndex * layerCount; + this.endIndex = (startIndex + count) * layerCount; + this.count = count; + const offsetStart = startIndex * layerCount * INSTANCED_COUNT; + this.instancedStart = offsetStart; + this.layerCount = layerCount; this.rebuild(originArray); } @@ -1135,6 +1043,10 @@ class MapVertexBlock implements IMapVertexBlock { this.renderDirty = false; } + updated(): void { + this.dirty = false; + } + markRenderDirty() { // todo: 潜在优化点:vertex, offset, alpha 的脏标记分开 this.renderDirty = true; @@ -1173,33 +1085,20 @@ class MapVertexBlock implements IMapVertexBlock { } rebuild(originArray: IMapVertexData) { - const vertexStart = this.vertexStart; - const offsetStart = this.offsetStart; - const alphaStart = this.alphaStart; + const offsetStart = this.instancedStart; const count = this.count; - this.vertexArray = originArray.vertexArray.subarray( - vertexStart, - vertexStart + count * 6 * 6 - ); - this.offsetArray = originArray.offsetArray.subarray( + this.instancedArray = originArray.instancedArray.subarray( offsetStart, - offsetStart + count * 2 - ); - this.alphaArray = originArray.alphaArray.subarray( - alphaStart, - alphaStart + count + offsetStart + count * INSTANCED_COUNT * this.layerCount ); this.renderer.getSortedLayer().forEach((v, i) => { - const vs = vertexStart + i * count * 6 * 6; - const os = offsetStart + i * count * 2; - const as = alphaStart + i * count; - const va = this.vertexArray.subarray(vs, vs + count * 6 * 6); - const oa = this.offsetArray.subarray(os, os + count * 2); - const aa = this.alphaArray.subarray(as, as + count); - this.vertexMap.set(v, va); - this.offsetMap.set(v, oa); - this.alphaMap.set(v, aa); + const os = i * count * INSTANCED_COUNT; + const oa = this.instancedArray.subarray( + os, + os + count * INSTANCED_COUNT + ); + this.instancedMap.set(v, oa); this.indexMap.set(v, i); this.layerDirty.set(v, { dirty: true, @@ -1209,33 +1108,21 @@ class MapVertexBlock implements IMapVertexBlock { dirtyBottom: this.blockHeight }); }); + this.dirty = true; } - getLayerVertex(layer: IMapLayer): Float32Array | null { - return this.vertexMap.get(layer) ?? null; - } - - getLayerOffset(layer: IMapLayer): Int16Array | null { - return this.offsetMap.get(layer) ?? null; - } - - getLayerAlpha(layer: IMapLayer): Float32Array | null { - return this.alphaMap.get(layer) ?? null; + getLayerInstanced(layer: IMapLayer): Float32Array | null { + return this.instancedMap.get(layer) ?? null; } getLayerData(layer: IMapLayer): IIndexedMapVertexData | null { - const vertex = this.vertexMap.get(layer); - const offset = this.offsetMap.get(layer); - const alpha = this.alphaMap.get(layer); + const offset = this.instancedMap.get(layer); const index = this.indexMap.get(layer); - if (!vertex || !offset || !alpha || isNil(index)) return null; + if (!offset || isNil(index)) return null; return { - vertexArray: vertex, - offsetArray: offset, - alphaArray: alpha, - vertexStart: this.vertexStart + index * this.count * 6 * 6, - offsetStart: this.offsetStart + index * this.count * 2, - alphaStart: this.alphaStart + index * this.count + instancedArray: offset, + instancedStart: + this.instancedStart + index * this.count * INSTANCED_COUNT }; } } diff --git a/packages-user/client-modules/src/render/map/viewport.ts b/packages-user/client-modules/src/render/map/viewport.ts index 8835bb2..5c17309 100644 --- a/packages-user/client-modules/src/render/map/viewport.ts +++ b/packages-user/client-modules/src/render/map/viewport.ts @@ -19,20 +19,31 @@ export class MapViewport implements IMapViewportController { this.vertex = renderer.vertex; } + private pushBlock( + list: IMapRenderArea[], + start: IBlockData, + end: IBlockData + ) { + const startIndex = start.data.startIndex; + const endIndex = end.data.endIndex; + list.push({ + startIndex, + endIndex, + count: endIndex - startIndex + }); + } + getRenderArea(): IMapRenderData { const { cellWidth, cellHeight, renderWidth, renderHeight } = this.renderer; const { blockWidth, blockHeight, width, height } = this.vertex.block; - // 减一是因为第一个像素是 0,所以最后一个像素就是宽度减一 - const r = renderWidth - 1; - const b = renderHeight - 1; // 其实只需要算左上角和右下角就行了 - const [left, top] = this.transform.transformed(0, 0); - const [right, bottom] = this.transform.transformed(r, b); - const cl = left / cellWidth; - const ct = top / cellHeight; - const cr = right / cellWidth; - const cb = bottom / cellHeight; + const [left, top] = this.transform.untransformed(-1, -1); + const [right, bottom] = this.transform.untransformed(1, 1); + const cl = (left * renderWidth) / cellWidth; + const ct = (top * renderHeight) / cellHeight; + const cr = (right * renderWidth) / cellWidth; + const cb = (bottom * renderHeight) / cellHeight; const blockLeft = clamp(Math.floor(cl / blockWidth), 0, width - 1); const blockRight = clamp(Math.floor(cr / blockWidth), 0, width - 1); const blockTop = clamp(Math.floor(ct / blockHeight), 0, height - 1); @@ -42,56 +53,93 @@ export class MapViewport implements IMapViewportController { const updateArea: IMapRenderArea[] = []; const blockList: IBlockData[] = []; - // 使用这种方式的话,索引在换行之前都是连续的,方便整合 - for (let ny = blockTop; ny <= blockBottom; ny++) { - const first = this.vertex.block.getBlockByLoc(blockLeft, ny)!; - const last = this.vertex.block.getBlockByLoc(blockRight, ny)!; - if (first.data.dirty) { - blockList.push(first); + const widthOne = blockLeft === blockRight; + const heightOne = blockTop === blockBottom; + + if (widthOne && heightOne) { + // 只能看到一个分块 + const block = this.vertex.block.getBlockByLoc(blockLeft, blockTop)!; + if (block.data.dirty || block.data.renderDirty) { + blockList.push(block); } - if (last.data.dirty) { - blockList.push(last); - } - renderArea.push({ - startIndex: first.data.startIndex, - endIndex: last.data.endIndex, - count: last.data.endIndex - first.data.startIndex - }); - for (let nx = blockLeft + 1; nx < blockRight; nx++) { - const block = this.vertex.block.getBlockByLoc(nx, ny)!; - if (block.data.dirty) { + } else if (widthOne) { + // 看到的区域分块宽度是 1 + for (let ny = blockTop; ny <= blockBottom; ny++) { + const block = this.vertex.block.getBlockByLoc(blockLeft, ny)!; + if (block.data.dirty || block.data.renderDirty) { blockList.push(block); } } + } else if (heightOne) { + // 看到的区域分块高度是 1 + for (let nx = blockLeft; nx <= blockRight; nx++) { + const block = this.vertex.block.getBlockByLoc(nx, blockTop)!; + if (block.data.dirty || block.data.renderDirty) { + blockList.push(block); + } + } + } else { + // 看到的区域分块宽高都不是 1 + // 使用这种方式的话,索引在换行之前都是连续的,方便整合 + for (let ny = blockTop; ny <= blockBottom; ny++) { + const first = this.vertex.block.getBlockByLoc(blockLeft, ny)!; + const last = this.vertex.block.getBlockByLoc(blockRight, ny)!; + if (first.data.dirty) { + blockList.push(first); + } + if (last.data.dirty && first !== last) { + blockList.push(last); + } + for (let nx = blockLeft + 1; nx < blockRight; nx++) { + const block = this.vertex.block.getBlockByLoc(nx, ny)!; + if (block.data.dirty) { + blockList.push(block); + } + } + } } if (blockList.length > 0) { if (blockList.length === 1) { const block = blockList[0]; - updateArea.push(block.data); + if (block.data.renderDirty) { + this.pushBlock(updateArea, block, block); + } + this.pushBlock(renderArea, block, block); } else { - let continuousStart: IBlockData = blockList[0]; - let continuousLast: IBlockData = blockList[0]; + // 更新区域 + let updateStart: IBlockData = blockList[0]; + let updateEnd: IBlockData = blockList[0]; + let renderStart: IBlockData = blockList[0]; + let renderEnd: IBlockData = blockList[0]; for (let i = 1; i < blockList.length; i++) { const block = blockList[i]; - if (block.index === continuousLast.index + 1) { - // 连续则合并 - continuousLast = block; + const { renderDirty } = block.data; + // 连续则合并 + // 渲染区域 + if (block.index === renderEnd.index + 1) { + renderEnd = block; } else { - const start = continuousStart.data.startIndex; - const end = continuousLast.data.endIndex; - updateArea.push({ - startIndex: start, - endIndex: end, - count: end - start - }); - continuousStart = block; - continuousLast = block; + this.pushBlock(renderArea, renderStart, renderEnd); + renderStart = block; + renderEnd = block; + } + // 缓冲区更新区域 + if (renderDirty && block.index === updateEnd.index + 1) { + updateEnd = block; + } else { + this.pushBlock(updateArea, updateStart, updateEnd); + updateStart = block; + updateEnd = block; } } + this.pushBlock(updateArea, updateStart, updateEnd); + this.pushBlock(renderArea, renderStart, renderEnd); } } + // todo: 动态内容 + return { render: renderArea, dirty: updateArea, diff --git a/packages-user/client-modules/src/render/shared.ts b/packages-user/client-modules/src/render/shared.ts index d8bc30f..2cbe2bc 100644 --- a/packages-user/client-modules/src/render/shared.ts +++ b/packages-user/client-modules/src/render/shared.ts @@ -5,6 +5,10 @@ import { Font } from '@motajs/render-style'; //#region 地图 +/** 每个格子的默认宽度,现阶段用处不大 */ +export const CELL_WIDTH = 32; +/** 每个格子的默认高度,现阶段用处不大 */ +export const CELL_HEIGHT = 32; /** 每个格子的宽高 */ export const CELL_SIZE = 32; /** 地图格子宽度,此处仅影响画面,可能不影响游戏内的部分逻辑,游戏内逻辑地图大小请在 core.js 中修改 */ @@ -29,10 +33,6 @@ export const DYNAMIC_RESERVE = 16; * 调整此值可以调整频率,值越大,越不容易因为数量小于预留数量而减小预留。 */ export const MOVING_TOLERANCE = 60; -/** 每个格子的默认宽度,现阶段用处不大 */ -export const CELL_WIDTH = 32; -/** 每个格子的默认高度,现阶段用处不大 */ -export const CELL_HEIGHT = 32; //#region 状态栏 diff --git a/packages-user/client-modules/src/render/ui/main.tsx b/packages-user/client-modules/src/render/ui/main.tsx index cfc68b1..843402e 100644 --- a/packages-user/client-modules/src/render/ui/main.tsx +++ b/packages-user/client-modules/src/render/ui/main.tsx @@ -1,4 +1,3 @@ -import { LayerShadowExtends } from '../legacy/shadow'; import { Props, Font, @@ -36,44 +35,51 @@ import { RightStatusBar } from './statusBar'; import { ReplayingStatus } from './toolbar'; -import { getHeroStatusOn, HeroSkill, NightSpecial } from '@user/data-state'; +import { + getHeroStatusOn, + HeroSkill, + NightSpecial, + state +} from '@user/data-state'; import { jumpIgnoreFloor } from '@user/legacy-plugin-data'; import { hook } from '@user/data-base'; -import { FloorDamageExtends, FloorItemDetail } from '../elements'; -import { LayerGroupPortal } from '../legacy/portal'; -import { LayerGroupFilter } from '../legacy/gameCanvas'; -import { LayerGroupHalo } from '../legacy/halo'; import { FloorChange } from '../legacy/fallback'; -import { PopText } from '../legacy/pop'; import { mainUIController } from './controller'; -import { - ILayerGroupRenderExtends, - LayerGroupAnimate, - FloorViewport, - ILayerRenderExtends, - HeroRenderer, - LayerDoorAnimate, - LayerGroup -} from '../elements'; +import { LayerGroup } from '../elements'; import { isNil } from 'lodash-es'; import { materials } from '@user/client-base'; +import { IRenderLayerData } from '../map/element'; const MainScene = defineComponent(() => { //#region 基本定义 - const layerGroupExtends: ILayerGroupRenderExtends[] = [ - new FloorDamageExtends(), - new FloorItemDetail(), - new LayerGroupFilter(), - new LayerGroupPortal(), - new LayerGroupHalo(), - new LayerGroupAnimate(), - new FloorViewport() - ]; - const eventExtends: ILayerRenderExtends[] = [ - new HeroRenderer(), - new LayerDoorAnimate(), - new LayerShadowExtends() + const layerList: IRenderLayerData[] = [ + { + layer: state.layer.getLayerByAlias('bg')!, + zIndex: 10, + alias: 'bg' + }, + { + layer: state.layer.getLayerByAlias('bg2')!, + zIndex: 20, + alias: 'bg2' + }, + { + layer: state.layer.getLayerByAlias('event')!, + zIndex: 30, + alias: 'event' + }, + { + layer: state.layer.getLayerByAlias('fg')!, + zIndex: 40, + alias: 'fg' + }, + { + layer: state.layer.getLayerByAlias('fg2')!, + zIndex: 50, + alias: 'fg2' + } ]; + const mainTextboxProps: Props = { text: '', hidden: true, @@ -272,7 +278,7 @@ const MainScene = defineComponent(() => { const tileset = materials.getAsset(0); if (!tileset) return; canvas.ctx.drawImage( - tileset.data.texture.source, + tileset.texture.source, 0, 0, canvas.width, @@ -283,6 +289,7 @@ const MainScene = defineComponent(() => { return () => (