diff --git a/src/core/textures/Texture.ts b/src/core/textures/Texture.ts index 98254db..cb4faa2 100644 --- a/src/core/textures/Texture.ts +++ b/src/core/textures/Texture.ts @@ -27,13 +27,14 @@ import type { TextureSizedInternalFormat } from "#TextureSizedInternalFormat"; import type MipmapTarget from "#MipmapTarget"; import type Box from "#Box"; import type { MipData } from "#MipData"; -import type TextureDataType from "#TextureDataType"; -import type TextureFormat from "#TextureFormat"; +import TextureDataType from "#TextureDataType"; +import TextureFormat from "#TextureFormat"; import type { TextureInternalFormat } from "#TextureInternalFormat"; import TextureUncompressedUnsizedInternalFormat from "#TextureUncompressedUnsizedInternalFormat"; import ImmutableError from "#ImmutableError"; import getTextureFormatForTextureInternalFormat from "#getTextureFormatForTextureInternalFormat"; import TextureFormatError from "#TextureFormatError"; +import getTextureDataTypesForTextureInternalFormat from "#getTextureDataTypesForTextureInternalFormat"; /** * A randomly-accessible array. @@ -517,58 +518,90 @@ export default abstract class Texture extends ContextDependent { * Sets the data in a mip. * @param target The mipmap that the mip belongs to. * @param level The level of the mip within its mipmap. + * @param data The data to fill the mip with. + * @param bounds The bounds of the mip to be updated. Defaults to the + * entire mip if not set. * @param format The format of the given data. Must be compatible with the * format of the texture. * @param type The type of the given data. Must be compatible with the * format of the given data. - * @param data The data to fill the mip with. - * @param bounds The bounds of the mip to be updated. Defaults to the entire mip if not set. + * @internal */ public setMip( target: MipmapTarget, level: number, - format: TextureFormat, - type: TextureDataType, data: MipData, - bounds?: Box + bounds?: Box, + format?: TextureFormat, + type?: TextureDataType ): void { - // TODO: Ensure that format, internal format, and data type are compatible. + // Ensure that the format and internal format are compatible. const expectedFormat: TextureFormat | null = getTextureFormatForTextureInternalFormat(this.format); + format ??= expectedFormat ?? TextureFormat.RGBA; + if (expectedFormat !== null && format !== expectedFormat) { + throw new TextureFormatError(); + } - if (expectedFormat !== format) { - if (this.isImmutableFormat || level > 0) { - throw new TextureFormatError(); - } + // Ensure that the data type and internal format are compatible. + const expectedDataTypes: TextureDataType[] | null = + getTextureDataTypesForTextureInternalFormat(this.format); + type ??= expectedDataTypes?.[0] ?? TextureDataType.UNSIGNED_BYTE; + if (expectedDataTypes !== null && !expectedDataTypes.includes(type)) { + throw new TextureFormatError(); + } - // TODO: Use a new default internal format if using an incompatible format for the top mip(?) + // Ensure that the specified bounds (if any) are no bigger than the mip. + const mipDims: number[] = this.getSizeOfMip(level); + if (typeof bounds === "undefined") { + // Default to the entire mip for immutable-format textures. + if (this.isImmutableFormat) { + bounds = { + x: 0, + y: 0, + z: 0, + width: mipDims[0] ?? 0, + height: mipDims[1] ?? 0, + depth: mipDims[2] ?? 0 + }; + } + } else { + // Throw an error if the specified bounding box (if any) is larger than the specified mip. + if ( + bounds.x + bounds.width > (mipDims[0] ?? 0) || + bounds.y + bounds.height > (mipDims[1] ?? 0) || + (bounds.z ?? 0) + (bounds.depth ?? 0) > (mipDims[2] ?? 0) + ) { + throw new RangeError(); + } } // TODO: May need to clear certain cache values when updating data? i.e. dims, max level, max/min LOD, etc. // TODO: Unpack alignment. - this.setMipInternal(target, level, format, type, data, bounds); + this.setMipInternal(target, level, data, format, type, bounds); } /** * Sets the data in a mip. * @param target The mipmap that the mip belongs to. * @param level The level of the mip within its mipmap. + * @param data The data to fill the mip with. * @param format The format of the given data. Must be compatible with the * format of the texture. * @param type The type of the given data. Must be compatible with the * format of the given data. - * @param data The data to fill the mip with. - * @param bounds The bounds of the mip to be updated. Defaults to the entire mip if not set. + * @param bounds The bounds of the mip to be updated. Defaults to the + * entire mip if not set. * @internal */ protected abstract setMipInternal( target: MipmapTarget, level: number, + data: MipData, format: TextureFormat, type: TextureDataType, - data: MipData, bounds?: Box ): void; diff --git a/src/core/textures/Texture2d.ts b/src/core/textures/Texture2d.ts index 124a82e..5e79bf2 100644 --- a/src/core/textures/Texture2d.ts +++ b/src/core/textures/Texture2d.ts @@ -79,23 +79,25 @@ export default class Texture2d extends Texture { * Sets the data in a mip. * @param target The mipmap that the mip belongs to. * @param level The level of the mip within its mipmap. + * @param data The data to fill the mip with. * @param format The format of the given data. Must be compatible with the * format of the texture. * @param type The type of the given data. Must be compatible with the * format of the given data. - * @param data The data to fill the mip with. - * @param bounds The bounds of the mip to be updated. Defaults to the entire mip if not set. + * @param bounds The bounds of the mip to be updated. Defaults to the + * entire mip if not set. + * @internal */ protected override setMipInternal( target: MipmapTarget, level: number, + data: MipData, format: TextureFormat, type: TextureDataType, - data: MipData, - bounds?: Box | undefined + bounds?: Box ): void { throw new Error( - `Method not implemented: Texture2d.setMipInternal(${target}, ${level}, ${format}, ${type}, ${typeof data}, ${typeof bounds})` + `Method not implemented: Texture2d.setMipInternal(${target}, ${level}, ${typeof data}, ${format}, ${type}, ${typeof bounds})` ); } } diff --git a/src/types/Box.ts b/src/types/Box.ts index 8458f63..c9391bc 100644 --- a/src/types/Box.ts +++ b/src/types/Box.ts @@ -1,4 +1,4 @@ -/** A rectangle. */ +/** A rectangle or rectangular prism. */ export default interface Box { /** The X-coordinate of the box. */ x: number; @@ -6,9 +6,15 @@ export default interface Box { /** The Y-coordinate of the box. */ y: number; + /** The Z-coordinate of the box if it is a prism. */ + z?: number; + /** The width of the box. */ width: number; /** The height of the box. */ height: number; + + /** The depth of the box if it is a prism. */ + depth?: number; } diff --git a/src/utility/internal/getTextureDataTypesForTextureInternalFormat.ts b/src/utility/internal/getTextureDataTypesForTextureInternalFormat.ts index 14aae1a..6e08264 100644 --- a/src/utility/internal/getTextureDataTypesForTextureInternalFormat.ts +++ b/src/utility/internal/getTextureDataTypesForTextureInternalFormat.ts @@ -9,7 +9,9 @@ import type { TextureCompressedInternalFormat } from "#TextureCompressedInternal * Gets a list of data types that can be supplied to the given texture internal * format. * @param internalFormat The texture internal format. - * @returns A list of data types. + * @returns A list of data types. If the return value is not null, it is + * guaranteed to contain at least one data type. The first data type in the + * list is a sensible default. * @see [`glTexImage2D`](https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml) * @internal */ @@ -21,7 +23,9 @@ export default function getTextureDataTypesForTextureInternalFormat( * Gets a list of data types that can be supplied to the given texture internal * format. * @param internalFormat The texture internal format. - * @returns A list of data types. + * @returns A list of data types. If the return value is not null, it is + * guaranteed to contain at least one data type. The first data type in the + * list is a sensible default. * @see [`glTexImage2D`](https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml) * @internal */ @@ -33,7 +37,9 @@ export default function getTextureDataTypesForTextureInternalFormat( * Gets a list of data types that can be supplied to the given texture internal * format. * @param internalFormat The texture internal format. - * @returns A list of data types. + * @returns A list of data types. If the return value is not null, it is + * guaranteed to contain at least one data type. The first data type in the + * list is a sensible default. * @see [`glTexImage2D`](https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glTexImage2D.xhtml) * @internal */ @@ -84,7 +90,7 @@ export default function getTextureDataTypesForTextureInternalFormat( case TextureUncompressedSizedInternalFormat.RG16F: case TextureUncompressedSizedInternalFormat.RGB16F: case TextureUncompressedSizedInternalFormat.RGBA16F: - return [TextureDataType.HALF_FLOAT, TextureDataType.FLOAT]; + return [TextureDataType.FLOAT, TextureDataType.HALF_FLOAT]; case TextureUncompressedSizedInternalFormat.R32F: case TextureUncompressedSizedInternalFormat.RG32F: case TextureUncompressedSizedInternalFormat.RGB32F: @@ -114,15 +120,15 @@ export default function getTextureDataTypesForTextureInternalFormat( return [TextureDataType.INT]; case TextureUncompressedSizedInternalFormat.R11F_G11F_B10F: return [ + TextureDataType.FLOAT, TextureDataType.UNSIGNED_INT_10F_11F_11F_REV, - TextureDataType.HALF_FLOAT, - TextureDataType.FLOAT + TextureDataType.HALF_FLOAT ]; case TextureUncompressedSizedInternalFormat.RGB9_E5: return [ + TextureDataType.FLOAT, TextureDataType.UNSIGNED_INT_5_9_9_9_REV, - TextureDataType.HALF_FLOAT, - TextureDataType.FLOAT + TextureDataType.HALF_FLOAT ]; case TextureUncompressedSizedInternalFormat.RGB5_A1: return [