diff --git a/extensions/proposals/WEBGL_multisampled_render_to_texture/extension.xml b/extensions/proposals/WEBGL_multisampled_render_to_texture/extension.xml new file mode 100644 index 0000000000..0ac5f8c550 --- /dev/null +++ b/extensions/proposals/WEBGL_multisampled_render_to_texture/extension.xml @@ -0,0 +1,160 @@ +<?xml version="1.0"?> + +<extension href="WEBGL_multisampled_render_to_texture/"> + <name>WEBGL_multisampled_render_to_texture</name> + <contact> + <a href="https://www.khronos.org/webgl/public-mailing-list/">WebGL working group</a> (public_webgl 'at' khronos.org) + </contact> + <contributors> + <contributor>Rik Cabanier, Facebook</contributor> + </contributors> + <number>??</number> + <depends> + <api version="1.0"/> + </depends> + <overview> + <mirrors href="https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_multisampled_render_to_texture2.txt" name="EXT_multisampled_render_to_texture2"> + </mirrors> + <features> + <feature> + Adds support for rendering multisampled to a color renderable texture, without requiring an explicit resolve of multisample data. + </feature> + </features> + </overview> + <idl xml:space="preserve"> +[Exposed=(Window,Worker), LegacyNoInterfaceObject] +interface WEBGL_multisampled_render_to_texture { + const GLenum RENDERBUFFER_SAMPLES_EXT = 0x8CAB; + const GLenum FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT = 0x8D56; + const GLenum MAX_SAMPLES_EXT = 0x8D57; + const GLenum FRAMEBUFFER_ATTACHMENT_TEXTURE_SAMPLES_EXT = 0x8D6C; + + undefined renderbufferStorageMultisampleEXT( + GLenum target, GLsizei samples, + GLenum internalformat, + GLsizei width, GLsizei height); + + undefined framebufferTexture2DMultisampleEXT( + GLenum target, GLenum attachment, + GLenum textarget, WebGLTexture? texture, + GLint level, GLsizei samples); +}; + </idl> + <newfun> + <function name="renderbufferStorageMultisampleEXT" type="undefined"> + <param name="target" type="GLenum"/> + <param name="samples" type="GLsizei"/> + <param name="internalformat" type="GLenum"/> + <param name="width" type="GLsizei"/> + <param name="height" type="GLsizei"/> + </function> + </newfun> + <newfun> + <function name="framebufferTexture2DMultisampleEXT" type="undefined"> + <param name="target" type="GLenum"/> + <param name="attachment" type="GLenum"/> + <param name="textarget" type="GLenum"/> + <param name="texture" type="GLuint"/> + <param name="level" type="GLint"/> + <param name="samples" type="GLsizei"/> + </function> + </newfun> + <newtok> + <function name="GetRenderbufferParameter" type="any"> + <param name="target" type="GLenum"/> + <param name="pname" type="GLenum"/> + Calling with the <code>pname</code> set to <code>RENDERBUFFER_SAMPLES_EXT</code> returns the number of samples. + <br/> +<!-- + If <code>renderbufferStorageMultisampleEXT</code> is called with <code>samples</code> is zero, then <code>RENDERBUFFER_SAMPLES_EXT</code> is set to zero. + Otherwise <code>samples</code> represents a request for a desired minimum number + of samples. Since different implementations may support different + sample counts for multisampled rendering, the actual number of samples + allocated for the renderbuffer image is implementation-dependent. + However, the resulting value for <code>RENDERBUFFER_SAMPLES_EXT</code> is + guaranteed to be greater than or equal to samples and no more than the + next larger sample count supported by the implementation. + <br/> +--> + The return type depends on the parameter queried: + <table width="30%"> + <tr><th>pname</th><th>returned type</th></tr> + <tr><td>RENDERBUFFER_SAMPLES_EXT</td><td>GLint</td></tr> + </table> + </function> + <function name="checkFramebufferStatus" type="GLenum"> + <param name="target" type="GLenum"/> + </function> + <function name="getParameter" type="any"> + <param name="pname" type="GLenum"/> + Calling with the <code>pname</code> set to <code>MAX_SAMPLES_EXT</code> returns the maximum number of samples. + <br/> + + The return type depends on the parameter queried: + <table width="30%"> + <tr><th>pname</th><th>returned type</th></tr> + <tr><td>MAX_SAMPLES_EXT</td><td>GLint</td></tr> + </table> + </function> + <function name="getFramebufferAttachmentParameter" type="any"> + <param name="target" type="GLenum"/> + <param name="attachment" type="GLenum"/> + <param name="pname" type="GLenum"/> + Calling with the <code>pname</code> set to <code>FRAMEBUFFER_ATTACHMENT_TEXTURE_SAMPLES_EXT</code> returns the number of samples in the multisampled texture. + <br/> + The return type depends on the parameter queried: + <table width="30%"> + <tr><th>pname</th><th>returned type</th></tr> + <tr><td>MAX_SAMPLES_EXT</td><td>GLint</td></tr> + </table> + </function> + </newtok> + <errors> + <error> + The error <code>OUT_OF_MEMORY</code> is generated when <code>RenderbufferStorageMultisampleEXT</code> cannot create storage of the specified size. + </error> + <error> + If <code>RenderbufferStorageMultisampleEXT</code> is called with a value of <code>samples</code> that is greater than <code>MAX_SAMPLES_EXT</code>, then the error <code>INVALID_VALUE</code> is generated. + </error> + <error> + The error <code>INVALID_ENUM</code> is generated if<code> FramebufferTexture2DMultisampleEXT</code> is called with a target that is not <code>FRAMEBUFFER</code>, <code>DRAW_FRAMEBUFFER</code>, or <code>READ_FRAMEBUFFER</code>. + </error> + <error> + The error <code>INVALID_ENUM</code> is generated if <code>FramebufferTexture2DMultisampleEXT</code> is called with an <code>attachment</code> that is not <code>COLOR_ATTACHMENT0</code>. + </error> + <error> + The error <code>INVALID_ENUM</code> is generated if <code>FramebufferTexture2DMultisampleEXT</code> is called with a textarget that is not <code>TEXTURE_2D</code>, <code>TEXTURE_CUBE_MAP_POSITIVE_X</code>, <code>TEXTURE_CUBE_MAP_POSITIVE_Y</code>, <code>TEXTURE_CUBE_MAP_POSITIVE_Z</code>, <code>TEXTURE_CUBE_MAP_NEGATIVE_X</code>, <code>TEXTURE_CUBE_MAP_NEGATIVE_Y</code>, or <code>TEXTURE_CUBE_MAP_NEGATIVE_Z</code>. + </error> + <error> + The error <code>INVALID_OPERATION</code> is generated if <code>FramebufferTexture2DMultisampleEXT</code> is called with samples greater than the maximum number of samples supported for target and its internalformat. + </error> + </errors> + <samplecode xml:space="preserve"> + <pre> + var gl = document.createElement('canvas').getContext('webgl2'); + var ext = gl.getExtension('WEBGL_multisampled_render_to_texture'); + var samples = gl.getParameter(ext.MAX_SAMPLES_EXT); + var fb = gl.createFramebuffer(); + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, fb); + + // Create color texture and storage. + var colorTex = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, colorTex); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, 512, 512); + ext.framebufferTexture2DMultisampleEXT(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTex, 0, samples); + + // Create depth texture and storage. + var depthStencilTex = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, depthStencilTex); + ext.renderbufferStorageMultisampleEXT(gl.RENDER_BUFFER, samples, gl.DEPTH32F_STENCIL8, 512, 512); + ext.framebufferTexture2DMultisampleEXT(gl.DRAW_FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.TEXTURE_2D, depthStencilTex, 0, samples); + + gl.drawElements(...); // Draw will be done with multisampling and flushed to the non-multisample texture. + </pre> + </samplecode> + <history> + <revision date="2021/10/09"> + <change>Initial revision.</change> + </revision> + </history> +</extension> diff --git a/sdk/tests/conformance2/extensions/webgl_multisampled_render_to_texture.html b/sdk/tests/conformance2/extensions/webgl_multisampled_render_to_texture.html new file mode 100644 index 0000000000..b16319b40c --- /dev/null +++ b/sdk/tests/conformance2/extensions/webgl_multisampled_render_to_texture.html @@ -0,0 +1,181 @@ +<!-- +Copyright (c) 2019 The Khronos Group Inc. +Use of this source code is governed by an MIT-style license that can be +found in the LICENSE.txt file. +--> + +<!DOCTYPE html> +<html> +<head> +<meta charset="utf-8"> +<title>WebGL OVR_multiview2 Conformance Tests</title> +<link rel="stylesheet" href="../../resources/js-test-style.css"/> +<script src="../../js/js-test-pre.js"></script> +<script src="../../js/webgl-test-utils.js"></script> +</head> +<body> +<div id="description"></div> +<div id="console"></div> +<script> +"use strict"; + +let wtu = WebGLTestUtils; +let gl = wtu.create3DContext(null, null, 2); +let ext = null; + +function runMultisampledTest() { + debug(""); + debug("Testing that color is written as multisampled"); + let program = wtu.setupColorQuad(gl, 0); + gl.viewport(0, 0, 2, 2); + let color = [0, 1.0, 0, 1.0]; + + let fb = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, fb); + + let colorTex = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, colorTex); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, 1, 1); + ext.framebufferTexture2DMultisampleEXT(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTex, 0, 4); + + gl.clearColor(1, 1, 1, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from clear"); + + gl.useProgram(program); + wtu.drawFloatColorQuad(gl, color); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from drawFloatColorQuad"); + + gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTex, 0); + let buf = new Uint8Array(4); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, buf); + if (buf[0] != 0 || + buf[1] != 255 || + buf[2] != 0 || + buf[3] != 255) { + testFailed("Pixel at should have been (0, 255, 0, 255), " + + "was (" + buf[0] + ", " + buf[1] + ", " + buf[2] + ", " + buf[3] + ")"); + } else + testPassed("runMultisampledTest with full green rgb"); + + gl.bindFramebuffer(gl.FRAMEBUFFER, fb); + let colorTex_partial = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, colorTex_partial); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, 1, 1); + ext.framebufferTexture2DMultisampleEXT(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTex_partial, 0, 4); + + gl.clearColor(1, 1, 1, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from clear"); + + program = wtu.setupColorQuad(gl, 0, { scale: 0.5 }); + gl.useProgram(program); + wtu.drawFloatColorQuad(gl, color); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from drawFloatColorQuad"); + + gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTex_partial, 0); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, buf); + if (buf[0] != 191 || + buf[1] != 255 || + buf[2] != 191 || + buf[3] != 255) { + testFailed("Pixel at should have been (191, 255, 191, 255), " + + "was (" + buf[0] + ", " + buf[1] + ", " + buf[2] + ", " + buf[3] + ")"); + } else + testPassed("runMultisampledTest with partial green rgb"); + + let colorTex_srgb = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, colorTex_srgb); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.SRGB8_ALPHA8, 1, 1); + ext.framebufferTexture2DMultisampleEXT(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTex_srgb, 0, 4); + + gl.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from clear"); + + gl.useProgram(program); + wtu.drawFloatColorQuad(gl, color); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from drawFloatColorQuad"); + + gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTex_srgb, 0); + gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, buf); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from readpixels"); + if (buf[0] != 0 || + buf[1] != 63 || + buf[2] != 0 || + buf[3] != 63) { + testFailed("Pixel at should have been (0, 63, 0, 63), " + + "was (" + buf[0] + ", " + buf[1] + ", " + buf[2] + ", " + buf[3] + ")"); + } else + testPassed("runMultisampledTest with partial srgb green"); + + debug(""); +} + +function runDrawBuffersClearTest() +{ + debug("Testing that calling clear() clears all views in all draw buffers"); + + let width = 256; + let height = 256; + + let samples = gl.getParameter(ext.MAX_SAMPLES_EXT); + + let fb = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, fb); + let colorTex = [null, null, null]; + let drawBuffers = [0, 0, 0]; + for (let texIndex = 0; texIndex < colorTex.length; ++texIndex) { + colorTex[texIndex] = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, colorTex[texIndex]); + gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, width, height); + ext.framebufferTexture2DMultisampleEXT(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + texIndex, gl.TEXTURE_2D, colorTex[texIndex], 0, samples); + drawBuffers[texIndex] = gl.COLOR_ATTACHMENT0 + texIndex; + } + + gl.viewport(0, 0, width, height); + gl.drawBuffers(drawBuffers); + + gl.clearColor(0, 1, 1, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors from clear"); + + let readFb = gl.createFramebuffer(); + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, readFb); + for (let texIndex = 0; texIndex < colorTex.length; ++texIndex) { + gl.framebufferTexture2D(gl.READ_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTex[texIndex], 0); + let expectedColor = [0, 255, 255, 255]; + wtu.checkCanvasRect(gl, 0, 0, width, height, expectedColor, 'color attachment ' + texIndex + ' should be cyan'); + } + + testPassed("runDrawBuffersClearTest"); +} + +description("This test verifies the functionality of the WEBGL_multisampled_render_to_texture extension, if it is available."); + +debug(""); + +if (!gl) { + testFailed("WebGL context does not exist"); +} else { + testPassed("WebGL context exists"); + + if (!gl.getExtension("WEBGL_multisampled_render_to_texture")) { + testPassed("No WEBGL_multisampled_render_to_texture support -- this is legal"); + } else { + testPassed("Successfully enabled WEBGL_multisampled_render_to_texture extension"); + ext = gl.getExtension('WEBGL_multisampled_render_to_texture'); + + runMultisampledTest(); + + runDrawBuffersClearTest(); + } +} + +debug(""); +var successfullyParsed = true; +</script> +<script src="../../js/js-test-post.js"></script> + +</body> +</html>