Skip to content

Commit 8a37a04

Browse files
authored
ImageOverlayPlugin: compose textures with the canvas API (#1415)
* Use canvas for texture composition * Cleanup * Remove use of "renderer" * Remove texture cleanup * Fix gaps * Remove premultipliedAlpha assumption * Fix up canvas composition * Simplify * Fix the flipping * Cleanup * Remove flipY
1 parent c1639dd commit 8a37a04

4 files changed

Lines changed: 44 additions & 181 deletions

File tree

src/three/plugins/images/ImageOverlayPlugin.js

Lines changed: 14 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { WebGLRenderTarget, Color, SRGBColorSpace, BufferAttribute, Matrix4, Vector3, Box3, Triangle, CanvasTexture } from 'three';
1+
import { Color, SRGBColorSpace, BufferAttribute, Matrix4, Vector3, Box3, Triangle, CanvasTexture } from 'three';
22
import { PriorityQueue, PriorityQueueItemRemovedError } from '3d-tiles-renderer/core';
33
import { CesiumIonAuth, GoogleCloudAuth } from '3d-tiles-renderer/core/plugins';
44
import { TiledTextureComposer } from './overlays/TiledTextureComposer.js';
@@ -126,7 +126,6 @@ export class ImageOverlayPlugin {
126126
const {
127127
overlays = [],
128128
resolution = 256,
129-
renderer = null,
130129
enableTileSplitting = true,
131130
} = options;
132131

@@ -136,7 +135,6 @@ export class ImageOverlayPlugin {
136135
this.priority = - 15;
137136

138137
// options
139-
this.renderer = renderer;
140138
this.resolution = resolution;
141139
this._enableTileSplitting = enableTileSplitting;
142140
this.overlays = [];
@@ -147,7 +145,6 @@ export class ImageOverlayPlugin {
147145
this.tileComposer = null;
148146
this.tileControllers = new Map();
149147
this.overlayInfo = new Map();
150-
this.usedTextures = new Set();
151148
this.meshParams = new WeakMap();
152149
this.pendingTiles = new Map();
153150
this.processedTiles = new Set();
@@ -169,13 +166,7 @@ export class ImageOverlayPlugin {
169166
// plugin functions
170167
init( tiles ) {
171168

172-
if ( ! this.renderer ) {
173-
174-
throw new Error( 'ImageOverlayPlugin: "renderer" instance must be provided.' );
175-
176-
}
177-
178-
const tileComposer = new TiledTextureComposer( this.renderer );
169+
const tileComposer = new TiledTextureComposer();
179170
const processQueue = new PriorityQueue();
180171
processQueue.maxJobs = 10;
181172
processQueue.priorityCallback = ( a, b ) => {
@@ -380,7 +371,7 @@ export class ImageOverlayPlugin {
380371

381372
const { target } = tileInfo.get( tile );
382373
bytes = bytes || 0;
383-
bytes += MemoryUtils.getTextureByteLength( target?.texture );
374+
bytes += MemoryUtils.getTextureByteLength( target );
384375

385376
}
386377

@@ -1163,7 +1154,7 @@ export class ImageOverlayPlugin {
11631154

11641155
}
11651156

1166-
const { tiles, overlayInfo, resolution, tileComposer, tileControllers, usedTextures, processQueue } = this;
1157+
const { tiles, overlayInfo, resolution, tileComposer, tileControllers, processQueue } = this;
11671158
const { ellipsoid } = tiles;
11681159
const { controller, tileInfo } = overlayInfo.get( overlay );
11691160
const tileController = tileControllers.get( tile );
@@ -1241,12 +1232,13 @@ export class ImageOverlayPlugin {
12411232
let target = null;
12421233
if ( heightInRange && countTilesInRange( range, info.level, overlay ) !== 0 ) {
12431234

1244-
target = new WebGLRenderTarget( resolution, resolution, {
1245-
depthBuffer: false,
1246-
stencilBuffer: false,
1247-
generateMipmaps: false,
1248-
colorSpace: SRGBColorSpace,
1249-
} );
1235+
const canvas = document.createElement( 'canvas' );
1236+
canvas.width = resolution;
1237+
canvas.height = resolution;
1238+
1239+
target = new CanvasTexture( canvas );
1240+
target.colorSpace = SRGBColorSpace;
1241+
target.generateMipmaps = false;
12501242

12511243
}
12521244

@@ -1274,7 +1266,7 @@ export class ImageOverlayPlugin {
12741266
// if the previous layer is present then draw it as an overlay to fill in any gaps while we wait for
12751267
// the next set of textures
12761268
tileComposer.setRenderTarget( target, range );
1277-
tileComposer.clear( 0xffffff, 0 );
1269+
tileComposer.clear();
12781270

12791271
forEachTileInBounds( range, info.level - 1, tiling, ( tx, ty, tl ) => {
12801272

@@ -1284,8 +1276,6 @@ export class ImageOverlayPlugin {
12841276
if ( tex && ! ( tex instanceof Promise ) ) {
12851277

12861278
tileComposer.draw( tex, span );
1287-
usedTextures.add( tex );
1288-
this._scheduleCleanup();
12891279

12901280
}
12911281

@@ -1313,16 +1303,14 @@ export class ImageOverlayPlugin {
13131303

13141304
// draw the textures
13151305
tileComposer.setRenderTarget( target, range );
1316-
tileComposer.clear( 0xffffff, 0 );
1306+
tileComposer.clear();
13171307

13181308
forEachTileInBounds( range, info.level, tiling, ( tx, ty, tl ) => {
13191309

13201310
// draw using normalized bounds since the mercator bounds are non-linear
13211311
const span = tiling.getTileBounds( tx, ty, tl, true, false );
13221312
const tex = imageSource.get( tx, ty, tl );
13231313
tileComposer.draw( tex, span );
1324-
usedTextures.add( tex );
1325-
this._scheduleCleanup();
13261314

13271315
} );
13281316

@@ -1380,7 +1368,7 @@ export class ImageOverlayPlugin {
13801368
params.layerInfo.length = overlays.length;
13811369

13821370
// assign the uniforms
1383-
params.layerMaps.value[ i ] = target !== null ? target.texture : null;
1371+
params.layerMaps.value[ i ] = target !== null ? target : null;
13841372
params.layerInfo.value[ i ] = overlay;
13851373

13861374
// mark per-layer defines
@@ -1397,30 +1385,6 @@ export class ImageOverlayPlugin {
13971385

13981386
}
13991387

1400-
_scheduleCleanup() {
1401-
1402-
// clean up textures used for drawing the tile overlays
1403-
if ( ! this._cleanupScheduled ) {
1404-
1405-
this._cleanupScheduled = true;
1406-
requestAnimationFrame( () => {
1407-
1408-
const { usedTextures } = this;
1409-
usedTextures.forEach( tex => {
1410-
1411-
tex.dispose();
1412-
1413-
} );
1414-
1415-
usedTextures.clear();
1416-
this._cleanupScheduled = false;
1417-
1418-
} );
1419-
1420-
}
1421-
1422-
}
1423-
14241388
_markNeedsUpdate() {
14251389

14261390
if ( this.needsUpdate === false ) {

src/three/plugins/images/overlays/TiledTextureComposer.js

Lines changed: 28 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,161 +1,59 @@
1-
import { ShaderMaterial, MathUtils, Vector2, PlaneGeometry, OrthographicCamera, Mesh, Color, DoubleSide } from 'three';
2-
3-
const _camera = /* @__PURE__ */ new OrthographicCamera();
4-
const _color = /* @__PURE__ */ new Color();
1+
import { MathUtils } from 'three';
52

63
// Utility for composing a series of tiled textures together onto a target texture in a given range
74
export class TiledTextureComposer {
85

9-
constructor( renderer ) {
6+
constructor() {
107

11-
this.renderer = renderer;
12-
this.renderTarget = null;
8+
this.canvas = null;
9+
this.context = null;
1310
this.range = [ 0, 0, 1, 1 ];
14-
this.quad = new Mesh( new PlaneGeometry(), new ComposeTextureMaterial() );
1511

1612
}
1713

1814
// set the target render texture and the range that represents the full span
1915
setRenderTarget( renderTarget, range ) {
2016

21-
this.renderTarget = renderTarget;
17+
this.canvas = renderTarget.image;
18+
this.context = renderTarget.image.getContext( '2d' );
2219
this.range = [ ...range ];
2320

2421
}
2522

2623
// draw the given texture at the given span with the provided projection
2724
draw( texture, span ) {
2825

29-
// draw the texture at the given sub range
30-
const { range, renderer, quad, renderTarget } = this;
31-
const material = quad.material;
32-
material.map = texture;
33-
34-
// map the range to draw the texture to
35-
material.minRange.x = MathUtils.mapLinear( span[ 0 ], range[ 0 ], range[ 2 ], - 1, 1 );
36-
material.minRange.y = MathUtils.mapLinear( span[ 1 ], range[ 1 ], range[ 3 ], - 1, 1 );
37-
38-
material.maxRange.x = MathUtils.mapLinear( span[ 2 ], range[ 0 ], range[ 2 ], - 1, 1 );
39-
material.maxRange.y = MathUtils.mapLinear( span[ 3 ], range[ 1 ], range[ 3 ], - 1, 1 );
40-
41-
// draw the texture
42-
const currentRenderTarget = renderer.getRenderTarget();
43-
const currentAutoClear = renderer.autoClear;
44-
renderer.autoClear = false;
45-
renderer.setRenderTarget( renderTarget );
46-
renderer.render( quad, _camera );
47-
renderer.setRenderTarget( currentRenderTarget );
48-
renderer.autoClear = currentAutoClear;
49-
50-
material.map = null;
51-
52-
}
53-
54-
// clear the set target
55-
clear( color, alpha = 1 ) {
56-
57-
// clear the texture
58-
const { renderer, renderTarget } = this;
59-
const currentRenderTarget = renderer.getRenderTarget();
60-
const currentClearColor = renderer.getClearColor( _color );
61-
const currentClearAlpha = renderer.getClearAlpha();
62-
63-
renderer.setClearColor( color, alpha );
64-
renderer.setRenderTarget( renderTarget );
65-
renderer.clear();
26+
const { canvas, range, context } = this;
27+
const { width, height } = canvas;
28+
const { image } = texture;
29+
const minX = Math.round( MathUtils.mapLinear( span[ 0 ], range[ 0 ], range[ 2 ], 0, width ) );
30+
const minY = Math.round( MathUtils.mapLinear( span[ 1 ], range[ 1 ], range[ 3 ], 0, height ) );
31+
const maxX = Math.round( MathUtils.mapLinear( span[ 2 ], range[ 0 ], range[ 2 ], 0, width ) );
32+
const maxY = Math.round( MathUtils.mapLinear( span[ 3 ], range[ 1 ], range[ 3 ], 0, height ) );
6633

67-
renderer.setRenderTarget( currentRenderTarget );
68-
renderer.setClearColor( currentClearColor, currentClearAlpha );
34+
const imageWidth = maxX - minX;
35+
const imageHeight = maxY - minY;
36+
if ( ! ( image instanceof ImageBitmap ) ) {
6937

70-
}
71-
72-
dispose() {
73-
74-
this.quad.material.dispose();
75-
this.quad.geometry.dispose();
76-
77-
}
78-
79-
}
80-
81-
// Draws the given texture with no depth testing at the given bounds defined by "minRange" and "maxRange"
82-
class ComposeTextureMaterial extends ShaderMaterial {
83-
84-
// the [ - 1, 1 ] NDC ranges to draw the texture at
85-
get minRange() {
38+
context.drawImage( image, minX, height - minY, imageWidth, - imageHeight );
8639

87-
return this.uniforms.minRange.value;
40+
} else {
8841

89-
}
90-
91-
get maxRange() {
42+
context.save();
43+
context.translate( minX, height - minY );
44+
context.scale( 1, - 1 );
45+
context.drawImage( image, 0, 0, imageWidth, imageHeight );
46+
context.restore();
9247

93-
return this.uniforms.maxRange.value;
48+
}
9449

9550
}
9651

97-
// access the map being drawn
98-
get map() {
99-
100-
return this.uniforms.map.value;
101-
102-
}
103-
104-
set map( v ) {
105-
106-
this.uniforms.map.value = v;
107-
108-
}
109-
110-
constructor() {
111-
112-
super( {
113-
depthWrite: false,
114-
depthTest: false,
115-
transparent: false,
116-
side: DoubleSide,
117-
premultipliedAlpha: true,
118-
uniforms: {
119-
map: { value: null },
120-
121-
// the normalized [0, 1] range of the target to draw to
122-
minRange: { value: new Vector2() },
123-
maxRange: { value: new Vector2() },
124-
},
125-
126-
vertexShader: /* glsl */`
127-
128-
uniform vec2 minRange;
129-
uniform vec2 maxRange;
130-
varying vec2 vUv;
131-
132-
void main() {
133-
134-
vUv = uv;
135-
gl_Position = vec4( mix( minRange, maxRange, uv ), 0, 1 );
136-
137-
}
138-
139-
`,
140-
141-
fragmentShader: /* glsl */`
142-
143-
uniform sampler2D map;
144-
uniform vec2 minRange;
145-
uniform vec2 maxRange;
146-
varying vec2 vUv;
147-
148-
void main() {
149-
150-
// sample the texture
151-
gl_FragColor = texture( map, vUv );
152-
#include <premultiplied_alpha_fragment>
153-
154-
}
155-
156-
`,
157-
} );
52+
// clear the set target
53+
clear() {
15854

55+
const { context, canvas } = this;
56+
context.clearRect( 0, 0, canvas.width, canvas.height );
15957

16058
}
16159

src/three/plugins/images/overlays/wrapOverlaysMaterial.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ export function wrapOverlaysMaterial( material, previousOnBeforeCompile ) {
146146
147147
} else {
148148
149-
// premultiplied alpha equation
149+
tint.rgb *= tint.a;
150150
diffuseColor = tint + diffuseColor * ( 1.0 - tint.a );
151151
152152
}

src/three/plugins/images/sources/TiledImageSource.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export class TiledImageSource extends DataCache {
2626
// helper for processing the buffer into a texture
2727
async processBufferToTexture( buffer ) {
2828

29+
// pre-flip the y axis
2930
const blob = new Blob( [ buffer ] );
3031
const imageBitmap = await createImageBitmap( blob, {
3132
premultiplyAlpha: 'none',

0 commit comments

Comments
 (0)