mirror of
https://github.com/visgl/luma.gl.git
synced 2025-12-08 17:36:19 +00:00
Async Textures (#876)
This commit is contained in:
parent
fc6da900d4
commit
d9799de1d4
@ -1,35 +0,0 @@
|
||||
# GLBLoader
|
||||
|
||||
Provides functions for parsing or generating the binary GLB containers used by glTF (and certain other formats).
|
||||
|
||||
Takes a JavaScript data structure and encodes it as a JSON blob with binary data (e.g. typed arrays) extracted into a binary chunk.
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
import {GLBLoader, loadFile} from 'loaders.gl';
|
||||
|
||||
loadFile(url, GLBLoader).then(data => {
|
||||
// Application code here
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## `GLBParser` class
|
||||
|
||||
The `GLBLoader` module exposes the `GLBParser` class with the following methods
|
||||
|
||||
### constructor
|
||||
|
||||
Creates a new `GLBParser` instance.
|
||||
|
||||
|
||||
### parse(arrayBuffer : ArrayBuffer) : Object
|
||||
|
||||
Parses an in-memory, GLB formatted `ArrayBuffer` into:
|
||||
|
||||
* `arrayBuffer` - just returns the input array buffer
|
||||
* `binaryByteOffset` - offset to the first byte in the binary chunk
|
||||
* `json` - a JavaScript "JSON" data structure with inlined binary data fields.
|
||||
@ -1,39 +0,0 @@
|
||||
# GLBWriter
|
||||
|
||||
The `GLBWriter` supports encoding of GLB files.
|
||||
|
||||
|
||||
## `GLBBuilder` class
|
||||
|
||||
The `GLBWriter` module exposes the `GLBBuilder` class that allows applications to dynamically build up a hybrid JSON/binary GLB file. It exposes the following methods:
|
||||
|
||||
|
||||
### constructor
|
||||
|
||||
Creates a new `GLBBuilder` instance.
|
||||
|
||||
|
||||
### addBuffer(typedArray : TypedArray, accessor : Object) : Number
|
||||
|
||||
Adds one binary array intended to be loaded back as a WebGL buffer.
|
||||
|
||||
* `typedArray` -
|
||||
* `accessor` - {size, type, ...}.
|
||||
|
||||
Type is autodeduced from the type of the typed array.
|
||||
|
||||
The binary data will be added to the GLB BIN chunk, and glTF `bufferView` and `accessor` fields will be populated.
|
||||
|
||||
|
||||
### addImage(typedArray: TypedArray) : Number
|
||||
|
||||
Adds an image
|
||||
|
||||
The binary image data will be added to the GLB BIN chunk, and glTF `bufferView` and `image` fields will be populated.
|
||||
|
||||
|
||||
### encode(json: Object | Array, options : Object) : ArrayBuffer
|
||||
|
||||
Writes JavaScript JSON data structure into an arrayBuffer that can be written atomically to file, extracting binary fields from the data and placing these in a compact binary chunk following the "stripped" JSON chunk.
|
||||
|
||||
Note: Once all binary buffers have been added `encode()` can be called..
|
||||
@ -1,45 +0,0 @@
|
||||
# KMLLoader
|
||||
|
||||
KML (Keyhole Markup Language) is an XML-based file format used to display geographic data in an Earth browser such as Google Earth (originally named "Keyhole Earth Viewer"). It can be used with any 2D or 3D maps.
|
||||
|
||||
References:
|
||||
|
||||
* [Keyhole Markup Language - Wikipedia](https://en.wikipedia.org/wiki/Keyhole_Markup_Language)
|
||||
* [KML Tutorial - Google](https://developers.google.com/kml/documentation/kml_tut)
|
||||
|
||||
|
||||
## Structure of Data
|
||||
|
||||
The parser will return a JavaScript object with a number of top-level array-valued fields:
|
||||
|
||||
| Field | Description |
|
||||
| --- | --- |
|
||||
| `documents` | |
|
||||
| `folders` | |
|
||||
| `links` | |
|
||||
| `points` | Points |
|
||||
| `lines` | Lines |
|
||||
| `polygons` | Polygons |
|
||||
| `imageoverlays` | Urls and bounds of bitmap overlays |
|
||||
|
||||
|
||||
## Parser Options
|
||||
|
||||
> Work in progress
|
||||
|
||||
| Option | Default | Description |
|
||||
| --- | --- | --- |
|
||||
| `useLngLatFormat` | `true` | KML longitudes and latitudes are specified as `[lat, lng]`. This option "normalizes" them to `[lng, lat]`. |
|
||||
| `useColorArrays` | `true` | Convert color strings to arrays |
|
||||
|
||||
|
||||
## Limitations
|
||||
|
||||
* Currently XML parsing is only implemented in browsers, not in Node.js. Check `KMLLoader.supported` to check at run-time.
|
||||
|
||||
|
||||
## License/Credits/Attributions
|
||||
|
||||
License: MIT
|
||||
|
||||
`XMLLoader` is an adaptation of Nick Blackwell's [`js-simplekml`](https://github.com/nickolanack/js-simplekml) module.
|
||||
@ -1,25 +0,0 @@
|
||||
# LASLoader
|
||||
|
||||
The LASER (LAS) file format is a public format for the interchange of 3-dimensional point cloud data data, developed for LIDAR mapping purposes.
|
||||
|
||||
* [LASER FILE FORMAT](https://www.asprs.org/divisions-committees/lidar-division/laser-las-file-format-exchange-activities)
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
import {OBJLoader, loadFile} from 'loaders.gl';
|
||||
|
||||
loadFile(url, OBJLoader, options).then(data => {
|
||||
// Application code here
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
TBA
|
||||
|
||||
|
||||
## Data Loaded
|
||||
|
||||
TBA
|
||||
@ -1,32 +0,0 @@
|
||||
# OBJLoader
|
||||
|
||||
This loader handles the OBJ half of the classic Wavefront OBJ/MTL format. The OBJ format is a simple ASCII format that lists vertices, normals and faces on successive lines.
|
||||
|
||||
References
|
||||
|
||||
* [Wavefront OBJ file (Wikipedia)](https://en.wikipedia.org/wiki/Wavefront_.obj_file)
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
import {OBJLoader, loadFile} from 'loaders.gl';
|
||||
|
||||
loadFile(url, OBJLoader).then(data => {
|
||||
// Application code here
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
## Loader Options
|
||||
|
||||
N/A
|
||||
|
||||
|
||||
## Data Loaded
|
||||
|
||||
* `positions` -
|
||||
* `normals` -
|
||||
* `faces` -
|
||||
|
||||
@ -1,33 +0,0 @@
|
||||
# PCDLoader
|
||||
|
||||
A point cloud format defined by the Point Cloud Library
|
||||
|
||||
Currently only `ascii` and `binary` subformats are supported. Compressed binary files are currently not supported.
|
||||
|
||||
References
|
||||
|
||||
* [Point Cloud Library](https://en.wikipedia.org/wiki/Point_Cloud_Library)
|
||||
* [PointClouds.org](http://pointclouds.org/documentation/tutorials/pcd_file_format.php)
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
import {PCFLoader, loadFile} from 'loaders.gl';
|
||||
|
||||
loadFile(url, PCFLoader)
|
||||
.then(({header, attributes}) => {
|
||||
// Application code here, e.g:
|
||||
// return new Geometry(attributes)
|
||||
});
|
||||
```
|
||||
|
||||
Loads `position`, `normal`, `color` attributes.
|
||||
|
||||
|
||||
## Attribution/Credits
|
||||
|
||||
This loader is a light adaption of the PCDLoader example in the THREE.js code base. The THREE.js source files contain the following attributions:
|
||||
|
||||
* @author Filipe Caixeta / http://filipecaixeta.com.br
|
||||
* @author Mugen87 / https://github.com/Mugen87
|
||||
@ -1,22 +0,0 @@
|
||||
# PLYLoader
|
||||
|
||||
PLY is a computer file format known as the Polygon File Format or the Stanford Triangle Format. It was principally designed to store three-dimensional data from 3D scanners.
|
||||
|
||||
|
||||
References
|
||||
|
||||
* [PLY format (Wikipedia)](https://en.wikipedia.org/wiki/PLY_(file_format))
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
import {PLYLoader, loadFile} from 'loaders.gl';
|
||||
|
||||
loadFile(url, PLYLoader).then(data => {
|
||||
// Application code here
|
||||
...
|
||||
});
|
||||
```
|
||||
|
||||
|
||||
@ -68,20 +68,32 @@ console.log(
|
||||
|
||||
## Methods
|
||||
|
||||
### Texture2D constructor
|
||||
### constructor(gl : WebGLRenderingContext, props : Object | data : any)
|
||||
|
||||
```
|
||||
new Texture2D(gl, {
|
||||
data=,
|
||||
width=,
|
||||
height=,
|
||||
mipmaps=,
|
||||
format=,
|
||||
type=,
|
||||
dataFormat=,
|
||||
parameters=,
|
||||
pixelStore=
|
||||
})
|
||||
import {Texture2D} from '@luma.gl/core'
|
||||
const texture1 = new Texture2D(gl, {
|
||||
data: ...,
|
||||
width: ...,
|
||||
height: ...,
|
||||
mipmaps: ...,
|
||||
format: ...,
|
||||
type: ...,
|
||||
dataFormat: ...,
|
||||
parameters: ...
|
||||
});
|
||||
```
|
||||
|
||||
There is also a short form where the image data (or a promise resolving to the image data) can be the second argument of the constructor:
|
||||
|
||||
```
|
||||
import {Texture2D} from '@luma.gl/core';
|
||||
import {loadImage} from '@loaders.gl/core';
|
||||
|
||||
const texture1 = new Texture2D(gl, loadImage(url));
|
||||
// equivalent to
|
||||
const texture1 = new Texture2D(gl, {data: loadImage(url)});
|
||||
|
||||
```
|
||||
|
||||
* `gl` (WebGLRenderingContext) - gl context
|
||||
|
||||
@ -2,6 +2,21 @@
|
||||
|
||||
## Version 7.0
|
||||
|
||||
### "Asynchronous" Textures
|
||||
|
||||
The `Texture` class now supports image data being initialized with a URL `string` or a `Promise`that resolves to any of the previously valid data types, e.g. an `Image` instance. This avoids the need to clutter your code with `promise.then()` and/or callback functions just to load textures.
|
||||
|
||||
```
|
||||
new Texture2D(gl, 'path/to/my/image.png');
|
||||
// or
|
||||
new Texture2D(gl, loadImage('path/to/my/image.png')); // loadImage returns a Promise
|
||||
```
|
||||
|
||||
### loaders.gl - New Companion Framework for 3D Asset Loading
|
||||
|
||||
[loaders.gl]() provides a rich suite of 3D file format loaders (including loaders for various popular `Mesh` and `PointCloud` formats) that parse files into objects that can be directly passed to luma.gl `Model` class.
|
||||
|
||||
|
||||
### New Submodule with GPGPU Utilities
|
||||
|
||||
* `@luma.gl/gpgpu` - an experimental module with a collection of GPU accelerated utility methods.
|
||||
@ -23,6 +38,13 @@ model.setAttributes({
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Version 6.4
|
||||
|
||||
Date: January 29, 2018
|
||||
|
||||
|
||||
## Version 6.3
|
||||
|
||||
Date: November 16, 2018
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
The IO submodule contains basic IO functions that can be implemented without
|
||||
dependencies.
|
||||
|
||||
More advanced IO support that handles e.g.
|
||||
|
||||
* multi-format image loading under Node.js
|
||||
* stream support in browsers
|
||||
|
||||
is being developed as a separate module.
|
||||
|
||||
Platform switching is handled by platform.js
|
||||
@ -1,108 +0,0 @@
|
||||
// Supports loading (requesting) assets with XHR (XmlHttpRequest)
|
||||
/* eslint-disable guard-for-in, complexity, no-try-catch */
|
||||
|
||||
/* global XMLHttpRequest */
|
||||
function noop() {}
|
||||
|
||||
const XHR_STATES = {
|
||||
UNINITIALIZED: 0,
|
||||
LOADING: 1,
|
||||
LOADED: 2,
|
||||
INTERACTIVE: 3,
|
||||
COMPLETED: 4
|
||||
};
|
||||
|
||||
class XHR {
|
||||
constructor({
|
||||
url,
|
||||
path = null,
|
||||
method = 'GET',
|
||||
asynchronous = true,
|
||||
noCache = false,
|
||||
// body = null,
|
||||
sendAsBinary = false,
|
||||
responseType = false,
|
||||
onProgress = noop,
|
||||
onError = noop,
|
||||
onAbort = noop,
|
||||
onComplete = noop
|
||||
}) {
|
||||
this.url = path ? path.join(path, url) : url;
|
||||
this.method = method;
|
||||
this.async = asynchronous;
|
||||
this.noCache = noCache;
|
||||
this.sendAsBinary = sendAsBinary;
|
||||
this.responseType = responseType;
|
||||
|
||||
this.req = new XMLHttpRequest();
|
||||
|
||||
this.req.onload = e => onComplete(e);
|
||||
this.req.onerror = e => onError(e);
|
||||
this.req.onabort = e => onAbort(e);
|
||||
this.req.onprogress = e => {
|
||||
if (e.lengthComputable) {
|
||||
onProgress(e, Math.round((e.loaded / e.total) * 100));
|
||||
} else {
|
||||
onProgress(e, -1);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
setRequestHeader(header, value) {
|
||||
this.req.setRequestHeader(header, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
// /* eslint-disable max-statements */
|
||||
sendAsync(body = this.body || null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const {req, method, noCache, sendAsBinary, responseType} = this;
|
||||
|
||||
const url = noCache
|
||||
? this.url + (this.url.indexOf('?') >= 0 ? '&' : '?') + Date.now()
|
||||
: this.url;
|
||||
|
||||
req.open(method, url, this.async);
|
||||
|
||||
if (responseType) {
|
||||
req.responseType = responseType;
|
||||
}
|
||||
|
||||
if (this.async) {
|
||||
req.onreadystatechange = e => {
|
||||
if (req.readyState === XHR_STATES.COMPLETED) {
|
||||
if (req.status === 200) {
|
||||
resolve(req.responseType ? req.response : req.responseText);
|
||||
} else {
|
||||
reject(new Error(`${req.status}: ${url}`));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (sendAsBinary) {
|
||||
req.sendAsBinary(body);
|
||||
} else {
|
||||
req.send(body);
|
||||
}
|
||||
|
||||
if (!this.async) {
|
||||
if (req.status === 200) {
|
||||
resolve(req.responseType ? req.response : req.responseText);
|
||||
} else {
|
||||
reject(new Error(`${req.status}: ${url}`));
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
/* eslint-enable max-statements */
|
||||
}
|
||||
|
||||
export function requestFile(opts) {
|
||||
const xhr = new XHR(opts);
|
||||
return xhr.sendAsync();
|
||||
}
|
||||
@ -1,121 +0,0 @@
|
||||
/* eslint-disable guard-for-in, complexity, no-try-catch */
|
||||
import assert from '../utils/assert';
|
||||
import {loadFile, loadImage} from './browser-load';
|
||||
import {Program, Texture2D} from '../webgl';
|
||||
import {Model} from '../core';
|
||||
import {Geometry} from '../geometry';
|
||||
|
||||
function noop() {}
|
||||
|
||||
export function loadTexture(gl, url, opts = {}) {
|
||||
assert(typeof url === 'string', 'loadTexture: url must be string');
|
||||
|
||||
return loadImage(url, opts).then(image => {
|
||||
return new Texture2D(gl, Object.assign({id: url}, opts, {data: image}));
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Loads (Requests) multiple files asynchronously
|
||||
*/
|
||||
export function loadFiles(opts = {}) {
|
||||
const {urls, onProgress = noop} = opts;
|
||||
assert(urls.every(url => typeof url === 'string'), 'loadImages: {urls} must be array of strings');
|
||||
let count = 0;
|
||||
return Promise.all(
|
||||
urls.map(url => {
|
||||
const promise = loadFile(Object.assign({url}, opts));
|
||||
promise.then(file =>
|
||||
onProgress({
|
||||
progress: ++count / urls.length,
|
||||
count,
|
||||
total: urls.length,
|
||||
url
|
||||
})
|
||||
);
|
||||
return promise;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Loads (requests) multiple images asynchronously
|
||||
*/
|
||||
export function loadImages(opts = {}) {
|
||||
const {urls, onProgress = noop} = opts;
|
||||
assert(urls.every(url => typeof url === 'string'), 'loadImages: {urls} must be array of strings');
|
||||
let count = 0;
|
||||
return Promise.all(
|
||||
urls.map(url => {
|
||||
const promise = loadImage(url, opts);
|
||||
promise.then(file =>
|
||||
onProgress({
|
||||
progress: ++count / urls.length,
|
||||
count,
|
||||
total: urls.length,
|
||||
url
|
||||
})
|
||||
);
|
||||
return promise;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function loadTextures(gl, opts = {}) {
|
||||
const {urls, onProgress = noop} = opts;
|
||||
assert(
|
||||
urls.every(url => typeof url === 'string'),
|
||||
'loadTextures: {urls} must be array of strings'
|
||||
);
|
||||
|
||||
return loadImages(Object.assign({urls, onProgress}, opts)).then(images =>
|
||||
images.map((img, i) => {
|
||||
return new Texture2D(gl, Object.assign({id: urls[i]}, opts, {data: img}));
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function loadProgram(gl, opts = {}) {
|
||||
const {vs, fs, onProgress = noop} = opts;
|
||||
return loadFiles(Object.assign({urls: [vs, fs], onProgress}, opts)).then(
|
||||
([vsText, fsText]) => new Program(gl, Object.assign({vs: vsText, fs: fsText}, opts))
|
||||
);
|
||||
}
|
||||
|
||||
// Loads a simple JSON format
|
||||
export function loadModel(gl, opts = {}) {
|
||||
const {url, onProgress = noop} = opts;
|
||||
return loadFiles(Object.assign({urls: [url], onProgress}, opts)).then(([file]) =>
|
||||
parseModel(gl, Object.assign({file}, opts))
|
||||
);
|
||||
}
|
||||
|
||||
export function parseModel(gl, opts = {}) {
|
||||
const {file, program = new Program(gl)} = opts;
|
||||
const json = typeof file === 'string' ? parseJSON(file) : file;
|
||||
// Remove any attributes so that we can create a geometry
|
||||
// TODO - change format to put these in geometry sub object?
|
||||
const attributes = {};
|
||||
const modelOptions = {};
|
||||
for (const key in json) {
|
||||
const value = json[key];
|
||||
if (Array.isArray(value)) {
|
||||
attributes[key] = key === 'indices' ? new Uint16Array(value) : new Float32Array(value);
|
||||
} else {
|
||||
modelOptions[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return new Model(
|
||||
gl,
|
||||
Object.assign({program, geometry: new Geometry({attributes})}, modelOptions, opts)
|
||||
);
|
||||
}
|
||||
|
||||
function parseJSON(file) {
|
||||
try {
|
||||
return JSON.parse(file);
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse JSON: ${error}`);
|
||||
}
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
export function loadFile(opts) {
|
||||
throw new Error('loadFile not implemented under Node');
|
||||
}
|
||||
|
||||
/*
|
||||
* Loads images asynchronously
|
||||
* returns a promise tracking the load
|
||||
*/
|
||||
export function loadImage(url) {
|
||||
throw new Error('loadImage not implemented under Node');
|
||||
}
|
||||
@ -16,32 +16,33 @@ import assert from '../utils/assert';
|
||||
|
||||
const LOG_PROGRAM_PERF_PRIORITY = 4;
|
||||
|
||||
// const GL_INTERLEAVED_ATTRIBS = 0x8C8C;
|
||||
const GL_SEPARATE_ATTRIBS = 0x8c8d;
|
||||
|
||||
const V6_DEPRECATED_METHODS = [
|
||||
'setVertexArray',
|
||||
'setAttributes',
|
||||
'setBuffers',
|
||||
'unsetBuffers',
|
||||
|
||||
'use',
|
||||
'getUniformCount',
|
||||
'getUniformInfo',
|
||||
'getUniformLocation',
|
||||
'getUniformValue',
|
||||
|
||||
'getVarying',
|
||||
'getFragDataLocation',
|
||||
'getAttachedShaders',
|
||||
'getAttributeCount',
|
||||
'getAttributeLocation',
|
||||
'getAttributeInfo'
|
||||
];
|
||||
|
||||
export default class Program extends Resource {
|
||||
constructor(gl, opts = {}) {
|
||||
super(gl, opts);
|
||||
|
||||
this.stubRemovedMethods('Program', 'v6.0', [
|
||||
'setVertexArray',
|
||||
'setAttributes',
|
||||
'setBuffers',
|
||||
'unsetBuffers',
|
||||
|
||||
'use',
|
||||
'getUniformCount',
|
||||
'getUniformInfo',
|
||||
'getUniformLocation',
|
||||
'getUniformValue',
|
||||
|
||||
'getVarying',
|
||||
'getFragDataLocation',
|
||||
'getAttachedShaders',
|
||||
'getAttributeCount',
|
||||
'getAttributeLocation',
|
||||
'getAttributeInfo'
|
||||
]);
|
||||
this.stubRemovedMethods('Program', 'v6.0', V6_DEPRECATED_METHODS);
|
||||
|
||||
// Experimental flag to avoid deleting Program object while it is cached
|
||||
this._isCached = false;
|
||||
@ -135,15 +136,20 @@ export default class Program extends Resource {
|
||||
// TODO - move vertex array binding and transform feedback binding to withParameters?
|
||||
assert(vertexArray);
|
||||
|
||||
if (uniforms) {
|
||||
// DEPRECATED: v7.0 (deprecated earlier but warning not properly implemented)
|
||||
log.deprecated('Program.draw({uniforms})', 'Program.setUniforms(uniforms)')();
|
||||
this.setUniforms(uniforms, samplers);
|
||||
}
|
||||
|
||||
// Note: async textures set as uniforms might still be loading.
|
||||
// Now that all uniforms have been updated, check if any texture
|
||||
// in the uniforms is not yet initialized, then we don't draw
|
||||
if (this._areTexturesLoading()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
vertexArray.bindForDraw(vertexCount, instanceCount, () => {
|
||||
if (uniforms) {
|
||||
// DEPRECATED: v7.0 (deprecated earlier but warning not properly implemented)
|
||||
log.deprecated('Program.draw({uniforms})', 'Program.setUniforms(uniforms)')();
|
||||
this.setUniforms(uniforms, samplers);
|
||||
}
|
||||
|
||||
this._bindTextures();
|
||||
|
||||
if (framebuffer !== undefined) {
|
||||
parameters = Object.assign({}, parameters, {framebuffer});
|
||||
}
|
||||
@ -153,6 +159,8 @@ export default class Program extends Resource {
|
||||
transformFeedback.begin(primitiveMode);
|
||||
}
|
||||
|
||||
this._bindTextures();
|
||||
|
||||
withParameters(this.gl, parameters, () => {
|
||||
// TODO - Use polyfilled WebGL2RenderingContext instead of ANGLE extension
|
||||
if (isIndexed && isInstanced) {
|
||||
@ -207,6 +215,32 @@ export default class Program extends Resource {
|
||||
|
||||
// PRIVATE METHODS
|
||||
|
||||
_areTexturesLoading() {
|
||||
let texturesLoaded = true;
|
||||
|
||||
for (const uniformName in this.uniforms) {
|
||||
const uniformSetter = this._uniformSetters[uniformName];
|
||||
|
||||
if (uniformSetter && uniformSetter.textureIndex !== undefined) {
|
||||
let uniform = this.uniforms[uniformName];
|
||||
|
||||
if (uniform instanceof Framebuffer) {
|
||||
const framebuffer = uniform;
|
||||
uniform = framebuffer.texture;
|
||||
}
|
||||
|
||||
if (uniform instanceof Texture) {
|
||||
const texture = uniform;
|
||||
// Check that texture is loaded
|
||||
texturesLoaded = texturesLoaded && texture.loaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !texturesLoaded;
|
||||
}
|
||||
|
||||
// Binds textures (and checks that async textures have loaded)
|
||||
// This needs to be done before every draw call
|
||||
_bindTextures() {
|
||||
for (const uniformName in this.uniforms) {
|
||||
@ -220,8 +254,9 @@ export default class Program extends Resource {
|
||||
uniform = uniform.texture;
|
||||
}
|
||||
if (uniform instanceof Texture) {
|
||||
const texture = uniform;
|
||||
// Bind texture to index
|
||||
uniform.bind(uniformSetter.textureIndex);
|
||||
texture.bind(uniformSetter.textureIndex);
|
||||
}
|
||||
// Bind a sampler (if supplied) to index
|
||||
if (sampler) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import GL from '@luma.gl/constants';
|
||||
import Texture from './texture';
|
||||
import {loadImage} from '../io';
|
||||
import {assertWebGLContext} from '../webgl-utils';
|
||||
|
||||
export default class Texture2D extends Texture {
|
||||
@ -7,24 +8,21 @@ export default class Texture2D extends Texture {
|
||||
return Texture.isSupported(gl, opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
* 2D WebGL Texture
|
||||
* Note: Constructor will initialize your texture.
|
||||
*
|
||||
* @class
|
||||
* @param {WebGLRenderingContext} gl - gl context
|
||||
* @param {Image|ArrayBuffer|null} opts= - named options
|
||||
* @param {Image|ArrayBuffer|null} opts.data= - buffer
|
||||
* @param {GLint} width - width of texture
|
||||
* @param {GLint} height - height of texture
|
||||
*/
|
||||
constructor(gl, opts = {}) {
|
||||
constructor(gl, props = {}) {
|
||||
assertWebGLContext(gl);
|
||||
|
||||
super(gl, Object.assign({}, opts, {target: gl.TEXTURE_2D}));
|
||||
// Signature: new Texture2D(gl, url | Promise)
|
||||
if (props instanceof Promise || typeof props === 'string') {
|
||||
props = {data: props};
|
||||
}
|
||||
// Signature: new Texture2D(gl, {data: url})
|
||||
if (typeof props.data === 'string') {
|
||||
props = Object.assign({}, props, {data: loadImage(props.data)});
|
||||
}
|
||||
|
||||
this.initialize(opts);
|
||||
super(gl, Object.assign({}, props, {target: gl.TEXTURE_2D}));
|
||||
|
||||
this.initialize(props);
|
||||
|
||||
Object.seal(this);
|
||||
}
|
||||
|
||||
@ -12,22 +12,22 @@ const FACES = [
|
||||
];
|
||||
|
||||
export default class TextureCube extends Texture {
|
||||
constructor(gl, opts = {}) {
|
||||
super(gl, Object.assign({}, opts, {target: GL.TEXTURE_CUBE_MAP}));
|
||||
this.initialize(opts);
|
||||
constructor(gl, props = {}) {
|
||||
super(gl, Object.assign({}, props, {target: GL.TEXTURE_CUBE_MAP}));
|
||||
this.initialize(props);
|
||||
Object.seal(this);
|
||||
}
|
||||
|
||||
/* eslint-disable max-len, max-statements */
|
||||
initialize(opts = {}) {
|
||||
const {format = GL.RGBA, mipmaps = true} = opts;
|
||||
initialize(props = {}) {
|
||||
const {format = GL.RGBA, mipmaps = true} = props;
|
||||
|
||||
let {width = 1, height = 1, type = GL.UNSIGNED_BYTE, dataFormat} = opts;
|
||||
let {width = 1, height = 1, type = GL.UNSIGNED_BYTE, dataFormat} = props;
|
||||
|
||||
// Deduce width and height based on one of the faces
|
||||
({type, dataFormat} = this._deduceParameters({format, type, dataFormat}));
|
||||
({width, height} = this._deduceImageSize({
|
||||
data: opts[GL.TEXTURE_CUBE_MAP_POSITIVE_X],
|
||||
data: props[GL.TEXTURE_CUBE_MAP_POSITIVE_X],
|
||||
width,
|
||||
height
|
||||
}));
|
||||
@ -36,26 +36,26 @@ export default class TextureCube extends Texture {
|
||||
assert(width === height);
|
||||
|
||||
// Temporarily apply any pixel store paramaters and build textures
|
||||
// withParameters(this.gl, opts, () => {
|
||||
// withParameters(this.gl, props, () => {
|
||||
// for (const face of CUBE_MAP_FACES) {
|
||||
// this.setImageData({
|
||||
// target: face,
|
||||
// data: opts[face],
|
||||
// data: props[face],
|
||||
// width, height, format, type, dataFormat, border, mipmaps
|
||||
// });
|
||||
// }
|
||||
// });
|
||||
|
||||
this.setCubeMapImageData(opts);
|
||||
this.setCubeMapImageData(props);
|
||||
|
||||
// Called here so that GL.
|
||||
// TODO - should genMipmap() be called on the cubemap or on the faces?
|
||||
if (mipmaps) {
|
||||
this.generateMipmap(opts);
|
||||
this.generateMipmap(props);
|
||||
}
|
||||
|
||||
// Store opts for accessors
|
||||
this.opts = opts;
|
||||
// Store props for accessors
|
||||
this.opts = props;
|
||||
}
|
||||
|
||||
subImage({face, data, x = 0, y = 0, mipmapLevel = 0}) {
|
||||
@ -74,17 +74,66 @@ export default class TextureCube extends Texture {
|
||||
generateMipmap = false
|
||||
}) {
|
||||
const {gl} = this;
|
||||
pixels = pixels || data;
|
||||
this.bind();
|
||||
if (this.width || this.height) {
|
||||
for (const face of FACES) {
|
||||
gl.texImage2D(face, 0, format, width, height, border, format, type, pixels[face]);
|
||||
const imageDataMap = pixels || data;
|
||||
|
||||
// TODO - Make this a method of Texture, and call that
|
||||
// A rare instance where a local function is the lesser evil?
|
||||
const setImageData = (face, imageData) => {
|
||||
if (this.width || this.height) {
|
||||
gl.texImage2D(face, 0, format, width, height, border, format, type, imageData);
|
||||
} else {
|
||||
gl.texImage2D(face, 0, format, format, type, imageData);
|
||||
}
|
||||
} else {
|
||||
for (const face of FACES) {
|
||||
gl.texImage2D(face, 0, format, format, type, pixels[face]);
|
||||
};
|
||||
|
||||
this.bind();
|
||||
for (const face of FACES) {
|
||||
const imageData = imageDataMap[face];
|
||||
|
||||
if (imageData instanceof Promise) {
|
||||
imageData.then(resolvedImageData => setImageData(face, resolvedImageData));
|
||||
} else {
|
||||
setImageData(face, imageData);
|
||||
}
|
||||
}
|
||||
this.unbind();
|
||||
}
|
||||
|
||||
setImageDataForFace(options) {
|
||||
const {
|
||||
face,
|
||||
width,
|
||||
height,
|
||||
pixels,
|
||||
data,
|
||||
border = 0,
|
||||
format = GL.RGBA,
|
||||
type = GL.UNSIGNED_BYTE
|
||||
// generateMipmap = false // TODO
|
||||
} = options;
|
||||
|
||||
const {gl} = this;
|
||||
|
||||
const imageData = pixels || data;
|
||||
|
||||
this.bind();
|
||||
if (imageData instanceof Promise) {
|
||||
imageData.then(resolvedImageData =>
|
||||
this.setImageDataForFace(
|
||||
Object.assign({}, options, {
|
||||
face,
|
||||
data: resolvedImageData,
|
||||
pixels: resolvedImageData
|
||||
})
|
||||
)
|
||||
);
|
||||
} else if (this.width || this.height) {
|
||||
gl.texImage2D(face, 0, format, width, height, border, format, type, imageData);
|
||||
} else {
|
||||
gl.texImage2D(face, 0, format, format, type, imageData);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
bind({index} = {}) {
|
||||
|
||||
@ -45,6 +45,11 @@ export default class Texture extends Resource {
|
||||
this.hasFloatTexture = gl.getExtension('OES_texture_float');
|
||||
this.textureUnit = undefined;
|
||||
|
||||
// Program.draw() checks the loaded flag of all textures to avoid
|
||||
// Textures that are still loading from promises
|
||||
// Set to true as soon as texture has been initialized with valid data
|
||||
this.loaded = false;
|
||||
|
||||
this.width = undefined;
|
||||
this.height = undefined;
|
||||
this.format = undefined;
|
||||
@ -63,6 +68,18 @@ export default class Texture extends Resource {
|
||||
initialize(props = {}) {
|
||||
let data = props.data;
|
||||
|
||||
if (data instanceof Promise) {
|
||||
data.then(resolvedImageData =>
|
||||
this.initialize(
|
||||
Object.assign({}, props, {
|
||||
pixels: resolvedImageData,
|
||||
data: resolvedImageData
|
||||
})
|
||||
)
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
const {
|
||||
pixels = null,
|
||||
format = GL.RGBA,
|
||||
@ -290,6 +307,8 @@ export default class Texture extends Resource {
|
||||
}
|
||||
});
|
||||
|
||||
this.loaded = true;
|
||||
|
||||
return this;
|
||||
}
|
||||
/* eslint-enable max-len, max-statements, complexity */
|
||||
|
||||
@ -26,6 +26,30 @@ test('WebGL#Texture2D construct/delete', t => {
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('WebGL#Texture2D async constructor', t => {
|
||||
const {gl} = fixture;
|
||||
|
||||
let texture = new Texture2D(gl);
|
||||
t.ok(texture instanceof Texture2D, 'Synchronous Texture2D construction successful');
|
||||
t.equal(texture.loaded, true, 'Sync Texture2D marked as loaded');
|
||||
texture.delete();
|
||||
|
||||
let loadCompleted;
|
||||
const loadPromise = new Promise(resolve => {
|
||||
loadCompleted = resolve; // eslint-disable-line
|
||||
});
|
||||
texture = new Texture2D(gl, loadPromise);
|
||||
t.ok(texture instanceof Texture2D, 'Asynchronous Texture2D construction successful');
|
||||
t.equal(texture.loaded, false, 'Async Texture2D initially marked as not loaded');
|
||||
|
||||
loadPromise.then(() => {
|
||||
t.equal(texture.loaded, true, 'Async Texture2D marked as loaded on promise completion');
|
||||
t.end();
|
||||
});
|
||||
|
||||
loadCompleted(null);
|
||||
});
|
||||
|
||||
test('WebGL#Texture2D buffer update', t => {
|
||||
const {gl} = fixture;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user