├── LICENSE ├── README.md ├── hello-bind-groups.html ├── hello-blend.html ├── hello-cube.html ├── hello-green.html ├── hello-triangle.html ├── hello-validation.html └── webgpu.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jeff Gilbert 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webgpu-js 2 | WebGPU on WebGL 2 3 | -------------------------------------------------------------------------------- /hello-bind-groups.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /hello-blend.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /hello-cube.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 280 | 281 | 282 | -------------------------------------------------------------------------------- /hello-green.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /hello-triangle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /hello-validation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /webgpu.js: -------------------------------------------------------------------------------- 1 | if (window.GPUBufferUsage === undefined) { 2 | GPUBufferUsage = { 3 | NONE : 0x0000, 4 | MAP_READ : 0x0001, 5 | MAP_WRITE: 0x0002, 6 | COPY_SRC : 0x0004, 7 | COPY_DST : 0x0008, 8 | INDEX : 0x0010, 9 | VERTEX : 0x0020, 10 | UNIFORM : 0x0040, 11 | STORAGE : 0x0080, 12 | }; 13 | } 14 | if (window.GPUTextureUsage === undefined) { 15 | GPUTextureUsage = { 16 | NONE : 0x00, 17 | COPY_SRC : 0x01, 18 | COPY_DST : 0x02, 19 | SAMPLED : 0x04, 20 | STORAGE : 0x08, 21 | OUTPUT_ATTACHMENT : 0x10, 22 | }; 23 | } 24 | if (window.GPUShaderStageBit === undefined) { 25 | GPUShaderStageBit = { 26 | NONE : 0x0, 27 | VERTEX : 0x1, 28 | FRAGMENT : 0x2, 29 | COMPUTE : 0x4, 30 | }; 31 | } 32 | if (window.GPUColorWriteBits === undefined) { 33 | GPUColorWriteBits = { 34 | NONE : 0x0, 35 | RED : 0x1, 36 | GREEN : 0x2, 37 | BLUE : 0x4, 38 | ALPHA : 0x8, 39 | ALL : 0xF, 40 | }; 41 | } 42 | 43 | navigator.gpu_js = (() => { 44 | const GL = WebGL2RenderingContext; 45 | const ORIG_GET_CONTEXT = HTMLCanvasElement.prototype.getContext; 46 | const SYNC_ERROR_DISPATCH = true; 47 | 48 | function is_subset(a, b) { 49 | for (const k in b) { 50 | let v_a = a[k]; 51 | let v_b = b[k]; 52 | if (typeof v_a === 'boolean') { 53 | v_a = v_a ? 1 : 0; 54 | v_b = v_b ? 1 : 0; 55 | } 56 | if (v_a > v_b) 57 | return false; 58 | } 59 | return true; 60 | } 61 | 62 | function lose_gl(gl) { 63 | const e = gl.getExtension('WEBGL_lose_context'); 64 | if (e) { 65 | e.loseContext(); 66 | } 67 | } 68 | 69 | class GlInfo { 70 | constructor(gl) { 71 | let renderer_pname = gl.RENDERER; 72 | const ext = gl.getExtension('WEBGL_debug_renderer_info'); 73 | if (ext) { 74 | renderer_pname = ext.UNMASKED_RENDERER_WEBGL; 75 | } 76 | 77 | this.name = 'WebGPU.js on ' + gl.getParameter(renderer_pname); 78 | 79 | this.extensions = { 80 | anisotropicFiltering: false, 81 | }; 82 | const gl_exts = gl.getSupportedExtensions(); 83 | if (gl_exts.includes('EXT_texture_filter_anisotropic')) { 84 | this.extensions.anisotropicFiltering = true; 85 | } 86 | 87 | this.limits = { 88 | maxBindGroups: 4, 89 | }; 90 | } 91 | } 92 | 93 | // - 94 | 95 | const setZeroTimeout = (function() { 96 | // See https://dbaron.org/log/20100309-faster-timeouts 97 | 98 | var timeouts = []; 99 | var messageName = "zero-timeout-message"; 100 | 101 | // Like setTimeout, but only takes a function argument. There's 102 | // no time argument (always zero) and no arguments (you have to 103 | // use a closure). 104 | function setZeroTimeout(fn) { 105 | timeouts.push(fn); 106 | window.postMessage(messageName, "*"); 107 | } 108 | 109 | function handleMessage(event) { 110 | if (event.source == window && event.data == messageName) { 111 | event.stopPropagation(); 112 | if (timeouts.length > 0) { 113 | var fn = timeouts.shift(); 114 | fn(); 115 | } 116 | } 117 | } 118 | 119 | window.addEventListener("message", handleMessage, true); 120 | 121 | return setZeroTimeout; 122 | })(); 123 | 124 | // - 125 | 126 | function console_error_passthrough(e) { 127 | console.error(e); 128 | return e; 129 | } 130 | 131 | function ASSERT(val, info) { 132 | if (!val) throw console_error_passthrough(new Error('ASSERT: ' + info)); 133 | } 134 | 135 | // - 136 | 137 | const IS_GPU_ERROR = {}; 138 | 139 | if (window.GPUOutOfMemoryError === undefined) { 140 | window.GPUOutOfMemoryError = class GPUOutOfMemoryError extends Error { 141 | constructor() { 142 | super(''); 143 | this.name = 'GPUOutOfMemoryError'; 144 | } 145 | }; 146 | IS_GPU_ERROR['GPUOutOfMemoryError'] = true; 147 | } 148 | if (window.GPUValidationError === undefined) { 149 | window.GPUValidationError = class GPUValidationError extends Error { 150 | constructor(message) { 151 | ASSERT(message, '`GPUValidationError.constructor` requires `message`.'); 152 | super(message); 153 | this.name = 'GPUValidationError'; 154 | } 155 | }; 156 | IS_GPU_ERROR['GPUValidationError'] = true; 157 | } 158 | 159 | // - 160 | 161 | function REQUIRE_NON_NULL(desc, name) { 162 | if (!desc) throw console_error_passthrough(new TypeError(name + ' shall not be null.')); 163 | } 164 | 165 | function REQUIRE(dict, type, key, val_type, fn_map) { 166 | const name = '`' + type + '.' + key + '`'; 167 | if (dict[key] === undefined) throw console_error_passthrough(new ReferenceError(name + ' required.')); 168 | if (val_type) { 169 | if (!(dict[key] instanceof val_type)) { 170 | throw console_error_passthrough(new TypeError(name + ' must be `' + val_type.name + '`.')); 171 | } 172 | } 173 | if (fn_map) { 174 | dict[key] = fn_map(dict[key]); 175 | } 176 | } 177 | 178 | function REQUIRE_SEQ(dict, type, key, val_type, fn_map) { 179 | const name = '`' + type + '.' + key + '`'; 180 | if (dict[key] === undefined) throw console_error_passthrough(new ReferenceError(name + ' required.')); 181 | if (dict[key].length === undefined) throw console_error_passthrough(new TypeError(name + ' must be a sequence.')); 182 | const seq = dict[key]; 183 | for (const i in seq) { 184 | const name_i = type + '.' + key + '[' + i + ']'; 185 | if (val_type) { 186 | if (!(seq[i] instanceof val_type)) { 187 | throw new console_error_passthrough(TypeError(name + ' must be `' + val_type.name + '`.')); 188 | } 189 | } 190 | if (fn_map) { 191 | seq[i] = fn_map(seq[i]); 192 | } 193 | } 194 | } 195 | 196 | function REQUIRE_VAL(dict, type, key, val) { 197 | const name = '`' + type + '.' + key + '`'; 198 | if (dict[key] !== val) throw console_error_passthrough(new Error(name + ' must be ' + val)); 199 | } 200 | 201 | function VALIDATE(ok, message) { 202 | if (!ok) throw new GPUValidationError(message); 203 | } 204 | 205 | // - 206 | 207 | function make_GPUColor(dict) { 208 | if (dict.length) { 209 | if (dict.length != 4) throw new TypeError('`GPUColor.length` must be 4.'); 210 | dict = { 211 | r: dict[0], 212 | g: dict[1], 213 | b: dict[2], 214 | a: dict[3], 215 | }; 216 | } else { 217 | REQUIRE(dict, 'GPUColor', 'r'); 218 | REQUIRE(dict, 'GPUColor', 'g'); 219 | REQUIRE(dict, 'GPUColor', 'b'); 220 | REQUIRE(dict, 'GPUColor', 'a'); 221 | dict = Object.assign({}, dict); 222 | } 223 | return dict; 224 | } 225 | 226 | function make_GPUOrigin2D(dict) { 227 | dict = Object.assign({ 228 | x: dict[0] || 0, 229 | y: dict[1] || 0, 230 | }, dict); 231 | Object.defineProperties(dict, { 232 | width: { 233 | get: () => { throw new ReferenceError('No `GPUOrigin2D.width`. Did you mean `x`?'); }, 234 | }, 235 | height: { 236 | get: () => { throw new ReferenceError('No `GPUOrigin2D.height`. Did you mean `y`?'); }, 237 | }, 238 | depth: { 239 | get: () => { throw new ReferenceError('No `GPUOrigin2D.depth`.'); }, 240 | }, 241 | }); 242 | return dict; 243 | } 244 | 245 | function make_GPUOrigin3D(dict) { 246 | dict = Object.assign({ 247 | x: dict[0] || 0, 248 | y: dict[1] || 0, 249 | z: dict[2] || 0, 250 | }, dict); 251 | Object.defineProperties(dict, { 252 | width: { 253 | get: () => { throw new ReferenceError('No `GPUOrigin3D.width`. Did you mean `x`?'); }, 254 | }, 255 | height: { 256 | get: () => { throw new ReferenceError('No `GPUOrigin3D.height`. Did you mean `y`?'); }, 257 | }, 258 | depth: { 259 | get: () => { throw new ReferenceError('No `GPUOrigin3D.depth`. Did you mean `z`?'); }, 260 | }, 261 | }); 262 | return dict; 263 | } 264 | 265 | function make_GPUExtent3D(dict) { 266 | if (dict.length) { 267 | if (dict.length != 3) throw new TypeError('`GPUExtent3D.length` must be 3.'); 268 | dict = { 269 | width: dict[0], 270 | height: dict[1], 271 | depth: dict[2], 272 | }; 273 | } else { 274 | dict = Object.assign({}, dict); 275 | REQUIRE(dict, 'GPUExtent3D', 'width'); 276 | REQUIRE(dict, 'GPUExtent3D', 'height'); 277 | REQUIRE(dict, 'GPUExtent3D', 'depth'); 278 | } 279 | Object.defineProperties(dict, { 280 | x: { 281 | get: () => { throw new ReferenceError('No `GPUExtent3D.x`. Did you mean `width`?'); }, 282 | }, 283 | y: { 284 | get: () => { throw new ReferenceError('No `GPUExtent3D.y`. Did you mean `height`?'); }, 285 | }, 286 | z: { 287 | get: () => { throw new ReferenceError('No `GPUExtent3D.z`. Did you mean `depth`?'); }, 288 | }, 289 | }); 290 | return dict; 291 | } 292 | 293 | // ----------------- 294 | 295 | /* 296 | STREAM_: Specified once, used at most a few times. 297 | STATIC_: Specified once, used many times. 298 | DYNAMIC_: Respecified repeatedly, used many times. 299 | _DRAW: Specified by app, used as source for GL. 300 | _READ: Specified by reading from GL, queried by app. 301 | _COPY: Specified by reading from GL, used as source for GL. 302 | */ 303 | function infer_gl_buf_usage(gpu_usage_bits, will_start_mapped) { 304 | // Cheeky string-manip keys! 305 | if (gpu_usage_bits & GPUBufferUsage.MAP_WRITE) 306 | return GL.DYNAMIC_DRAW; 307 | 308 | if (gpu_usage_bits & GPUBufferUsage.MAP_READ) { 309 | if (gpu_usage_bits & GPUBufferUsage.STORAGE) 310 | return GL.DYNAMIC_READ; 311 | return GL.STREAM_READ; 312 | } 313 | 314 | const for_draw_bits = (GPUBufferUsage.INDEX | 315 | GPUBufferUsage.VERTEX | 316 | GPUBufferUsage.UNIFORM | 317 | GPUBufferUsage.INDIRECT); 318 | const is_for_draw = (gpu_usage_bits & for_draw_bits); 319 | if (will_start_mapped) { 320 | if (is_for_draw) 321 | return GL.STATIC_DRAW; 322 | return GL.STREAM_DRAW; 323 | } 324 | 325 | if (gpu_usage_bits & GPUBufferUsage.STORAGE) 326 | return GL.DYNAMIC_COPY; 327 | if (is_for_draw) 328 | return GL.STATIC_COPY; 329 | return GL.STREAM_COPY; 330 | } 331 | 332 | class GPUBuffer_JS { 333 | constructor(device, desc, will_start_mapped) { 334 | this.device = device; 335 | if (!desc) 336 | return; 337 | desc = make_GPUBufferDescriptor(desc); 338 | 339 | this.desc = desc; 340 | this._gl_usage = infer_gl_buf_usage(desc.usage, will_start_mapped); 341 | 342 | if (desc.usage & (GPUBufferUsage.MAP_READ | GPUBufferUsage.MAP_WRITE)) { 343 | this._map_buf = new Uint8Array(desc.size); 344 | } 345 | 346 | this._gl_target = GL.ARRAY_BUFFER; 347 | if (desc.usage & GPUBufferUsage.INDEX) { 348 | ASSERT(!(desc.usage & (GPUBufferUsage.VERTEX | GPUBufferUsage.UNIFORM)), 349 | 'Not supported: GPUBufferUsage.INDEX combined with VERTEX and UNIFORM'); 350 | this._gl_target = GL.ELEMENT_ARRAY_BUFFER; 351 | } 352 | 353 | if (!will_start_mapped) { 354 | const gl = this.device.gl; 355 | this._gl_obj = gl.createBuffer(); 356 | gl.bindBuffer(this._gl_target, this._gl_obj); 357 | 358 | let err = gl.getError(); 359 | ASSERT(!err, 'Unexpected WebGL error: 0x' + err.toString(16)); 360 | gl.bufferData(this._gl_target, desc.size, this._gl_usage); 361 | err = gl.getError(); 362 | if (err == GL.OUT_OF_MEMORY) { 363 | while (gl.getError()) {} 364 | this.desc = null; 365 | throw new GPUOutOfMemoryError(); 366 | } 367 | ASSERT(!err, 'Unexpected WebGL error: 0x' + err.toString(16)); 368 | 369 | gl.bindBuffer(this._gl_target, null); 370 | } 371 | } 372 | 373 | _map_write() { 374 | if (this._map_buf) { 375 | this._write_map = this._map_buf; 376 | } 377 | if (!this._write_map) { 378 | // Create temporary for initial upload. 379 | this._write_map = new Uint8Array(this.desc.size); 380 | } 381 | this._map_ready = true; 382 | return this._write_map.buffer; 383 | } 384 | 385 | _mapped() { 386 | return this._read_map || this._write_map; 387 | } 388 | 389 | mapWriteAsync() { 390 | try { 391 | VALIDATE(!this.device._is_lost, 'Device is lost.'); 392 | VALIDATE(this.desc, 'Invalid object.'); 393 | VALIDATE(!this._mapped(), 'Cannot be mapped.'); 394 | VALIDATE(this.desc.usage & GPUBufferUsage.MAP_WRITE, 'Missing GPUBufferUsage.MAP_WRITE.'); 395 | 396 | const ret = this._map_write(); 397 | return new Promise((good, bad) => { 398 | ASSERT(this._mapped() && this._map_ready, '(should be ready)'); 399 | good(ret); 400 | }); 401 | } catch (e) { this.device._catch(e); } 402 | } 403 | 404 | mapReadAsync() { 405 | try { 406 | VALIDATE(!this.device._is_lost, 'Device is lost.'); 407 | VALIDATE(this.desc, 'Invalid object.'); 408 | VALIDATE(!this._mapped(), 'Cannot be mapped.'); 409 | VALIDATE(this.desc.usage & GPUBufferUsage.MAP_READ, 'Missing GPUBufferUsage.MAP_READ.'); 410 | this._read_map = this._map_buf; 411 | 412 | let p_good; // :p 413 | const p = new Promise((good, bad) => { 414 | p_good = good; 415 | }); 416 | 417 | this.device._add_fenced_todo(() => { 418 | const gl = this.device.gl; 419 | gl.bindBuffer(this._gl_target, this._gl_obj); 420 | gl.getBufferSubData(this._gl_target, 0, this._read_map); 421 | gl.bindBuffer(this._gl_target, null); 422 | 423 | this._map_ready = true; 424 | ASSERT(this._mapped() && this._map_ready, '(should be ready)'); 425 | p_good(this._read_map.buffer); 426 | }); 427 | return p; 428 | } catch (e) { this.device._catch(e); } 429 | } 430 | 431 | unmap() { 432 | try { 433 | VALIDATE(!this.device._is_lost, 'Device is lost.'); 434 | VALIDATE(this.desc, 'Invalid object.'); 435 | VALIDATE(this._map_ready, 'unmap() target must be presently mapped.'); 436 | 437 | if (this._read_map) { 438 | this._read_map = null; 439 | return; 440 | } 441 | 442 | const gl = this.device.gl; 443 | if (!this._gl_obj) { 444 | this._gl_obj = gl.createBuffer(); 445 | gl.bindBuffer(this._gl_target, this._gl_obj); 446 | 447 | let err = gl.getError(); 448 | ASSERT(!err, 'Unexpected WebGL error: 0x' + err.toString(16)); 449 | gl.bufferData(this._gl_target, this._write_map, this._gl_usage); 450 | if (gl.getError() == GL.OUT_OF_MEMORY) 451 | throw new GPUOutOfMemoryError(); 452 | 453 | gl.bindBuffer(this._gl_target, null); 454 | } else { 455 | gl.bindBuffer(this._gl_target, this._gl_obj); 456 | gl.bufferSubData(this._gl_target, 0, this._write_map); 457 | gl.bindBuffer(this._gl_target, null); 458 | } 459 | this._write_map = null; 460 | } catch (e) { this.device._catch(e); } 461 | } 462 | 463 | destroy() { 464 | try { 465 | VALIDATE(!this.device._is_lost, 'Device is lost.'); 466 | VALIDATE(this.desc, 'Invalid object.'); 467 | VALIDATE(!this._mapped(), 'Cannot be mapped.'); 468 | if (this._gl_obj) { 469 | const gl = this.device.gl; 470 | gl.deleteBuffer(this._gl_obj); 471 | } 472 | this._map_buf = null; 473 | } catch (e) { this.device._catch(e); } 474 | } 475 | } 476 | 477 | function make_GPUBufferDescriptor(desc) { 478 | desc = Object.assign({ 479 | }, desc); 480 | REQUIRE(desc, 'GPUBufferDescriptor', 'size'); 481 | REQUIRE(desc, 'GPUBufferDescriptor', 'usage'); 482 | return desc; 483 | } 484 | 485 | 486 | // ----------------- 487 | 488 | function make_GPUSamplerDescriptor(desc) { 489 | desc = Object.assign({ 490 | addressModeU: 'clamp-to-edge', 491 | addressModeV: 'clamp-to-edge', 492 | addressModeW: 'clamp-to-edge', 493 | magFilter: 'nearest', 494 | minFilter: 'nearest', 495 | mipmapFilter: 'nearest', 496 | lodMinClamp: 0, 497 | lodMaxClamp: 0xffffffff, 498 | compare: 'never', 499 | }, desc); 500 | return desc; 501 | } 502 | 503 | const WRAP_MODE = { 504 | 'clamp-to-edge': GL.CLAMP_TO_EDGE, 505 | 'repeat': GL.REPEAT, 506 | 'mirror-repeat': GL.MIRROR, 507 | }; 508 | 509 | const FILTER_MODE = { 510 | nearest: GL.NEAREST, 511 | linear: GL.LINEAR, 512 | }; 513 | const FILTER_MODE_KEY = { 514 | nearest: 'NEAREST', 515 | linear: 'LINEAR', 516 | }; 517 | 518 | class GPUSampler_JS { 519 | constructor(device, desc) { 520 | this.device = device; 521 | if (!desc) 522 | return; 523 | desc = make_GPUSamplerDescriptor(desc); 524 | this.desc = desc; 525 | 526 | const gl = this.device.gl; 527 | this._gl_obj = gl.createSampler(); 528 | 529 | const param = (name, map, pname) => { 530 | ASSERT(pname, 'pname'); 531 | const val = desc[name]; 532 | const mapped = map[val]; 533 | VALIDATE(mapped, name + ' invalid: ' + val); 534 | gl.samplerParameteri(this._gl_obj, pname, mapped); 535 | }; 536 | param('addressModeU', WRAP_MODE, GL.TEXTURE_WRAP_S); 537 | param('addressModeV', WRAP_MODE, GL.TEXTURE_WRAP_T); 538 | param('addressModeW', WRAP_MODE, GL.TEXTURE_WRAP_R); 539 | param('magFilter', FILTER_MODE, GL.TEXTURE_MAG_FILTER); 540 | 541 | const min = FILTER_MODE_KEY[desc.minFilter]; 542 | const mipmap = FILTER_MODE_KEY[desc.mipmapFilter]; 543 | VALIDATE(min, 'minFilter invalid: ' + min); 544 | VALIDATE(mipmap, 'mipmapFilter invalid: ' + mipmap); 545 | const key = min + '_MIPMAP_' + mipmap; 546 | gl.samplerParameteri(this._gl_obj, GL.TEXTURE_MIN_FILTER, GL[key]); 547 | 548 | gl.samplerParameterf(this._gl_obj, GL.TEXTURE_MIN_LOD, desc.lodMinClamp); 549 | gl.samplerParameterf(this._gl_obj, GL.TEXTURE_MAX_LOD, desc.lodMaxClamp); 550 | 551 | if (desc.compare != 'never') { 552 | param('compare', COMPARE_FUNC, GL.TEXTURE_COMPARE_FUNC); 553 | gl.samplerParameteri(this._gl_obj, GL.TEXTURE_COMPARE_MODE, GL.COMPARE_REF_TO_TEXTURE); 554 | } 555 | } 556 | } 557 | 558 | // ------- 559 | 560 | function make_GPUTextureViewDescriptor(desc) { 561 | desc = Object.assign({ 562 | baseMipLevel: 0, 563 | mipLevelCount: 1, 564 | baseArrayLayer: 0, 565 | arrayLayerCount: 1, 566 | }, desc); 567 | REQUIRE(desc, 'GPUTextureViewDescriptor', 'format'); 568 | REQUIRE(desc, 'GPUTextureViewDescriptor', 'dimension'); 569 | REQUIRE(desc, 'GPUTextureViewDescriptor', 'aspect'); 570 | return desc; 571 | } 572 | 573 | const TEX_TARGET_BY_VIEW_DIM = { 574 | '1d': GL.TEXTURE_2D, 575 | '2d': GL.TEXTURE_2D, 576 | '2d-array': GL.TEXTURE_2D_ARRAY, 577 | 'cube': GL.TEXTURE_CUBE_MAP, 578 | //'cube-array': GL.TEXTURE_2D_ARRAY, 579 | '3d': GL.TEXTURE_3D, 580 | }; 581 | 582 | class GPUTextureView_JS { 583 | constructor(tex, desc) { 584 | this.tex = tex; 585 | if (!desc) 586 | return; 587 | desc = make_GPUTextureViewDescriptor(desc); 588 | this.desc = desc; 589 | ASSERT(desc.baseArrayLayer == 0, 'Not supported: baseArrayLayer > 0'); 590 | ASSERT(desc.arrayLayerCount == tex.desc.arrayLayerCount, 591 | 'Not supported: GPUTextureViewDescriptor.arrayLayerCount != GPUTextureDescriptor.arrayLayerCount'); 592 | 593 | this._depth = tex._depth; 594 | this._stencil = tex._stencil; 595 | if (desc.aspect == 'depth-only') { 596 | this._stencil = undefined; 597 | } else if (desc.aspect == 'stencil-only') { 598 | this._depth = undefined; 599 | } 600 | } 601 | 602 | _bind_texture() { 603 | const gl = this.tex.device.gl; 604 | const gl_obj = this.tex._ensure_gl_obj(this.desc.dimension); 605 | gl.bindTexture(gl_obj.target, gl_obj); 606 | gl.texParameteri(gl_obj.target, GL.TEXTURE_BASE_LEVEL, this.desc.baseMipLevel); 607 | gl.texParameteri(gl_obj.target, GL.TEXTURE_MAX_LEVEL, this.desc.baseMipLevel + this.desc.mipLevelCount); 608 | } 609 | 610 | _framebuffer_attach(fb_target, attachment_enum) { 611 | const gl = this.tex.device.gl; 612 | const tex = this.tex; 613 | 614 | if (tex.swap_chain) { 615 | // We'll need a temp. 616 | console.error('Creating temporary rendertarget for SwapChain.'); 617 | } 618 | const gl_obj = tex._ensure_gl_obj(); 619 | const obj_target = gl_obj.target; 620 | if (obj_target == GL.TEXTURE_2D) { 621 | gl.framebufferTexture2D(fb_target, attachment_enum, 622 | obj_target, gl_obj, this.desc.baseMipLevel); 623 | } else if (obj_target == GL.TEXTURE_CUBE_MAP) { 624 | gl.framebufferTexture2D(fb_target, attachment_enum, 625 | TEXTURE_CUBE_MAP_POSITIVE_X + this.desc.baseArrayLayer, 626 | gl_obj, this.desc.baseMipLevel); 627 | } else if (obj_target == GL.TEXTURE_3D || 628 | obj_target == GL.TEXTURE_2D_ARRAY) { 629 | gl.framebufferTextureLayer(fb_target, attachment_enum, 630 | gl_obj, this.desc.baseMipLevel, this.desc.baseArrayLayer); 631 | } else if (obj_target == GL.RENDERBUFFER) { 632 | gl.framebufferRenderbuffer(fb_target, attachment_enum, obj_target, gl_obj); 633 | } else { 634 | ASSERT(false, 'Bad obj_target: 0x' + obj_target.toString(16)); 635 | } 636 | } 637 | 638 | _bind_as_draw_fb() { 639 | const gl = this.tex.device.gl; 640 | ASSERT(this.desc.arrayLayerCount == 1, 'desc.arrayLayerCount: 1'); 641 | ASSERT(this.desc.mipLevelCount == 1, 'desc.mipLevelCount: 1'); 642 | if (this._draw_fb === undefined) { 643 | if (this.tex.swap_chain) { 644 | this._draw_fb = null; 645 | } else { 646 | this._draw_fb = gl.createFramebuffer(); 647 | gl.bindFramebuffer(GL.DRAW_FRAMEBUFFER, this._draw_fb); 648 | this._framebuffer_attach(GL.DRAW_FRAMEBUFFER, GL.COLOR_ATTACHMENT0); 649 | } 650 | } 651 | gl.bindFramebuffer(GL.DRAW_FRAMEBUFFER, this._draw_fb); 652 | } 653 | } 654 | 655 | // ----- 656 | 657 | function make_GPUTextureDescriptor(desc) { 658 | desc = Object.assign({ 659 | arrayLayerCount: 1, 660 | mipLevelCount: 1, 661 | sampleCount: 1, 662 | dimension: '2d', 663 | }, desc); 664 | REQUIRE(desc, 'GPUTextureDescriptor', 'size'); 665 | REQUIRE(desc, 'GPUTextureDescriptor', 'format'); 666 | REQUIRE(desc, 'GPUTextureDescriptor', 'usage'); 667 | desc.size = make_GPUExtent3D(desc.size); 668 | return desc; 669 | } 670 | 671 | const TEX_FORMAT_INFO = { 672 | /* Normal 8 bit formats */ 673 | 'r8unorm' : {format: GL.R8 , unpack_format: GL.RED , type: GL.UNSIGNED_BYTE , float: true }, 674 | 'r8snorm' : {format: GL.R8_SNORM , unpack_format: GL.RED , type: GL.BYTE , float: true }, 675 | 'r8uint' : {format: GL.R8UI , unpack_format: GL.RED , type: GL.UNSIGNED_BYTE , float: false}, 676 | 'r8sint' : {format: GL.R8I , unpack_format: GL.RED , type: GL.BYTE , float: false}, 677 | /* Normal 16 bit formats */ 678 | //'r16unorm' : {format: GL.R16 , unpack_format: GL.RED , type: GL.UNSIGNED_SHORT , float: true }, 679 | //'r16snorm' : {format: GL.R16_SNORM , unpack_format: GL.RED , type: GL.UNSIGNED_SHORT , float: true }, 680 | 'r16uint' : {format: GL.R16UI , unpack_format: GL.RED , type: GL.UNSIGNED_SHORT , float: false}, 681 | 'r16sint' : {format: GL.R16I , unpack_format: GL.RED , type: GL.SHORT , float: false}, 682 | 'r16float' : {format: GL.R16F , unpack_format: GL.RED , type: GL.HALF_FLOAT , float: true }, 683 | 'rg8unorm' : {format: GL.RG8 , unpack_format: GL.RG , type: GL.UNSIGNED_BYTE , float: true }, 684 | 'rg8snorm' : {format: GL.RG8_SNORM , unpack_format: GL.RG , type: GL.BYTE , float: true }, 685 | 'rg8uint' : {format: GL.RG8UI , unpack_format: GL.RG , type: GL.UNSIGNED_BYTE , float: false}, 686 | 'rg8sint' : {format: GL.RG8I , unpack_format: GL.RG , type: GL.BYTE , float: false}, 687 | /* Packed 16 bit formats */ 688 | 'b5g6r5unorm' : {format: GL.RGB565 , unpack_format: GL.RGB , type: GL.UNSIGNED_SHORT_5_6_5 , float: true }, 689 | /* Normal 32 bit formats */ 690 | 'r32uint' : {format: GL.R32UI , unpack_format: GL.RED , type: GL.UNSIGNED_INT , float: false}, 691 | 'r32sint' : {format: GL.R32I , unpack_format: GL.RED , type: GL.INT , float: false}, 692 | 'r32float' : {format: GL.R32F , unpack_format: GL.RED , type: GL.FLOAT , float: true }, 693 | //'rg16unorm' : {format: GL.RG16 , unpack_format: GL.RG , type: GL.UNSIGNED_SHORT , float: true }, 694 | //'rg16snorm' : {format: GL.RG16_SNORM , unpack_format: GL.RG , type: GL.SHORT , float: true }, 695 | 'rg16uint' : {format: GL.RG16UI , unpack_format: GL.RG , type: GL.UNSIGNED_SHORT , float: false}, 696 | 'rg16sint' : {format: GL.RG16I , unpack_format: GL.RG , type: GL.SHORT , float: false}, 697 | 'rg16float' : {format: GL.RG16F , unpack_format: GL.RG , type: GL.HALF_FLOAT , float: true }, 698 | 'rgba8unorm' : {format: GL.RGBA8 , unpack_format: GL.RGBA , type: GL.UNSIGNED_BYTE , float: true }, 699 | 'rgba8unorm-srgb' : {format: GL.SRGB8_ALPHA8 , unpack_format: GL.RGBA , type: GL.UNSIGNED_BYTE , float: true }, 700 | 'rgba8snorm' : {format: GL.RGBA8_SNORM , unpack_format: GL.RGBA , type: GL.BYTE , float: true }, 701 | 'rgba8uint' : {format: GL.RGBA8UI , unpack_format: GL.RGBA , type: GL.UNSIGNED_BYTE , float: false}, 702 | 'rgba8sint' : {format: GL.RGBA8I , unpack_format: GL.RGBA , type: GL.BYTE , float: false}, 703 | 'bgra8unorm' : {format: GL.RGBA8 , unpack_format: GL.RGBA , type: GL.UNSIGNED_BYTE , float: true }, 704 | 'bgra8unorm-srgb' : {format: GL.SRGB8_ALPHA8 , unpack_format: GL.RGBA , type: GL.UNSIGNED_BYTE , float: true }, 705 | /* Packed 32 bit formats */ 706 | 'rgb10a2unorm' : {format: GL.RGB10_A2 , unpack_format: GL.RGBA , type: GL.UNSIGNED_INT_2_10_10_10_REV , float: true }, 707 | 'rg11b10float' : {format: GL.R11F_G11F_B10F , unpack_format: GL.RGB , type: GL.UNSIGNED_INT_10F_11F_11F_REV, float: true }, 708 | /* Normal 64 bit formats */ 709 | 'rg32uint' : {format: GL.RG32UI , unpack_format: GL.RG , type: GL.UNSIGNED_INT , float: false}, 710 | 'rg32sint' : {format: GL.RG32I , unpack_format: GL.RG , type: GL.INT , float: false}, 711 | 'rg32float' : {format: GL.RG32F , unpack_format: GL.RG , type: GL.FLOAT , float: true }, 712 | //'rgba16unorm' : {format: GL.RGBA16 , unpack_format: GL.RGBA , type: GL.UNSIGNED_SHORT , float: true }, 713 | //'rgba16snorm' : {format: GL.RGBA16_SNORM , unpack_format: GL.RGBA , type: GL.SHORT , float: true }, 714 | 'rgba16uint' : {format: GL.RGBA16UI , unpack_format: GL.RGBA , type: GL.UNSIGNED_SHORT , float: false}, 715 | 'rgba16sint' : {format: GL.RGBA16I , unpack_format: GL.RGBA , type: GL.SHORT , float: false}, 716 | 'rgba16float' : {format: GL.RGBA16F , unpack_format: GL.RGBA , type: GL.HALF_FLOAT , float: true }, 717 | /* Normal 128 bit formats */ 718 | 'rgba32uint' : {format: GL.RGBA32UI , unpack_format: GL.RGBA , type: GL.UNSIGNED_INT , float: false}, 719 | 'rgba32sint' : {format: GL.RGBA32I , unpack_format: GL.RGBA , type: GL.INT , float: false}, 720 | 'rgba32float' : {format: GL.RGBA32F , unpack_format: GL.RGBA , type: GL.FLOAT , float: true }, 721 | /* Depth/stencil formats */ 722 | 'depth32float' : {format: GL.DEPTH_COMPONENT32F, unpack_format: GL.DEPTH_COMPONENT, type: GL.FLOAT , float: true }, 723 | 'depth24plus' : {format: GL.DEPTH_COMPONENT24 , unpack_format: GL.DEPTH_COMPONENT, type: GL.UNSIGNED_INT , float: true }, 724 | 'depth24plus-stencil8': {format: GL.DEPTH24_STENCIL8 , unpack_format: GL.DEPTH_STENCIL , type: GL.UNSIGNED_INT_24_8 , float: true }, 725 | }; 726 | 727 | const DEFAULT_RENDERABLE = Object.fromEntries([ 728 | 'R8', 729 | 'RG8', 730 | 'RGB8', 731 | 'RGB565', 732 | 'RGBA4', 733 | 'RGB5_A1', 734 | 'RGBA8', 735 | 'RGB10_A2', 736 | 'RGB10_A2UI', 737 | 'SRGB8_ALPHA8', 738 | 'R8I', 739 | 'R8UI', 740 | 'R16I', 741 | 'R16UI', 742 | 'R32I', 743 | 'R32UI', 744 | 'RG8I', 745 | 'RG8UI', 746 | 'RG16I', 747 | 'RG16UI', 748 | 'RG32I', 749 | 'RG32UI', 750 | 'RGBA8I', 751 | 'RGBA8UI', 752 | 'RGBA16I', 753 | 'RGBA16UI', 754 | 'RGBA32I', 755 | 'RGBA32UI', 756 | ].map(x => [GL[x], true])); 757 | const FLOAT_RENDERABLE = Object.fromEntries([ 758 | 'R16F', 759 | 'R32F', 760 | 'RG16F', 761 | 'RG32F', 762 | 'RGBA16F', 763 | 'RGBA32F', 764 | 'R11F_G11F_B10F', 765 | ].map(x => [GL[x], true])); 766 | 767 | const DEPTH_STENCIL_FORMAT = { 768 | 'depth32float': {depth: true}, 769 | 'depth24plus': {depth: true}, 770 | 'depth24plus-stencil8': {depth: true, stencil: true}, 771 | }; 772 | 773 | class GPUTexture_JS { 774 | constructor(device, desc, swap_chain) { 775 | this.device = device; 776 | if (!desc) 777 | return; 778 | desc = make_GPUTextureDescriptor(desc); 779 | this.desc = desc; 780 | this.swap_chain = swap_chain; 781 | desc.format_info = TEX_FORMAT_INFO[desc.format]; 782 | VALIDATE(desc.format_info, 'Unsupported: GPUTextureFormat ' + desc.format); 783 | 784 | if (desc.usage & (GPUTextureUsage.COPY_SRC | GPUTextureUsage.OUTPUT_ATTACHMENT)) { 785 | let renderable = DEFAULT_RENDERABLE[desc.format_info.format]; 786 | if (!renderable) { 787 | renderable = FLOAT_RENDERABLE[desc.format_info.format]; 788 | if (renderable) { 789 | const gl = device.gl; 790 | const ext = gl.getExtension('EXT_color_buffer_float'); 791 | ASSERT(ext, 792 | 'EXT_color_buffer_float required for GPUTextureUsage.[COPY_SRC,OUTPUT_ATTACHMENT] with ' + desc.format); 793 | } 794 | } 795 | ASSERT(renderable, 796 | 'Unsupported: GPUTextureUsage.[COPY_SRC,OUTPUT_ATTACHMENT] with ' + desc.format); 797 | } 798 | 799 | if (desc.dimension == '1d') { 800 | desc.size.height = 1; 801 | desc.size.depth = 1; 802 | desc.arrayLayerCount = 1; 803 | } else if (desc.dimension == '2d') { 804 | desc.size.depth = 1; 805 | } else { 806 | desc.arrayLayerCount = 1; 807 | } 808 | if (desc.sampleCount != 1) { 809 | ASSERT(desc.dimension == '2d', 810 | 'Unsupported: desc.sampleCount!=1 && dimension!=2d'); 811 | ASSERT(desc.arrayLayerCount == 1, 812 | 'Unsupported: desc.sampleCount!=1 && arrayLayerCount!=1)'); 813 | ASSERT(desc.mipLevelCount == 1, 814 | 'Unsupported: desc.sampleCount!=1 && mipLevelCount!=1)'); 815 | ASSERT(!(desc.usage & GPUTextureUsage.SAMPLED), 816 | 'Unsupported: desc.sampleCount!=1 && (desc.usage & GPUTextureUsage.SAMPLED)'); 817 | } 818 | 819 | const ds_info = DEPTH_STENCIL_FORMAT[desc.format]; 820 | if (ds_info) { 821 | this._depth = ds_info.depth; 822 | this._stencil = ds_info.stencil; 823 | } 824 | 825 | if (!this.swap_chain) { 826 | this._ensure_gl_obj(); 827 | } 828 | } 829 | 830 | _ensure_gl_obj(dim) { 831 | const desc = this.desc; 832 | const gl = this.device.gl; 833 | 834 | if (this._gl_obj) { 835 | ASSERT(dim == this._gl_obj.dim, 836 | 'GPUTextureViewDimension conversion not supported: ' + dim + '->' + this._gl_obj.dim); 837 | } else if (desc.sampleCount != 1) { 838 | this._gl_obj = gl.createRenderbuffer(); 839 | this._gl_obj.target = GL.RENDERBUFFER; 840 | 841 | const format = TEX_FORMAT_INFO[desc.format].format; 842 | gl.bindRenderbuffer(this._gl_obj.target, this._gl_obj); 843 | gl.renderbufferStorageMultisample(this._gl_obj.target, desc.sampleCount, format, 844 | desc.size.width, desc.size.height); 845 | } else { 846 | if (!dim) { 847 | // Guess! 848 | if (desc.dimension == '1d') { 849 | dim = '2d'; 850 | } else if (desc.dimension == '3d') { 851 | dim = '3d'; 852 | } else if (desc.arrayLayerCount == 6 && 853 | desc.usage & GPUTextureUsage.SAMPLED) { 854 | dim = 'cube'; // A Good Guess. :) 855 | } else if (desc.arrayLayerCount > 1) { 856 | dim = '2d-array'; 857 | } else { 858 | dim = '2d'; 859 | } 860 | } 861 | 862 | this._gl_obj = gl.createTexture(); 863 | 864 | const bind_into = (target) => { 865 | this._gl_obj.target = target; 866 | gl.bindTexture(this._gl_obj.target, this._gl_obj); 867 | }; 868 | 869 | const format = TEX_FORMAT_INFO[desc.format].format; 870 | if (dim == '3d') { 871 | bind_into(GL.TEXTURE_3D); 872 | gl.texStorage3D(this._gl_obj.target, desc.mipLevelCount, format, desc.size.width, 873 | desc.size.height, desc.size.depth); 874 | } else if (dim == 'cube') { 875 | bind_into(GL.TEXTURE_CUBE_MAP); 876 | gl.texStorage2D(this._gl_obj.target, desc.mipLevelCount, format, desc.size.width, 877 | desc.size.height); 878 | } else if (dim == '2d-array') { 879 | bind_into(GL.TEXTURE_2D_ARRAY); 880 | gl.texStorage3D(this._gl_obj.target, desc.mipLevelCount, format, desc.size.width, 881 | desc.size.height, desc.arrayLayerCount); 882 | } else { 883 | bind_into(GL.TEXTURE_2D); 884 | gl.texStorage2D(this._gl_obj.target, desc.mipLevelCount, format, desc.size.width, 885 | desc.size.height); 886 | } 887 | } 888 | this._gl_obj.dim = dim; 889 | return this._gl_obj; 890 | } 891 | 892 | createView(desc) { 893 | try { 894 | VALIDATE(!this.device._is_lost, 'Device is lost.'); 895 | VALIDATE(this.desc, 'Invalid object.'); 896 | REQUIRE_NON_NULL(desc, 'GPUTextureView'); 897 | return new GPUTextureView_JS(this, desc); 898 | } catch (e) { 899 | this.device._catch(e); 900 | return new GPUTextureView_JS(this, null); 901 | } 902 | } 903 | 904 | createDefaultView() { 905 | try { 906 | return this.createView({ 907 | format: this.desc.format, 908 | dimension: this.desc.dimension, 909 | aspect: 'all', 910 | }); 911 | } catch (e) { this.device._catch(e); } 912 | } 913 | 914 | destroy() { 915 | try { 916 | VALIDATE(!this.device._is_lost, 'Device is lost.'); 917 | VALIDATE(this.desc, 'Invalid object.'); 918 | const gl = this.device.gl; 919 | gl.deleteTexture(this._gl_obj); 920 | } catch (e) { this.device._catch(e); } 921 | } 922 | } 923 | 924 | // - 925 | 926 | class GPUBindGroupLayout_JS { 927 | constructor(device, desc) { 928 | this.device = device; 929 | if (!desc) 930 | return; 931 | desc = make_GPUBindGroupLayout(desc); 932 | this.desc = desc; 933 | 934 | this._sparse_binding_layouts = []; 935 | for (const binding_layout of desc.bindings) { 936 | VALIDATE(this._sparse_binding_layouts[binding_layout.binding] === undefined, 937 | 'Duplicate binding layout location.'); 938 | this._sparse_binding_layouts[binding_layout.binding] = binding_layout; 939 | } 940 | } 941 | } 942 | 943 | function make_GPUBindGroupLayout(desc) { 944 | desc = Object.assign({ 945 | }, desc); 946 | REQUIRE_SEQ(desc, 'GPUBindGroupLayoutDescriptor', 'bindings', null, make_GPUBindGroupLayoutBinding); 947 | return desc; 948 | } 949 | function make_GPUBindGroupLayoutBinding(desc) { 950 | desc = Object.assign({ 951 | multisample: false, 952 | dynamic: false, 953 | }, desc); 954 | REQUIRE(desc, 'GPUBindGroupLayoutBinding', 'binding'); 955 | REQUIRE(desc, 'GPUBindGroupLayoutBinding', 'visibility'); 956 | REQUIRE(desc, 'GPUBindGroupLayoutBinding', 'type'); 957 | return desc; 958 | } 959 | 960 | // - 961 | 962 | class GPUPipelineLayout_JS { 963 | constructor(device, desc) { 964 | this.device = device; 965 | if (!desc) 966 | return; 967 | desc = make_GPUPipelineLayoutDescriptor(desc); 968 | this.desc = desc; 969 | let bindingCount = 0; 970 | desc.bindGroupLayouts.forEach(x => { 971 | x._bindingOffset = bindingCount; 972 | bindingCount += x.desc.bindings.length; 973 | }); 974 | } 975 | } 976 | 977 | function make_GPUPipelineLayoutDescriptor(desc) { 978 | desc = Object.assign({ 979 | }, desc); 980 | REQUIRE_SEQ(desc, 'GPUPipelineLayoutDescriptor', 'bindGroupLayouts', GPUBindGroupLayout_JS); 981 | return desc; 982 | } 983 | 984 | // - 985 | 986 | class GPUBindGroup_JS { 987 | constructor(device, desc) { 988 | this.device = device; 989 | if (!desc) 990 | return; 991 | desc = make_GPUBindGroupDescriptor(desc); 992 | this.desc = desc; 993 | 994 | const used_bindings = {}; 995 | for (const binding of desc.bindings) { 996 | VALIDATE(!used_bindings[binding.binding], 'Duplicate binding location.'); 997 | used_bindings[binding.binding] = true; 998 | 999 | const layout = desc.layout._sparse_binding_layouts[binding.binding]; 1000 | VALIDATE(layout, 'Binding location has no BindGroupLayout entry.'); 1001 | 1002 | if (binding.resource.sampler) { 1003 | VALIDATE(layout.type == 'sampler', 'GPUSampler must be bound to GPUBindingType sampler'); 1004 | 1005 | } else if (binding.resource.texture_view) { 1006 | const types = ['sampled-texture', 'storage-texture']; 1007 | VALIDATE(types.includes(layout.type), 'GPUTextureView must be bound to GPUBindingType ' + types.join('/')); 1008 | VALIDATE(layout.textureDimension == binding.resource.texture_view.desc.dimension, 1009 | 'Bad GPUBindGroupLayoutBinding.textureDimension for given GPUTextureView.'); 1010 | VALIDATE(layout.multisample == (binding.resource.texture_view.tex.desc.sampleCount != 1), 1011 | 'Bad GPUBindGroupLayoutBinding.multisample for given GPUTextureView.'); 1012 | 1013 | } else if (binding.resource.buffer_binding) { 1014 | const types = ['uniform-buffer', 'storage-buffer', 'readonly-storage-buffer']; 1015 | VALIDATE(types.includes(layout.type), 'GPUBufferBinding must be bound to GPUBindingType ' + types.join('/')); 1016 | const cur = binding.resource.buffer_binding; 1017 | VALIDATE(cur.buffer.desc.usage & GPUBufferUsage.UNIFORM, 1018 | 'GPUBufferBinding.buffer must have GPUBufferUsage.UNIFORM.'); 1019 | 1020 | } else { 1021 | ASSERT(false, 'Bad GPUBindingResource type.'); 1022 | } 1023 | binding._layout = layout; 1024 | } 1025 | } 1026 | } 1027 | 1028 | function make_GPUBindGroupDescriptor(desc) { 1029 | desc = Object.assign({ 1030 | }, desc); 1031 | REQUIRE(desc, 'GPUBindGroupDescriptor', 'layout', GPUBindGroupLayout_JS); 1032 | REQUIRE_SEQ(desc, 'GPUBindGroupDescriptor', 'bindings', null, make_GPUBindGroupBinding); 1033 | return desc; 1034 | } 1035 | function make_GPUBindGroupBinding(desc) { 1036 | desc = Object.assign({ 1037 | }, desc); 1038 | REQUIRE(desc, 'GPUBindGroupBinding', 'binding'); 1039 | REQUIRE(desc, 'GPUBindGroupBinding', 'resource', null, make_GPUBindingResource); 1040 | return desc; 1041 | } 1042 | function make_GPUBindingResource(cur) { 1043 | if (cur instanceof GPUSampler_JS) 1044 | return {sampler: cur}; 1045 | if (cur instanceof GPUTextureView_JS) 1046 | return {texture_view: cur}; 1047 | 1048 | desc = Object.assign({ 1049 | offset: 0, 1050 | }, cur); 1051 | REQUIRE(desc, 'GPUBufferBinding', 'buffer', GPUBuffer_JS); 1052 | return {buffer_binding: desc}; 1053 | } 1054 | 1055 | // - 1056 | 1057 | const PRIM_TOPO = { 1058 | 'point-list' : GL.POINTS, 1059 | 'line-list' : GL.LINES, 1060 | 'line-strip' : GL.LINE_STRIP, 1061 | 'triangle-list' : GL.TRIANGLES, 1062 | 'triangle-strip': GL.TRIANGLE_STRIP, 1063 | }; 1064 | 1065 | const VERTEX_FORMAT = { 1066 | uchar2 : {channels: 2, size: 1*2, type: GL.UNSIGNED_BYTE , norm: false, float: false}, 1067 | uchar4 : {channels: 4, size: 1*4, type: GL.UNSIGNED_BYTE , norm: false, float: false}, 1068 | char2 : {channels: 2, size: 1*2, type: GL.BYTE , norm: false, float: false}, 1069 | char4 : {channels: 4, size: 1*4, type: GL.BYTE , norm: false, float: false}, 1070 | uchar2norm : {channels: 2, size: 1*2, type: GL.UNSIGNED_BYTE , norm: true , float: true }, 1071 | uchar4norm : {channels: 4, size: 1*4, type: GL.UNSIGNED_BYTE , norm: true , float: true }, 1072 | char2norm : {channels: 2, size: 1*2, type: GL.BYTE , norm: true , float: true }, 1073 | char4norm : {channels: 4, size: 1*4, type: GL.BYTE , norm: true , float: true }, 1074 | ushort2 : {channels: 2, size: 2*2, type: GL.UNSIGNED_SHORT, norm: false, float: false}, 1075 | ushort4 : {channels: 4, size: 2*4, type: GL.UNSIGNED_SHORT, norm: false, float: false}, 1076 | short2 : {channels: 2, size: 2*2, type: GL.SHORT , norm: false, float: false}, 1077 | short4 : {channels: 4, size: 2*4, type: GL.SHORT , norm: false, float: false}, 1078 | ushort2norm: {channels: 2, size: 2*2, type: GL.UNSIGNED_SHORT, norm: true , float: true }, 1079 | ushort4norm: {channels: 4, size: 2*4, type: GL.UNSIGNED_SHORT, norm: true , float: true }, 1080 | short2norm: {channels: 2, size: 2*2, type: GL.SHORT , norm: true , float: true }, 1081 | short4norm: {channels: 4, size: 2*4, type: GL.SHORT , norm: true , float: true }, 1082 | half2 : {channels: 2, size: 2*2, type: GL.HALF_FLOAT , norm: false, float: true }, 1083 | half4 : {channels: 4, size: 2*4, type: GL.HALF_FLOAT , norm: false, float: true }, 1084 | float : {channels: 1, size: 4*1, type: GL.FLOAT , norm: false, float: true }, 1085 | float2 : {channels: 2, size: 4*2, type: GL.FLOAT , norm: false, float: true }, 1086 | float3 : {channels: 3, size: 4*3, type: GL.FLOAT , norm: false, float: true }, 1087 | float4 : {channels: 4, size: 4*4, type: GL.FLOAT , norm: false, float: true }, 1088 | uint : {channels: 1, size: 4*1, type: GL.UNSIGNED_INT , norm: false, float: false}, 1089 | uint2 : {channels: 2, size: 4*2, type: GL.UNSIGNED_INT , norm: false, float: false}, 1090 | uint3 : {channels: 3, size: 4*3, type: GL.UNSIGNED_INT , norm: false, float: false}, 1091 | uint4 : {channels: 4, size: 4*4, type: GL.UNSIGNED_INT , norm: false, float: false}, 1092 | int : {channels: 1, size: 4*1, type: GL.INT , norm: false, float: false}, 1093 | int2 : {channels: 2, size: 4*2, type: GL.INT , norm: false, float: false}, 1094 | int3 : {channels: 3, size: 4*3, type: GL.INT , norm: false, float: false}, 1095 | int4 : {channels: 4, size: 4*4, type: GL.INT , norm: false, float: false}, 1096 | }; 1097 | 1098 | const BLEND_EQUATION = { 1099 | 'add' : GL.FUNC_ADD, 1100 | 'subtract' : GL.FUNC_SUBTRACT, 1101 | 'reverse-subtract': GL.FUNC_REVERSE_SUBTRACT, 1102 | 'min' : GL.MIN, 1103 | 'max' : GL.MAX, 1104 | }; 1105 | const BLEND_FUNC = { 1106 | 'zero' : GL.ZERO, 1107 | 'one' : GL.ONE, 1108 | 'src-color' : GL.SRC_COLOR, 1109 | 'one-minus-src-color' : GL.ONE_MINUS_SRC_COLOR, 1110 | 'src-alpha' : GL.SRC_ALPHA, 1111 | 'one-minus-src-alpha' : GL.ONE_MINUS_SRC_ALPHA, 1112 | 'dst-color' : GL.DST_COLOR, 1113 | 'one-minus-dst-color' : GL.ONE_MINUS_DST_COLOR, 1114 | 'dst-alpha' : GL.DST_ALPHA, 1115 | 'one-minus-dst-alpha' : GL.ONE_MINUS_DST_ALPHA, 1116 | 'blend-color' : GL.CONSTANT_COLOR, 1117 | 'one-minus-blend-color': GL.ONE_MINUS_CONSTANT_COLOR, 1118 | }; 1119 | const COMPARE_FUNC = { 1120 | 'never' : GL.NEVER, 1121 | 'less' : GL.LESS, 1122 | 'equal' : GL.EQUAL, 1123 | 'less-equal' : GL.LEQUAL, 1124 | 'greater' : GL.GREATER, 1125 | 'not-equal' : GL.NOTEQUAL, 1126 | 'greater-equal': GL.GEAQUAL, 1127 | 'always' : GL.ALWAYS, 1128 | }; 1129 | const STENCIL_OP = { 1130 | 'keep' : GL.KEEP, 1131 | 'zero' : GL.ZERO, 1132 | 'replace' : GL.REPLACE, 1133 | 'invert' : GL.INVERT, 1134 | 'increment-clamp': GL.INCR, 1135 | 'decrement-clamp': GL.DECR, 1136 | 'increment-wrap' : GL.INCR_WRAP, 1137 | 'decrement-wrap' : GL.DECR_WRAP, 1138 | }; 1139 | 1140 | const SAMPLER_INFO_BY_TYPE = {}; 1141 | SAMPLER_INFO_BY_TYPE[GL.SAMPLER_2D ] = {dim: '2d' , type: 'f', shadow: false}; 1142 | SAMPLER_INFO_BY_TYPE[GL.SAMPLER_2D_SHADOW ] = {dim: '2d' , type: 'f', shadow: true }; 1143 | SAMPLER_INFO_BY_TYPE[GL.SAMPLER_3D ] = {dim: '3d' , type: 'f', shadow: false}; 1144 | SAMPLER_INFO_BY_TYPE[GL.SAMPLER_CUBE ] = {dim: 'cube' , type: 'f', shadow: false}; 1145 | SAMPLER_INFO_BY_TYPE[GL.SAMPLER_CUBE_SHADOW ] = {dim: 'cube' , type: 'f', shadow: true }; 1146 | SAMPLER_INFO_BY_TYPE[GL.SAMPLER_2D_ARRAY ] = {dim: '2d-array', type: 'f', shadow: false}; 1147 | SAMPLER_INFO_BY_TYPE[GL.SAMPLER_2D_ARRAY_SHADOW ] = {dim: '2d-array', type: 'f', shadow: true }; 1148 | SAMPLER_INFO_BY_TYPE[GL.INT_SAMPLER_2D ] = {dim: '2d' , type: 'i', shadow: false}; 1149 | SAMPLER_INFO_BY_TYPE[GL.INT_SAMPLER_3D ] = {dim: '3d' , type: 'i', shadow: false}; 1150 | SAMPLER_INFO_BY_TYPE[GL.INT_SAMPLER_CUBE ] = {dim: 'cube' , type: 'i', shadow: false}; 1151 | SAMPLER_INFO_BY_TYPE[GL.INT_SAMPLER_2D_ARRAY ] = {dim: '2d-array', type: 'i', shadow: false}; 1152 | SAMPLER_INFO_BY_TYPE[GL.UNSIGNED_INT_SAMPLER_2D ] = {dim: '2d' , type: 'u', shadow: false}; 1153 | SAMPLER_INFO_BY_TYPE[GL.UNSIGNED_INT_SAMPLER_3D ] = {dim: '3d' , type: 'u', shadow: false}; 1154 | SAMPLER_INFO_BY_TYPE[GL.UNSIGNED_INT_SAMPLER_CUBE ] = {dim: 'cube' , type: 'u', shadow: false}; 1155 | SAMPLER_INFO_BY_TYPE[GL.UNSIGNED_INT_SAMPLER_2D_ARRAY] = {dim: '2d-array', type: 'u', shadow: false}; 1156 | 1157 | const RE_BINDING_PREFIX = /webgpu_group([0-9]+)_binding([0-9]+)_*(.*)/; 1158 | 1159 | 1160 | class GPURenderPipeline_JS { 1161 | constructor(device, desc) { 1162 | this.device = device; 1163 | if (!desc) 1164 | return; 1165 | desc = make_GPURenderPipelineDescriptor(desc); 1166 | this.desc = desc; 1167 | 1168 | const gl = this.device.gl; 1169 | this.vao = gl.createVertexArray(); 1170 | gl.bindVertexArray(this.vao); 1171 | 1172 | const buff_list = this.desc.vertexInput.vertexBuffers; 1173 | for (const buff_i in buff_list) { 1174 | const buff_desc = buff_list[buff_i]; 1175 | const instance_divisor = (buff_desc.stepMode == 'instance' ? 1 : 0); 1176 | for (const attrib of buff_desc.attributeSet) { 1177 | gl.enableVertexAttribArray(attrib.shaderLocation); 1178 | gl.vertexAttribDivisor(attrib.shaderLocation, instance_divisor); 1179 | const format = VERTEX_FORMAT[attrib.format]; 1180 | 1181 | attrib.set_buf_offset = (gpu_buf, buf_offset) => { 1182 | const gl_buf = gpu_buf ? gpu_buf._gl_obj : null; 1183 | gl.bindBuffer(GL.ARRAY_BUFFER, gl_buf); 1184 | if (format.float) { 1185 | gl.vertexAttribPointer(attrib.shaderLocation, format.channels, format.type, 1186 | format.norm, buff_desc.stride, 1187 | buf_offset + attrib.offset); 1188 | } else { 1189 | gl.vertexAttribIPointer(attrib.shaderLocation, format.channels, format.type, 1190 | buff_desc.stride, 1191 | buf_offset + attrib.offset); 1192 | } 1193 | }; 1194 | } 1195 | } 1196 | 1197 | // - 1198 | 1199 | const prog = gl.createProgram(); 1200 | 1201 | function attach_shader(type, pipe_stage_desc) { 1202 | if (!pipe_stage_desc) 1203 | return; 1204 | const s = gl.createShader(type); 1205 | gl.shaderSource(s, pipe_stage_desc.module.desc.code); 1206 | gl.compileShader(s); 1207 | gl.attachShader(prog, s); 1208 | return s; 1209 | } 1210 | const vs = attach_shader(GL.VERTEX_SHADER, desc.vertexStage); 1211 | const fs = attach_shader(GL.FRAGMENT_SHADER, desc.fragmentStage); 1212 | gl.linkProgram(prog); 1213 | 1214 | const ok = gl.getProgramParameter(prog, gl.LINK_STATUS); 1215 | if (!ok) { 1216 | console.log('Error linking program:'); 1217 | console.log('\nLink log: ' + gl.getProgramInfoLog(prog)); 1218 | console.log('\nVert shader log: ' + gl.getShaderInfoLog(vs)); 1219 | if (fs) { 1220 | console.log('\nFrag shader log: ' + gl.getShaderInfoLog(fs)); 1221 | } 1222 | } 1223 | gl.deleteShader(vs); 1224 | if (fs) { 1225 | gl.deleteShader(fs); 1226 | } 1227 | this.prog = prog; 1228 | 1229 | // - 1230 | // `layout(binding=N) uniform` only added in ESSL310 :( 1231 | 1232 | function parse_binding(name, kind) { 1233 | const match = name.match(RE_BINDING_PREFIX); 1234 | VALIDATE(match, 1235 | name + ': GLSL uniform ' + kind + ' binding must start with /webgpu_group[0-9]+_binding[0-9]+_*/'); 1236 | return { 1237 | group: parseInt(match[1]), 1238 | binding: parseInt(match[2]), 1239 | }; 1240 | } 1241 | 1242 | const prog_was = gl.getParameter(gl.CURRENT_PROGRAM); 1243 | gl.useProgram(prog); 1244 | try { 1245 | const au_count = gl.getProgramParameter(prog, GL.ACTIVE_UNIFORMS); 1246 | const au_ids = new Array(au_count).fill(1).map((x,i) => i); 1247 | const au_types = gl.getActiveUniforms(prog, au_ids, GL.UNIFORM_TYPE); 1248 | const au_block = gl.getActiveUniforms(prog, au_ids, GL.UNIFORM_BLOCK_INDEX); 1249 | 1250 | const validated_blocks = {}; 1251 | const used_locations = {}; 1252 | au_ids.forEach(active_id => { 1253 | const block_id = au_block[active_id]; 1254 | if (block_id != -1) { 1255 | if (validated_blocks[block_id]) 1256 | return; 1257 | 1258 | const name = gl.getActiveUniformBlockName(prog, block_id); 1259 | const loc = parse_binding(name, 'block'); 1260 | const loc_str = loc.group + ',' + loc.binding; 1261 | VALIDATE(!used_locations[loc_str], 1262 | name + ': Location already in use: ' + loc_str); 1263 | used_locations[loc_str] = name; 1264 | 1265 | const bg_layout = desc.layout.desc.bindGroupLayouts[loc.group]; 1266 | VALIDATE(bg_layout, name + ': No corresponding GPUBindGroupLayout'); 1267 | 1268 | const binding_info = bg_layout.desc.bindings[loc.binding]; 1269 | VALIDATE(binding_info, name + ': No corresponding GPUBindGroupLayoutBinding'); 1270 | const types = ['uniform-buffer' , 'storage-buffer', 'readonly-storage-buffer']; 1271 | VALIDATE(types.includes(binding_info.type), 1272 | name + ': GLSL uniform block requires GPUBindingType ' + types.join('/')); 1273 | 1274 | const gl_binding = bg_layout._bindingOffset + loc.binding; 1275 | gl.uniformBlockBinding(prog, block_id, gl_binding); 1276 | validated_blocks[block_id] = true; 1277 | 1278 | //const req_size = gl.getActiveUniformBlockParameter(prog, block_id, GL.UNIFORM_BLOCK_DATA_SIZE); 1279 | //console.log(name + ': req_size: ' + req_size); 1280 | return; 1281 | } 1282 | // Default-block uniforms: 1283 | 1284 | const info = gl.getActiveUniform(prog, active_id); 1285 | const name = info.name; 1286 | const sampler_info = SAMPLER_INFO_BY_TYPE[info.type]; 1287 | VALIDATE(sampler_info, name + ': GLSL non-sampler uniforms must be within a uniform block'); 1288 | 1289 | // - 1290 | 1291 | const t_loc = parse_binding(name, 'sampler'); 1292 | const s_loc = Object.assign({}, t_loc); 1293 | s_loc.binding += 1; 1294 | 1295 | const t_name = name + ' (texture)'; 1296 | const s_name = name + ' (sampler)'; 1297 | 1298 | const t_loc_str = t_loc.group + ',' + t_loc.binding; 1299 | const s_loc_str = s_loc.group + ',' + s_loc.binding; 1300 | 1301 | VALIDATE(!used_locations[t_loc_str], 1302 | t_name + ': Binding is already in use: ' + used_locations[t_loc_str]); 1303 | VALIDATE(!used_locations[s_loc_str], 1304 | s_name + ': Binding is already in use: ' + used_locations[s_loc_str]); 1305 | used_locations[t_loc_str] = t_name; 1306 | used_locations[s_loc_str] = s_name; 1307 | 1308 | // - 1309 | 1310 | const bg_layout = desc.layout.desc.bindGroupLayouts[t_loc.group]; 1311 | VALIDATE(bg_layout, name + ': No corresponding GPUBindGroupLayout'); 1312 | 1313 | const t_binding_info = bg_layout.desc.bindings[t_loc.binding]; 1314 | VALIDATE(t_binding_info, 1315 | t_name + ': No corresponding GPUBindGroupLayoutBinding for binding ' + t_loc_str); 1316 | VALIDATE(t_binding_info.type == 'sampled-texture', 1317 | t_name + ': GLSL sampler requires layout GPUBindingType "sampled-texture" for binding ' + t_loc_str); 1318 | VALIDATE(sampler_info.dim == t_binding_info.textureDimension, 1319 | t_name + ': GLSL sampler dimensions must match GPUBindGroupLayout.textureDimension for binding ' + t_loc_str); 1320 | 1321 | const s_binding_info = bg_layout.desc.bindings[s_loc.binding]; 1322 | VALIDATE(s_binding_info, 1323 | s_name + ': No corresponding GPUBindGroupLayout for binding ' + s_loc_str); 1324 | VALIDATE(s_binding_info.type == 'sampler', 1325 | s_name + ': GLSL sampler requires layout GPUBindingType "sampler" for binding ' + s_loc_str); 1326 | 1327 | const gl_binding = bg_layout._bindingOffset + t_loc.binding; 1328 | const gl_loc = gl.getUniformLocation(prog, name); 1329 | gl.uniform1i(gl_loc, gl_binding); 1330 | }); 1331 | } finally { 1332 | gl.useProgram(prog_was); 1333 | } 1334 | 1335 | // - 1336 | 1337 | function equal_GPUBlendDescriptor(a, b) { 1338 | return (a.srcFactor == b.srcFactor && 1339 | a.dstFactor == b.dstFactor && 1340 | a.operation == b.operation); 1341 | } 1342 | function is_trivial_blend(x) { 1343 | return (x.srcFactor == 'one' && 1344 | x.dstFactor == 'zero' && 1345 | x.operation == 'add'); 1346 | } 1347 | 1348 | let example = null; 1349 | let matching = true; 1350 | desc.colorStates.forEach((cur, i) => { 1351 | if (!example) { 1352 | example = cur; 1353 | } 1354 | matching &= (equal_GPUBlendDescriptor(cur.alphaBlend, example.alphaBlend) && 1355 | equal_GPUBlendDescriptor(cur.colorBlend, example.colorBlend) && 1356 | cur.writeMask == example.writeMask); 1357 | }); 1358 | ASSERT(matching, 'Differing alphaBlend, colorBlend, and writeMask not supported.'); 1359 | 1360 | const has_blending = example && (!is_trivial_blend(example.alphaBlend) || 1361 | !is_trivial_blend(example.colorBlend)); 1362 | this._set_blend_and_mask = () => { 1363 | if (example) { 1364 | gl.colorMask(example.writeMask & GPUColorWriteBits.RED, 1365 | example.writeMask & GPUColorWriteBits.GREEN, 1366 | example.writeMask & GPUColorWriteBits.BLUE, 1367 | example.writeMask & GPUColorWriteBits.ALPHA); 1368 | } 1369 | if (has_blending) { 1370 | gl.enable(GL.BLEND); 1371 | gl.blendEquationSeparate(BLEND_EQUATION[example.colorBlend.operation], 1372 | BLEND_EQUATION[example.alphaBlend.operation]); 1373 | gl.blendFuncSeparate(BLEND_FUNC[example.colorBlend.srcFactor], 1374 | BLEND_FUNC[example.colorBlend.dstFactor], 1375 | BLEND_FUNC[example.alphaBlend.srcFactor], 1376 | BLEND_FUNC[example.alphaBlend.dstFactor]); 1377 | } else { 1378 | gl.disable(GL.BLEND); 1379 | } 1380 | }; 1381 | 1382 | this._set_stencil_ref = (ref) => { 1383 | const ds_desc = desc.depthStencilState 1384 | if (!ds_desc) 1385 | return; 1386 | gl.stencilFuncSeparate(GL.FRONT, COMPARE_FUNC[ds_desc.stencilFront.compare], ref, 1387 | ds_desc.stencilReadMask); 1388 | gl.stencilFuncSeparate(GL.BACK, COMPARE_FUNC[ds_desc.stencilBack.compare], ref, 1389 | ds_desc.stencilReadMask); 1390 | }; 1391 | } 1392 | 1393 | _setup(fb, color_attachments, ds_attach_desc) { 1394 | const gl = this.device.gl; 1395 | 1396 | gl.bindVertexArray(this.vao); 1397 | gl.useProgram(this.prog); 1398 | this._set_blend_and_mask(); 1399 | 1400 | const rast = this.desc.rasterizationState; 1401 | gl.frontFace(rast.frontFace == 'ccw' ? GL.CCW : GL.CW); 1402 | if (rast.cullMode == 'none') { 1403 | gl.disable(GL.CULL_FACE); 1404 | } else { 1405 | gl.enable(GL.CULL_FACE); 1406 | gl.cullFace(rast.cullMode == 'back' ? GL.BACK : GL.FRONT); 1407 | } 1408 | gl.polygonOffset(rast.depthBias, rast.depthBiasSlopeScale); 1409 | if (rast.depthBiasClamp) { 1410 | console.log('depthBiasClamp unsupported'); 1411 | } 1412 | 1413 | if (this.desc.fragmentStage) { 1414 | gl.disable(GL.RASTERIZER_DISCARD); 1415 | 1416 | const draw_bufs = []; 1417 | for (let i in this.desc.colorStates) { 1418 | i |= 0; 1419 | const ca = color_attachments[i]; 1420 | ca._load_op(); 1421 | 1422 | while (draw_bufs.length < i) { 1423 | draw_bufs.push(0); 1424 | } 1425 | if (fb) { 1426 | draw_bufs.push(GL.COLOR_ATTACHMENT0 + i); 1427 | } else { 1428 | draw_bufs.push(GL.BACK); 1429 | } 1430 | } 1431 | gl.drawBuffers(draw_bufs); 1432 | 1433 | // - 1434 | 1435 | let depth_test = false; 1436 | let stencil_test = false; 1437 | const ds_desc = this.desc.depthStencilState; 1438 | if (ds_desc) { 1439 | depth_test = (ds_desc.depthCompare == 'always' && 1440 | !ds_desc.depthWriteEnabled); 1441 | stencil_test = (ds_desc.stencilFront.compare == 'always' && 1442 | ds_desc.stencilBack.compare == 'always' && 1443 | !ds_desc.stencilWriteMask); 1444 | 1445 | VALIDATE(ds_attach_desc, 'Pipeline has depth-stencil but render-pass does not.'); 1446 | ds_attach_desc._load_op(); 1447 | } 1448 | 1449 | if (depth_test) { 1450 | gl.enable(gl.DEPTH_TEST); 1451 | gl.depthMask(ds_desc.depthWriteEnabled); 1452 | gl.depthFunc(COMPARE_FUNC[ds_desc.depthCompare]); 1453 | } else { 1454 | gl.disable(gl.DEPTH_TEST); 1455 | } 1456 | if (stencil_test) { 1457 | gl.enable(gl.STENCIL_TEST); 1458 | gl.stencilOpSeparate(GL.FRONT, 1459 | STENCIL_OP[ds_desc.stencilFront.failOp], 1460 | STENCIL_OP[ds_desc.stencilFront.depthFailOp], 1461 | STENCIL_OP[ds_desc.stencilFront.passOp]); 1462 | gl.stencilOpSeparate(GL.BACK, 1463 | STENCIL_OP[ds_desc.stencilBack.failOp], 1464 | STENCIL_OP[ds_desc.stencilBack.depthFailOp], 1465 | STENCIL_OP[ds_desc.stencilBack.passOp]); 1466 | gl.stencilMask(ds_desc.stencilWriteMask); 1467 | } else { 1468 | gl.disable(gl.STENCIL_TEST); 1469 | } 1470 | } else { 1471 | gl.enable(GL.RASTERIZER_DISCARD); 1472 | } 1473 | 1474 | if (this.desc.alphaToCoverageEnabled) { 1475 | gl.enable(GL.SAMPLE_ALPHA_TO_COVERAGE); 1476 | } else { 1477 | gl.disable(GL.SAMPLE_ALPHA_TO_COVERAGE); 1478 | } 1479 | } 1480 | 1481 | _set_vert_buffers(set_list) { 1482 | const buff_list = this.desc.vertexInput.vertexBuffers; 1483 | for (const buff_i in buff_list) { 1484 | if (!set_list[buff_i]) 1485 | continue; 1486 | const [buf, buf_offset] = set_list[buff_i]; 1487 | const buff_desc = buff_list[buff_i]; 1488 | for (const attrib of buff_desc.attributeSet) { 1489 | attrib.set_buf_offset(buf, buf_offset); 1490 | } 1491 | } 1492 | } 1493 | } 1494 | 1495 | function make_GPURenderPipelineDescriptor(desc) { 1496 | desc = make_GPUPipelineDescriptorBase(desc); 1497 | desc = Object.assign({ 1498 | fragmentStage: null, 1499 | depthStencilState: null, 1500 | sampleCount: 1, 1501 | sampleMask: 0xFFFFFFFF, 1502 | alphaToCoverageEnabled: false, 1503 | }, desc); 1504 | REQUIRE(desc, 'GPURenderPipelineDescriptor', 'vertexStage', null, make_GPUPipelineStageDescriptor); 1505 | REQUIRE(desc, 'GPURenderPipelineDescriptor', 'primitiveTopology'); 1506 | REQUIRE(desc, 'GPURenderPipelineDescriptor', 'rasterizationState', null, make_GPURasterizationStateDescriptor); 1507 | REQUIRE_SEQ(desc, 'GPURenderPipelineDescriptor', 'colorStates', null, make_GPUColorStateDescriptor); 1508 | REQUIRE(desc, 'GPURenderPipelineDescriptor', 'vertexInput', null, make_GPUVertexInputDescriptor); 1509 | if (desc.fragmentStage) { 1510 | desc.fragmentStage = make_GPUPipelineStageDescriptor(desc.fragmentStage); 1511 | } 1512 | return desc; 1513 | } 1514 | function make_GPURasterizationStateDescriptor(desc) { 1515 | desc = Object.assign({ 1516 | frontFace: 'ccw', 1517 | cullMode: 'none', 1518 | depthBias: 0, 1519 | depthBiasSlopeScale: 0, 1520 | depthBiasClamp: 0, 1521 | }, desc); 1522 | return desc; 1523 | } 1524 | function make_GPUColorStateDescriptor(desc) { 1525 | desc = Object.assign({ 1526 | writeMask: GPUColorWriteBits.ALL, 1527 | }, desc); 1528 | REQUIRE(desc, 'GPUColorStateDescriptor', 'format'); 1529 | REQUIRE(desc, 'GPUColorStateDescriptor', 'alphaBlend', null, make_GPUBlendDescriptor); 1530 | REQUIRE(desc, 'GPUColorStateDescriptor', 'colorBlend', null, make_GPUBlendDescriptor); 1531 | return desc; 1532 | } 1533 | function make_GPUBlendDescriptor(desc) { 1534 | desc = Object.assign({ 1535 | srcFactor: 'one', 1536 | dstFactor: 'zero', 1537 | operation: 'add', 1538 | }, desc); 1539 | return desc; 1540 | } 1541 | function make_GPUDepthStencilStateDescriptor(desc) { 1542 | desc = Object.assign({ 1543 | depthWriteEnabled: false, 1544 | depthCompare: 'always', 1545 | stencilReadMask: 0xFFFFFFFF, 1546 | stencilWriteMask: 0xFFFFFFFF, 1547 | }, desc); 1548 | REQUIRE(desc, 'GPUDepthStencilStateDescriptor', 'format'); 1549 | REQUIRE(desc, 'GPUDepthStencilStateDescriptor', 'stencilFront', null, make_GPUStencilStateFaceDescriptor); 1550 | REQUIRE(desc, 'GPUDepthStencilStateDescriptor', 'stencilBack', null, make_GPUStencilStateFaceDescriptor); 1551 | return desc; 1552 | } 1553 | function make_GPUStencilStateFaceDescriptor(desc) { 1554 | desc = Object.assign({ 1555 | compare: 'always', 1556 | failOp: 'keep', 1557 | depthFailOp: 'keep', 1558 | passOp: 'keep', 1559 | }, desc); 1560 | return desc; 1561 | } 1562 | function make_GPUVertexInputDescriptor(desc) { 1563 | desc = Object.assign({ 1564 | indexFormat: 'uint32', 1565 | }, desc); 1566 | REQUIRE_SEQ(desc, 'GPUVertexInputDescriptor ', 'vertexBuffers', null, make_nullable_GPUVertexBufferDescriptor); 1567 | return desc; 1568 | } 1569 | function make_nullable_GPUVertexBufferDescriptor(desc) { 1570 | if (!desc) 1571 | return null; 1572 | desc = Object.assign({ 1573 | stepMode: 'vertex', 1574 | }, desc); 1575 | REQUIRE(desc, 'GPUVertexBufferDescriptor', 'stride'); 1576 | REQUIRE_SEQ(desc, 'GPUVertexBufferDescriptor ', 'attributeSet', null, make_GPUVertexAttributeDescriptor); 1577 | return desc; 1578 | } 1579 | function make_GPUVertexAttributeDescriptor(desc) { 1580 | desc = Object.assign({ 1581 | offset: 0, 1582 | }, desc); 1583 | REQUIRE(desc, 'GPUVertexAttributeDescriptor', 'format'); 1584 | REQUIRE(desc, 'GPUVertexAttributeDescriptor', 'shaderLocation'); 1585 | return desc; 1586 | } 1587 | 1588 | // - 1589 | 1590 | function make_GPUPipelineStageDescriptor(desc) { 1591 | desc = Object.assign({ 1592 | }, desc); 1593 | REQUIRE(desc, 'GPUPipelineStageDescriptor', 'module', GPUShaderModule_JS); 1594 | REQUIRE_VAL(desc, 'GPUPipelineStageDescriptor', 'entryPoint', 'main'); 1595 | return desc; 1596 | } 1597 | function make_GPUPipelineDescriptorBase(desc) { 1598 | desc = Object.assign({ 1599 | }, desc); 1600 | REQUIRE(desc, 'GPUPipelineDescriptorBase', 'layout', GPUPipelineLayout_JS); 1601 | return desc; 1602 | } 1603 | 1604 | // - 1605 | 1606 | class GPUProgrammablePassEncoder_JS { 1607 | constructor(cmd_enc) { 1608 | this.cmd_enc = cmd_enc; 1609 | } 1610 | 1611 | endPass() { 1612 | try { 1613 | VALIDATE(!this.cmd_enc.device._is_lost, 'Device is lost.'); 1614 | VALIDATE(this.desc, 'Invalid object.'); 1615 | if (this.cmd_enc.in_pass == this) { 1616 | this.cmd_enc.in_pass = null; 1617 | } 1618 | } catch (e) { this.cmd_enc.device._catch(e); } 1619 | } 1620 | } 1621 | 1622 | function make_GPURenderPassColorAttachmentDescriptor(desc) { 1623 | desc = Object.assign({ 1624 | resolveTarget: null, 1625 | clearColor: [0,0,0,1], 1626 | }, desc); 1627 | REQUIRE(desc, 'GPURenderPassColorAttachmentDescriptor', 'attachment', GPUTextureView_JS); 1628 | REQUIRE(desc, 'GPURenderPassColorAttachmentDescriptor', 'loadOp'); 1629 | REQUIRE(desc, 'GPURenderPassColorAttachmentDescriptor', 'storeOp'); 1630 | desc.clearColor = make_GPUColor(desc.clearColor); 1631 | return desc; 1632 | } 1633 | 1634 | function make_GPURenderPassDepthStencilAttachmentDescriptor(desc) { 1635 | if (!desc) 1636 | return null; 1637 | desc = Object.assign({ 1638 | clearStencil: 0, 1639 | }, desc); 1640 | REQUIRE(desc, 'GPURenderPassDepthStencilAttachmentDescriptor ', 'attachment', GPUTextureView_JS); 1641 | REQUIRE(desc, 'GPURenderPassDepthStencilAttachmentDescriptor ', 'depthLoadOp'); 1642 | REQUIRE(desc, 'GPURenderPassDepthStencilAttachmentDescriptor ', 'depthStoreOp'); 1643 | REQUIRE(desc, 'GPURenderPassDepthStencilAttachmentDescriptor ', 'clearDepth'); 1644 | REQUIRE(desc, 'GPURenderPassDepthStencilAttachmentDescriptor ', 'stencilLoadOp'); 1645 | REQUIRE(desc, 'GPURenderPassDepthStencilAttachmentDescriptor ', 'stencilStoreOp'); 1646 | return desc; 1647 | } 1648 | 1649 | function make_GPURenderPassDescriptor(desc) { 1650 | desc = Object.assign({ 1651 | depthStencilAttachment: make_GPURenderPassDepthStencilAttachmentDescriptor(desc.depthStencilAttachment), 1652 | }, desc); 1653 | REQUIRE_SEQ(desc, 'GPURenderPassDescriptor ', 'colorAttachments', null, make_GPURenderPassColorAttachmentDescriptor); 1654 | return desc; 1655 | } 1656 | 1657 | const INDEX_FORMAT = { 1658 | uint16: {type: GL.UNSIGNED_SHORT, size: 2}, 1659 | uint32: {type: GL.UNSIGNED_INT, size: 4}, 1660 | }; 1661 | 1662 | class GPURenderPassEncoder_JS extends GPUProgrammablePassEncoder_JS { 1663 | constructor(cmd_enc, desc) { 1664 | super(cmd_enc); 1665 | this.cmd_enc = cmd_enc; 1666 | if (!desc) 1667 | return; 1668 | desc = make_GPURenderPassDescriptor(desc); 1669 | this.desc = desc; 1670 | 1671 | const attachment = desc.depthStencilAttachment || desc.colorAttachments[0]; 1672 | VALIDATE(attachment, 'Must have at least one color or depthStencil attachment.'); 1673 | const size = attachment.attachment.tex.desc.size; 1674 | 1675 | const device = cmd_enc.device; 1676 | const gl = device.gl; 1677 | 1678 | // - 1679 | 1680 | let direct_backbuffer_bypass = (desc.colorAttachments.length == 1 && 1681 | desc.colorAttachments[0].attachment.tex.swap_chain && 1682 | !desc.colorAttachments[0].resolveTarget); 1683 | const ds_desc = desc.depthStencilAttachment; 1684 | if (ds_desc) { 1685 | if (ds_desc.attachment._depth) { 1686 | direct_backbuffer_bypass &= (ds_desc.depthLoadOp != 'load' && 1687 | ds_desc.depthStoreOp != 'store'); 1688 | } 1689 | if (ds_desc.attachment._stencil) { 1690 | direct_backbuffer_bypass &= (ds_desc.stencilLoadOp != 'load' && 1691 | ds_desc.stencilStoreOp != 'store'); 1692 | } 1693 | } 1694 | //direct_backbuffer_bypass = false; 1695 | 1696 | this.fb = null; 1697 | if (!direct_backbuffer_bypass) { 1698 | // Guess not! 1699 | this.fb = gl.createFramebuffer(); 1700 | } 1701 | 1702 | // - 1703 | 1704 | function validate_view(name, view, for_depth_stencil) { 1705 | VALIDATE(view.desc.arrayLayerCount == 1, name + ': Must have arrayLayerCount=1.'); 1706 | VALIDATE(view.desc.mipLevelCount == 1, name + ': Must have mipLevelCount=1.'); 1707 | VALIDATE(view.tex.desc.size.width == size.width && 1708 | view.tex.desc.size.height == size.height, name + ': Sizes must match.'); 1709 | if (for_depth_stencil) { 1710 | VALIDATE(view._depth || view._stencil, name + ': Must have depth or stencil.'); 1711 | } else { 1712 | VALIDATE(!view._depth && !view._stencil, name + ': Must not have depth or stencil.'); 1713 | } 1714 | } 1715 | 1716 | // - 1717 | 1718 | desc.colorAttachments.forEach((x, i) => { 1719 | const view = x.attachment; 1720 | const tex = view.tex; 1721 | validate_view('colorAttachments[].attachment', view, false); 1722 | 1723 | if (this.fb) { 1724 | if (tex.swap_chain) { 1725 | // Bad news... 1726 | console.error('SwapChain Texture attached, but didn\'t qualify for direct_backbuffer_bypass.'); 1727 | } 1728 | gl.bindFramebuffer(GL.FRAMEBUFFER, this.fb); 1729 | view._framebuffer_attach(GL.FRAMEBUFFER, GL.COLOR_ATTACHMENT0 + i); 1730 | } 1731 | 1732 | if (x.loadOp == 'clear') { 1733 | const color = [ 1734 | x.clearColor.r, 1735 | x.clearColor.g, 1736 | x.clearColor.b, 1737 | x.clearColor.a, 1738 | ]; 1739 | const format = tex.desc.format; 1740 | let fn_clear = gl.clearBufferfv; 1741 | if (format.includes('sint')) { 1742 | fn_clear = gl.clearBufferiv; 1743 | } else if (format.includes('uint')) { 1744 | fn_clear = gl.clearBufferuiv; 1745 | } 1746 | let did_load = false; 1747 | x._load_op = () => { 1748 | if (did_load) 1749 | return; 1750 | did_load = true; 1751 | fn_clear.call(gl, GL.COLOR, i, color); 1752 | }; 1753 | } else { 1754 | x._load_op = () => {}; 1755 | } 1756 | 1757 | if (x.resolveTarget) { 1758 | const name = 'colorAttachments[].resolveTarget'; 1759 | validate_view(name, x.resolveTarget, false); 1760 | VALIDATE(x.resolveTarget.tex.desc.sampleCount == 1, 1761 | name + ': Must have sampleCount=1.'); 1762 | x.resolveTarget._bind_as_draw_fb(); // Ensure created. 1763 | } 1764 | 1765 | x._store_op = () => { 1766 | let read_attach_enum = GL.COLOR_ATTACHMENT0 + i; 1767 | if (!this.fb) { 1768 | read_attach_enum = GL.COLOR; 1769 | } 1770 | 1771 | function blit() { 1772 | gl.readBuffer(read_attach_enum); 1773 | const w = size.width; 1774 | const h = size.height; 1775 | gl.blitFramebuffer(0,0, w,h, 0,0, w,h, GL.COLOR_BUFFER_BIT, GL.NEAREST); 1776 | } 1777 | 1778 | let discard = true; 1779 | if (x.resolveTarget) { 1780 | x.resolveTarget._bind_as_draw_fb(); 1781 | blit(); 1782 | } else if (x.storeOp == 'store') { 1783 | if (this.fb && tex.swap_chain) { 1784 | gl.bindFramebuffer(GL.DRAW_FRAMEBUFFER, null); 1785 | blit(); 1786 | } else { 1787 | discard = false; 1788 | } 1789 | } 1790 | if (discard) { 1791 | gl.invalidateFramebuffer(GL.READ_FRAMEBUFFER, [read_attach_enum]); 1792 | } 1793 | }; 1794 | }); 1795 | 1796 | // - 1797 | 1798 | if (ds_desc) { 1799 | const view = ds_desc.attachment; 1800 | validate_view('depthStencilAttachment.attachment', view, true); 1801 | 1802 | if (this.fb) { 1803 | gl.bindFramebuffer(GL.FRAMEBUFFER, this.fb); 1804 | if (view._depth) { 1805 | view._framebuffer_attach(GL.FRAMEBUFFER, GL.DEPTH_ATTACHMENT); 1806 | } 1807 | if (view._stencil) { 1808 | view._framebuffer_attach(GL.FRAMEBUFFER, GL.STENCIL_ATTACHMENT); 1809 | } 1810 | } 1811 | 1812 | let did_load = false; 1813 | ds_desc._load_op = () => { 1814 | if (did_load) 1815 | return; 1816 | did_load = true; 1817 | const clear_depth = (view._depth && ds_desc.depthLoadOp == 'clear'); 1818 | const clear_stencil = (view._stencil && ds_desc.stencilLoadOp == 'clear'); 1819 | if (clear_depth) { 1820 | gl.depthMask(true); 1821 | if (!clear_stencil) { 1822 | gl.clearBufferfv(GL.DEPTH, 0, [ds_desc.clearDepth]); 1823 | } 1824 | } 1825 | if (clear_stencil) { 1826 | gl.stencilMask(0xffffffff); 1827 | if (clear_depth) { 1828 | gl.clearBufferfi(GL.DEPTH_STENCIL, 0, ds_desc.clearDepth, ds_desc.clearStencil); 1829 | } else { 1830 | gl.clearBufferiv(GL.STENCIL, 0, [ds_desc.clearStencil]); 1831 | } 1832 | } 1833 | }; 1834 | ds_desc._store_op = () => { 1835 | let keep_depth = (view._depth && ds_desc.depthStoreOp == 'store'); 1836 | let keep_stencil = (view._stencil && ds_desc.stencilStoreOp == 'store'); 1837 | 1838 | let depth_enum = GL.DEPTH_ATTACHMENT; 1839 | let stencil_enum = GL.STENCIL_ATTACHMENT; 1840 | if (!this.fb) { 1841 | depth_enum = GL.DEPTH; 1842 | stencil_enum = GL.STENCIL; 1843 | } 1844 | let discard_list = []; 1845 | if (!keep_depth) { 1846 | discard_list.push(depth_enum); 1847 | } 1848 | if (!keep_stencil) { 1849 | discard_list.push(stencil_enum); 1850 | } 1851 | if (discard_list.length) { 1852 | gl.invalidateFramebuffer(GL.READ_FRAMEBUFFER, discard_list); 1853 | } 1854 | }; 1855 | } 1856 | 1857 | // - 1858 | 1859 | this.cmd_enc._add(() => { 1860 | gl.bindFramebuffer(GL.FRAMEBUFFER, this.fb); 1861 | }); 1862 | 1863 | this._vert_buf_list = []; 1864 | this._bind_group_list = []; 1865 | this._deferred_bind_group_updates = []; 1866 | 1867 | this.setBlendColor([1, 1, 1, 1]); 1868 | this.setStencilReference(0); 1869 | this.setViewport(0, 0, size.width, size.height, 0, 1); 1870 | this.setScissorRect(0, 0, size.width, size.height); 1871 | this.setIndexBuffer(0, [], []); 1872 | this.setVertexBuffers(0, [], []); 1873 | } 1874 | 1875 | setBindGroup(index, bind_group, dynamic_offset_list) { 1876 | try { 1877 | VALIDATE(!this.cmd_enc.device._is_lost, 'Device is lost.'); 1878 | VALIDATE(this.desc, 'Invalid object.'); 1879 | 1880 | dynamic_offset_list = (dynamic_offset_list || []).slice(); 1881 | 1882 | this._deferred_bind_group_updates[index] = { 1883 | bind_group, 1884 | dynamic_offset_list, 1885 | }; 1886 | } catch (e) { this.cmd_enc.device._catch(e); } 1887 | } 1888 | 1889 | setPipeline(pipeline) { 1890 | try { 1891 | VALIDATE(!this.cmd_enc.device._is_lost, 'Device is lost.'); 1892 | VALIDATE(this.desc, 'Invalid object.'); 1893 | 1894 | this._pipeline = pipeline; 1895 | this._pipeline_ready = false; 1896 | this._vert_bufs_ready = false; 1897 | this._stencil_ref_ready = false; 1898 | 1899 | } catch (e) { this.cmd_enc.device._catch(e); } 1900 | } 1901 | setBlendColor(color) { 1902 | try { 1903 | VALIDATE(!this.cmd_enc.device._is_lost, 'Device is lost.'); 1904 | VALIDATE(this.desc, 'Invalid object.'); 1905 | 1906 | color = make_GPUColor(color); 1907 | const gl = this.cmd_enc.device.gl; 1908 | 1909 | this.cmd_enc._add(() => { 1910 | gl.blendColor(color.r, color.g, color.b, color.a); 1911 | }); 1912 | } catch (e) { this.cmd_enc.device._catch(e); } 1913 | } 1914 | setStencilReference(ref) { 1915 | try { 1916 | VALIDATE(!this.cmd_enc.device._is_lost, 'Device is lost.'); 1917 | VALIDATE(this.desc, 'Invalid object.'); 1918 | 1919 | this._stencil_ref = ref; 1920 | this._stencil_ref_ready = false; 1921 | 1922 | } catch (e) { this.cmd_enc.device._catch(e); } 1923 | } 1924 | 1925 | setViewport(x, y, w, h, min_depth, max_depth) { 1926 | try { 1927 | VALIDATE(!this.cmd_enc.device._is_lost, 'Device is lost.'); 1928 | VALIDATE(this.desc, 'Invalid object.'); 1929 | 1930 | const gl = this.cmd_enc.device.gl; 1931 | 1932 | this.cmd_enc._add(() => { 1933 | gl.viewport(x, y, w, h); 1934 | gl.depthRange(min_depth, max_depth); 1935 | }); 1936 | } catch (e) { this.cmd_enc.device._catch(e); } 1937 | } 1938 | 1939 | setScissorRect(x, y, w, h) { 1940 | try { 1941 | VALIDATE(!this.cmd_enc.device._is_lost, 'Device is lost.'); 1942 | VALIDATE(this.desc, 'Invalid object.'); 1943 | 1944 | const gl = this.cmd_enc.device.gl; 1945 | 1946 | this.cmd_enc._add(() => { 1947 | gl.scissor(x, y, w, h); 1948 | }); 1949 | } catch (e) { this.cmd_enc.device._catch(e); } 1950 | } 1951 | 1952 | setIndexBuffer(buffer, offset) { 1953 | try { 1954 | VALIDATE(!this.cmd_enc.device._is_lost, 'Device is lost.'); 1955 | VALIDATE(this.desc, 'Invalid object.'); 1956 | 1957 | this._index_buf_offset = offset; 1958 | const gl = this.cmd_enc.device.gl; 1959 | const gl_buf = buffer ? buffer._gl_obj : null; 1960 | 1961 | this.cmd_enc._add(() => { 1962 | gl.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, gl_buf); 1963 | }); 1964 | } catch (e) { this.cmd_enc.device._catch(e); } 1965 | } 1966 | setVertexBuffers(start_slot, buffers, offsets) { 1967 | try { 1968 | VALIDATE(!this.cmd_enc.device._is_lost, 'Device is lost.'); 1969 | VALIDATE(this.desc, 'Invalid object.'); 1970 | 1971 | for (let i in buffers) { 1972 | i |= 0; // Arr ids are strings! 0 + '0' is '00'! 1973 | const slot = start_slot + i; 1974 | this._vert_buf_list[slot] = [buffers[i], offsets[i]]; 1975 | } 1976 | this._vert_bufs_ready = false; 1977 | 1978 | } catch (e) { this.cmd_enc.device._catch(e); } 1979 | } 1980 | 1981 | _pre_draw() { 1982 | const gl = this.cmd_enc.device.gl; 1983 | const pipeline = this._pipeline; 1984 | if (!this._pipeline_ready) { 1985 | this.cmd_enc._add(() => { 1986 | // Generally speaking, don't use `this.foo` in _add, but rather use copies. 1987 | // Exception: Immutable objects: 1988 | pipeline._setup(this.fb, this.desc.colorAttachments, this.desc.depthStencilAttachment); 1989 | }); 1990 | this._pipeline_ready = true; 1991 | } 1992 | if (!this._vert_bufs_ready) { 1993 | const cur_buf_list = this._vert_buf_list.slice(); // Make a copy to embed. 1994 | this.cmd_enc._add(() => { 1995 | pipeline._set_vert_buffers(cur_buf_list); 1996 | }); 1997 | this._vert_bufs_ready = true; 1998 | } 1999 | if (!this._stencil_ref_ready) { 2000 | const cup_stencil_ref = this._stencil_ref; 2001 | this.cmd_enc._add(() => { 2002 | pipeline._set_stencil_ref(cup_stencil_ref); 2003 | }); 2004 | this._stencil_ref_ready = true; 2005 | } 2006 | const pipeline_bg_layouts = pipeline.desc.layout.desc.bindGroupLayouts; 2007 | if (this._deferred_bind_group_updates.length) { 2008 | this._deferred_bind_group_updates.forEach((update, i) => { 2009 | //console.log('def', update, i); 2010 | const bg_layout = pipeline_bg_layouts[i]; 2011 | if (!bg_layout) 2012 | return; 2013 | //console.log('bg_layout', bg_layout); 2014 | this._deferred_bind_group_updates[i] = undefined; 2015 | 2016 | for (const cur of update.bind_group.desc.bindings) { 2017 | let loc = bg_layout._bindingOffset + cur.binding; 2018 | //console.log(cur, loc); 2019 | const res = cur.resource; 2020 | 2021 | if (res.sampler) { 2022 | loc -= 1; // GPUSamplers are implied just after their sampled-textures. 2023 | this.cmd_enc._add(() => { 2024 | //console.log('bindSampler', loc, res.sampler._gl_obj); 2025 | gl.bindSampler(loc, res.sampler._gl_obj); 2026 | }); 2027 | continue; 2028 | } 2029 | 2030 | if (res.texture_view) { 2031 | this.cmd_enc._add(() => { 2032 | //console.log('activeTexture', loc); 2033 | gl.activeTexture(GL.TEXTURE0 + loc); 2034 | res.texture_view._bind_texture(); 2035 | }); 2036 | continue; 2037 | } 2038 | 2039 | const buf = res.buffer_binding.buffer; 2040 | let offset = res.buffer_binding.offset; 2041 | let size = res.buffer_binding.size; 2042 | 2043 | if (cur._layout.dynamic) { 2044 | VALIDATE(update.dynamic_offset_list.length, 'Not enough dynamic offsets'); 2045 | offset += update.dynamic_offset_list.shift(); 2046 | } 2047 | 2048 | if (!size) { 2049 | size = buf.desc.size - offset; 2050 | } 2051 | this.cmd_enc._add(() => { 2052 | //console.log('bindBufferRange', loc, buf._gl_obj, offset, size); 2053 | gl.bindBufferRange(GL.UNIFORM_BUFFER, loc, buf._gl_obj, offset, size); 2054 | }); 2055 | } 2056 | }); 2057 | while (this._deferred_bind_group_updates.length) { 2058 | const last = this._deferred_bind_group_updates[this._deferred_bind_group_updates.length-1]; 2059 | if (last) 2060 | break; 2061 | this._deferred_bind_group_updates.pop(); 2062 | } 2063 | } 2064 | } 2065 | 2066 | draw(vert_count, inst_count, base_vert, base_inst) { 2067 | try { 2068 | VALIDATE(!this.cmd_enc.device._is_lost, 'Device is lost.'); 2069 | VALIDATE(this.desc, 'Invalid object.'); 2070 | VALIDATE(base_inst == 0, 'firstInstance must be 0'); 2071 | 2072 | this._pre_draw(); 2073 | 2074 | const gl = this.cmd_enc.device.gl; 2075 | const prim_topo = PRIM_TOPO[this._pipeline.desc.primitiveTopology]; 2076 | 2077 | this.cmd_enc._add(() => { 2078 | gl.drawArraysInstanced(prim_topo, base_vert, vert_count, inst_count); 2079 | }); 2080 | } catch (e) { this.cmd_enc.device._catch(e); } 2081 | } 2082 | drawIndexed(index_count, inst_count, base_index, base_vert, base_inst) { 2083 | try { 2084 | VALIDATE(!this.cmd_enc.device._is_lost, 'Device is lost.'); 2085 | VALIDATE(this.desc, 'Invalid object.'); 2086 | VALIDATE(base_inst == 0, 'firstInstance must be 0'); 2087 | 2088 | this._pre_draw(); 2089 | 2090 | const gl = this.cmd_enc.device.gl; 2091 | const prim_topo = PRIM_TOPO[this._pipeline.desc.primitiveTopology]; 2092 | const format = INDEX_FORMAT[this._pipeline.desc.vertexInput.indexFormat]; 2093 | const offset = this._index_buf_offset + base_index * format.size; 2094 | 2095 | this.cmd_enc._add(() => { 2096 | gl.drawElementsInstanced(prim_topo, index_count, format.type, offset, inst_count); 2097 | }); 2098 | } catch (e) { this.cmd_enc.device._catch(e); } 2099 | } 2100 | 2101 | endPass() { 2102 | try { 2103 | VALIDATE(!this.cmd_enc.device._is_lost, 'Device is lost.'); 2104 | VALIDATE(this.desc, 'Invalid object.'); 2105 | this.cmd_enc._add(() => { 2106 | for (const x of this.desc.colorAttachments) { 2107 | x._load_op(); // In case it was never otherwise used. 2108 | x._store_op(); 2109 | } 2110 | const ds_desc = this.desc.depthStencilAttachment; 2111 | if (ds_desc) { 2112 | ds_desc._load_op(); 2113 | ds_desc._store_op(); 2114 | } 2115 | }); 2116 | super.endPass(); 2117 | } catch (e) { this.cmd_enc.device._catch(e); } 2118 | } 2119 | } 2120 | 2121 | // - 2122 | 2123 | 2124 | class GPUCommandBuffer_JS { 2125 | constructor(device, enc) { 2126 | this.device = device; 2127 | this.enc = enc; 2128 | } 2129 | } 2130 | 2131 | class GPUCommandEncoder_JS { 2132 | constructor(device, desc) { 2133 | this.device = device; 2134 | if (!desc) 2135 | return; 2136 | desc = make_GPUCommandEncoderDescriptor(desc); 2137 | this.desc = desc; 2138 | 2139 | this.in_pass = null; 2140 | this.is_finished = false; 2141 | this.cmds = []; 2142 | } 2143 | 2144 | _add(fn_cmd) { 2145 | this.cmds.push(fn_cmd); 2146 | } 2147 | 2148 | beginRenderPass(desc) { 2149 | try { 2150 | VALIDATE(!this.device._is_lost, 'Device is lost.'); 2151 | VALIDATE(this.desc, 'Invalid object.'); 2152 | VALIDATE(!this.in_pass, 'endPass not called.'); 2153 | VALIDATE(!this.is_finished, 'Already finished.'); 2154 | REQUIRE_NON_NULL(desc, 'GPURenderPassDescriptor'); 2155 | const ret = new GPURenderPassEncoder_JS(this, desc); 2156 | this.in_pass = ret; 2157 | return ret; 2158 | } catch (e) { 2159 | this.device._catch(e); 2160 | return new GPURenderPassEncoder_JS(this, null); 2161 | } 2162 | } 2163 | 2164 | copyBufferToBuffer(source, sourceOffset, destination, destinationOffset, size) { 2165 | try { 2166 | VALIDATE(!this.device._is_lost, 'Device is lost.'); 2167 | VALIDATE(this.desc, 'Invalid object.'); 2168 | VALIDATE(!this.in_pass, 'Cannot be within pass.'); 2169 | VALIDATE(!this.is_finished, 'Already finished.'); 2170 | 2171 | VALIDATE(source.device == this.device && 2172 | source.desc && 2173 | !source._mapped() && 2174 | source.desc.usage & GPUBufferUsage.COPY_SRC, 'Invalid source object.'); 2175 | VALIDATE(destination.device == this.device && 2176 | destination.desc && 2177 | !destination._mapped() && 2178 | destination.desc.usage & GPUBufferUsage.COPY_DST, 'Invalid destination object.'); 2179 | 2180 | const gl = this.device.gl; 2181 | gl.bindBuffer(GL.COPY_READ_BUFFER, source._gl_obj); 2182 | gl.bindBuffer(GL.COPY_WRITE_BUFFER, destination._gl_obj); 2183 | gl.copyBufferSubData(GL.COPY_READ_BUFFER, GL.COPY_WRITE_BUFFER, sourceOffset, destinationOffset, size); 2184 | gl.bindBuffer(GL.COPY_READ_BUFFER, null); 2185 | gl.bindBuffer(GL.COPY_WRITE_BUFFER, null); 2186 | } catch (e) { this.device._catch(e); } 2187 | } 2188 | 2189 | copyBufferToTexture(src, dest, size) { 2190 | try { 2191 | VALIDATE(!this.device._is_lost, 'Device is lost.'); 2192 | VALIDATE(this.desc, 'Invalid object.'); 2193 | VALIDATE(!this.in_pass, 'Cannot be within pass.'); 2194 | VALIDATE(!this.is_finished, 'Already finished.'); 2195 | 2196 | src = make_GPUBufferCopyView(src); 2197 | dest = make_GPUTextureCopyView(dest); 2198 | size = make_GPUExtent3D(size); 2199 | 2200 | VALIDATE(src.buffer.device == this.device && 2201 | src.buffer.desc && 2202 | !src.buffer._mapped() && 2203 | src.buffer.desc.usage & GPUBufferUsage.COPY_SRC, 'Invalid source object.'); 2204 | VALIDATE(dest.texture.device == this.device && 2205 | dest.texture.desc && 2206 | dest.texture.desc.usage & GPUTextureUsage.COPY_DST, 'Invalid destination object.'); 2207 | 2208 | const format = TEX_FORMAT_INFO[dest.texture.desc.format]; 2209 | 2210 | const gl = this.device.gl; 2211 | gl.bindBuffer(GL.PIXEL_UNPACK_BUFFER, src.buffer._gl_obj); 2212 | const dst_target = dest.texture._gl_obj.target; 2213 | gl.bindTexture(dst_target, dest.texture._gl_obj); 2214 | if (dst_target == GL.TEXTURE_3D) { 2215 | gl.texSubImage3D(dst_target, dest.mipLevel, dest.origin.x, dest.origin.y, dest.origin.z, 2216 | size.width, size.height, size.depth, 2217 | format.unpack_format, format.type, src.offset); 2218 | } else if (dst_target == GL.TEXTURE_2D_ARRAY) { 2219 | gl.texSubImage3D(dst_target, dest.mipLevel, dest.origin.x, dest.origin.y, dest.arrayLayer, 2220 | size.width, size.height, 1, 2221 | format.unpack_format, format.type, src.offset); 2222 | } else if (dst_target == GL.TEXTURE_CUBE_MAP) { 2223 | // Cubemaps are the wooorst. 2224 | gl.texSubImage2D(dst_target, dest.mipLevel, dest.origin.x, dest.origin.y, 2225 | size.width, size.height, 2226 | format.unpack_format, format.type, src.offset); 2227 | } else { 2228 | gl.texSubImage2D(dst_target, dest.mipLevel, dest.origin.x, dest.origin.y, 2229 | size.width, size.height, 2230 | format.unpack_format, format.type, src.offset); 2231 | } 2232 | gl.bindTexture(dst_target, null); 2233 | gl.bindBuffer(GL.PIXEL_UNPACK_BUFFER, null); 2234 | } catch (e) { this.device._catch(e); } 2235 | } 2236 | 2237 | finish() { 2238 | try { 2239 | VALIDATE(!this.device._is_lost, 'Device is lost.'); 2240 | VALIDATE(this.desc, 'Invalid object.'); 2241 | VALIDATE(!this.in_pass, 'endPass not called.'); 2242 | VALIDATE(!this.is_finished, 'Already finished.'); 2243 | this.is_finished = true; 2244 | return new GPUCommandBuffer_JS(this.device, this); 2245 | } catch (e) { 2246 | this.device._catch(e); 2247 | return new GPUCommandBuffer_JS(this.device, null); 2248 | } 2249 | } 2250 | } 2251 | 2252 | function make_GPUCommandEncoderDescriptor(desc) { 2253 | desc = Object.assign({ 2254 | }, desc); 2255 | return desc; 2256 | } 2257 | 2258 | function make_GPUBufferCopyView(desc) { 2259 | desc = Object.assign({ 2260 | offset: 0, 2261 | }, desc); 2262 | REQUIRE(desc, 'GPUBufferCopyView ', 'buffer', GPUBuffer_JS); 2263 | REQUIRE(desc, 'GPUBufferCopyView ', 'rowPitch'); 2264 | REQUIRE(desc, 'GPUBufferCopyView ', 'imageHeight'); 2265 | return desc; 2266 | } 2267 | function make_GPUTextureCopyView(desc) { 2268 | desc = Object.assign({ 2269 | mipLevel: 0, 2270 | arrayLayer: 0, 2271 | origin: [0, 0, 0], 2272 | }, desc); 2273 | REQUIRE(desc, 'GPUTextureCopyView ', 'texture', GPUTexture_JS); 2274 | desc.origin = make_GPUOrigin3D(desc.origin); 2275 | return desc; 2276 | } 2277 | 2278 | // - 2279 | 2280 | function make_GPUShaderModuleDescriptor(desc) { 2281 | REQUIRE(desc, 'GPUShaderModuleDescriptor', 'code'); 2282 | desc = Object.assign({ 2283 | }, desc); 2284 | return desc; 2285 | } 2286 | 2287 | class GPUShaderModule_JS { 2288 | constructor(device, desc) { 2289 | this.device = device; 2290 | if (!desc) 2291 | return; 2292 | desc = make_GPUShaderModuleDescriptor(desc); 2293 | this.desc = desc; 2294 | } 2295 | } 2296 | 2297 | // - 2298 | 2299 | class GPUQueue_JS { 2300 | constructor(device) { 2301 | this.device = device; 2302 | } 2303 | 2304 | submit(buffers) { 2305 | try { 2306 | VALIDATE(!this.device._is_lost, 'Device is lost.'); 2307 | buffers.forEach(cmd_buf => { 2308 | const cmds = cmd_buf.enc.cmds; 2309 | cmds.forEach(x => { 2310 | x(); 2311 | }) 2312 | }); 2313 | } catch (e) { this.device._catch(e); } 2314 | } 2315 | } 2316 | 2317 | // - 2318 | function make_GPUExtensions(dict) { 2319 | return Object.assign({ 2320 | anisotropicFiltering: false, 2321 | }, dict); 2322 | } 2323 | 2324 | function make_GPULimits(dict) { 2325 | return Object.assign({ 2326 | maxBindGroups: 4, 2327 | }, dict); 2328 | } 2329 | 2330 | function make_GPUDeviceDescriptor(desc) { 2331 | desc.extensions = make_GPUExtensions(desc.extensions); 2332 | desc.limits = make_GPULimits(desc.limits); 2333 | return desc; 2334 | } 2335 | 2336 | class GPUDeviceLostInfo_JS { 2337 | constructor(message) { 2338 | this._message = message.slice(); 2339 | } 2340 | 2341 | get message() { return this._message; } 2342 | } 2343 | 2344 | const FILTER_TYPE = { 2345 | 'out-of-memory': 'GPUOutOfMemoryError', 2346 | 'validation' : 'GPUValidationError', 2347 | }; 2348 | 2349 | if (window.GPUUncapturedErrorEvent === undefined) { 2350 | window.GPUUncapturedErrorEvent = class GPUUncapturedErrorEvent extends Event { 2351 | constructor(type_str, init_dict) { 2352 | super(type_str, init_dict); 2353 | ASSERT(init_dict.error, '`GPUUncapturedErrorEventInit.error` required.'); 2354 | this._error = init_dict.error; 2355 | } 2356 | 2357 | get error() { 2358 | return this._error; 2359 | } 2360 | 2361 | toString() { 2362 | return 'GPUUncapturedErrorEvent: ' + this.error.toString(); 2363 | } 2364 | }; 2365 | } 2366 | 2367 | class GPUDevice_JS extends EventTarget { 2368 | constructor(adapter, desc) { 2369 | super(); 2370 | this._adapter = adapter; 2371 | desc = make_GPUDeviceDescriptor(desc); 2372 | this.desc = desc; 2373 | 2374 | this._gl = null; 2375 | this._is_lost = false; 2376 | this._lost_promise = new Promise((yes, no) => { 2377 | this._resolve_lost = yes; 2378 | }); 2379 | 2380 | this._queue = new GPUQueue_JS(this); 2381 | this._error_scopes = []; 2382 | } 2383 | 2384 | get adapter() { return this._adapter; } 2385 | get extensions() { return this.gl_info.extensions; } 2386 | get limits() { return this.gl_info.limits; } 2387 | get lost() { return this._lost_promise; } 2388 | 2389 | _lose_gl() { 2390 | const gl = this.gl; 2391 | const ext = gl.getExtension('WEBGL_lose_context'); 2392 | ext.loseContext(); 2393 | ASSERT(gl.getError() == GL.CONTEXT_LOST_WEBGL, 'First CONTEXT_LOST_WEBGL.'); 2394 | ASSERT(!gl.getError(), 'Then NO_ERROR.'); 2395 | } 2396 | 2397 | lose() { 2398 | console.error('GPUDevice lost!'); 2399 | this.desc = null; 2400 | this._is_lost = true; 2401 | this._resolve_lost(); 2402 | } 2403 | 2404 | _ensure_gl(c) { 2405 | if (!this._gl) { 2406 | this._gl = (() => { 2407 | const adapter = this.adapter; 2408 | const gl = adapter.make_gl(c); 2409 | if (!gl) { 2410 | console.log('Failed to make_gl.'); 2411 | return null; 2412 | } 2413 | 2414 | const gl_info = new GlInfo(gl); 2415 | 2416 | if (!is_subset(gl_info.extensions, adapter.last_info.extensions)) { 2417 | console.log('`extensions` not a subset of adapters\'.'); 2418 | return null; 2419 | } 2420 | 2421 | if (!is_subset(gl_info.limits, adapter.last_info.limits)) { 2422 | console.log('`limits` not a subset of adapters\'.'); 2423 | return null; 2424 | } 2425 | return gl; 2426 | })(); 2427 | if (!this._gl) { 2428 | this.lose(); 2429 | return null; 2430 | } 2431 | this._gl.canvas.addEventListener('webglcontextlost', (e) => { 2432 | e.preventDefault(); 2433 | this.lose(); 2434 | }); 2435 | } 2436 | return this._gl; 2437 | } 2438 | 2439 | get gl() { 2440 | return this._ensure_gl(); 2441 | } 2442 | 2443 | // - 2444 | 2445 | createBuffer(desc) { 2446 | try { 2447 | VALIDATE(!this._is_lost, 'Device is lost.'); 2448 | REQUIRE_NON_NULL(desc, 'GPUBufferDescriptor'); 2449 | return new GPUBuffer_JS(this, desc, false); 2450 | } catch (e) { 2451 | this._catch(e); 2452 | return new GPUBuffer_JS(this, null); 2453 | } 2454 | } 2455 | createBufferMapped(desc) { 2456 | try { 2457 | VALIDATE(!this._is_lost, 'Device is lost.'); 2458 | REQUIRE_NON_NULL(desc, 'GPUBufferDescriptor'); 2459 | const buf = new GPUBuffer_JS(this, desc, true); 2460 | const init_map = buf._map_write(); 2461 | return [buf, init_map]; 2462 | } catch (e) { 2463 | this._catch(e); 2464 | return new GPUBuffer_JS(this, null); 2465 | } 2466 | } 2467 | createBufferMappedAsync(desc) { 2468 | try { 2469 | VALIDATE(!this._is_lost, 'Device is lost.'); 2470 | REQUIRE_NON_NULL(desc, 'GPUBufferDescriptor'); 2471 | const ret = this.createBufferMapped(desc); 2472 | return new Promise((good, bad) => { 2473 | good(ret); 2474 | }); 2475 | } catch (e) { 2476 | this._catch(e); 2477 | return new GPUBuffer_JS(this, null); 2478 | } 2479 | } 2480 | createTexture(desc) { 2481 | try { 2482 | VALIDATE(!this._is_lost, 'Device is lost.'); 2483 | REQUIRE_NON_NULL(desc, 'GPUTextureDescriptor'); 2484 | return new GPUTexture_JS(this, desc); 2485 | } catch (e) { 2486 | this._catch(e); 2487 | return new GPUTexture_JS(this, null); 2488 | } 2489 | } 2490 | createSampler(desc) { 2491 | try { 2492 | VALIDATE(!this._is_lost, 'Device is lost.'); 2493 | return new GPUSampler_JS(this, desc || {}); 2494 | } catch (e) { 2495 | this._catch(e); 2496 | return new GPUSampler_JS(this, null); 2497 | } 2498 | } 2499 | 2500 | createBindGroupLayout(desc) { 2501 | try { 2502 | VALIDATE(!this._is_lost, 'Device is lost.'); 2503 | REQUIRE_NON_NULL(desc, 'GPUBindGroupLayoutDescriptor'); 2504 | return new GPUBindGroupLayout_JS(this, desc); 2505 | } catch (e) { 2506 | this._catch(e); 2507 | return new GPUBindGroupLayout_JS(this, null); 2508 | } 2509 | } 2510 | createPipelineLayout(desc) { 2511 | try { 2512 | VALIDATE(!this._is_lost, 'Device is lost.'); 2513 | REQUIRE_NON_NULL(desc, 'GPUPipelineLayoutDescriptor'); 2514 | return new GPUPipelineLayout_JS(this, desc); 2515 | } catch (e) { 2516 | this._catch(e); 2517 | return new GPUPipelineLayout_JS(this, null); 2518 | } 2519 | } 2520 | createBindGroup(desc) { 2521 | try { 2522 | VALIDATE(!this._is_lost, 'Device is lost.'); 2523 | REQUIRE_NON_NULL(desc, 'GPUBindGroupDescriptor'); 2524 | return new GPUBindGroup_JS(this, desc); 2525 | } catch (e) { 2526 | this._catch(e); 2527 | return new GPUBindGroup_JS(this, null); 2528 | } 2529 | } 2530 | 2531 | createShaderModule(desc) { 2532 | try { 2533 | VALIDATE(!this._is_lost, 'Device is lost.'); 2534 | REQUIRE_NON_NULL(desc, 'GPUShaderModuleDescriptor'); 2535 | return new GPUShaderModule_JS(this, desc); 2536 | } catch (e) { 2537 | this._catch(e); 2538 | return new GPUShaderModule_JS(this, null); 2539 | } 2540 | } 2541 | createRenderPipeline(desc) { 2542 | try { 2543 | VALIDATE(!this._is_lost, 'Device is lost.'); 2544 | REQUIRE_NON_NULL(desc, 'GPURenderPipelineDescriptor'); 2545 | return new GPURenderPipeline_JS(this, desc); 2546 | } catch (e) { 2547 | this._catch(e); 2548 | return new GPURenderPipeline_JS(this, null); 2549 | } 2550 | } 2551 | 2552 | createCommandEncoder(desc) { 2553 | try { 2554 | VALIDATE(!this._is_lost, 'Device is lost.'); 2555 | REQUIRE_NON_NULL(desc, 'GPUCommandEncoderDescriptor'); 2556 | return new GPUCommandEncoder_JS(this, desc); 2557 | } catch (e) { 2558 | this._catch(e); 2559 | return new GPUCommandEncoder_JS(this, null); 2560 | } 2561 | } 2562 | 2563 | getQueue() { 2564 | return this._queue; 2565 | } 2566 | 2567 | // - 2568 | 2569 | pushErrorScope(filter) { 2570 | const new_scope = { 2571 | filter: FILTER_TYPE[filter], 2572 | error: null, 2573 | }; 2574 | this._error_scopes.unshift(new_scope); 2575 | } 2576 | 2577 | popErrorScope() { 2578 | return new Promise((good, bad) => { 2579 | if (this._is_lost) 2580 | return bad('Device lost.'); 2581 | if (!this._error_scopes.length) 2582 | return bad('Error scope stack is empty.'); 2583 | const popped = this._error_scopes.shift(); 2584 | if (!popped.error) 2585 | return bad('Top of stack has no error.'); 2586 | good(popped.error); 2587 | }); 2588 | } 2589 | 2590 | _catch(error) { 2591 | if (!IS_GPU_ERROR[error.name]) throw error; 2592 | 2593 | for (const scope of this._error_scopes) { 2594 | if (error.name != scope.filter) 2595 | continue; 2596 | 2597 | if (!scope.error) { // Only capture the first. 2598 | scope.error = error; 2599 | } 2600 | return; 2601 | } 2602 | 2603 | const dispatch = () => { 2604 | const event = new GPUUncapturedErrorEvent('uncapturederror', { 2605 | error: error, 2606 | }); 2607 | if (this.dispatchEvent(event)) { 2608 | console.error(error.toString()); 2609 | } 2610 | } 2611 | if (SYNC_ERROR_DISPATCH) { 2612 | dispatch(); 2613 | } else { 2614 | setZeroTimeout(dispatch); // Dispatch async 2615 | } 2616 | } 2617 | } 2618 | 2619 | 2620 | class GPUAdapter_JS { 2621 | constructor() {} 2622 | 2623 | get name() { return this.last_info.name; } 2624 | get extensions() { return this.last_info.extensions; } 2625 | 2626 | requestDevice(desc) { 2627 | return new Promise((yes, no) => { 2628 | const ret = new GPUDevice_JS(this, desc); 2629 | if (!is_subset(ret.desc.extensions, this.last_info.extensions)) 2630 | return no('`extensions` not a subset of adapters\'.'); 2631 | 2632 | if (!is_subset(ret.desc.limits, this.last_info.limits)) 2633 | return no('`limits` not a subset of adapters\'.'); 2634 | 2635 | yes(ret); 2636 | }); 2637 | } 2638 | 2639 | make_gl(for_canvas) { 2640 | let c = for_canvas; 2641 | if (!c) { 2642 | c = document.createElement('canvas'); 2643 | c.width = 1; 2644 | c.height = 1; 2645 | } 2646 | return ORIG_GET_CONTEXT.call(c, 'webgl2', { 2647 | antialias: false, 2648 | alpha: true, 2649 | depth: true, 2650 | stencil: true, 2651 | premultipliedAlpha: false, 2652 | powerPreference: this.desc.powerPreference, 2653 | }); 2654 | } 2655 | } 2656 | 2657 | class GPU_JS { 2658 | requestAdapter(desc) { 2659 | desc = Object.assign({}, desc); 2660 | if (!desc.powerPreference) { 2661 | desc.powerPreference = 'default'; // map to webgl 2662 | } 2663 | 2664 | return new Promise((yes, no) => { 2665 | const ret = new GPUAdapter_JS(); 2666 | ret.desc = desc; 2667 | const gl = ret.make_gl(); 2668 | if (!gl) 2669 | return no('WebGL 2 required.'); 2670 | 2671 | ret.last_info = new GlInfo(gl); 2672 | lose_gl(gl); 2673 | 2674 | yes(ret); 2675 | }); 2676 | } 2677 | } 2678 | 2679 | // - 2680 | 2681 | function make_GPUSwapChainDescriptor(desc) { 2682 | REQUIRE(desc, 'GPUSwapChainDescriptor', 'device', GPUDevice_JS); 2683 | REQUIRE(desc, 'GPUSwapChainDescriptor', 'format'); 2684 | desc = Object.assign({ 2685 | usage: GPUTextureUsage.OUTPUT_ATTACHMENT, 2686 | }, desc); 2687 | return desc; 2688 | } 2689 | 2690 | class GPUSwapChain_JS { 2691 | constructor(context, desc) { 2692 | this.context = context; 2693 | this.desc = desc; 2694 | } 2695 | 2696 | getCurrentTexture() { 2697 | if (!this.tex) { 2698 | if (!this.context) 2699 | throw new Error('Out-of-date swap chain'); 2700 | 2701 | const canvas = this.context.canvas; 2702 | const desc = { 2703 | size: [canvas.width, canvas.height, 1], 2704 | format: this.desc.format, 2705 | usage: this.desc.usage, 2706 | }; 2707 | let sc_if_fast = null; 2708 | if (canvas == this.desc.device.gl.canvas) { 2709 | sc_if_fast = this; 2710 | } 2711 | this.tex = new GPUTexture_JS(this.desc.device, desc, sc_if_fast); 2712 | } 2713 | return this.tex; 2714 | } 2715 | } 2716 | 2717 | // - 2718 | 2719 | class GPUCanvasContext_JS { 2720 | constructor(canvas) { 2721 | this.canvas = canvas; 2722 | this.gl = null; 2723 | this.swap_chain = null; 2724 | } 2725 | 2726 | getSwapChainPreferredFormat(device) { 2727 | return new Promise((yes, no) => { 2728 | return yes('rgba8unorm'); 2729 | }); 2730 | } 2731 | 2732 | configureSwapChain(desc) { 2733 | if (this.swap_chain) { 2734 | this.swap_chain.context = null; 2735 | this.swap_chain = null; 2736 | } 2737 | desc = make_GPUSwapChainDescriptor(desc); 2738 | 2739 | this.gl = desc.device._ensure_gl(this.canvas); 2740 | if (this.gl.canvas != this.canvas) { 2741 | console.log('Slowpath: configureSwapChain called after GL creation for GPUDevice.'); 2742 | this.gl = desc.device.adapter.make_gl(this.canvas); 2743 | } 2744 | if (!this.gl) 2745 | return null; 2746 | 2747 | this.swap_chain = new GPUSwapChain_JS(this, desc); 2748 | return this.swap_chain; 2749 | } 2750 | } 2751 | 2752 | HTMLCanvasElement.prototype.getContext = function(type) { 2753 | const type_gpu = (type == 'gpu'); 2754 | if (this._gpu_js) { 2755 | if (!type_gpu) return null; 2756 | return this._gpu_js; 2757 | } 2758 | 2759 | let ret = ORIG_GET_CONTEXT.apply(this, arguments); 2760 | if (!ret && type_gpu) { 2761 | ret = new GPUCanvasContext_JS(this); 2762 | this._gpu_js = ret; 2763 | } 2764 | return ret; 2765 | }; 2766 | 2767 | return new GPU_JS(); 2768 | })(); 2769 | 2770 | if (!navigator.gpu) { 2771 | navigator.gpu = navigator.gpu_js; 2772 | } 2773 | --------------------------------------------------------------------------------