11// File: js/routes/web-tutorial.js
2- // Draw a 25 % cyan square; header & footer remain visible .
3- // Works by relying 100 % on flex‐ layout ( no manual pixel math).
2+ // Draws a 25% square that can be toggled between filled and wireframe modes .
3+ // The site’s header and footer remain visible (pure flex layout, no manual pixel math).
44
5- import { loadShader } from '../utils/shaderLoader.js' ; // Helper to load WGSL from /wgsl
5+ import { loadShader } from '../utils/shaderLoader.js' ;
66
77export default async function WebGPUFlatSquare ( host ) {
88 /* ───────────── 1 · LAYOUT (pure flex, no calculations) ───────────── */
99 host . replaceChildren ( ) ; // Remove any existing children in the host container
1010 host . style . cssText =
1111 'flex:1 0 auto; position:relative; overflow:hidden; background:#000; margin:0; padding:0;' ;
12- // “host” now grows to fill the space between header & footer, and can contain an absolutely positioned canvas
12+ // “host” now grows to fill the space between header & footer, and can contain absolutely‐ positioned content.
1313
14+ // ─── Insert a “Wireframe” checkbox at the top‐left corner of the host ───
15+ const uiContainer = document . createElement ( 'div' ) ;
16+ uiContainer . style . cssText =
17+ 'position:absolute; top:10px; left:10px; z-index:10; color:white; font-family:sans-serif;' ;
18+ uiContainer . innerHTML = `
19+ <label style="font-size:1rem; user-select:none;">
20+ <input type="checkbox" id="wireframeToggle" style="transform:scale(1.2); margin-right:4px;" />
21+ Wireframe
22+ </label>
23+ ` ;
24+ host . appendChild ( uiContainer ) ;
25+
26+ const wireCheckbox = uiContainer . querySelector ( '#wireframeToggle' ) ;
27+
28+ /* ───────────── 2 · CANVAS CREATION & WEBGPU INIT ───────────── */
1429 const canvas = Object . assign ( document . createElement ( 'canvas' ) , {
1530 style : 'position:absolute; width:100%; height:100%; cursor:default;' ,
1631 } ) ;
17- host . appendChild ( canvas ) ; // Insert the canvas into “host”
18-
19- /* ───────────── 2 · WEBGPU INIT ───────────── */
20- if ( ! navigator . gpu ) return alert ( 'WebGPU not supported' ) ; // Abort if no WebGPU
21- const adapter = await navigator . gpu . requestAdapter ( ) ; // Request a GPU adapter
22- if ( ! adapter ) return alert ( 'No GPU adapter available' ) ; // Abort if none
23- const device = await adapter . requestDevice ( ) ; // Acquire a logical GPU device
24- const ctx = canvas . getContext ( 'webgpu' ) ; // Get WebGPU rendering context
25- const format = navigator . gpu . getPreferredCanvasFormat ( ) ; // Recommended swapchain format
26-
27- const resize = ( ) => { // Resize handler
28- const dpr = devicePixelRatio ; // Device‐pixel ratio
29- canvas . width = host . clientWidth * dpr ; // Physical pixel width
30- canvas . height = host . clientHeight * dpr ; // Physical pixel height
32+ host . appendChild ( canvas ) ;
33+
34+ if ( ! navigator . gpu ) {
35+ return alert ( 'WebGPU not supported' ) ;
36+ }
37+ const adapter = await navigator . gpu . requestAdapter ( ) ;
38+ if ( ! adapter ) {
39+ return alert ( 'No GPU adapter available' ) ;
40+ }
41+ const device = await adapter . requestDevice ( ) ;
42+ const ctx = canvas . getContext ( 'webgpu' ) ;
43+ const format = navigator . gpu . getPreferredCanvasFormat ( ) ;
44+
45+ const resize = ( ) => {
46+ const dpr = devicePixelRatio ;
47+ canvas . width = host . clientWidth * dpr ;
48+ canvas . height = host . clientHeight * dpr ;
3149 ctx . configure ( {
3250 device,
3351 format,
3452 alphaMode : 'premultiplied' ,
3553 size : [ canvas . width , canvas . height ] ,
36- } ) ; // Reconfigure the swapchain
54+ } ) ;
3755 } ;
38- resize ( ) ; // Initial sizing
39- addEventListener ( 'resize' , resize ) ; // Update on window resize
56+ resize ( ) ;
57+ window . addEventListener ( 'resize' , resize ) ;
4058
41- /* ───────────── 3 · SHADER & GEOMETRY ───────────── */
42- const wgsl = await loadShader ( 'square-flat.wgsl' ) ; // Load WGSL from /wgsl/square-flat.wgsl
43- const module = device . createShaderModule ( { code : wgsl } ) ; // Create GPUShaderModule
59+ /* ───────────── 3 · SHADER & GEOMETRY SETUP ───────────── */
60+ // Load WGSL source from /wgsl/square-flat.wgsl
61+ const wgsl = await loadShader ( 'square-flat.wgsl' ) ;
62+ const module = device . createShaderModule ( { code : wgsl } ) ;
4463
45- const s = 0.5 ; // Half‐size in NDC space (±0.5 covers 50 % of viewport)
46- // Vertex data: [x, y, z, r, g, b] for 4 corners of a square
64+ // In NDC, ±0.5 covers 50% of the viewport in both X and Y.
65+ const s = 0.5 ;
66+
67+ // Vertex layout: [x, y, z, r, g, b]
4768 const vertices = new Float32Array ( [
48- - s , - s , 0 , 0 , 0 , 1 , // bottom‐ left (cyan )
49- s , - s , 0 , 0 , 1 , 0 , // bottom‐ right (cyan )
50- s , s , 0 , 1 , 0 , 0 , // top‐ right (cyan )
51- - s , s , 0 , 1 , 1 , 1 , // top‐ left (cyan )
69+ - s , - s , 0 , 0 , 0 , 1 , // bottom- left (blue )
70+ s , - s , 0 , 0 , 1 , 0 , // bottom- right (green )
71+ s , s , 0 , 1 , 0 , 0 , // top- right (red )
72+ - s , s , 0 , 1 , 1 , 1 , // top- left (white )
5273 ] ) ;
53- // Indices to form two triangles (0→1→2 and 0→2→3) from those 4 vertices
54- const indices = new Uint16Array ( [ 0 , 1 , 2 , 0 , 2 , 3 , ] ) ;
5574
56- // Create and upload vertex buffer
75+ // Filled‐mode indices (two triangles: 0→1→2 and 0→2→3)
76+ const triIndices = new Uint16Array ( [
77+ 0 , 1 , 2 ,
78+ 0 , 2 , 3 ,
79+ ] ) ;
80+
81+ // Wireframe indices: draw all four outer edges + the diagonal (0→2)
82+ const lineIndices = new Uint16Array ( [
83+ 0 , 1 , // bottom edge
84+ 1 , 2 , // right edge
85+ 2 , 3 , // top edge
86+ 3 , 0 , // left edge
87+ 0 , 2 , // diagonal (connect bottom-left ↔ top-right)
88+ ] ) ;
89+
90+ // Create and upload the shared vertex buffer
5791 const vb = device . createBuffer ( {
5892 size : vertices . byteLength ,
5993 usage : GPUBufferUsage . VERTEX | GPUBufferUsage . COPY_DST ,
6094 } ) ;
6195 device . queue . writeBuffer ( vb , 0 , vertices ) ;
6296
63- // Create and upload index buffer
64- const ib = device . createBuffer ( {
65- size : indices . byteLength ,
97+ // Create and upload the triangle‐list index buffer
98+ const ibTriangles = device . createBuffer ( {
99+ size : triIndices . byteLength ,
100+ usage : GPUBufferUsage . INDEX | GPUBufferUsage . COPY_DST ,
101+ } ) ;
102+ device . queue . writeBuffer ( ibTriangles , 0 , triIndices ) ;
103+
104+ // Create and upload the line‐list index buffer
105+ const ibLines = device . createBuffer ( {
106+ size : lineIndices . byteLength ,
66107 usage : GPUBufferUsage . INDEX | GPUBufferUsage . COPY_DST ,
67108 } ) ;
68- device . queue . writeBuffer ( ib , 0 , indices ) ;
109+ device . queue . writeBuffer ( ibLines , 0 , lineIndices ) ;
69110
70- // Create render pipeline
71- const pipeline = device . createRenderPipeline ( {
111+ /* ───────────── 4 · PIPELINE CREATION ───────────── */
112+ // Pipeline for filled triangles
113+ const pipelineFilled = device . createRenderPipeline ( {
72114 layout : 'auto' ,
73115 vertex : {
74116 module,
75117 entryPoint : 'vs_main' ,
76- buffers : [ {
77- arrayStride : 24 , // 3 floats for position + 3 floats for color = 6×4 bytes = 24 bytes
78- attributes : [
79- { shaderLocation : 0 , offset : 0 , format : 'float32x3' } , // position
80- { shaderLocation : 1 , offset : 12 , format : 'float32x3' } , // color
81- ] ,
82- } ] ,
118+ buffers : [
119+ {
120+ arrayStride : 24 , // 6 floats per vertex (3 position + 3 color) = 24 bytes
121+ attributes : [
122+ { shaderLocation : 0 , offset : 0 , format : 'float32x3' } , // position
123+ { shaderLocation : 1 , offset : 12 , format : 'float32x3' } , // color
124+ ] ,
125+ } ,
126+ ] ,
83127 } ,
84128 fragment : {
85129 module,
@@ -92,24 +136,65 @@ export default async function WebGPUFlatSquare(host) {
92136 } ,
93137 } ) ;
94138
95- /* ───────────── 4 · RENDER LOOP ───────────── */
139+ // Pipeline for wireframe (line-list)
140+ const pipelineWire = device . createRenderPipeline ( {
141+ layout : 'auto' ,
142+ vertex : {
143+ module,
144+ entryPoint : 'vs_main' ,
145+ buffers : [
146+ {
147+ arrayStride : 24 ,
148+ attributes : [
149+ { shaderLocation : 0 , offset : 0 , format : 'float32x3' } ,
150+ { shaderLocation : 1 , offset : 12 , format : 'float32x3' } ,
151+ ] ,
152+ } ,
153+ ] ,
154+ } ,
155+ fragment : {
156+ module,
157+ entryPoint : 'fs_main' ,
158+ targets : [ { format } ] ,
159+ } ,
160+ primitive : {
161+ topology : 'line-list' ,
162+ cullMode : 'none' ,
163+ } ,
164+ } ) ;
165+
166+ /* ───────────── 5 · RENDER LOOP ───────────── */
96167 function frame ( ) {
97- const enc = device . createCommandEncoder ( ) ; // Begin command encoding
98- const pass = enc . beginRenderPass ( { // Start a render pass
168+ const encoder = device . createCommandEncoder ( ) ;
169+ const pass = encoder . beginRenderPass ( {
99170 colorAttachments : [ {
100- view : ctx . getCurrentTexture ( ) . createView ( ) , // Current swapchain texture
171+ view : ctx . getCurrentTexture ( ) . createView ( ) ,
101172 loadOp : 'clear' ,
102- clearValue : { r : 0 , g : 0 , b : 0 , a : 1 } , // Clear to opaque black
173+ clearValue : { r : 0 , g : 0 , b : 0 , a : 1 } ,
103174 storeOp : 'store' ,
104175 } ] ,
105176 } ) ;
106- pass . setPipeline ( pipeline ) ; // Bind pipeline
107- pass . setVertexBuffer ( 0 , vb ) ; // Bind the vertex buffer
108- pass . setIndexBuffer ( ib , 'uint16' ) ; // Bind the index buffer
109- pass . drawIndexed ( 6 ) ; // Draw 6 indices (2 triangles)
110- pass . end ( ) ; // End the render pass
111- device . queue . submit ( [ enc . finish ( ) ] ) ; // Submit to GPU queue
112- requestAnimationFrame ( frame ) ; // Next frame
177+
178+ if ( wireCheckbox . checked ) {
179+ // Wireframe pipeline: bind line index buffer
180+ pass . setPipeline ( pipelineWire ) ;
181+ pass . setVertexBuffer ( 0 , vb ) ;
182+ pass . setIndexBuffer ( ibLines , 'uint16' ) ;
183+ // Draw 5 line segments → 10 indices total
184+ pass . drawIndexed ( lineIndices . length , 1 , 0 , 0 , 0 ) ;
185+ } else {
186+ // Filled pipeline: bind triangle index buffer
187+ pass . setPipeline ( pipelineFilled ) ;
188+ pass . setVertexBuffer ( 0 , vb ) ;
189+ pass . setIndexBuffer ( ibTriangles , 'uint16' ) ;
190+ // Draw 2 triangles → 6 indices total
191+ pass . drawIndexed ( triIndices . length , 1 , 0 , 0 , 0 ) ;
192+ }
193+
194+ pass . end ( ) ;
195+ device . queue . submit ( [ encoder . finish ( ) ] ) ;
196+ requestAnimationFrame ( frame ) ;
113197 }
114- requestAnimationFrame ( frame ) ; // Kick off rendering
198+
199+ requestAnimationFrame ( frame ) ;
115200}
0 commit comments