<commit_before>/**
 * Copyright 2020 <NAME> <<EMAIL>>
 */
import { mat4, vec3 } from "gl-matrix";

import { CharCellGrid, FLAG_MASK_LIGATURE, FLAG_MASK_WIDTH, FLAG_WIDTH_SHIFT, FLAG_MASK_EXTRA_FONT, STYLE_MASK_CURSOR, STYLE_MASK_INVISIBLE, STYLE_MASK_FAINT } from "extraterm-char-cell-grid";
import { log, Logger, getLogger } from "extraterm-logging";
import { TextureFontAtlas } from "./font_atlas/TextureFontAtlas";
import { MonospaceFontMetrics } from "./font_metrics/MonospaceFontMetrics";
import { normalizedCellIterator } from "./NormalizedCellIterator";


const CANVAS_SIZE_STEP = 512;

export class WebGLRenderer {
  private _log: Logger = null;

  private _fontAtlas: TextureFontAtlas = null;
  private _metrics: MonospaceFontMetrics = null;
  private _canvas: HTMLCanvasElement = null;
  private _glContext: WebGL2RenderingContext = null;
  private _triangleIndexBuffer: WebGLBuffer = null;
  private _textureCoordBuffer: WebGLBuffer = null;
  private _vertexPositionBuffer: WebGLBuffer = null;
  private _texture: WebGLTexture;
  private _shaderProgram: WebGLProgram;
  private _vertexPositionAttrib: number;
  private _textureCoordAttrib: number;
  private _projectionMatrixLocation: WebGLUniformLocation;
  private _modelViewMatrixLocation: WebGLUniformLocation;
  private _uSamplerLocation: WebGLUniformLocation;
  private _projectionMatrix: mat4;
  private _modelViewMatrix: mat4;

  // Vertex shader program
  private _vertexShaderSource = `#version 300 es
    in vec4 aVertexPosition;
    in vec2 aTextureCoord;

    in vec2 aPos;
    in vec2 tPos;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    out highp vec2 vTextureCoord;

    void main(void) {
      gl_Position = uProjectionMatrix * uModelViewMatrix * (aVertexPosition + vec4(aPos, 0, 0));
      vTextureCoord = aTextureCoord + tPos;
    }
  `;

  // Fragment shader program
  private _fragmentShaderSource = `#version 300 es
    in highp vec2 vTextureCoord;

    uniform sampler2D uSampler;

    out highp vec4 fragColor;

    void main(void) {
      fragColor = texture(uSampler, vTextureCoord);
    }
  `;

  constructor(fontAtlas: TextureFontAtlas) {
    this._log = getLogger("WebGLRenderer", this);
    this._fontAtlas = fontAtlas;
    this._metrics = this._fontAtlas.getMetrics();
  }

  init(): boolean {
    this._canvas = document.createElement("canvas");
    document.body.appendChild(this._canvas);

    const gl = this._canvas.getContext("webgl2");
    this._glContext = gl;

    // If we don't have a GL context, give up now
    if (gl == null) {
      this._log.warn("Unable to initialize WebGL. Your browser or machine may not support it.");
      return false;
    }

    this._shaderProgram = this._initShaderProgram(gl, this._vertexShaderSource,
      this._fragmentShaderSource);
    this._vertexPositionAttrib = gl.getAttribLocation(this._shaderProgram, "aVertexPosition");
    this._textureCoordAttrib = gl.getAttribLocation(this._shaderProgram, "aTextureCoord");
    this._projectionMatrixLocation = gl.getUniformLocation(this._shaderProgram, "uProjectionMatrix");
    this._modelViewMatrixLocation = gl.getUniformLocation(this._shaderProgram, "uModelViewMatrix");
    this._uSamplerLocation = gl.getUniformLocation(this._shaderProgram, "uSampler");

    this._vertexPositionBuffer = gl.createBuffer();
    this._textureCoordBuffer = gl.createBuffer();
    this._triangleIndexBuffer = gl.createBuffer();

    gl.clearColor(0.0, 0.0, 0.0, 1.0);  // Clear to black, fully opaque
    gl.disable(gl.DEPTH_TEST);
    gl.clear(gl.COLOR_BUFFER_BIT);

    gl.useProgram(this._shaderProgram);
    gl.uniform1i(this._uSamplerLocation, 0);

    this._setupProjections();
    this._resizeCanvas(1, 1);

    this._texture = gl.createTexture();

    return true;
  }

  // Initialize a shader program, so WebGL knows how to draw our data
  private _initShaderProgram(gl: WebGLRenderingContext, vsSource: string, fsSource: string): WebGLProgram {
    const vertexShader = this._loadShader(gl, gl.VERTEX_SHADER, "VERTEX_SHADER", vsSource);
    const fragmentShader = this._loadShader(gl, gl.FRAGMENT_SHADER, "FRAGMENT_SHADER", fsSource);

    const shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, vertexShader);
    gl.attachShader(shaderProgram, fragmentShader);
    gl.linkProgram(shaderProgram);

    if ( ! gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
      this._log.warn(`Unable to link the shader program: ${gl.getProgramInfoLog(shaderProgram)}`);
      return null;
    }

    return shaderProgram;
  }

  private _setupProjections(): void {
    const canvasWidth = this._canvas.width;
    const canvasHeight = this._canvas.height;

    const gl = this._glContext;
    gl.useProgram(this._shaderProgram);

    gl.viewport(0, 0, canvasWidth, canvasHeight);

    const projectionMatrix = mat4.create();
    mat4.ortho(projectionMatrix, 0, canvasWidth, 0, canvasHeight, 0, 100);

    // Flip the image vertically so that we can keep our texture coords with 0,0
    // being top left and create y value going down the screen.
    const scaleVector = vec3.create();
    vec3.set(scaleVector, 1, -1, 1);
    mat4.scale(projectionMatrix, projectionMatrix, scaleVector);
    this._projectionMatrix = projectionMatrix;

    const modelViewMatrix = mat4.create();
    mat4.translate(modelViewMatrix, modelViewMatrix, [0, -canvasHeight, -1.0]);
    this._modelViewMatrix = modelViewMatrix;

    gl.uniformMatrix4fv(this._projectionMatrixLocation, false, this._projectionMatrix);
    gl.uniformMatrix4fv(this._modelViewMatrixLocation, false, this._modelViewMatrix);
  }

  private _resizeCanvas(minWidth: number, minHeight: number): void {
    let changed = false;
    if (this._canvas.width < minWidth) {
      this._canvas.width = (Math.floor(minWidth / CANVAS_SIZE_STEP) + 1) * CANVAS_SIZE_STEP;
      changed = true;
    }
    if (this._canvas.height < minHeight) {
      this._canvas.height = (Math.floor(minHeight / CANVAS_SIZE_STEP) + 1) * CANVAS_SIZE_STEP;
      changed = true;
    }
    if ( ! changed) {
      return;
    }

    this._setupProjections();
  }

  // creates a shader of the given type, uploads the source and
  // compiles it.
  //
  private _loadShader(gl: WebGLRenderingContext, type: number, shaderName: string, source: string): WebGLShader {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    if ( ! gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
      this._log.warn(`An error occurred compiling the shader ${shaderName}: ${gl.getShaderInfoLog(shader)}`);
      gl.deleteShader(shader);
      return null;
    }

    return shader;
  }

  private _initBuffers(gl: WebGLRenderingContext, atlas: TextureFontAtlas): void {
    gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexPositionBuffer);
    const vertexPositions = this._templateVertexPositions();
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexPositions), gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, this._textureCoordBuffer);
    const textureCoordinates = this._templateTexturePositions(atlas);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), gl.STATIC_DRAW);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._triangleIndexBuffer);
    const indices = this._templateTriangleIndexes();
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
  }

  private _templateVertexPositions(): number[] {
    const result: number[] = [];

    const boxScale = 1; // 1.0 is normal, 0.9 is useful for debugging to get a grid effect.
    const x = 0;
    const x2 = boxScale * this._metrics.widthPx;
    const y = 0;
    const y2 = boxScale * this._metrics.heightPx;

    result.push(x);
    result.push(y);
    result.push(1.0); // Z

    result.push(x);
    result.push(y2);
    result.push(1.0); // Z

    result.push(x2);
    result.push(y2);
    result.push(1.0); // Z

    result.push(x2);
    result.push(y);
    result.push(1.0); // Z
    return result;
  }

  private _templateTriangleIndexes(): number [] {
    return [0, 1, 2, 0, 2, 3];
  }

  private _templateTexturePositions(atlas: TextureFontAtlas): number[] {
    const result: number[] = [];
    const xPixels = 0;
    const yPixels = 0;
    const x2Pixels = atlas.getTextureCellWidth();
    const y2Pixels = atlas.getTextureCellHeight();

    result.push(xPixels);
    result.push(yPixels);

    result.push(xPixels);
    result.push(y2Pixels);

    result.push(x2Pixels);
    result.push(y2Pixels);

    result.push(x2Pixels);
    result.push(yPixels);

    return result;
  }


  private _gridTexturePositions(cellGrid: CharCellGrid, atlas: TextureFontAtlas): number[] {
    const result: number[] = [];
    const textureCellWidth = atlas.getTextureCellWidth();
    for (let j=0; j<cellGrid.height; j++) {
      for (const normalizedCell of normalizedCellIterator(cellGrid, j)) {
        const codePoint = normalizedCell.codePoint;
        const fontIndex = normalizedCell.extraFontFlag ? 1 : 0;
        const x = normalizedCell.x;
        const coord = atlas.loadCodePoint(codePoint, cellGrid.getStyle(x, j), fontIndex, cellGrid.getFgRGBA(x, j),
          cellGrid.getBgRGBA(x, j));
        result.push(coord.textureXpx + normalizedCell.segment * textureCellWidth);
        result.push(coord.textureYpx);
      }
    }
    return result;
  }

  // Initialize a texture and load an image.
  // When the image finished loading copy it into the texture.
  //
  private _loadAtlasTexture(gl: WebGLRenderingContext, atlasCanvas: HTMLCanvasElement): WebGLTexture {
    const level = 0;
    const internalFormat = gl.RGBA;
    const srcFormat = gl.RGBA;
    const srcType = gl.UNSIGNED_BYTE;

    gl.bindTexture(gl.TEXTURE_2D, this._texture);
    gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, srcFormat, srcType, atlasCanvas);

    // WebGL1 has different requirements for power of 2 images
    // vs non power of 2 images so check if the image is a
    // power of 2 in both dimensions.
    if (isPowerOf2(atlasCanvas.width) && isPowerOf2(atlasCanvas.height)) {
      // Yes, it's a power of 2. Generate mips.
      gl.generateMipmap(gl.TEXTURE_2D);
    } else {
      // No, it's not a power of 2. Turn off mips and set
      // wrapping to clamp to edge
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    }

    return this._texture;
  }

  render(destinationContext: CanvasRenderingContext2D, cellGrid: CharCellGrid, firstRow: number,
      rowCount: number): void {

    const rectWidth = this._metrics.widthPx * cellGrid.width;
    const rectHeight = this._metrics.heightPx * cellGrid.height;
    this._resizeCanvas(rectWidth, rectHeight);

    this._initBuffers(this._glContext, this._fontAtlas);

    {
      const numComponents = 2;
      const type = this._glContext.FLOAT;
      const normalize = false;
      const stride = 0;
      const offset = 0;

      const tPosAttrib = this._glContext.getAttribLocation(this._shaderProgram, "tPos");
      const tPosBuffer = this._glContext.createBuffer();
      this._glContext.bindBuffer(this._glContext.ARRAY_BUFFER, tPosBuffer);
<commit_msg>[WebGL Renderer] Set up some buffers at `init()` time not `render()`<commit_after>/**
 * Copyright 2020 <NAME> <<EMAIL>>
 */
import { mat4, vec3 } from "gl-matrix";

import { CharCellGrid, FLAG_MASK_LIGATURE, FLAG_MASK_WIDTH, FLAG_WIDTH_SHIFT, FLAG_MASK_EXTRA_FONT, STYLE_MASK_CURSOR, STYLE_MASK_INVISIBLE, STYLE_MASK_FAINT } from "extraterm-char-cell-grid";
import { log, Logger, getLogger } from "extraterm-logging";
import { TextureFontAtlas } from "./font_atlas/TextureFontAtlas";
import { MonospaceFontMetrics } from "./font_metrics/MonospaceFontMetrics";
import { normalizedCellIterator } from "./NormalizedCellIterator";


const CANVAS_SIZE_STEP = 512;

export class WebGLRenderer {
  private _log: Logger = null;

  private _fontAtlas: TextureFontAtlas = null;
  private _metrics: MonospaceFontMetrics = null;
  private _canvas: HTMLCanvasElement = null;
  private _glContext: WebGL2RenderingContext = null;
  private _triangleIndexBuffer: WebGLBuffer = null;
  private _textureCoordBuffer: WebGLBuffer = null;
  private _vertexPositionBuffer: WebGLBuffer = null;
  private _texture: WebGLTexture;
  private _shaderProgram: WebGLProgram;
  private _vertexPositionAttrib: number;
  private _textureCoordAttrib: number;
  private _projectionMatrixLocation: WebGLUniformLocation;
  private _modelViewMatrixLocation: WebGLUniformLocation;
  private _uSamplerLocation: WebGLUniformLocation;
  private _projectionMatrix: mat4;
  private _modelViewMatrix: mat4;

  // Vertex shader program
  private _vertexShaderSource = `#version 300 es
    in vec4 aVertexPosition;
    in vec2 aTextureCoord;

    in vec2 aPos;
    in vec2 tPos;

    uniform mat4 uModelViewMatrix;
    uniform mat4 uProjectionMatrix;

    out highp vec2 vTextureCoord;

    void main(void) {
      gl_Position = uProjectionMatrix * uModelViewMatrix * (aVertexPosition + vec4(aPos, 0, 0));
      vTextureCoord = aTextureCoord + tPos;
    }
  `;

  // Fragment shader program
  private _fragmentShaderSource = `#version 300 es
    in highp vec2 vTextureCoord;

    uniform sampler2D uSampler;

    out highp vec4 fragColor;

    void main(void) {
      fragColor = texture(uSampler, vTextureCoord);
    }
  `;

  constructor(fontAtlas: TextureFontAtlas) {
    this._log = getLogger("WebGLRenderer", this);
    this._fontAtlas = fontAtlas;
    this._metrics = this._fontAtlas.getMetrics();
  }

  init(): boolean {
    this._canvas = document.createElement("canvas");
    document.body.appendChild(this._canvas);

    const gl = this._canvas.getContext("webgl2");
    this._glContext = gl;

    // If we don't have a GL context, give up now
    if (gl == null) {
      this._log.warn("Unable to initialize WebGL. Your browser or machine may not support it.");
      return false;
    }

    this._shaderProgram = this._initShaderProgram(gl, this._vertexShaderSource,
      this._fragmentShaderSource);
    this._vertexPositionAttrib = gl.getAttribLocation(this._shaderProgram, "aVertexPosition");
    this._textureCoordAttrib = gl.getAttribLocation(this._shaderProgram, "aTextureCoord");
    this._projectionMatrixLocation = gl.getUniformLocation(this._shaderProgram, "uProjectionMatrix");
    this._modelViewMatrixLocation = gl.getUniformLocation(this._shaderProgram, "uModelViewMatrix");
    this._uSamplerLocation = gl.getUniformLocation(this._shaderProgram, "uSampler");

    this._vertexPositionBuffer = gl.createBuffer();
    this._textureCoordBuffer = gl.createBuffer();
    this._triangleIndexBuffer = gl.createBuffer();

    gl.clearColor(0.0, 0.0, 0.0, 1.0);  // Clear to black, fully opaque
    gl.disable(gl.DEPTH_TEST);
    gl.clear(gl.COLOR_BUFFER_BIT);

    gl.useProgram(this._shaderProgram);
    gl.uniform1i(this._uSamplerLocation, 0);

    this._setupProjections();
    this._initBuffers(this._glContext, this._fontAtlas);
    this._resizeCanvas(1, 1);

    this._texture = gl.createTexture();

    return true;
  }

  // Initialize a shader program, so WebGL knows how to draw our data
  private _initShaderProgram(gl: WebGLRenderingContext, vsSource: string, fsSource: string): WebGLProgram {
    const vertexShader = this._loadShader(gl, gl.VERTEX_SHADER, "VERTEX_SHADER", vsSource);
    const fragmentShader = this._loadShader(gl, gl.FRAGMENT_SHADER, "FRAGMENT_SHADER", fsSource);

    const shaderProgram = gl.createProgram();
    gl.attachShader(shaderProgram, vertexShader);
    gl.attachShader(shaderProgram, fragmentShader);
    gl.linkProgram(shaderProgram);

    if ( ! gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
      this._log.warn(`Unable to link the shader program: ${gl.getProgramInfoLog(shaderProgram)}`);
      return null;
    }

    return shaderProgram;
  }

  private _setupProjections(): void {
    const canvasWidth = this._canvas.width;
    const canvasHeight = this._canvas.height;

    const gl = this._glContext;
    gl.useProgram(this._shaderProgram);

    gl.viewport(0, 0, canvasWidth, canvasHeight);

    const projectionMatrix = mat4.create();
    mat4.ortho(projectionMatrix, 0, canvasWidth, 0, canvasHeight, 0, 100);

    // Flip the image vertically so that we can keep our texture coords with 0,0
    // being top left and create y value going down the screen.
    const scaleVector = vec3.create();
    vec3.set(scaleVector, 1, -1, 1);
    mat4.scale(projectionMatrix, projectionMatrix, scaleVector);
    this._projectionMatrix = projectionMatrix;

    const modelViewMatrix = mat4.create();
    mat4.translate(modelViewMatrix, modelViewMatrix, [0, -canvasHeight, -1.0]);
    this._modelViewMatrix = modelViewMatrix;

    gl.uniformMatrix4fv(this._projectionMatrixLocation, false, this._projectionMatrix);
    gl.uniformMatrix4fv(this._modelViewMatrixLocation, false, this._modelViewMatrix);
  }

  private _resizeCanvas(minWidth: number, minHeight: number): void {
    let changed = false;
    if (this._canvas.width < minWidth) {
      this._canvas.width = (Math.floor(minWidth / CANVAS_SIZE_STEP) + 1) * CANVAS_SIZE_STEP;
      changed = true;
    }
    if (this._canvas.height < minHeight) {
      this._canvas.height = (Math.floor(minHeight / CANVAS_SIZE_STEP) + 1) * CANVAS_SIZE_STEP;
      changed = true;
    }
    if ( ! changed) {
      return;
    }

    this._setupProjections();
  }

  // creates a shader of the given type, uploads the source and
  // compiles it.
  //
  private _loadShader(gl: WebGLRenderingContext, type: number, shaderName: string, source: string): WebGLShader {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source);
    gl.compileShader(shader);
    if ( ! gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
      this._log.warn(`An error occurred compiling the shader ${shaderName}: ${gl.getShaderInfoLog(shader)}`);
      gl.deleteShader(shader);
      return null;
    }

    return shader;
  }

  private _initBuffers(gl: WebGLRenderingContext, atlas: TextureFontAtlas): void {
    gl.bindBuffer(gl.ARRAY_BUFFER, this._vertexPositionBuffer);
    const vertexPositions = this._templateVertexPositions();
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexPositions), gl.STATIC_DRAW);

    gl.bindBuffer(gl.ARRAY_BUFFER, this._textureCoordBuffer);
    const textureCoordinates = this._templateTexturePositions(atlas);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(textureCoordinates), gl.STATIC_DRAW);

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this._triangleIndexBuffer);
    const indices = this._templateTriangleIndexes();
    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
  }

  private _templateVertexPositions(): number[] {
    const result: number[] = [];

    const boxScale = 1; // 1.0 is normal, 0.9 is useful for debugging to get a grid effect.
    const x = 0;
    const x2 = boxScale * this._metrics.widthPx;
    const y = 0;
    const y2 = boxScale * this._metrics.heightPx;

    result.push(x);
    result.push(y);
    result.push(1.0); // Z

    result.push(x);
    result.push(y2);
    result.push(1.0); // Z

    result.push(x2);
    result.push(y2);
    result.push(1.0); // Z

    result.push(x2);
    result.push(y);
    result.push(1.0); // Z
    return result;
  }

  private _templateTriangleIndexes(): number [] {
    return [0, 1, 2, 0, 2, 3];
  }

  private _templateTexturePositions(atlas: TextureFontAtlas): number[] {
    const result: number[] = [];
    const xPixels = 0;
    const yPixels = 0;
    const x2Pixels = atlas.getTextureCellWidth();
    const y2Pixels = atlas.getTextureCellHeight();

    result.push(xPixels);
    result.push(yPixels);

    result.push(xPixels);
    result.push(y2Pixels);

    result.push(x2Pixels);
    result.push(y2Pixels);

    result.push(x2Pixels);
    result.push(yPixels);

    return result;
  }


  private _gridTexturePositions(cellGrid: CharCellGrid, atlas: TextureFontAtlas): number[] {
    const result: number[] = [];
    const textureCellWidth = atlas.getTextureCellWidth();
    for (let j=0; j<cellGrid.height; j++) {
      for (const normalizedCell of normalizedCellIterator(cellGrid, j)) {
        const codePoint = normalizedCell.codePoint;
        const fontIndex = normalizedCell.extraFontFlag ? 1 : 0;
        const x = normalizedCell.x;
        const coord = atlas.loadCodePoint(codePoint, cellGrid.getStyle(x, j), fontIndex, cellGrid.getFgRGBA(x, j),
          cellGrid.getBgRGBA(x, j));
        result.push(coord.textureXpx + normalizedCell.segment * textureCellWidth);
        result.push(coord.textureYpx);
      }
    }
    return result;
  }

  // Initialize a texture and load an image.
  // When the image finished loading copy it into the texture.
  //
  private _loadAtlasTexture(gl: WebGLRenderingContext, atlasCanvas: HTMLCanvasElement): WebGLTexture {
    const level = 0;
    const internalFormat = gl.RGBA;
    const srcFormat = gl.RGBA;
    const srcType = gl.UNSIGNED_BYTE;

    gl.bindTexture(gl.TEXTURE_2D, this._texture);
    gl.texImage2D(gl.TEXTURE_2D, level, internalFormat, srcFormat, srcType, atlasCanvas);

    // WebGL1 has different requirements for power of 2 images
    // vs non power of 2 images so check if the image is a
    // power of 2 in both dimensions.
    if (isPowerOf2(atlasCanvas.width) && isPowerOf2(atlasCanvas.height)) {
      // Yes, it's a power of 2. Generate mips.
      gl.generateMipmap(gl.TEXTURE_2D);
    } else {
      // No, it's not a power of 2. Turn off mips and set
      // wrapping to clamp to edge
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    }

    return this._texture;
  }

  render(destinationContext: CanvasRenderingContext2D, cellGrid: CharCellGrid, firstRow: number,
      rowCount: number): void {

    const rectWidth = this._metrics.widthPx * cellGrid.width;
    const rectHeight = this._metrics.heightPx * cellGrid.height;
    this._resizeCanvas(rectWidth, rectHeight);

    {
      const numComponents = 2;
      const type = this._glContext.FLOAT;
      const normalize = false;
      const stride = 0;
      const offset = 0;

      const tPosAttrib = this._glContext.getAttribLocation(this._shaderProgram, "tPos");
      const tPosBuffer = this._glContext.createBuffer();
      this._glContext.bindBuffer(this._glContext.ARRAY_BUFFER, tPosBuffer);

{'fraction_non_alphanumeric': 0.1065882972190543, 'fraction_numerical': 0.011930871129046408, 'mean_word_length': 3.805860033726813, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2139957', 'n_tokens_mistral': 7382, 'n_tokens_neox': 7113, 'n_words': 1630}