Skip to content

Commit 5635a89

Browse files
authored
feat: mipmap generator (#209)
1 parent bb86063 commit 5635a89

File tree

3 files changed

+140
-0
lines changed

3 files changed

+140
-0
lines changed

src/webgpu/Device.ts

+7
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ import {
8484
} from './utils';
8585
import { preprocessShader_GLSL } from '../shader';
8686
import { RenderBundle_WebGPU } from './RenderBundle';
87+
import { MipmapGenerator } from './MipmapGenerator';
8788

8889
export class Device_WebGPU implements SwapChain, IDevice_WebGPU {
8990
private swapChainWidth = 0;
@@ -123,6 +124,7 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU {
123124
private canvasContext: GPUCanvasContext;
124125
private glsl_compile: typeof glsl_compile_;
125126
private WGSLComposer: WGSLComposer;
127+
private mipmapGenerator: MipmapGenerator;
126128

127129
constructor(
128130
adapter: GPUAdapter,
@@ -137,6 +139,7 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU {
137139
this.canvasContext = canvasContext;
138140
this.glsl_compile = glsl_compile;
139141
this.WGSLComposer = wGSLComposer;
142+
this.mipmapGenerator = new MipmapGenerator(device);
140143

141144
this.fallbackTexture2D = this.createFallbackTexture(
142145
TextureDimension.TEXTURE_2D,
@@ -266,6 +269,10 @@ export class Device_WebGPU implements SwapChain, IDevice_WebGPU {
266269
return this;
267270
}
268271

272+
getMipmapGenerator(): MipmapGenerator {
273+
return this.mipmapGenerator;
274+
}
275+
269276
getCanvas(): HTMLCanvasElement | OffscreenCanvas {
270277
return this.canvas;
271278
}

src/webgpu/MipmapGenerator.ts

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
export class MipmapGenerator {
2+
private readonly _device: GPUDevice;
3+
private readonly _mipmapShader: GPUShaderModule;
4+
private readonly _mipmapSampler: GPUSampler;
5+
private readonly _pipelines: Map<GPUTextureFormat, GPURenderPipeline>;
6+
7+
constructor(device: GPUDevice) {
8+
this._device = device;
9+
10+
this._mipmapShader = this._device.createShaderModule({
11+
label: 'MipmapGenerator',
12+
code: `
13+
struct VSOutput {
14+
@builtin(position) position: vec4f,
15+
@location(0) texcoord: vec2f,
16+
};
17+
18+
@vertex fn vs(
19+
@builtin(vertex_index) vertexIndex : u32
20+
) -> VSOutput {
21+
let pos = array(
22+
// 1st triangle
23+
vec2f( 0.0, 0.0), // center
24+
vec2f( 1.0, 0.0), // right, center
25+
vec2f( 0.0, 1.0), // center, top
26+
27+
// 2nd triangle
28+
vec2f( 0.0, 1.0), // center, top
29+
vec2f( 1.0, 0.0), // right, center
30+
vec2f( 1.0, 1.0), // right, top
31+
);
32+
33+
var vsOutput: VSOutput;
34+
let xy = pos[vertexIndex];
35+
vsOutput.position = vec4f(xy * 2.0 - 1.0, 0.0, 1.0);
36+
vsOutput.texcoord = vec2f(xy.x, 1.0 - xy.y);
37+
return vsOutput;
38+
}
39+
40+
@group(0) @binding(0) var ourSampler: sampler;
41+
@group(0) @binding(1) var ourTexture: texture_2d<f32>;
42+
43+
@fragment fn fs(fsInput: VSOutput) -> @location(0) vec4f {
44+
return textureSample(ourTexture, ourSampler, fsInput.texcoord);
45+
}
46+
`,
47+
});
48+
this._mipmapSampler = this._device.createSampler({
49+
minFilter: 'linear',
50+
});
51+
this._pipelines = new Map();
52+
}
53+
54+
private _requestPipeline(format: GPUTextureFormat) {
55+
let pipeline = this._pipelines.get(format);
56+
if (!pipeline) {
57+
pipeline = this._device.createRenderPipeline({
58+
layout: 'auto',
59+
vertex: {
60+
module: this._mipmapShader,
61+
entryPoint: 'vs',
62+
},
63+
fragment: {
64+
module: this._mipmapShader,
65+
entryPoint: 'fs',
66+
targets: [{ format }],
67+
},
68+
});
69+
this._pipelines.set(format, pipeline);
70+
}
71+
return pipeline;
72+
}
73+
74+
generateMipmap(texture: GPUTexture) {
75+
const commandEncoder = this._device.createCommandEncoder({
76+
label: 'mipmap generator command encoder',
77+
});
78+
const pipeline = this._requestPipeline(texture.format,);
79+
80+
let width = texture.width;
81+
let height = texture.height;
82+
let baseMipLevel = 0;
83+
84+
while ((width > 1 || height > 1) && baseMipLevel < texture.mipLevelCount - 1) {
85+
width = Math.max(1, width / 2);
86+
height = Math.max(1, height / 2);
87+
88+
const bindGroup = this._device.createBindGroup({
89+
layout: pipeline.getBindGroupLayout(0),
90+
entries: [
91+
{
92+
binding: 0,
93+
resource: this._mipmapSampler,
94+
},
95+
{
96+
binding: 1,
97+
resource: texture.createView({
98+
baseMipLevel,
99+
mipLevelCount: 1,
100+
}),
101+
},
102+
],
103+
});
104+
105+
++baseMipLevel;
106+
107+
const pass = commandEncoder.beginRenderPass({
108+
colorAttachments: [
109+
{
110+
view: texture.createView({
111+
baseMipLevel,
112+
mipLevelCount: 1,
113+
}),
114+
loadOp: 'clear',
115+
storeOp: 'store',
116+
},
117+
],
118+
});
119+
pass.setPipeline(pipeline);
120+
pass.setBindGroup(0, bindGroup);
121+
pass.draw(6);
122+
pass.end();
123+
}
124+
125+
this._device.queue.submit([commandEncoder.finish()]);
126+
}
127+
}

src/webgpu/Texture.ts

+6
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
getBlockInformationFromFormat,
77
translateTextureViewDimension,
88
} from './utils';
9+
import { Device_WebGPU } from './Device';
910

1011
export class Texture_WebGPU
1112
extends ResourceBase_WebGPU
@@ -86,6 +87,7 @@ export class Texture_WebGPU
8687
GPUTextureUsage.TEXTURE_BINDING |
8788
GPUTextureUsage.COPY_DST |
8889
GPUTextureUsage.RENDER_ATTACHMENT,
90+
mipLevelCount: this.mipLevelCount,
8991
};
9092
const texture = device.createTexture(textureDescriptor);
9193

@@ -97,6 +99,10 @@ export class Texture_WebGPU
9799
);
98100
}
99101

102+
if(this.mipLevelCount > 1) {
103+
(this.device as Device_WebGPU).getMipmapGenerator().generateMipmap(texture);
104+
}
105+
100106
return [texture, width, height];
101107
}
102108

0 commit comments

Comments
 (0)