PostProcessStage.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943
  1. import BoundingRectangle from '../Core/BoundingRectangle.js';
  2. import Check from '../Core/Check.js';
  3. import Color from '../Core/Color.js';
  4. import combine from '../Core/combine.js';
  5. import createGuid from '../Core/createGuid.js';
  6. import defaultValue from '../Core/defaultValue.js';
  7. import defined from '../Core/defined.js';
  8. import defineProperties from '../Core/defineProperties.js';
  9. import destroyObject from '../Core/destroyObject.js';
  10. import DeveloperError from '../Core/DeveloperError.js';
  11. import PixelFormat from '../Core/PixelFormat.js';
  12. import Resource from '../Core/Resource.js';
  13. import PassState from '../Renderer/PassState.js';
  14. import PixelDatatype from '../Renderer/PixelDatatype.js';
  15. import RenderState from '../Renderer/RenderState.js';
  16. import Sampler from '../Renderer/Sampler.js';
  17. import ShaderSource from '../Renderer/ShaderSource.js';
  18. import Texture from '../Renderer/Texture.js';
  19. import TextureMagnificationFilter from '../Renderer/TextureMagnificationFilter.js';
  20. import TextureMinificationFilter from '../Renderer/TextureMinificationFilter.js';
  21. import TextureWrap from '../Renderer/TextureWrap.js';
  22. import when from '../ThirdParty/when.js';
  23. import PostProcessStageSampleMode from './PostProcessStageSampleMode.js';
  24. /**
  25. * Runs a post-process stage on either the texture rendered by the scene or the output of a previous post-process stage.
  26. *
  27. * @alias PostProcessStage
  28. * @constructor
  29. *
  30. * @param {Object} options An object with the following properties:
  31. * @param {String} options.fragmentShader The fragment shader to use. The default <code>sampler2D</code> uniforms are <code>colorTexture</code> and <code>depthTexture</code>. The color texture is the output of rendering the scene or the previous stage. The depth texture is the output from rendering the scene. The shader should contain one or both uniforms. There is also a <code>vec2</code> varying named <code>v_textureCoordinates</code> that can be used to sample the textures.
  32. * @param {Object} [options.uniforms] An object whose properties will be used to set the shaders uniforms. The properties can be constant values or a function. A constant value can also be a URI, data URI, or HTML element to use as a texture.
  33. * @param {Number} [options.textureScale=1.0] A number in the range (0.0, 1.0] used to scale the texture dimensions. A scale of 1.0 will render this post-process stage to a texture the size of the viewport.
  34. * @param {Boolean} [options.forcePowerOfTwo=false] Whether or not to force the texture dimensions to be both equal powers of two. The power of two will be the next power of two of the minimum of the dimensions.
  35. * @param {PostProcessStageSampleMode} [options.sampleMode=PostProcessStageSampleMode.NEAREST] How to sample the input color texture.
  36. * @param {PixelFormat} [options.pixelFormat=PixelFormat.RGBA] The color pixel format of the output texture.
  37. * @param {PixelDatatype} [options.pixelDatatype=PixelDatatype.UNSIGNED_BYTE] The pixel data type of the output texture.
  38. * @param {Color} [options.clearColor=Color.BLACK] The color to clear the output texture to.
  39. * @param {BoundingRectangle} [options.scissorRectangle] The rectangle to use for the scissor test.
  40. * @param {String} [options.name=createGuid()] The unique name of this post-process stage for reference by other stages in a composite. If a name is not supplied, a GUID will be generated.
  41. *
  42. * @exception {DeveloperError} options.textureScale must be greater than 0.0 and less than or equal to 1.0.
  43. * @exception {DeveloperError} options.pixelFormat must be a color format.
  44. * @exception {DeveloperError} When options.pixelDatatype is FLOAT, this WebGL implementation must support the OES_texture_float extension. Check context.floatingPointTexture.
  45. *
  46. * @see PostProcessStageComposite
  47. *
  48. * @example
  49. * // Simple stage to change the color
  50. * var fs =
  51. * 'uniform sampler2D colorTexture;\n' +
  52. * 'varying vec2 v_textureCoordinates;\n' +
  53. * 'uniform float scale;\n' +
  54. * 'uniform vec3 offset;\n' +
  55. * 'void main() {\n' +
  56. * ' vec4 color = texture2D(colorTexture, v_textureCoordinates);\n' +
  57. * ' gl_FragColor = vec4(color.rgb * scale + offset, 1.0);\n' +
  58. * '}\n';
  59. * scene.postProcessStages.add(new Cesium.PostProcessStage({
  60. * fragmentShader : fs,
  61. * uniforms : {
  62. * scale : 1.1,
  63. * offset : function() {
  64. * return new Cesium.Cartesian3(0.1, 0.2, 0.3);
  65. * }
  66. * }
  67. * }));
  68. *
  69. * @example
  70. * // Simple stage to change the color of what is selected.
  71. * // If czm_selected returns true, the current fragment belongs to geometry in the selected array.
  72. * var fs =
  73. * 'uniform sampler2D colorTexture;\n' +
  74. * 'varying vec2 v_textureCoordinates;\n' +
  75. * 'uniform vec4 highlight;\n' +
  76. * 'void main() {\n' +
  77. * ' vec4 color = texture2D(colorTexture, v_textureCoordinates);\n' +
  78. * ' if (czm_selected()) {\n' +
  79. * ' vec3 highlighted = highlight.a * highlight.rgb + (1.0 - highlight.a) * color.rgb;\n' +
  80. * ' gl_FragColor = vec4(highlighted, 1.0);\n' +
  81. * ' } else { \n' +
  82. * ' gl_FragColor = color;\n' +
  83. * ' }\n' +
  84. * '}\n';
  85. * var stage = scene.postProcessStages.add(new Cesium.PostProcessStage({
  86. * fragmentShader : fs,
  87. * uniforms : {
  88. * highlight : function() {
  89. * return new Cesium.Color(1.0, 0.0, 0.0, 0.5);
  90. * }
  91. * }
  92. * }));
  93. * stage.selected = [cesium3DTileFeature];
  94. */
  95. function PostProcessStage(options) {
  96. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  97. var fragmentShader = options.fragmentShader;
  98. var textureScale = defaultValue(options.textureScale, 1.0);
  99. var pixelFormat = defaultValue(options.pixelFormat, PixelFormat.RGBA);
  100. //>>includeStart('debug', pragmas.debug);
  101. Check.typeOf.string('options.fragmentShader', fragmentShader);
  102. Check.typeOf.number.greaterThan('options.textureScale', textureScale, 0.0);
  103. Check.typeOf.number.lessThanOrEquals('options.textureScale', textureScale, 1.0);
  104. if (!PixelFormat.isColorFormat(pixelFormat)) {
  105. throw new DeveloperError('options.pixelFormat must be a color format.');
  106. }
  107. //>>includeEnd('debug');
  108. this._fragmentShader = fragmentShader;
  109. this._uniforms = options.uniforms;
  110. this._textureScale = textureScale;
  111. this._forcePowerOfTwo = defaultValue(options.forcePowerOfTwo, false);
  112. this._sampleMode = defaultValue(options.sampleMode, PostProcessStageSampleMode.NEAREST);
  113. this._pixelFormat = pixelFormat;
  114. this._pixelDatatype = defaultValue(options.pixelDatatype, PixelDatatype.UNSIGNED_BYTE);
  115. this._clearColor = defaultValue(options.clearColor, Color.BLACK);
  116. this._uniformMap = undefined;
  117. this._command = undefined;
  118. this._colorTexture = undefined;
  119. this._depthTexture = undefined;
  120. this._idTexture = undefined;
  121. this._actualUniforms = {};
  122. this._dirtyUniforms = [];
  123. this._texturesToRelease = [];
  124. this._texturesToCreate = [];
  125. this._texturePromise = undefined;
  126. var passState = new PassState();
  127. passState.scissorTest = {
  128. enabled : true,
  129. rectangle : defined(options.scissorRectangle) ? BoundingRectangle.clone(options.scissorRectangle) : new BoundingRectangle()
  130. };
  131. this._passState = passState;
  132. this._ready = false;
  133. var name = options.name;
  134. if (!defined(name)) {
  135. name = createGuid();
  136. }
  137. this._name = name;
  138. this._logDepthChanged = undefined;
  139. this._useLogDepth = undefined;
  140. this._selectedIdTexture = undefined;
  141. this._selected = undefined;
  142. this._selectedShadow = undefined;
  143. this._parentSelected = undefined;
  144. this._parentSelectedShadow = undefined;
  145. this._combinedSelected = undefined;
  146. this._combinedSelectedShadow = undefined;
  147. this._selectedLength = 0;
  148. this._parentSelectedLength = 0;
  149. this._selectedDirty = true;
  150. // set by PostProcessStageCollection
  151. this._textureCache = undefined;
  152. this._index = undefined;
  153. /**
  154. * Whether or not to execute this post-process stage when ready.
  155. *
  156. * @type {Boolean}
  157. */
  158. this.enabled = true;
  159. this._enabled = true;
  160. }
  161. defineProperties(PostProcessStage.prototype, {
  162. /**
  163. * Determines if this post-process stage is ready to be executed. A stage is only executed when both <code>ready</code>
  164. * and {@link PostProcessStage#enabled} are <code>true</code>. A stage will not be ready while it is waiting on textures
  165. * to load.
  166. *
  167. * @memberof PostProcessStage.prototype
  168. * @type {Boolean}
  169. * @readonly
  170. */
  171. ready : {
  172. get : function() {
  173. return this._ready;
  174. }
  175. },
  176. /**
  177. * The unique name of this post-process stage for reference by other stages in a {@link PostProcessStageComposite}.
  178. *
  179. * @memberof PostProcessStage.prototype
  180. * @type {String}
  181. * @readonly
  182. */
  183. name : {
  184. get : function() {
  185. return this._name;
  186. }
  187. },
  188. /**
  189. * The fragment shader to use when execute this post-process stage.
  190. * <p>
  191. * The shader must contain a sampler uniform declaration for <code>colorTexture</code>, <code>depthTexture</code>,
  192. * or both.
  193. * </p>
  194. * <p>
  195. * The shader must contain a <code>vec2</code> varying declaration for <code>v_textureCoordinates</code> for sampling
  196. * the texture uniforms.
  197. * </p>
  198. *
  199. * @memberof PostProcessStage.prototype
  200. * @type {String}
  201. * @readonly
  202. */
  203. fragmentShader : {
  204. get : function() {
  205. return this._fragmentShader;
  206. }
  207. },
  208. /**
  209. * An object whose properties are used to set the uniforms of the fragment shader.
  210. * <p>
  211. * The object property values can be either a constant or a function. The function will be called
  212. * each frame before the post-process stage is executed.
  213. * </p>
  214. * <p>
  215. * A constant value can also be a URI to an image, a data URI, or an HTML element that can be used as a texture, such as HTMLImageElement or HTMLCanvasElement.
  216. * </p>
  217. * <p>
  218. * If this post-process stage is part of a {@link PostProcessStageComposite} that does not execute in series, the constant value can also be
  219. * the name of another stage in a composite. This will set the uniform to the output texture the stage with that name.
  220. * </p>
  221. *
  222. * @memberof PostProcessStage.prototype
  223. * @type {Object}
  224. * @readonly
  225. */
  226. uniforms : {
  227. get : function() {
  228. return this._uniforms;
  229. }
  230. },
  231. /**
  232. * A number in the range (0.0, 1.0] used to scale the output texture dimensions. A scale of 1.0 will render this post-process stage to a texture the size of the viewport.
  233. *
  234. * @memberof PostProcessStage.prototype
  235. * @type {Number}
  236. * @readonly
  237. */
  238. textureScale : {
  239. get : function() {
  240. return this._textureScale;
  241. }
  242. },
  243. /**
  244. * Whether or not to force the output texture dimensions to be both equal powers of two. The power of two will be the next power of two of the minimum of the dimensions.
  245. *
  246. * @memberof PostProcessStage.prototype
  247. * @type {Number}
  248. * @readonly
  249. */
  250. forcePowerOfTwo : {
  251. get : function() {
  252. return this._forcePowerOfTwo;
  253. }
  254. },
  255. /**
  256. * How to sample the input color texture.
  257. *
  258. * @memberof PostProcessStage.prototype
  259. * @type {PostProcessStageSampleMode}
  260. * @readonly
  261. */
  262. sampleMode : {
  263. get : function() {
  264. return this._sampleMode;
  265. }
  266. },
  267. /**
  268. * The color pixel format of the output texture.
  269. *
  270. * @memberof PostProcessStage.prototype
  271. * @type {PixelFormat}
  272. * @readonly
  273. */
  274. pixelFormat : {
  275. get : function() {
  276. return this._pixelFormat;
  277. }
  278. },
  279. /**
  280. * The pixel data type of the output texture.
  281. *
  282. * @memberof PostProcessStage.prototype
  283. * @type {PixelDatatype}
  284. * @readonly
  285. */
  286. pixelDatatype : {
  287. get : function() {
  288. return this._pixelDatatype;
  289. }
  290. },
  291. /**
  292. * The color to clear the output texture to.
  293. *
  294. * @memberof PostProcessStage.prototype
  295. * @type {Color}
  296. * @readonly
  297. */
  298. clearColor : {
  299. get : function() {
  300. return this._clearColor;
  301. }
  302. },
  303. /**
  304. * The {@link BoundingRectangle} to use for the scissor test. A default bounding rectangle will disable the scissor test.
  305. *
  306. * @memberof PostProcessStage.prototype
  307. * @type {BoundingRectangle}
  308. * @readonly
  309. */
  310. scissorRectangle : {
  311. get : function() {
  312. return this._passState.scissorTest.rectangle;
  313. }
  314. },
  315. /**
  316. * A reference to the texture written to when executing this post process stage.
  317. *
  318. * @memberof PostProcessStage.prototype
  319. * @type {Texture}
  320. * @readonly
  321. * @private
  322. */
  323. outputTexture : {
  324. get : function() {
  325. if (defined(this._textureCache)) {
  326. var framebuffer = this._textureCache.getFramebuffer(this._name);
  327. if (defined(framebuffer)) {
  328. return framebuffer.getColorTexture(0);
  329. }
  330. }
  331. return undefined;
  332. }
  333. },
  334. /**
  335. * The features selected for applying the post-process.
  336. * <p>
  337. * In the fragment shader, use <code>czm_selected</code> to determine whether or not to apply the post-process
  338. * stage to that fragment. For example:
  339. * <code>
  340. * if (czm_selected(v_textureCoordinates)) {
  341. * // apply post-process stage
  342. * } else {
  343. * gl_FragColor = texture2D(colorTexture, v_textureCordinates);
  344. * }
  345. * </code>
  346. * </p>
  347. *
  348. * @memberof PostProcessStage.prototype
  349. * @type {Array}
  350. */
  351. selected : {
  352. get : function() {
  353. return this._selected;
  354. },
  355. set : function(value) {
  356. this._selected = value;
  357. }
  358. },
  359. /**
  360. * @private
  361. */
  362. parentSelected : {
  363. get : function() {
  364. return this._parentSelected;
  365. },
  366. set : function(value) {
  367. this._parentSelected = value;
  368. }
  369. }
  370. });
  371. var depthTextureRegex = /uniform\s+sampler2D\s+depthTexture/g;
  372. /**
  373. * @private
  374. */
  375. PostProcessStage.prototype._isSupported = function(context) {
  376. return !depthTextureRegex.test(this._fragmentShader) || context.depthTexture;
  377. };
  378. function getUniformValueGetterAndSetter(stage, uniforms, name) {
  379. var currentValue = uniforms[name];
  380. if (typeof currentValue === 'string' || currentValue instanceof HTMLCanvasElement || currentValue instanceof HTMLImageElement ||
  381. currentValue instanceof HTMLVideoElement || currentValue instanceof ImageData) {
  382. stage._dirtyUniforms.push(name);
  383. }
  384. return {
  385. get : function() {
  386. return uniforms[name];
  387. },
  388. set : function(value) {
  389. var currentValue = uniforms[name];
  390. uniforms[name] = value;
  391. var actualUniforms = stage._actualUniforms;
  392. var actualValue = actualUniforms[name];
  393. if (defined(actualValue) && actualValue !== currentValue && actualValue instanceof Texture && !defined(stage._textureCache.getStageByName(name))) {
  394. stage._texturesToRelease.push(actualValue);
  395. delete actualUniforms[name];
  396. delete actualUniforms[name + 'Dimensions'];
  397. }
  398. if (currentValue instanceof Texture) {
  399. stage._texturesToRelease.push(currentValue);
  400. }
  401. if (typeof value === 'string' || value instanceof HTMLCanvasElement || value instanceof HTMLImageElement ||
  402. value instanceof HTMLVideoElement || value instanceof ImageData) {
  403. stage._dirtyUniforms.push(name);
  404. } else {
  405. actualUniforms[name] = value;
  406. }
  407. }
  408. };
  409. }
  410. function getUniformMapFunction(stage, name) {
  411. return function() {
  412. var value = stage._actualUniforms[name];
  413. if (typeof value === 'function') {
  414. return value();
  415. }
  416. return value;
  417. };
  418. }
  419. function getUniformMapDimensionsFunction(uniformMap, name) {
  420. return function() {
  421. var texture = uniformMap[name]();
  422. if (defined(texture)) {
  423. return texture.dimensions;
  424. }
  425. return undefined;
  426. };
  427. }
  428. function createUniformMap(stage) {
  429. if (defined(stage._uniformMap)) {
  430. return;
  431. }
  432. var uniformMap = {};
  433. var newUniforms = {};
  434. var uniforms = stage._uniforms;
  435. var actualUniforms = stage._actualUniforms;
  436. for (var name in uniforms) {
  437. if (uniforms.hasOwnProperty(name)) {
  438. if (typeof uniforms[name] !== 'function') {
  439. uniformMap[name] = getUniformMapFunction(stage, name);
  440. newUniforms[name] = getUniformValueGetterAndSetter(stage, uniforms, name);
  441. } else {
  442. uniformMap[name] = uniforms[name];
  443. newUniforms[name] = uniforms[name];
  444. }
  445. actualUniforms[name] = uniforms[name];
  446. var value = uniformMap[name]();
  447. if (typeof value === 'string' || value instanceof Texture || value instanceof HTMLImageElement ||
  448. value instanceof HTMLCanvasElement || value instanceof HTMLVideoElement) {
  449. uniformMap[name + 'Dimensions'] = getUniformMapDimensionsFunction(uniformMap, name);
  450. }
  451. }
  452. }
  453. stage._uniforms = {};
  454. defineProperties(stage._uniforms, newUniforms);
  455. stage._uniformMap = combine(uniformMap, {
  456. colorTexture : function() {
  457. return stage._colorTexture;
  458. },
  459. colorTextureDimensions : function() {
  460. return stage._colorTexture.dimensions;
  461. },
  462. depthTexture : function() {
  463. return stage._depthTexture;
  464. },
  465. depthTextureDimensions : function() {
  466. return stage._depthTexture.dimensions;
  467. },
  468. czm_idTexture : function() {
  469. return stage._idTexture;
  470. },
  471. czm_selectedIdTexture : function() {
  472. return stage._selectedIdTexture;
  473. },
  474. czm_selectedIdTextureStep : function() {
  475. return 1.0 / stage._selectedIdTexture.width;
  476. }
  477. });
  478. }
  479. function createDrawCommand(stage, context) {
  480. if (defined(stage._command) && !stage._logDepthChanged && !stage._selectedDirty) {
  481. return;
  482. }
  483. var fs = stage._fragmentShader;
  484. if (defined(stage._selectedIdTexture)) {
  485. var width = stage._selectedIdTexture.width;
  486. fs = fs.replace(/varying\s+vec2\s+v_textureCoordinates;/g, '');
  487. fs =
  488. '#define CZM_SELECTED_FEATURE \n' +
  489. 'uniform sampler2D czm_idTexture; \n' +
  490. 'uniform sampler2D czm_selectedIdTexture; \n' +
  491. 'uniform float czm_selectedIdTextureStep; \n' +
  492. 'varying vec2 v_textureCoordinates; \n' +
  493. 'bool czm_selected(vec2 offset) \n' +
  494. '{ \n' +
  495. ' bool selected = false;\n' +
  496. ' vec4 id = texture2D(czm_idTexture, v_textureCoordinates + offset); \n' +
  497. ' for (int i = 0; i < ' + width + '; ++i) \n' +
  498. ' { \n' +
  499. ' vec4 selectedId = texture2D(czm_selectedIdTexture, vec2((float(i) + 0.5) * czm_selectedIdTextureStep, 0.5)); \n' +
  500. ' if (all(equal(id, selectedId))) \n' +
  501. ' { \n' +
  502. ' return true; \n' +
  503. ' } \n' +
  504. ' } \n' +
  505. ' return false; \n' +
  506. '} \n\n' +
  507. 'bool czm_selected() \n' +
  508. '{ \n' +
  509. ' return czm_selected(vec2(0.0)); \n' +
  510. '} \n\n' +
  511. fs;
  512. }
  513. var fragmentShader = new ShaderSource({
  514. defines : [stage._useLogDepth ? 'LOG_DEPTH' : ''],
  515. sources : [fs]
  516. });
  517. stage._command = context.createViewportQuadCommand(fragmentShader, {
  518. uniformMap : stage._uniformMap,
  519. owner : stage
  520. });
  521. }
  522. function createSampler(stage) {
  523. var mode = stage._sampleMode;
  524. var minFilter;
  525. var magFilter;
  526. if (mode === PostProcessStageSampleMode.LINEAR) {
  527. minFilter = TextureMinificationFilter.LINEAR;
  528. magFilter = TextureMagnificationFilter.LINEAR;
  529. } else {
  530. minFilter = TextureMinificationFilter.NEAREST;
  531. magFilter = TextureMagnificationFilter.NEAREST;
  532. }
  533. var sampler = stage._sampler;
  534. if (!defined(sampler) || sampler.minificationFilter !== minFilter || sampler.magnificationFilter !== magFilter) {
  535. stage._sampler = new Sampler({
  536. wrapS : TextureWrap.CLAMP_TO_EDGE,
  537. wrapT : TextureWrap.CLAMP_TO_EDGE,
  538. minificationFilter : minFilter,
  539. magnificationFilter : magFilter
  540. });
  541. }
  542. }
  543. function createLoadImageFunction(stage, name) {
  544. return function(image) {
  545. stage._texturesToCreate.push({
  546. name : name,
  547. source : image
  548. });
  549. };
  550. }
  551. function createStageOutputTextureFunction(stage, name) {
  552. return function() {
  553. return stage._textureCache.getOutputTexture(name);
  554. };
  555. }
  556. function updateUniformTextures(stage, context) {
  557. var i;
  558. var texture;
  559. var name;
  560. var texturesToRelease = stage._texturesToRelease;
  561. var length = texturesToRelease.length;
  562. for (i = 0; i < length; ++i) {
  563. texture = texturesToRelease[i];
  564. texture = texture && texture.destroy();
  565. }
  566. texturesToRelease.length = 0;
  567. var texturesToCreate = stage._texturesToCreate;
  568. length = texturesToCreate.length;
  569. for (i = 0; i < length; ++i) {
  570. var textureToCreate = texturesToCreate[i];
  571. name = textureToCreate.name;
  572. var source = textureToCreate.source;
  573. stage._actualUniforms[name] = new Texture({
  574. context : context,
  575. source : source
  576. });
  577. }
  578. texturesToCreate.length = 0;
  579. var dirtyUniforms = stage._dirtyUniforms;
  580. if (dirtyUniforms.length === 0 && !defined(stage._texturePromise)) {
  581. stage._ready = true;
  582. return;
  583. }
  584. if (dirtyUniforms.length === 0 || defined(stage._texturePromise)) {
  585. return;
  586. }
  587. length = dirtyUniforms.length;
  588. var uniforms = stage._uniforms;
  589. var promises = [];
  590. for (i = 0; i < length; ++i) {
  591. name = dirtyUniforms[i];
  592. var stageNameUrlOrImage = uniforms[name];
  593. var stageWithName = stage._textureCache.getStageByName(stageNameUrlOrImage);
  594. if (defined(stageWithName)) {
  595. stage._actualUniforms[name] = createStageOutputTextureFunction(stage, stageNameUrlOrImage);
  596. } else if (typeof stageNameUrlOrImage === 'string') {
  597. var resource = new Resource({
  598. url : stageNameUrlOrImage
  599. });
  600. promises.push(resource.fetchImage().then(createLoadImageFunction(stage, name)));
  601. } else {
  602. stage._texturesToCreate.push({
  603. name : name,
  604. source : stageNameUrlOrImage
  605. });
  606. }
  607. }
  608. dirtyUniforms.length = 0;
  609. if (promises.length > 0) {
  610. stage._ready = false;
  611. stage._texturePromise = when.all(promises).then(function() {
  612. stage._ready = true;
  613. stage._texturePromise = undefined;
  614. });
  615. } else {
  616. stage._ready = true;
  617. }
  618. }
  619. function releaseResources(stage) {
  620. if (defined(stage._command)) {
  621. stage._command.shaderProgram = stage._command.shaderProgram && stage._command.shaderProgram.destroy();
  622. stage._command = undefined;
  623. }
  624. stage._selectedIdTexture = stage._selectedIdTexture && stage._selectedIdTexture.destroy();
  625. var textureCache = stage._textureCache;
  626. if (!defined(textureCache)) {
  627. return;
  628. }
  629. var uniforms = stage._uniforms;
  630. var actualUniforms = stage._actualUniforms;
  631. for (var name in actualUniforms) {
  632. if (actualUniforms.hasOwnProperty(name)) {
  633. if (actualUniforms[name] instanceof Texture) {
  634. if (!defined(textureCache.getStageByName(uniforms[name]))) {
  635. actualUniforms[name].destroy();
  636. }
  637. stage._dirtyUniforms.push(name);
  638. }
  639. }
  640. }
  641. }
  642. function isSelectedTextureDirty(stage) {
  643. var length = defined(stage._selected) ? stage._selected.length : 0;
  644. var parentLength = defined(stage._parentSelected) ? stage._parentSelected : 0;
  645. var dirty = stage._selected !== stage._selectedShadow || length !== stage._selectedLength;
  646. dirty = dirty || stage._parentSelected !== stage._parentSelectedShadow || parentLength !== stage._parentSelectedLength;
  647. if (defined(stage._selected) && defined(stage._parentSelected)) {
  648. stage._combinedSelected = stage._selected.concat(stage._parentSelected);
  649. } else if (defined(stage._parentSelected)) {
  650. stage._combinedSelected = stage._parentSelected;
  651. } else {
  652. stage._combinedSelected = stage._selected;
  653. }
  654. if (!dirty && defined(stage._combinedSelected)) {
  655. if (!defined(stage._combinedSelectedShadow)) {
  656. return true;
  657. }
  658. length = stage._combinedSelected.length;
  659. for (var i = 0; i < length; ++i) {
  660. if (stage._combinedSelected[i] !== stage._combinedSelectedShadow[i]) {
  661. return true;
  662. }
  663. }
  664. }
  665. return dirty;
  666. }
  667. function createSelectedTexture(stage, context) {
  668. if (!stage._selectedDirty) {
  669. return;
  670. }
  671. stage._selectedIdTexture = stage._selectedIdTexture && stage._selectedIdTexture.destroy();
  672. stage._selectedIdTexture = undefined;
  673. var features = stage._combinedSelected;
  674. if (!defined(features)) {
  675. return;
  676. }
  677. var i;
  678. var feature;
  679. var textureLength = 0;
  680. var length = features.length;
  681. for (i = 0; i < length; ++i) {
  682. feature = features[i];
  683. if (defined(feature.pickIds)) {
  684. textureLength += feature.pickIds.length;
  685. } else if (defined(feature.pickId)) {
  686. ++textureLength;
  687. }
  688. }
  689. if (length === 0 || textureLength === 0) {
  690. // max pick id is reserved
  691. var empty = new Uint8Array(4);
  692. empty[0] = 255;
  693. empty[1] = 255;
  694. empty[2] = 255;
  695. empty[3] = 255;
  696. stage._selectedIdTexture = new Texture({
  697. context : context,
  698. pixelFormat : PixelFormat.RGBA,
  699. pixelDatatype : PixelDatatype.UNSIGNED_BYTE,
  700. source : {
  701. arrayBufferView : empty,
  702. width : 1,
  703. height : 1
  704. },
  705. sampler : new Sampler({
  706. wrapS : TextureWrap.CLAMP_TO_EDGE,
  707. wrapT : TextureWrap.CLAMP_TO_EDGE,
  708. minificationFilter : TextureMinificationFilter.NEAREST,
  709. magnificationFilter : TextureMagnificationFilter.NEAREST
  710. })
  711. });
  712. return;
  713. }
  714. var pickColor;
  715. var offset = 0;
  716. var ids = new Uint8Array(textureLength * 4);
  717. for (i = 0; i < length; ++i) {
  718. feature = features[i];
  719. if (defined(feature.pickIds)) {
  720. var pickIds = feature.pickIds;
  721. var pickIdsLength = pickIds.length;
  722. for (var j = 0; j < pickIdsLength; ++j) {
  723. pickColor = pickIds[j].color;
  724. ids[offset] = Color.floatToByte(pickColor.red);
  725. ids[offset + 1] = Color.floatToByte(pickColor.green);
  726. ids[offset + 2] = Color.floatToByte(pickColor.blue);
  727. ids[offset + 3] = Color.floatToByte(pickColor.alpha);
  728. offset += 4;
  729. }
  730. } else if (defined(feature.pickId)) {
  731. pickColor = feature.pickId.color;
  732. ids[offset] = Color.floatToByte(pickColor.red);
  733. ids[offset + 1] = Color.floatToByte(pickColor.green);
  734. ids[offset + 2] = Color.floatToByte(pickColor.blue);
  735. ids[offset + 3] = Color.floatToByte(pickColor.alpha);
  736. offset += 4;
  737. }
  738. }
  739. stage._selectedIdTexture = new Texture({
  740. context : context,
  741. pixelFormat : PixelFormat.RGBA,
  742. pixelDatatype : PixelDatatype.UNSIGNED_BYTE,
  743. source : {
  744. arrayBufferView : ids,
  745. width : textureLength,
  746. height : 1
  747. },
  748. sampler : new Sampler({
  749. wrapS : TextureWrap.CLAMP_TO_EDGE,
  750. wrapT : TextureWrap.CLAMP_TO_EDGE,
  751. minificationFilter : TextureMinificationFilter.NEAREST,
  752. magnificationFilter : TextureMagnificationFilter.NEAREST
  753. })
  754. });
  755. }
  756. /**
  757. * A function that will be called before execute. Used to create WebGL resources and load any textures.
  758. * @param {Context} context The context.
  759. * @param {Boolean} useLogDepth Whether the scene uses a logarithmic depth buffer.
  760. * @private
  761. */
  762. PostProcessStage.prototype.update = function(context, useLogDepth) {
  763. if (this.enabled !== this._enabled && !this.enabled) {
  764. releaseResources(this);
  765. }
  766. this._enabled = this.enabled;
  767. if (!this._enabled) {
  768. return;
  769. }
  770. this._logDepthChanged = useLogDepth !== this._useLogDepth;
  771. this._useLogDepth = useLogDepth;
  772. this._selectedDirty = isSelectedTextureDirty(this);
  773. this._selectedShadow = this._selected;
  774. this._parentSelectedShadow = this._parentSelected;
  775. this._combinedSelectedShadow = this._combinedSelected;
  776. this._selectedLength = defined(this._selected) ? this._selected.length : 0;
  777. this._parentSelectedLength = defined(this._parentSelected) ? this._parentSelected.length : 0;
  778. createSelectedTexture(this, context);
  779. createUniformMap(this);
  780. updateUniformTextures(this, context);
  781. createDrawCommand(this, context);
  782. createSampler(this);
  783. this._selectedDirty = false;
  784. if (!this._ready) {
  785. return;
  786. }
  787. var framebuffer = this._textureCache.getFramebuffer(this._name);
  788. this._command.framebuffer = framebuffer;
  789. if (!defined(framebuffer)) {
  790. return;
  791. }
  792. var colorTexture = framebuffer.getColorTexture(0);
  793. var renderState;
  794. if (colorTexture.width !== context.drawingBufferWidth || colorTexture.height !== context.drawingBufferHeight) {
  795. renderState = this._renderState;
  796. if (!defined(renderState) || colorTexture.width !== renderState.viewport.width || colorTexture.height !== renderState.viewport.height) {
  797. this._renderState = RenderState.fromCache({
  798. viewport : new BoundingRectangle(0, 0, colorTexture.width, colorTexture.height)
  799. });
  800. }
  801. }
  802. this._command.renderState = renderState;
  803. };
  804. /**
  805. * Executes the post-process stage. The color texture is the texture rendered to by the scene or from the previous stage.
  806. * @param {Context} context The context.
  807. * @param {Texture} colorTexture The input color texture.
  808. * @param {Texture} depthTexture The input depth texture.
  809. * @param {Texture} idTexture The id texture.
  810. * @private
  811. */
  812. PostProcessStage.prototype.execute = function(context, colorTexture, depthTexture, idTexture) {
  813. if (!defined(this._command) || !defined(this._command.framebuffer) || !this._ready || !this._enabled) {
  814. return;
  815. }
  816. this._colorTexture = colorTexture;
  817. this._depthTexture = depthTexture;
  818. this._idTexture = idTexture;
  819. if (!Sampler.equals(this._colorTexture.sampler, this._sampler)) {
  820. this._colorTexture.sampler = this._sampler;
  821. }
  822. var passState = this.scissorRectangle.width > 0 && this.scissorRectangle.height > 0 ? this._passState : undefined;
  823. if (defined(passState)) {
  824. passState.context = context;
  825. }
  826. this._command.execute(context, passState);
  827. };
  828. /**
  829. * Returns true if this object was destroyed; otherwise, false.
  830. * <p>
  831. * If this object was destroyed, it should not be used; calling any function other than
  832. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
  833. * </p>
  834. *
  835. * @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
  836. *
  837. * @see PostProcessStage#destroy
  838. */
  839. PostProcessStage.prototype.isDestroyed = function() {
  840. return false;
  841. };
  842. /**
  843. * Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
  844. * release of WebGL resources, instead of relying on the garbage collector to destroy this object.
  845. * <p>
  846. * Once an object is destroyed, it should not be used; calling any function other than
  847. * <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
  848. * assign the return value (<code>undefined</code>) to the object as done in the example.
  849. * </p>
  850. *
  851. * @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
  852. *
  853. * @see PostProcessStage#isDestroyed
  854. */
  855. PostProcessStage.prototype.destroy = function() {
  856. releaseResources(this);
  857. return destroyObject(this);
  858. };
  859. export default PostProcessStage;