1681 lines
47 KiB
JavaScript
1681 lines
47 KiB
JavaScript
var check = require('./util/check')
|
|
var extend = require('./util/extend')
|
|
var values = require('./util/values')
|
|
var isTypedArray = require('./util/is-typed-array')
|
|
var isNDArrayLike = require('./util/is-ndarray')
|
|
var pool = require('./util/pool')
|
|
var convertToHalfFloat = require('./util/to-half-float')
|
|
var isArrayLike = require('./util/is-array-like')
|
|
var flattenUtils = require('./util/flatten')
|
|
var isPow2 = require('./util/is-pow2')
|
|
|
|
var dtypes = require('./constants/arraytypes.json')
|
|
var arrayTypes = require('./constants/arraytypes.json')
|
|
|
|
var GL_COMPRESSED_TEXTURE_FORMATS = 0x86A3
|
|
|
|
var GL_TEXTURE_2D = 0x0DE1
|
|
var GL_TEXTURE_CUBE_MAP = 0x8513
|
|
var GL_TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515
|
|
|
|
var GL_RGBA = 0x1908
|
|
var GL_ALPHA = 0x1906
|
|
var GL_RGB = 0x1907
|
|
var GL_LUMINANCE = 0x1909
|
|
var GL_LUMINANCE_ALPHA = 0x190A
|
|
|
|
var GL_RGBA4 = 0x8056
|
|
var GL_RGB5_A1 = 0x8057
|
|
var GL_RGB565 = 0x8D62
|
|
|
|
var GL_UNSIGNED_SHORT_4_4_4_4 = 0x8033
|
|
var GL_UNSIGNED_SHORT_5_5_5_1 = 0x8034
|
|
var GL_UNSIGNED_SHORT_5_6_5 = 0x8363
|
|
var GL_UNSIGNED_INT_24_8_WEBGL = 0x84FA
|
|
|
|
var GL_DEPTH_COMPONENT = 0x1902
|
|
var GL_DEPTH_STENCIL = 0x84F9
|
|
|
|
var GL_SRGB_EXT = 0x8C40
|
|
var GL_SRGB_ALPHA_EXT = 0x8C42
|
|
|
|
var GL_HALF_FLOAT_OES = 0x8D61
|
|
|
|
var GL_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0
|
|
var GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1
|
|
var GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2
|
|
var GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3
|
|
|
|
var GL_COMPRESSED_RGB_ATC_WEBGL = 0x8C92
|
|
var GL_COMPRESSED_RGBA_ATC_EXPLICIT_ALPHA_WEBGL = 0x8C93
|
|
var GL_COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL = 0x87EE
|
|
|
|
var GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG = 0x8C00
|
|
var GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG = 0x8C01
|
|
var GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG = 0x8C02
|
|
var GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG = 0x8C03
|
|
|
|
var GL_COMPRESSED_RGB_ETC1_WEBGL = 0x8D64
|
|
|
|
var GL_UNSIGNED_BYTE = 0x1401
|
|
var GL_UNSIGNED_SHORT = 0x1403
|
|
var GL_UNSIGNED_INT = 0x1405
|
|
var GL_FLOAT = 0x1406
|
|
|
|
var GL_TEXTURE_WRAP_S = 0x2802
|
|
var GL_TEXTURE_WRAP_T = 0x2803
|
|
|
|
var GL_REPEAT = 0x2901
|
|
var GL_CLAMP_TO_EDGE = 0x812F
|
|
var GL_MIRRORED_REPEAT = 0x8370
|
|
|
|
var GL_TEXTURE_MAG_FILTER = 0x2800
|
|
var GL_TEXTURE_MIN_FILTER = 0x2801
|
|
|
|
var GL_NEAREST = 0x2600
|
|
var GL_LINEAR = 0x2601
|
|
var GL_NEAREST_MIPMAP_NEAREST = 0x2700
|
|
var GL_LINEAR_MIPMAP_NEAREST = 0x2701
|
|
var GL_NEAREST_MIPMAP_LINEAR = 0x2702
|
|
var GL_LINEAR_MIPMAP_LINEAR = 0x2703
|
|
|
|
var GL_GENERATE_MIPMAP_HINT = 0x8192
|
|
var GL_DONT_CARE = 0x1100
|
|
var GL_FASTEST = 0x1101
|
|
var GL_NICEST = 0x1102
|
|
|
|
var GL_TEXTURE_MAX_ANISOTROPY_EXT = 0x84FE
|
|
|
|
var GL_UNPACK_ALIGNMENT = 0x0CF5
|
|
var GL_UNPACK_FLIP_Y_WEBGL = 0x9240
|
|
var GL_UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241
|
|
var GL_UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243
|
|
|
|
var GL_BROWSER_DEFAULT_WEBGL = 0x9244
|
|
|
|
var GL_TEXTURE0 = 0x84C0
|
|
|
|
var MIPMAP_FILTERS = [
|
|
GL_NEAREST_MIPMAP_NEAREST,
|
|
GL_NEAREST_MIPMAP_LINEAR,
|
|
GL_LINEAR_MIPMAP_NEAREST,
|
|
GL_LINEAR_MIPMAP_LINEAR
|
|
]
|
|
|
|
var CHANNELS_FORMAT = [
|
|
0,
|
|
GL_LUMINANCE,
|
|
GL_LUMINANCE_ALPHA,
|
|
GL_RGB,
|
|
GL_RGBA
|
|
]
|
|
|
|
var FORMAT_CHANNELS = {}
|
|
FORMAT_CHANNELS[GL_LUMINANCE] =
|
|
FORMAT_CHANNELS[GL_ALPHA] =
|
|
FORMAT_CHANNELS[GL_DEPTH_COMPONENT] = 1
|
|
FORMAT_CHANNELS[GL_DEPTH_STENCIL] =
|
|
FORMAT_CHANNELS[GL_LUMINANCE_ALPHA] = 2
|
|
FORMAT_CHANNELS[GL_RGB] =
|
|
FORMAT_CHANNELS[GL_SRGB_EXT] = 3
|
|
FORMAT_CHANNELS[GL_RGBA] =
|
|
FORMAT_CHANNELS[GL_SRGB_ALPHA_EXT] = 4
|
|
|
|
var formatTypes = {}
|
|
formatTypes[GL_RGBA4] = GL_UNSIGNED_SHORT_4_4_4_4
|
|
formatTypes[GL_RGB565] = GL_UNSIGNED_SHORT_5_6_5
|
|
formatTypes[GL_RGB5_A1] = GL_UNSIGNED_SHORT_5_5_5_1
|
|
formatTypes[GL_DEPTH_COMPONENT] = GL_UNSIGNED_INT
|
|
formatTypes[GL_DEPTH_STENCIL] = GL_UNSIGNED_INT_24_8_WEBGL
|
|
|
|
function objectName (str) {
|
|
return '[object ' + str + ']'
|
|
}
|
|
|
|
var CANVAS_CLASS = objectName('HTMLCanvasElement')
|
|
var OFFSCREENCANVAS_CLASS = objectName('OffscreenCanvas')
|
|
var CONTEXT2D_CLASS = objectName('CanvasRenderingContext2D')
|
|
var BITMAP_CLASS = objectName('ImageBitmap')
|
|
var IMAGE_CLASS = objectName('HTMLImageElement')
|
|
var VIDEO_CLASS = objectName('HTMLVideoElement')
|
|
|
|
var PIXEL_CLASSES = Object.keys(dtypes).concat([
|
|
CANVAS_CLASS,
|
|
OFFSCREENCANVAS_CLASS,
|
|
CONTEXT2D_CLASS,
|
|
BITMAP_CLASS,
|
|
IMAGE_CLASS,
|
|
VIDEO_CLASS
|
|
])
|
|
|
|
// for every texture type, store
|
|
// the size in bytes.
|
|
var TYPE_SIZES = []
|
|
TYPE_SIZES[GL_UNSIGNED_BYTE] = 1
|
|
TYPE_SIZES[GL_FLOAT] = 4
|
|
TYPE_SIZES[GL_HALF_FLOAT_OES] = 2
|
|
|
|
TYPE_SIZES[GL_UNSIGNED_SHORT] = 2
|
|
TYPE_SIZES[GL_UNSIGNED_INT] = 4
|
|
|
|
var FORMAT_SIZES_SPECIAL = []
|
|
FORMAT_SIZES_SPECIAL[GL_RGBA4] = 2
|
|
FORMAT_SIZES_SPECIAL[GL_RGB5_A1] = 2
|
|
FORMAT_SIZES_SPECIAL[GL_RGB565] = 2
|
|
FORMAT_SIZES_SPECIAL[GL_DEPTH_STENCIL] = 4
|
|
|
|
FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGB_S3TC_DXT1_EXT] = 0.5
|
|
FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGBA_S3TC_DXT1_EXT] = 0.5
|
|
FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGBA_S3TC_DXT3_EXT] = 1
|
|
FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGBA_S3TC_DXT5_EXT] = 1
|
|
|
|
FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGB_ATC_WEBGL] = 0.5
|
|
FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGBA_ATC_EXPLICIT_ALPHA_WEBGL] = 1
|
|
FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL] = 1
|
|
|
|
FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG] = 0.5
|
|
FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG] = 0.25
|
|
FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG] = 0.5
|
|
FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG] = 0.25
|
|
|
|
FORMAT_SIZES_SPECIAL[GL_COMPRESSED_RGB_ETC1_WEBGL] = 0.5
|
|
|
|
function isNumericArray (arr) {
|
|
return (
|
|
Array.isArray(arr) &&
|
|
(arr.length === 0 ||
|
|
typeof arr[0] === 'number'))
|
|
}
|
|
|
|
function isRectArray (arr) {
|
|
if (!Array.isArray(arr)) {
|
|
return false
|
|
}
|
|
var width = arr.length
|
|
if (width === 0 || !isArrayLike(arr[0])) {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
function classString (x) {
|
|
return Object.prototype.toString.call(x)
|
|
}
|
|
|
|
function isCanvasElement (object) {
|
|
return classString(object) === CANVAS_CLASS
|
|
}
|
|
|
|
function isOffscreenCanvas (object) {
|
|
return classString(object) === OFFSCREENCANVAS_CLASS
|
|
}
|
|
|
|
function isContext2D (object) {
|
|
return classString(object) === CONTEXT2D_CLASS
|
|
}
|
|
|
|
function isBitmap (object) {
|
|
return classString(object) === BITMAP_CLASS
|
|
}
|
|
|
|
function isImageElement (object) {
|
|
return classString(object) === IMAGE_CLASS
|
|
}
|
|
|
|
function isVideoElement (object) {
|
|
return classString(object) === VIDEO_CLASS
|
|
}
|
|
|
|
function isPixelData (object) {
|
|
if (!object) {
|
|
return false
|
|
}
|
|
var className = classString(object)
|
|
if (PIXEL_CLASSES.indexOf(className) >= 0) {
|
|
return true
|
|
}
|
|
return (
|
|
isNumericArray(object) ||
|
|
isRectArray(object) ||
|
|
isNDArrayLike(object))
|
|
}
|
|
|
|
function typedArrayCode (data) {
|
|
return arrayTypes[Object.prototype.toString.call(data)] | 0
|
|
}
|
|
|
|
function convertData (result, data) {
|
|
var n = data.length
|
|
switch (result.type) {
|
|
case GL_UNSIGNED_BYTE:
|
|
case GL_UNSIGNED_SHORT:
|
|
case GL_UNSIGNED_INT:
|
|
case GL_FLOAT:
|
|
var converted = pool.allocType(result.type, n)
|
|
converted.set(data)
|
|
result.data = converted
|
|
break
|
|
|
|
case GL_HALF_FLOAT_OES:
|
|
result.data = convertToHalfFloat(data)
|
|
break
|
|
|
|
default:
|
|
check.raise('unsupported texture type, must specify a typed array')
|
|
}
|
|
}
|
|
|
|
function preConvert (image, n) {
|
|
return pool.allocType(
|
|
image.type === GL_HALF_FLOAT_OES
|
|
? GL_FLOAT
|
|
: image.type, n)
|
|
}
|
|
|
|
function postConvert (image, data) {
|
|
if (image.type === GL_HALF_FLOAT_OES) {
|
|
image.data = convertToHalfFloat(data)
|
|
pool.freeType(data)
|
|
} else {
|
|
image.data = data
|
|
}
|
|
}
|
|
|
|
function transposeData (image, array, strideX, strideY, strideC, offset) {
|
|
var w = image.width
|
|
var h = image.height
|
|
var c = image.channels
|
|
var n = w * h * c
|
|
var data = preConvert(image, n)
|
|
|
|
var p = 0
|
|
for (var i = 0; i < h; ++i) {
|
|
for (var j = 0; j < w; ++j) {
|
|
for (var k = 0; k < c; ++k) {
|
|
data[p++] = array[strideX * j + strideY * i + strideC * k + offset]
|
|
}
|
|
}
|
|
}
|
|
|
|
postConvert(image, data)
|
|
}
|
|
|
|
function getTextureSize (format, type, width, height, isMipmap, isCube) {
|
|
var s
|
|
if (typeof FORMAT_SIZES_SPECIAL[format] !== 'undefined') {
|
|
// we have a special array for dealing with weird color formats such as RGB5A1
|
|
s = FORMAT_SIZES_SPECIAL[format]
|
|
} else {
|
|
s = FORMAT_CHANNELS[format] * TYPE_SIZES[type]
|
|
}
|
|
|
|
if (isCube) {
|
|
s *= 6
|
|
}
|
|
|
|
if (isMipmap) {
|
|
// compute the total size of all the mipmaps.
|
|
var total = 0
|
|
|
|
var w = width
|
|
while (w >= 1) {
|
|
// we can only use mipmaps on a square image,
|
|
// so we can simply use the width and ignore the height:
|
|
total += s * w * w
|
|
w /= 2
|
|
}
|
|
return total
|
|
} else {
|
|
return s * width * height
|
|
}
|
|
}
|
|
|
|
module.exports = function createTextureSet (
|
|
gl, extensions, limits, reglPoll, contextState, stats, config) {
|
|
// -------------------------------------------------------
|
|
// Initialize constants and parameter tables here
|
|
// -------------------------------------------------------
|
|
var mipmapHint = {
|
|
"don't care": GL_DONT_CARE,
|
|
'dont care': GL_DONT_CARE,
|
|
'nice': GL_NICEST,
|
|
'fast': GL_FASTEST
|
|
}
|
|
|
|
var wrapModes = {
|
|
'repeat': GL_REPEAT,
|
|
'clamp': GL_CLAMP_TO_EDGE,
|
|
'mirror': GL_MIRRORED_REPEAT
|
|
}
|
|
|
|
var magFilters = {
|
|
'nearest': GL_NEAREST,
|
|
'linear': GL_LINEAR
|
|
}
|
|
|
|
var minFilters = extend({
|
|
'mipmap': GL_LINEAR_MIPMAP_LINEAR,
|
|
'nearest mipmap nearest': GL_NEAREST_MIPMAP_NEAREST,
|
|
'linear mipmap nearest': GL_LINEAR_MIPMAP_NEAREST,
|
|
'nearest mipmap linear': GL_NEAREST_MIPMAP_LINEAR,
|
|
'linear mipmap linear': GL_LINEAR_MIPMAP_LINEAR
|
|
}, magFilters)
|
|
|
|
var colorSpace = {
|
|
'none': 0,
|
|
'browser': GL_BROWSER_DEFAULT_WEBGL
|
|
}
|
|
|
|
var textureTypes = {
|
|
'uint8': GL_UNSIGNED_BYTE,
|
|
'rgba4': GL_UNSIGNED_SHORT_4_4_4_4,
|
|
'rgb565': GL_UNSIGNED_SHORT_5_6_5,
|
|
'rgb5 a1': GL_UNSIGNED_SHORT_5_5_5_1
|
|
}
|
|
|
|
var textureFormats = {
|
|
'alpha': GL_ALPHA,
|
|
'luminance': GL_LUMINANCE,
|
|
'luminance alpha': GL_LUMINANCE_ALPHA,
|
|
'rgb': GL_RGB,
|
|
'rgba': GL_RGBA,
|
|
'rgba4': GL_RGBA4,
|
|
'rgb5 a1': GL_RGB5_A1,
|
|
'rgb565': GL_RGB565
|
|
}
|
|
|
|
var compressedTextureFormats = {}
|
|
|
|
if (extensions.ext_srgb) {
|
|
textureFormats.srgb = GL_SRGB_EXT
|
|
textureFormats.srgba = GL_SRGB_ALPHA_EXT
|
|
}
|
|
|
|
if (extensions.oes_texture_float) {
|
|
textureTypes.float32 = textureTypes.float = GL_FLOAT
|
|
}
|
|
|
|
if (extensions.oes_texture_half_float) {
|
|
textureTypes['float16'] = textureTypes['half float'] = GL_HALF_FLOAT_OES
|
|
}
|
|
|
|
if (extensions.webgl_depth_texture) {
|
|
extend(textureFormats, {
|
|
'depth': GL_DEPTH_COMPONENT,
|
|
'depth stencil': GL_DEPTH_STENCIL
|
|
})
|
|
|
|
extend(textureTypes, {
|
|
'uint16': GL_UNSIGNED_SHORT,
|
|
'uint32': GL_UNSIGNED_INT,
|
|
'depth stencil': GL_UNSIGNED_INT_24_8_WEBGL
|
|
})
|
|
}
|
|
|
|
if (extensions.webgl_compressed_texture_s3tc) {
|
|
extend(compressedTextureFormats, {
|
|
'rgb s3tc dxt1': GL_COMPRESSED_RGB_S3TC_DXT1_EXT,
|
|
'rgba s3tc dxt1': GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
|
|
'rgba s3tc dxt3': GL_COMPRESSED_RGBA_S3TC_DXT3_EXT,
|
|
'rgba s3tc dxt5': GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
|
|
})
|
|
}
|
|
|
|
if (extensions.webgl_compressed_texture_atc) {
|
|
extend(compressedTextureFormats, {
|
|
'rgb atc': GL_COMPRESSED_RGB_ATC_WEBGL,
|
|
'rgba atc explicit alpha': GL_COMPRESSED_RGBA_ATC_EXPLICIT_ALPHA_WEBGL,
|
|
'rgba atc interpolated alpha': GL_COMPRESSED_RGBA_ATC_INTERPOLATED_ALPHA_WEBGL
|
|
})
|
|
}
|
|
|
|
if (extensions.webgl_compressed_texture_pvrtc) {
|
|
extend(compressedTextureFormats, {
|
|
'rgb pvrtc 4bppv1': GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG,
|
|
'rgb pvrtc 2bppv1': GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG,
|
|
'rgba pvrtc 4bppv1': GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG,
|
|
'rgba pvrtc 2bppv1': GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG
|
|
})
|
|
}
|
|
|
|
if (extensions.webgl_compressed_texture_etc1) {
|
|
compressedTextureFormats['rgb etc1'] = GL_COMPRESSED_RGB_ETC1_WEBGL
|
|
}
|
|
|
|
// Copy over all texture formats
|
|
var supportedCompressedFormats = Array.prototype.slice.call(
|
|
gl.getParameter(GL_COMPRESSED_TEXTURE_FORMATS))
|
|
Object.keys(compressedTextureFormats).forEach(function (name) {
|
|
var format = compressedTextureFormats[name]
|
|
if (supportedCompressedFormats.indexOf(format) >= 0) {
|
|
textureFormats[name] = format
|
|
}
|
|
})
|
|
|
|
var supportedFormats = Object.keys(textureFormats)
|
|
limits.textureFormats = supportedFormats
|
|
|
|
// associate with every format string its
|
|
// corresponding GL-value.
|
|
var textureFormatsInvert = []
|
|
Object.keys(textureFormats).forEach(function (key) {
|
|
var val = textureFormats[key]
|
|
textureFormatsInvert[val] = key
|
|
})
|
|
|
|
// associate with every type string its
|
|
// corresponding GL-value.
|
|
var textureTypesInvert = []
|
|
Object.keys(textureTypes).forEach(function (key) {
|
|
var val = textureTypes[key]
|
|
textureTypesInvert[val] = key
|
|
})
|
|
|
|
var magFiltersInvert = []
|
|
Object.keys(magFilters).forEach(function (key) {
|
|
var val = magFilters[key]
|
|
magFiltersInvert[val] = key
|
|
})
|
|
|
|
var minFiltersInvert = []
|
|
Object.keys(minFilters).forEach(function (key) {
|
|
var val = minFilters[key]
|
|
minFiltersInvert[val] = key
|
|
})
|
|
|
|
var wrapModesInvert = []
|
|
Object.keys(wrapModes).forEach(function (key) {
|
|
var val = wrapModes[key]
|
|
wrapModesInvert[val] = key
|
|
})
|
|
|
|
// colorFormats[] gives the format (channels) associated to an
|
|
// internalformat
|
|
var colorFormats = supportedFormats.reduce(function (color, key) {
|
|
var glenum = textureFormats[key]
|
|
if (glenum === GL_LUMINANCE ||
|
|
glenum === GL_ALPHA ||
|
|
glenum === GL_LUMINANCE ||
|
|
glenum === GL_LUMINANCE_ALPHA ||
|
|
glenum === GL_DEPTH_COMPONENT ||
|
|
glenum === GL_DEPTH_STENCIL ||
|
|
(extensions.ext_srgb &&
|
|
(glenum === GL_SRGB_EXT ||
|
|
glenum === GL_SRGB_ALPHA_EXT))) {
|
|
color[glenum] = glenum
|
|
} else if (glenum === GL_RGB5_A1 || key.indexOf('rgba') >= 0) {
|
|
color[glenum] = GL_RGBA
|
|
} else {
|
|
color[glenum] = GL_RGB
|
|
}
|
|
return color
|
|
}, {})
|
|
|
|
function TexFlags () {
|
|
// format info
|
|
this.internalformat = GL_RGBA
|
|
this.format = GL_RGBA
|
|
this.type = GL_UNSIGNED_BYTE
|
|
this.compressed = false
|
|
|
|
// pixel storage
|
|
this.premultiplyAlpha = false
|
|
this.flipY = false
|
|
this.unpackAlignment = 1
|
|
this.colorSpace = GL_BROWSER_DEFAULT_WEBGL
|
|
|
|
// shape info
|
|
this.width = 0
|
|
this.height = 0
|
|
this.channels = 0
|
|
}
|
|
|
|
function copyFlags (result, other) {
|
|
result.internalformat = other.internalformat
|
|
result.format = other.format
|
|
result.type = other.type
|
|
result.compressed = other.compressed
|
|
|
|
result.premultiplyAlpha = other.premultiplyAlpha
|
|
result.flipY = other.flipY
|
|
result.unpackAlignment = other.unpackAlignment
|
|
result.colorSpace = other.colorSpace
|
|
|
|
result.width = other.width
|
|
result.height = other.height
|
|
result.channels = other.channels
|
|
}
|
|
|
|
function parseFlags (flags, options) {
|
|
if (typeof options !== 'object' || !options) {
|
|
return
|
|
}
|
|
|
|
if ('premultiplyAlpha' in options) {
|
|
check.type(options.premultiplyAlpha, 'boolean',
|
|
'invalid premultiplyAlpha')
|
|
flags.premultiplyAlpha = options.premultiplyAlpha
|
|
}
|
|
|
|
if ('flipY' in options) {
|
|
check.type(options.flipY, 'boolean',
|
|
'invalid texture flip')
|
|
flags.flipY = options.flipY
|
|
}
|
|
|
|
if ('alignment' in options) {
|
|
check.oneOf(options.alignment, [1, 2, 4, 8],
|
|
'invalid texture unpack alignment')
|
|
flags.unpackAlignment = options.alignment
|
|
}
|
|
|
|
if ('colorSpace' in options) {
|
|
check.parameter(options.colorSpace, colorSpace,
|
|
'invalid colorSpace')
|
|
flags.colorSpace = colorSpace[options.colorSpace]
|
|
}
|
|
|
|
if ('type' in options) {
|
|
var type = options.type
|
|
check(extensions.oes_texture_float ||
|
|
!(type === 'float' || type === 'float32'),
|
|
'you must enable the OES_texture_float extension in order to use floating point textures.')
|
|
check(extensions.oes_texture_half_float ||
|
|
!(type === 'half float' || type === 'float16'),
|
|
'you must enable the OES_texture_half_float extension in order to use 16-bit floating point textures.')
|
|
check(extensions.webgl_depth_texture ||
|
|
!(type === 'uint16' || type === 'uint32' || type === 'depth stencil'),
|
|
'you must enable the WEBGL_depth_texture extension in order to use depth/stencil textures.')
|
|
check.parameter(type, textureTypes,
|
|
'invalid texture type')
|
|
flags.type = textureTypes[type]
|
|
}
|
|
|
|
var w = flags.width
|
|
var h = flags.height
|
|
var c = flags.channels
|
|
var hasChannels = false
|
|
if ('shape' in options) {
|
|
check(Array.isArray(options.shape) && options.shape.length >= 2,
|
|
'shape must be an array')
|
|
w = options.shape[0]
|
|
h = options.shape[1]
|
|
if (options.shape.length === 3) {
|
|
c = options.shape[2]
|
|
check(c > 0 && c <= 4, 'invalid number of channels')
|
|
hasChannels = true
|
|
}
|
|
check(w >= 0 && w <= limits.maxTextureSize, 'invalid width')
|
|
check(h >= 0 && h <= limits.maxTextureSize, 'invalid height')
|
|
} else {
|
|
if ('radius' in options) {
|
|
w = h = options.radius
|
|
check(w >= 0 && w <= limits.maxTextureSize, 'invalid radius')
|
|
}
|
|
if ('width' in options) {
|
|
w = options.width
|
|
check(w >= 0 && w <= limits.maxTextureSize, 'invalid width')
|
|
}
|
|
if ('height' in options) {
|
|
h = options.height
|
|
check(h >= 0 && h <= limits.maxTextureSize, 'invalid height')
|
|
}
|
|
if ('channels' in options) {
|
|
c = options.channels
|
|
check(c > 0 && c <= 4, 'invalid number of channels')
|
|
hasChannels = true
|
|
}
|
|
}
|
|
flags.width = w | 0
|
|
flags.height = h | 0
|
|
flags.channels = c | 0
|
|
|
|
var hasFormat = false
|
|
if ('format' in options) {
|
|
var formatStr = options.format
|
|
check(extensions.webgl_depth_texture ||
|
|
!(formatStr === 'depth' || formatStr === 'depth stencil'),
|
|
'you must enable the WEBGL_depth_texture extension in order to use depth/stencil textures.')
|
|
check.parameter(formatStr, textureFormats,
|
|
'invalid texture format')
|
|
var internalformat = flags.internalformat = textureFormats[formatStr]
|
|
flags.format = colorFormats[internalformat]
|
|
if (formatStr in textureTypes) {
|
|
if (!('type' in options)) {
|
|
flags.type = textureTypes[formatStr]
|
|
}
|
|
}
|
|
if (formatStr in compressedTextureFormats) {
|
|
flags.compressed = true
|
|
}
|
|
hasFormat = true
|
|
}
|
|
|
|
// Reconcile channels and format
|
|
if (!hasChannels && hasFormat) {
|
|
flags.channels = FORMAT_CHANNELS[flags.format]
|
|
} else if (hasChannels && !hasFormat) {
|
|
if (flags.channels !== CHANNELS_FORMAT[flags.format]) {
|
|
flags.format = flags.internalformat = CHANNELS_FORMAT[flags.channels]
|
|
}
|
|
} else if (hasFormat && hasChannels) {
|
|
check(
|
|
flags.channels === FORMAT_CHANNELS[flags.format],
|
|
'number of channels inconsistent with specified format')
|
|
}
|
|
}
|
|
|
|
function setFlags (flags) {
|
|
gl.pixelStorei(GL_UNPACK_FLIP_Y_WEBGL, flags.flipY)
|
|
gl.pixelStorei(GL_UNPACK_PREMULTIPLY_ALPHA_WEBGL, flags.premultiplyAlpha)
|
|
gl.pixelStorei(GL_UNPACK_COLORSPACE_CONVERSION_WEBGL, flags.colorSpace)
|
|
gl.pixelStorei(GL_UNPACK_ALIGNMENT, flags.unpackAlignment)
|
|
}
|
|
|
|
// -------------------------------------------------------
|
|
// Tex image data
|
|
// -------------------------------------------------------
|
|
function TexImage () {
|
|
TexFlags.call(this)
|
|
|
|
this.xOffset = 0
|
|
this.yOffset = 0
|
|
|
|
// data
|
|
this.data = null
|
|
this.needsFree = false
|
|
|
|
// html element
|
|
this.element = null
|
|
|
|
// copyTexImage info
|
|
this.needsCopy = false
|
|
}
|
|
|
|
function parseImage (image, options) {
|
|
var data = null
|
|
if (isPixelData(options)) {
|
|
data = options
|
|
} else if (options) {
|
|
check.type(options, 'object', 'invalid pixel data type')
|
|
parseFlags(image, options)
|
|
if ('x' in options) {
|
|
image.xOffset = options.x | 0
|
|
}
|
|
if ('y' in options) {
|
|
image.yOffset = options.y | 0
|
|
}
|
|
if (isPixelData(options.data)) {
|
|
data = options.data
|
|
}
|
|
}
|
|
|
|
check(
|
|
!image.compressed ||
|
|
data instanceof Uint8Array,
|
|
'compressed texture data must be stored in a uint8array')
|
|
|
|
if (options.copy) {
|
|
check(!data, 'can not specify copy and data field for the same texture')
|
|
var viewW = contextState.viewportWidth
|
|
var viewH = contextState.viewportHeight
|
|
image.width = image.width || (viewW - image.xOffset)
|
|
image.height = image.height || (viewH - image.yOffset)
|
|
image.needsCopy = true
|
|
check(image.xOffset >= 0 && image.xOffset < viewW &&
|
|
image.yOffset >= 0 && image.yOffset < viewH &&
|
|
image.width > 0 && image.width <= viewW &&
|
|
image.height > 0 && image.height <= viewH,
|
|
'copy texture read out of bounds')
|
|
} else if (!data) {
|
|
image.width = image.width || 1
|
|
image.height = image.height || 1
|
|
image.channels = image.channels || 4
|
|
} else if (isTypedArray(data)) {
|
|
image.channels = image.channels || 4
|
|
image.data = data
|
|
if (!('type' in options) && image.type === GL_UNSIGNED_BYTE) {
|
|
image.type = typedArrayCode(data)
|
|
}
|
|
} else if (isNumericArray(data)) {
|
|
image.channels = image.channels || 4
|
|
convertData(image, data)
|
|
image.alignment = 1
|
|
image.needsFree = true
|
|
} else if (isNDArrayLike(data)) {
|
|
var array = data.data
|
|
if (!Array.isArray(array) && image.type === GL_UNSIGNED_BYTE) {
|
|
image.type = typedArrayCode(array)
|
|
}
|
|
var shape = data.shape
|
|
var stride = data.stride
|
|
var shapeX, shapeY, shapeC, strideX, strideY, strideC
|
|
if (shape.length === 3) {
|
|
shapeC = shape[2]
|
|
strideC = stride[2]
|
|
} else {
|
|
check(shape.length === 2, 'invalid ndarray pixel data, must be 2 or 3D')
|
|
shapeC = 1
|
|
strideC = 1
|
|
}
|
|
shapeX = shape[0]
|
|
shapeY = shape[1]
|
|
strideX = stride[0]
|
|
strideY = stride[1]
|
|
image.alignment = 1
|
|
image.width = shapeX
|
|
image.height = shapeY
|
|
image.channels = shapeC
|
|
image.format = image.internalformat = CHANNELS_FORMAT[shapeC]
|
|
image.needsFree = true
|
|
transposeData(image, array, strideX, strideY, strideC, data.offset)
|
|
} else if (isCanvasElement(data) || isOffscreenCanvas(data) || isContext2D(data)) {
|
|
if (isCanvasElement(data) || isOffscreenCanvas(data)) {
|
|
image.element = data
|
|
} else {
|
|
image.element = data.canvas
|
|
}
|
|
image.width = image.element.width
|
|
image.height = image.element.height
|
|
image.channels = 4
|
|
} else if (isBitmap(data)) {
|
|
image.element = data
|
|
image.width = data.width
|
|
image.height = data.height
|
|
image.channels = 4
|
|
} else if (isImageElement(data)) {
|
|
image.element = data
|
|
image.width = data.naturalWidth
|
|
image.height = data.naturalHeight
|
|
image.channels = 4
|
|
} else if (isVideoElement(data)) {
|
|
image.element = data
|
|
image.width = data.videoWidth
|
|
image.height = data.videoHeight
|
|
image.channels = 4
|
|
} else if (isRectArray(data)) {
|
|
var w = image.width || data[0].length
|
|
var h = image.height || data.length
|
|
var c = image.channels
|
|
if (isArrayLike(data[0][0])) {
|
|
c = c || data[0][0].length
|
|
} else {
|
|
c = c || 1
|
|
}
|
|
var arrayShape = flattenUtils.shape(data)
|
|
var n = 1
|
|
for (var dd = 0; dd < arrayShape.length; ++dd) {
|
|
n *= arrayShape[dd]
|
|
}
|
|
var allocData = preConvert(image, n)
|
|
flattenUtils.flatten(data, arrayShape, '', allocData)
|
|
postConvert(image, allocData)
|
|
image.alignment = 1
|
|
image.width = w
|
|
image.height = h
|
|
image.channels = c
|
|
image.format = image.internalformat = CHANNELS_FORMAT[c]
|
|
image.needsFree = true
|
|
}
|
|
|
|
if (image.type === GL_FLOAT) {
|
|
check(limits.extensions.indexOf('oes_texture_float') >= 0,
|
|
'oes_texture_float extension not enabled')
|
|
} else if (image.type === GL_HALF_FLOAT_OES) {
|
|
check(limits.extensions.indexOf('oes_texture_half_float') >= 0,
|
|
'oes_texture_half_float extension not enabled')
|
|
}
|
|
|
|
// do compressed texture validation here.
|
|
}
|
|
|
|
function setImage (info, target, miplevel) {
|
|
var element = info.element
|
|
var data = info.data
|
|
var internalformat = info.internalformat
|
|
var format = info.format
|
|
var type = info.type
|
|
var width = info.width
|
|
var height = info.height
|
|
|
|
setFlags(info)
|
|
|
|
if (element) {
|
|
gl.texImage2D(target, miplevel, format, format, type, element)
|
|
} else if (info.compressed) {
|
|
gl.compressedTexImage2D(target, miplevel, internalformat, width, height, 0, data)
|
|
} else if (info.needsCopy) {
|
|
reglPoll()
|
|
gl.copyTexImage2D(
|
|
target, miplevel, format, info.xOffset, info.yOffset, width, height, 0)
|
|
} else {
|
|
gl.texImage2D(target, miplevel, format, width, height, 0, format, type, data || null)
|
|
}
|
|
}
|
|
|
|
function setSubImage (info, target, x, y, miplevel) {
|
|
var element = info.element
|
|
var data = info.data
|
|
var internalformat = info.internalformat
|
|
var format = info.format
|
|
var type = info.type
|
|
var width = info.width
|
|
var height = info.height
|
|
|
|
setFlags(info)
|
|
|
|
if (element) {
|
|
gl.texSubImage2D(
|
|
target, miplevel, x, y, format, type, element)
|
|
} else if (info.compressed) {
|
|
gl.compressedTexSubImage2D(
|
|
target, miplevel, x, y, internalformat, width, height, data)
|
|
} else if (info.needsCopy) {
|
|
reglPoll()
|
|
gl.copyTexSubImage2D(
|
|
target, miplevel, x, y, info.xOffset, info.yOffset, width, height)
|
|
} else {
|
|
gl.texSubImage2D(
|
|
target, miplevel, x, y, width, height, format, type, data)
|
|
}
|
|
}
|
|
|
|
// texImage pool
|
|
var imagePool = []
|
|
|
|
function allocImage () {
|
|
return imagePool.pop() || new TexImage()
|
|
}
|
|
|
|
function freeImage (image) {
|
|
if (image.needsFree) {
|
|
pool.freeType(image.data)
|
|
}
|
|
TexImage.call(image)
|
|
imagePool.push(image)
|
|
}
|
|
|
|
// -------------------------------------------------------
|
|
// Mip map
|
|
// -------------------------------------------------------
|
|
function MipMap () {
|
|
TexFlags.call(this)
|
|
|
|
this.genMipmaps = false
|
|
this.mipmapHint = GL_DONT_CARE
|
|
this.mipmask = 0
|
|
this.images = Array(16)
|
|
}
|
|
|
|
function parseMipMapFromShape (mipmap, width, height) {
|
|
var img = mipmap.images[0] = allocImage()
|
|
mipmap.mipmask = 1
|
|
img.width = mipmap.width = width
|
|
img.height = mipmap.height = height
|
|
img.channels = mipmap.channels = 4
|
|
}
|
|
|
|
function parseMipMapFromObject (mipmap, options) {
|
|
var imgData = null
|
|
if (isPixelData(options)) {
|
|
imgData = mipmap.images[0] = allocImage()
|
|
copyFlags(imgData, mipmap)
|
|
parseImage(imgData, options)
|
|
mipmap.mipmask = 1
|
|
} else {
|
|
parseFlags(mipmap, options)
|
|
if (Array.isArray(options.mipmap)) {
|
|
var mipData = options.mipmap
|
|
for (var i = 0; i < mipData.length; ++i) {
|
|
imgData = mipmap.images[i] = allocImage()
|
|
copyFlags(imgData, mipmap)
|
|
imgData.width >>= i
|
|
imgData.height >>= i
|
|
parseImage(imgData, mipData[i])
|
|
mipmap.mipmask |= (1 << i)
|
|
}
|
|
} else {
|
|
imgData = mipmap.images[0] = allocImage()
|
|
copyFlags(imgData, mipmap)
|
|
parseImage(imgData, options)
|
|
mipmap.mipmask = 1
|
|
}
|
|
}
|
|
copyFlags(mipmap, mipmap.images[0])
|
|
|
|
// For textures of the compressed format WEBGL_compressed_texture_s3tc
|
|
// we must have that
|
|
//
|
|
// "When level equals zero width and height must be a multiple of 4.
|
|
// When level is greater than 0 width and height must be 0, 1, 2 or a multiple of 4. "
|
|
//
|
|
// but we do not yet support having multiple mipmap levels for compressed textures,
|
|
// so we only test for level zero.
|
|
|
|
if (
|
|
mipmap.compressed &&
|
|
(
|
|
mipmap.internalformat === GL_COMPRESSED_RGB_S3TC_DXT1_EXT ||
|
|
mipmap.internalformat === GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ||
|
|
mipmap.internalformat === GL_COMPRESSED_RGBA_S3TC_DXT3_EXT ||
|
|
mipmap.internalformat === GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
|
|
)
|
|
) {
|
|
check(mipmap.width % 4 === 0 && mipmap.height % 4 === 0,
|
|
'for compressed texture formats, mipmap level 0 must have width and height that are a multiple of 4')
|
|
}
|
|
}
|
|
|
|
function setMipMap (mipmap, target) {
|
|
var images = mipmap.images
|
|
for (var i = 0; i < images.length; ++i) {
|
|
if (!images[i]) {
|
|
return
|
|
}
|
|
setImage(images[i], target, i)
|
|
}
|
|
}
|
|
|
|
var mipPool = []
|
|
|
|
function allocMipMap () {
|
|
var result = mipPool.pop() || new MipMap()
|
|
TexFlags.call(result)
|
|
result.mipmask = 0
|
|
for (var i = 0; i < 16; ++i) {
|
|
result.images[i] = null
|
|
}
|
|
return result
|
|
}
|
|
|
|
function freeMipMap (mipmap) {
|
|
var images = mipmap.images
|
|
for (var i = 0; i < images.length; ++i) {
|
|
if (images[i]) {
|
|
freeImage(images[i])
|
|
}
|
|
images[i] = null
|
|
}
|
|
mipPool.push(mipmap)
|
|
}
|
|
|
|
// -------------------------------------------------------
|
|
// Tex info
|
|
// -------------------------------------------------------
|
|
function TexInfo () {
|
|
this.minFilter = GL_NEAREST
|
|
this.magFilter = GL_NEAREST
|
|
|
|
this.wrapS = GL_CLAMP_TO_EDGE
|
|
this.wrapT = GL_CLAMP_TO_EDGE
|
|
|
|
this.anisotropic = 1
|
|
|
|
this.genMipmaps = false
|
|
this.mipmapHint = GL_DONT_CARE
|
|
}
|
|
|
|
function parseTexInfo (info, options) {
|
|
if ('min' in options) {
|
|
var minFilter = options.min
|
|
check.parameter(minFilter, minFilters)
|
|
info.minFilter = minFilters[minFilter]
|
|
if (MIPMAP_FILTERS.indexOf(info.minFilter) >= 0 && !('faces' in options)) {
|
|
info.genMipmaps = true
|
|
}
|
|
}
|
|
|
|
if ('mag' in options) {
|
|
var magFilter = options.mag
|
|
check.parameter(magFilter, magFilters)
|
|
info.magFilter = magFilters[magFilter]
|
|
}
|
|
|
|
var wrapS = info.wrapS
|
|
var wrapT = info.wrapT
|
|
if ('wrap' in options) {
|
|
var wrap = options.wrap
|
|
if (typeof wrap === 'string') {
|
|
check.parameter(wrap, wrapModes)
|
|
wrapS = wrapT = wrapModes[wrap]
|
|
} else if (Array.isArray(wrap)) {
|
|
check.parameter(wrap[0], wrapModes)
|
|
check.parameter(wrap[1], wrapModes)
|
|
wrapS = wrapModes[wrap[0]]
|
|
wrapT = wrapModes[wrap[1]]
|
|
}
|
|
} else {
|
|
if ('wrapS' in options) {
|
|
var optWrapS = options.wrapS
|
|
check.parameter(optWrapS, wrapModes)
|
|
wrapS = wrapModes[optWrapS]
|
|
}
|
|
if ('wrapT' in options) {
|
|
var optWrapT = options.wrapT
|
|
check.parameter(optWrapT, wrapModes)
|
|
wrapT = wrapModes[optWrapT]
|
|
}
|
|
}
|
|
info.wrapS = wrapS
|
|
info.wrapT = wrapT
|
|
|
|
if ('anisotropic' in options) {
|
|
var anisotropic = options.anisotropic
|
|
check(typeof anisotropic === 'number' &&
|
|
anisotropic >= 1 && anisotropic <= limits.maxAnisotropic,
|
|
'aniso samples must be between 1 and ')
|
|
info.anisotropic = options.anisotropic
|
|
}
|
|
|
|
if ('mipmap' in options) {
|
|
var hasMipMap = false
|
|
switch (typeof options.mipmap) {
|
|
case 'string':
|
|
check.parameter(options.mipmap, mipmapHint,
|
|
'invalid mipmap hint')
|
|
info.mipmapHint = mipmapHint[options.mipmap]
|
|
info.genMipmaps = true
|
|
hasMipMap = true
|
|
break
|
|
|
|
case 'boolean':
|
|
hasMipMap = info.genMipmaps = options.mipmap
|
|
break
|
|
|
|
case 'object':
|
|
check(Array.isArray(options.mipmap), 'invalid mipmap type')
|
|
info.genMipmaps = false
|
|
hasMipMap = true
|
|
break
|
|
|
|
default:
|
|
check.raise('invalid mipmap type')
|
|
}
|
|
if (hasMipMap && !('min' in options)) {
|
|
info.minFilter = GL_NEAREST_MIPMAP_NEAREST
|
|
}
|
|
}
|
|
}
|
|
|
|
function setTexInfo (info, target) {
|
|
gl.texParameteri(target, GL_TEXTURE_MIN_FILTER, info.minFilter)
|
|
gl.texParameteri(target, GL_TEXTURE_MAG_FILTER, info.magFilter)
|
|
gl.texParameteri(target, GL_TEXTURE_WRAP_S, info.wrapS)
|
|
gl.texParameteri(target, GL_TEXTURE_WRAP_T, info.wrapT)
|
|
if (extensions.ext_texture_filter_anisotropic) {
|
|
gl.texParameteri(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, info.anisotropic)
|
|
}
|
|
if (info.genMipmaps) {
|
|
gl.hint(GL_GENERATE_MIPMAP_HINT, info.mipmapHint)
|
|
gl.generateMipmap(target)
|
|
}
|
|
}
|
|
|
|
// -------------------------------------------------------
|
|
// Full texture object
|
|
// -------------------------------------------------------
|
|
var textureCount = 0
|
|
var textureSet = {}
|
|
var numTexUnits = limits.maxTextureUnits
|
|
var textureUnits = Array(numTexUnits).map(function () {
|
|
return null
|
|
})
|
|
|
|
function REGLTexture (target) {
|
|
TexFlags.call(this)
|
|
this.mipmask = 0
|
|
this.internalformat = GL_RGBA
|
|
|
|
this.id = textureCount++
|
|
|
|
this.refCount = 1
|
|
|
|
this.target = target
|
|
this.texture = gl.createTexture()
|
|
|
|
this.unit = -1
|
|
this.bindCount = 0
|
|
|
|
this.texInfo = new TexInfo()
|
|
|
|
if (config.profile) {
|
|
this.stats = { size: 0 }
|
|
}
|
|
}
|
|
|
|
function tempBind (texture) {
|
|
gl.activeTexture(GL_TEXTURE0)
|
|
gl.bindTexture(texture.target, texture.texture)
|
|
}
|
|
|
|
function tempRestore () {
|
|
var prev = textureUnits[0]
|
|
if (prev) {
|
|
gl.bindTexture(prev.target, prev.texture)
|
|
} else {
|
|
gl.bindTexture(GL_TEXTURE_2D, null)
|
|
}
|
|
}
|
|
|
|
function destroy (texture) {
|
|
var handle = texture.texture
|
|
check(handle, 'must not double destroy texture')
|
|
var unit = texture.unit
|
|
var target = texture.target
|
|
if (unit >= 0) {
|
|
gl.activeTexture(GL_TEXTURE0 + unit)
|
|
gl.bindTexture(target, null)
|
|
textureUnits[unit] = null
|
|
}
|
|
gl.deleteTexture(handle)
|
|
texture.texture = null
|
|
texture.params = null
|
|
texture.pixels = null
|
|
texture.refCount = 0
|
|
delete textureSet[texture.id]
|
|
stats.textureCount--
|
|
}
|
|
|
|
extend(REGLTexture.prototype, {
|
|
bind: function () {
|
|
var texture = this
|
|
texture.bindCount += 1
|
|
var unit = texture.unit
|
|
if (unit < 0) {
|
|
for (var i = 0; i < numTexUnits; ++i) {
|
|
var other = textureUnits[i]
|
|
if (other) {
|
|
if (other.bindCount > 0) {
|
|
continue
|
|
}
|
|
other.unit = -1
|
|
}
|
|
textureUnits[i] = texture
|
|
unit = i
|
|
break
|
|
}
|
|
if (unit >= numTexUnits) {
|
|
check.raise('insufficient number of texture units')
|
|
}
|
|
if (config.profile && stats.maxTextureUnits < (unit + 1)) {
|
|
stats.maxTextureUnits = unit + 1 // +1, since the units are zero-based
|
|
}
|
|
texture.unit = unit
|
|
gl.activeTexture(GL_TEXTURE0 + unit)
|
|
gl.bindTexture(texture.target, texture.texture)
|
|
}
|
|
return unit
|
|
},
|
|
|
|
unbind: function () {
|
|
this.bindCount -= 1
|
|
},
|
|
|
|
decRef: function () {
|
|
if (--this.refCount <= 0) {
|
|
destroy(this)
|
|
}
|
|
}
|
|
})
|
|
|
|
function createTexture2D (a, b) {
|
|
var texture = new REGLTexture(GL_TEXTURE_2D)
|
|
textureSet[texture.id] = texture
|
|
stats.textureCount++
|
|
|
|
function reglTexture2D (a, b) {
|
|
var texInfo = texture.texInfo
|
|
TexInfo.call(texInfo)
|
|
var mipData = allocMipMap()
|
|
|
|
if (typeof a === 'number') {
|
|
if (typeof b === 'number') {
|
|
parseMipMapFromShape(mipData, a | 0, b | 0)
|
|
} else {
|
|
parseMipMapFromShape(mipData, a | 0, a | 0)
|
|
}
|
|
} else if (a) {
|
|
check.type(a, 'object', 'invalid arguments to regl.texture')
|
|
parseTexInfo(texInfo, a)
|
|
parseMipMapFromObject(mipData, a)
|
|
} else {
|
|
// empty textures get assigned a default shape of 1x1
|
|
parseMipMapFromShape(mipData, 1, 1)
|
|
}
|
|
|
|
if (texInfo.genMipmaps) {
|
|
mipData.mipmask = (mipData.width << 1) - 1
|
|
}
|
|
texture.mipmask = mipData.mipmask
|
|
|
|
copyFlags(texture, mipData)
|
|
|
|
check.texture2D(texInfo, mipData, limits)
|
|
texture.internalformat = mipData.internalformat
|
|
|
|
reglTexture2D.width = mipData.width
|
|
reglTexture2D.height = mipData.height
|
|
|
|
tempBind(texture)
|
|
setMipMap(mipData, GL_TEXTURE_2D)
|
|
setTexInfo(texInfo, GL_TEXTURE_2D)
|
|
tempRestore()
|
|
|
|
freeMipMap(mipData)
|
|
|
|
if (config.profile) {
|
|
texture.stats.size = getTextureSize(
|
|
texture.internalformat,
|
|
texture.type,
|
|
mipData.width,
|
|
mipData.height,
|
|
texInfo.genMipmaps,
|
|
false)
|
|
}
|
|
reglTexture2D.format = textureFormatsInvert[texture.internalformat]
|
|
reglTexture2D.type = textureTypesInvert[texture.type]
|
|
|
|
reglTexture2D.mag = magFiltersInvert[texInfo.magFilter]
|
|
reglTexture2D.min = minFiltersInvert[texInfo.minFilter]
|
|
|
|
reglTexture2D.wrapS = wrapModesInvert[texInfo.wrapS]
|
|
reglTexture2D.wrapT = wrapModesInvert[texInfo.wrapT]
|
|
|
|
return reglTexture2D
|
|
}
|
|
|
|
function subimage (image, x_, y_, level_) {
|
|
check(!!image, 'must specify image data')
|
|
|
|
var x = x_ | 0
|
|
var y = y_ | 0
|
|
var level = level_ | 0
|
|
|
|
var imageData = allocImage()
|
|
copyFlags(imageData, texture)
|
|
imageData.width = 0
|
|
imageData.height = 0
|
|
parseImage(imageData, image)
|
|
imageData.width = imageData.width || ((texture.width >> level) - x)
|
|
imageData.height = imageData.height || ((texture.height >> level) - y)
|
|
|
|
check(
|
|
texture.type === imageData.type &&
|
|
texture.format === imageData.format &&
|
|
texture.internalformat === imageData.internalformat,
|
|
'incompatible format for texture.subimage')
|
|
check(
|
|
x >= 0 && y >= 0 &&
|
|
x + imageData.width <= texture.width &&
|
|
y + imageData.height <= texture.height,
|
|
'texture.subimage write out of bounds')
|
|
check(
|
|
texture.mipmask & (1 << level),
|
|
'missing mipmap data')
|
|
check(
|
|
imageData.data || imageData.element || imageData.needsCopy,
|
|
'missing image data')
|
|
|
|
tempBind(texture)
|
|
setSubImage(imageData, GL_TEXTURE_2D, x, y, level)
|
|
tempRestore()
|
|
|
|
freeImage(imageData)
|
|
|
|
return reglTexture2D
|
|
}
|
|
|
|
function resize (w_, h_) {
|
|
var w = w_ | 0
|
|
var h = (h_ | 0) || w
|
|
if (w === texture.width && h === texture.height) {
|
|
return reglTexture2D
|
|
}
|
|
|
|
reglTexture2D.width = texture.width = w
|
|
reglTexture2D.height = texture.height = h
|
|
|
|
tempBind(texture)
|
|
|
|
for (var i = 0; texture.mipmask >> i; ++i) {
|
|
var _w = w >> i
|
|
var _h = h >> i
|
|
if (!_w || !_h) break
|
|
gl.texImage2D(
|
|
GL_TEXTURE_2D,
|
|
i,
|
|
texture.format,
|
|
_w,
|
|
_h,
|
|
0,
|
|
texture.format,
|
|
texture.type,
|
|
null)
|
|
}
|
|
tempRestore()
|
|
|
|
// also, recompute the texture size.
|
|
if (config.profile) {
|
|
texture.stats.size = getTextureSize(
|
|
texture.internalformat,
|
|
texture.type,
|
|
w,
|
|
h,
|
|
false,
|
|
false)
|
|
}
|
|
|
|
return reglTexture2D
|
|
}
|
|
|
|
reglTexture2D(a, b)
|
|
|
|
reglTexture2D.subimage = subimage
|
|
reglTexture2D.resize = resize
|
|
reglTexture2D._reglType = 'texture2d'
|
|
reglTexture2D._texture = texture
|
|
if (config.profile) {
|
|
reglTexture2D.stats = texture.stats
|
|
}
|
|
reglTexture2D.destroy = function () {
|
|
texture.decRef()
|
|
}
|
|
|
|
return reglTexture2D
|
|
}
|
|
|
|
function createTextureCube (a0, a1, a2, a3, a4, a5) {
|
|
var texture = new REGLTexture(GL_TEXTURE_CUBE_MAP)
|
|
textureSet[texture.id] = texture
|
|
stats.cubeCount++
|
|
|
|
var faces = new Array(6)
|
|
|
|
function reglTextureCube (a0, a1, a2, a3, a4, a5) {
|
|
var i
|
|
var texInfo = texture.texInfo
|
|
TexInfo.call(texInfo)
|
|
for (i = 0; i < 6; ++i) {
|
|
faces[i] = allocMipMap()
|
|
}
|
|
|
|
if (typeof a0 === 'number' || !a0) {
|
|
var s = (a0 | 0) || 1
|
|
for (i = 0; i < 6; ++i) {
|
|
parseMipMapFromShape(faces[i], s, s)
|
|
}
|
|
} else if (typeof a0 === 'object') {
|
|
if (a1) {
|
|
parseMipMapFromObject(faces[0], a0)
|
|
parseMipMapFromObject(faces[1], a1)
|
|
parseMipMapFromObject(faces[2], a2)
|
|
parseMipMapFromObject(faces[3], a3)
|
|
parseMipMapFromObject(faces[4], a4)
|
|
parseMipMapFromObject(faces[5], a5)
|
|
} else {
|
|
parseTexInfo(texInfo, a0)
|
|
parseFlags(texture, a0)
|
|
if ('faces' in a0) {
|
|
var faceInput = a0.faces
|
|
check(Array.isArray(faceInput) && faceInput.length === 6,
|
|
'cube faces must be a length 6 array')
|
|
for (i = 0; i < 6; ++i) {
|
|
check(typeof faceInput[i] === 'object' && !!faceInput[i],
|
|
'invalid input for cube map face')
|
|
copyFlags(faces[i], texture)
|
|
parseMipMapFromObject(faces[i], faceInput[i])
|
|
}
|
|
} else {
|
|
for (i = 0; i < 6; ++i) {
|
|
parseMipMapFromObject(faces[i], a0)
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
check.raise('invalid arguments to cube map')
|
|
}
|
|
|
|
copyFlags(texture, faces[0])
|
|
check.optional(function () {
|
|
if (!limits.npotTextureCube) {
|
|
check(isPow2(texture.width) && isPow2(texture.height), 'your browser does not support non power or two texture dimensions')
|
|
}
|
|
})
|
|
|
|
if (texInfo.genMipmaps) {
|
|
texture.mipmask = (faces[0].width << 1) - 1
|
|
} else {
|
|
texture.mipmask = faces[0].mipmask
|
|
}
|
|
|
|
check.textureCube(texture, texInfo, faces, limits)
|
|
texture.internalformat = faces[0].internalformat
|
|
|
|
reglTextureCube.width = faces[0].width
|
|
reglTextureCube.height = faces[0].height
|
|
|
|
tempBind(texture)
|
|
for (i = 0; i < 6; ++i) {
|
|
setMipMap(faces[i], GL_TEXTURE_CUBE_MAP_POSITIVE_X + i)
|
|
}
|
|
setTexInfo(texInfo, GL_TEXTURE_CUBE_MAP)
|
|
tempRestore()
|
|
|
|
if (config.profile) {
|
|
texture.stats.size = getTextureSize(
|
|
texture.internalformat,
|
|
texture.type,
|
|
reglTextureCube.width,
|
|
reglTextureCube.height,
|
|
texInfo.genMipmaps,
|
|
true)
|
|
}
|
|
|
|
reglTextureCube.format = textureFormatsInvert[texture.internalformat]
|
|
reglTextureCube.type = textureTypesInvert[texture.type]
|
|
|
|
reglTextureCube.mag = magFiltersInvert[texInfo.magFilter]
|
|
reglTextureCube.min = minFiltersInvert[texInfo.minFilter]
|
|
|
|
reglTextureCube.wrapS = wrapModesInvert[texInfo.wrapS]
|
|
reglTextureCube.wrapT = wrapModesInvert[texInfo.wrapT]
|
|
|
|
for (i = 0; i < 6; ++i) {
|
|
freeMipMap(faces[i])
|
|
}
|
|
|
|
return reglTextureCube
|
|
}
|
|
|
|
function subimage (face, image, x_, y_, level_) {
|
|
check(!!image, 'must specify image data')
|
|
check(typeof face === 'number' && face === (face | 0) &&
|
|
face >= 0 && face < 6, 'invalid face')
|
|
|
|
var x = x_ | 0
|
|
var y = y_ | 0
|
|
var level = level_ | 0
|
|
|
|
var imageData = allocImage()
|
|
copyFlags(imageData, texture)
|
|
imageData.width = 0
|
|
imageData.height = 0
|
|
parseImage(imageData, image)
|
|
imageData.width = imageData.width || ((texture.width >> level) - x)
|
|
imageData.height = imageData.height || ((texture.height >> level) - y)
|
|
|
|
check(
|
|
texture.type === imageData.type &&
|
|
texture.format === imageData.format &&
|
|
texture.internalformat === imageData.internalformat,
|
|
'incompatible format for texture.subimage')
|
|
check(
|
|
x >= 0 && y >= 0 &&
|
|
x + imageData.width <= texture.width &&
|
|
y + imageData.height <= texture.height,
|
|
'texture.subimage write out of bounds')
|
|
check(
|
|
texture.mipmask & (1 << level),
|
|
'missing mipmap data')
|
|
check(
|
|
imageData.data || imageData.element || imageData.needsCopy,
|
|
'missing image data')
|
|
|
|
tempBind(texture)
|
|
setSubImage(imageData, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, x, y, level)
|
|
tempRestore()
|
|
|
|
freeImage(imageData)
|
|
|
|
return reglTextureCube
|
|
}
|
|
|
|
function resize (radius_) {
|
|
var radius = radius_ | 0
|
|
if (radius === texture.width) {
|
|
return
|
|
}
|
|
|
|
reglTextureCube.width = texture.width = radius
|
|
reglTextureCube.height = texture.height = radius
|
|
|
|
tempBind(texture)
|
|
for (var i = 0; i < 6; ++i) {
|
|
for (var j = 0; texture.mipmask >> j; ++j) {
|
|
gl.texImage2D(
|
|
GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
|
|
j,
|
|
texture.format,
|
|
radius >> j,
|
|
radius >> j,
|
|
0,
|
|
texture.format,
|
|
texture.type,
|
|
null)
|
|
}
|
|
}
|
|
tempRestore()
|
|
|
|
if (config.profile) {
|
|
texture.stats.size = getTextureSize(
|
|
texture.internalformat,
|
|
texture.type,
|
|
reglTextureCube.width,
|
|
reglTextureCube.height,
|
|
false,
|
|
true)
|
|
}
|
|
|
|
return reglTextureCube
|
|
}
|
|
|
|
reglTextureCube(a0, a1, a2, a3, a4, a5)
|
|
|
|
reglTextureCube.subimage = subimage
|
|
reglTextureCube.resize = resize
|
|
reglTextureCube._reglType = 'textureCube'
|
|
reglTextureCube._texture = texture
|
|
if (config.profile) {
|
|
reglTextureCube.stats = texture.stats
|
|
}
|
|
reglTextureCube.destroy = function () {
|
|
texture.decRef()
|
|
}
|
|
|
|
return reglTextureCube
|
|
}
|
|
|
|
// Called when regl is destroyed
|
|
function destroyTextures () {
|
|
for (var i = 0; i < numTexUnits; ++i) {
|
|
gl.activeTexture(GL_TEXTURE0 + i)
|
|
gl.bindTexture(GL_TEXTURE_2D, null)
|
|
textureUnits[i] = null
|
|
}
|
|
values(textureSet).forEach(destroy)
|
|
|
|
stats.cubeCount = 0
|
|
stats.textureCount = 0
|
|
}
|
|
|
|
if (config.profile) {
|
|
stats.getTotalTextureSize = function () {
|
|
var total = 0
|
|
Object.keys(textureSet).forEach(function (key) {
|
|
total += textureSet[key].stats.size
|
|
})
|
|
return total
|
|
}
|
|
}
|
|
|
|
function restoreTextures () {
|
|
for (var i = 0; i < numTexUnits; ++i) {
|
|
var tex = textureUnits[i]
|
|
if (tex) {
|
|
tex.bindCount = 0
|
|
tex.unit = -1
|
|
textureUnits[i] = null
|
|
}
|
|
}
|
|
|
|
values(textureSet).forEach(function (texture) {
|
|
texture.texture = gl.createTexture()
|
|
gl.bindTexture(texture.target, texture.texture)
|
|
for (var i = 0; i < 32; ++i) {
|
|
if ((texture.mipmask & (1 << i)) === 0) {
|
|
continue
|
|
}
|
|
if (texture.target === GL_TEXTURE_2D) {
|
|
gl.texImage2D(GL_TEXTURE_2D,
|
|
i,
|
|
texture.internalformat,
|
|
texture.width >> i,
|
|
texture.height >> i,
|
|
0,
|
|
texture.internalformat,
|
|
texture.type,
|
|
null)
|
|
} else {
|
|
for (var j = 0; j < 6; ++j) {
|
|
gl.texImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + j,
|
|
i,
|
|
texture.internalformat,
|
|
texture.width >> i,
|
|
texture.height >> i,
|
|
0,
|
|
texture.internalformat,
|
|
texture.type,
|
|
null)
|
|
}
|
|
}
|
|
}
|
|
setTexInfo(texture.texInfo, texture.target)
|
|
})
|
|
}
|
|
|
|
function refreshTextures () {
|
|
for (var i = 0; i < numTexUnits; ++i) {
|
|
var tex = textureUnits[i]
|
|
if (tex) {
|
|
tex.bindCount = 0
|
|
tex.unit = -1
|
|
textureUnits[i] = null
|
|
}
|
|
gl.activeTexture(GL_TEXTURE0 + i)
|
|
gl.bindTexture(GL_TEXTURE_2D, null)
|
|
gl.bindTexture(GL_TEXTURE_CUBE_MAP, null)
|
|
}
|
|
}
|
|
|
|
return {
|
|
create2D: createTexture2D,
|
|
createCube: createTextureCube,
|
|
clear: destroyTextures,
|
|
getTexture: function (wrapper) {
|
|
return null
|
|
},
|
|
restore: restoreTextures,
|
|
refresh: refreshTextures
|
|
}
|
|
}
|