@@ -4,24 +4,76 @@ import volumeWGSL from './volume.wgsl';
44import { quitIfWebGPUNotAvailable } from '../util' ;
55
66const canvas = document . querySelector ( 'canvas' ) as HTMLCanvasElement ;
7+ const status = document . getElementById ( 'status' ) as HTMLDivElement ;
78
89const gui = new GUI ( ) ;
910
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+
1037// 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+ } = {
1244 rotateCamera : true ,
13- near : 2.0 ,
14- far : 7.0 ,
45+ near : 4.3 ,
46+ far : 4.4 ,
47+ textureFormat : 'r8unorm' ,
1548} ;
1649
1750gui . add ( params , 'rotateCamera' , true ) ;
1851gui . add ( params , 'near' , 2.0 , 7.0 ) ;
1952gui . 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+ } ) ;
2058
2159const adapter = await navigator . gpu ?. requestAdapter ( {
2260 featureLevel : 'compatibility' ,
2361} ) ;
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+
2577quitIfWebGPUNotAvailable ( adapter , device ) ;
2678const context = canvas . getContext ( 'webgpu' ) as GPUCanvasContext ;
2779
@@ -77,34 +129,26 @@ const uniformBuffer = device.createBuffer({
77129 usage : GPUBufferUsage . UNIFORM | GPUBufferUsage . COPY_DST ,
78130} ) ;
79131
132+ let volumeTexture : GPUTexture | null = null ;
133+
80134// 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 ] ;
83139 const width = 180 ;
84140 const height = 216 ;
85141 const depth = 180 ;
86- const format : GPUTextureFormat = 'r8unorm' ;
87- const blockLength = 1 ;
88- const bytesPerBlock = 1 ;
89142 const blocksWide = Math . ceil ( width / blockLength ) ;
90143 const blocksHigh = Math . ceil ( height / blockLength ) ;
91144 const bytesPerRow = blocksWide * bytesPerBlock ;
92- const dataPath =
93- '../../assets/img/volume/t1_icbm_normal_1mm_pn0_rf0_180x216x180_uint8_1x1.bin-gz' ;
94145
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+ }
108152
109153 volumeTexture = device . createTexture ( {
110154 dimension : '3d' ,
@@ -113,16 +157,19 @@ let volumeTexture: GPUTexture;
113157 usage : GPUTextureUsage . TEXTURE_BINDING | GPUTextureUsage . COPY_DST ,
114158 } ) ;
115159
160+ const response = await fetch ( dataPath ) ;
161+ const buffer = await response . arrayBuffer ( ) ;
162+
116163 device . queue . writeTexture (
117- {
118- texture : volumeTexture ,
119- } ,
120- byteArray ,
164+ { texture : volumeTexture } ,
165+ buffer ,
121166 { bytesPerRow : bytesPerRow , rowsPerImage : blocksHigh } ,
122167 [ width , height , depth ]
123168 ) ;
124169}
125170
171+ await createVolumeTexture ( params . textureFormat ) ;
172+
126173// Create a sampler with linear filtering for smooth interpolation.
127174const sampler = device . createSampler ( {
128175 magFilter : 'linear' ,
@@ -131,7 +178,7 @@ const sampler = device.createSampler({
131178 maxAnisotropy : 16 ,
132179} ) ;
133180
134- const uniformBindGroup = device . createBindGroup ( {
181+ const bindGroupDescriptor : GPUBindGroupDescriptor = {
135182 layout : pipeline . getBindGroupLayout ( 0 ) ,
136183 entries : [
137184 {
@@ -146,17 +193,17 @@ const uniformBindGroup = device.createBindGroup({
146193 } ,
147194 {
148195 binding : 2 ,
149- resource : volumeTexture . createView ( ) ,
196+ resource : undefined , // Assigned later
150197 } ,
151198 ] ,
152- } ) ;
199+ } ;
153200
154201const renderPassDescriptor : GPURenderPassDescriptor = {
155202 colorAttachments : [
156203 {
157204 view : undefined , // Assigned later
158205
159- clearValue : [ 0.5 , 0.5 , 0.5 , 1.0 ] ,
206+ clearValue : [ 0 , 0 , 0 , 1.0 ] ,
160207 loadOp : 'clear' ,
161208 storeOp : 'discard' ,
162209 } ,
@@ -207,9 +254,13 @@ function frame() {
207254
208255 const commandEncoder = device . createCommandEncoder ( ) ;
209256 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+ }
213264 passEncoder . end ( ) ;
214265 device . queue . submit ( [ commandEncoder . finish ( ) ] ) ;
215266
0 commit comments