Framebuffer.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. import Check from '../Core/Check.js';
  2. import defaultValue from '../Core/defaultValue.js';
  3. import defined from '../Core/defined.js';
  4. import defineProperties from '../Core/defineProperties.js';
  5. import destroyObject from '../Core/destroyObject.js';
  6. import DeveloperError from '../Core/DeveloperError.js';
  7. import PixelFormat from '../Core/PixelFormat.js';
  8. import ContextLimits from './ContextLimits.js';
  9. import PixelDatatype from './PixelDatatype.js';
  10. function attachTexture(framebuffer, attachment, texture) {
  11. var gl = framebuffer._gl;
  12. gl.framebufferTexture2D(gl.FRAMEBUFFER, attachment, texture._target, texture._texture, 0);
  13. }
  14. function attachRenderbuffer(framebuffer, attachment, renderbuffer) {
  15. var gl = framebuffer._gl;
  16. gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, renderbuffer._getRenderbuffer());
  17. }
  18. /**
  19. * Creates a framebuffer with optional initial color, depth, and stencil attachments.
  20. * Framebuffers are used for render-to-texture effects; they allow us to render to
  21. * textures in one pass, and read from it in a later pass.
  22. *
  23. * @param {Object} options The initial framebuffer attachments as shown in the example below. <code>context</code> is required. The possible properties are <code>colorTextures</code>, <code>colorRenderbuffers</code>, <code>depthTexture</code>, <code>depthRenderbuffer</code>, <code>stencilRenderbuffer</code>, <code>depthStencilTexture</code>, and <code>depthStencilRenderbuffer</code>.
  24. *
  25. * @exception {DeveloperError} Cannot have both color texture and color renderbuffer attachments.
  26. * @exception {DeveloperError} Cannot have both a depth texture and depth renderbuffer attachment.
  27. * @exception {DeveloperError} Cannot have both a depth-stencil texture and depth-stencil renderbuffer attachment.
  28. * @exception {DeveloperError} Cannot have both a depth and depth-stencil renderbuffer.
  29. * @exception {DeveloperError} Cannot have both a stencil and depth-stencil renderbuffer.
  30. * @exception {DeveloperError} Cannot have both a depth and stencil renderbuffer.
  31. * @exception {DeveloperError} The color-texture pixel-format must be a color format.
  32. * @exception {DeveloperError} The depth-texture pixel-format must be DEPTH_COMPONENT.
  33. * @exception {DeveloperError} The depth-stencil-texture pixel-format must be DEPTH_STENCIL.
  34. * @exception {DeveloperError} The number of color attachments exceeds the number supported.
  35. * @exception {DeveloperError} The color-texture pixel datatype is HALF_FLOAT and the WebGL implementation does not support the EXT_color_buffer_half_float extension.
  36. * @exception {DeveloperError} The color-texture pixel datatype is FLOAT and the WebGL implementation does not support the EXT_color_buffer_float or WEBGL_color_buffer_float extensions.
  37. *
  38. * @example
  39. * // Create a framebuffer with color and depth texture attachments.
  40. * var width = context.canvas.clientWidth;
  41. * var height = context.canvas.clientHeight;
  42. * var framebuffer = new Framebuffer({
  43. * context : context,
  44. * colorTextures : [new Texture({
  45. * context : context,
  46. * width : width,
  47. * height : height,
  48. * pixelFormat : PixelFormat.RGBA
  49. * })],
  50. * depthTexture : new Texture({
  51. * context : context,
  52. * width : width,
  53. * height : height,
  54. * pixelFormat : PixelFormat.DEPTH_COMPONENT,
  55. * pixelDatatype : PixelDatatype.UNSIGNED_SHORT
  56. * })
  57. * });
  58. *
  59. * @private
  60. */
  61. function Framebuffer(options) {
  62. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  63. var context = options.context;
  64. //>>includeStart('debug', pragmas.debug);
  65. Check.defined('options.context', context);
  66. //>>includeEnd('debug');
  67. var gl = context._gl;
  68. var maximumColorAttachments = ContextLimits.maximumColorAttachments;
  69. this._gl = gl;
  70. this._framebuffer = gl.createFramebuffer();
  71. this._colorTextures = [];
  72. this._colorRenderbuffers = [];
  73. this._activeColorAttachments = [];
  74. this._depthTexture = undefined;
  75. this._depthRenderbuffer = undefined;
  76. this._stencilRenderbuffer = undefined;
  77. this._depthStencilTexture = undefined;
  78. this._depthStencilRenderbuffer = undefined;
  79. /**
  80. * When true, the framebuffer owns its attachments so they will be destroyed when
  81. * {@link Framebuffer#destroy} is called or when a new attachment is assigned
  82. * to an attachment point.
  83. *
  84. * @type {Boolean}
  85. * @default true
  86. *
  87. * @see Framebuffer#destroy
  88. */
  89. this.destroyAttachments = defaultValue(options.destroyAttachments, true);
  90. // Throw if a texture and renderbuffer are attached to the same point. This won't
  91. // cause a WebGL error (because only one will be attached), but is likely a developer error.
  92. //>>includeStart('debug', pragmas.debug);
  93. if (defined(options.colorTextures) && defined(options.colorRenderbuffers)) {
  94. throw new DeveloperError('Cannot have both color texture and color renderbuffer attachments.');
  95. }
  96. if (defined(options.depthTexture) && defined(options.depthRenderbuffer)) {
  97. throw new DeveloperError('Cannot have both a depth texture and depth renderbuffer attachment.');
  98. }
  99. if (defined(options.depthStencilTexture) && defined(options.depthStencilRenderbuffer)) {
  100. throw new DeveloperError('Cannot have both a depth-stencil texture and depth-stencil renderbuffer attachment.');
  101. }
  102. //>>includeEnd('debug');
  103. // Avoid errors defined in Section 6.5 of the WebGL spec
  104. var depthAttachment = (defined(options.depthTexture) || defined(options.depthRenderbuffer));
  105. var depthStencilAttachment = (defined(options.depthStencilTexture) || defined(options.depthStencilRenderbuffer));
  106. //>>includeStart('debug', pragmas.debug);
  107. if (depthAttachment && depthStencilAttachment) {
  108. throw new DeveloperError('Cannot have both a depth and depth-stencil attachment.');
  109. }
  110. if (defined(options.stencilRenderbuffer) && depthStencilAttachment) {
  111. throw new DeveloperError('Cannot have both a stencil and depth-stencil attachment.');
  112. }
  113. if (depthAttachment && defined(options.stencilRenderbuffer)) {
  114. throw new DeveloperError('Cannot have both a depth and stencil attachment.');
  115. }
  116. //>>includeEnd('debug');
  117. ///////////////////////////////////////////////////////////////////
  118. this._bind();
  119. var texture;
  120. var renderbuffer;
  121. var i;
  122. var length;
  123. var attachmentEnum;
  124. if (defined(options.colorTextures)) {
  125. var textures = options.colorTextures;
  126. length = this._colorTextures.length = this._activeColorAttachments.length = textures.length;
  127. //>>includeStart('debug', pragmas.debug);
  128. if (length > maximumColorAttachments) {
  129. throw new DeveloperError('The number of color attachments exceeds the number supported.');
  130. }
  131. //>>includeEnd('debug');
  132. for (i = 0; i < length; ++i) {
  133. texture = textures[i];
  134. //>>includeStart('debug', pragmas.debug);
  135. if (!PixelFormat.isColorFormat(texture.pixelFormat)) {
  136. throw new DeveloperError('The color-texture pixel-format must be a color format.');
  137. }
  138. if (texture.pixelDatatype === PixelDatatype.FLOAT && !context.colorBufferFloat) {
  139. throw new DeveloperError('The color texture pixel datatype is FLOAT and the WebGL implementation does not support the EXT_color_buffer_float or WEBGL_color_buffer_float extensions. See Context.colorBufferFloat.');
  140. }
  141. if (texture.pixelDatatype === PixelDatatype.HALF_FLOAT && !context.colorBufferHalfFloat) {
  142. throw new DeveloperError('The color texture pixel datatype is HALF_FLOAT and the WebGL implementation does not support the EXT_color_buffer_half_float extension. See Context.colorBufferHalfFloat.');
  143. }
  144. //>>includeEnd('debug');
  145. attachmentEnum = this._gl.COLOR_ATTACHMENT0 + i;
  146. attachTexture(this, attachmentEnum, texture);
  147. this._activeColorAttachments[i] = attachmentEnum;
  148. this._colorTextures[i] = texture;
  149. }
  150. }
  151. if (defined(options.colorRenderbuffers)) {
  152. var renderbuffers = options.colorRenderbuffers;
  153. length = this._colorRenderbuffers.length = this._activeColorAttachments.length = renderbuffers.length;
  154. //>>includeStart('debug', pragmas.debug);
  155. if (length > maximumColorAttachments) {
  156. throw new DeveloperError('The number of color attachments exceeds the number supported.');
  157. }
  158. //>>includeEnd('debug');
  159. for (i = 0; i < length; ++i) {
  160. renderbuffer = renderbuffers[i];
  161. attachmentEnum = this._gl.COLOR_ATTACHMENT0 + i;
  162. attachRenderbuffer(this, attachmentEnum, renderbuffer);
  163. this._activeColorAttachments[i] = attachmentEnum;
  164. this._colorRenderbuffers[i] = renderbuffer;
  165. }
  166. }
  167. if (defined(options.depthTexture)) {
  168. texture = options.depthTexture;
  169. //>>includeStart('debug', pragmas.debug);
  170. if (texture.pixelFormat !== PixelFormat.DEPTH_COMPONENT) {
  171. throw new DeveloperError('The depth-texture pixel-format must be DEPTH_COMPONENT.');
  172. }
  173. //>>includeEnd('debug');
  174. attachTexture(this, this._gl.DEPTH_ATTACHMENT, texture);
  175. this._depthTexture = texture;
  176. }
  177. if (defined(options.depthRenderbuffer)) {
  178. renderbuffer = options.depthRenderbuffer;
  179. attachRenderbuffer(this, this._gl.DEPTH_ATTACHMENT, renderbuffer);
  180. this._depthRenderbuffer = renderbuffer;
  181. }
  182. if (defined(options.stencilRenderbuffer)) {
  183. renderbuffer = options.stencilRenderbuffer;
  184. attachRenderbuffer(this, this._gl.STENCIL_ATTACHMENT, renderbuffer);
  185. this._stencilRenderbuffer = renderbuffer;
  186. }
  187. if (defined(options.depthStencilTexture)) {
  188. texture = options.depthStencilTexture;
  189. //>>includeStart('debug', pragmas.debug);
  190. if (texture.pixelFormat !== PixelFormat.DEPTH_STENCIL) {
  191. throw new DeveloperError('The depth-stencil pixel-format must be DEPTH_STENCIL.');
  192. }
  193. //>>includeEnd('debug');
  194. attachTexture(this, this._gl.DEPTH_STENCIL_ATTACHMENT, texture);
  195. this._depthStencilTexture = texture;
  196. }
  197. if (defined(options.depthStencilRenderbuffer)) {
  198. renderbuffer = options.depthStencilRenderbuffer;
  199. attachRenderbuffer(this, this._gl.DEPTH_STENCIL_ATTACHMENT, renderbuffer);
  200. this._depthStencilRenderbuffer = renderbuffer;
  201. }
  202. this._unBind();
  203. }
  204. defineProperties(Framebuffer.prototype, {
  205. /**
  206. * The status of the framebuffer. If the status is not WebGLConstants.FRAMEBUFFER_COMPLETE,
  207. * a {@link DeveloperError} will be thrown when attempting to render to the framebuffer.
  208. * @memberof Framebuffer.prototype
  209. * @type {Number}
  210. */
  211. status : {
  212. get : function() {
  213. this._bind();
  214. var status = this._gl.checkFramebufferStatus(this._gl.FRAMEBUFFER);
  215. this._unBind();
  216. return status;
  217. }
  218. },
  219. numberOfColorAttachments : {
  220. get : function() {
  221. return this._activeColorAttachments.length;
  222. }
  223. },
  224. depthTexture: {
  225. get : function() {
  226. return this._depthTexture;
  227. }
  228. },
  229. depthRenderbuffer: {
  230. get : function() {
  231. return this._depthRenderbuffer;
  232. }
  233. },
  234. stencilRenderbuffer : {
  235. get : function() {
  236. return this._stencilRenderbuffer;
  237. }
  238. },
  239. depthStencilTexture : {
  240. get : function() {
  241. return this._depthStencilTexture;
  242. }
  243. },
  244. depthStencilRenderbuffer : {
  245. get : function() {
  246. return this._depthStencilRenderbuffer;
  247. }
  248. },
  249. /**
  250. * True if the framebuffer has a depth attachment. Depth attachments include
  251. * depth and depth-stencil textures, and depth and depth-stencil renderbuffers. When
  252. * rendering to a framebuffer, a depth attachment is required for the depth test to have effect.
  253. * @memberof Framebuffer.prototype
  254. * @type {Boolean}
  255. */
  256. hasDepthAttachment : {
  257. get : function() {
  258. return !!(this.depthTexture || this.depthRenderbuffer || this.depthStencilTexture || this.depthStencilRenderbuffer);
  259. }
  260. }
  261. });
  262. Framebuffer.prototype._bind = function() {
  263. var gl = this._gl;
  264. gl.bindFramebuffer(gl.FRAMEBUFFER, this._framebuffer);
  265. };
  266. Framebuffer.prototype._unBind = function() {
  267. var gl = this._gl;
  268. gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  269. };
  270. Framebuffer.prototype._getActiveColorAttachments = function() {
  271. return this._activeColorAttachments;
  272. };
  273. Framebuffer.prototype.getColorTexture = function(index) {
  274. //>>includeStart('debug', pragmas.debug);
  275. if (!defined(index) || index < 0 || index >= this._colorTextures.length) {
  276. throw new DeveloperError('index is required, must be greater than or equal to zero and must be less than the number of color attachments.');
  277. }
  278. //>>includeEnd('debug');
  279. return this._colorTextures[index];
  280. };
  281. Framebuffer.prototype.getColorRenderbuffer = function(index) {
  282. //>>includeStart('debug', pragmas.debug);
  283. if (!defined(index) || index < 0 || index >= this._colorRenderbuffers.length) {
  284. throw new DeveloperError('index is required, must be greater than or equal to zero and must be less than the number of color attachments.');
  285. }
  286. //>>includeEnd('debug');
  287. return this._colorRenderbuffers[index];
  288. };
  289. Framebuffer.prototype.isDestroyed = function() {
  290. return false;
  291. };
  292. Framebuffer.prototype.destroy = function() {
  293. if (this.destroyAttachments) {
  294. // If the color texture is a cube map face, it is owned by the cube map, and will not be destroyed.
  295. var i = 0;
  296. var textures = this._colorTextures;
  297. var length = textures.length;
  298. for (; i < length; ++i) {
  299. var texture = textures[i];
  300. if (defined(texture)) {
  301. texture.destroy();
  302. }
  303. }
  304. var renderbuffers = this._colorRenderbuffers;
  305. length = renderbuffers.length;
  306. for (i = 0; i < length; ++i) {
  307. var renderbuffer = renderbuffers[i];
  308. if (defined(renderbuffer)) {
  309. renderbuffer.destroy();
  310. }
  311. }
  312. this._depthTexture = this._depthTexture && this._depthTexture.destroy();
  313. this._depthRenderbuffer = this._depthRenderbuffer && this._depthRenderbuffer.destroy();
  314. this._stencilRenderbuffer = this._stencilRenderbuffer && this._stencilRenderbuffer.destroy();
  315. this._depthStencilTexture = this._depthStencilTexture && this._depthStencilTexture.destroy();
  316. this._depthStencilRenderbuffer = this._depthStencilRenderbuffer && this._depthStencilRenderbuffer.destroy();
  317. }
  318. this._gl.deleteFramebuffer(this._framebuffer);
  319. return destroyObject(this);
  320. };
  321. export default Framebuffer;