Skip to content

Commit

Permalink
Texture array updates WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
heretique committed Jun 13, 2024
1 parent 24d4328 commit a784bff
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 77 deletions.
8 changes: 4 additions & 4 deletions src/framework/handlers/texture.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,18 @@ const _completePartialMipmapChain = function (texture) {
for (let level = texture._levels.length; level < requiredMipLevels; ++level) {
const width = Math.max(1, texture._width >> (level - 1));
const height = Math.max(1, texture._height >> (level - 1));
if (texture.cubemap) {
if (texture.cubemap || texture.array) {
const mips = [];
for (let face = 0; face < 6; ++face) {
mips.push(downsample(width, height, texture._levels[level - 1][face]));
for (let slice = 0; slice < texture.slices; ++slice) {
mips.push(downsample(width, height, texture._levels[level - 1][slice]));
}
texture._levels.push(mips);
} else {
texture._levels.push(downsample(width, height, texture._levels[level - 1]));
}
}

texture._levelsUpdated = texture.cubemap ? [[true, true, true, true, true, true]] : [true];
texture._levelsUpdated = (texture.cubemap || texture.array) ? [Array(texture.slices).fill(true)] : [true];
};

/**
Expand Down
2 changes: 1 addition & 1 deletion src/platform/graphics/texture-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ class TextureUtils {
/**
* Calculate the GPU memory required for a texture.
*
* @param {string} dimension - Texture's dimension
* @param {number} width - Texture's width.
* @param {number} height - Texture's height.
* @param {number} slices - Texture's slices.
* @param {number} format - Texture's pixel format PIXELFORMAT_***.
* @param {boolean} isVolume - True if the texture is a volume texture, false otherwise.
* @param {boolean} mipmaps - True if the texture includes mipmaps, false otherwise.
* @returns {number} The number of bytes of GPU memory required for the texture.
* @ignore
Expand Down
81 changes: 47 additions & 34 deletions src/platform/graphics/texture.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,6 @@ class Texture {
/** @protected */
_invalid = false;

/** @protected */
_lockedLevel = -1;

/** @protected */
_lockedMode = TEXTURELOCK_NONE;

Expand Down Expand Up @@ -295,7 +292,7 @@ class Texture {
if (this._levels) {
this.upload(options.immediate ?? false);
} else {
this._levels = this.cubemap ? [[null, null, null, null, null, null]] : [null];
this._levels = (this.cubemap || this.array) ? [Array(this._slices).fill(null)] : [null];
}

// track the texture
Expand Down Expand Up @@ -787,7 +784,7 @@ class Texture {

// Force a full resubmission of the texture to the GPU (used on a context restore event)
dirtyAll() {
this._levelsUpdated = this.cubemap ? [[true, true, true, true, true, true]] : [true];
this._levelsUpdated = (this.cubemap || this.array) ? [Array(this._slices).fill(true)] : [true];

this._needsUpload = true;
this._needsMipmapsUpload = this._mipmaps;
Expand All @@ -804,6 +801,8 @@ class Texture {
* to 0.
* @param {number} [options.face] - If the texture is a cubemap, this is the index of the face
* to lock.
* @param {number} [options.slice] - If the texture is a texture array, this is the index of the
* slice to lock.
* @param {number} [options.mode] - The lock mode. Can be:
* - {@link TEXTURELOCK_READ}
* - {@link TEXTURELOCK_WRITE}
Expand All @@ -815,6 +814,7 @@ class Texture {
// Initialize options to some sensible defaults
options.level ??= 0;
options.face ??= 0;
options.slice ??= 0;
options.mode ??= TEXTURELOCK_WRITE;

Debug.assert(
Expand All @@ -829,19 +829,38 @@ class Texture {
this
);

Debug.assert(
options.level >= 0 && options.level < this._levels.length,
'Invalid mip level',
this
);

Debug.assert(
((this.cubemap || this.array) && options.mode === TEXTURELOCK_WRITE) ? options.level === 0 : true,
'Only mip level 0 can be locked for writing for cubemaps and texture arrays',
this
);

this._lockedMode = options.mode;
this._lockedLevel = options.level;

const levels = this.cubemap ? this._levels[options.face] : this._levels;
const levels = this.cubemap ? this._levels[options.face] : this.array ? this._levels[options.slice] : this._levels;
if (levels[options.level] === null) {
// allocate storage for this mip level
const width = Math.max(1, this._width >> options.level);
const height = Math.max(1, this._height >> options.level);
const depth = Math.max(1, (this._dimension === TEXTUREDIMENSION_3D ? this._slices : 1) >> options.level);
const depth = Math.max(1, this.depth >> options.level);
const data = new ArrayBuffer(TextureUtils.calcLevelGpuSize(width, height, depth, this._format));
levels[options.level] = new (getPixelFormatArrayType(this._format))(data);
}

if (this._lockedMode === TEXTURELOCK_WRITE) {
if (this.cubemap || this.array) {
this._levelsUpdated[0][options.face ?? options.slice] = true;
} else {
this._levelsUpdated[0] = true;
}
}

return levels[options.level];
}

Expand All @@ -851,22 +870,21 @@ class Texture {
*
* @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement|HTMLCanvasElement[]|HTMLImageElement[]|HTMLVideoElement[]} source - A
* canvas, image or video element, or an array of 6 canvas, image or video elements.
* @param {number} [mipLevel] - A non-negative integer specifying the image level of detail.
* Defaults to 0, which represents the base image source. A level value of N, that is greater
* than 0, represents the image source for the Nth mipmap reduction level.
* @param {boolean} [immediate] - When set to true it forces an immediate upload upon assignment. Defaults to false.
*/
setSource(source, mipLevel = 0, immediate = false) {
setSource(source, immediate = false) {
let invalid = false;
let width, height;

if (this.cubemap) {
if (this.cubemap || this.array) {
if (source[0]) {
// rely on first face sizes
width = source[0].width || 0;
height = source[0].height || 0;

for (let i = 0; i < 6; i++) {
for (let i = 0; i < this._slices; i++) {
const face = source[i];
// cubemap becomes invalid if any condition is not satisfied
if (!face || // face is missing
Expand All @@ -884,9 +902,9 @@ class Texture {

if (!invalid) {
// mark levels as updated
for (let i = 0; i < 6; i++) {
if (this._levels[mipLevel][i] !== source[i])
this._levelsUpdated[mipLevel][i] = true;
for (let i = 0; i < this._slices; i++) {
if (this._levels[0][i] !== source[i])
this._levelsUpdated[0][i] = true;
}
}
} else {
Expand All @@ -896,8 +914,8 @@ class Texture {

if (!invalid) {
// mark level as updated
if (source !== this._levels[mipLevel])
this._levelsUpdated[mipLevel] = true;
if (source !== this._levels[0])
this._levelsUpdated[0] = true;

width = source.width;
height = source.height;
Expand All @@ -912,23 +930,22 @@ class Texture {
this._height = 4;

// remove levels
if (this.cubemap) {
for (let i = 0; i < 6; i++) {
this._levels[mipLevel][i] = null;
this._levelsUpdated[mipLevel][i] = true;
if (this.cubemap || this.array) {
for (let i = 0; i < this._slices; i++) {
this._levels[0][i] = null;
this._levelsUpdated[0][i] = true;
}
} else {
this._levels[mipLevel] = null;
this._levelsUpdated[mipLevel] = true;
this._levels[0] = null;
this._levelsUpdated[0] = true;
}
} else {
// valid texture
if (mipLevel === 0) {
this._width = width;
this._height = height;
}

this._levels[mipLevel] = source;
this._width = width;
this._height = height;

this._levels[0] = source;
}

// valid or changed state of validity
Expand All @@ -944,14 +961,11 @@ class Texture {
* Get the pixel data of the texture. If this is a cubemap then an array of 6 images will be
* returned otherwise a single image.
*
* @param {number} [mipLevel] - A non-negative integer specifying the image level of detail.
* Defaults to 0, which represents the base image source. A level value of N, that is greater
* than 0, represents the image source for the Nth mipmap reduction level.
* @returns {HTMLImageElement} The source image of this texture. Can be null if source not
* assigned for specific image level.
*/
getSource(mipLevel = 0) {
return this._levels[mipLevel];
getSource() {
return this._levels[0];
}

/**
Expand All @@ -967,7 +981,6 @@ class Texture {
if (this._lockedMode === TEXTURELOCK_WRITE) {
this.upload(immediate);
}
this._lockedLevel = -1;
this._lockedMode = TEXTURELOCK_NONE;
}

Expand Down
78 changes: 40 additions & 38 deletions src/platform/graphics/webgl/webgl-texture.js
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ class WebglTexture {

const requiredMipLevels = texture.requiredMipLevels;

if (texture.array) {
if (texture.array && !this._glCreated) {
// for texture arrays we reserve the space in advance
gl.texStorage3D(gl.TEXTURE_2D_ARRAY,
requiredMipLevels,
Expand Down Expand Up @@ -445,10 +445,10 @@ class WebglTexture {
if (device._isBrowserInterface(mipObject[0])) {
// Upload the image, canvas or video
for (face = 0; face < texture.slices; face++) {
if (!texture._levelsUpdated[0][face])
let src = mipObject[face];
if (!texture._levelsUpdated[0][face] || !src)
continue;

let src = mipObject[face];
// Downsize images that are too large to be used as cube maps
if (device._isImageBrowserInterface(src)) {
if (src.width > device.maxCubeMapSize || src.height > device.maxCubeMapSize) {
Expand Down Expand Up @@ -487,10 +487,10 @@ class WebglTexture {
// Upload the byte array
resMult = 1 / Math.pow(2, mipLevel);
for (face = 0; face < texture.slices; face++) {
if (!texture._levelsUpdated[0][face])
const texData = mipObject[face];
if (!texture._levelsUpdated[0][face] || !texData)
continue;

const texData = mipObject[face];
if (texture._compressed) {
if (this._glCreated && texData) {
gl.compressedTexSubImage2D(
Expand Down Expand Up @@ -570,38 +570,40 @@ class WebglTexture {
mipObject);
}
} else if (texture.array && typeof mipObject === "object") {
if (texture._slices === mipObject.length) {
if (texture._compressed) {
for (let index = 0; index < texture._slices; index++) {
gl.compressedTexSubImage3D(
gl.TEXTURE_2D_ARRAY,
mipLevel,
0,
0,
index,
Math.max(Math.floor(texture._width * resMult), 1),
Math.max(Math.floor(texture._height * resMult), 1),
1,
this._glFormat,
mipObject[index]
);
}
} else {
for (let index = 0; index < texture.slices; index++) {
gl.texSubImage3D(
gl.TEXTURE_2D_ARRAY,
mipLevel,
0,
0,
index,
Math.max(Math.floor(texture._width * resMult), 1),
Math.max(Math.floor(texture._height * resMult), 1),
1,
this._glFormat,
this._glPixelType,
mipObject[index]
);
}
if (texture._compressed) {
for (let index = 0; index < texture._slices; index++) {
if (!texture._levelsUpdated[0][index] || !mipObject[index])
continue;
gl.compressedTexSubImage3D(
gl.TEXTURE_2D_ARRAY,
mipLevel,
0,
0,
index,
Math.max(Math.floor(texture._width * resMult), 1),
Math.max(Math.floor(texture._height * resMult), 1),
1,
this._glFormat,
mipObject[index]
);
}
} else {
for (let index = 0; index < texture.slices; index++) {
if (!texture._levelsUpdated[0][index] || !mipObject[index])
continue;
gl.texSubImage3D(
gl.TEXTURE_2D_ARRAY,
mipLevel,
0,
0,
index,
Math.max(Math.floor(texture._width * resMult), 1),
Math.max(Math.floor(texture._height * resMult), 1),
1,
this._glFormat,
this._glPixelType,
mipObject[index]
);
}
}
} else {
Expand Down Expand Up @@ -716,7 +718,7 @@ class WebglTexture {
}

if (texture._needsUpload) {
if (texture.cubemap) {
if (texture.cubemap || texture.array) {
for (let i = 0; i < texture.slices; i++)
texture._levelsUpdated[0][i] = false;
} else {
Expand Down

0 comments on commit a784bff

Please sign in to comment.