Skip to content

Commit

Permalink
chore: TextureView plumbing (#1967)
Browse files Browse the repository at this point in the history
  • Loading branch information
ibgreen committed Feb 26, 2024
1 parent 58cf04d commit ae36d56
Show file tree
Hide file tree
Showing 9 changed files with 256 additions and 93 deletions.
37 changes: 37 additions & 0 deletions docs/api-reference/core/resources/texture-view.md
Original file line number Diff line number Diff line change
@@ -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.
178 changes: 90 additions & 88 deletions modules/core/src/adapter/resources/framebuffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};

/**
Expand All @@ -36,9 +37,9 @@ export abstract class Framebuffer extends Resource<FramebufferProps> {
/** 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);
Expand Down Expand Up @@ -70,59 +71,8 @@ export abstract class Framebuffer extends Resource<FramebufferProps> {
}
}

// /** Returns fully populated attachment object. */
// protected normalizeColorAttachment(
// attachment: Texture | ColorTextureFormat
// ): Required<ColorAttachment> {

// const COLOR_ATTACHMENT_DEFAULTS: Required<ColorAttachment> = {
// 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<DepthStencilAttachment> {
// const DEPTH_STENCIL_ATTACHMENT_DEFAULTS: Required<DepthStencilAttachment> = {
// 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);
Expand Down Expand Up @@ -195,36 +145,88 @@ export abstract class Framebuffer extends Resource<FramebufferProps> {
this.attachResource(resizedTexture);
}
}

/** Create a color attachment for WebGL *
protected override createColorTexture(colorAttachment: Required<ColorAttachment>): Required<ColorAttachment> {
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<ColorAttachment>): Required<ColorAttachment> {
// 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<ColorAttachment> {

// const COLOR_ATTACHMENT_DEFAULTS: Required<ColorAttachment> = {
// 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<DepthStencilAttachment> {
// const DEPTH_STENCIL_ATTACHMENT_DEFAULTS: Required<DepthStencilAttachment> = {
// 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};
// }
50 changes: 50 additions & 0 deletions modules/core/src/adapter/resources/texture-view.ts
Original file line number Diff line number Diff line change
@@ -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<TextureViewProps> {
static override defaultProps: Required<TextureViewProps> = {
...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);
}
}
3 changes: 2 additions & 1 deletion modules/core/src/adapter/types/shader-layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions modules/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
10 changes: 7 additions & 3 deletions modules/webgl/src/adapter/resources/webgl-framebuffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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
);
}
Expand Down Expand Up @@ -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');
}
Expand Down
27 changes: 27 additions & 0 deletions modules/webgl/src/adapter/resources/webgl-texture-view.ts
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit ae36d56

Please sign in to comment.