diff options
Diffstat (limited to 'dom/canvas/test/webgl-conf/checkout/deqp/functional/gles3/es3fMultisampleTests.js')
-rw-r--r-- | dom/canvas/test/webgl-conf/checkout/deqp/functional/gles3/es3fMultisampleTests.js | 1741 |
1 files changed, 1741 insertions, 0 deletions
diff --git a/dom/canvas/test/webgl-conf/checkout/deqp/functional/gles3/es3fMultisampleTests.js b/dom/canvas/test/webgl-conf/checkout/deqp/functional/gles3/es3fMultisampleTests.js new file mode 100644 index 000000000..1babbd35d --- /dev/null +++ b/dom/canvas/test/webgl-conf/checkout/deqp/functional/gles3/es3fMultisampleTests.js @@ -0,0 +1,1741 @@ +/*------------------------------------------------------------------------- + * drawElements Quality Program OpenGL ES Utilities + * ------------------------------------------------ + * + * Copyright 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +'use strict'; +goog.provide('functional.gles3.es3fMultisampleTests'); +goog.require('framework.common.tcuTestCase'); +goog.require('framework.delibs.debase.deMath'); +goog.require('framework.delibs.debase.deRandom'); +goog.require('framework.delibs.debase.deString'); +goog.require('framework.opengl.gluShaderProgram'); +goog.require('framework.opengl.gluStrUtil'); +goog.require('framework.opengl.gluTextureUtil'); +goog.require('framework.common.tcuImageCompare'); +goog.require('framework.common.tcuLogImage'); +goog.require('framework.common.tcuRGBA'); +goog.require('framework.common.tcuSurface'); +goog.require('framework.common.tcuTexture'); +goog.require('modules.shared.glsTextureTestUtil'); + +goog.scope(function() { + /** @type {?WebGL2RenderingContext} */ var gl; + var es3fMultisampleTests = functional.gles3.es3fMultisampleTests; + var deMath = framework.delibs.debase.deMath; + var deRandom = framework.delibs.debase.deRandom; + var deString = framework.delibs.debase.deString; + var gluShaderProgram = framework.opengl.gluShaderProgram; + var tcuRGBA = framework.common.tcuRGBA; + var tcuSurface = framework.common.tcuSurface; + var tcuTestCase = framework.common.tcuTestCase; + var tcuTexture = framework.common.tcuTexture; + var gluStrUtil = framework.opengl.gluStrUtil; + var glsTextureTestUtil = modules.shared.glsTextureTestUtil; + var tcuImageCompare = framework.common.tcuImageCompare; + var gluTextureUtil = framework.opengl.gluTextureUtil; + var tcuLogImage = framework.common.tcuLogImage; + + /** + * @constructor + * @struct + * @param {Array<number>} p0_ + * @param {Array<number>} p1_ + * @param {Array<number>} p2_ + * @param {Array<number>} p3_ + */ + es3fMultisampleTests.QuadCorners = function(p0_, p1_, p2_, p3_) { + /** @type {Array<number>} */ this.p0 = p0_; + /** @type {Array<number>} */ this.p1 = p1_; + /** @type {Array<number>} */ this.p2 = p2_; + /** @type {Array<number>} */ this.p3 = p3_; + }; + + /** + * @param {number} defaultCount + * @return {number} + */ + es3fMultisampleTests.getIterationCount = function(defaultCount) { + // The C++ test takes an argument from the command line. + // Leaving this function in case we want to be able to take an argument from the URL + return defaultCount; + }; + + /** + * @param {Array<number>} point + * @param {Array<number>} p0 + * @param {Array<number>} p1 + * @param {Array<number>} p2 + * @param {Array<number>} p3 + * @return {boolean} + */ + es3fMultisampleTests.isInsideQuad = function(point, p0, p1, p2, p3) { + /** @type {number} */ var dot0 = (point[0] - p0[0]) * (p1[1] - p0[1]) + (point[1] - p0[1]) * (p0[0] - p1[0]); + /** @type {number} */ var dot1 = (point[0] - p1[0]) * (p2[1] - p1[1]) + (point[1] - p1[1]) * (p1[0] - p2[0]); + /** @type {number} */ var dot2 = (point[0] - p2[0]) * (p3[1] - p2[1]) + (point[1] - p2[1]) * (p2[0] - p3[0]); + /** @type {number} */ var dot3 = (point[0] - p3[0]) * (p0[1] - p3[1]) + (point[1] - p3[1]) * (p3[0] - p0[0]); + + return (dot0 > 0) == (dot1 > 0) && (dot1 > 0) == (dot2 > 0) && (dot2 > 0) == (dot3 > 0); + }; + + /** + * Check if a region in an image is unicolored. + * + * Checks if the pixels in img inside the convex quadilateral defined by + * p0, p1, p2 and p3 are all (approximately) of the same color. + * + * @param {tcuSurface.Surface} img + * @param {Array<number>} p0 + * @param {Array<number>} p1 + * @param {Array<number>} p2 + * @param {Array<number>} p3 + * @return {boolean} + */ + es3fMultisampleTests.isPixelRegionUnicolored = function(img, p0, p1, p2, p3) { + /** @type {number} */ var xMin = deMath.clamp(Math.min(p0[0], p1[0], p2[0], p3[0]), 0, img.getWidth() - 1); + /** @type {number} */ var yMin = deMath.clamp(Math.min(p0[1], p1[1], p2[1], p3[1]), 0, img.getHeight() - 1); + /** @type {number} */ var xMax = deMath.clamp(Math.max(p0[0], p1[0], p2[0], p3[0]), 0, img.getWidth() - 1); + /** @type {number} */ var yMax = deMath.clamp(Math.max(p0[1], p1[1], p2[1], p3[1]), 0, img.getHeight() - 1); + /** @type {boolean} */ var insideEncountered = false; //!< Whether we have already seen at least one pixel inside the region. + /** @type {tcuRGBA.RGBA} */ var insideColor; //!< Color of the first pixel inside the region. + /** @type {tcuRGBA.RGBA} */ var threshold = tcuRGBA.newRGBAComponents(3, 3, 3, 3); + for (var y = yMin; y <= yMax; y++) + for (var x = xMin; x <= xMax; x++) + if (es3fMultisampleTests.isInsideQuad([x, y], p0, p1, p2, p3)) { + /** @type {tcuRGBA.RGBA} */ var pixColor = new tcuRGBA.RGBA(img.getPixel(x, y)); + + if (insideEncountered) + if (!tcuRGBA.compareThreshold(pixColor, insideColor, threshold)) // Pixel color differs from already-detected color inside same region - region not unicolored. + return false; + else { + insideEncountered = true; + insideColor = pixColor; + } + } + return true; + }; + + /** + * [drawUnicolorTestErrors description] + * @param {tcuSurface.Surface} img + * @param {tcuTexture.PixelBufferAccess} errorImg + * @param {Array<number>} p0 + * @param {Array<number>} p1 + * @param {Array<number>} p2 + * @param {Array<number>} p3 + * @return {boolean} + */ + es3fMultisampleTests.drawUnicolorTestErrors = function(img, errorImg, p0, p1, p2, p3) { + /** @type {number} */ var xMin = deMath.clamp(Math.min(p0[0], p1[0], p2[0], p3[0]), 0, img.getWidth() - 1); + /** @type {number} */ var yMin = deMath.clamp(Math.min(p0[1], p1[1], p2[1], p3[1]), 0, img.getHeight() - 1); + /** @type {number} */ var xMax = deMath.clamp(Math.max(p0[0], p1[0], p2[0], p3[0]), 0, img.getWidth() - 1); + /** @type {number} */ var yMax = deMath.clamp(Math.max(p0[1], p1[1], p2[1], p3[1]), 0, img.getHeight() - 1); + /** @type {tcuRGBA.RGBA} */ var refColor = new tcuRGBA.RGBA(img.getPixel(Math.floor((xMin + xMax) / 2), Math.floor((yMin + yMax) / 2))); + /** @type {tcuRGBA.RGBA} */ var threshold = tcuRGBA.newRGBAComponents(3, 3, 3, 3); + for (var y = yMin; y <= yMax; y++) + for (var x = xMin; x <= xMax; x++) + if (es3fMultisampleTests.isInsideQuad([x, y], p0, p1, p2, p3)) { + if (!tcuRGBA.compareThreshold(new tcuRGBA.RGBA(img.getPixel(x, y)), refColor, threshold)) { + img.setPixel(x, y, tcuRGBA.RGBA.red.toVec()); // TODO: this might also be toIVec() + errorImg.setPixel([1.0, 0.0, 0.0, 1.0], x, y); + } + } + return true; + }; + + /** + * @constructor + * @struct + * @param {number=} numSamples_ + * @param {boolean=} useDepth_ + * @param {boolean=} useStencil_ + */ + es3fMultisampleTests.FboParams = function(numSamples_, useDepth_, useStencil_) { + /** @type {boolean} */ var useFbo_ = true; + if (numSamples_ === undefined && useDepth_ === undefined && useStencil_ === undefined) + useFbo_ = false; + /** @type {boolean} */ this.useFbo = useFbo_; + /** @type {number} */ this.numSamples = numSamples_ === undefined ? -1 : numSamples_; + /** @type {boolean} */ this.useDepth = useDepth_ === undefined ? false : useDepth_; + /** @type {boolean} */ this.useStencil = useStencil_ === undefined ? false : useStencil_; + + }; + + /** + * @constructor + * @extends {tcuTestCase.DeqpTest} + * @param {string} name + * @param {string} desc + * @param {number} desiredViewportSize + * @param {es3fMultisampleTests.FboParams} fboParams + */ + es3fMultisampleTests.MultisampleCase = function(name, desc, desiredViewportSize, fboParams) { + tcuTestCase.DeqpTest.call(this, name, desc); + /** @type {number} */ this.m_numSamples = 0; + /** @type {number} */ this.m_viewportSize = 0; + /** @type {number} */ this.m_desiredViewportSize = desiredViewportSize; + /** @type {es3fMultisampleTests.FboParams} */ this.m_fboParams = fboParams; + /** @type {WebGLRenderbuffer} */ this.m_msColorRbo = null; + /** @type {WebGLRenderbuffer} */ this.m_msDepthStencilRbo = null; + /** @type {WebGLRenderbuffer} */ this.m_resolveColorRbo = null; + /** @type {WebGLFramebuffer} */ this.m_msFbo = null; + /** @type {WebGLFramebuffer} */ this.m_resolveFbo = null; + /** @type {gluShaderProgram.ShaderProgram} */ this.m_program = null; + /** @type {number} */ this.m_attrPositionLoc = -1; + /** @type {number} */ this.m_attrColorLoc = -1; + /** @type {number} */ this.m_renderWidth = fboParams.useFbo ? 2 * desiredViewportSize : gl.drawingBufferWidth; + /** @type {number} */ this.m_renderHeight = fboParams.useFbo ? 2 * desiredViewportSize : gl.drawingBufferHeight; + /** @type {number} */ this.m_viewportX = 0; + /** @type {number} */ this.m_viewportY = 0; + /** @type {deRandom.Random} */ this.m_rnd = new deRandom.Random(deString.deStringHash(this.name)); + if (this.m_fboParams.useFbo) + assertMsgOptions(this.m_fboParams.numSamples >= 0, 'fboParams.numSamples < 0', false, true); + }; + + es3fMultisampleTests.MultisampleCase.prototype = Object.create(tcuTestCase.DeqpTest.prototype); + + /** Copy the constructor */ + es3fMultisampleTests.MultisampleCase.prototype.constructor = es3fMultisampleTests.MultisampleCase; + + /* Rest states */ + es3fMultisampleTests.MultisampleCase.prototype.deinit = function() { + gl.colorMask(true, true, true, true); + gl.depthMask(true); + + gl.clearColor(0.0, 0.0, 0.0, 0.0); + gl.clearDepth(1.0); + gl.clearStencil(0); + + gl.disable(gl.STENCIL_TEST); + gl.disable(gl.DEPTH_TEST); + gl.disable(gl.BLEND) + gl.disable(gl.SAMPLE_COVERAGE); + gl.disable(gl.SAMPLE_ALPHA_TO_COVERAGE); + + if (this.m_program) { + gl.deleteProgram(this.m_program.getProgram()); + this.m_program = null; + } + if (this.m_msColorRbo) { + gl.deleteRenderbuffer(this.m_msColorRbo); + this.m_msColorRbo = null; + } + if (this.m_msDepthStencilRbo) { + gl.deleteRenderbuffer(this.m_msDepthStencilRbo); + this.m_msDepthStencilRbo = null; + } + if (this.m_resolveColorRbo) { + gl.deleteRenderbuffer(this.m_resolveColorRbo); + this.m_resolveColorRbo = null; + } + + if (this.m_msFbo) { + gl.deleteFramebuffer(this.m_msFbo); + this.m_msFbo = null; + } + if (this.m_resolveFbo) { + gl.deleteFramebuffer(this.m_resolveFbo); + this.m_resolveFbo = null; + } + + gl.bindRenderbuffer(gl.RENDERBUFFER, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + } + + /** + * @protected + * @param {Array<number>} p0 + * @param {Array<number>} p1 + * @param {Array<number>} p2 + * @param {Array<number>} c0 + * @param {Array<number>} c1 + * @param {Array<number>} c2 + */ + es3fMultisampleTests.MultisampleCase.prototype.renderTriangle_pAsVec3cAsVec4 = function(p0, p1, p2, c0, c1, c2) { + /** @type {Array<number>} */ var vertexPositions = [ + p0[0], p0[1], p0[2], 1.0, + p1[0], p1[1], p1[2], 1.0, + p2[0], p2[1], p2[2], 1.0 + ]; + /** @type {Array<number>} */ var vertexColors = [ + c0[0], c0[1], c0[2], c0[3], + c1[0], c1[1], c1[2], c1[3], + c2[0], c2[1], c2[2], c2[3] + ]; + + var posGLBuffer = gl.createBuffer(); + /** @type {ArrayBufferView} */ var posBuffer = new Float32Array(vertexPositions); + gl.bindBuffer(gl.ARRAY_BUFFER, posGLBuffer); + gl.bufferData(gl.ARRAY_BUFFER, posBuffer, gl.STATIC_DRAW); + + gl.enableVertexAttribArray(this.m_attrPositionLoc); + gl.vertexAttribPointer(this.m_attrPositionLoc, 4, gl.FLOAT, false, 0, 0); + + var colGLBuffer = gl.createBuffer(); + /** @type {ArrayBufferView} */ var colBuffer = new Float32Array(vertexColors); + gl.bindBuffer(gl.ARRAY_BUFFER, colGLBuffer); + gl.bufferData(gl.ARRAY_BUFFER, colBuffer, gl.STATIC_DRAW); + + gl.enableVertexAttribArray(this.m_attrColorLoc); + gl.vertexAttribPointer(this.m_attrColorLoc, 4, gl.FLOAT, false, 0, 0); + + gl.useProgram(this.m_program.getProgram()); + gl.drawArrays(gl.TRIANGLES, 0, 3); + + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.deleteBuffer(colGLBuffer); + gl.deleteBuffer(posGLBuffer); + }; + + /** + * @protected + * @param {Array<number>} p0 + * @param {Array<number>} p1 + * @param {Array<number>} p2 + * @param {Array<number>} color + */ + es3fMultisampleTests.MultisampleCase.prototype.renderTriangle_pAsVec3WithColor = function(p0, p1, p2, color) { + this.renderTriangle_pAsVec3cAsVec4(p0, p1, p2, color, color, color); + }; + + /** + * @protected + * @param {Array<number>} p0 + * @param {Array<number>} p1 + * @param {Array<number>} p2 + * @param {Array<number>} c0 + * @param {Array<number>} c1 + * @param {Array<number>} c2 + */ + es3fMultisampleTests.MultisampleCase.prototype.renderTriangle_pAsVec2 = function(p0, p1, p2, c0, c1, c2) { + this.renderTriangle_pAsVec3cAsVec4( + [p0[0], p0[1], 0.0], + [p1[0], p1[1], 0.0], + [p2[0], p2[1], 0.0], + c0, c1, c2); + }; + + /** + * @protected + * @param {Array<number>} p0 + * @param {Array<number>} p1 + * @param {Array<number>} p2 + * @param {Array<number>} color + */ + es3fMultisampleTests.MultisampleCase.prototype.renderTriangle_pAsVec2WithColor = function(p0, p1, p2, color) { + this.renderTriangle_pAsVec2(p0, p1, p2, color, color, color); + }; + + /** + * @protected + * @param {Array<number>} p0 + * @param {Array<number>} p1 + * @param {Array<number>} p2 + * @param {Array<number>} p3 + * @param {Array<number>} c0 + * @param {Array<number>} c1 + * @param {Array<number>} c2 + * @param {Array<number>} c3 + */ + es3fMultisampleTests.MultisampleCase.prototype.renderQuad = function(p0, p1, p2, p3, c0, c1, c2, c3) { + this.renderTriangle_pAsVec2(p0, p1, p2, c0, c1, c2); + this.renderTriangle_pAsVec2(p2, p1, p3, c2, c1, c3); + }; + + /** + * @protected + * @param {Array<number>} p0 + * @param {Array<number>} p1 + * @param {Array<number>} p2 + * @param {Array<number>} p3 + * @param {Array<number>} color + */ + es3fMultisampleTests.MultisampleCase.prototype.renderQuad_WithColor = function(p0, p1, p2, p3, color) { + this.renderQuad(p0, p1, p2, p3, color, color, color, color); + }; + + /** + * @protected + * @param {Array<number>} p0 + * @param {Array<number>} p1 + * @param {Array<number>} color + */ + es3fMultisampleTests.MultisampleCase.prototype.renderLine = function(p0, p1, color) { + /** @type {Array<number>} */ var vertexPositions = [ + p0[0], p0[1], 0.0, 1.0, + p1[0], p1[1], 0.0, 1.0 + ]; + /** @type {Array<number>} */ var vertexColors = [ + color[0], color[1], color[2], color[3], + color[0], color[1], color[2], color[3] + ]; + + var posGLBuffer = gl.createBuffer(); + /** @type {ArrayBufferView} */ var posBuffer = new Float32Array(vertexPositions); + gl.bindBuffer(gl.ARRAY_BUFFER, posGLBuffer); + gl.bufferData(gl.ARRAY_BUFFER, posBuffer, gl.STATIC_DRAW); + + gl.enableVertexAttribArray(this.m_attrPositionLoc); + gl.vertexAttribPointer(this.m_attrPositionLoc, 4, gl.FLOAT, false, 0, 0); + + var colGLBuffer = gl.createBuffer(); + /** @type {ArrayBufferView} */ var colBuffer = new Float32Array(vertexColors); + gl.bindBuffer(gl.ARRAY_BUFFER, colGLBuffer); + gl.bufferData(gl.ARRAY_BUFFER, colBuffer, gl.STATIC_DRAW); + + gl.enableVertexAttribArray(this.m_attrColorLoc); + gl.vertexAttribPointer(this.m_attrColorLoc, 4, gl.FLOAT, false, 0, 0); + + gl.useProgram(this.m_program.getProgram()); + gl.drawArrays(gl.LINES, 0, 2); + + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.deleteBuffer(colGLBuffer); + gl.deleteBuffer(posGLBuffer); + }; + + /** + * @protected + */ + es3fMultisampleTests.MultisampleCase.prototype.randomizeViewport = function() { + this.m_viewportX = this.m_rnd.getInt(0, this.m_renderWidth - this.m_viewportSize); + this.m_viewportY = this.m_rnd.getInt(0, this.m_renderHeight - this.m_viewportSize); + + gl.viewport(this.m_viewportX, this.m_viewportY, this.m_viewportSize, this.m_viewportSize); + }; + + /** + * @protected + * @return {tcuSurface.Surface} + */ + es3fMultisampleTests.MultisampleCase.prototype.readImage = function() { + /** @type {tcuSurface.Surface} */ + var dst = new tcuSurface.Surface(this.m_viewportSize, this.m_viewportSize); + /** @type {number} */ var pixelSize = dst.getAccess().getFormat().getPixelSize(); + /** @type {number} */ var param = deMath.deIsPowerOfTwo32(pixelSize) ? Math.min(pixelSize, 8) : 1; + /** @type {gluTextureUtil.TransferFormat} */ var format = gluTextureUtil.getTransferFormat(dst.getAccess().getFormat()); + /** @type {number} */ var width = dst.getAccess().getWidth(); + /** @type {number} */ var height = dst.getAccess().getHeight(); + if (this.m_fboParams.useFbo) { + gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, this.m_resolveFbo); + gl.blitFramebuffer(0, 0, this.m_renderWidth, this.m_renderHeight, 0, 0, this.m_renderWidth, this.m_renderHeight, gl.COLOR_BUFFER_BIT, gl.NEAREST); + gl.bindFramebuffer(gl.READ_FRAMEBUFFER, this.m_resolveFbo); + + gl.pixelStorei(gl.PACK_ALIGNMENT, param); + gl.readPixels(this.m_viewportX, this.m_viewportY, width, height, format.format, format.dataType, dst.getAccess().getDataPtr()); + + gl.bindFramebuffer(gl.FRAMEBUFFER, this.m_msFbo); + } + else { + gl.pixelStorei(gl.PACK_ALIGNMENT, param); + gl.readPixels(this.m_viewportX, this.m_viewportY, width, height, format.format, format.dataType, dst.getAccess().getDataPtr()); + } + return dst; + }; + + es3fMultisampleTests.MultisampleCase.prototype.init = function() { + /** @type {string} */ var vertShaderSource = '' + + '#version 300 es\n' + + 'in highp vec4 a_position;\n' + + 'in mediump vec4 a_color;\n' + + 'out mediump vec4 v_color;\n' + + 'void main()\n' + + '{\n' + + ' gl_Position = a_position;\n' + + ' v_color = a_color;\n' + + '}\n'; + + /** @type {string} */ var fragShaderSource = '' + + '#version 300 es\n' + + 'in mediump vec4 v_color;\n' + + 'layout(location = 0) out mediump vec4 o_color;\n' + + 'void main()\n' + + '{\n' + + ' o_color = v_color;\n' + + '}\n'; + + var numSamples = /** @type {number} */ (gl.getParameter(gl.SAMPLES)); + if (!this.m_fboParams.useFbo && numSamples <= 1) { + var msg = 'No multisample buffers'; + checkMessage(false, msg); + return false; + } + + if (this.m_fboParams.useFbo) { + if (this.m_fboParams.numSamples > 0) + this.m_numSamples = this.m_fboParams.numSamples; + else { + bufferedLogToConsole('Querying maximum number of samples for ' + gluStrUtil.getPixelFormatName(gl.RGBA8) + ' with gl.getInternalformatParameter()'); + var supportedSampleCountArray = /** @type {Int32Array} */ (gl.getInternalformatParameter(gl.RENDERBUFFER, gl.RGBA8, gl.SAMPLES)); + if (supportedSampleCountArray.length == 0) { + var msg = 'No supported sample counts'; + checkMessage(false, msg); + return false; + } + this.m_numSamples = supportedSampleCountArray[0]; + } + + bufferedLogToConsole('Using FBO of size (' + this.m_renderWidth + ', ' + this.m_renderHeight + ') with ' + this.m_numSamples + ' samples'); + } + else { + // Query and log number of samples per pixel. + this.m_numSamples = numSamples; + bufferedLogToConsole('gl.SAMPLES =' + this.m_numSamples); + } + + // Prepare program. + + assertMsgOptions(!this.m_program, 'Program loaded when it should not be.', false, true); + + this.m_program = new gluShaderProgram.ShaderProgram( + gl, + gluShaderProgram.makeVtxFragSources(vertShaderSource, fragShaderSource)); + + if (!this.m_program.isOk()) + throw new Error('Failed to compile program'); + + this.m_attrPositionLoc = gl.getAttribLocation(this.m_program.getProgram(), 'a_position'); + this.m_attrColorLoc = gl.getAttribLocation(this.m_program.getProgram(), 'a_color'); + + if (this.m_attrPositionLoc < 0 || this.m_attrColorLoc < 0) { + this.m_program = null; + throw new Error('Invalid attribute locations'); + } + + if (this.m_fboParams.useFbo) { + // Setup ms color RBO. + this.m_msColorRbo = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, this.m_msColorRbo); + + // If glRenderbufferStorageMultisample() fails, check if it's because of a too high sample count. + // \note We don't do the check until now because some implementations can't handle the gl.SAMPLES query with glGetInternalformativ(), + // and we don't want that to be the cause of test case failure. + try { + gl.renderbufferStorageMultisample(gl.RENDERBUFFER, this.m_numSamples, gl.RGBA8, this.m_renderWidth, this.m_renderHeight); + } + catch (e) { + /** @type {Int32Array} */ var supportedSampleCountArray = /** @type {Int32Array} */ (gl.getInternalformatParameter(gl.RENDERBUFFER, gl.RGBA8, gl.SAMPLES)); + var maxSampleCount = supportedSampleCountArray[0]; + if (maxSampleCount < this.m_numSamples) + throw new Error('Maximum sample count returned by gl.getInternalformatParameter() for ' + gluStrUtil.getPixelFormatName(gl.RGBA8) + ' is only ' + maxSampleCount); + else + throw new Error('Unspecified error.'); + } + + if (this.m_fboParams.useDepth || this.m_fboParams.useStencil) { + // Setup ms depth & stencil RBO. + this.m_msDepthStencilRbo = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, this.m_msDepthStencilRbo); + gl.renderbufferStorageMultisample(gl.RENDERBUFFER, this.m_numSamples, gl.DEPTH24_STENCIL8, this.m_renderWidth, this.m_renderHeight); + } + + // Setup ms FBO. + this.m_msFbo = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.m_msFbo); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, this.m_msColorRbo); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, this.m_msDepthStencilRbo); + + // Setup resolve color RBO. + this.m_resolveColorRbo = gl.createRenderbuffer(); + gl.bindRenderbuffer(gl.RENDERBUFFER, this.m_resolveColorRbo); + gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA8, this.m_renderWidth, this.m_renderHeight); + + // Setup resolve FBO. + this.m_resolveFbo = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, this.m_resolveFbo); + gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, this.m_resolveColorRbo); + + // Use ms FBO. + gl.bindFramebuffer(gl.FRAMEBUFFER, this.m_msFbo); + } + + // Get suitable viewport size. + + this.m_viewportSize = Math.min(this.m_desiredViewportSize, this.m_renderWidth, this.m_renderHeight); + this.randomizeViewport(); + return true; + }; + + /** + * Base class for cases testing the value of sample count. + * + * Draws a test pattern (defined by renderPattern() of an inheriting class) + * and counts the number of distinct colors in the resulting image. That + * number should be at least the value of sample count plus one. This is + * repeated with increased values of m_currentIteration until this correct + * number of colors is detected or m_currentIteration reaches + * m_maxNumIterations. + * + * @extends {es3fMultisampleTests.MultisampleCase} + * @constructor + * @param {string} name + * @param {string} desc + * @param {es3fMultisampleTests.FboParams} fboParams + */ + es3fMultisampleTests.NumSamplesCase = function(name, desc, fboParams) { + es3fMultisampleTests.MultisampleCase.call(this, name, desc, 256, fboParams); + /** @type {number} */ var DEFAULT_MAX_NUM_ITERATIONS = 16; + /** @type {number} */ this.m_currentIteration = 0; + /** @type {number} */ this.m_maxNumIterations = es3fMultisampleTests.getIterationCount(DEFAULT_MAX_NUM_ITERATIONS); + /** @type {Array<tcuRGBA.RGBA>} */ this.m_detectedColors = []; + }; + + es3fMultisampleTests.NumSamplesCase.prototype = Object.create(es3fMultisampleTests.MultisampleCase.prototype); + + /** Copy the constructor */ + es3fMultisampleTests.NumSamplesCase.prototype.constructor = es3fMultisampleTests.NumSamplesCase; + + /** + * @return {tcuTestCase.IterateResult} + */ + es3fMultisampleTests.NumSamplesCase.prototype.iterate = function() { + this.randomizeViewport(); + + gl.clearColor(0.0, 0.0, 0.0, 1.0); + gl.clear(gl.COLOR_BUFFER_BIT); + + this.renderPattern(); + + // Read and log rendered image. + + /** @type {tcuSurface.Surface} */ var renderedImg = this.readImage(); + tcuLogImage.logImage('RenderedImage', 'Rendered image', renderedImg.getAccess()); + + // Detect new, previously unseen colors from image. + + /** @type {number} */ var requiredNumDistinctColors = this.m_numSamples + 1; + + for (var y = 0; y < renderedImg.getHeight() && this.m_detectedColors.length < requiredNumDistinctColors; y++) + for (var x = 0; x < renderedImg.getWidth() && this.m_detectedColors.length < requiredNumDistinctColors; x++) { + /** @type {tcuRGBA.RGBA} */ var color = new tcuRGBA.RGBA(renderedImg.getPixel(x, y)); + + /** @type {number} */ var i; + for (i = 0; i < this.m_detectedColors.length; i++) { + if (tcuRGBA.compareThreshold(color, this.m_detectedColors[i], tcuRGBA.newRGBAComponents(3, 3, 3, 3))) + break; + } + + if (i === this.m_detectedColors.length) + this.m_detectedColors.push(color); // Color not previously detected. + } + + // Log results. + + bufferedLogToConsole('Number of distinct colors detected so far: ' + (this.m_detectedColors.length >= requiredNumDistinctColors ? 'at least ' : '') + this.m_detectedColors.length); + + + if (this.m_detectedColors.length < requiredNumDistinctColors) { + // Haven't detected enough different colors yet. + + this.m_currentIteration++; + + if (this.m_currentIteration >= this.m_maxNumIterations) { + testFailedOptions('Failure: Number of distinct colors detected is lower than sample count+1', false); + return tcuTestCase.IterateResult.STOP; + } + else { + bufferedLogToConsole('The number of distinct colors detected is lower than sample count+1 - trying again with a slightly altered pattern'); + return tcuTestCase.IterateResult.CONTINUE; + } + } + else { + testPassedOptions('Success: The number of distinct colors detected is at least sample count+1', true); + return tcuTestCase.IterateResult.STOP; + } + }; + + /** + * @extends {es3fMultisampleTests.NumSamplesCase} + * @constructor + * @param {string} name + * @param {string} desc + * @param {number=} numFboSamples + */ + es3fMultisampleTests.PolygonNumSamplesCase = function(name, desc, numFboSamples) { + numFboSamples = numFboSamples === undefined ? 0 : numFboSamples; + /** @type {es3fMultisampleTests.FboParams} */ + var params = numFboSamples >= 0 ? new es3fMultisampleTests.FboParams(numFboSamples, false, false) : new es3fMultisampleTests.FboParams(); + es3fMultisampleTests.NumSamplesCase.call(this, name, desc, params); + }; + + es3fMultisampleTests.PolygonNumSamplesCase.prototype = Object.create(es3fMultisampleTests.NumSamplesCase.prototype); + + /** Copy the constructor */ + es3fMultisampleTests.PolygonNumSamplesCase.prototype.constructor = es3fMultisampleTests.PolygonNumSamplesCase; + + es3fMultisampleTests.PolygonNumSamplesCase.prototype.renderPattern = function() { + // The test pattern consists of several triangles with edges at different angles. + + /** @type {number} */ var numTriangles = 25; + for (var i = 0; i < numTriangles; i++) { + /** @type {number} */ var angle0 = 2.0 * Math.PI * i / numTriangles + 0.001 * this.m_currentIteration; + /** @type {number} */ var angle1 = 2.0 * Math.PI * (i + 0.5) / numTriangles + 0.001 * this.m_currentIteration; + + this.renderTriangle_pAsVec2WithColor( + [0.0, 0.0], + [Math.cos(angle0) * 0.95, Math.sin(angle0) * 0.95], + [Math.cos(angle1) * 0.95, Math.sin(angle1) * 0.95], + [1.0, 1.0, 1.0, 1.0]); + } + }; + + /** + * @extends {es3fMultisampleTests.NumSamplesCase} + * @constructor + * @param {string} name + * @param {string} desc + * @param {number=} numFboSamples + */ + es3fMultisampleTests.LineNumSamplesCase = function(name, desc, numFboSamples) { + numFboSamples = numFboSamples === undefined ? 0 : numFboSamples; + /** @type {es3fMultisampleTests.FboParams} */ + var params = numFboSamples >= 0 ? new es3fMultisampleTests.FboParams(numFboSamples, false, false) : new es3fMultisampleTests.FboParams(); + es3fMultisampleTests.NumSamplesCase.call(this, name, desc, params); + }; + + es3fMultisampleTests.LineNumSamplesCase.prototype = Object.create(es3fMultisampleTests.NumSamplesCase.prototype); + + /** Copy the constructor */ + es3fMultisampleTests.LineNumSamplesCase.prototype.constructor = es3fMultisampleTests.LineNumSamplesCase; + + es3fMultisampleTests.LineNumSamplesCase.prototype.renderPattern = function() { + // The test pattern consists of several lines at different angles. + + // We scale the number of lines based on the viewport size. This is because a gl line's thickness is + // constant in pixel units, i.e. they get relatively thicker as viewport size decreases. Thus we must + // decrease the number of lines in order to decrease the extent of overlap among the lines in the + // center of the pattern. + /** @type {number} */ var numLines = Math.floor(100.0 * Math.sqrt(this.m_viewportSize / 256.0)); + + for (var i = 0; i < numLines; i++) { + /** @type {number} */ var angle = 2.0 * Math.PI * i / numLines + 0.001 * this.m_currentIteration; + this.renderLine([0.0, 0.0], [Math.cos(angle) * 0.95, Math.sin(angle) * 0.95], [1.0, 1.0, 1.0, 1.0]); + } + }; + + /** + * Case testing behaviour of common edges when multisampling. + * + * Draws a number of test patterns, each with a number of quads, each made + * of two triangles, rotated at different angles. The inner edge inside the + * quad (i.e. the common edge of the two triangles) still should not be + * visible, despite multisampling - i.e. the two triangles forming the quad + * should never get any common coverage bits in any pixel. + * + * @extends {es3fMultisampleTests.MultisampleCase} + * @constructor + * @param {string} name + * @param {string} desc + * @param {es3fMultisampleTests.CommonEdgeCase.CaseType} caseType + * @param {number} numFboSamples + */ + es3fMultisampleTests.CommonEdgeCase = function(name, desc, caseType, numFboSamples) { + /** @type {number} */ var cases = caseType === es3fMultisampleTests.CommonEdgeCase.CaseType.SMALL_QUADS ? 128 : 32; + numFboSamples = numFboSamples === undefined ? 0 : numFboSamples; + /** @type {es3fMultisampleTests.FboParams} */ + var params = numFboSamples >= 0 ? new es3fMultisampleTests.FboParams(numFboSamples, false, false) : new es3fMultisampleTests.FboParams(); + + es3fMultisampleTests.MultisampleCase.call(this, name, desc, cases, params); + /** @type {number} */ var DEFAULT_SMALL_QUADS_ITERATIONS = 16; + /** @type {number} */ var DEFAULT_BIGGER_THAN_VIEWPORT_QUAD_ITERATIONS = 64; // 8*8 + /** @type {es3fMultisampleTests.CommonEdgeCase.CaseType} */ this.m_caseType = caseType; + /** @type {number} */ this.m_currentIteration = 0; + /** @type {number} */ + this.m_numIterations = caseType === es3fMultisampleTests.CommonEdgeCase.CaseType.SMALL_QUADS ? es3fMultisampleTests.getIterationCount(DEFAULT_SMALL_QUADS_ITERATIONS) : + caseType === es3fMultisampleTests.CommonEdgeCase.CaseType.BIGGER_THAN_VIEWPORT_QUAD ? es3fMultisampleTests.getIterationCount(DEFAULT_BIGGER_THAN_VIEWPORT_QUAD_ITERATIONS) : + 8; + }; + + es3fMultisampleTests.CommonEdgeCase.prototype = Object.create(es3fMultisampleTests.MultisampleCase.prototype); + + /** Copy the constructor */ + es3fMultisampleTests.CommonEdgeCase.prototype.constructor = es3fMultisampleTests.CommonEdgeCase; + + /** + * @enum {number} + */ + es3fMultisampleTests.CommonEdgeCase.CaseType = { + SMALL_QUADS: 0, //!< Draw several small quads per iteration. + BIGGER_THAN_VIEWPORT_QUAD: 1, //!< Draw one bigger-than-viewport quad per iteration. + FIT_VIEWPORT_QUAD: 2 //!< Draw one exactly viewport-sized, axis aligned quad per iteration. + }; + + es3fMultisampleTests.CommonEdgeCase.prototype.init = function() { + var inited = es3fMultisampleTests.MultisampleCase.prototype.init.call(this); + if (!inited) { + return false; + } + + if (this.m_caseType === es3fMultisampleTests.CommonEdgeCase.CaseType.SMALL_QUADS) { + // Check for a big enough viewport. With too small viewports the test case can't analyze the resulting image well enough. + + /** @type {number} */ var minViewportSize = 32; + + if (this.m_viewportSize < minViewportSize) + throw new Error('Render target width or height too low (is ' + this.m_viewportSize + ', should be at least ' + minViewportSize + ')'); + } + + gl.enable(gl.BLEND); + gl.blendEquation(gl.FUNC_ADD); + gl.blendFunc(gl.ONE, gl.ONE); + bufferedLogToConsole('Additive blending enabled in order to detect (erroneously) overlapping samples'); + }; + + /** + * @return {tcuTestCase.IterateResult} + */ + es3fMultisampleTests.CommonEdgeCase.prototype.iterate = function() { + /** @type {tcuSurface.Surface} */ var errorImg = new tcuSurface.Surface(this.m_viewportSize, this.m_viewportSize); + + this.randomizeViewport(); + + gl.clearColor(0.0, 0.0, 0.0, 1.0); + gl.clear(gl.COLOR_BUFFER_BIT); + + // Draw test pattern. Test patterns consist of quads formed with two triangles. + // After drawing the pattern, we check that the interior pixels of each quad are + // all the same color - this is meant to verify that there are no artifacts on the inner edge. + + /** @type {Array<es3fMultisampleTests.QuadCorners>} */ var unicoloredRegions = []; + + /** @type {Array<Array<number>>} */ var corners; + /** @type {number} */ var angleCos; + /** @type {number} */ var angleSin; + /** @type {number} */ var angle; + /** @type {number} */ var quadDiagLen; + /** @type {number} */ var unicolorRegionScale; + /** @type {number} */ var quadBaseAngleNdx + /** @type {number} */ var quadSubAngleNdx; + + if (this.m_caseType == es3fMultisampleTests.CommonEdgeCase.CaseType.SMALL_QUADS) { + // Draw several quads, rotated at different angles. + + quadDiagLen = 2.0 / 3.0 * 0.9; // \note Fit 3 quads in both x and y directions. + + + // \note First and second iteration get exact 0 (and 90, 180, 270) and 45 (and 135, 225, 315) angle quads, as they are kind of a special case. + + if (this.m_currentIteration === 0) { + angleCos = 1.0; + angleSin = 0.0; + } + else if (this.m_currentIteration === 1) { + angleCos = Math.SQRT1_2; + angleSin = Math.SQRT1_2; + } + else { + angle = 0.5 * Math.PI * (this.m_currentIteration - 1) / (this.m_numIterations - 1); + angleCos = Math.cos(angle); + angleSin = Math.sin(angle); + } + + corners = [ + deMath.scale([angleCos, angleSin], 0.5 * quadDiagLen), + deMath.scale([-angleSin, angleCos], 0.5 * quadDiagLen), + deMath.scale([-angleCos, -angleSin], 0.5 * quadDiagLen), + deMath.scale([angleSin, -angleCos], 0.5 * quadDiagLen) + ]; + + // Draw 8 quads. + // First four are rotated at angles angle+0, angle+90, angle+180 and angle+270. + // Last four are rotated the same angles as the first four, but the ordering of the last triangle's vertices is reversed. + + for (var quadNdx = 0; quadNdx < 8; quadNdx++) { + /** @type {Array<number>} */ + var center = deMath.addScalar( + deMath.scale([quadNdx % 3, quadNdx / 3], (2.0 - quadDiagLen)/ 2.0), + (-0.5 * (2.0 - quadDiagLen))); + + this.renderTriangle_pAsVec2WithColor( + deMath.add(corners[(0 + quadNdx) % 4], center), + deMath.add(corners[(1 + quadNdx) % 4], center), + deMath.add(corners[(2 + quadNdx) % 4], center), + [0.5, 0.5, 0.5, 1.0]); + + if (quadNdx >= 4) { + this.renderTriangle_pAsVec2WithColor( + deMath.add(corners[(3 + quadNdx) % 4], center), + deMath.add(corners[(2 + quadNdx) % 4], center), + deMath.add(corners[(0 + quadNdx) % 4], center), + [0.5, 0.5, 0.5, 1.0]); + } + else { + this.renderTriangle_pAsVec2WithColor( + deMath.add(corners[(0 + quadNdx) % 4], center), + deMath.add(corners[(2 + quadNdx) % 4], center), + deMath.add(corners[(3 + quadNdx) % 4], center), + [0.5, 0.5, 0.5, 1.0]); + } + + // The size of the 'interior' of a quad is assumed to be approximately unicolorRegionScale*<actual size of quad>. + // By 'interior' we here mean the region of non-boundary pixels of the rendered quad for which we can safely assume + // that it has all coverage bits set to 1, for every pixel. + unicolorRegionScale = 1.0 - 6.0 * 2.0 / this.m_viewportSize / quadDiagLen; + unicoloredRegions.push( + new es3fMultisampleTests.QuadCorners( + deMath.add(center, deMath.scale(corners[0], unicolorRegionScale)), + deMath.add(center, deMath.scale(corners[1], unicolorRegionScale)), + deMath.add(center, deMath.scale(corners[2], unicolorRegionScale)), + deMath.add(center, deMath.scale(corners[3], unicolorRegionScale)))); + } + } + else if (this.m_caseType === es3fMultisampleTests.CommonEdgeCase.CaseType.BIGGER_THAN_VIEWPORT_QUAD) { + // Draw a bigger-than-viewport quad, rotated at an angle depending on m_currentIteration. + + quadBaseAngleNdx = Math.floor(this.m_currentIteration / 8); + quadSubAngleNdx = this.m_currentIteration % 8; + + if (quadBaseAngleNdx === 0) { + angleCos = 1.0; + angleSin = 0.0; + } + else if (quadBaseAngleNdx === 1) { + angleCos = Math.SQRT1_2; + angleSin = Math.SQRT1_2; + } + else { + angle = 0.5 * Math.PI * (this.m_currentIteration - 1) / (this.m_numIterations - 1); + angleCos = Math.cos(angle); + angleSin = Math.sin(angle); + } + + quadDiagLen = 2.5 / Math.max(angleCos, angleSin); + + corners = [ + deMath.scale([angleCos, angleSin], 0.5 * quadDiagLen), + deMath.scale([-angleSin, angleCos], 0.5 * quadDiagLen), + deMath.scale([-angleCos, -angleSin], 0.5 * quadDiagLen), + deMath.scale([angleSin, -angleCos], 0.5 * quadDiagLen) + ]; + + this.renderTriangle_pAsVec2WithColor( + corners[(0 + quadSubAngleNdx) % 4], + corners[(1 + quadSubAngleNdx) % 4], + corners[(2 + quadSubAngleNdx) % 4], + [0.5, 0.5, 0.5, 1.0]); + + if (quadSubAngleNdx >= 4) { + this.renderTriangle_pAsVec2WithColor( + corners[(3 + quadSubAngleNdx) % 4], + corners[(2 + quadSubAngleNdx) % 4], + corners[(0 + quadSubAngleNdx) % 4], + [0.5, 0.5, 0.5, 1.0]); + } + else { + this.renderTriangle_pAsVec2WithColor( + corners[(0 + quadSubAngleNdx) % 4], + corners[(2 + quadSubAngleNdx) % 4], + corners[(3 + quadSubAngleNdx) % 4], + [0.5, 0.5, 0.5, 1.0]); + } + + unicolorRegionScale = 1.0 - 6.0 * 2.0 / this.m_viewportSize / quadDiagLen; + unicoloredRegions.push( + new es3fMultisampleTests.QuadCorners( + deMath.scale(corners[0], unicolorRegionScale), + deMath.scale(corners[1], unicolorRegionScale), + deMath.scale(corners[2], unicolorRegionScale), + deMath.scale(corners[3], unicolorRegionScale))); + } + else if (this.m_caseType === es3fMultisampleTests.CommonEdgeCase.CaseType.FIT_VIEWPORT_QUAD) { + // Draw an exactly viewport-sized quad, rotated by multiples of 90 degrees angle depending on m_currentIteration. + + quadSubAngleNdx = this.m_currentIteration % 8; + + corners = [ + [1.0, 1.0], + [-1.0, 1.0], + [-1.0, -1.0], + [1.0, -1.0] + ]; + + this.renderTriangle_pAsVec2WithColor( + corners[(0 + quadSubAngleNdx) % 4], + corners[(1 + quadSubAngleNdx) % 4], + corners[(2 + quadSubAngleNdx) % 4], + [0.5, 0.5, 0.5, 1.0]); + + if (quadSubAngleNdx >= 4) { + this.renderTriangle_pAsVec2WithColor( + corners[(3 + quadSubAngleNdx) % 4], + corners[(2 + quadSubAngleNdx) % 4], + corners[(0 + quadSubAngleNdx) % 4], + [0.5, 0.5, 0.5, 1.0]); + } + else { + this.renderTriangle_pAsVec2WithColor( + corners[(0 + quadSubAngleNdx) % 4], + corners[(2 + quadSubAngleNdx) % 4], + corners[(3 + quadSubAngleNdx) % 4], + [0.5, 0.5, 0.5, 1.0]); + } + + unicoloredRegions.push(new es3fMultisampleTests.QuadCorners(corners[0], corners[1], corners[2], corners[3])); + } + else + throw new Error('CaseType not supported.'); + + // Read pixels and check unicolored regions. + + /** @type {tcuSurface.Surface} */ var renderedImg = this.readImage(); + + errorImg.getAccess().clear([0.0, 1.0, 0.0, 1.0]); + tcuLogImage.logImage('RenderedImage', 'Rendered image', renderedImg.getAccess()); + + /** @type {boolean} */ var errorsDetected = false; + for (var i = 0; i < unicoloredRegions.length; i++) { + /** @type {es3fMultisampleTests.QuadCorners} */ var region = unicoloredRegions[i]; + /** @type {Array<number>} */ var p0Win = deMath.scale(deMath.addScalar(region.p0, 1.0), 0.5 * (this.m_viewportSize - 1) + 0.5); + /** @type {Array<number>} */ var p1Win = deMath.scale(deMath.addScalar(region.p1, 1.0), 0.5 * (this.m_viewportSize - 1) + 0.5); + /** @type {Array<number>} */ var p2Win = deMath.scale(deMath.addScalar(region.p2, 1.0), 0.5 * (this.m_viewportSize - 1) + 0.5); + /** @type {Array<number>} */ var p3Win = deMath.scale(deMath.addScalar(region.p3, 1.0), 0.5 * (this.m_viewportSize - 1) + 0.5); + /** @type {boolean} */ var errorsInCurrentRegion = !es3fMultisampleTests.isPixelRegionUnicolored(renderedImg, p0Win, p1Win, p2Win, p3Win); + + if (errorsInCurrentRegion) + es3fMultisampleTests.drawUnicolorTestErrors(renderedImg, errorImg.getAccess(), p0Win, p1Win, p2Win, p3Win); + + errorsDetected = errorsDetected || errorsInCurrentRegion; + } + + this.m_currentIteration++; + + if (errorsDetected) { + bufferedLogToConsole('Failure: Not all quad interiors seem unicolored - common-edge artifacts?'); + bufferedLogToConsole('Erroneous pixels are drawn red in the following image'); + tcuLogImage.logImage('RenderedImageWithErrors', 'Rendered image with errors marked', renderedImg.getAccess()); + tcuLogImage.logImage('ErrorsOnly', 'Image with error pixels only', errorImg.getAccess()); + testFailedOptions('Failed: iteration ' + (this.m_currentIteration - 1), false); + return tcuTestCase.IterateResult.STOP; + } + else if (this.m_currentIteration < this.m_numIterations) { + bufferedLogToConsole('Quads seem OK - moving on to next pattern'); + return tcuTestCase.IterateResult.CONTINUE; + } + else { + bufferedLogToConsole('Success: All quad interiors seem unicolored (no common-edge artifacts)'); + testPassedOptions('Passed: iteration ' + (this.m_currentIteration - 1), true); + return tcuTestCase.IterateResult.STOP; + } + }; + + /** + * Test that depth values are per-sample. + * + * Draws intersecting, differently-colored polygons and checks that there + * are at least sample count+1 distinct colors present, due to some of the + * samples at the intersection line belonging to one and some to another + * polygon. + * + * @extends {es3fMultisampleTests.NumSamplesCase} + * @constructor + * @param {string} name + * @param {string} desc + * @param {number=} numFboSamples + */ + es3fMultisampleTests.SampleDepthCase = function(name, desc, numFboSamples) { + numFboSamples = numFboSamples === undefined ? 0 : numFboSamples; + /** @type {es3fMultisampleTests.FboParams} */ + var params = numFboSamples >= 0 ? new es3fMultisampleTests.FboParams(numFboSamples, true, false) : new es3fMultisampleTests.FboParams(); + es3fMultisampleTests.NumSamplesCase.call(this, name, desc, params); + }; + + es3fMultisampleTests.SampleDepthCase.prototype = Object.create(es3fMultisampleTests.NumSamplesCase.prototype); + + /** Copy the constructor */ + es3fMultisampleTests.SampleDepthCase.prototype.constructor = es3fMultisampleTests.SampleDepthCase; + + es3fMultisampleTests.SampleDepthCase.prototype.init = function() { + var inited = es3fMultisampleTests.MultisampleCase.prototype.init.call(this); + if (!inited) { + return false; + } + + gl.enable(gl.DEPTH_TEST); + gl.depthFunc(gl.LESS); + + bufferedLogToConsole('Depth test enabled, depth func is gl.LESS'); + bufferedLogToConsole('Drawing several bigger-than-viewport black or white polygons intersecting each other'); + }; + + es3fMultisampleTests.SampleDepthCase.prototype.renderPattern = function() { + gl.clearColor(0.0, 0.0, 0.0, 0.0); + gl.clearDepth(1.0); + gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); + + /** @type {number} */ var numPolygons = 50; + + for (var i = 0; i < numPolygons; i++) { + /** @type {Array<number>} */ var color = i % 2 == 0 ? [1.0, 1.0, 1.0, 1.0] : [0.0, 0.0, 0.0, 1.0]; + /** @type {number} */ var angle = 2.0 * Math.PI * i / numPolygons + 0.001 * this.m_currentIteration; + /** @type {Array<number>} */ var pt0 = [3.0 * Math.cos(angle + 2.0 * Math.PI * 0.0 / 3.0), 3.0 * Math.sin(angle + 2.0 * Math.PI * 0.0 / 3.0), 1.0]; + /** @type {Array<number>} */ var pt1 = [3.0 * Math.cos(angle + 2.0 * Math.PI * 1.0 / 3.0), 3.0 * Math.sin(angle + 2.0 * Math.PI * 1.0 / 3.0), 0.0]; + /** @type {Array<number>} */ var pt2 = [3.0 * Math.cos(angle + 2.0 * Math.PI * 2.0 / 3.0), 3.0 * Math.sin(angle + 2.0 * Math.PI * 2.0 / 3.0), 0.0]; + + this.renderTriangle_pAsVec3WithColor(pt0, pt1, pt2, color); + } + }; + + /** + * Test that stencil buffer values are per-sample. + * + * Draws a unicolored pattern and marks drawn samples in stencil buffer; + * then clears and draws a viewport-size quad with that color and with + * proper stencil test such that the resulting image should be exactly the + * same as after the pattern was first drawn. + * + * @extends {es3fMultisampleTests.MultisampleCase} + * @constructor + * @param {string} name + * @param {string} desc + * @param {number=} numFboSamples + */ + es3fMultisampleTests.SampleStencilCase = function(name, desc, numFboSamples) { + numFboSamples = numFboSamples === undefined ? 0 : numFboSamples; + /** @type {es3fMultisampleTests.FboParams} */ + var params = numFboSamples >= 0 ? new es3fMultisampleTests.FboParams(numFboSamples, false, true) : new es3fMultisampleTests.FboParams(); + es3fMultisampleTests.MultisampleCase.call(this, name, desc, 256, params); + }; + + es3fMultisampleTests.SampleStencilCase.prototype = Object.create(es3fMultisampleTests.MultisampleCase.prototype); + + /** Copy the constructor */ + es3fMultisampleTests.SampleStencilCase.prototype.constructor = es3fMultisampleTests.SampleStencilCase; + + /** + * @return {tcuTestCase.IterateResult} + */ + es3fMultisampleTests.SampleStencilCase.prototype.iterate = function() { + this.randomizeViewport(); + + gl.clearColor(0.0, 0.0, 0.0, 1.0); + gl.clearStencil(0); + gl.clear(gl.COLOR_BUFFER_BIT | gl.STENCIL_BUFFER_BIT); + gl.enable(gl.STENCIL_TEST); + gl.stencilFunc(gl.ALWAYS, 1, 1); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE); + + bufferedLogToConsole('Drawing a pattern with gl.stencilFunc(gl.ALWAYS, 1, 1) and gl.stencilOp(gl.KEEP, gl.KEEP, gl.REPLACE)'); + + /** @type {number} */ var numTriangles = 25; + for (var i = 0; i < numTriangles; i++) { + /** @type {number} */ var angle0 = 2.0 * Math.PI * i / numTriangles; + /** @type {number} */ var angle1 = 2.0 * Math.PI * (i + 0.5) / numTriangles; + + this.renderTriangle_pAsVec2WithColor( + [0.0, 0.0], + [Math.cos(angle0) * 0.95, Math.sin(angle0) * 0.95], + [Math.cos(angle1) * 0.95, Math.sin(angle1) * 0.95], + [1.0, 1.0, 1.0, 1.0]); + } + + /** @type {tcuSurface.Surface} */ var renderedImgFirst = this.readImage(); + tcuLogImage.logImage('RenderedImgFirst', 'First image rendered', renderedImgFirst.getAccess()); + bufferedLogToConsole('Clearing color buffer to black'); + + gl.clear(gl.COLOR_BUFFER_BIT); + gl.stencilFunc(gl.EQUAL, 1, 1); + gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP); + + bufferedLogToConsole('Checking that color buffer was actually cleared to black'); + + /** @type {tcuSurface.Surface} */ var clearedImg = this.readImage(); + + for (var y = 0; y < clearedImg.getHeight(); y++) + for (var x = 0; x < clearedImg.getWidth(); x++) { + /** @type {tcuRGBA.RGBA} */ var clr = new tcuRGBA.RGBA(clearedImg.getPixel(x, y)); + if (!clr.equals(tcuRGBA.RGBA.black)) { + bufferedLogToConsole('Failure: first non-black pixel, color ' + clr.toString() + ', detected at coordinates (' + x + ', ' + y + ')'); + tcuLogImage.logImage('ClearedImg', 'Image after clearing, erroneously non-black', clearedImg.getAccess()); + testFailedOptions('Failed', false); + return tcuTestCase.IterateResult.STOP; + } + } + + bufferedLogToConsole('Drawing a viewport-sized quad with gl.stencilFunc(gl.EQUAL, 1, 1) and gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP) - should result in same image as the first'); + + this.renderQuad_WithColor( + [-1.0, -1.0], + [1.0, -1.0], + [-1.0, 1.0], + [1.0, 1.0], + [1.0, 1.0, 1.0, 1.0]); + + /** @type {tcuSurface.Surface} */ var renderedImgSecond = this.readImage(); + tcuLogImage.logImage('RenderedImgSecond', 'Second image rendered', renderedImgSecond.getAccess()); + /** @type {boolean} */ + var passed = tcuImageCompare.pixelThresholdCompare( + 'ImageCompare', + 'Image comparison', + renderedImgFirst, + renderedImgSecond, + [0,0,0,0]); + + if (passed) { + bufferedLogToConsole('Success: The two images rendered are identical'); + testPassedOptions('Passed', true); + } + else + testFailedOptions('Failed', false); + + return tcuTestCase.IterateResult.STOP; + }; + + /** + * Tests coverage mask generation proportionality property. + * + * Tests that the number of coverage bits in a coverage mask created by + * gl.SAMPLE_ALPHA_TO_COVERAGE or gl.SAMPLE_COVERAGE is, on average, + * proportional to the alpha or coverage value, respectively. Draws + * multiple frames, each time increasing the alpha or coverage value used, + * and checks that the average color is changing appropriately. + * + * @extends {es3fMultisampleTests.MultisampleCase} + * @constructor + * @param {string} name + * @param {string} desc + * @param {es3fMultisampleTests.MaskProportionalityCase.CaseType} type + * @param {number=} numFboSamples + */ + es3fMultisampleTests.MaskProportionalityCase = function(name, desc, type, numFboSamples) { + numFboSamples = numFboSamples === undefined ? 0 : numFboSamples; + /** @type {es3fMultisampleTests.FboParams} */ + var params = numFboSamples >= 0 ? new es3fMultisampleTests.FboParams(numFboSamples, false, false) : new es3fMultisampleTests.FboParams(); + es3fMultisampleTests.MultisampleCase.call(this, name, desc, 32, params); + /** @type {es3fMultisampleTests.MaskProportionalityCase.CaseType} */ this.m_type = type; + /** @type {number} */ this.m_numIterations; + /** @type {number} */ this.m_currentIteration = 0; + /** @type {number} */ this.m_previousIterationColorSum = -1; + }; + + es3fMultisampleTests.MaskProportionalityCase.prototype = Object.create(es3fMultisampleTests.MultisampleCase.prototype); + + /** Copy the constructor */ + es3fMultisampleTests.MaskProportionalityCase.prototype.constructor = es3fMultisampleTests.MaskProportionalityCase; + + /** + * @enum {number} + */ + es3fMultisampleTests.MaskProportionalityCase.CaseType = { + ALPHA_TO_COVERAGE: 0, + SAMPLE_COVERAGE: 1, + SAMPLE_COVERAGE_INVERTED: 2 + }; + + es3fMultisampleTests.MaskProportionalityCase.prototype.init = function() { + var inited = es3fMultisampleTests.MultisampleCase.prototype.init.call(this); + if (!inited) { + return false; + } + + if (this.m_type == es3fMultisampleTests.MaskProportionalityCase.CaseType.ALPHA_TO_COVERAGE) { + gl.enable(gl.SAMPLE_ALPHA_TO_COVERAGE); + bufferedLogToConsole('gl.SAMPLE_ALPHA_TO_COVERAGE is enabled'); + } + else { + assertMsgOptions( + this.m_type == es3fMultisampleTests.MaskProportionalityCase.CaseType.SAMPLE_COVERAGE || + this.m_type == es3fMultisampleTests.MaskProportionalityCase.CaseType.SAMPLE_COVERAGE_INVERTED, + 'CaseType should be SAMPLE_COVERAGE or SAMPLE_COVERAGE_INVERTED', false, true); + + gl.enable(gl.SAMPLE_COVERAGE); + bufferedLogToConsole('gl.SAMPLE_COVERAGE is enabled'); + } + + this.m_numIterations = Math.max(2, es3fMultisampleTests.getIterationCount(this.m_numSamples * 5)); + + this.randomizeViewport(); // \note Using the same viewport for every iteration since coverage mask may depend on window-relative pixel coordinate. + }; + + /** + * @return {tcuTestCase.IterateResult} + */ + es3fMultisampleTests.MaskProportionalityCase.prototype.iterate = function() { + bufferedLogToConsole('Clearing color to black'); + gl.colorMask(true, true, true, true); + gl.clearColor(0.0, 0.0, 0.0, 1.0); + gl.clear(gl.COLOR_BUFFER_BIT); + + if (this.m_type === es3fMultisampleTests.MaskProportionalityCase.CaseType.ALPHA_TO_COVERAGE) { + gl.colorMask(true, true, true, false); + bufferedLogToConsole('Using color mask TRUE, TRUE, TRUE, FALSE'); + } + + // Draw quad. + + /** @type {Array<number>} */ var pt0 = [-1.0, -1.0]; + /** @type {Array<number>} */ var pt1 = [1.0, -1.0]; + /** @type {Array<number>} */ var pt2 = [-1.0, 1.0]; + /** @type {Array<number>} */ var pt3 = [1.0, 1.0]; + /** @type {Array<number>} */ var quadColor = [1.0, 0.0, 0.0, 1.0]; + /** @type {number} */ var alphaOrCoverageValue = this.m_currentIteration / (this.m_numIterations-1); + + if (this.m_type === es3fMultisampleTests.MaskProportionalityCase.CaseType.ALPHA_TO_COVERAGE) { + bufferedLogToConsole('Drawing a red quad using alpha value ' + alphaOrCoverageValue); + quadColor[3] = alphaOrCoverageValue; + } + else { + assertMsgOptions( + this.m_type === es3fMultisampleTests.MaskProportionalityCase.CaseType.SAMPLE_COVERAGE || + this.m_type === es3fMultisampleTests.MaskProportionalityCase.CaseType.SAMPLE_COVERAGE_INVERTED, + 'CaseType should be SAMPLE_COVERAGE or SAMPLE_COVERAGE_INVERTED', false, true); + + /** @type {boolean} */ var isInverted = (this.m_type === es3fMultisampleTests.MaskProportionalityCase.CaseType.SAMPLE_COVERAGE_INVERTED); + /** @type {number} */ var coverageValue = isInverted ? 1.0 - alphaOrCoverageValue : alphaOrCoverageValue; + bufferedLogToConsole('Drawing a red quad using sample coverage value ' + coverageValue + (isInverted ? ' (inverted)' : '')); + gl.sampleCoverage(coverageValue, isInverted ? true : false); + } + + this.renderQuad_WithColor(pt0, pt1, pt2, pt3, quadColor); + + // Read and log image. + /** @type {tcuSurface.Surface} */ var renderedImg = this.readImage(); + /** @type {number} */ var numPixels = renderedImg.getWidth() * renderedImg.getHeight(); + + tcuLogImage.logImage('RenderedImage', 'Rendered image', renderedImg.getAccess()); + // Compute average red component in rendered image. + + /** @type {number} */ var sumRed = 0; + + for (var y = 0; y < renderedImg.getHeight(); y++) + for (var x = 0; x < renderedImg.getWidth(); x++) + sumRed += new tcuRGBA.RGBA(renderedImg.getPixel(x, y)).getRed(); + + bufferedLogToConsole('Average red color component: ' + (sumRed / 255.0 / numPixels)); + + // Check if average color has decreased from previous frame's color. + + if (sumRed < this.m_previousIterationColorSum) { + bufferedLogToConsole('Failure: Current average red color component is lower than previous'); + testFailedOptions('Failed', false); + return tcuTestCase.IterateResult.STOP; + } + + // Check if coverage mask is not all-zeros if alpha or coverage value is 0 (or 1, if inverted). + + if (this.m_currentIteration == 0 && sumRed != 0) + { + bufferedLogToConsole('Failure: Image should be completely black'); + testFailedOptions('Failed', false); + return tcuTestCase.IterateResult.STOP; + } + + if (this.m_currentIteration == this.m_numIterations-1 && sumRed != 0xff*numPixels) + { + bufferedLogToConsole('Failure: Image should be completely red'); + + testFailedOptions('Failed', false); + return tcuTestCase.IterateResult.STOP; + } + + this.m_previousIterationColorSum = sumRed; + + this.m_currentIteration++; + + if (this.m_currentIteration >= this.m_numIterations) + { + bufferedLogToConsole('Success: Number of coverage mask bits set appears to be, on average, proportional to ' + + (this.m_type == es3fMultisampleTests.MaskProportionalityCase.CaseType.ALPHA_TO_COVERAGE ? 'alpha' : + this.m_type == es3fMultisampleTests.MaskProportionalityCase.CaseType.SAMPLE_COVERAGE ? 'sample coverage value' : + 'inverted sample coverage value')); + + testPassedOptions('Passed', true); + return tcuTestCase.IterateResult.STOP; + } + else + return tcuTestCase.IterateResult.CONTINUE; + }; + + /** + * Tests coverage mask generation constancy property. + * + * Tests that the coverage mask created by gl.SAMPLE_ALPHA_TO_COVERAGE or + * gl.SAMPLE_COVERAGE is constant at given pixel coordinates, with a given + * alpha component or coverage value, respectively. Draws two quads, with + * the second one fully overlapping the first one such that at any given + * pixel, both quads have the same alpha or coverage value. This way, if + * the constancy property is fulfilled, only the second quad should be + * visible. + * @extends {es3fMultisampleTests.MultisampleCase} + * @constructor + * @param {string} name + * @param {string} desc + * @param {es3fMultisampleTests.MaskConstancyCase.CaseType} type + * @param {number=} numFboSamples + */ + es3fMultisampleTests.MaskConstancyCase = function(name, desc, type, numFboSamples) { + numFboSamples = numFboSamples === undefined ? 0 : numFboSamples; + /** @type {es3fMultisampleTests.FboParams} */ + var params = numFboSamples >= 0 ? new es3fMultisampleTests.FboParams(numFboSamples, false, false) : new es3fMultisampleTests.FboParams(); + es3fMultisampleTests.MultisampleCase.call(this, name, desc, 256, params); + var CaseType = es3fMultisampleTests.MaskConstancyCase.CaseType; + /** @type {boolean} */ this.m_isAlphaToCoverageCase = (type === CaseType.ALPHA_TO_COVERAGE || type === CaseType.BOTH || type === CaseType.BOTH_INVERTED); + /** @type {boolean} */ this.m_isSampleCoverageCase = (type === CaseType.SAMPLE_COVERAGE || type === CaseType.SAMPLE_COVERAGE_INVERTED || type === CaseType.BOTH || type === CaseType.BOTH_INVERTED); + /** @type {boolean} */ this.m_isInvertedSampleCoverageCase = (type === CaseType.SAMPLE_COVERAGE_INVERTED || type === CaseType.BOTH_INVERTED); + }; + + es3fMultisampleTests.MaskConstancyCase.prototype = Object.create(es3fMultisampleTests.MultisampleCase.prototype); + + /** Copy the constructor */ + es3fMultisampleTests.MaskConstancyCase.prototype.constructor = es3fMultisampleTests.MaskConstancyCase; + + /** + * @enum {number} + */ + es3fMultisampleTests.MaskConstancyCase.CaseType = { + ALPHA_TO_COVERAGE: 0, //!< Use only alpha-to-coverage. + SAMPLE_COVERAGE: 1, //!< Use only sample coverage. + SAMPLE_COVERAGE_INVERTED: 2, //!< Use only inverted sample coverage. + BOTH: 3, //!< Use both alpha-to-coverage and sample coverage. + BOTH_INVERTED: 4 //!< Use both alpha-to-coverage and inverted sample coverage. + }; + + /** + * @return {tcuTestCase.IterateResult} + */ + es3fMultisampleTests.MaskConstancyCase.prototype.iterate = function() { + this.randomizeViewport(); + + bufferedLogToConsole('Clearing color to black'); + gl.clearColor(0.0, 0.0, 0.0, 1.0); + gl.clear(gl.COLOR_BUFFER_BIT); + + if (this.m_isAlphaToCoverageCase) { + gl.enable(gl.SAMPLE_ALPHA_TO_COVERAGE); + gl.colorMask(true, true, true, false); + bufferedLogToConsole('gl.SAMPLE_ALPHA_TO_COVERAGE is enabled'); + bufferedLogToConsole('Color mask is TRUE, TRUE, TRUE, FALSE'); + } + + if (this.m_isSampleCoverageCase) { + gl.enable(gl.SAMPLE_COVERAGE); + bufferedLogToConsole('gl.SAMPLE_COVERAGE is enabled'); + } + + bufferedLogToConsole('Drawing several green quads, each fully overlapped by a red quad with the same ' + + (this.m_isAlphaToCoverageCase ? 'alpha' : '') + + (this.m_isAlphaToCoverageCase && this.m_isSampleCoverageCase ? ' and ' : '') + + (this.m_isInvertedSampleCoverageCase ? 'inverted ' : '') + + (this.m_isSampleCoverageCase ? 'sample coverage' : '') + + ' values'); + + /** @type {number} */ var numQuadRowsCols = this.m_numSamples * 4; + + for (var row = 0; row < numQuadRowsCols; row++) { + for (var col = 0; col < numQuadRowsCols; col++) { + /** @type {number} */ var x0 = (col + 0) / numQuadRowsCols * 2.0 - 1.0; + /** @type {number} */ var x1 = (col + 1) / numQuadRowsCols * 2.0 - 1.0; + /** @type {number} */ var y0 = (row + 0) / numQuadRowsCols * 2.0 - 1.0; + /** @type {number} */ var y1 = (row + 1) / numQuadRowsCols * 2.0 - 1.0; + /** @type {Array<number>} */ var baseGreen = [0.0, 1.0, 0.0, 0.0]; + /** @type {Array<number>} */ var baseRed = [1.0, 0.0, 0.0, 0.0]; + /** @type {Array<number>} */ var alpha0 = [0.0, 0.0, 0.0, this.m_isAlphaToCoverageCase ? col / (numQuadRowsCols - 1) : 1.0]; + /** @type {Array<number>} */ var alpha1 = [0.0, 0.0, 0.0, this.m_isAlphaToCoverageCase ? row / (numQuadRowsCols - 1) : 1.0]; + + if (this.m_isSampleCoverageCase) { + /** @type {number} */ var value = (row*numQuadRowsCols + col) / (numQuadRowsCols*numQuadRowsCols - 1); + gl.sampleCoverage(this.m_isInvertedSampleCoverageCase ? 1.0 - value : value, this.m_isInvertedSampleCoverageCase ? true : false); + } + + this.renderQuad([x0, y0], [x1, y0], [x0, y1], [x1, y1], + deMath.add(baseGreen, alpha0), deMath.add(baseGreen, alpha1), + deMath.add(baseGreen, alpha0), deMath.add(baseGreen, alpha1)); + this.renderQuad([x0, y0], [x1, y0], [x0, y1], [x1, y1], + deMath.add(baseRed, alpha0), deMath.add(baseRed, alpha1), + deMath.add(baseRed, alpha0), deMath.add(baseRed, alpha1)); + } + } + + /** @type {tcuSurface.Surface} */ var renderedImg = this.readImage(); + + tcuLogImage.logImage('RenderedImage', 'Rendered image', renderedImg.getAccess()); + for (var y = 0; y < renderedImg.getHeight(); y++) + for (var x = 0; x < renderedImg.getWidth(); x++) { + if (new tcuRGBA.RGBA(renderedImg.getPixel(x, y)).getGreen() > 0) { + bufferedLogToConsole('Failure: Non-zero green color component detected - should have been completely overwritten by red quad'); + testFailedOptions('Failed', false); + return tcuTestCase.IterateResult.STOP; + } + } + + bufferedLogToConsole('Success: Coverage mask appears to be constant at a given pixel coordinate with a given ' + + (this.m_isAlphaToCoverageCase ? 'alpha' : '') + + (this.m_isAlphaToCoverageCase && this.m_isSampleCoverageCase ? ' and ' : '') + + (this.m_isSampleCoverageCase ? 'coverage value' : '')); + + testPassedOptions('Passed', true); + + return tcuTestCase.IterateResult.STOP; + } + + + /** + * Tests coverage mask inversion validity. + * + * Tests that the coverage masks obtained by glSampleCoverage(..., true) + * and glSampleCoverage(..., false) are indeed each others' inverses. + * This is done by drawing a pattern, with varying coverage values, + * overlapped by a pattern that has inverted masks and is otherwise + * identical. The resulting image is compared to one obtained by drawing + * the same pattern but with all-ones coverage masks. + * @extends {es3fMultisampleTests.MultisampleCase} + * @constructor + * @param {string} name + * @param {string} desc + * @param {number=} numFboSamples + */ + es3fMultisampleTests.CoverageMaskInvertCase = function(name, desc, numFboSamples) { + numFboSamples = numFboSamples === undefined ? 0 : numFboSamples; + /** @type {es3fMultisampleTests.FboParams} */ + var params = numFboSamples >= 0 ? new es3fMultisampleTests.FboParams(numFboSamples, false, false) : new es3fMultisampleTests.FboParams(); + es3fMultisampleTests.MultisampleCase.call(this, name, desc, 256, params); + }; + + es3fMultisampleTests.CoverageMaskInvertCase.prototype = Object.create(es3fMultisampleTests.MultisampleCase.prototype); + + /** Copy the constructor */ + es3fMultisampleTests.CoverageMaskInvertCase.prototype.constructor = es3fMultisampleTests.CoverageMaskInvertCase; + + /** + * @param {boolean} invertSampleCoverage + */ + es3fMultisampleTests.CoverageMaskInvertCase.prototype.drawPattern = function(invertSampleCoverage) { + /** @type {number} */ var numTriangles = 25; + for (var i = 0; i < numTriangles; i++) { + gl.sampleCoverage(i / (numTriangles - 1), invertSampleCoverage ? true : false); + + /** @type {number} */ var angle0 = 2.0 * Math.PI * i / numTriangles; + /** @type {number} */ var angle1 = 2.0 * Math.PI * (i + 0.5) / numTriangles; + + this.renderTriangle_pAsVec2WithColor( + [0.0, 0.0], + [Math.cos(angle0) * 0.95, Math.sin(angle0) * 0.95], + [Math.cos(angle1) * 0.95, Math.sin(angle1) * 0.95], + [0.4 + i / numTriangles * 0.6, + 0.5 + i / numTriangles * 0.3, + 0.6 - i / numTriangles * 0.5, + 0.7 - i / numTriangles * 0.7]); + } + }; + + /** + * @return {tcuTestCase.IterateResult} + */ + es3fMultisampleTests.CoverageMaskInvertCase.prototype.iterate = function() { + this.randomizeViewport(); + + gl.enable(gl.BLEND); + gl.blendEquation(gl.FUNC_ADD); + gl.blendFunc(gl.ONE, gl.ONE); + bufferedLogToConsole('Additive blending enabled in order to detect (erroneously) overlapping samples'); + + bufferedLogToConsole('Clearing color to all-zeros'); + gl.clearColor(0.0, 0.0, 0.0, 0.0); + gl.clear(gl.COLOR_BUFFER_BIT); + bufferedLogToConsole('Drawing the pattern with gl.SAMPLE_COVERAGE disabled'); + this.drawPattern(false); + /** @type {tcuSurface.Surface} */ var renderedImgNoSampleCoverage = this.readImage(); + + tcuLogImage.logImage('RenderedImageNoSampleCoverage', 'Rendered image with gl.SAMPLE_COVERAGE disabled', renderedImgNoSampleCoverage.getAccess()); + bufferedLogToConsole('Clearing color to all-zeros'); + gl.clear(gl.COLOR_BUFFER_BIT); + gl.enable(gl.SAMPLE_COVERAGE); + bufferedLogToConsole('Drawing the pattern with gl.SAMPLE_COVERAGE enabled, using non-inverted masks'); + this.drawPattern(false); + bufferedLogToConsole('Drawing the pattern with gl.SAMPLE_COVERAGE enabled, using same sample coverage values but inverted masks'); + this.drawPattern(true); + /** @type {tcuSurface.Surface} */ var renderedImgSampleCoverage = this.readImage(); + + tcuLogImage.logImage('RenderedImageSampleCoverage', 'Rendered image with gl.SAMPLE_COVERAGE enabled', renderedImgSampleCoverage.getAccess()); + /** @type {boolean} */ var passed = tcuImageCompare.pixelThresholdCompare( + 'CoverageVsNoCoverage', + 'Comparison of same pattern with gl.SAMPLE_COVERAGE disabled and enabled', + renderedImgNoSampleCoverage, + renderedImgSampleCoverage, + [0, 0, 0, 0]); + + if (passed) { + bufferedLogToConsole('Success: The two images rendered are identical'); + testPassedOptions('Passed', true); + } + else { + testFailedOptions('Failed', false); + } + + return tcuTestCase.IterateResult.STOP; + }; + + es3fMultisampleTests.init = function() { + var testGroup = tcuTestCase.runner.testCases; + /** + * @enum {number} + */ + var CaseType = { + DEFAULT_FRAMEBUFFER: 0, + FBO_4_SAMPLES: 1, + FBO_8_SAMPLES: 2, + FBO_MAX_SAMPLES: 3 + }; + + for (var caseTypeI in CaseType) { + /** @type {CaseType} */ var caseType = CaseType[caseTypeI]; + /** @type {number} */ + var numFboSamples = caseType === CaseType.DEFAULT_FRAMEBUFFER ? -1 : + caseType === CaseType.FBO_4_SAMPLES ? 4 : + caseType === CaseType.FBO_8_SAMPLES ? 8 : + caseType === CaseType.FBO_MAX_SAMPLES ? 0 : + -2; + + /** @type {?string} */ + var name = caseType === CaseType.DEFAULT_FRAMEBUFFER ? 'default_framebuffer' : + caseType === CaseType.FBO_4_SAMPLES ? 'fbo_4_samples' : + caseType === CaseType.FBO_8_SAMPLES ? 'fbo_8_samples' : + caseType === CaseType.FBO_MAX_SAMPLES ? 'fbo_max_samples' : + null; + /** @type {?string} */ + var desc = caseType === CaseType.DEFAULT_FRAMEBUFFER ? 'Render into default framebuffer' : + caseType === CaseType.FBO_4_SAMPLES ? 'Render into a framebuffer object with 4 samples' : + caseType === CaseType.FBO_8_SAMPLES ? 'Render into a framebuffer object with 8 samples' : + caseType === CaseType.FBO_MAX_SAMPLES ? 'Render into a framebuffer object with the maximum number of samples' : + null; + + /** @type {tcuTestCase.DeqpTest} */ var group = tcuTestCase.newTest(name, desc); + + assertMsgOptions(group.name != null, 'Error: No Test Name', false, true); + assertMsgOptions(group.description != null, 'Error: No Test Description', false, true); + assertMsgOptions(numFboSamples >= -1, 'Assert Failed: numFboSamples >= -1', false, true); + testGroup.addChild(group); + + group.addChild(new es3fMultisampleTests.PolygonNumSamplesCase( + 'num_samples_polygon', + 'Test sanity of the sample count, with polygons', + numFboSamples)); + + group.addChild(new es3fMultisampleTests.LineNumSamplesCase( + 'num_samples_line', + 'Test sanity of the sample count, with lines', + numFboSamples)); + + group.addChild(new es3fMultisampleTests.CommonEdgeCase( + 'common_edge_small_quads', + 'Test polygons\'s common edges with small quads', + es3fMultisampleTests.CommonEdgeCase.CaseType.SMALL_QUADS, + numFboSamples)); + + group.addChild(new es3fMultisampleTests.CommonEdgeCase( + 'common_edge_big_quad', + 'Test polygon\'s common edges with bigger-than-viewport quads', + es3fMultisampleTests.CommonEdgeCase.CaseType.BIGGER_THAN_VIEWPORT_QUAD, + numFboSamples)); + + group.addChild(new es3fMultisampleTests.CommonEdgeCase( + 'common_edge_viewport_quad', + 'Test polygons\' common edges with exactly viewport-sized quads', + es3fMultisampleTests.CommonEdgeCase.CaseType.FIT_VIEWPORT_QUAD, + numFboSamples)); + + group.addChild(new es3fMultisampleTests.SampleDepthCase( + 'depth', + 'Test that depth values are per-sample', + numFboSamples)); + + group.addChild(new es3fMultisampleTests.SampleStencilCase( + 'stencil', + 'Test that stencil values are per-sample', + numFboSamples)); + + group.addChild(new es3fMultisampleTests.CoverageMaskInvertCase( + 'sample_coverage_invert', + 'Test that non-inverted and inverted sample coverage masks are each other\'s negations', + numFboSamples)); + + + group.addChild(new es3fMultisampleTests.MaskProportionalityCase( + 'proportionality_alpha_to_coverage', + 'Test the proportionality property of GL_SAMPLE_ALPHA_TO_COVERAGE', + es3fMultisampleTests.MaskProportionalityCase.CaseType.ALPHA_TO_COVERAGE, + numFboSamples)); + group.addChild(new es3fMultisampleTests.MaskProportionalityCase( + 'proportionality_sample_coverage', + 'Test the proportionality property of GL_SAMPLE_COVERAGE', + es3fMultisampleTests.MaskProportionalityCase.CaseType.SAMPLE_COVERAGE, + numFboSamples)); + group.addChild(new es3fMultisampleTests.MaskProportionalityCase( + 'proportionality_sample_coverage_inverted', + 'Test the proportionality property of inverted-mask GL_SAMPLE_COVERAGE', + es3fMultisampleTests.MaskProportionalityCase.CaseType.SAMPLE_COVERAGE_INVERTED, + numFboSamples)); + + group.addChild(new es3fMultisampleTests.MaskConstancyCase( + 'constancy_alpha_to_coverage', + 'Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_ALPHA_TO_COVERAGE', + es3fMultisampleTests.MaskConstancyCase.CaseType.ALPHA_TO_COVERAGE, + numFboSamples)); + group.addChild(new es3fMultisampleTests.MaskConstancyCase( + 'constancy_sample_coverage', + 'Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_COVERAGE', + es3fMultisampleTests.MaskConstancyCase.CaseType.SAMPLE_COVERAGE, + numFboSamples)); + group.addChild(new es3fMultisampleTests.MaskConstancyCase( + 'constancy_sample_coverage_inverted', + 'Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using inverted-mask GL_SAMPLE_COVERAGE', + es3fMultisampleTests.MaskConstancyCase.CaseType.SAMPLE_COVERAGE_INVERTED, + numFboSamples)); + group.addChild(new es3fMultisampleTests.MaskConstancyCase( + 'constancy_both', + 'Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_ALPHA_TO_COVERAGE and GL_SAMPLE_COVERAGE', + es3fMultisampleTests.MaskConstancyCase.CaseType.BOTH, + numFboSamples)); + group.addChild(new es3fMultisampleTests.MaskConstancyCase( + 'constancy_both_inverted', + 'Test that coverage mask is constant at given coordinates with a given alpha or coverage value, using GL_SAMPLE_ALPHA_TO_COVERAGE and inverted-mask GL_SAMPLE_COVERAGE', + es3fMultisampleTests.MaskConstancyCase.CaseType.BOTH_INVERTED, + numFboSamples)); + } + }; + + /** + * Run test + * @param {WebGL2RenderingContext} context + */ + es3fMultisampleTests.run = function(context) { + gl = context; + //Set up Test Root parameters + var testName = 'multisample'; + var testDescription = 'Multisample Tests'; + var state = tcuTestCase.runner; + + state.testName = testName; + state.setRoot(tcuTestCase.newTest(testName, testDescription, null)); + + //Set up name and description of this test series. + setCurrentTestName(testName); + description(testDescription); + + try { + //Create test cases + es3fMultisampleTests.init(); + //Run test cases + tcuTestCase.runTestCases(); + } + catch (err) { + testFailedOptions('Failed to es3fMultisampleTests.run tests', false); + tcuTestCase.runner.terminate(); + } + }; + +}); |