summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/util/texture/texel_view.ts
blob: 33c27efbbc3e664a04ca3bf5d8c5b41aafab7391 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import { assert, memcpy } from '../../../common/util/util.js';
import { EncodableTextureFormat, kTextureFormatInfo } from '../../capability_info.js';
import { reifyExtent3D, reifyOrigin3D } from '../unions.js';

import { kTexelRepresentationInfo, makeClampToRange, PerTexelComponent } from './texel_data.js';

/** Function taking some x,y,z coordinates and returning `Readonly<T>`. */
export type PerPixelAtLevel<T> = (coords: Required<GPUOrigin3DDict>) => Readonly<T>;

/**
 * Wrapper to view various representations of texture data in other ways. E.g., can:
 * - Provide a mapped buffer, containing copied texture data, and read color values.
 * - Provide a function that generates color values by coordinate, and convert to ULPs-from-zero.
 *
 * MAINTENANCE_TODO: Would need some refactoring to support block formats, which could be partially
 * supported if useful.
 */
export class TexelView {
  /** The GPUTextureFormat of the TexelView. */
  readonly format: EncodableTextureFormat;
  /** Generates the bytes for the texel at the given coordinates. */
  readonly bytes: PerPixelAtLevel<Uint8Array>;
  /** Generates the ULPs-from-zero for the texel at the given coordinates. */
  readonly ulpFromZero: PerPixelAtLevel<PerTexelComponent<number>>;
  /** Generates the color for the texel at the given coordinates. */
  readonly color: PerPixelAtLevel<PerTexelComponent<number>>;

  private constructor(
    format: EncodableTextureFormat,
    {
      bytes,
      ulpFromZero,
      color,
    }: {
      bytes: PerPixelAtLevel<Uint8Array>;
      ulpFromZero: PerPixelAtLevel<PerTexelComponent<number>>;
      color: PerPixelAtLevel<PerTexelComponent<number>>;
    }
  ) {
    this.format = format;
    this.bytes = bytes;
    this.ulpFromZero = ulpFromZero;
    this.color = color;
  }

  /**
   * Produces a TexelView from "linear image data", i.e. the `writeTexture` format. Takes a
   * reference to the input `subrectData`, so any changes to it will be visible in the TexelView.
   */
  static fromTextureDataByReference(
    format: EncodableTextureFormat,
    subrectData: Uint8Array | Uint8ClampedArray,
    {
      bytesPerRow,
      rowsPerImage,
      subrectOrigin,
      subrectSize,
    }: {
      bytesPerRow: number;
      rowsPerImage: number;
      subrectOrigin: GPUOrigin3D;
      subrectSize: GPUExtent3D;
    }
  ) {
    const origin = reifyOrigin3D(subrectOrigin);
    const size = reifyExtent3D(subrectSize);

    const info = kTextureFormatInfo[format];
    assert(info.blockWidth === 1 && info.blockHeight === 1, 'unimplemented for block formats');

    return TexelView.fromTexelsAsBytes(format, coords => {
      assert(
        coords.x >= origin.x &&
          coords.y >= origin.y &&
          coords.z >= origin.z &&
          coords.x < origin.x + size.width &&
          coords.y < origin.y + size.height &&
          coords.z < origin.z + size.depthOrArrayLayers,
        'coordinate out of bounds'
      );

      const imageOffsetInRows = (coords.z - origin.z) * rowsPerImage;
      const rowOffset = (imageOffsetInRows + (coords.y - origin.y)) * bytesPerRow;
      const offset = rowOffset + (coords.x - origin.x) * info.bytesPerBlock;

      // MAINTENANCE_TODO: To support block formats, decode the block and then index into the result.
      return subrectData.subarray(offset, offset + info.bytesPerBlock) as Uint8Array;
    });
  }

  /** Produces a TexelView from a generator of bytes for individual texel blocks. */
  static fromTexelsAsBytes(
    format: EncodableTextureFormat,
    generator: PerPixelAtLevel<Uint8Array>
  ): TexelView {
    const info = kTextureFormatInfo[format];
    assert(info.blockWidth === 1 && info.blockHeight === 1, 'unimplemented for block formats');

    const repr = kTexelRepresentationInfo[format];
    return new TexelView(format, {
      bytes: generator,
      ulpFromZero: coords => repr.bitsToULPFromZero(repr.unpackBits(generator(coords))),
      color: coords => repr.bitsToNumber(repr.unpackBits(generator(coords))),
    });
  }

  /** Produces a TexelView from a generator of numeric "color" values for each texel. */
  static fromTexelsAsColors(
    format: EncodableTextureFormat,
    generator: PerPixelAtLevel<PerTexelComponent<number>>,
    { clampToFormatRange = false }: { clampToFormatRange?: boolean } = {}
  ): TexelView {
    const info = kTextureFormatInfo[format];
    assert(info.blockWidth === 1 && info.blockHeight === 1, 'unimplemented for block formats');

    if (clampToFormatRange) {
      const applyClamp = makeClampToRange(format);
      const oldGenerator = generator;
      generator = coords => applyClamp(oldGenerator(coords));
    }

    const repr = kTexelRepresentationInfo[format];
    return new TexelView(format, {
      bytes: coords => new Uint8Array(repr.pack(repr.encode(generator(coords)))),
      ulpFromZero: coords => repr.bitsToULPFromZero(repr.numberToBits(generator(coords))),
      color: generator,
    });
  }

  /** Writes the contents of a TexelView as "linear image data", i.e. the `writeTexture` format. */
  writeTextureData(
    subrectData: Uint8Array | Uint8ClampedArray,
    {
      bytesPerRow,
      rowsPerImage,
      subrectOrigin: subrectOrigin_,
      subrectSize: subrectSize_,
    }: {
      bytesPerRow: number;
      rowsPerImage: number;
      subrectOrigin: GPUOrigin3D;
      subrectSize: GPUExtent3D;
    }
  ): void {
    const subrectOrigin = reifyOrigin3D(subrectOrigin_);
    const subrectSize = reifyExtent3D(subrectSize_);

    const info = kTextureFormatInfo[this.format];
    assert(info.blockWidth === 1 && info.blockHeight === 1, 'unimplemented for block formats');

    for (let z = subrectOrigin.z; z < subrectOrigin.z + subrectSize.depthOrArrayLayers; ++z) {
      for (let y = subrectOrigin.y; y < subrectOrigin.y + subrectSize.height; ++y) {
        for (let x = subrectOrigin.x; x < subrectOrigin.x + subrectSize.width; ++x) {
          const start = (z * rowsPerImage + y) * bytesPerRow + x * info.bytesPerBlock;
          memcpy({ src: this.bytes({ x, y, z }) }, { dst: subrectData, start });
        }
      }
    }
  }
}