diff --git a/docs/api-reference/core/resources/texture-view.md b/docs/api-reference/core/resources/texture-view.md new file mode 100644 index 0000000000..eeb9e3cb75 --- /dev/null +++ b/docs/api-reference/core/resources/texture-view.md @@ -0,0 +1,37 @@ +## TextureView + +A `TextureView` is a view onto some subset of the texture subresources defined by a particular `Texture`. + +### Subresource Selection + +The set of texture subresources of a texture view view, is the subset of the subresources +of the associated `Texture` for which each subresource satisfies the following: +- The mipmap level of s is ≥ props.baseMipLevel and < props.baseMipLevel + props.mipLevelCount. +- The array layer of s is ≥ props.baseArrayLayer and < props.baseArrayLayer + props.arrayLayerCount. +- The aspect of s is in the set of aspects of props.aspect. + +### Render Extent + +There is an implicit "render extent" associated with a renderable `TextureView`. +This render extent depends on the baseMipLevel. + +### TextureView Aliasing + +Two `TextureView` objects are texture-view-aliasing if and only if their sets of subresources intersect. + +## Usage + +```ts +const texture = device.createTexture({...}); +const textureView = texture.createView({...}); +``` + +## Types + +### TextureViewProps + +## Methods + +### `constructor` + +The constructor for `TextureView` should not be called directly. Use `Texture.createView()` instead. diff --git a/modules/core/src/adapter/resources/framebuffer.ts b/modules/core/src/adapter/resources/framebuffer.ts index 20df973893..32fe9b7c99 100644 --- a/modules/core/src/adapter/resources/framebuffer.ts +++ b/modules/core/src/adapter/resources/framebuffer.ts @@ -5,13 +5,14 @@ import type {ColorTextureFormat, DepthStencilTextureFormat, TextureFormat} from import type {Device} from '../device'; import {Resource, ResourceProps} from './resource'; import {Texture} from './texture'; +import {TextureView} from './texture-view'; import {log} from '../../utils/log'; export type FramebufferProps = ResourceProps & { width?: number; height?: number; - colorAttachments?: (Texture | ColorTextureFormat)[]; - depthStencilAttachment?: (Texture | DepthStencilTextureFormat) | null; + colorAttachments?: (Texture | TextureView | ColorTextureFormat)[]; + depthStencilAttachment?: (Texture | TextureView | DepthStencilTextureFormat) | null; }; /** @@ -36,9 +37,9 @@ export abstract class Framebuffer extends Resource { /** Height of all attachments in this framebuffer */ height: number; /** Color attachments */ - colorAttachments: Texture[] = []; + colorAttachments: (Texture | TextureView)[] = []; /** Depth-stencil attachment, if provided */ - depthStencilAttachment: Texture | null = null; + depthStencilAttachment: Texture | TextureView | null = null; constructor(device: Device, props: FramebufferProps = {}) { super(device, props, Framebuffer.defaultProps); @@ -70,59 +71,8 @@ export abstract class Framebuffer extends Resource { } } - // /** Returns fully populated attachment object. */ - // protected normalizeColorAttachment( - // attachment: Texture | ColorTextureFormat - // ): Required { - - // const COLOR_ATTACHMENT_DEFAULTS: Required = { - // texture: undefined!, - // format: undefined!, - // clearValue: [0.0, 0.0, 0.0, 0.0], - // loadOp: 'clear', - // storeOp: 'store' - // }; - - // if (attachment instanceof Texture) { - // return {...COLOR_ATTACHMENT_DEFAULTS, texture: attachment}; - // } - // if (typeof attachment === 'string') { - // return {...COLOR_ATTACHMENT_DEFAULTS, format: attachment}; - // } - // return {...COLOR_ATTACHMENT_DEFAULTS, ...attachment}; - // } - - // /** Wraps texture inside fully populated attachment object. */ - // protected normalizeDepthStencilAttachment( - // attachment: DepthStencilAttachment | Texture | DepthStencilTextureFormat - // ): Required { - // const DEPTH_STENCIL_ATTACHMENT_DEFAULTS: Required = { - // texture: undefined!, - // format: undefined!, - - // depthClearValue: 1.0, - // depthLoadOp: 'clear', - // depthStoreOp: 'store', - // depthReadOnly: false, - - // stencilClearValue: 0, - // stencilLoadOp: 'clear', - // stencilStoreOp: 'store', - // stencilReadOnly: false - // }; - - // if (typeof attachment === 'string') { - // return {...DEPTH_STENCIL_ATTACHMENT_DEFAULTS, format: attachment}; - // } - // // @ts-expect-error attachment instanceof Texture doesn't cover Renderbuffer - // if (attachment.handle || attachment instanceof Texture) { - // return {...DEPTH_STENCIL_ATTACHMENT_DEFAULTS, texture: attachment as Texture}; - // } - // return {...DEPTH_STENCIL_ATTACHMENT_DEFAULTS, ...attachment}; - // } - /** Auto creates any textures */ - protected autoCreateAttachmentTextures(){ + protected autoCreateAttachmentTextures(): void { this.colorAttachments = this.props.colorAttachments.map(attachment => { if (typeof attachment === 'string') { const texture = this.createColorTexture(attachment); @@ -195,36 +145,88 @@ export abstract class Framebuffer extends Resource { this.attachResource(resizedTexture); } } - - /** Create a color attachment for WebGL * - protected override createColorTexture(colorAttachment: Required): Required { - return this.device._createTexture({ - id: `${this.id}-color`, - data: null, // reserves texture memory, but texels are undefined - format, - // type: GL.UNSIGNED_BYTE, - width: this.width, - height: this.height, - // Note: Mipmapping can be disabled by texture resource when we resize the texture - // to a non-power-of-two dimenstion (NPOT texture) under WebGL1. To have consistant - // behavior we always disable mipmaps. - mipmaps: false, - // Set MIN and MAG filtering parameters so mipmaps are not used in sampling. - // Use LINEAR so subpixel algos like fxaa work. - // Set WRAP modes that support NPOT textures too. - sampler: { - minFilter: 'linear', - magFilter: 'linear', - addressModeU: 'clamp-to-edge', - addressModeV: 'clamp-to-edge' - } - // parameters: { - // [GL.TEXTURE_MIN_FILTER]: GL.LINEAR, - // [GL.TEXTURE_MAG_FILTER]: GL.LINEAR, - // [GL.TEXTURE_WRAP_S]: GL.CLAMP_TO_EDGE, - // [GL.TEXTURE_WRAP_T]: GL.CLAMP_TO_EDGE - // } - }); - } - */ } + +// TODO - remove if not needed + +// Create a color attachment for WebGL * +// protected override createColorTexture(colorAttachment: Required): Required { +// return this.device._createTexture({ +// id: `${this.id}-color`, +// data: null, // reserves texture memory, but texels are undefined +// format, +// // type: GL.UNSIGNED_BYTE, +// width: this.width, +// height: this.height, +// // Note: Mipmapping can be disabled by texture resource when we resize the texture +// // to a non-power-of-two dimenstion (NPOT texture) under WebGL1. To have consistant +// // behavior we always disable mipmaps. +// mipmaps: false, +// // Set MIN and MAG filtering parameters so mipmaps are not used in sampling. +// // Use LINEAR so subpixel algos like fxaa work. +// // Set WRAP modes that support NPOT textures too. +// sampler: { +// minFilter: 'linear', +// magFilter: 'linear', +// addressModeU: 'clamp-to-edge', +// addressModeV: 'clamp-to-edge' +// } +// // parameters: { +// // [GL.TEXTURE_MIN_FILTER]: GL.LINEAR, +// // [GL.TEXTURE_MAG_FILTER]: GL.LINEAR, +// // [GL.TEXTURE_WRAP_S]: GL.CLAMP_TO_EDGE, +// // [GL.TEXTURE_WRAP_T]: GL.CLAMP_TO_EDGE +// // } +// }); +// } + +// /** Returns fully populated attachment object. */ +// protected normalizeColorAttachment( +// attachment: Texture | ColorTextureFormat +// ): Required { + +// const COLOR_ATTACHMENT_DEFAULTS: Required = { +// texture: undefined!, +// format: undefined!, +// clearValue: [0.0, 0.0, 0.0, 0.0], +// loadOp: 'clear', +// storeOp: 'store' +// }; + +// if (attachment instanceof Texture) { +// return {...COLOR_ATTACHMENT_DEFAULTS, texture: attachment}; +// } +// if (typeof attachment === 'string') { +// return {...COLOR_ATTACHMENT_DEFAULTS, format: attachment}; +// } +// return {...COLOR_ATTACHMENT_DEFAULTS, ...attachment}; +// } + +// /** Wraps texture inside fully populated attachment object. */ +// protected normalizeDepthStencilAttachment( +// attachment: DepthStencilAttachment | Texture | DepthStencilTextureFormat +// ): Required { +// const DEPTH_STENCIL_ATTACHMENT_DEFAULTS: Required = { +// texture: undefined!, +// format: undefined!, + +// depthClearValue: 1.0, +// depthLoadOp: 'clear', +// depthStoreOp: 'store', +// depthReadOnly: false, + +// stencilClearValue: 0, +// stencilLoadOp: 'clear', +// stencilStoreOp: 'store', +// stencilReadOnly: false +// }; + +// if (typeof attachment === 'string') { +// return {...DEPTH_STENCIL_ATTACHMENT_DEFAULTS, format: attachment}; +// } +// // @ts-expect-error attachment instanceof Texture doesn't cover Renderbuffer +// if (attachment.handle || attachment instanceof Texture) { +// return {...DEPTH_STENCIL_ATTACHMENT_DEFAULTS, texture: attachment as Texture}; +// } +// return {...DEPTH_STENCIL_ATTACHMENT_DEFAULTS, ...attachment}; +// } diff --git a/modules/core/src/adapter/resources/texture-view.ts b/modules/core/src/adapter/resources/texture-view.ts new file mode 100644 index 0000000000..e0570e0954 --- /dev/null +++ b/modules/core/src/adapter/resources/texture-view.ts @@ -0,0 +1,50 @@ +// luma.gl, MIT license +// Copyright (c) vis.gl contributors + +import type {Device} from '../device'; +import type {Texture} from './texture'; +import type {TextureFormat} from '../types/texture-formats'; +import {Resource, ResourceProps} from './resource'; + +/** Properties for initializing a texture view */ +export type TextureViewProps = ResourceProps & { + /** The format of the texture view. Must be either the format of the texture or one of the viewFormats specified during its creation. */ + format?: TextureFormat; + /** The dimension to view the texture as. */ + dimension?: '1d' | '2d' | '2d-array' | 'cube' | 'cube-array' | '3d'; + /** Which aspect(s) of the texture are accessible to the texture view. default "all"*/ + aspect?: 'all' | 'stencil-only' | 'depth-only'; + /** The first (most detailed) mipmap level accessible to the texture view. default 0*/ + baseMipLevel?: number; + /** How many mipmap levels, starting with baseMipLevel, are accessible to the texture view. */ + mipLevelCount: number; + /** The index of the first array layer accessible to the texture view. default 0 */ + baseArrayLayer?: number; + /** How many array layers, starting with baseArrayLayer, are accessible to the texture view. */ + arrayLayerCount: number; +}; + +/** Immutable TextureView object */ +export abstract class TextureView extends Resource { + static override defaultProps: Required = { + ...Resource.defaultProps, + format: undefined, + dimension: undefined, + aspect: 'all', + baseMipLevel: 0, + mipLevelCount: undefined, + baseArrayLayer: 0, + arrayLayerCount: undefined + }; + + abstract texture: Texture; + + override get [Symbol.toStringTag](): string { + return 'TextureView'; + } + + /** Should not be constructed directly. Use `texture.createView(props)` */ + constructor(device: Device, props: TextureViewProps & {texture: Texture}) { + super(device, props, TextureView.defaultProps); + } +} diff --git a/modules/core/src/adapter/types/shader-layout.ts b/modules/core/src/adapter/types/shader-layout.ts index 774ef4eb3c..053b533dca 100644 --- a/modules/core/src/adapter/types/shader-layout.ts +++ b/modules/core/src/adapter/types/shader-layout.ts @@ -5,6 +5,7 @@ import {AccessorObject} from '../types/accessor'; import type {Buffer} from '../resources/buffer'; import type {Sampler} from '../resources/sampler'; import type {Texture} from '../resources/texture'; +import type {TextureView} from '../resources/texture-view'; /** * Describes all shader binding points for a `RenderPipeline` or `ComputePipeline` @@ -148,7 +149,7 @@ type StorageTextureBindingLayout = { // BINDINGS /** Binding value */ -export type Binding = Texture | Sampler | Buffer | {buffer: Buffer; offset?: number; size?: number}; +export type Binding = TextureView | Texture | Sampler | Buffer | {buffer: Buffer; offset?: number; size?: number}; // SHADER LAYOUTS diff --git a/modules/core/src/index.ts b/modules/core/src/index.ts index e6e6c1aa96..205e8251ba 100644 --- a/modules/core/src/index.ts +++ b/modules/core/src/index.ts @@ -26,6 +26,8 @@ export type {BufferProps} from './adapter/resources/buffer'; export {Buffer} from './adapter/resources/buffer'; export type {TextureProps, TextureData} from './adapter/resources/texture'; export {Texture} from './adapter/resources/texture'; +export type {TextureViewProps} from './adapter/resources/texture-view'; +export {TextureView} from './adapter/resources/texture-view'; export type {ExternalTextureProps} from './adapter/resources/external-texture'; export {ExternalTexture} from './adapter/resources/external-texture'; export type {ShaderProps} from './adapter/resources/shader'; diff --git a/modules/webgl/src/adapter/resources/webgl-command-buffer.ts b/modules/webgl/src/adapter/resources/webgl-command-buffer.ts index b1536774b5..882953cde2 100644 --- a/modules/webgl/src/adapter/resources/webgl-command-buffer.ts +++ b/modules/webgl/src/adapter/resources/webgl-command-buffer.ts @@ -156,7 +156,7 @@ function _copyTextureToBuffer(device: WebGLDevice, options: CopyTextureToBufferO const webglBuffer = destination as WEBGLBuffer; const sourceWidth = width || framebuffer.width; const sourceHeight = height || framebuffer.height; - const sourceParams = getWebGLTextureParameters(framebuffer.texture.format); + const sourceParams = getWebGLTextureParameters(framebuffer.texture.props.format); const sourceFormat = sourceParams.dataFormat; const sourceType = sourceParams.type; diff --git a/modules/webgl/src/adapter/resources/webgl-framebuffer.ts b/modules/webgl/src/adapter/resources/webgl-framebuffer.ts index 6fe8940e80..52097fa0f4 100644 --- a/modules/webgl/src/adapter/resources/webgl-framebuffer.ts +++ b/modules/webgl/src/adapter/resources/webgl-framebuffer.ts @@ -6,11 +6,11 @@ import {Framebuffer, Texture, assert} from '@luma.gl/core'; import {GL} from '@luma.gl/constants'; import {WebGLDevice} from '../webgl-device'; import {WEBGLTexture} from './webgl-texture'; +import {WEBGLTextureView} from './webgl-texture-view'; import {WEBGLRenderbuffer} from '../objects/webgl-renderbuffer'; import {getDepthStencilAttachmentWebGL} from '../converters/texture-formats'; -export type TextureAttachment = [Texture, number?, number?]; -export type Attachment = WEBGLTexture | WEBGLRenderbuffer | TextureAttachment; +export type Attachment = WEBGLTextureView | WEBGLTexture | WEBGLRenderbuffer; /** luma.gl Framebuffer, WebGL implementation */ export class WEBGLFramebuffer extends Framebuffer { @@ -54,7 +54,7 @@ export class WEBGLFramebuffer extends Framebuffer { if (this.depthStencilAttachment) { this._attachOne( - getDepthStencilAttachmentWebGL(this.depthStencilAttachment.format), + getDepthStencilAttachmentWebGL(this.depthStencilAttachment.props.format), this.depthStencilAttachment as WEBGLTexture ); } @@ -140,6 +140,10 @@ export class WEBGLFramebuffer extends Framebuffer { } else if (attachment instanceof WEBGLTexture) { this._attachTexture(attachmentPoint, attachment, 0, 0); return attachment; + } else if (attachment instanceof WEBGLTextureView) { + const textureView = attachment; + this._attachTexture(attachmentPoint, textureView.texture, textureView.props.baseMipLevel, textureView.props.baseArrayLayer); + return attachment.texture; } throw new Error('attach'); } diff --git a/modules/webgl/src/adapter/resources/webgl-texture-view.ts b/modules/webgl/src/adapter/resources/webgl-texture-view.ts new file mode 100644 index 0000000000..77dc851b61 --- /dev/null +++ b/modules/webgl/src/adapter/resources/webgl-texture-view.ts @@ -0,0 +1,27 @@ +// luma.gl, MIT license +// Copyright (c) vis.gl contributors + +import type {Device, TextureViewProps} from '@luma.gl/core'; +// import {decodeTextureFormat} from '@luma.gl/core'; +import {TextureView, Texture} from '@luma.gl/core'; + +import {WebGLDevice} from '../webgl-device'; +import {WEBGLTexture} from './webgl-texture'; + +export class WEBGLTextureView extends TextureView { + readonly device: WebGLDevice; + readonly gl: WebGL2RenderingContext; + readonly handle: WebGLTexture; + + readonly texture: WEBGLTexture; + + constructor(device: Device, props: TextureViewProps & {texture: WEBGLTexture}) { + super(device, {...Texture.defaultProps, ...props}); + + this.device = device as WebGLDevice; + this.gl = this.device.gl; + this.handle = null; + + this.texture = props.texture; + } +} diff --git a/modules/webgpu/src/adapter/resources/webgpu-texture-view.ts b/modules/webgpu/src/adapter/resources/webgpu-texture-view.ts new file mode 100644 index 0000000000..c870714d06 --- /dev/null +++ b/modules/webgpu/src/adapter/resources/webgpu-texture-view.ts @@ -0,0 +1,40 @@ +// luma.gl, MIT license +// Copyright (c) vis.gl contributors + +import {TextureView, TextureViewProps} from '@luma.gl/core'; +import type {WebGPUDevice} from '../webgpu-device'; +import type {WebGPUTexture} from './webgpu-texture'; + +export type WebGPUTextureViewProps = TextureViewProps & { + handle?: GPUTextureView; +} + +/** + * + */ +export class WebGPUTextureView extends TextureView { + readonly device: WebGPUDevice; + readonly handle: GPUTextureView; + readonly texture: WebGPUTexture; + + constructor(device: WebGPUDevice, props: WebGPUTextureViewProps & {texture: WebGPUTexture}) { + super(device, props); + this.device = device; + this.texture = props.texture; + + this.handle = this.handle || this.texture.handle.createView({ + format: (props.format || this.texture.format) as GPUTextureFormat, + dimension: props.dimension || this.texture.dimension, + aspect: props.aspect, + baseMipLevel: props.baseMipLevel, + mipLevelCount: props.mipLevelCount, // GPUIntegerCoordinate; + baseArrayLayer: props.baseArrayLayer, // GPUIntegerCoordinate; + arrayLayerCount: props.arrayLayerCount, // GPUIntegerCoordinate; + }); + this.handle.label = this.props.id; + } + + override destroy(): void { + // this.handle.destroy(); + } +}