Skip to content

Commit b7d0147

Browse files
authored
refactor: add texture upload/dispose api to renderer (#692)
Summary: adds abstract uploadTexture and disposeTexture to the renderer base and implements them in both backends. this is going to be used for uploading textures directly to the gpu without having to do it lazily through the existing render path
1 parent 045d5cd commit b7d0147

5 files changed

Lines changed: 80 additions & 40 deletions

File tree

src/core/renderer.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Camera } from "../objects/cameras/camera";
22
import { Color, ColorLike } from "../math/color";
33
import { Layer } from "./layer";
44
import { Viewport } from "./viewport";
5+
import { Texture } from "../objects/textures/texture";
56

67
export abstract class Renderer {
78
private readonly canvas_: HTMLCanvasElement | null;
@@ -64,6 +65,10 @@ export abstract class Renderer {
6465

6566
public abstract get gpuTextureCount(): number;
6667

68+
public abstract uploadTexture(texture: Texture): void;
69+
70+
public abstract disposeTexture(texture: Texture): void;
71+
6772
public set backgroundColor(color: ColorLike) {
6873
this.backgroundColor_ = Color.from(color);
6974
}

src/renderers/webgl_renderer.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Geometry, Primitive } from "../core/geometry";
1313
import { Box2 } from "../math/box2";
1414
import { Viewport } from "../core/viewport";
1515
import { Camera } from "../objects/cameras/camera";
16+
import { Texture } from "../objects/textures/texture";
1617

1718
import { mat4, vec2, vec3, vec4 } from "gl-matrix";
1819
import { Frustum } from "../math/frustum";
@@ -166,7 +167,7 @@ export class WebGLRenderer extends Renderer {
166167
protected renderObject(layer: Layer, objectIndex: number, camera: Camera) {
167168
const object = layer.objects[objectIndex];
168169
object.popStaleTextures().forEach((texture) => {
169-
this.textures_.disposeTexture(texture);
170+
this.textures_.dispose(texture);
170171
});
171172

172173
if (!object.programName) return;
@@ -276,6 +277,14 @@ export class WebGLRenderer extends Renderer {
276277
}
277278
}
278279

280+
public override uploadTexture(texture: Texture) {
281+
this.textures_.uploadTexture(texture);
282+
}
283+
284+
public override disposeTexture(texture: Texture) {
285+
this.textures_.dispose(texture);
286+
}
287+
279288
private glGetPrimitive(type: Primitive) {
280289
switch (type) {
281290
case "points":

src/renderers/webgl_textures.ts

Lines changed: 35 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -47,25 +47,13 @@ export class WebGLTextures {
4747
`Texture index ${index} must be in [0, ${this.maxTextureUnits_ - 1}]`
4848
);
4949
}
50-
this.gl_.activeTexture(this.gl_.TEXTURE0 + index);
51-
52-
const textureType = this.getTextureType(texture);
53-
const info = this.getDataFormatInfo(texture.dataFormat, texture.dataType);
54-
55-
if (!this.textures_.has(texture)) {
56-
this.generateTexture(texture, info, textureType);
57-
}
5850

5951
const textureId = this.textures_.get(texture);
60-
if (!textureId) {
61-
throw new Error("Failed to retrieve texture ID");
62-
}
63-
64-
this.gl_.bindTexture(textureType, textureId);
65-
if (texture.needsUpdate && texture.data !== null) {
66-
this.configureTextureParameters(texture, textureType);
67-
this.uploadTextureData(texture, info, textureType);
68-
texture.needsUpdate = false;
52+
if (textureId && !texture.needsUpdate) {
53+
this.gl_.activeTexture(this.gl_.TEXTURE0 + index);
54+
this.gl_.bindTexture(this.getTextureType(texture), textureId);
55+
} else {
56+
this.uploadTexture(texture, index);
6957
}
7058

7159
this.currentTexture_ = texture;
@@ -123,7 +111,35 @@ export class WebGLTextures {
123111
}
124112
}
125113

126-
public disposeTexture(texture: Texture) {
114+
public uploadTexture(texture: Texture, index = 0) {
115+
if (this.textures_.has(texture) && !texture.needsUpdate) return;
116+
117+
if (texture.data === null) {
118+
throw new Error("Cannot upload a texture that has no CPU data");
119+
}
120+
121+
const textureType = this.getTextureType(texture);
122+
const info = this.getDataFormatInfo(texture.dataFormat, texture.dataType);
123+
124+
this.gl_.activeTexture(this.gl_.TEXTURE0 + index);
125+
126+
if (!this.textures_.has(texture)) {
127+
this.generateTexture(texture, info, textureType);
128+
}
129+
130+
const textureId = this.textures_.get(texture);
131+
if (!textureId) {
132+
throw new Error("Failed to retrieve texture ID");
133+
}
134+
135+
this.gl_.bindTexture(textureType, textureId);
136+
this.configureTextureParameters(texture, textureType);
137+
this.uploadTextureData(texture, info, textureType);
138+
139+
texture.needsUpdate = false;
140+
}
141+
142+
public dispose(texture: Texture) {
127143
const id = this.textures_.get(texture);
128144
if (id) {
129145
this.gl_.deleteTexture(id);
@@ -142,7 +158,7 @@ export class WebGLTextures {
142158

143159
public disposeAll() {
144160
for (const texture of Array.from(this.textures_.keys())) {
145-
this.disposeTexture(texture);
161+
this.dispose(texture);
146162
}
147163
this.gpuTextureBytes_ = 0;
148164
this.textureCount_ = 0;

src/renderers/webgpu/webgpu_renderer.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,14 @@ class WebGPURenderer extends Renderer {
391391
// in beginRenderPass(). There is no imperative clear command.
392392
}
393393

394+
public override uploadTexture(texture: Texture) {
395+
this.texturePool_.get(texture);
396+
}
397+
398+
public override disposeTexture(texture: Texture) {
399+
this.texturePool_.dispose(texture);
400+
}
401+
394402
private setUniformsForObject(
395403
object: RenderableObject,
396404
pipeline: WebGPUPipeline,

src/renderers/webgpu/webgpu_texture_pool.ts

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,34 @@ export default class WebGPUTexturePool {
2020
this.textures_ = [];
2121
}
2222

23-
public get(entry: Texture) {
24-
const cached = this.textures_.find((t) => t.entry === entry);
25-
if (cached) {
26-
if (entry.needsUpdate) {
27-
this.upload(entry, cached.texture);
28-
}
29-
return cached.texture;
23+
public get(entry: Texture): GPUTexture {
24+
let cached = this.textures_.find((t) => t.entry === entry);
25+
if (cached && !entry.needsUpdate) return cached.texture;
26+
27+
if (entry.data === null) {
28+
throw new Error("Cannot upload a texture that has no CPU data");
3029
}
3130

32-
const texture = this.device_.createTexture({
33-
size: textureSize(entry),
34-
dimension: entry instanceof Texture3D ? "3d" : "2d",
35-
format: textureGPUFormat(entry),
36-
usage:
37-
GPUTextureUsage.TEXTURE_BINDING |
38-
GPUTextureUsage.COPY_DST |
39-
GPUTextureUsage.COPY_SRC,
40-
});
31+
if (!cached) {
32+
const texture = this.device_.createTexture({
33+
size: textureSize(entry),
34+
dimension: entry instanceof Texture3D ? "3d" : "2d",
35+
format: textureGPUFormat(entry),
36+
usage:
37+
GPUTextureUsage.TEXTURE_BINDING |
38+
GPUTextureUsage.COPY_DST |
39+
GPUTextureUsage.COPY_SRC,
40+
});
41+
this.textures_.push({ entry: entry, texture: texture });
42+
cached = this.textures_[this.textures_.length - 1];
43+
}
4144

42-
this.upload(entry, texture);
43-
this.textures_.push({ entry: entry, texture: texture });
45+
this.upload(entry, cached.texture);
4446

4547
entry.readTexel = (x, y, z) =>
46-
this.readTexel(texture, entry.dataType, x, y, z);
48+
this.readTexel(cached.texture, entry.dataType, x, y, z);
4749

48-
return texture;
50+
return cached.texture;
4951
}
5052

5153
private async readTexel(

0 commit comments

Comments
 (0)