/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "WebGL2Context.h" #include "GLContext.h" #include "WebGLQuery.h" namespace mozilla { /* * We fake ANY_SAMPLES_PASSED and ANY_SAMPLES_PASSED_CONSERVATIVE with * SAMPLES_PASSED on desktop. * * OpenGL ES 3.0 spec 4.1.6: * If the target of the query is ANY_SAMPLES_PASSED_CONSERVATIVE, an * implementation may choose to use a less precise version of the test which * can additionally set the samples-boolean state to TRUE in some other * implementation-dependent cases. */ static const char* GetQueryTargetEnumString(GLenum target) { switch (target) { case LOCAL_GL_ANY_SAMPLES_PASSED: return "ANY_SAMPLES_PASSED"; case LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE: return "ANY_SAMPLES_PASSED_CONSERVATIVE"; case LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: return "TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN"; default: break; } MOZ_ASSERT(false, "Unknown query `target`."); return "UNKNOWN_QUERY_TARGET"; } static inline GLenum SimulateOcclusionQueryTarget(const gl::GLContext* gl, GLenum target) { MOZ_ASSERT(target == LOCAL_GL_ANY_SAMPLES_PASSED || target == LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE, "unknown occlusion query target"); if (gl->IsSupported(gl::GLFeature::occlusion_query_boolean)) { return target; } else if (gl->IsSupported(gl::GLFeature::occlusion_query2)) { return LOCAL_GL_ANY_SAMPLES_PASSED; } return LOCAL_GL_SAMPLES_PASSED; } WebGLRefPtr& WebGLContext::GetQuerySlotByTarget(GLenum target) { /* This function assumes that target has been validated for either * WebGL1 or WebGL2. */ switch (target) { case LOCAL_GL_ANY_SAMPLES_PASSED: case LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE: return mActiveOcclusionQuery; case LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: return mActiveTransformFeedbackQuery; default: MOZ_CRASH("Should not get here."); } } // ------------------------------------------------------------------------- // Query Objects already_AddRefed WebGL2Context::CreateQuery() { if (IsContextLost()) return nullptr; if (mActiveOcclusionQuery && !gl->IsGLES()) { /* http://www.opengl.org/registry/specs/ARB/occlusion_query.txt * * Calling either GenQueriesARB or DeleteQueriesARB while any query of * any target is active causes an INVALID_OPERATION error to be * generated. */ GenerateWarning("createQuery: The WebGL 2 prototype might generate" " INVALID_OPERATION when creating a query object while" " one other is active."); /* * We *need* to lock webgl2 to GL>=3.0 on desktop, but we don't have a * good mechanism to do this yet. See bug 898404. */ } RefPtr globj = new WebGLQuery(this); return globj.forget(); } void WebGL2Context::DeleteQuery(WebGLQuery* query) { if (IsContextLost()) return; if (!query) return; if (query->IsDeleted()) return; if (query->IsActive()) EndQuery(query->mType); if (mActiveOcclusionQuery && !gl->IsGLES()) { /* http://www.opengl.org/registry/specs/ARB/occlusion_query.txt * * Calling either GenQueriesARB or DeleteQueriesARB while any query of * any target is active causes an INVALID_OPERATION error to be * generated. */ GenerateWarning("deleteQuery: The WebGL 2 prototype might generate" " INVALID_OPERATION when deleting a query object while" " one other is active."); } query->RequestDelete(); } bool WebGL2Context::IsQuery(WebGLQuery* query) { if (IsContextLost()) return false; if (!query) return false; return (ValidateObjectAllowDeleted("isQuery", query) && !query->IsDeleted() && query->HasEverBeenActive()); } void WebGL2Context::BeginQuery(GLenum target, WebGLQuery* query) { if (IsContextLost()) return; if (!ValidateQueryTarget(target, "beginQuery")) return; if (!query) { /* From GLES's EXT_occlusion_query_boolean: * BeginQueryEXT sets the active query object name for the query * type given by to . If BeginQueryEXT is called with * an of zero, if the active query object name for is * non-zero (for the targets ANY_SAMPLES_PASSED_EXT and * ANY_SAMPLES_PASSED_CONSERVATIVE_EXT, if the active query for * either target is non-zero), if is the name of an existing * query object whose type does not match , or if is * the active query object name for any query type, the error * INVALID_OPERATION is generated. */ ErrorInvalidOperation("beginQuery: Query should not be null."); return; } if (query->IsDeleted()) { /* From GLES's EXT_occlusion_query_boolean: * BeginQueryEXT fails and an INVALID_OPERATION error is generated * if is not a name returned from a previous call to * GenQueriesEXT, or if such a name has since been deleted with * DeleteQueriesEXT. */ ErrorInvalidOperation("beginQuery: Query has been deleted."); return; } if (query->HasEverBeenActive() && query->mType != target) { ErrorInvalidOperation("beginQuery: Target doesn't match with the query" " type."); return; } WebGLRefPtr& querySlot = GetQuerySlotByTarget(target); WebGLQuery* activeQuery = querySlot.get(); if (activeQuery) return ErrorInvalidOperation("beginQuery: An other query already active."); if (!query->HasEverBeenActive()) query->mType = target; MakeContextCurrent(); if (target == LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN) { gl->fBeginQuery(LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN, query->mGLName); } else { gl->fBeginQuery(SimulateOcclusionQueryTarget(gl, target), query->mGLName); } UpdateBoundQuery(target, query); } void WebGL2Context::EndQuery(GLenum target) { if (IsContextLost()) return; if (!ValidateQueryTarget(target, "endQuery")) return; WebGLRefPtr& querySlot = GetQuerySlotByTarget(target); WebGLQuery* activeQuery = querySlot.get(); if (!activeQuery || target != activeQuery->mType) { /* From GLES's EXT_occlusion_query_boolean: * marks the end of the sequence of commands to be tracked for the * query type given by . The active query object for * is updated to indicate that query results are not * available, and the active query object name for is reset * to zero. When the commands issued prior to EndQueryEXT have * completed and a final query result is available, the query object * active when EndQueryEXT is called is updated by the GL. The query * object is updated to indicate that the query results are * available and to contain the query result. If the active query * object name for is zero when EndQueryEXT is called, the * error INVALID_OPERATION is generated. */ ErrorInvalidOperation("endQuery: There is no active query of type %s.", GetQueryTargetEnumString(target)); return; } MakeContextCurrent(); if (target == LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN) { gl->fEndQuery(LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN); } else { gl->fEndQuery(SimulateOcclusionQueryTarget(gl, target)); } UpdateBoundQuery(target, nullptr); } already_AddRefed WebGL2Context::GetQuery(GLenum target, GLenum pname) { if (IsContextLost()) return nullptr; if (!ValidateQueryTarget(target, "getQuery")) return nullptr; if (pname != LOCAL_GL_CURRENT_QUERY) { /* OpenGL ES 3.0 spec 6.1.7: * pname must be CURRENT_QUERY. */ ErrorInvalidEnum("getQuery: `pname` must be CURRENT_QUERY."); return nullptr; } WebGLRefPtr& targetSlot = GetQuerySlotByTarget(target); RefPtr tmp = targetSlot.get(); if (tmp && tmp->mType != target) { // Query in slot doesn't match target return nullptr; } return tmp.forget(); } void WebGL2Context::GetQueryParameter(JSContext*, WebGLQuery* query, GLenum pname, JS::MutableHandleValue retval) { retval.set(JS::NullValue()); if (IsContextLost()) return; if (!query) { /* OpenGL ES 3.0 spec 6.1.7 (spec getQueryObject 1): * If id is not the name of a query object, or if the query object * named by id is currently active, then an INVALID_OPERATION error * is generated. pname must be QUERY_RESULT or * QUERY_RESULT_AVAILABLE. */ ErrorInvalidOperation("getQueryObject: `query` should not be null."); return; } if (query->IsDeleted()) { // See (spec getQueryObject 1) ErrorInvalidOperation("getQueryObject: `query` has been deleted."); return; } if (query->IsActive()) { // See (spec getQueryObject 1) ErrorInvalidOperation("getQueryObject: `query` is active."); return; } if (!query->HasEverBeenActive()) { /* See (spec getQueryObject 1) * If this instance of WebGLQuery has never been active before, that * mean that query->mGLName is not a query object yet. */ ErrorInvalidOperation("getQueryObject: `query` has never been active."); return; } MakeContextCurrent(); GLuint returned = 0; switch (pname) { case LOCAL_GL_QUERY_RESULT_AVAILABLE: gl->fGetQueryObjectuiv(query->mGLName, LOCAL_GL_QUERY_RESULT_AVAILABLE, &returned); retval.set(JS::BooleanValue(returned != 0)); return; case LOCAL_GL_QUERY_RESULT: gl->fGetQueryObjectuiv(query->mGLName, LOCAL_GL_QUERY_RESULT, &returned); if (query->mType == LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN) { retval.set(JS::NumberValue(returned)); return; } /* * test (returned != 0) is important because ARB_occlusion_query on desktop drivers * return the number of samples drawed when the OpenGL ES extension * ARB_occlusion_query_boolean return only a boolean if a sample has been drawed. */ retval.set(JS::BooleanValue(returned != 0)); return; default: break; } ErrorInvalidEnum("getQueryObject: `pname` must be QUERY_RESULT{_AVAILABLE}."); } void WebGL2Context::UpdateBoundQuery(GLenum target, WebGLQuery* query) { WebGLRefPtr& querySlot = GetQuerySlotByTarget(target); querySlot = query; } bool WebGL2Context::ValidateQueryTarget(GLenum target, const char* info) { switch (target) { case LOCAL_GL_ANY_SAMPLES_PASSED: case LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE: case LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN: return true; default: ErrorInvalidEnumInfo(info, target); return false; } } } // namespace mozilla