@@ -4,24 +4,76 @@ import volumeWGSL from './volume.wgsl';
4
4
import { quitIfWebGPUNotAvailable } from '../util' ;
5
5
6
6
const canvas = document . querySelector ( 'canvas' ) as HTMLCanvasElement ;
7
+ const status = document . getElementById ( 'status' ) as HTMLDivElement ;
7
8
8
9
const gui = new GUI ( ) ;
9
10
11
+ const brainImages = {
12
+ r8unorm : {
13
+ bytesPerBlock : 1 ,
14
+ blockLength : 1 ,
15
+ feature : undefined ,
16
+ dataPath :
17
+ '../../assets/img/volume/t1_icbm_normal_1mm_pn0_rf0_180x216x180_uint8_1x1.bin.gz' ,
18
+ } ,
19
+ 'bc4-r-unorm' : {
20
+ bytesPerBlock : 8 ,
21
+ blockLength : 4 ,
22
+ feature : 'texture-compression-bc-sliced-3d' ,
23
+ dataPath :
24
+ '../../assets/img/volume/t1_icbm_normal_1mm_pn0_rf0_180x216x180_bc4_4x4.bin.gz' ,
25
+ // Generated with texconv from https://github.com/microsoft/DirectXTex/releases
26
+ } ,
27
+ 'astc-12x12-unorm' : {
28
+ bytesPerBlock : 16 ,
29
+ blockLength : 12 ,
30
+ feature : 'texture-compression-astc-sliced-3d' ,
31
+ dataPath :
32
+ '../../assets/img/volume/t1_icbm_normal_1mm_pn0_rf0_180x216x180_astc_12x12.bin.gz' ,
33
+ // Generated with astcenc from https://github.com/ARM-software/astc-encoder/releases
34
+ } ,
35
+ } ;
36
+
10
37
// GUI parameters
11
- const params : { rotateCamera : boolean ; near : number ; far : number } = {
38
+ const params : {
39
+ rotateCamera : boolean ;
40
+ near : number ;
41
+ far : number ;
42
+ textureFormat : GPUTextureFormat ;
43
+ } = {
12
44
rotateCamera : true ,
13
- near : 2.0 ,
14
- far : 7.0 ,
45
+ near : 4.3 ,
46
+ far : 4.4 ,
47
+ textureFormat : 'r8unorm' ,
15
48
} ;
16
49
17
50
gui . add ( params , 'rotateCamera' , true ) ;
18
51
gui . add ( params , 'near' , 2.0 , 7.0 ) ;
19
52
gui . add ( params , 'far' , 2.0 , 7.0 ) ;
53
+ gui
54
+ . add ( params , 'textureFormat' , Object . keys ( brainImages ) )
55
+ . onChange ( async ( ) => {
56
+ await createVolumeTexture ( params . textureFormat ) ;
57
+ } ) ;
20
58
21
59
const adapter = await navigator . gpu ?. requestAdapter ( {
22
60
featureLevel : 'compatibility' ,
23
61
} ) ;
24
- const device = await adapter ?. requestDevice ( ) ;
62
+ const requiredFeatures = [ ] ;
63
+ if ( adapter ?. features . has ( 'texture-compression-bc-sliced-3d' ) ) {
64
+ requiredFeatures . push (
65
+ 'texture-compression-bc' ,
66
+ 'texture-compression-bc-sliced-3d'
67
+ ) ;
68
+ }
69
+ if ( adapter ?. features . has ( 'texture-compression-astc-sliced-3d' ) ) {
70
+ requiredFeatures . push (
71
+ 'texture-compression-astc' ,
72
+ 'texture-compression-astc-sliced-3d'
73
+ ) ;
74
+ }
75
+ const device = await adapter ?. requestDevice ( { requiredFeatures } ) ;
76
+
25
77
quitIfWebGPUNotAvailable ( adapter , device ) ;
26
78
const context = canvas . getContext ( 'webgpu' ) as GPUCanvasContext ;
27
79
@@ -77,34 +129,26 @@ const uniformBuffer = device.createBuffer({
77
129
usage : GPUBufferUsage . UNIFORM | GPUBufferUsage . COPY_DST ,
78
130
} ) ;
79
131
132
+ let volumeTexture : GPUTexture | null = null ;
133
+
80
134
// Fetch the image and upload it into a GPUTexture.
81
- let volumeTexture : GPUTexture ;
82
- {
135
+ async function createVolumeTexture ( format : GPUTextureFormat ) {
136
+ volumeTexture = null ;
137
+
138
+ const { blockLength, bytesPerBlock, dataPath, feature } = brainImages [ format ] ;
83
139
const width = 180 ;
84
140
const height = 216 ;
85
141
const depth = 180 ;
86
- const format : GPUTextureFormat = 'r8unorm' ;
87
- const blockLength = 1 ;
88
- const bytesPerBlock = 1 ;
89
142
const blocksWide = Math . ceil ( width / blockLength ) ;
90
143
const blocksHigh = Math . ceil ( height / blockLength ) ;
91
144
const bytesPerRow = blocksWide * bytesPerBlock ;
92
- const dataPath =
93
- '../../assets/img/volume/t1_icbm_normal_1mm_pn0_rf0_180x216x180_uint8_1x1.bin-gz' ;
94
145
95
- // Fetch the compressed data
96
- const response = await fetch ( dataPath ) ;
97
- const compressedArrayBuffer = await response . arrayBuffer ( ) ;
98
-
99
- // Decompress the data using DecompressionStream for gzip format
100
- const decompressionStream = new DecompressionStream ( 'gzip' ) ;
101
- const decompressedStream = new Response (
102
- compressedArrayBuffer
103
- ) . body . pipeThrough ( decompressionStream ) ;
104
- const decompressedArrayBuffer = await new Response (
105
- decompressedStream
106
- ) . arrayBuffer ( ) ;
107
- const byteArray = new Uint8Array ( decompressedArrayBuffer ) ;
146
+ if ( feature && ! device . features . has ( feature ) ) {
147
+ status . textContent = `${ feature } not supported` ;
148
+ return ;
149
+ } else {
150
+ status . textContent = '' ;
151
+ }
108
152
109
153
volumeTexture = device . createTexture ( {
110
154
dimension : '3d' ,
@@ -113,16 +157,19 @@ let volumeTexture: GPUTexture;
113
157
usage : GPUTextureUsage . TEXTURE_BINDING | GPUTextureUsage . COPY_DST ,
114
158
} ) ;
115
159
160
+ const response = await fetch ( dataPath ) ;
161
+ const buffer = await response . arrayBuffer ( ) ;
162
+
116
163
device . queue . writeTexture (
117
- {
118
- texture : volumeTexture ,
119
- } ,
120
- byteArray ,
164
+ { texture : volumeTexture } ,
165
+ buffer ,
121
166
{ bytesPerRow : bytesPerRow , rowsPerImage : blocksHigh } ,
122
167
[ width , height , depth ]
123
168
) ;
124
169
}
125
170
171
+ await createVolumeTexture ( params . textureFormat ) ;
172
+
126
173
// Create a sampler with linear filtering for smooth interpolation.
127
174
const sampler = device . createSampler ( {
128
175
magFilter : 'linear' ,
@@ -131,7 +178,7 @@ const sampler = device.createSampler({
131
178
maxAnisotropy : 16 ,
132
179
} ) ;
133
180
134
- const uniformBindGroup = device . createBindGroup ( {
181
+ const bindGroupDescriptor : GPUBindGroupDescriptor = {
135
182
layout : pipeline . getBindGroupLayout ( 0 ) ,
136
183
entries : [
137
184
{
@@ -146,17 +193,17 @@ const uniformBindGroup = device.createBindGroup({
146
193
} ,
147
194
{
148
195
binding : 2 ,
149
- resource : volumeTexture . createView ( ) ,
196
+ resource : undefined , // Assigned later
150
197
} ,
151
198
] ,
152
- } ) ;
199
+ } ;
153
200
154
201
const renderPassDescriptor : GPURenderPassDescriptor = {
155
202
colorAttachments : [
156
203
{
157
204
view : undefined , // Assigned later
158
205
159
- clearValue : [ 0.5 , 0.5 , 0.5 , 1.0 ] ,
206
+ clearValue : [ 0 , 0 , 0 , 1.0 ] ,
160
207
loadOp : 'clear' ,
161
208
storeOp : 'discard' ,
162
209
} ,
@@ -207,9 +254,13 @@ function frame() {
207
254
208
255
const commandEncoder = device . createCommandEncoder ( ) ;
209
256
const passEncoder = commandEncoder . beginRenderPass ( renderPassDescriptor ) ;
210
- passEncoder . setPipeline ( pipeline ) ;
211
- passEncoder . setBindGroup ( 0 , uniformBindGroup ) ;
212
- passEncoder . draw ( 3 ) ;
257
+ if ( volumeTexture ) {
258
+ bindGroupDescriptor . entries [ 2 ] . resource = volumeTexture . createView ( ) ;
259
+ const uniformBindGroup = device . createBindGroup ( bindGroupDescriptor ) ;
260
+ passEncoder . setPipeline ( pipeline ) ;
261
+ passEncoder . setBindGroup ( 0 , uniformBindGroup ) ;
262
+ passEncoder . draw ( 3 ) ;
263
+ }
213
264
passEncoder . end ( ) ;
214
265
device . queue . submit ( [ commandEncoder . finish ( ) ] ) ;
215
266
0 commit comments