import {
|
clone,
|
GltfImageLoader,
|
GltfTextureLoader,
|
GltfLoaderUtil,
|
JobScheduler,
|
Resource,
|
ResourceCache,
|
SupportedImageFormats,
|
Texture,
|
TextureMinificationFilter,
|
when,
|
} from "../../Source/Cesium.js";
|
import createScene from "../createScene.js";
|
import loaderProcess from "../loaderProcess.js";
|
import waitForLoaderProcess from "../waitForLoaderProcess.js";
|
|
describe(
|
"Scene/GltfTextureLoader",
|
function () {
|
var image = new Image();
|
image.src =
|
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=";
|
|
var imageNpot = new Image();
|
imageNpot.src =
|
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACAQMAAACnuvRZAAAAA3NCSVQICAjb4U/gAAAABlBMVEUAAAD///+l2Z/dAAAAAnRSTlP/AOW3MEoAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAcdEVYdFNvZnR3YXJlAEFkb2JlIEZpcmV3b3JrcyBDUzQGstOgAAAAFnRFWHRDcmVhdGlvbiBUaW1lADAxLzA0LzE0Kb6O2wAAAAxJREFUCJljeMDwAAADhAHBgGgjpQAAAABJRU5ErkJggg==";
|
|
var gltfUri = "https://example.com/model.glb";
|
var gltfResource = new Resource({
|
url: gltfUri,
|
});
|
|
var gltf = {
|
images: [
|
{
|
uri: "image.png",
|
},
|
],
|
textures: [
|
{
|
source: 0,
|
},
|
{
|
source: 0,
|
sampler: 0,
|
},
|
{
|
source: 0,
|
sampler: 1,
|
},
|
],
|
materials: [
|
{
|
emissiveTexture: {
|
index: 0,
|
},
|
occlusionTexture: {
|
index: 1,
|
},
|
normalTexture: {
|
index: 2,
|
},
|
},
|
],
|
samplers: [
|
{
|
magFilter: 9728, // NEAREST
|
minFilter: 9984, // NEAREST_MIPMAP_NEAREST
|
wrapS: 10497, // REPEAT
|
wrapT: 10497, // REPEAT
|
},
|
{
|
magFilter: 9728, // NEAREST
|
minFilter: 9728, // NEAREST
|
wrapS: 33071, // CLAMP_TO_EDGE
|
wrapT: 33071, // CLAMP_TO_EDGE
|
},
|
],
|
};
|
|
var gltfKtx2BaseResource = new Resource({
|
url: "./Data/Images/",
|
});
|
|
var gltfKtx2 = {
|
images: [
|
{
|
uri: "image.png",
|
},
|
{
|
uri: "Green4x4_ETC1S.ktx2",
|
},
|
{
|
uri: "Green4x4Mipmap_ETC1S.ktx2",
|
},
|
],
|
textures: [
|
{
|
source: 0,
|
sampler: 0,
|
extensions: {
|
KHR_texture_basisu: {
|
source: 1,
|
},
|
},
|
},
|
{
|
source: 0,
|
sampler: 1,
|
extensions: {
|
KHR_texture_basisu: {
|
source: 2,
|
},
|
},
|
},
|
],
|
materials: [
|
{
|
emissiveTexture: {
|
index: 0,
|
},
|
occlusionTexture: {
|
index: 1,
|
},
|
},
|
],
|
samplers: [
|
{
|
magFilter: 9728, // NEAREST
|
minFilter: 9728, // NEAREST
|
wrapS: 10497, // REPEAT
|
wrapT: 10497, // REPEAT
|
},
|
{
|
magFilter: 9728, // NEAREST
|
minFilter: 9984, // NEAREST_MIPMAP_NEAREST
|
wrapS: 33071, // CLAMP_TO_EDGE
|
wrapT: 33071, // CLAMP_TO_EDGE
|
},
|
],
|
};
|
|
var scene;
|
|
beforeAll(function () {
|
scene = createScene();
|
});
|
|
afterAll(function () {
|
scene.destroyForSpecs();
|
});
|
|
afterEach(function () {
|
ResourceCache.clearForSpecs();
|
});
|
|
it("throws if resourceCache is undefined", function () {
|
expect(function () {
|
return new GltfTextureLoader({
|
resourceCache: undefined,
|
gltf: gltf,
|
imageId: 0,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
supportedImageFormats: new SupportedImageFormats(),
|
});
|
}).toThrowDeveloperError();
|
});
|
|
it("throws if gltf is undefined", function () {
|
expect(function () {
|
return new GltfTextureLoader({
|
resourceCache: ResourceCache,
|
gltf: undefined,
|
textureInfo: gltf.materials[0].emissiveTexture,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
supportedImageFormats: new SupportedImageFormats(),
|
});
|
}).toThrowDeveloperError();
|
});
|
|
it("throws if textureInfo is undefined", function () {
|
expect(function () {
|
return new GltfTextureLoader({
|
resourceCache: ResourceCache,
|
gltf: gltf,
|
textureInfo: undefined,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
supportedImageFormats: new SupportedImageFormats(),
|
});
|
}).toThrowDeveloperError();
|
});
|
|
it("throws if gltfResource is undefined", function () {
|
expect(function () {
|
return new GltfTextureLoader({
|
resourceCache: ResourceCache,
|
gltf: gltf,
|
textureInfo: gltf.materials[0].emissiveTexture,
|
gltfResource: undefined,
|
baseResource: gltfResource,
|
supportedImageFormats: new SupportedImageFormats(),
|
});
|
}).toThrowDeveloperError();
|
});
|
|
it("throws if baseResource is undefined", function () {
|
expect(function () {
|
return new GltfTextureLoader({
|
resourceCache: ResourceCache,
|
gltf: gltf,
|
textureInfo: gltf.materials[0].emissiveTexture,
|
gltfResource: gltfResource,
|
baseResource: undefined,
|
supportedImageFormats: new SupportedImageFormats(),
|
});
|
}).toThrowDeveloperError();
|
});
|
|
it("throws if supportedImageFormats is undefined", function () {
|
expect(function () {
|
return new GltfTextureLoader({
|
resourceCache: ResourceCache,
|
gltf: gltf,
|
textureInfo: gltf.materials[0].emissiveTexture,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
supportedImageFormats: undefined,
|
});
|
}).toThrowDeveloperError();
|
});
|
|
it("rejects promise if image fails to load", function () {
|
var error = new Error("404 Not Found");
|
spyOn(Resource.prototype, "fetchImage").and.returnValue(
|
when.reject(error)
|
);
|
|
var textureLoader = new GltfTextureLoader({
|
resourceCache: ResourceCache,
|
gltf: gltf,
|
textureInfo: gltf.materials[0].emissiveTexture,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
supportedImageFormats: new SupportedImageFormats(),
|
});
|
|
textureLoader.load();
|
|
return textureLoader.promise
|
.then(function (textureLoader) {
|
fail();
|
})
|
.otherwise(function (runtimeError) {
|
expect(runtimeError.message).toBe(
|
"Failed to load texture\nFailed to load image: image.png\n404 Not Found"
|
);
|
});
|
});
|
|
it("loads texture", function () {
|
spyOn(Resource.prototype, "fetchImage").and.returnValue(
|
when.resolve(image)
|
);
|
|
// Simulate JobScheduler not being ready for a few frames
|
var processCallsTotal = 3;
|
var processCallsCount = 0;
|
var jobScheduler = scene.frameState.jobScheduler;
|
var originalJobSchedulerExecute = jobScheduler.execute;
|
spyOn(JobScheduler.prototype, "execute").and.callFake(function (
|
job,
|
jobType
|
) {
|
if (processCallsCount++ >= processCallsTotal) {
|
return originalJobSchedulerExecute.call(jobScheduler, job, jobType);
|
}
|
return false;
|
});
|
|
var textureLoader = new GltfTextureLoader({
|
resourceCache: ResourceCache,
|
gltf: gltf,
|
textureInfo: gltf.materials[0].emissiveTexture,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
supportedImageFormats: new SupportedImageFormats(),
|
});
|
|
loaderProcess(textureLoader, scene); // Check that calling process before load doesn't break anything
|
|
textureLoader.load();
|
|
return waitForLoaderProcess(textureLoader, scene).then(function (
|
textureLoader
|
) {
|
loaderProcess(textureLoader, scene); // Check that calling process after load doesn't break anything
|
expect(textureLoader.texture.width).toBe(1);
|
expect(textureLoader.texture.height).toBe(1);
|
});
|
});
|
|
it("creates texture synchronously", function () {
|
spyOn(Resource.prototype, "fetchImage").and.returnValue(
|
when.resolve(image)
|
);
|
|
var textureLoader = new GltfTextureLoader({
|
resourceCache: ResourceCache,
|
gltf: gltf,
|
textureInfo: gltf.materials[0].emissiveTexture,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
supportedImageFormats: new SupportedImageFormats(),
|
asynchronous: false,
|
});
|
|
textureLoader.load();
|
|
return waitForLoaderProcess(textureLoader, scene).then(function (
|
textureLoader
|
) {
|
loaderProcess(textureLoader, scene); // Check that calling process after load doesn't break anything
|
expect(textureLoader.texture.width).toBe(1);
|
expect(textureLoader.texture.height).toBe(1);
|
});
|
});
|
|
it("loads KTX2/Basis texture", function () {
|
if (!scene.context.supportsBasis) {
|
return;
|
}
|
|
var gl = scene.context._gl;
|
spyOn(gl, "compressedTexImage2D").and.callThrough();
|
|
var textureLoader = new GltfTextureLoader({
|
resourceCache: ResourceCache,
|
gltf: gltfKtx2,
|
textureInfo: gltf.materials[0].emissiveTexture,
|
gltfResource: gltfResource,
|
baseResource: gltfKtx2BaseResource,
|
supportedImageFormats: new SupportedImageFormats({
|
basis: true,
|
}),
|
});
|
|
textureLoader.load();
|
|
return waitForLoaderProcess(textureLoader, scene).then(function (
|
textureLoader
|
) {
|
expect(textureLoader.texture.width).toBe(4);
|
expect(textureLoader.texture.height).toBe(4);
|
expect(gl.compressedTexImage2D.calls.count()).toEqual(1);
|
});
|
});
|
|
it("loads KTX2/Basis texture with mipmap", function () {
|
if (!scene.context.supportsBasis) {
|
return;
|
}
|
|
var gl = scene.context._gl;
|
spyOn(gl, "compressedTexImage2D").and.callThrough();
|
|
var textureLoader = new GltfTextureLoader({
|
resourceCache: ResourceCache,
|
gltf: gltfKtx2,
|
textureInfo: gltf.materials[0].occlusionTexture,
|
gltfResource: gltfResource,
|
baseResource: gltfKtx2BaseResource,
|
supportedImageFormats: new SupportedImageFormats({
|
basis: true,
|
}),
|
});
|
|
textureLoader.load();
|
|
return waitForLoaderProcess(textureLoader, scene).then(function (
|
textureLoader
|
) {
|
expect(textureLoader.texture.width).toBe(4);
|
expect(textureLoader.texture.height).toBe(4);
|
expect(gl.compressedTexImage2D.calls.count()).toEqual(3);
|
});
|
});
|
|
it("loads KTX2/Basis texture with incompatible mipmap sampler", function () {
|
if (!scene.context.supportsBasis) {
|
return;
|
}
|
|
spyOn(GltfLoaderUtil, "createSampler").and.callThrough();
|
|
var gltfKtx2MissingMipmap = clone(gltfKtx2, true);
|
gltfKtx2MissingMipmap.samplers[0].minFilter =
|
TextureMinificationFilter.NEAREST_MIPMAP_NEAREST;
|
|
var textureLoader = new GltfTextureLoader({
|
resourceCache: ResourceCache,
|
gltf: gltfKtx2MissingMipmap,
|
textureInfo: gltfKtx2MissingMipmap.materials[0].emissiveTexture,
|
gltfResource: gltfResource,
|
baseResource: gltfKtx2BaseResource,
|
supportedImageFormats: new SupportedImageFormats({
|
basis: true,
|
}),
|
asynchronous: false,
|
});
|
|
textureLoader.load();
|
|
return waitForLoaderProcess(textureLoader, scene).then(function (
|
textureLoader
|
) {
|
expect(GltfLoaderUtil.createSampler).toHaveBeenCalledWith({
|
gltf: gltfKtx2MissingMipmap,
|
textureInfo: gltf.materials[0].emissiveTexture,
|
compressedTextureNoMipmap: true,
|
});
|
expect(textureLoader.texture.sampler.minificationFilter).toBe(
|
TextureMinificationFilter.NEAREST
|
);
|
});
|
});
|
|
it("generates mipmap if sampler requires it", function () {
|
spyOn(Resource.prototype, "fetchImage").and.returnValue(
|
when.resolve(image)
|
);
|
|
var generateMipmap = spyOn(
|
Texture.prototype,
|
"generateMipmap"
|
).and.callThrough();
|
|
var textureLoader = new GltfTextureLoader({
|
resourceCache: ResourceCache,
|
gltf: gltf,
|
textureInfo: gltf.materials[0].occlusionTexture, // This texture has a sampler that require a mipmap
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
supportedImageFormats: new SupportedImageFormats(),
|
});
|
|
textureLoader.load();
|
|
return waitForLoaderProcess(textureLoader, scene).then(function (
|
textureLoader
|
) {
|
expect(textureLoader.texture.width).toBe(1);
|
expect(textureLoader.texture.height).toBe(1);
|
expect(generateMipmap).toHaveBeenCalled();
|
});
|
});
|
|
it("generates power-of-two texture if sampler requires it", function () {
|
spyOn(Resource.prototype, "fetchImage").and.returnValue(
|
when.resolve(imageNpot)
|
);
|
|
var textureLoader = new GltfTextureLoader({
|
resourceCache: ResourceCache,
|
gltf: gltf,
|
textureInfo: gltf.materials[0].occlusionTexture, // This texture has a sampler that require power-of-two texture
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
supportedImageFormats: new SupportedImageFormats(),
|
});
|
|
textureLoader.load();
|
|
return waitForLoaderProcess(textureLoader, scene).then(function (
|
textureLoader
|
) {
|
expect(textureLoader.texture.width).toBe(4);
|
expect(textureLoader.texture.height).toBe(2);
|
});
|
});
|
|
it("does not generate power-of-two texture if sampler does not require it", function () {
|
spyOn(Resource.prototype, "fetchImage").and.returnValue(
|
when.resolve(imageNpot)
|
);
|
|
var textureLoader = new GltfTextureLoader({
|
resourceCache: ResourceCache,
|
gltf: gltf,
|
textureInfo: gltf.materials[0].normalTexture, // This texture has a sampler that does not require power-of-two texture
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
supportedImageFormats: new SupportedImageFormats(),
|
});
|
|
textureLoader.load();
|
|
return waitForLoaderProcess(textureLoader, scene).then(function (
|
textureLoader
|
) {
|
expect(textureLoader.texture.width).toBe(3);
|
expect(textureLoader.texture.height).toBe(2);
|
});
|
});
|
|
it("destroys texture loader", function () {
|
spyOn(Resource.prototype, "fetchImage").and.returnValue(
|
when.resolve(image)
|
);
|
|
var unloadImage = spyOn(
|
GltfImageLoader.prototype,
|
"unload"
|
).and.callThrough();
|
|
var destroyTexture = spyOn(
|
Texture.prototype,
|
"destroy"
|
).and.callThrough();
|
|
var textureLoader = new GltfTextureLoader({
|
resourceCache: ResourceCache,
|
gltf: gltf,
|
textureInfo: gltf.materials[0].emissiveTexture,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
supportedImageFormats: new SupportedImageFormats(),
|
});
|
|
expect(textureLoader.texture).not.toBeDefined();
|
|
textureLoader.load();
|
|
return waitForLoaderProcess(textureLoader, scene).then(function (
|
textureLoader
|
) {
|
expect(textureLoader.texture).toBeDefined();
|
expect(textureLoader.isDestroyed()).toBe(false);
|
|
textureLoader.destroy();
|
|
expect(textureLoader.texture).not.toBeDefined();
|
expect(textureLoader.isDestroyed()).toBe(true);
|
expect(unloadImage).toHaveBeenCalled();
|
expect(destroyTexture).toHaveBeenCalled();
|
});
|
});
|
|
function resolveImageAfterDestroy(reject) {
|
var deferredPromise = when.defer();
|
spyOn(Resource.prototype, "fetchImage").and.returnValue(
|
deferredPromise.promise
|
);
|
|
// Load a copy of the image into the cache so that the image
|
// promise resolves even if the texture loader is destroyed
|
var imageLoaderCopy = ResourceCache.loadImage({
|
gltf: gltf,
|
imageId: 0,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
});
|
|
var textureLoader = new GltfTextureLoader({
|
resourceCache: ResourceCache,
|
gltf: gltf,
|
textureInfo: gltf.materials[0].emissiveTexture,
|
gltfResource: gltfResource,
|
baseResource: gltfResource,
|
supportedImageFormats: new SupportedImageFormats(),
|
});
|
|
expect(textureLoader.texture).not.toBeDefined();
|
|
textureLoader.load();
|
textureLoader.destroy();
|
|
if (reject) {
|
deferredPromise.reject(new Error());
|
} else {
|
deferredPromise.resolve(image);
|
}
|
|
expect(textureLoader.texture).not.toBeDefined();
|
expect(textureLoader.isDestroyed()).toBe(true);
|
|
ResourceCache.unload(imageLoaderCopy);
|
}
|
|
it("handles resolving image after destroy", function () {
|
resolveImageAfterDestroy(false);
|
});
|
|
it("handles rejecting image after destroy", function () {
|
resolveImageAfterDestroy(true);
|
});
|
},
|
"WebGL"
|
);
|