mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 14:18:31 +09:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7463236b34 | ||
|
|
658e08c918 | ||
|
|
e543d9f7da | ||
|
|
31d7d97e86 | ||
|
|
cf89f394c5 | ||
|
|
2412807afd | ||
|
|
9ed287dbc7 | ||
|
|
836306feee | ||
|
|
126f617428 |
@@ -877,6 +877,172 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
Memcpy(ids, names.data(), static_cast<SizeT>(n) * sizeof(GLuint));
|
Memcpy(ids, names.data(), static_cast<SizeT>(n) * sizeof(GLuint));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CreateTransformFeedbacks(GLsizei n, GLuint* ids) {
|
||||||
|
if (n < 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "n must be non-negative."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (n == 0 || ids == nullptr) return;
|
||||||
|
Vector<Uint> names;
|
||||||
|
MG_State::pGLContext->GenTransformFeedbackNames(static_cast<Uint>(n), names);
|
||||||
|
// Unlike glGenTransformFeedbacks, the names are objects immediately: there is no bind step
|
||||||
|
// to create them from (GL 4.6 core 13.2.1).
|
||||||
|
for (const Uint name : names) {
|
||||||
|
MG_State::pGLContext->CreateTransformFeedbackObject(name);
|
||||||
|
}
|
||||||
|
Memcpy(ids, names.data(), static_cast<SizeT>(n) * sizeof(GLuint));
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
// Shared front half of the by-name transform feedback entry points: the object has to exist
|
||||||
|
// (INVALID_OPERATION otherwise) before anything else about the call is looked at.
|
||||||
|
Bool ValidateNamedTransformFeedback(GLuint xfb, const char* functionName) {
|
||||||
|
if (!MG_State::pGLContext->IsTransformFeedbackObject(xfb)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
std::to_string(xfb) + " is not a transform feedback object."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ValidateTransformFeedbackBufferIndex(GLuint index, const char* functionName) {
|
||||||
|
if (index >= MG_State::GLState::GLContext::MAX_TRANSFORM_FEEDBACK_BUFFERS) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
"index exceeds GL_MAX_TRANSFORM_FEEDBACK_BUFFERS."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A capture binding may not be changed while the object is capturing (GL 4.6 core 13.2.2).
|
||||||
|
Bool ValidateNamedTransformFeedbackNotActive(GLuint xfb, const char* functionName) {
|
||||||
|
if (MG_State::pGLContext->IsNamedTransformFeedbackActive(xfb)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
"The transform feedback object is capturing."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedPtr<MG_State::GLState::BufferObject> ResolveTransformFeedbackBuffer(GLuint buffer,
|
||||||
|
const char* functionName) {
|
||||||
|
if (buffer == 0) return nullptr;
|
||||||
|
if (!MG_State::pGLContext->ValidateBufferName(buffer)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", functionName,
|
||||||
|
std::to_string(buffer) + " is not a buffer object."));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return MG_State::pGLContext->GetBufferObject(buffer);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void TransformFeedbackBufferBase(GLuint xfb, GLuint index, GLuint buffer) {
|
||||||
|
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||||
|
if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return;
|
||||||
|
if (!ValidateNamedTransformFeedbackNotActive(xfb, __func__)) return;
|
||||||
|
if (buffer != 0 && !MG_State::pGLContext->ValidateBufferName(buffer)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
std::to_string(buffer) + " is not a buffer object."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
MG_State::pGLContext->SetNamedTransformFeedbackBinding(xfb, index,
|
||||||
|
ResolveTransformFeedbackBuffer(buffer, __func__), {},
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TransformFeedbackBufferRange(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) {
|
||||||
|
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||||
|
if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return;
|
||||||
|
if (!ValidateNamedTransformFeedbackNotActive(xfb, __func__)) return;
|
||||||
|
if (offset < 0 || size <= 0 || (offset % 4) != 0 || (size % 4) != 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"offset and size must be non-negative multiples of 4."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (buffer != 0 && !MG_State::pGLContext->ValidateBufferName(buffer)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
std::to_string(buffer) + " is not a buffer object."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto bufferObject = ResolveTransformFeedbackBuffer(buffer, __func__);
|
||||||
|
const Range1D range{static_cast<SizeT>(offset), static_cast<SizeT>(offset) + static_cast<SizeT>(size)};
|
||||||
|
MG_State::pGLContext->SetNamedTransformFeedbackBinding(xfb, index, bufferObject, range,
|
||||||
|
bufferObject != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetTransformFeedbackiv(GLuint xfb, GLenum pname, GLint* param) {
|
||||||
|
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||||
|
if (!param) return;
|
||||||
|
switch (pname) {
|
||||||
|
case GL_TRANSFORM_FEEDBACK_ACTIVE:
|
||||||
|
*param = MG_State::pGLContext->IsNamedTransformFeedbackActive(xfb) ? GL_TRUE : GL_FALSE;
|
||||||
|
return;
|
||||||
|
case GL_TRANSFORM_FEEDBACK_PAUSED:
|
||||||
|
*param = MG_State::pGLContext->IsNamedTransformFeedbackPaused(xfb) ? GL_TRUE : GL_FALSE;
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"pname must be GL_TRANSFORM_FEEDBACK_ACTIVE or _PAUSED."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetTransformFeedbacki_v(GLuint xfb, GLenum pname, GLuint index, GLint* param) {
|
||||||
|
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||||
|
if (pname != GL_TRANSFORM_FEEDBACK_BUFFER_BINDING) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"pname must be GL_TRANSFORM_FEEDBACK_BUFFER_BINDING."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return;
|
||||||
|
if (!param) return;
|
||||||
|
const auto binding = MG_State::pGLContext->GetNamedTransformFeedbackBinding(xfb, index);
|
||||||
|
*param = binding.Buffer ? static_cast<GLint>(binding.Buffer->GetExternalIndex()) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetTransformFeedbacki64_v(GLuint xfb, GLenum pname, GLuint index, GLint64* param) {
|
||||||
|
if (!ValidateNamedTransformFeedback(xfb, __func__)) return;
|
||||||
|
if (pname != GL_TRANSFORM_FEEDBACK_BUFFER_START && pname != GL_TRANSFORM_FEEDBACK_BUFFER_SIZE) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"pname must be GL_TRANSFORM_FEEDBACK_BUFFER_START or _SIZE."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!ValidateTransformFeedbackBufferIndex(index, __func__)) return;
|
||||||
|
if (!param) return;
|
||||||
|
const auto binding = MG_State::pGLContext->GetNamedTransformFeedbackBinding(xfb, index);
|
||||||
|
// glTransformFeedbackBufferBase leaves both at zero; only the range form sets them
|
||||||
|
// (GL 4.6 core table 23.48).
|
||||||
|
if (!binding.Buffer || !binding.HasExplicitRange) {
|
||||||
|
*param = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
*param = (pname == GL_TRANSFORM_FEEDBACK_BUFFER_START)
|
||||||
|
? static_cast<GLint64>(binding.Range.start)
|
||||||
|
: static_cast<GLint64>(binding.Range.end - binding.Range.start);
|
||||||
|
}
|
||||||
|
|
||||||
void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids) {
|
void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids) {
|
||||||
if (n < 0) {
|
if (n < 0) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
|
|||||||
@@ -16,7 +16,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void PauseTransformFeedback(void);
|
void PauseTransformFeedback(void);
|
||||||
void ResumeTransformFeedback(void);
|
void ResumeTransformFeedback(void);
|
||||||
void GenTransformFeedbacks(GLsizei n, GLuint* ids);
|
void GenTransformFeedbacks(GLsizei n, GLuint* ids);
|
||||||
|
void CreateTransformFeedbacks(GLsizei n, GLuint* ids);
|
||||||
void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids);
|
void DeleteTransformFeedbacks(GLsizei n, const GLuint* ids);
|
||||||
|
void TransformFeedbackBufferBase(GLuint xfb, GLuint index, GLuint buffer);
|
||||||
|
void TransformFeedbackBufferRange(GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size);
|
||||||
|
void GetTransformFeedbackiv(GLuint xfb, GLenum pname, GLint* param);
|
||||||
|
void GetTransformFeedbacki_v(GLuint xfb, GLenum pname, GLuint index, GLint* param);
|
||||||
|
void GetTransformFeedbacki64_v(GLuint xfb, GLenum pname, GLuint index, GLint64* param);
|
||||||
void BindTransformFeedback(GLenum target, GLuint id);
|
void BindTransformFeedback(GLenum target, GLuint id);
|
||||||
GLboolean IsTransformFeedback(GLuint id);
|
GLboolean IsTransformFeedback(GLuint id);
|
||||||
void DrawTransformFeedback(GLenum mode, GLuint id);
|
void DrawTransformFeedback(GLenum mode, GLuint id);
|
||||||
|
|||||||
@@ -996,7 +996,7 @@ DECLARE_GL_FUNCTION_HEAD(void, MultiDrawElementsIndirect, GLenum mode, GLenum ty
|
|||||||
DECLARE_GL_FUNCTION_HEAD(GLint, GetProgramResourceLocationIndex, GLuint program, GLenum programInterface, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetProgramResourceLocationIndex, program, programInterface, name)
|
DECLARE_GL_FUNCTION_HEAD(GLint, GetProgramResourceLocationIndex, GLuint program, GLenum programInterface, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetProgramResourceLocationIndex, program, programInterface, name)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ShaderStorageBlockBinding, GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ShaderStorageBlockBinding, program, storageBlockIndex, storageBlockBinding)
|
DECLARE_GL_FUNCTION_HEAD(void, ShaderStorageBlockBinding, GLuint program, GLuint storageBlockIndex, GLuint storageBlockBinding) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ShaderStorageBlockBinding, program, storageBlockIndex, storageBlockBinding)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureView, GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureView, texture, target, origtexture, internalformat, minlevel, numlevels, minlayer, numlayers)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureView, GLuint texture, GLenum target, GLuint origtexture, GLenum internalformat, GLuint minlevel, GLuint numlevels, GLuint minlayer, GLuint numlayers) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureView, texture, target, origtexture, internalformat, minlevel, numlevels, minlayer, numlayers)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexAttribLFormat, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexAttribLFormat, attribindex, size, type, relativeoffset)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexAttribLFormat, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttribLFormat, attribindex, size, type, relativeoffset)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BufferStorage, GLenum target, GLsizeiptr size, const void* data, GLbitfield flags) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BufferStorage, target, size, data, flags)
|
DECLARE_GL_FUNCTION_HEAD(void, BufferStorage, GLenum target, GLsizeiptr size, const void* data, GLbitfield flags) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BufferStorage, target, size, data, flags)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ClearTexImage, GLuint texture, GLint level, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearTexImage, texture, level, format, type, data)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearTexImage, GLuint texture, GLint level, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearTexImage, texture, level, format, type, data)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, ClearTexSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearTexSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, data)
|
DECLARE_GL_FUNCTION_HEAD(void, ClearTexSubImage, GLuint texture, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClearTexSubImage, texture, level, xoffset, yoffset, zoffset, width, height, depth, format, type, data)
|
||||||
@@ -1007,12 +1007,12 @@ DECLARE_GL_FUNCTION_HEAD(void, BindSamplers, GLuint first, GLsizei count, const
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindImageTextures, GLuint first, GLsizei count, const GLuint* textures) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindImageTextures, first, count, textures)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindImageTextures, GLuint first, GLsizei count, const GLuint* textures) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindImageTextures, first, count, textures)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, BindVertexBuffers, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, const GLsizei* strides) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindVertexBuffers, first, count, buffers, offsets, strides)
|
DECLARE_GL_FUNCTION_HEAD(void, BindVertexBuffers, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, const GLsizei* strides) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindVertexBuffers, first, count, buffers, offsets, strides)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClipControl, GLenum origin, GLenum depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClipControl, origin, depth)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ClipControl, GLenum origin, GLenum depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClipControl, origin, depth)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, CreateTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CreateTransformFeedbacks, n, ids)
|
DECLARE_GL_FUNCTION_HEAD(void, CreateTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateTransformFeedbacks, n, ids)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackBufferBase, GLuint xfb, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TransformFeedbackBufferBase, xfb, index, buffer)
|
DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackBufferBase, GLuint xfb, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackBufferBase, xfb, index, buffer)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackBufferRange, GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TransformFeedbackBufferRange, xfb, index, buffer, offset, size)
|
DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackBufferRange, GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackBufferRange, xfb, index, buffer, offset, size)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbackiv, GLuint xfb, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbackiv, xfb, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbackiv, GLuint xfb, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbackiv, xfb, pname, param)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbacki_v, GLuint xfb, GLenum pname, GLuint index, GLint* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbacki_v, xfb, pname, index, param)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbacki_v, GLuint xfb, GLenum pname, GLuint index, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbacki_v, xfb, pname, index, param)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetTransformFeedbacki64_v, GLuint xfb, GLenum pname, GLuint index, GLint64* param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetTransformFeedbacki64_v, xfb, pname, index, param)
|
DECLARE_GL_FUNCTION_HEAD(void, GetTransformFeedbacki64_v, GLuint xfb, GLenum pname, GLuint index, GLint64* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTransformFeedbacki64_v, xfb, pname, index, param)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, CreateBuffers, GLsizei n, GLuint* buffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateBuffers, n, buffers)
|
DECLARE_GL_FUNCTION_HEAD(void, CreateBuffers, GLsizei n, GLuint* buffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateBuffers, n, buffers)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, NamedBufferStorage, GLuint buffer, GLsizeiptr size, const void* data, GLbitfield flags) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferStorage, buffer, size, data, flags)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedBufferStorage, GLuint buffer, GLsizeiptr size, const void* data, GLbitfield flags) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferStorage, buffer, size, data, flags)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, NamedBufferData, GLuint buffer, GLsizeiptr size, const void* data, GLenum usage) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferData, buffer, size, data, usage)
|
DECLARE_GL_FUNCTION_HEAD(void, NamedBufferData, GLuint buffer, GLsizeiptr size, const void* data, GLenum usage) DECLARE_GL_FUNCTION_END_NO_RETURN(void, NamedBufferData, buffer, size, data, usage)
|
||||||
@@ -1090,7 +1090,7 @@ DECLARE_GL_FUNCTION_HEAD(void, VertexArrayVertexBuffers, GLuint vaobj, GLuint fi
|
|||||||
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribBinding, GLuint vaobj, GLuint attribindex, GLuint bindingindex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribBinding, vaobj, attribindex, bindingindex)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribBinding, GLuint vaobj, GLuint attribindex, GLuint bindingindex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribBinding, vaobj, attribindex, bindingindex)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribFormat, vaobj, attribindex, size, type, normalized, relativeoffset)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribFormat, vaobj, attribindex, size, type, normalized, relativeoffset)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribIFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribIFormat, vaobj, attribindex, size, type, relativeoffset)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribIFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribIFormat, vaobj, attribindex, size, type, relativeoffset)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, VertexArrayAttribLFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, VertexArrayAttribLFormat, vaobj, attribindex, size, type, relativeoffset)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayAttribLFormat, GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayAttribLFormat, vaobj, attribindex, size, type, relativeoffset)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayBindingDivisor, GLuint vaobj, GLuint bindingindex, GLuint divisor) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayBindingDivisor, vaobj, bindingindex, divisor)
|
DECLARE_GL_FUNCTION_HEAD(void, VertexArrayBindingDivisor, GLuint vaobj, GLuint bindingindex, GLuint divisor) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexArrayBindingDivisor, vaobj, bindingindex, divisor)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetVertexArrayiv, GLuint vaobj, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexArrayiv, vaobj, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, GetVertexArrayiv, GLuint vaobj, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexArrayiv, vaobj, pname, param)
|
||||||
DECLARE_GL_FUNCTION_HEAD(void, GetVertexArrayIndexediv, GLuint vaobj, GLuint index, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexArrayIndexediv, vaobj, index, pname, param)
|
DECLARE_GL_FUNCTION_HEAD(void, GetVertexArrayIndexediv, GLuint vaobj, GLuint index, GLenum pname, GLint* param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexArrayIndexediv, vaobj, index, pname, param)
|
||||||
|
|||||||
@@ -1143,6 +1143,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
const FramebufferAttachmentType attachmentType = MG_Util::ConvertGLEnumToFramebufferAttachmentType(attachment);
|
const FramebufferAttachmentType attachmentType = MG_Util::ConvertGLEnumToFramebufferAttachmentType(attachment);
|
||||||
if (!FramebufferImpl::ValidateFramebufferAttachmentType(attachmentType)) return;
|
if (!FramebufferImpl::ValidateFramebufferAttachmentType(attachmentType)) return;
|
||||||
|
if (!FramebufferImpl::ValidateColorAttachmentInRange(attachmentType, functionName)) return;
|
||||||
if (!TextureImpl::ValidateTextureName(texture, true)) return;
|
if (!TextureImpl::ValidateTextureName(texture, true)) return;
|
||||||
|
|
||||||
if (texture == 0) {
|
if (texture == 0) {
|
||||||
@@ -1239,6 +1240,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
FramebufferTarget framebufferTarget = MG_Util::ConvertGLEnumToFramebufferTarget(target);
|
FramebufferTarget framebufferTarget = MG_Util::ConvertGLEnumToFramebufferTarget(target);
|
||||||
RenderbufferTarget rbTarget = MG_Util::ConvertGLEnumToRenderbufferTarget(renderbuffertarget);
|
RenderbufferTarget rbTarget = MG_Util::ConvertGLEnumToRenderbufferTarget(renderbuffertarget);
|
||||||
if (!FramebufferImpl::ValidateFramebufferAttachmentType(attachmentType)) return;
|
if (!FramebufferImpl::ValidateFramebufferAttachmentType(attachmentType)) return;
|
||||||
|
if (!FramebufferImpl::ValidateColorAttachmentInRange(attachmentType, "FramebufferRenderbuffer_State")) return;
|
||||||
if (!FramebufferImpl::ValidateFramebufferTarget(framebufferTarget)) return;
|
if (!FramebufferImpl::ValidateFramebufferTarget(framebufferTarget)) return;
|
||||||
if (!FramebufferImpl::ValidateRenderbufferTarget(rbTarget)) return;
|
if (!FramebufferImpl::ValidateRenderbufferTarget(rbTarget)) return;
|
||||||
auto& bindingSlot = MG_State::pGLContext->GetFramebufferBindingSlot(framebufferTarget);
|
auto& bindingSlot = MG_State::pGLContext->GetFramebufferBindingSlot(framebufferTarget);
|
||||||
@@ -1283,6 +1285,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
FramebufferAttachmentType attachmentType = MG_Util::ConvertGLEnumToFramebufferAttachmentType(attachment);
|
FramebufferAttachmentType attachmentType = MG_Util::ConvertGLEnumToFramebufferAttachmentType(attachment);
|
||||||
RenderbufferTarget rbTarget = MG_Util::ConvertGLEnumToRenderbufferTarget(renderbuffertarget);
|
RenderbufferTarget rbTarget = MG_Util::ConvertGLEnumToRenderbufferTarget(renderbuffertarget);
|
||||||
if (!FramebufferImpl::ValidateFramebufferAttachmentType(attachmentType)) return;
|
if (!FramebufferImpl::ValidateFramebufferAttachmentType(attachmentType)) return;
|
||||||
|
if (!FramebufferImpl::ValidateColorAttachmentInRange(attachmentType, "NamedFramebufferRenderbuffer_State"))
|
||||||
|
return;
|
||||||
if (!FramebufferImpl::ValidateRenderbufferTarget(rbTarget)) return;
|
if (!FramebufferImpl::ValidateRenderbufferTarget(rbTarget)) return;
|
||||||
|
|
||||||
if (renderbuffer == 0) {
|
if (renderbuffer == 0) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
// End of Source File Header
|
// End of Source File Header
|
||||||
|
|
||||||
#include "Validators.h"
|
#include "Validators.h"
|
||||||
|
#include <MG_Backend/BackendObjects.h>
|
||||||
#include <MG_State/GLState/Core.h>
|
#include <MG_State/GLState/Core.h>
|
||||||
#include <MG_State/GLState/ErrorState/Error.h>
|
#include <MG_State/GLState/ErrorState/Error.h>
|
||||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||||
@@ -60,6 +61,26 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool ValidateColorAttachmentInRange(FramebufferAttachmentType attachment, const char* caller) {
|
||||||
|
const auto first = static_cast<SizeT>(FramebufferAttachmentType::Color0);
|
||||||
|
const auto index = static_cast<SizeT>(attachment);
|
||||||
|
if (index < first) return true;
|
||||||
|
const auto colorIndex = index - first;
|
||||||
|
const auto limit = static_cast<SizeT>(
|
||||||
|
MG_Backend::pActiveBackendObject ? MG_Backend::pActiveBackendObject->GetDynamicParameters()
|
||||||
|
.MaxColorAttachments
|
||||||
|
: static_cast<Int>(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS));
|
||||||
|
if (colorIndex >= limit) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl/FramebufferImpl", caller,
|
||||||
|
std::format("Colour attachment {} is beyond GL_MAX_COLOR_ATTACHMENTS ({}).", colorIndex, limit)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Bool ValidateRenderbufferTarget(RenderbufferTarget target) {
|
Bool ValidateRenderbufferTarget(RenderbufferTarget target) {
|
||||||
if (target == RenderbufferTarget::Unknown) {
|
if (target == RenderbufferTarget::Unknown) {
|
||||||
using namespace MG_Util;
|
using namespace MG_Util;
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl {
|
|||||||
Bool ValidateFramebufferTarget(FramebufferTarget target);
|
Bool ValidateFramebufferTarget(FramebufferTarget target);
|
||||||
Bool ValidateFramebufferName(Uint index, Bool allowZero = true);
|
Bool ValidateFramebufferName(Uint index, Bool allowZero = true);
|
||||||
Bool ValidateFramebufferAttachmentType(FramebufferAttachmentType attachment);
|
Bool ValidateFramebufferAttachmentType(FramebufferAttachmentType attachment);
|
||||||
|
// GL_COLOR_ATTACHMENTn is a token per n up to 31, but only the first GL_MAX_COLOR_ATTACHMENTS of
|
||||||
|
// them name an attachment point of a framebuffer object; the rest are INVALID_OPERATION for the
|
||||||
|
// attaching entry points (GL 4.6 core 9.2.7). Non-colour attachments pass through unchanged.
|
||||||
|
Bool ValidateColorAttachmentInRange(FramebufferAttachmentType attachment, const char* caller);
|
||||||
Bool ValidateRenderbufferTarget(RenderbufferTarget target);
|
Bool ValidateRenderbufferTarget(RenderbufferTarget target);
|
||||||
Bool ValidateRenderbufferName(Uint index, Bool allowZero = true);
|
Bool ValidateRenderbufferName(Uint index, Bool allowZero = true);
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl
|
} // namespace MobileGL::MG_Impl::GLImpl::FramebufferImpl
|
||||||
|
|||||||
@@ -671,6 +671,40 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (target) {
|
switch (target) {
|
||||||
|
// The vertex buffer binding points of the vertex array object that is bound. Indexed by
|
||||||
|
// binding point, not by attribute (GL 4.6 core 10.3.1).
|
||||||
|
case GL_VERTEX_BINDING_BUFFER:
|
||||||
|
case GL_VERTEX_BINDING_DIVISOR:
|
||||||
|
case GL_VERTEX_BINDING_OFFSET:
|
||||||
|
case GL_VERTEX_BINDING_STRIDE: {
|
||||||
|
if (index >= VertexArrayImpl::GetMaxVertexAttribBindings()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"Vertex buffer binding index is out of range."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||||
|
if (!vao) {
|
||||||
|
*data = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto& binding = vao->GetBindingPoint(index);
|
||||||
|
switch (target) {
|
||||||
|
case GL_VERTEX_BINDING_BUFFER:
|
||||||
|
*data = binding.Buffer ? static_cast<GLint>(binding.Buffer->GetExternalIndex()) : 0;
|
||||||
|
return;
|
||||||
|
case GL_VERTEX_BINDING_DIVISOR:
|
||||||
|
*data = static_cast<GLint>(binding.Divisor);
|
||||||
|
return;
|
||||||
|
case GL_VERTEX_BINDING_OFFSET:
|
||||||
|
*data = static_cast<GLint>(binding.Offset);
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
*data = static_cast<GLint>(binding.Stride);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
case GL_IMAGE_BINDING_NAME:
|
case GL_IMAGE_BINDING_NAME:
|
||||||
case GL_IMAGE_BINDING_LEVEL:
|
case GL_IMAGE_BINDING_LEVEL:
|
||||||
case GL_IMAGE_BINDING_LAYERED:
|
case GL_IMAGE_BINDING_LAYERED:
|
||||||
@@ -1738,20 +1772,22 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
*params = vao ? static_cast<GLint>(vao->GetExternalIndex()) : 0;
|
*params = vao ? static_cast<GLint>(vao->GetExternalIndex()) : 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// The vertex buffer binding points are per-binding-index state, so the non-indexed getter
|
||||||
|
// has nothing to answer with (GL 4.6 core table 23.4).
|
||||||
|
case GL_VERTEX_BINDING_BUFFER:
|
||||||
case GL_VERTEX_BINDING_DIVISOR:
|
case GL_VERTEX_BINDING_DIVISOR:
|
||||||
*params = 0; // vertex-binding entrypoints are stubbed
|
|
||||||
return;
|
|
||||||
case GL_VERTEX_BINDING_OFFSET:
|
case GL_VERTEX_BINDING_OFFSET:
|
||||||
*params = 0; // vertex-binding entrypoints are stubbed
|
|
||||||
return;
|
|
||||||
case GL_VERTEX_BINDING_STRIDE:
|
case GL_VERTEX_BINDING_STRIDE:
|
||||||
*params = 0; // vertex-binding entrypoints are stubbed
|
RecordIndexedOnlyGetterError(__func__, pname);
|
||||||
return;
|
return;
|
||||||
case GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET:
|
case GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET:
|
||||||
*params = 0; // vertex-binding entrypoints are stubbed
|
*params = static_cast<GLint>(VertexArrayImpl::GetMaxVertexAttribRelativeOffset());
|
||||||
return;
|
return;
|
||||||
case GL_MAX_VERTEX_ATTRIB_BINDINGS:
|
case GL_MAX_VERTEX_ATTRIB_BINDINGS:
|
||||||
*params = 0; // vertex-binding entrypoints are stubbed
|
*params = static_cast<GLint>(VertexArrayImpl::GetMaxVertexAttribBindings());
|
||||||
|
return;
|
||||||
|
case GL_MAX_VERTEX_ATTRIB_STRIDE:
|
||||||
|
*params = static_cast<GLint>(VertexArrayImpl::GetMaxVertexAttribStride());
|
||||||
return;
|
return;
|
||||||
case GL_VIEWPORT: {
|
case GL_VIEWPORT: {
|
||||||
const auto& vp = MG_State::pGLContext->GetViewport();
|
const auto& vp = MG_State::pGLContext->GetViewport();
|
||||||
|
|||||||
@@ -2039,11 +2039,63 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
// resolved - by binding for the target forms, by name for the DSA ones. `size` is
|
// resolved - by binding for the target forms, by name for the DSA ones. `size` is
|
||||||
// kWholeBuffer for the non-Range entry points, which attach the buffer as it grows rather
|
// kWholeBuffer for the non-Range entry points, which attach the buffer as it grows rather
|
||||||
// than freezing the size it happens to have now.
|
// than freezing the size it happens to have now.
|
||||||
|
// The sized internal formats a buffer texture accepts (GL 4.6 core table 8.16). This is a much
|
||||||
|
// shorter list than the renderable or texturable formats, so it cannot be inferred from either.
|
||||||
|
static Bool IsBufferTextureInternalFormat(GLenum internalformat) {
|
||||||
|
switch (internalformat) {
|
||||||
|
case GL_R8:
|
||||||
|
case GL_R16:
|
||||||
|
case GL_R16F:
|
||||||
|
case GL_R32F:
|
||||||
|
case GL_R8I:
|
||||||
|
case GL_R16I:
|
||||||
|
case GL_R32I:
|
||||||
|
case GL_R8UI:
|
||||||
|
case GL_R16UI:
|
||||||
|
case GL_R32UI:
|
||||||
|
case GL_RG8:
|
||||||
|
case GL_RG16:
|
||||||
|
case GL_RG16F:
|
||||||
|
case GL_RG32F:
|
||||||
|
case GL_RG8I:
|
||||||
|
case GL_RG16I:
|
||||||
|
case GL_RG32I:
|
||||||
|
case GL_RG8UI:
|
||||||
|
case GL_RG16UI:
|
||||||
|
case GL_RG32UI:
|
||||||
|
case GL_RGB32F:
|
||||||
|
case GL_RGB32I:
|
||||||
|
case GL_RGB32UI:
|
||||||
|
case GL_RGBA8:
|
||||||
|
case GL_RGBA16:
|
||||||
|
case GL_RGBA16F:
|
||||||
|
case GL_RGBA32F:
|
||||||
|
case GL_RGBA8I:
|
||||||
|
case GL_RGBA16I:
|
||||||
|
case GL_RGBA32I:
|
||||||
|
case GL_RGBA8UI:
|
||||||
|
case GL_RGBA16UI:
|
||||||
|
case GL_RGBA32UI:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void AttachBufferToTexture(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
static void AttachBufferToTexture(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
||||||
GLenum internalformat, GLuint buffer, GLintptr offset, SizeT size,
|
GLenum internalformat, GLuint buffer, GLintptr offset, SizeT size,
|
||||||
const char* caller) {
|
const char* caller) {
|
||||||
using MG_State::GLState::TextureObjectBuffer;
|
using MG_State::GLState::TextureObjectBuffer;
|
||||||
TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
|
TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat);
|
||||||
|
if (!IsBufferTextureInternalFormat(internalformat)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", caller,
|
||||||
|
std::format("internalformat 0x{:X} is not one of the sized formats a buffer texture accepts.",
|
||||||
|
internalformat)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return;
|
if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return;
|
||||||
|
|
||||||
auto& bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
|
auto& bufferObject = MG_State::pGLContext->GetBufferObject(buffer);
|
||||||
@@ -2056,8 +2108,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
|
||||||
if (textureObject->GetStorageType() != TextureStorageType::Buffer) {
|
if (textureObject->GetStorageType() != TextureStorageType::Buffer) {
|
||||||
|
// A texture whose target is something else is a wrong object, not a wrong token
|
||||||
|
// (GL 4.6 core 8.9).
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidEnum,
|
ErrorCode::InvalidOperation,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
||||||
"The effective target of `texture` is not `GL_TEXTURE_BUFFER`."));
|
"The effective target of `texture` is not `GL_TEXTURE_BUFFER`."));
|
||||||
return;
|
return;
|
||||||
@@ -2086,6 +2140,15 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
"offset is not a multiple of GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT."));
|
"offset is not a multiple of GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// The range has to lie inside the buffer that is being attached. Detaching (buffer
|
||||||
|
// zero) carries no range to check.
|
||||||
|
if (bufferObject && static_cast<SizeT>(offset) + size > bufferObject->GetSize()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
||||||
|
"offset + size is greater than the buffer object's GL_BUFFER_SIZE."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto* texBufferObject = static_cast<TextureObjectBuffer*>(textureObject.get());
|
auto* texBufferObject = static_cast<TextureObjectBuffer*>(textureObject.get());
|
||||||
@@ -3730,23 +3793,43 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GetTextureImage(texture, level, format, type, bufSize, pixels);
|
GetTextureImage(texture, level, format, type, bufSize, pixels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A buffer texture carries none of the sampler or level state these queries report. Reached by
|
||||||
|
// name there is no target token to blame, so the wrong object is INVALID_OPERATION rather than
|
||||||
|
// the INVALID_ENUM the target forms report for an unaccepted target (GL 4.6 core 8.11).
|
||||||
|
static Bool ValidateNamedTextureHasParameters(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
|
||||||
|
const char* caller) {
|
||||||
|
if (!textureObject) return false;
|
||||||
|
if (textureObject->GetStorageType() == TextureStorageType::Buffer) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
||||||
|
"The effective target of `texture` has no texture parameters."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void GetTextureParameteriv(GLuint texture, GLenum pname, GLint* params) {
|
void GetTextureParameteriv(GLuint texture, GLenum pname, GLint* params) {
|
||||||
auto textureObject = GetTextureObjectByName(texture, __func__);
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
||||||
|
if (!ValidateNamedTextureHasParameters(textureObject, __func__)) return;
|
||||||
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameteriv_State(target, pname, params); });
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameteriv_State(target, pname, params); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetTextureParameterfv(GLuint texture, GLenum pname, GLfloat* params) {
|
void GetTextureParameterfv(GLuint texture, GLenum pname, GLfloat* params) {
|
||||||
auto textureObject = GetTextureObjectByName(texture, __func__);
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
||||||
|
if (!ValidateNamedTextureHasParameters(textureObject, __func__)) return;
|
||||||
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterfv_State(target, pname, params); });
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterfv_State(target, pname, params); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetTextureParameterIiv(GLuint texture, GLenum pname, GLint* params) {
|
void GetTextureParameterIiv(GLuint texture, GLenum pname, GLint* params) {
|
||||||
auto textureObject = GetTextureObjectByName(texture, __func__);
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
||||||
|
if (!ValidateNamedTextureHasParameters(textureObject, __func__)) return;
|
||||||
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterIiv_State(target, pname, params); });
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterIiv_State(target, pname, params); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void GetTextureParameterIuiv(GLuint texture, GLenum pname, GLuint* params) {
|
void GetTextureParameterIuiv(GLuint texture, GLenum pname, GLuint* params) {
|
||||||
auto textureObject = GetTextureObjectByName(texture, __func__);
|
auto textureObject = GetTextureObjectByName(texture, __func__);
|
||||||
|
if (!ValidateNamedTextureHasParameters(textureObject, __func__)) return;
|
||||||
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterIuiv_State(target, pname, params); });
|
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GetTexParameterIuiv_State(target, pname, params); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -105,12 +105,45 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return pname == GL_CURRENT_VERTEX_ATTRIB;
|
return pname == GL_CURRENT_VERTEX_ATTRIB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The stride a pointer-style call gives its binding point: the argument when it is non-zero,
|
||||||
|
// otherwise the tightly packed element size (GL 4.6 core 10.3.2). A packed 2_10_10_10 or
|
||||||
|
// 10F_11F_11F attribute is one 32-bit word regardless of its component count.
|
||||||
|
static int EffectiveVertexStride(GLsizei stride, GLint size, GLenum type) {
|
||||||
|
if (stride != 0) return static_cast<int>(stride);
|
||||||
|
switch (type) {
|
||||||
|
case GL_INT_2_10_10_10_REV:
|
||||||
|
case GL_UNSIGNED_INT_2_10_10_10_REV:
|
||||||
|
case GL_UNSIGNED_INT_10F_11F_11F_REV:
|
||||||
|
return 4;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return static_cast<int>(size * MG_Util::GetGLTypeSize(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
// glBindVertexBuffers / glVertexArrayVertexBuffers take a range of binding points, and a
|
||||||
|
// range that runs past the last one is INVALID_OPERATION rather than the INVALID_VALUE a
|
||||||
|
// single out-of-range index gets (GL 4.6 core 10.3.1).
|
||||||
|
static bool ValidateVertexBindingRange(GLuint first, GLsizei count, const char* funcName) {
|
||||||
|
if (count < 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName, "count must be non-negative."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (static_cast<Uint64>(first) + static_cast<Uint64>(count) >
|
||||||
|
VertexArrayImpl::GetMaxVertexAttribBindings()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName,
|
||||||
|
"first + count exceeds GL_MAX_VERTEX_ATTRIB_BINDINGS."));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
static bool ValidateVertexBindingIndex(GLuint bindingindex, const char* funcName) {
|
static bool ValidateVertexBindingIndex(GLuint bindingindex, const char* funcName) {
|
||||||
// Bound by the same dynamic limit as attribute indices: the default attribute -> binding
|
if (bindingindex >= VertexArrayImpl::GetMaxVertexAttribBindings()) {
|
||||||
// mapping is the identity, so a binding point the backend cannot address as an attribute
|
|
||||||
// would resolve into an attribute the backend must then reject on every draw. Real drivers
|
|
||||||
// likewise report MAX_VERTEX_ATTRIB_BINDINGS == MAX_VERTEX_ATTRIBS.
|
|
||||||
if (bindingindex >= VertexArrayImpl::GetMaxVertexAttribs()) {
|
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidValue,
|
ErrorCode::InvalidValue,
|
||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName,
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", funcName,
|
||||||
@@ -155,6 +188,17 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
SharedPtr<MG_State::GLState::VertexArrayObject> GetNamedVertexArrayObject_State(GLuint vaobj,
|
SharedPtr<MG_State::GLState::VertexArrayObject> GetNamedVertexArrayObject_State(GLuint vaobj,
|
||||||
const char* caller) {
|
const char* caller) {
|
||||||
|
// Name zero is not a vertex array object in a core profile: it names the default vertex
|
||||||
|
// array, which the by-name (direct state access) entry points never accept. MobileGL keeps a
|
||||||
|
// real object at index 0 for the compatibility paths, so the generic name validation below
|
||||||
|
// would otherwise let it through (GL 4.6 core 10.3.1).
|
||||||
|
if (vaobj == 0) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
||||||
|
"Vertex array name 0 is not a vertex array object."));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
if (!VertexArrayImpl::ValidateVertexArrayName(vaobj)) return nullptr;
|
if (!VertexArrayImpl::ValidateVertexArrayName(vaobj)) return nullptr;
|
||||||
if (!VertexArrayImpl::ValidateVertexArrayObject(vaobj)) return nullptr;
|
if (!VertexArrayImpl::ValidateVertexArrayObject(vaobj)) return nullptr;
|
||||||
return MG_State::pGLContext->GetVertexArrayObject(vaobj);
|
return MG_State::pGLContext->GetVertexArrayObject(vaobj);
|
||||||
@@ -210,7 +254,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
|
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
|
||||||
// Integer path: never normalized, never BGRA/packed (the validator rejects those).
|
// Integer path: never normalized, never BGRA/packed (the validator rejects those).
|
||||||
if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, dataType, false, stride, true)) return;
|
if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, type, dataType, false, stride, true)) return;
|
||||||
|
|
||||||
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||||
if (!vao) {
|
if (!vao) {
|
||||||
@@ -227,6 +271,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
vao->SetAttributeFormat(index, size, dataType, false, stride, offset, true, false);
|
vao->SetAttributeFormat(index, size, dataType, false, stride, offset, true, false);
|
||||||
vao->BindAttributeBuffer(index, vbo);
|
vao->BindAttributeBuffer(index, vbo);
|
||||||
|
vao->MirrorPointerIntoBinding(index, vbo, offset, EffectiveVertexStride(stride, size, type));
|
||||||
}
|
}
|
||||||
|
|
||||||
void VertexAttribPointer_State(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,
|
void VertexAttribPointer_State(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride,
|
||||||
@@ -234,7 +279,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
|
if (!VertexArrayImpl::ValidateVertexAttributeIndex(index)) return;
|
||||||
|
|
||||||
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
|
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
|
||||||
if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, dataType, normalized == GL_TRUE, stride, false))
|
if (!VertexArrayImpl::ValidateVertexAttribFormat(index, size, type, dataType, normalized == GL_TRUE, stride, false))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
auto& vao = MG_State::pGLContext->GetBoundVertexArray();
|
||||||
@@ -256,6 +301,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
const int effectiveSize = isBgra ? 4 : size;
|
const int effectiveSize = isBgra ? 4 : size;
|
||||||
vao->SetAttributeFormat(index, effectiveSize, dataType, normalized, stride, offset, false, isBgra);
|
vao->SetAttributeFormat(index, effectiveSize, dataType, normalized, stride, offset, false, isBgra);
|
||||||
vao->BindAttributeBuffer(index, vbo);
|
vao->BindAttributeBuffer(index, vbo);
|
||||||
|
vao->MirrorPointerIntoBinding(index, vbo, offset, EffectiveVertexStride(stride, effectiveSize, type));
|
||||||
}
|
}
|
||||||
|
|
||||||
void BindVertexArray_State(GLuint array) {
|
void BindVertexArray_State(GLuint array) {
|
||||||
@@ -359,6 +405,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "offset and stride must be non-negative."));
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, "offset and stride must be non-negative."));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (static_cast<Uint>(stride) > VertexArrayImpl::GetMaxVertexAttribStride()) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
||||||
|
"stride exceeds GL_MAX_VERTEX_ATTRIB_STRIDE."));
|
||||||
|
return;
|
||||||
|
}
|
||||||
auto bufferObject = GetVertexArrayBufferObject_State(buffer, caller);
|
auto bufferObject = GetVertexArrayBufferObject_State(buffer, caller);
|
||||||
if (buffer != 0 && !bufferObject) return;
|
if (buffer != 0 && !bufferObject) return;
|
||||||
|
|
||||||
@@ -376,6 +429,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
const GLintptr* offsets, const GLsizei* strides) {
|
const GLintptr* offsets, const GLsizei* strides) {
|
||||||
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayVertexBuffers_State");
|
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayVertexBuffers_State");
|
||||||
if (!vao) return;
|
if (!vao) return;
|
||||||
|
if (!ValidateVertexBindingRange(first, count, "VertexArrayVertexBuffers_State")) return;
|
||||||
for (GLsizei i = 0; i < count; ++i) {
|
for (GLsizei i = 0; i < count; ++i) {
|
||||||
if (!buffers) {
|
if (!buffers) {
|
||||||
VertexBufferBinding_State(vao, first + i, 0, 0, 16, "VertexArrayVertexBuffers_State");
|
VertexBufferBinding_State(vao, first + i, 0, 0, 16, "VertexArrayVertexBuffers_State");
|
||||||
@@ -389,12 +443,36 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
static void VertexAttribFormatSeparate_State(const SharedPtr<MG_State::GLState::VertexArrayObject>& vao,
|
static void VertexAttribFormatSeparate_State(const SharedPtr<MG_State::GLState::VertexArrayObject>& vao,
|
||||||
GLuint attribindex, GLint size, GLenum type, GLboolean normalized,
|
GLuint attribindex, GLint size, GLenum type, GLboolean normalized,
|
||||||
GLuint relativeoffset, Bool isInteger, const char* caller) {
|
GLuint relativeoffset, Bool isInteger, const char* caller) {
|
||||||
|
static_cast<void>(caller);
|
||||||
if (!VertexArrayImpl::ValidateVertexAttributeIndex(attribindex)) return;
|
if (!VertexArrayImpl::ValidateVertexAttributeIndex(attribindex)) return;
|
||||||
|
|
||||||
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
|
DataType dataType = MG_Util::ConvertGLEnumToDataType(type);
|
||||||
if (!VertexArrayImpl::ValidateVertexAttribPointerParams(attribindex, size, dataType, 0)) return;
|
// The separate-format entry points take the same size/type rules as the pointer ones,
|
||||||
|
// GL_BGRA included, so they need the full format validation rather than the pointer-only
|
||||||
|
// subset - that one reports GL_BGRA as an out-of-range size.
|
||||||
|
if (!VertexArrayImpl::ValidateVertexAttribFormat(attribindex, size, type, dataType, normalized == GL_TRUE, 0,
|
||||||
|
isInteger))
|
||||||
|
return;
|
||||||
|
if (!VertexArrayImpl::ValidateVertexAttribRelativeOffset(relativeoffset)) return;
|
||||||
|
|
||||||
vao->SetAttributeFormatSeparate(attribindex, size, dataType, normalized, isInteger, relativeoffset);
|
const Bool isBgra = (size == static_cast<GLint>(GL_BGRA));
|
||||||
|
vao->SetAttributeFormatSeparate(attribindex, isBgra ? 4 : size, dataType, normalized, isInteger,
|
||||||
|
relativeoffset, isBgra);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The long (64-bit) attribute format. MobileGL has no 64-bit vertex attributes, so nothing is
|
||||||
|
// recorded; what the entry point owes the application is the parameter validation, which is
|
||||||
|
// observable through glGetError regardless of whether the format could be used in a draw.
|
||||||
|
static void VertexAttribLFormatSeparate_State(GLuint attribindex, GLint size, GLenum type,
|
||||||
|
GLuint relativeoffset) {
|
||||||
|
if (!VertexArrayImpl::ValidateVertexAttributeIndex(attribindex)) return;
|
||||||
|
if (!VertexArrayImpl::ValidateVertexAttribLFormat(attribindex, size, type)) return;
|
||||||
|
if (!VertexArrayImpl::ValidateVertexAttribRelativeOffset(relativeoffset)) return;
|
||||||
|
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "VertexAttribLFormat",
|
||||||
|
"64-bit vertex attributes are not supported."));
|
||||||
}
|
}
|
||||||
|
|
||||||
void VertexArrayAttribFormat_State(GLuint vaobj, GLuint attribindex, GLint size, GLenum type,
|
void VertexArrayAttribFormat_State(GLuint vaobj, GLuint attribindex, GLint size, GLenum type,
|
||||||
@@ -1153,6 +1231,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
const GLsizei* strides) {
|
const GLsizei* strides) {
|
||||||
auto vao = GetBoundVertexArrayOrError("BindVertexBuffers");
|
auto vao = GetBoundVertexArrayOrError("BindVertexBuffers");
|
||||||
if (!vao) return;
|
if (!vao) return;
|
||||||
|
if (!ValidateVertexBindingRange(first, count, "BindVertexBuffers")) return;
|
||||||
for (GLsizei i = 0; i < count; ++i) {
|
for (GLsizei i = 0; i < count; ++i) {
|
||||||
if (!buffers) {
|
if (!buffers) {
|
||||||
VertexBufferBinding_State(vao, first + i, 0, 0, 16, "BindVertexBuffers");
|
VertexBufferBinding_State(vao, first + i, 0, 0, 16, "BindVertexBuffers");
|
||||||
@@ -1177,6 +1256,18 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
"VertexAttribIFormat");
|
"VertexAttribIFormat");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VertexAttribLFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) {
|
||||||
|
auto vao = GetBoundVertexArrayOrError("VertexAttribLFormat");
|
||||||
|
if (!vao) return;
|
||||||
|
VertexAttribLFormatSeparate_State(attribindex, size, type, relativeoffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VertexArrayAttribLFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) {
|
||||||
|
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayAttribLFormat");
|
||||||
|
if (!vao) return;
|
||||||
|
VertexAttribLFormatSeparate_State(attribindex, size, type, relativeoffset);
|
||||||
|
}
|
||||||
|
|
||||||
void VertexAttribBinding(GLuint attribindex, GLuint bindingindex) {
|
void VertexAttribBinding(GLuint attribindex, GLuint bindingindex) {
|
||||||
auto vao = GetBoundVertexArrayOrError("VertexAttribBinding");
|
auto vao = GetBoundVertexArrayOrError("VertexAttribBinding");
|
||||||
if (!vao) return;
|
if (!vao) return;
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void VertexArrayAttribFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized,
|
void VertexArrayAttribFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLboolean normalized,
|
||||||
GLuint relativeoffset);
|
GLuint relativeoffset);
|
||||||
void VertexArrayAttribIFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
|
void VertexArrayAttribIFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
|
||||||
|
void VertexArrayAttribLFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
|
||||||
void VertexArrayAttribBinding(GLuint vaobj, GLuint attribindex, GLuint bindingindex);
|
void VertexArrayAttribBinding(GLuint vaobj, GLuint attribindex, GLuint bindingindex);
|
||||||
void VertexArrayBindingDivisor(GLuint vaobj, GLuint bindingindex, GLuint divisor);
|
void VertexArrayBindingDivisor(GLuint vaobj, GLuint bindingindex, GLuint divisor);
|
||||||
void VertexArrayVertexBuffers(GLuint vaobj, GLuint first, GLsizei count, const GLuint* buffers,
|
void VertexArrayVertexBuffers(GLuint vaobj, GLuint first, GLsizei count, const GLuint* buffers,
|
||||||
@@ -107,6 +108,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
const GLsizei* strides);
|
const GLsizei* strides);
|
||||||
void VertexAttribFormat(GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset);
|
void VertexAttribFormat(GLuint attribindex, GLint size, GLenum type, GLboolean normalized, GLuint relativeoffset);
|
||||||
void VertexAttribIFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
|
void VertexAttribIFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
|
||||||
|
void VertexAttribLFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset);
|
||||||
void VertexAttribBinding(GLuint attribindex, GLuint bindingindex);
|
void VertexAttribBinding(GLuint attribindex, GLuint bindingindex);
|
||||||
void VertexBindingDivisor(GLuint bindingindex, GLuint divisor);
|
void VertexBindingDivisor(GLuint bindingindex, GLuint divisor);
|
||||||
void VertexAttribDivisor(GLuint index, GLuint divisor);
|
void VertexAttribDivisor(GLuint index, GLuint divisor);
|
||||||
|
|||||||
@@ -23,6 +23,18 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
|||||||
return std::min(static_cast<Uint>(backendLimit), capacity);
|
return std::min(static_cast<Uint>(backendLimit), capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Uint GetMaxVertexAttribBindings() {
|
||||||
|
return GetMaxVertexAttribs();
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint GetMaxVertexAttribRelativeOffset() {
|
||||||
|
return 2047;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint GetMaxVertexAttribStride() {
|
||||||
|
return 2048;
|
||||||
|
}
|
||||||
|
|
||||||
Bool ValidateVertexArrayName(Uint index) {
|
Bool ValidateVertexArrayName(Uint index) {
|
||||||
Bool isValid = MG_State::pGLContext->ValidateVertexArrayName(index);
|
Bool isValid = MG_State::pGLContext->ValidateVertexArrayName(index);
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
@@ -90,9 +102,31 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, DataType type, Bool normalized, Int stride,
|
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, GLenum glType, DataType type, Bool normalized,
|
||||||
Bool integerPath) {
|
Int stride, Bool integerPath) {
|
||||||
constexpr const char* fn = "ValidateVertexAttribFormat";
|
constexpr const char* fn = "ValidateVertexAttribFormat";
|
||||||
|
// GL_UNSIGNED_INT_10F_11F_11F_REV is a three-component float-path-only packing that has no
|
||||||
|
// DataType of its own, so it has to be recognised by name before the conversion below turns
|
||||||
|
// it into Unknown and reports the wrong error (GL 4.6 core 10.3.2).
|
||||||
|
if (glType == GL_UNSIGNED_INT_10F_11F_11F_REV) {
|
||||||
|
if (integerPath) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", fn,
|
||||||
|
std::format("GL_UNSIGNED_INT_10F_11F_11F_REV is not an integer-path type (attribute {}).",
|
||||||
|
index)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (sizeRaw != 3) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", fn,
|
||||||
|
std::format("GL_UNSIGNED_INT_10F_11F_11F_REV requires size 3 (attribute {}).", index)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (type == DataType::Unknown) {
|
if (type == DataType::Unknown) {
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidEnum,
|
ErrorCode::InvalidEnum,
|
||||||
@@ -170,4 +204,40 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bool ValidateVertexAttribLFormat(Uint index, GLint size, GLenum type) {
|
||||||
|
constexpr const char* fn = "ValidateVertexAttribLFormat";
|
||||||
|
if (size < 1 || size > 4) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", fn,
|
||||||
|
std::format("Invalid size {} for attribute {}. Must be 1-4.", size, index)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// GL 4.6 core 10.3.2: the long form takes GL_DOUBLE and nothing else.
|
||||||
|
if (type != GL_DOUBLE) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", fn,
|
||||||
|
std::format("Type 0x{:X} is not GL_DOUBLE (attribute {}).", type, index)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool ValidateVertexAttribRelativeOffset(Uint relativeOffset) {
|
||||||
|
const Uint limit = GetMaxVertexAttribRelativeOffset();
|
||||||
|
if (relativeOffset > limit) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidValue,
|
||||||
|
MakeUnique<GenericErrorInfo>(
|
||||||
|
"MG_Impl/GLImpl", "ValidateVertexAttribRelativeOffset",
|
||||||
|
std::format("relativeoffset {} exceeds GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET ({}).", relativeOffset,
|
||||||
|
limit)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl
|
} // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl
|
||||||
|
|||||||
@@ -15,6 +15,20 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
|||||||
// capacity). Falls back to the capacity when no backend is active (unit tests).
|
// capacity). Falls back to the capacity when no backend is active (unit tests).
|
||||||
Uint GetMaxVertexAttribs();
|
Uint GetMaxVertexAttribs();
|
||||||
|
|
||||||
|
// GL_MAX_VERTEX_ATTRIB_BINDINGS. The default attribute -> binding mapping is the identity, so a
|
||||||
|
// binding point that cannot also be an attribute index would resolve into an attribute the
|
||||||
|
// backend has to reject on every draw; real drivers report the two limits equal as well.
|
||||||
|
Uint GetMaxVertexAttribBindings();
|
||||||
|
|
||||||
|
// GL_MAX_VERTEX_ATTRIB_RELATIVE_OFFSET. The relative offset is folded into the resolved
|
||||||
|
// attribute offset in the frontend and never reaches a backend limit, so this is the value the
|
||||||
|
// spec requires an implementation to support at minimum (GL 4.6 core table 23.63).
|
||||||
|
Uint GetMaxVertexAttribRelativeOffset();
|
||||||
|
|
||||||
|
// GL_MAX_VERTEX_ATTRIB_STRIDE. Like the relative offset above, the stride never reaches a
|
||||||
|
// backend limit of its own, so this is the spec minimum (GL 4.6 core table 23.63).
|
||||||
|
Uint GetMaxVertexAttribStride();
|
||||||
|
|
||||||
Bool ValidateVertexArrayName(Uint index);
|
Bool ValidateVertexArrayName(Uint index);
|
||||||
Bool ValidateVertexArrayObject(Uint index);
|
Bool ValidateVertexArrayObject(Uint index);
|
||||||
Bool ValidateVertexAttributeIndex(Uint index);
|
Bool ValidateVertexAttributeIndex(Uint index);
|
||||||
@@ -22,6 +36,13 @@ namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl {
|
|||||||
// Full glVertexAttribPointer / glVertexAttribIPointer format validation, including the packed
|
// Full glVertexAttribPointer / glVertexAttribIPointer format validation, including the packed
|
||||||
// 2_10_10_10 types and GL_BGRA size. sizeRaw is the untranslated GL size (possibly GL_BGRA);
|
// 2_10_10_10 types and GL_BGRA size. sizeRaw is the untranslated GL size (possibly GL_BGRA);
|
||||||
// integerPath selects the glVertexAttribIPointer rules.
|
// integerPath selects the glVertexAttribIPointer rules.
|
||||||
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, DataType type, Bool normalized, Int stride,
|
Bool ValidateVertexAttribFormat(Uint index, GLint sizeRaw, GLenum glType, DataType type, Bool normalized,
|
||||||
Bool integerPath);
|
Int stride, Bool integerPath);
|
||||||
|
// glVertexAttribLFormat / glVertexArrayAttribLFormat: the only accepted type is GL_DOUBLE and
|
||||||
|
// the size range is 1-4 (GL_BGRA is a float-path size). Separate from the function above
|
||||||
|
// because the long path shares none of its type or size rules.
|
||||||
|
Bool ValidateVertexAttribLFormat(Uint index, GLint size, GLenum type);
|
||||||
|
// Shared by every *Format entry point: INVALID_VALUE once relativeoffset leaves the range the
|
||||||
|
// implementation advertises.
|
||||||
|
Bool ValidateVertexAttribRelativeOffset(Uint relativeOffset);
|
||||||
} // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl
|
} // namespace MobileGL::MG_Impl::GLImpl::VertexArrayImpl
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
BindingSlot<BufferObject>& GetBindingSlot(BufferTarget target);
|
BindingSlot<BufferObject>& GetBindingSlot(BufferTarget target);
|
||||||
// For glBindBufferBase / glBindBufferRange
|
// For glBindBufferBase / glBindBufferRange
|
||||||
BindingSlotRange1D<BufferObject>& GetBindingPoint(BufferTarget target, Uint index);
|
BindingSlotRange1D<BufferObject>& GetBindingPoint(BufferTarget target, Uint index);
|
||||||
|
const BindingSlotRange1D<BufferObject>& GetBindingPoint(BufferTarget target, Uint index) const {
|
||||||
|
return const_cast<BufferState*>(this)->GetBindingPoint(target, index);
|
||||||
|
}
|
||||||
constexpr SizeT GetBindingPointCount(const BufferTarget target) const {
|
constexpr SizeT GetBindingPointCount(const BufferTarget target) const {
|
||||||
auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target);
|
auto it = std::find(BufferBindPointTargets.begin(), BufferBindPointTargets.end(), target);
|
||||||
auto index = std::distance(BufferBindPointTargets.begin(), it);
|
auto index = std::distance(BufferBindPointTargets.begin(), it);
|
||||||
|
|||||||
@@ -842,6 +842,64 @@ namespace MobileGL::MG_State {
|
|||||||
const auto it = m_transformFeedbackObjects.find(index);
|
const auto it = m_transformFeedbackObjects.find(index);
|
||||||
return it != m_transformFeedbackObjects.end() && it->second.hasCompletedSpan;
|
return it != m_transformFeedbackObjects.end() && it->second.hasCompletedSpan;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GLContext::CreateTransformFeedbackObject(Uint index) {
|
||||||
|
// glCreateTransformFeedbacks has no bind step to infer existence from, so the name it
|
||||||
|
// hands out is already the name of an object (GL 4.6 core 13.2.1).
|
||||||
|
m_transformFeedbackObjects[index] = {};
|
||||||
|
m_transformFeedbackObjects[index].everBound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool GLContext::IsNamedTransformFeedbackActive(Uint index) const {
|
||||||
|
if (index == m_boundTransformFeedback) return m_transformFeedbackActive;
|
||||||
|
const auto it = m_transformFeedbackObjects.find(index);
|
||||||
|
return it != m_transformFeedbackObjects.end() && it->second.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool GLContext::IsNamedTransformFeedbackPaused(Uint index) const {
|
||||||
|
if (index == m_boundTransformFeedback) return m_transformFeedbackPaused;
|
||||||
|
const auto it = m_transformFeedbackObjects.find(index);
|
||||||
|
return it != m_transformFeedbackObjects.end() && it->second.paused;
|
||||||
|
}
|
||||||
|
|
||||||
|
NamedTransformFeedbackBinding GLContext::GetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex) const {
|
||||||
|
NamedTransformFeedbackBinding result;
|
||||||
|
if (bufferIndex >= MAX_TRANSFORM_FEEDBACK_BUFFERS) return result;
|
||||||
|
// The bound object's capture bindings live in the context's own binding points, not in
|
||||||
|
// the saved copy - that one is only written when the object is swapped out.
|
||||||
|
if (index == m_boundTransformFeedback) {
|
||||||
|
const auto& point = m_bufferState.GetBindingPoint(BufferTarget::TransformFeedback, bufferIndex);
|
||||||
|
result.Buffer = point.GetBoundObject();
|
||||||
|
result.Range = point.GetRange();
|
||||||
|
result.HasExplicitRange = point.HasExplicitRange();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const auto it = m_transformFeedbackObjects.find(index);
|
||||||
|
if (it == m_transformFeedbackObjects.end()) return result;
|
||||||
|
const auto& saved = it->second.bindings[bufferIndex];
|
||||||
|
result.Buffer = saved.buffer;
|
||||||
|
result.Range = saved.range;
|
||||||
|
result.HasExplicitRange = saved.hasExplicitRange;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLContext::SetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex,
|
||||||
|
const SharedPtr<BufferObject>& buffer, Range1D range,
|
||||||
|
Bool hasExplicitRange) {
|
||||||
|
if (bufferIndex >= MAX_TRANSFORM_FEEDBACK_BUFFERS) return;
|
||||||
|
if (index == m_boundTransformFeedback) {
|
||||||
|
auto& point = m_bufferState.GetBindingPoint(BufferTarget::TransformFeedback, bufferIndex);
|
||||||
|
point.Bind(buffer);
|
||||||
|
if (buffer && hasExplicitRange) {
|
||||||
|
point.SetRange(range, true);
|
||||||
|
} else {
|
||||||
|
point.ClearRange();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto& object = m_transformFeedbackObjects[index];
|
||||||
|
object.bindings[bufferIndex] = {buffer, range, hasExplicitRange};
|
||||||
|
}
|
||||||
} // namespace GLState
|
} // namespace GLState
|
||||||
|
|
||||||
// Leak-at-exit storage; see GlobalObjects.cpp.
|
// Leak-at-exit storage; see GlobalObjects.cpp.
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ namespace MobileGL {
|
|||||||
// translates the result into its own API call.
|
// translates the result into its own API call.
|
||||||
VertexAttribTypeInfo ClassifyVertexAttribType(GLenum glType);
|
VertexAttribTypeInfo ClassifyVertexAttribType(GLenum glType);
|
||||||
|
|
||||||
|
// One indexed capture binding of a transform feedback object, as the by-name queries
|
||||||
|
// report it. An empty Buffer means the binding point is unbound.
|
||||||
|
struct NamedTransformFeedbackBinding {
|
||||||
|
SharedPtr<BufferObject> Buffer;
|
||||||
|
Range1D Range{};
|
||||||
|
Bool HasExplicitRange = false;
|
||||||
|
};
|
||||||
|
|
||||||
class GLContext {
|
class GLContext {
|
||||||
public:
|
public:
|
||||||
GLContext() = default;
|
GLContext() = default;
|
||||||
@@ -299,6 +307,17 @@ namespace MobileGL {
|
|||||||
// cannot express: an empty completed span is legal and draws nothing.
|
// cannot express: an empty completed span is legal and draws nothing.
|
||||||
Bool HasTransformFeedbackCompletedSpan(Uint index) const;
|
Bool HasTransformFeedbackCompletedSpan(Uint index) const;
|
||||||
|
|
||||||
|
// The by-name (direct state access) view. A named object that happens to be the
|
||||||
|
// bound one is answered from the live copy, since that is where its state actually
|
||||||
|
// is until a bind swaps it out.
|
||||||
|
void CreateTransformFeedbackObject(Uint index);
|
||||||
|
Bool IsNamedTransformFeedbackActive(Uint index) const;
|
||||||
|
Bool IsNamedTransformFeedbackPaused(Uint index) const;
|
||||||
|
NamedTransformFeedbackBinding GetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex) const;
|
||||||
|
void SetNamedTransformFeedbackBinding(Uint index, Uint bufferIndex,
|
||||||
|
const SharedPtr<BufferObject>& buffer, Range1D range,
|
||||||
|
Bool hasExplicitRange);
|
||||||
|
|
||||||
// Framebuffer
|
// Framebuffer
|
||||||
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
|
void GenFramebufferNames(Uint number, Vector<Uint>& framebuffers);
|
||||||
const SharedPtr<FramebufferObject>& GetFramebufferObject(Uint index);
|
const SharedPtr<FramebufferObject>& GetFramebufferObject(Uint index);
|
||||||
|
|||||||
@@ -77,6 +77,26 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
BumpAttributeFormatVersion(index);
|
BumpAttributeFormatVersion(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void VertexArrayObject::MirrorPointerIntoBinding(Uint index, const SharedPtr<BufferObject>& buffer, SizeT offset,
|
||||||
|
int effectiveStride) {
|
||||||
|
if (index >= MAX_VERTEX_ATTRIBS || index >= MAX_VERTEX_ATTRIB_BINDINGS) return;
|
||||||
|
|
||||||
|
// glVertexAttribPointer is defined in terms of the binding model (GL 4.6 core 10.3.2): it
|
||||||
|
// also sets binding point `index` to the buffer, the pointer as the offset, and the
|
||||||
|
// *effective* stride, and points the attribute at that binding point with relative offset 0.
|
||||||
|
// The flat attribute view keeps the raw stride, because VERTEX_ATTRIB_ARRAY_STRIDE reports
|
||||||
|
// that argument verbatim, so the binding point is recorded alongside the resolved attribute
|
||||||
|
// rather than being resolved into it.
|
||||||
|
m_attributeBindingIndex[index] = index;
|
||||||
|
m_attributeRelativeOffset[index] = 0;
|
||||||
|
|
||||||
|
auto& binding = m_bindingPoints[index];
|
||||||
|
binding.Buffer = buffer;
|
||||||
|
binding.Offset = offset;
|
||||||
|
binding.Stride = effectiveStride;
|
||||||
|
binding.Divisor = m_attributes[index].Divisor;
|
||||||
|
}
|
||||||
|
|
||||||
void VertexArrayObject::BindAttributeBuffer(Uint index, const SharedPtr<BufferObject>& buffer) {
|
void VertexArrayObject::BindAttributeBuffer(Uint index, const SharedPtr<BufferObject>& buffer) {
|
||||||
if (index >= MAX_VERTEX_ATTRIBS) return;
|
if (index >= MAX_VERTEX_ATTRIBS) return;
|
||||||
|
|
||||||
@@ -111,6 +131,11 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
|
|
||||||
void VertexArrayObject::SetAttributeDivisor(Uint index, Uint divisor) {
|
void VertexArrayObject::SetAttributeDivisor(Uint index, Uint divisor) {
|
||||||
if (index >= MAX_VERTEX_ATTRIBS) return;
|
if (index >= MAX_VERTEX_ATTRIBS) return;
|
||||||
|
// glVertexAttribDivisor is VertexBindingDivisor on the attribute's own binding point
|
||||||
|
// (GL 4.6 core 10.3.2), so the binding-point view has to follow the resolved attribute.
|
||||||
|
if (index < MAX_VERTEX_ATTRIB_BINDINGS && m_attributeBindingIndex[index] == index) {
|
||||||
|
m_bindingPoints[index].Divisor = divisor;
|
||||||
|
}
|
||||||
if (m_attributes[index].Divisor == divisor) return;
|
if (m_attributes[index].Divisor == divisor) return;
|
||||||
m_attributes[index].Divisor = divisor;
|
m_attributes[index].Divisor = divisor;
|
||||||
BumpAttributeFormatVersion(index);
|
BumpAttributeFormatVersion(index);
|
||||||
@@ -187,18 +212,18 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void VertexArrayObject::SetAttributeFormatSeparate(Uint attribIndex, int size, DataType type, Bool normalized,
|
void VertexArrayObject::SetAttributeFormatSeparate(Uint attribIndex, int size, DataType type, Bool normalized,
|
||||||
Bool isInteger, Uint relativeOffset) {
|
Bool isInteger, Uint relativeOffset, Bool isBgra) {
|
||||||
if (attribIndex >= MAX_VERTEX_ATTRIBS) return;
|
if (attribIndex >= MAX_VERTEX_ATTRIBS) return;
|
||||||
if (size < 1 || size > 4) return;
|
if (size < 1 || size > 4) return;
|
||||||
|
|
||||||
auto& attr = m_attributes[attribIndex];
|
auto& attr = m_attributes[attribIndex];
|
||||||
if (attr.Size != size || attr.Type != type || attr.Normalized != normalized || attr.IsInteger != isInteger ||
|
if (attr.Size != size || attr.Type != type || attr.Normalized != normalized || attr.IsInteger != isInteger ||
|
||||||
attr.IsBgra || m_attributeRelativeOffset[attribIndex] != relativeOffset) {
|
attr.IsBgra != isBgra || m_attributeRelativeOffset[attribIndex] != relativeOffset) {
|
||||||
attr.Size = size;
|
attr.Size = size;
|
||||||
attr.Type = type;
|
attr.Type = type;
|
||||||
attr.Normalized = normalized;
|
attr.Normalized = normalized;
|
||||||
attr.IsInteger = isInteger;
|
attr.IsInteger = isInteger;
|
||||||
attr.IsBgra = false; // the binding-format path (glVertexAttribFormat) does not carry BGRA
|
attr.IsBgra = isBgra;
|
||||||
m_attributeRelativeOffset[attribIndex] = relativeOffset;
|
m_attributeRelativeOffset[attribIndex] = relativeOffset;
|
||||||
BumpAttributeFormatVersion(attribIndex);
|
BumpAttributeFormatVersion(attribIndex);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,12 @@ namespace MobileGL {
|
|||||||
|
|
||||||
void BindAttributeBuffer(Uint index, const SharedPtr<BufferObject>& buffer);
|
void BindAttributeBuffer(Uint index, const SharedPtr<BufferObject>& buffer);
|
||||||
|
|
||||||
|
// Record what the pointer-style API implies for the binding-point view: attribute
|
||||||
|
// `index` bound to binding point `index` with relative offset 0, and that binding
|
||||||
|
// point carrying the buffer, the pointer offset and the effective stride.
|
||||||
|
void MirrorPointerIntoBinding(Uint index, const SharedPtr<BufferObject>& buffer, SizeT offset,
|
||||||
|
int effectiveStride);
|
||||||
|
|
||||||
BindingSlot<BufferObject>& GetIndexBufferBindingSlot();
|
BindingSlot<BufferObject>& GetIndexBufferBindingSlot();
|
||||||
const BindingSlot<BufferObject>& GetIndexBufferBindingSlot() const;
|
const BindingSlot<BufferObject>& GetIndexBufferBindingSlot() const;
|
||||||
|
|
||||||
@@ -82,7 +88,7 @@ namespace MobileGL {
|
|||||||
void SetBindingDivisor(Uint bindingIndex, Uint divisor);
|
void SetBindingDivisor(Uint bindingIndex, Uint divisor);
|
||||||
void SetAttributeBinding(Uint attribIndex, Uint bindingIndex);
|
void SetAttributeBinding(Uint attribIndex, Uint bindingIndex);
|
||||||
void SetAttributeFormatSeparate(Uint attribIndex, int size, DataType type, Bool normalized,
|
void SetAttributeFormatSeparate(Uint attribIndex, int size, DataType type, Bool normalized,
|
||||||
Bool isInteger, Uint relativeOffset);
|
Bool isInteger, Uint relativeOffset, Bool isBgra = false);
|
||||||
|
|
||||||
// The binding-point view the attributes were resolved from. Kept queryable
|
// The binding-point view the attributes were resolved from. Kept queryable
|
||||||
// because glGetVertexArrayIndexed[64]iv reports it verbatim, and the resolved
|
// because glGetVertexArrayIndexed[64]iv reports it verbatim, and the resolved
|
||||||
|
|||||||
@@ -1056,9 +1056,17 @@ namespace MobileGL::MG_Util::BackendLoader {
|
|||||||
caps.MaxComputeWorkGroupInvocations = maxComputeWorkGroupInvocations;
|
caps.MaxComputeWorkGroupInvocations = maxComputeWorkGroupInvocations;
|
||||||
caps.MaxShaderStorageBufferBindings = maxShaderStorageBufferBindings;
|
caps.MaxShaderStorageBufferBindings = maxShaderStorageBufferBindings;
|
||||||
caps.MaxTextureBufferSize = maxTextureBufferSize;
|
caps.MaxTextureBufferSize = maxTextureBufferSize;
|
||||||
|
// Through glesFuncs, like every other capability query here: a bare glGetIntegerv resolves
|
||||||
|
// to MobileGL's own exported entry point, which answers this pname from the very
|
||||||
|
// capability table being filled in - so the driver's real alignment never arrived and the
|
||||||
|
// backend reported an unconstrained offset it cannot honour.
|
||||||
GLint textureBufferOffsetAlignment = 1;
|
GLint textureBufferOffsetAlignment = 1;
|
||||||
glGetIntegerv(GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT, &textureBufferOffsetAlignment);
|
glesFuncs.glGetIntegerv(GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT, &textureBufferOffsetAlignment);
|
||||||
while (glGetError() != GL_NO_ERROR) {}
|
// Core in ES 3.2 and in EXT_texture_buffer; an older context rejects the pname and leaves
|
||||||
|
// the default in place.
|
||||||
|
if (glesFuncs.glGetError) {
|
||||||
|
while (glesFuncs.glGetError() != GL_NO_ERROR) {}
|
||||||
|
}
|
||||||
caps.TextureBufferOffsetAlignment = std::max(1, textureBufferOffsetAlignment);
|
caps.TextureBufferOffsetAlignment = std::max(1, textureBufferOffsetAlignment);
|
||||||
caps.MaxUniformBufferBindings = maxUniformBufferBindings;
|
caps.MaxUniformBufferBindings = maxUniformBufferBindings;
|
||||||
caps.MaxUniformBlockSize = maxUniformBlockSize;
|
caps.MaxUniformBlockSize = maxUniformBlockSize;
|
||||||
|
|||||||
+63
-1
@@ -1,12 +1,16 @@
|
|||||||
# Running the OpenGL CTS against MobileGL
|
# Running the OpenGL CTS against MobileGL
|
||||||
|
|
||||||
This directory contains two supported paths:
|
This directory contains three supported paths:
|
||||||
|
|
||||||
- Android arm64 / MobileGL EGL: the KHR-GL33 workflow documented below and in
|
- Android arm64 / MobileGL EGL: the KHR-GL33 workflow documented below and in
|
||||||
`skills/gl-cts-on-mobilegl/SKILL.md`.
|
`skills/gl-cts-on-mobilegl/SKILL.md`.
|
||||||
- Windows x64 / MobileGL WGL: the GL30-GL46 pipeline in
|
- Windows x64 / MobileGL WGL: the GL30-GL46 pipeline in
|
||||||
`scripts/wgl_glcts_pipeline.py`, documented by
|
`scripts/wgl_glcts_pipeline.py`, documented by
|
||||||
`skills/wgl-gl-cts-on-mobilegl/SKILL.md`.
|
`skills/wgl-gl-cts-on-mobilegl/SKILL.md`.
|
||||||
|
- Desktop Linux x64 / MobileGL EGL: `scripts/run_cts_local.py` against the
|
||||||
|
`mobilegl-desktop` VK-GL-CTS target, documented in "Desktop Linux workflow"
|
||||||
|
below and in `skills/linux-gl-cts-on-mobilegl/SKILL.md`. This is the path that
|
||||||
|
needs no device and no GPU.
|
||||||
|
|
||||||
Windows prerequisites are Git, Python 3.9+, CMake, Visual Studio 2022's Desktop
|
Windows prerequisites are Git, Python 3.9+, CMake, Visual Studio 2022's Desktop
|
||||||
C++ workload, and a Vulkan SDK visible to CMake. DirectVulkan also needs a
|
C++ workload, and a Vulkan SDK visible to CMake. DirectVulkan also needs a
|
||||||
@@ -25,6 +29,64 @@ resumes individual suites after crashes/timeouts, and writes Markdown plus JSON
|
|||||||
reports below the printed `runs/<first-16-of-run-fingerprint>` directory. Its
|
reports below the printed `runs/<first-16-of-run-fingerprint>` directory. Its
|
||||||
manifest records provenance and the runner settings used to validate a resume.
|
manifest records provenance and the runner settings used to validate a resume.
|
||||||
|
|
||||||
|
## Desktop Linux workflow
|
||||||
|
|
||||||
|
The `mobilegl-desktop` target builds `glcts` as an ordinary host executable that
|
||||||
|
reaches OpenGL only through `libMobileGL.so`. Both backends run headless with no
|
||||||
|
GPU at all, which makes this the cheapest way to measure a single test group
|
||||||
|
while working on it.
|
||||||
|
|
||||||
|
Apt packages: `mesa-vulkan-drivers` (lavapipe, for DirectVulkan),
|
||||||
|
`libegl1-mesa-dev` and `libgles2-mesa-dev` (the system EGL/ES that DirectGLES
|
||||||
|
drives), `libvulkan-dev`, `ninja-build`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cmake -S . -B build-linux -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DMOBILEGL_BUILD_TEST=OFF -DMOBILEGL_BUILD_BENCHMARK=OFF
|
||||||
|
cmake --build build-linux --parallel "$(nproc)"
|
||||||
|
|
||||||
|
python tools/cts/scripts/sync_to_cts.py "$CTS"
|
||||||
|
git -C "$CTS" apply <path-to>/tools/cts/patches/0001-fbo-color-texture-attachment.patch
|
||||||
|
cmake -S "$CTS" -B "$CTS/build-cts" -G Ninja -DDEQP_TARGET=mobilegl-desktop \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release
|
||||||
|
ninja -C "$CTS/build-cts" glcts
|
||||||
|
|
||||||
|
python tools/cts/scripts/run_cts_local.py --backend DirectGLES \
|
||||||
|
--glcts "$CTS/build-cts/external/openglcts/modules/glcts" \
|
||||||
|
--lib build-linux/libMobileGL.so \
|
||||||
|
--caselist cases.txt --outdir runs/gles --env EGL_PLATFORM=surfaceless
|
||||||
|
python tools/cts/scripts/qpa_report.py runs/gles --label DirectGLES
|
||||||
|
```
|
||||||
|
|
||||||
|
`EGL_PLATFORM=surfaceless` is not optional for DirectGLES: without a `/dev/dri`
|
||||||
|
node Mesa's EGL fails `eglInitialize` on the default display, and MobileGL
|
||||||
|
reports that as `EGL_BAD_ALLOC` out of `eglCreatePbufferSurface`. DirectVulkan
|
||||||
|
needs nothing extra - lavapipe exposes `VK_EXT_headless_surface`, which is what
|
||||||
|
the desktop platform port's pbuffer path requires.
|
||||||
|
|
||||||
|
Two known differences from a real GPU, both MobileGL's rather than the harness's:
|
||||||
|
DirectVulkan reads back zeros from the **default** framebuffer (a user FBO,
|
||||||
|
renderbuffer- or texture-attached, is correct on both backends), and lavapipe
|
||||||
|
supports renderbuffer formats that Adreno reports as unsupported, so the Android
|
||||||
|
runs see `NotSupported` where these do not.
|
||||||
|
|
||||||
|
### Reference results: KHR-GL45.direct_state_access
|
||||||
|
|
||||||
|
`opengl-cts-4.6.8.1`, the 371 `direct_state_access` cases of the `gl45-main`
|
||||||
|
mustpass list, lavapipe / Mesa 25.2.8, `--deqp-surface-type=fbo`.
|
||||||
|
|
||||||
|
| backend | conformance | strict Pass | Fail | InternalError | Crash |
|
||||||
|
| --- | ---: | ---: | ---: | ---: | ---: |
|
||||||
|
| DirectGLES | **74.93%** | 67.39% | 85 | 8 | 0 |
|
||||||
|
| DirectVulkan | **73.32%** | 73.05% | 88 | 8 | 3 |
|
||||||
|
|
||||||
|
`textures_storage_multisample_*` is 54 of what remains on either backend and
|
||||||
|
needs a feature rather than a fix: a multisample texture has to be attachable to
|
||||||
|
a framebuffer and then sampleable through `sampler2DMS`, and the array half of
|
||||||
|
the group additionally needs layered attachments, which the framebuffer
|
||||||
|
attachment model does not represent yet. `program_pipelines_*` needs
|
||||||
|
ARB_separate_shader_objects, which is stubbed throughout.
|
||||||
|
|
||||||
## Android KHR-GL33 workflow
|
## Android KHR-GL33 workflow
|
||||||
|
|
||||||
Goal: measure how much of the OpenGL 3.3 core-profile conformance suite MobileGL
|
Goal: measure how much of the OpenGL 3.3 core-profile conformance suite MobileGL
|
||||||
|
|||||||
@@ -17,3 +17,4 @@ Each skill is a self-contained package, matching the layout used by
|
|||||||
| --- | --- |
|
| --- | --- |
|
||||||
| [gl-cts-on-mobilegl](gl-cts-on-mobilegl/SKILL.md) | Build VK-GL-CTS `glcts` as a standalone Android arm64 binary against MobileGL's own EGL, run KHR-GL33, and report a per-backend OpenGL 3.3 core conformance rate. |
|
| [gl-cts-on-mobilegl](gl-cts-on-mobilegl/SKILL.md) | Build VK-GL-CTS `glcts` as a standalone Android arm64 binary against MobileGL's own EGL, run KHR-GL33, and report a per-backend OpenGL 3.3 core conformance rate. |
|
||||||
| [wgl-gl-cts-on-mobilegl](wgl-gl-cts-on-mobilegl/SKILL.md) | Build MobileGL's Windows x64 WGL drop-in, run GL30-GL46 core CTS against DirectGLES and DirectVulkan, resume safely, and emit validated reports. |
|
| [wgl-gl-cts-on-mobilegl](wgl-gl-cts-on-mobilegl/SKILL.md) | Build MobileGL's Windows x64 WGL drop-in, run GL30-GL46 core CTS against DirectGLES and DirectVulkan, resume safely, and emit validated reports. |
|
||||||
|
| [linux-gl-cts-on-mobilegl](linux-gl-cts-on-mobilegl/SKILL.md) | Build `glcts` as a desktop Linux host binary against MobileGL, run any CTS group headlessly with no GPU, and report Espryt and Magma separately. The path to reach for while iterating on a fix. |
|
||||||
|
|||||||
@@ -0,0 +1,159 @@
|
|||||||
|
---
|
||||||
|
name: linux-gl-cts-on-mobilegl
|
||||||
|
description: Run the Khronos OpenGL CTS (VK-GL-CTS glcts) against MobileGL on desktop Linux with no GPU and no device, and report a per-backend conformance rate for Espryt (DirectGLES) and Magma (DirectVulkan). Use when measuring or iterating on the conformance of one test group, when a fix needs a before/after number, or when neither an Android device nor a Windows GPU box is available.
|
||||||
|
---
|
||||||
|
|
||||||
|
# OpenGL CTS on MobileGL (desktop Linux)
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
`glcts` is built as an ordinary x86-64 host executable that reaches OpenGL only
|
||||||
|
through `libMobileGL.so`, using the `mobilegl-desktop` VK-GL-CTS target and the
|
||||||
|
`tcu::Platform` port in `tools/cts/platform/`. Nothing links `libGL` or `libEGL`,
|
||||||
|
so a result is unambiguously MobileGL's.
|
||||||
|
|
||||||
|
Both backends run headless on software rendering, so this needs no GPU at all:
|
||||||
|
|
||||||
|
- **Espryt** (`DirectGLES`) drives Mesa's OpenGL ES through the system EGL.
|
||||||
|
- **Magma** (`DirectVulkan`) runs on lavapipe, whose `VK_EXT_headless_surface`
|
||||||
|
is what the desktop platform port's pbuffer path requires.
|
||||||
|
|
||||||
|
Always report the two backends **separately**. They are different
|
||||||
|
implementations of the same front end, they fail different cases, and a single
|
||||||
|
combined number hides which one a change moved.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo apt-get install -y ninja-build cmake libvulkan-dev \
|
||||||
|
libegl1-mesa-dev libgles2-mesa-dev mesa-vulkan-drivers
|
||||||
|
```
|
||||||
|
|
||||||
|
`mesa-vulkan-drivers` is what installs lavapipe; without it DirectVulkan has no
|
||||||
|
ICD and `eglInitialize` fails inside the backend. A C++23 toolchain is required
|
||||||
|
(GCC 13+, or Clang 20+ — Clang 18 defines `__cpp_concepts` as 201907L, which
|
||||||
|
switches libstdc++'s `<expected>` off and the build fails in
|
||||||
|
`MG_Util/ShaderTranspiler/Types.h`).
|
||||||
|
|
||||||
|
```sh
|
||||||
|
export MG=<path-to-MobileGL-worktree>
|
||||||
|
export CTS=<path-to-VK-GL-CTS-checkout>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 1 — build libMobileGL.so
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git -C "$MG" submodule update --init --recursive
|
||||||
|
python3 "$MG/3rdparty/glslang/update_glslang_sources.py" # SPIRV-Tools; ENABLE_OPT is forced on
|
||||||
|
|
||||||
|
cmake -S "$MG" -B "$MG/build-linux" -G Ninja -DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DMOBILEGL_BUILD_TEST=OFF -DMOBILEGL_BUILD_BENCHMARK=OFF \
|
||||||
|
-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||||
|
cmake --build "$MG/build-linux" --parallel "$(nproc)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Add `-DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=FALSE` when iterating. The
|
||||||
|
Release configuration turns LTO on, which makes every relink cost minutes for no
|
||||||
|
behavioural difference; a conformance number is identical either way.
|
||||||
|
|
||||||
|
## Step 2 — get VK-GL-CTS and build glcts
|
||||||
|
|
||||||
|
Use a release tag so the mustpass list, and therefore the reported rate, is
|
||||||
|
citable.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git -C "$CTS" checkout opengl-cts-4.6.8.1
|
||||||
|
python3 "$CTS/external/fetch_sources.py"
|
||||||
|
|
||||||
|
python3 "$MG/tools/cts/scripts/sync_to_cts.py" "$CTS"
|
||||||
|
git -C "$CTS" apply "$MG/tools/cts/patches/0001-fbo-color-texture-attachment.patch"
|
||||||
|
|
||||||
|
cmake -S "$CTS" -B "$CTS/build-cts" -G Ninja -DDEQP_TARGET=mobilegl-desktop \
|
||||||
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
|
||||||
|
ninja -C "$CTS/build-cts" glcts
|
||||||
|
```
|
||||||
|
|
||||||
|
Confirm the configure output says `*** Using MobileGL desktop target`. Budget a
|
||||||
|
couple of hours for the `glcts` link on a small machine; it is a one-time cost
|
||||||
|
that ccache makes cheap afterwards.
|
||||||
|
|
||||||
|
If `fetch_sources.py` dies with `HTTP Error 403` it is an egress policy blocking
|
||||||
|
the GitHub archive downloads (zlib, libpng), not a broken checkout: `git clone`
|
||||||
|
still works, so clone the package at the tag the script pins into
|
||||||
|
`external/<pkg>/src` by hand — for libpng also copy
|
||||||
|
`scripts/pnglibconf.h.prebuilt` to `src/pnglibconf.h`, which is what the
|
||||||
|
script's post-extract step does.
|
||||||
|
|
||||||
|
## Step 3 — run, once per backend
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd "$MG"
|
||||||
|
for BACKEND in DirectGLES DirectVulkan; do
|
||||||
|
python3 tools/cts/scripts/run_cts_local.py --backend "$BACKEND" \
|
||||||
|
--glcts "$CTS/build-cts/external/openglcts/modules/glcts" \
|
||||||
|
--lib build-linux/libMobileGL.so \
|
||||||
|
--caselist cases.txt --outdir "runs/${BACKEND}" \
|
||||||
|
--env EGL_PLATFORM=surfaceless
|
||||||
|
python3 tools/cts/scripts/qpa_report.py "runs/${BACKEND}" --label "$BACKEND"
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
`cases.txt` is any subset of a mustpass list. For one group, filter the list
|
||||||
|
rather than running the whole suite:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
grep direct_state_access \
|
||||||
|
"$CTS"/external/openglcts/data/gl_cts/data/mustpass/gl/khronos_mustpass/main/gl45-main.txt \
|
||||||
|
> cases.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
`run_cts_local.py` re-invokes `glcts` with only the cases that have no result
|
||||||
|
yet, so a crash costs one case rather than the run, and it records the case that
|
||||||
|
was open when the process died as `Crash`. `qpa_report.py` scores `Pass`,
|
||||||
|
`NotSupported` and the warning statuses as non-failures, the way Khronos scores
|
||||||
|
a submission, and reports the strict `Pass`-only rate alongside.
|
||||||
|
|
||||||
|
## Required flags, and why
|
||||||
|
|
||||||
|
| Flag | Why it is not optional |
|
||||||
|
| --- | --- |
|
||||||
|
| `--env EGL_PLATFORM=surfaceless` | For DirectGLES. With no `/dev/dri` node Mesa's EGL fails `eglInitialize` on the default display, and MobileGL surfaces that as `EGL_BAD_ALLOC` out of `eglCreatePbufferSurface` — which points at the wrong call entirely. Harmless for DirectVulkan, so pass it to both. |
|
||||||
|
| `--deqp-surface-type=fbo` (the runner's default) | DirectVulkan reads back zeros from the **default** framebuffer. dEQP verifies nearly everything through `glReadPixels`, so rendering to the surface scores Magma near zero for a reason unrelated to conformance. Use it for both backends so the two numbers stay comparable. |
|
||||||
|
| `--deqp-terminate-on-device-lost=disable` (supplied by the runner) | Its default calls `glGetGraphicsResetStatus()` after every case. That is GL 4.5 / `KHR_robustness`, absent from what MobileGL exports, so the pointer is null and the process segfaults on the first case. |
|
||||||
|
|
||||||
|
## What this environment does and does not tell you
|
||||||
|
|
||||||
|
Reproducible here, and MobileGL's own rather than a driver quirk:
|
||||||
|
|
||||||
|
- DirectVulkan's default-framebuffer readback returns zeros; a user FBO,
|
||||||
|
renderbuffer- or texture-attached, is correct on both backends. This is the
|
||||||
|
same defect the Android runs work around, so it can be debugged without a
|
||||||
|
phone.
|
||||||
|
|
||||||
|
Different from a real GPU, so do not read conformance into it:
|
||||||
|
|
||||||
|
- lavapipe supports renderbuffer formats Adreno reports as unsupported, so the
|
||||||
|
Android runs see `NotSupported` where these do not, and vice versa.
|
||||||
|
- Backend limits differ. `GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT` is 16 on llvmpipe
|
||||||
|
and on lavapipe; a device that reports 1 will not exercise the same paths.
|
||||||
|
- Everything is a software rasterizer, so a case that fails only under real
|
||||||
|
timing or real tiling will not fail here.
|
||||||
|
|
||||||
|
## Reference results: KHR-GL45.direct_state_access
|
||||||
|
|
||||||
|
`opengl-cts-4.6.8.1`, the 371 `direct_state_access` cases of the `gl45-main`
|
||||||
|
mustpass list, Mesa 25.2.8, `--deqp-surface-type=fbo`.
|
||||||
|
|
||||||
|
| backend | renderer | conformance | strict Pass | Fail | InternalError | Crash |
|
||||||
|
| --- | --- | ---: | ---: | ---: | ---: | ---: |
|
||||||
|
| DirectGLES | Espryt | **74.93%** | 67.39% | 85 | 8 | 0 |
|
||||||
|
| DirectVulkan | Magma | **73.32%** | 73.05% | 88 | 8 | 3 |
|
||||||
|
|
||||||
|
## Contents
|
||||||
|
|
||||||
|
platform/tcuMobileGLPlatform.{cpp,hpp} dEQP tcu::Platform for MobileGL
|
||||||
|
targets/mobilegl-desktop.cmake VK-GL-CTS target (-DDEQP_TARGET=mobilegl-desktop)
|
||||||
|
scripts/sync_to_cts.py inject the port into a CTS checkout
|
||||||
|
scripts/run_cts_local.py crash-resuming local-host runner
|
||||||
|
scripts/qpa_report.py .qpa -> conformance rate
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
interface:
|
||||||
|
display_name: "OpenGL CTS on MobileGL (desktop Linux)"
|
||||||
|
short_description: "Run VK-GL-CTS glcts against MobileGL headlessly and report Espryt and Magma separately"
|
||||||
|
default_prompt: "Use $linux-gl-cts-on-mobilegl to run the selected OpenGL CTS group against MobileGL on this Linux host and report the conformance rate for DirectGLES (Espryt) and DirectVulkan (Magma) separately."
|
||||||
Reference in New Issue
Block a user