[Feat] (MG_Impl, MG_Backend, MG_State): implement glMultiDrawArrays and glGetBufferSubData

Two previously-stubbed GL 3.3 Core entry points.

glMultiDrawArrays: mirrors the existing glMultiDrawElements(BaseVertex)
architecture end to end -- a new MultiDrawArrays backend function-table
slot dispatched from the frontend after program/primitive-mode validation
(plus a drawcount < 0 -> GL_INVALID_VALUE guard).
- DirectGLES: PrepareForDraw once, then loop native glDrawArrays with the
  same per-range client-side array upload the single DrawArrays does.
- DirectVulkan: build a MultiDrawCmd payload and hand it to a new
  VulkanRenderer::MultiDrawArrays, which does one SetupDraw over the union
  of the sub-draw vertex ranges and then a vkCmdDraw per range (mirrors
  VulkanRenderer::MultiDrawElements).

glGetBufferSubData: reads a range of the bound buffer's CPU shadow into
client memory via a new BufferObject::DownloadSubData, with the same
validation shape as BufferSubData (INVALID_VALUE for negative/overflowing
range, INVALID_OPERATION for no bound buffer or a non-persistent mapped
buffer). The shadow reflects CPU writes and backend write-backs but not
arbitrary GPU-side writes, which is documented on the method.

Tests: 2 BufferTest cases for glGetBufferSubData (round-trip read of a
middle range and the whole buffer, plus out-of-range/negative/no-buffer
errors). BufferTest 32/32, SanityTest 30/30, VertexArrayTest 42/42;
library builds clean. (The glMultiDrawArrays draw paths are not
runtime-testable on this host and are compile-verified against the tested
MultiDrawElements pattern.)
This commit is contained in:
2026-07-10 23:46:14 -04:00
parent 4dd2b2216c
commit dc3c2cc5c7
17 changed files with 217 additions and 2 deletions
@@ -799,6 +799,55 @@ namespace MobileGL::MG_Impl::GLImpl {
bufferObject->UploadSubData({(void*)data, (SizeT)size}, offset);
}
void GetBufferSubData_State(GLenum target, GLintptr offset, GLsizeiptr size, void* data) {
MGLOG_D("%s: target = %s, offset = %d, size = %d, data = %p", __func__,
MG_Util::ConvertGLEnumToString(target).c_str(), offset, size, data);
if (!data) {
// Match BufferSubData_State: a null pointer is a caller bug, not a GL-specified error.
return;
}
if (size < 0 || offset < 0) {
MG_State::pGLContext->RecordError(ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetBufferSubData_State",
"Offset and size must be non-negative."));
return;
}
BufferTarget bufferTarget = MG_Util::ConvertGLEnumToBufferTarget(target);
if (!BufferImpl::ValidateBufferTarget(bufferTarget)) return;
auto& bindingSlot = GetBufferBindingSlot(bufferTarget);
auto& bufferObject = bindingSlot.GetBoundObject();
if (!bufferObject) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetBufferSubData_State",
"Buffer target is bound to no buffer object."));
return;
}
SizeT bufferSize = bufferObject->GetSize();
if (static_cast<SizeT>(offset) + static_cast<SizeT>(size) > bufferSize) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetBufferSubData_State",
"Offset and size exceed buffer size."));
return;
}
if (bufferObject->IsMapped() &&
!(bufferObject->GetMappingAccess() & BufferMappingAccessBit::Persistent)) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetBufferSubData_State",
"Cannot read from a buffer object mapped without GL_MAP_PERSISTENT_BIT."));
return;
}
bufferObject->DownloadSubData(data, static_cast<SizeT>(offset), static_cast<SizeT>(size));
}
void BufferData_State(GLenum target, GLsizeiptr size, const void* data, GLenum usage) {
MGLOG_D("%s: %s, size = %d, data = %p, usage = %s", __func__, MG_Util::ConvertGLEnumToString(target).c_str(),
size, data, MG_Util::ConvertGLEnumToString(usage).c_str());
@@ -1425,6 +1474,10 @@ namespace MobileGL::MG_Impl::GLImpl {
BufferSubData_State(target, offset, size, data);
}
void GetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, void* data) {
GetBufferSubData_State(target, offset, size, data);
}
void BufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage) {
BufferData_State(target, size, data, usage);
}
@@ -40,6 +40,7 @@ namespace MobileGL::MG_Impl::GLImpl {
void CopyBufferSubData(GLenum readTarget, GLenum writeTarget, GLintptr readOffset, GLintptr writeOffset,
GLsizeiptr size);
void BufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, const void* data);
void GetBufferSubData(GLenum target, GLintptr offset, GLsizeiptr size, void* data);
void BufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage);
void BindBuffer(GLenum target, GLuint buffer);
void GenBuffers(GLsizei n, GLuint* buffers);
@@ -116,6 +116,13 @@ namespace MobileGL::MG_Impl::GLImpl {
MG_Backend::gBackendFunctionsTable.GL.DrawArrays(mode, first, count);
}
void MultiDrawArrays_Backend(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
MG_Backend::gBackendFunctionsTable.GL.MultiDrawArrays(mode, first, count, drawcount);
}
void DrawElementsBaseVertex_Backend(GLenum mode, GLsizei count, GLenum type, const void* indices,
GLint basevertex) {
#ifdef TRACY_ENABLE
@@ -426,6 +433,18 @@ namespace MobileGL::MG_Impl::GLImpl {
DrawArrays_Backend(mode, first, count);
}
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) {
if (!ValidateCurrentProgramForExecution(__func__)) return;
if (!ValidatePrimitiveModeForBackend(__func__, mode)) return;
if (drawcount < 0) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "drawcount must be non-negative."));
return;
}
MultiDrawArrays_Backend(mode, first, count, drawcount);
}
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
GLsizei drawcount) {
if (!ValidateCurrentProgramForExecution(__func__)) return;
@@ -38,6 +38,7 @@ namespace MobileGL::MG_Impl::GLImpl {
void DrawArraysIndirect(GLenum mode, const void* indirect);
void DrawElementsBaseVertex(GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex);
void DrawArrays(GLenum mode, GLint first, GLsizei count);
void MultiDrawArrays(GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount);
void MultiDrawElements(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
GLsizei drawcount);
void MultiDrawElementsBaseVertex(GLenum mode, const GLsizei* count, GLenum type, const void* const* indices,
@@ -785,7 +785,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, LoadTransposeMatrixf, const GLfloat* m) DECL
DECLARE_GL_FUNCTION_STUB_HEAD(void, LoadTransposeMatrixd, const GLdouble* m) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, LoadTransposeMatrixd, m)
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultTransposeMatrixf, const GLfloat* m) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultTransposeMatrixf, m)
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultTransposeMatrixd, const GLdouble* m) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultTransposeMatrixd, m)
DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiDrawArrays, GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiDrawArrays, mode, first, count, drawcount)
DECLARE_GL_FUNCTION_HEAD(void, MultiDrawArrays, GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MultiDrawArrays, mode, first, count, drawcount)
DECLARE_GL_FUNCTION_HEAD(void, MultiDrawElements, GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, GLsizei drawcount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MultiDrawElements, mode, count, type, indices, drawcount)
DECLARE_GL_FUNCTION_HEAD(void, PointParameterf, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PointParameterf, pname, param)
DECLARE_GL_FUNCTION_HEAD(void, PointParameterfv, GLenum pname, const GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PointParameterfv, pname, params)
@@ -830,7 +830,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3iv, const GLint* v) DECLARE_GL_FUN
DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3s, GLshort x, GLshort y, GLshort z) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, WindowPos3s, x, y, z)
DECLARE_GL_FUNCTION_STUB_HEAD(void, WindowPos3sv, const GLshort* v) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, WindowPos3sv, v)
DECLARE_GL_FUNCTION_HEAD(void, GetQueryObjectiv, GLuint id, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryObjectiv, id, pname, params)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetBufferSubData, GLenum target, GLintptr offset, GLsizeiptr size, void* data) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetBufferSubData, target, offset, size, data)
DECLARE_GL_FUNCTION_HEAD(void, GetBufferSubData, GLenum target, GLintptr offset, GLsizeiptr size, void* data) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetBufferSubData, target, offset, size, data)
DECLARE_GL_FUNCTION_HEAD(void, GetVertexAttribdv, GLuint index, GLenum pname, GLdouble* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetVertexAttribdv, index, pname, params)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib1d, GLuint index, GLdouble x) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib1d, index, x)
DECLARE_GL_FUNCTION_HEAD(void, VertexAttrib1dv, GLuint index, const GLdouble* v) DECLARE_GL_FUNCTION_END_NO_RETURN(void, VertexAttrib1dv, index, v)