[Feat] (MG_Impl/VertexArray): implement client-side buffer

This commit is contained in:
2026-06-07 13:02:58 +08:00
parent fbaf5261e2
commit ad1ca4ea92
5 changed files with 118 additions and 12 deletions
@@ -863,6 +863,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
#endif
DrawSyncBit syncBit = DrawSyncBit::None;
PrepareForDraw(syncBit);
const auto& currentVAO = MG_State::pGLContext->GetBoundVertexArray();
if (currentVAO) {
const auto& backendVAOIt = VertexArrayImpl::g_backendVertexArrayObjects.find(currentVAO.get());
if (backendVAOIt != VertexArrayImpl::g_backendVertexArrayObjects.end()) {
backendVAOIt->second->SyncClientSideAttributesForDrawArrays(currentVAO, first, count);
}
}
g_GLESFuncs.glDrawArrays(mode, first, count);
}
@@ -194,10 +194,34 @@ namespace MobileGL::MG_Backend::DirectGLES {
} // namespace BufferImpl
namespace VertexArrayImpl {
namespace {
SizeT GetDataTypeSize(DataType type) {
switch (type) {
case DataType::Int8:
case DataType::Uint8:
return 1;
case DataType::Int16:
case DataType::Uint16:
case DataType::Float16:
return 2;
case DataType::Int32:
case DataType::Uint32:
case DataType::Float32:
case DataType::Fixed32:
return 4;
case DataType::Float64:
return 8;
default:
return 0;
}
}
} // namespace
BackendVertexArrayObject::BackendVertexArrayObject() {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
#endif
m_clientAttributeBufferIds.fill(0);
g_GLESFuncs.glGenVertexArrays(1, &m_backendVAOId);
if (m_backendVAOId == 0) {
MGLOG_E("Failed to generate vertex array object.");
@@ -207,6 +231,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
}
BackendVertexArrayObject::~BackendVertexArrayObject() {
if (m_backendVAOId != 0) {
g_GLESFuncs.glDeleteVertexArrays(1, &m_backendVAOId);
m_backendVAOId = 0;
}
for (auto& bufferId : m_clientAttributeBufferIds) {
if (bufferId != 0) {
g_GLESFuncs.glDeleteBuffers(1, &bufferId);
bufferId = 0;
}
}
}
void BackendVertexArrayObject::Bind() const {
#ifdef TRACY_ENABLE
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
@@ -312,6 +349,58 @@ namespace MobileGL::MG_Backend::DirectGLES {
m_syncedAttributeVersions = allAttributeVersions;
}
void BackendVertexArrayObject::SyncClientSideAttributesForDrawArrays(
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject, GLint first, GLsizei count) {
if (!stateVAOObject || count <= 0 || first < 0) {
return;
}
Bind();
const auto& allAttributes = stateVAOObject->GetAllAttributes();
for (Uint attribIndex = 0; attribIndex < allAttributes.size(); ++attribIndex) {
const auto& attrib = allAttributes[attribIndex];
if (!attrib.Enabled || attrib.Buffer) {
continue;
}
const auto* clientData = reinterpret_cast<const Uint8*>(attrib.Offset);
const SizeT componentSize = GetDataTypeSize(attrib.Type);
if (!clientData || componentSize == 0 || attrib.Size <= 0) {
continue;
}
const SizeT elementSize = componentSize * static_cast<SizeT>(attrib.Size);
const SizeT stride = attrib.Stride > 0 ? static_cast<SizeT>(attrib.Stride) : elementSize;
const SizeT uploadSize = static_cast<SizeT>(first + count - 1) * stride + elementSize;
auto& bufferId = m_clientAttributeBufferIds[attribIndex];
if (bufferId == 0) {
g_GLESFuncs.glGenBuffers(1, &bufferId);
if (bufferId == 0) {
MGLOG_E("Failed to create client-side vertex attribute upload buffer.");
continue;
}
}
g_GLESFuncs.glBindBuffer(GL_ARRAY_BUFFER, bufferId);
g_GLESFuncs.glBufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(uploadSize), clientData,
GL_STREAM_DRAW);
if (!attrib.IsInteger) {
g_GLESFuncs.glVertexAttribPointer(
attribIndex, attrib.Size, MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
attrib.Normalized ? GL_TRUE : GL_FALSE, static_cast<GLsizei>(stride), nullptr);
} else {
g_GLESFuncs.glVertexAttribIPointer(attribIndex, attrib.Size,
MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
static_cast<GLsizei>(stride), nullptr);
}
}
BufferImpl::g_boundVertexBufferObject = nullptr;
}
StateBackendObjectRegistry<MG_State::GLState::VertexArrayObject, BackendVertexArrayObject>
g_backendVertexArrayObjects;
} // namespace VertexArrayImpl
@@ -137,12 +137,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
class BackendVertexArrayObject {
public:
BackendVertexArrayObject();
~BackendVertexArrayObject();
void SyncToBackend(const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject);
void SyncClientSideAttributesForDrawArrays(
const SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject, GLint first, GLsizei count);
Uint GetBackendVertexArrayId() const { return m_backendVAOId; }
void Bind() const;
private:
Uint m_backendVAOId = 0;
Array<Uint, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS> m_clientAttributeBufferIds;
Bool m_isInitialized = false;
Uint16 m_syncedIndexBufferVersion = 0;
Array<MG_State::GLState::VertexAttributeVersion, MG_State::GLState::VertexArrayObject::MAX_VERTEX_ATTRIBS>
@@ -60,12 +60,6 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& vboSlot = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex);
auto& vbo = vboSlot.GetBoundObject();
if (!vbo) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "VertexAttribPointer_State",
"No buffer is bound to GL_ARRAY_BUFFER."));
return;
}
auto offset = reinterpret_cast<SizeT>(pointer);
@@ -91,12 +85,6 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& vboSlot = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::Vertex);
auto& vbo = vboSlot.GetBoundObject();
if (!vbo) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "VertexAttribPointer_State",
"No buffer is bound to GL_ARRAY_BUFFER."));
return;
}
SizeT offset = reinterpret_cast<SizeT>(pointer);
@@ -363,6 +363,24 @@ TEST_F(GeneralVertexArrayTest, General_ElementArrayBufferBindingIsVaoLocalAndZer
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
TEST_F(GeneralVertexArrayTest, General_ClientSideVertexAttribPointerIsAccepted) {
GLuint vao = CreateVAO();
BindBuffer(GL_ARRAY_BUFFER, 0);
float vertices[] = {0.0f, 0.0f, 1.0f, 1.0f};
VertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, vertices);
EnableVertexAttribArray(0);
auto vaoObj = MG_State::pGLContext->GetVertexArrayObject(vao);
ASSERT_NE(vaoObj, nullptr);
const auto& attr = vaoObj->GetAttribute(0);
EXPECT_TRUE(attr.Enabled);
EXPECT_EQ(attr.Buffer, nullptr);
EXPECT_EQ(attr.Offset, reinterpret_cast<SizeT>(vertices));
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
TEST_F(GeneralVertexArrayTest, General_IntegerAttributes) {
GLuint vao = CreateVAO();
GLuint vbo = CreateVBO(GL_ARRAY_BUFFER, 128);