onnxruntime

Форк
0
648 строк · 20.5 Кб
1
// Copyright (c) Microsoft Corporation. All rights reserved.
2
// Licensed under the MIT License.
3

4
import { env } from 'onnxruntime-common';
5

6
import * as DataEncoders from './texture-data-encoder';
7
import { DataEncoder, Encoder, EncoderUsage } from './texture-data-encoder';
8
import { repeatedTry } from './utils';
9

10
export interface FenceContext {
11
  query: WebGLSync | null;
12
  isFencePassed(): boolean;
13
}
14

15
type PollItem = {
16
  isDoneFn: () => boolean;
17
  resolveFn: () => void;
18
};
19

20
export function linearSearchLastTrue(arr: Array<() => boolean>): number {
21
  let i = 0;
22
  for (; i < arr.length; ++i) {
23
    const isDone = arr[i]();
24
    if (!isDone) {
25
      break;
26
    }
27
  }
28
  return i - 1;
29
}
30

31
/**
32
 * Abstraction and wrapper around WebGLRenderingContext and its operations
33
 */
34
export class WebGLContext {
35
  gl: WebGLRenderingContext;
36
  version: 1 | 2;
37

38
  private vertexbuffer: WebGLBuffer;
39
  private framebuffer: WebGLFramebuffer;
40

41
  // WebGL flags and vital parameters
42
  private isFloatTextureAttachableToFrameBuffer: boolean;
43
  isFloat32DownloadSupported: boolean;
44
  isRenderFloat32Supported: boolean;
45
  isBlendSupported: boolean;
46
  maxTextureSize: number;
47
  // private maxCombinedTextureImageUnits: number;
48
  private maxTextureImageUnits: number;
49
  // private maxCubeMapTextureSize: number;
50
  // private shadingLanguageVersion: string;
51
  // private webglVendor: string;
52
  // private webglVersion: string;
53

54
  // WebGL2 flags and vital parameters
55
  // private max3DTextureSize: number;
56
  // private maxArrayTextureLayers: number;
57
  // private maxColorAttachments: number;
58
  // private maxDrawBuffers: number;
59

60
  // WebGL extensions
61
  // eslint-disable-next-line camelcase
62
  textureFloatExtension: OES_texture_float | null;
63
  // eslint-disable-next-line camelcase
64
  textureHalfFloatExtension: OES_texture_half_float | null;
65

66
  // WebGL2 extensions
67
  colorBufferFloatExtension: unknown | null;
68
  // eslint-disable-next-line @typescript-eslint/naming-convention
69
  disjointTimerQueryWebgl2Extension: { TIME_ELAPSED_EXT: GLenum; GPU_DISJOINT_EXT: GLenum } | null;
70

71
  private disposed: boolean;
72
  private frameBufferBound = false;
73

74
  constructor(gl: WebGLRenderingContext, version: 1 | 2) {
75
    this.gl = gl;
76
    this.version = version;
77

78
    this.getExtensions();
79
    this.vertexbuffer = this.createVertexbuffer();
80
    this.framebuffer = this.createFramebuffer();
81
    this.queryVitalParameters();
82
  }
83

84
  allocateTexture(width: number, height: number, encoder: DataEncoder, data?: Encoder.DataArrayType): WebGLTexture {
85
    const gl = this.gl;
86
    // create the texture
87
    const texture = gl.createTexture();
88
    // bind the texture so the following methods effect this texture.
89
    gl.bindTexture(gl.TEXTURE_2D, texture);
90
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
91
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
92
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
93
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
94
    const buffer = data ? encoder.encode(data, width * height) : null;
95
    gl.texImage2D(
96
      gl.TEXTURE_2D,
97
      0, // Level of detail.
98
      encoder.internalFormat,
99
      width,
100
      height,
101
      0, // Always 0 in OpenGL ES.
102
      encoder.format,
103
      encoder.textureType,
104
      buffer,
105
    );
106
    this.checkError();
107
    return texture as WebGLTexture;
108
  }
109
  updateTexture(
110
    texture: WebGLTexture,
111
    width: number,
112
    height: number,
113
    encoder: DataEncoder,
114
    data: Encoder.DataArrayType,
115
  ): void {
116
    const gl = this.gl;
117
    gl.bindTexture(gl.TEXTURE_2D, texture);
118
    const buffer = encoder.encode(data, width * height);
119
    gl.texSubImage2D(
120
      gl.TEXTURE_2D,
121
      0, // level
122
      0, // xoffset
123
      0, // yoffset
124
      width,
125
      height,
126
      encoder.format,
127
      encoder.textureType,
128
      buffer,
129
    );
130
    this.checkError();
131
  }
132
  attachFramebuffer(texture: WebGLTexture, width: number, height: number): void {
133
    const gl = this.gl;
134
    // Make it the target for framebuffer operations - including rendering.
135
    gl.bindTexture(gl.TEXTURE_2D, texture);
136
    gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
137
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); // 0, we aren't using MIPMAPs
138
    this.checkError();
139
    gl.viewport(0, 0, width, height);
140
    gl.scissor(0, 0, width, height);
141
  }
142
  readTexture(
143
    texture: WebGLTexture,
144
    width: number,
145
    height: number,
146
    dataSize: number,
147
    dataType: Encoder.DataType,
148
    channels: number,
149
  ): Encoder.DataArrayType {
150
    const gl = this.gl;
151
    if (!channels) {
152
      channels = 1;
153
    }
154
    if (!this.frameBufferBound) {
155
      this.attachFramebuffer(texture, width, height);
156
    }
157
    const encoder = this.getEncoder(dataType, channels);
158
    const buffer = encoder.allocate(width * height);
159
    // bind texture to framebuffer
160
    gl.bindTexture(gl.TEXTURE_2D, texture);
161
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0); // 0, we aren't using MIPMAPs
162
    // TODO: Check if framebuffer is ready
163
    gl.readPixels(0, 0, width, height, gl.RGBA, encoder.textureType, buffer);
164
    this.checkError();
165
    // unbind FB
166
    return encoder.decode(buffer, dataSize);
167
  }
168

169
  isFramebufferReady(): boolean {
170
    // TODO: Implement logic to check if the framebuffer is ready
171
    return true;
172
  }
173
  getActiveTexture(): string {
174
    const gl = this.gl;
175
    const n = gl.getParameter(this.gl.ACTIVE_TEXTURE);
176
    return `TEXTURE${n - gl.TEXTURE0}`;
177
  }
178
  getTextureBinding(): WebGLTexture {
179
    return this.gl.getParameter(this.gl.TEXTURE_BINDING_2D);
180
  }
181
  getFramebufferBinding(): WebGLFramebuffer {
182
    return this.gl.getParameter(this.gl.FRAMEBUFFER_BINDING);
183
  }
184
  setVertexAttributes(positionHandle: number, textureCoordHandle: number): void {
185
    const gl = this.gl;
186
    gl.vertexAttribPointer(positionHandle, 3, gl.FLOAT, false, 20, 0);
187
    gl.enableVertexAttribArray(positionHandle);
188
    if (textureCoordHandle !== -1) {
189
      gl.vertexAttribPointer(textureCoordHandle, 2, gl.FLOAT, false, 20, 12);
190
      gl.enableVertexAttribArray(textureCoordHandle);
191
    }
192
    this.checkError();
193
  }
194
  createProgram(vertexShader: WebGLShader, fragShader: WebGLShader): WebGLProgram {
195
    const gl = this.gl;
196
    const program = gl.createProgram()!;
197

198
    // the program consists of our shaders
199
    gl.attachShader(program, vertexShader);
200
    gl.attachShader(program, fragShader);
201
    gl.linkProgram(program);
202
    return program;
203
  }
204
  compileShader(shaderSource: string, shaderType: number): WebGLShader {
205
    const gl = this.gl;
206
    const shader = gl.createShader(shaderType);
207
    if (!shader) {
208
      throw new Error(`createShader() returned null with type ${shaderType}`);
209
    }
210

211
    gl.shaderSource(shader, shaderSource);
212
    gl.compileShader(shader);
213
    if (gl.getShaderParameter(shader, gl.COMPILE_STATUS) === false) {
214
      throw new Error(`Failed to compile shader: ${gl.getShaderInfoLog(shader)}
215
Shader source:
216
${shaderSource}`);
217
    }
218
    return shader;
219
  }
220
  deleteShader(shader: WebGLShader): void {
221
    this.gl.deleteShader(shader);
222
  }
223
  bindTextureToUniform(texture: WebGLTexture, position: number, uniformHandle: WebGLUniformLocation): void {
224
    const gl = this.gl;
225
    gl.activeTexture(gl.TEXTURE0 + position);
226
    this.checkError();
227
    gl.bindTexture(gl.TEXTURE_2D, texture);
228
    this.checkError();
229
    gl.uniform1i(uniformHandle, position);
230
    this.checkError();
231
  }
232
  draw(): void {
233
    this.gl.drawArrays(this.gl.TRIANGLE_STRIP, 0, 4);
234
    this.checkError();
235
  }
236
  checkError(): void {
237
    if (env.debug) {
238
      const gl = this.gl;
239
      const error = gl.getError();
240
      let label = '';
241
      switch (error) {
242
        case gl.NO_ERROR:
243
          return;
244
        case gl.INVALID_ENUM:
245
          label = 'INVALID_ENUM';
246
          break;
247
        case gl.INVALID_VALUE:
248
          label = 'INVALID_VALUE';
249
          break;
250
        case gl.INVALID_OPERATION:
251
          label = 'INVALID_OPERATION';
252
          break;
253
        case gl.INVALID_FRAMEBUFFER_OPERATION:
254
          label = 'INVALID_FRAMEBUFFER_OPERATION';
255
          break;
256
        case gl.OUT_OF_MEMORY:
257
          label = 'OUT_OF_MEMORY';
258
          break;
259
        case gl.CONTEXT_LOST_WEBGL:
260
          label = 'CONTEXT_LOST_WEBGL';
261
          break;
262
        default:
263
          label = `Unknown WebGL Error: ${error.toString(16)}`;
264
      }
265
      throw new Error(label);
266
    }
267
  }
268
  deleteTexture(texture: WebGLTexture): void {
269
    this.gl.deleteTexture(texture);
270
  }
271
  deleteProgram(program: WebGLProgram): void {
272
    this.gl.deleteProgram(program);
273
  }
274
  getEncoder(dataType: Encoder.DataType, channels: number, usage: EncoderUsage = EncoderUsage.Default): DataEncoder {
275
    if (this.version === 2) {
276
      return new DataEncoders.RedFloat32DataEncoder(this.gl as WebGL2RenderingContext, channels);
277
    }
278

279
    switch (dataType) {
280
      case 'float':
281
        if (usage === EncoderUsage.UploadOnly || this.isRenderFloat32Supported) {
282
          return new DataEncoders.RGBAFloatDataEncoder(this.gl, channels);
283
        } else {
284
          return new DataEncoders.RGBAFloatDataEncoder(
285
            this.gl,
286
            channels,
287
            this.textureHalfFloatExtension!.HALF_FLOAT_OES,
288
          );
289
        }
290
      case 'int':
291
        throw new Error('not implemented');
292
      case 'byte':
293
        return new DataEncoders.Uint8DataEncoder(this.gl, channels);
294
      default:
295
        throw new Error(`Invalid dataType: ${dataType}`);
296
    }
297
  }
298
  clearActiveTextures(): void {
299
    const gl = this.gl;
300
    for (let unit = 0; unit < this.maxTextureImageUnits; ++unit) {
301
      gl.activeTexture(gl.TEXTURE0 + unit);
302
      gl.bindTexture(gl.TEXTURE_2D, null);
303
    }
304
  }
305
  dispose(): void {
306
    if (this.disposed) {
307
      return;
308
    }
309
    const gl = this.gl;
310
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
311
    gl.deleteFramebuffer(this.framebuffer);
312
    gl.bindBuffer(gl.ARRAY_BUFFER, null);
313
    gl.deleteBuffer(this.vertexbuffer);
314
    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
315
    gl.finish();
316
    this.disposed = true;
317
  }
318

319
  private createDefaultGeometry(): Float32Array {
320
    // Sets of x,y,z(=0),s,t coordinates.
321
    return new Float32Array([
322
      -1.0,
323
      1.0,
324
      0.0,
325
      0.0,
326
      1.0, // upper left
327
      -1.0,
328
      -1.0,
329
      0.0,
330
      0.0,
331
      0.0, // lower left
332
      1.0,
333
      1.0,
334
      0.0,
335
      1.0,
336
      1.0, // upper right
337
      1.0,
338
      -1.0,
339
      0.0,
340
      1.0,
341
      0.0, // lower right
342
    ]);
343
  }
344
  private createVertexbuffer(): WebGLBuffer {
345
    const gl = this.gl;
346
    const buffer = gl.createBuffer();
347
    if (!buffer) {
348
      throw new Error('createBuffer() returned null');
349
    }
350
    const geometry = this.createDefaultGeometry();
351
    gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
352
    gl.bufferData(gl.ARRAY_BUFFER, geometry, gl.STATIC_DRAW);
353
    this.checkError();
354
    return buffer;
355
  }
356
  private createFramebuffer(): WebGLFramebuffer {
357
    const fb = this.gl.createFramebuffer();
358
    if (!fb) {
359
      throw new Error('createFramebuffer returned null');
360
    }
361
    return fb;
362
  }
363

364
  private queryVitalParameters(): void {
365
    const gl = this.gl;
366

367
    this.isFloatTextureAttachableToFrameBuffer = this.checkFloatTextureAttachableToFrameBuffer();
368
    this.isRenderFloat32Supported = this.checkRenderFloat32();
369
    this.isFloat32DownloadSupported = this.checkFloat32Download();
370

371
    if (this.version === 1 && !this.textureHalfFloatExtension && !this.isRenderFloat32Supported) {
372
      throw new Error('both float32 and float16 TextureType are not supported');
373
    }
374

375
    this.isBlendSupported = !this.isRenderFloat32Supported || this.checkFloat32Blend();
376

377
    // this.maxCombinedTextureImageUnits = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);
378
    this.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
379
    this.maxTextureImageUnits = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
380
    // this.maxCubeMapTextureSize = gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE);
381
    // this.shadingLanguageVersion = gl.getParameter(gl.SHADING_LANGUAGE_VERSION);
382
    // this.webglVendor = gl.getParameter(gl.VENDOR);
383
    // this.webglVersion = gl.getParameter(gl.VERSION);
384

385
    if (this.version === 2) {
386
      // this.max3DTextureSize = gl.getParameter(WebGL2RenderingContext.MAX_3D_TEXTURE_SIZE);
387
      // this.maxArrayTextureLayers = gl.getParameter(WebGL2RenderingContext.MAX_ARRAY_TEXTURE_LAYERS);
388
      // this.maxColorAttachments = gl.getParameter(WebGL2RenderingContext.MAX_COLOR_ATTACHMENTS);
389
      // this.maxDrawBuffers = gl.getParameter(WebGL2RenderingContext.MAX_DRAW_BUFFERS);
390
    }
391
  }
392
  private getExtensions(): void {
393
    if (this.version === 2) {
394
      this.colorBufferFloatExtension = this.gl.getExtension('EXT_color_buffer_float');
395
      this.disjointTimerQueryWebgl2Extension = this.gl.getExtension('EXT_disjoint_timer_query_webgl2');
396
    } else {
397
      this.textureFloatExtension = this.gl.getExtension('OES_texture_float');
398
      this.textureHalfFloatExtension = this.gl.getExtension('OES_texture_half_float');
399
    }
400
  }
401

402
  private checkFloatTextureAttachableToFrameBuffer(): boolean {
403
    // test whether Float32 texture is supported:
404
    // STEP.1 create a float texture
405
    const gl = this.gl;
406
    const texture = gl.createTexture();
407
    gl.bindTexture(gl.TEXTURE_2D, texture);
408
    // eslint-disable-next-line @typescript-eslint/naming-convention
409
    const internalFormat = this.version === 2 ? (gl as unknown as { RGBA32F: number }).RGBA32F : gl.RGBA;
410
    gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, 1, 1, 0, gl.RGBA, gl.FLOAT, null);
411
    // STEP.2 bind a frame buffer
412
    const frameBuffer = gl.createFramebuffer();
413
    gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
414
    // STEP.3 attach texture to framebuffer
415
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
416
    // STEP.4 test whether framebuffer is complete
417
    const isComplete = gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE;
418
    gl.bindTexture(gl.TEXTURE_2D, null);
419
    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
420
    gl.deleteTexture(texture);
421
    gl.deleteFramebuffer(frameBuffer);
422
    return isComplete;
423
  }
424

425
  private checkRenderFloat32(): boolean {
426
    if (this.version === 2) {
427
      if (!this.colorBufferFloatExtension) {
428
        return false;
429
      }
430
    } else {
431
      if (!this.textureFloatExtension) {
432
        return false;
433
      }
434
    }
435
    return this.isFloatTextureAttachableToFrameBuffer;
436
  }
437

438
  private checkFloat32Download(): boolean {
439
    if (this.version === 2) {
440
      if (!this.colorBufferFloatExtension) {
441
        return false;
442
      }
443
    } else {
444
      if (!this.textureFloatExtension) {
445
        return false;
446
      }
447
      if (!this.gl.getExtension('WEBGL_color_buffer_float')) {
448
        return false;
449
      }
450
    }
451
    return this.isFloatTextureAttachableToFrameBuffer;
452
  }
453

454
  /**
455
   * Check whether GL_BLEND is supported
456
   */
457
  private checkFloat32Blend(): boolean {
458
    // it looks like currently (2019-05-08) there is no easy way to detect whether BLEND is supported
459
    // https://github.com/microsoft/onnxjs/issues/145
460

461
    const gl = this.gl;
462

463
    let texture: WebGLTexture | null | undefined;
464
    let frameBuffer: WebGLFramebuffer | null | undefined;
465
    let vertexShader: WebGLShader | null | undefined;
466
    let fragmentShader: WebGLShader | null | undefined;
467
    let program: WebGLProgram | null | undefined;
468

469
    try {
470
      texture = gl.createTexture();
471
      frameBuffer = gl.createFramebuffer();
472
      gl.bindTexture(gl.TEXTURE_2D, texture);
473

474
      // eslint-disable-next-line @typescript-eslint/naming-convention
475
      const internalFormat = this.version === 2 ? (gl as unknown as { RGBA32F: number }).RGBA32F : gl.RGBA;
476
      gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, 1, 1, 0, gl.RGBA, gl.FLOAT, null);
477

478
      gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
479
      gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
480

481
      gl.enable(gl.BLEND);
482

483
      vertexShader = gl.createShader(gl.VERTEX_SHADER);
484
      if (!vertexShader) {
485
        return false;
486
      }
487
      gl.shaderSource(vertexShader, 'void main(){}');
488
      gl.compileShader(vertexShader);
489

490
      fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
491
      if (!fragmentShader) {
492
        return false;
493
      }
494
      gl.shaderSource(fragmentShader, 'precision highp float;void main(){gl_FragColor=vec4(0.5);}');
495
      gl.compileShader(fragmentShader);
496

497
      program = gl.createProgram();
498
      if (!program) {
499
        return false;
500
      }
501
      gl.attachShader(program, vertexShader);
502
      gl.attachShader(program, fragmentShader);
503
      gl.linkProgram(program);
504
      gl.useProgram(program);
505

506
      gl.drawArrays(gl.POINTS, 0, 1);
507
      return gl.getError() === gl.NO_ERROR;
508
    } finally {
509
      gl.disable(gl.BLEND);
510

511
      if (program) {
512
        gl.deleteProgram(program);
513
      }
514
      if (vertexShader) {
515
        gl.deleteShader(vertexShader);
516
      }
517
      if (fragmentShader) {
518
        gl.deleteShader(fragmentShader);
519
      }
520
      if (frameBuffer) {
521
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
522
        gl.deleteFramebuffer(frameBuffer);
523
      }
524
      if (texture) {
525
        gl.bindTexture(gl.TEXTURE_2D, null);
526
        gl.deleteTexture(texture);
527
      }
528
    }
529
  }
530

531
  beginTimer(): WebGLQuery {
532
    if (this.version === 2 && this.disjointTimerQueryWebgl2Extension) {
533
      const gl2 = this.gl as WebGL2RenderingContext;
534
      const ext = this.disjointTimerQueryWebgl2Extension;
535

536
      const query = gl2.createQuery() as WebGLQuery;
537
      gl2.beginQuery(ext.TIME_ELAPSED_EXT, query);
538
      return query;
539
    } else {
540
      // TODO: add webgl 1 handling.
541
      throw new Error('WebGL1 profiling currently not supported.');
542
    }
543
  }
544

545
  endTimer() {
546
    if (this.version === 2 && this.disjointTimerQueryWebgl2Extension) {
547
      const gl2 = this.gl as WebGL2RenderingContext;
548
      const ext = this.disjointTimerQueryWebgl2Extension;
549
      gl2.endQuery(ext.TIME_ELAPSED_EXT);
550
      return;
551
    } else {
552
      // TODO: add webgl 1 handling.
553
      throw new Error('WebGL1 profiling currently not supported');
554
    }
555
  }
556

557
  isTimerResultAvailable(query: WebGLQuery): boolean {
558
    let available = false,
559
      disjoint = false;
560
    if (this.version === 2 && this.disjointTimerQueryWebgl2Extension) {
561
      const gl2 = this.gl as WebGL2RenderingContext;
562
      const ext = this.disjointTimerQueryWebgl2Extension;
563

564
      available = gl2.getQueryParameter(query, gl2.QUERY_RESULT_AVAILABLE);
565
      disjoint = gl2.getParameter(ext.GPU_DISJOINT_EXT);
566
    } else {
567
      // TODO: add webgl 1 handling.
568
      throw new Error('WebGL1 profiling currently not supported');
569
    }
570

571
    return available && !disjoint;
572
  }
573

574
  getTimerResult(query: WebGLQuery): number {
575
    let timeElapsed = 0;
576
    if (this.version === 2) {
577
      const gl2 = this.gl as WebGL2RenderingContext;
578
      timeElapsed = gl2.getQueryParameter(query, gl2.QUERY_RESULT);
579
      gl2.deleteQuery(query);
580
    } else {
581
      // TODO: add webgl 1 handling.
582
      throw new Error('WebGL1 profiling currently not supported');
583
    }
584
    // return miliseconds
585
    return timeElapsed / 1000000;
586
  }
587

588
  async waitForQueryAndGetTime(query: WebGLQuery): Promise<number> {
589
    await repeatedTry(() => this.isTimerResultAvailable(query));
590
    return this.getTimerResult(query);
591
  }
592

593
  public async createAndWaitForFence(): Promise<void> {
594
    const fenceContext = this.createFence(this.gl);
595
    return this.pollFence(fenceContext);
596
  }
597

598
  private createFence(gl: WebGLRenderingContext): FenceContext {
599
    let isFencePassed: () => boolean;
600
    const gl2 = gl as WebGL2RenderingContext;
601
    const query = gl2.fenceSync(gl2.SYNC_GPU_COMMANDS_COMPLETE, 0);
602
    gl.flush();
603
    if (query === null) {
604
      isFencePassed = () => true;
605
    } else {
606
      isFencePassed = () => {
607
        const status = gl2.clientWaitSync(query, 0, 0);
608
        return status === gl2.ALREADY_SIGNALED || status === gl2.CONDITION_SATISFIED;
609
      };
610
    }
611
    return { query, isFencePassed };
612
  }
613

614
  async pollFence(fenceContext: FenceContext) {
615
    return new Promise<void>((resolve) => {
616
      void this.addItemToPoll(
617
        () => fenceContext.isFencePassed(),
618
        () => resolve(),
619
      );
620
    });
621
  }
622

623
  private itemsToPoll: PollItem[] = [];
624

625
  pollItems(): void {
626
    // Find the last query that has finished.
627
    const index = linearSearchLastTrue(this.itemsToPoll.map((x) => x.isDoneFn));
628
    for (let i = 0; i <= index; ++i) {
629
      const { resolveFn } = this.itemsToPoll[i];
630
      resolveFn();
631
    }
632
    this.itemsToPoll = this.itemsToPoll.slice(index + 1);
633
  }
634

635
  private async addItemToPoll(isDoneFn: () => boolean, resolveFn: () => void) {
636
    this.itemsToPoll.push({ isDoneFn, resolveFn });
637
    if (this.itemsToPoll.length > 1) {
638
      // We already have a running loop that polls.
639
      return;
640
    }
641
    // Start a new loop that polls.
642
    await repeatedTry(() => {
643
      this.pollItems();
644
      // End the loop if no more items to poll.
645
      return this.itemsToPoll.length === 0;
646
    });
647
  }
648
}
649

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.