-
Notifications
You must be signed in to change notification settings - Fork 2.2k
/
texture.js
122 lines (101 loc) · 4.37 KB
/
texture.js
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
// @flow
import window from '../util/window.js';
const {HTMLImageElement, HTMLCanvasElement, HTMLVideoElement, ImageData, ImageBitmap} = window;
import type Context from '../gl/context.js';
import type {RGBAImage, AlphaImage} from '../util/image.js';
export type TextureFormat =
| $PropertyType<WebGLRenderingContext, 'RGBA'>
| $PropertyType<WebGLRenderingContext, 'ALPHA'>;
export type TextureFilter =
| $PropertyType<WebGLRenderingContext, 'LINEAR'>
| $PropertyType<WebGLRenderingContext, 'LINEAR_MIPMAP_NEAREST'>
| $PropertyType<WebGLRenderingContext, 'NEAREST'>;
export type TextureWrap =
| $PropertyType<WebGLRenderingContext, 'REPEAT'>
| $PropertyType<WebGLRenderingContext, 'CLAMP_TO_EDGE'>
| $PropertyType<WebGLRenderingContext, 'MIRRORED_REPEAT'>;
type EmptyImage = {
width: number,
height: number,
data: null
}
export type TextureImage =
| RGBAImage
| AlphaImage
| HTMLImageElement
| HTMLCanvasElement
| HTMLVideoElement
| ImageData
| EmptyImage
| ImageBitmap;
class Texture {
context: Context;
size: [number, number];
texture: WebGLTexture;
format: TextureFormat;
filter: ?TextureFilter;
wrap: ?TextureWrap;
useMipmap: boolean;
constructor(context: Context, image: TextureImage, format: TextureFormat, options: ?{ premultiply?: boolean, useMipmap?: boolean }) {
this.context = context;
this.format = format;
this.texture = context.gl.createTexture();
this.update(image, options);
}
update(image: TextureImage, options: ?{premultiply?: boolean, useMipmap?: boolean}, position?: { x: number, y: number }) {
const {width, height} = image;
const resize = (!this.size || this.size[0] !== width || this.size[1] !== height) && !position;
const {context} = this;
const {gl} = context;
this.useMipmap = Boolean(options && options.useMipmap);
gl.bindTexture(gl.TEXTURE_2D, this.texture);
context.pixelStoreUnpackFlipY.set(false);
context.pixelStoreUnpack.set(1);
context.pixelStoreUnpackPremultiplyAlpha.set(this.format === gl.RGBA && (!options || options.premultiply !== false));
if (resize) {
this.size = [width, height];
if (image instanceof HTMLImageElement || image instanceof HTMLCanvasElement || image instanceof HTMLVideoElement || image instanceof ImageData || (ImageBitmap && image instanceof ImageBitmap)) {
gl.texImage2D(gl.TEXTURE_2D, 0, this.format, this.format, gl.UNSIGNED_BYTE, image);
} else {
gl.texImage2D(gl.TEXTURE_2D, 0, this.format, width, height, 0, this.format, gl.UNSIGNED_BYTE, image.data);
}
} else {
const {x, y} = position || {x: 0, y: 0};
if (image instanceof HTMLImageElement || image instanceof HTMLCanvasElement || image instanceof HTMLVideoElement || image instanceof ImageData || (ImageBitmap && image instanceof ImageBitmap)) {
gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, gl.RGBA, gl.UNSIGNED_BYTE, image);
} else {
gl.texSubImage2D(gl.TEXTURE_2D, 0, x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, image.data);
}
}
if (this.useMipmap && this.isSizePowerOfTwo()) {
gl.generateMipmap(gl.TEXTURE_2D);
}
}
bind(filter: TextureFilter, wrap: TextureWrap, minFilter: ?TextureFilter) {
const {context} = this;
const {gl} = context;
gl.bindTexture(gl.TEXTURE_2D, this.texture);
if (minFilter === gl.LINEAR_MIPMAP_NEAREST && !this.isSizePowerOfTwo()) {
minFilter = gl.LINEAR;
}
if (filter !== this.filter) {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, minFilter || filter);
this.filter = filter;
}
if (wrap !== this.wrap) {
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrap);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrap);
this.wrap = wrap;
}
}
isSizePowerOfTwo() {
return this.size[0] === this.size[1] && (Math.log(this.size[0]) / Math.LN2) % 1 === 0;
}
destroy() {
const {gl} = this.context;
gl.deleteTexture(this.texture);
this.texture = (null: any);
}
}
export default Texture;