Framebuffer.js 15 KB

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