[Feat] (...): Add FramebufferState while implementing these gl functions.

This commit is contained in:
BZLZHH
2025-05-04 01:17:09 +08:00
parent 03c9531e7a
commit 30beaf6eb9
11 changed files with 337 additions and 6 deletions
+2
View File
@@ -66,6 +66,8 @@ add_library(${CMAKE_PROJECT_NAME} SHARED
MG/MG_GL/State/Program/ShaderObject.cpp
MG/MG_GL/State/Program/ProgramState.cpp
MG/MG_GL/State/Framebuffer/FramebufferState.cpp
# MG_GL/BackendLoader
MG/MG_GL/BackendLoader/GLES/Loader.cpp
+15
View File
@@ -23,6 +23,21 @@ namespace MG_Constants {
inline const int LOG_LEVEL_FATAL = 0x0014;
}
namespace Framebuffer {
static const std::unordered_set<GLenum> VALID_ATTACHMENTS = {
GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT,
GL_STENCIL_ATTACHMENT, GL_DEPTH_STENCIL_ATTACHMENT
}; // OpenGL 3
static const std::unordered_set<GLenum> VALID_TARGETS = {
GL_FRAMEBUFFER, GL_DRAW_FRAMEBUFFER, GL_READ_FRAMEBUFFER
}; // OpenGL 3
static const std::unordered_set<GLenum> VALID_ACCESS = {
GL_READ_ONLY, GL_WRITE_ONLY, GL_READ_WRITE
}; // OpenGL 3
}
namespace Buffer {
static const std::unordered_set<GLenum> VALID_PARAM_NAMES = {
GL_BUFFER_ACCESS, GL_BUFFER_MAPPED, GL_BUFFER_SIZE, GL_BUFFER_USAGE
+1 -1
View File
@@ -27,7 +27,7 @@ namespace MG_Global {
}
namespace Common {
inline const int LogLevel = MG_Constants::Common::LOG_LEVEL_INFO;
inline const int LogLevel = MG_Constants::Common::LOG_LEVEL_DEBUG;
#ifdef __ANDROID__
inline const char* LOG_FILE_PATH = "/sdcard/MG/latest.log";
+3
View File
@@ -53,6 +53,9 @@
#ifndef MOBILEGL_PROGRAMSTATE_H
#include "MG_GL/State/Program/ProgramState.h"
#endif
#ifndef MOBILEGL_FRAMEBUFFERSTATE_H
#include "MG_GL/State/Framebuffer/FramebufferState.h"
#endif
#ifndef MOBILEGL_LOOKUP_H
#include "MG_GL/Implementations/GLX/LookUp/LookUp.h"
#endif
@@ -5,7 +5,63 @@
#include "GL_Framebuffer.h"
namespace MG_GL::GL {
void GenFramebuffers(GLsizei n, GLuint* framebuffers) {
MG_Util::Debug::LogD("glGenFramebuffers, n: %d, framebuffers: %p", n, framebuffers);
GLenum result = MG_State::CreateFramebuffers(n, framebuffers);
if (result == GL_NO_ERROR) return;
MG_State::SetError(result);
MG_Util::Debug::LogE("Framebuffer generation failed: %s", MG_Util::Debug::GLEnumToString(result));
}
void DeleteFramebuffers(GLsizei n, const GLuint* framebuffers) {
MG_Util::Debug::LogD("glDeleteFramebuffers, n: %d, framebuffers: %p", n, framebuffers);
for (GLsizei i = 0; i < n; ++i) {
GLenum result = MG_State::DeleteFramebuffer(framebuffers[i]);
if (result != GL_NO_ERROR) {
MG_State::SetError(result);
MG_Util::Debug::LogE("Failed to delete framebuffer %u: %s",
framebuffers[i], MG_Util::Debug::GLEnumToString(result));
}
}
}
void BindFramebuffer(GLenum target, GLuint framebuffer) {
MG_Util::Debug::LogD("glBindFramebuffer, target: %s, fb: %u",
MG_Util::Debug::GLEnumToString(target), framebuffer);
GLenum result = MG_State::BindFramebuffer(target, framebuffer);
if (result == GL_NO_ERROR) return;
MG_State::SetError(result);
MG_Util::Debug::LogE("Framebuffer bind error: %s", MG_Util::Debug::GLEnumToString(result));
}
void FramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget,
GLuint texture, GLint level) {
MG_Util::Debug::LogD("glFramebufferTexture2D, target: %s, attach: %s, textarget: %s, tex: %u, level: %d",
MG_Util::Debug::GLEnumToString(target),
MG_Util::Debug::GLEnumToString(attachment),
MG_Util::Debug::GLEnumToString(textarget),
texture, level);
GLenum result = MG_State::AttachTexture2DToFramebuffer(
target, attachment, textarget, texture, level
);
if (result == GL_NO_ERROR) return;
MG_State::SetError(result);
MG_Util::Debug::LogE("Texture attachment failed: %s", MG_Util::Debug::GLEnumToString(result));
}
GLenum CheckFramebufferStatus(GLenum target) {
return GL_FRAMEBUFFER_COMPLETE;
MG_Util::Debug::LogD("glCheckFramebufferStatus, target: %s",
MG_Util::Debug::GLEnumToString(target));
GLenum result = MG_State::ValidateFramebufferCompleteness(target);
if (result >= GL_FRAMEBUFFER_COMPLETE) {
MG_Util::Debug::LogD("Framebuffer status: %s",
MG_Util::Debug::GLEnumToString(result));
return result;
}
MG_State::SetError(result);
MG_Util::Debug::LogE("Framebuffer incomplete: %s",
MG_Util::Debug::GLEnumToString(result));
return result;
}
}
@@ -8,6 +8,10 @@
#include "../../../../Includes.h"
namespace MG_GL::GL {
void GenFramebuffers(GLsizei n, GLuint* framebuffers);
void DeleteFramebuffers(GLsizei n, const GLuint* framebuffers);
void BindFramebuffer(GLenum target, GLuint framebuffer);
void FramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
GLenum CheckFramebufferStatus(GLenum target);
}
@@ -37,7 +37,7 @@ DECLARE_GL_FUNCTION_HEAD(void, ActiveTexture, GLenum texture) DECLARE_GL_FUNCTIO
DECLARE_GL_FUNCTION_HEAD(void, AttachShader, GLuint program, GLuint shader) DECLARE_GL_FUNCTION_END_NO_RETURN(void, AttachShader, program,shader)
DECLARE_GL_FUNCTION_HEAD(void, BindAttribLocation, GLuint program, GLuint index, const GLchar *name) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindAttribLocation, program,index,name)
DECLARE_GL_FUNCTION_HEAD(void, BindBuffer, GLenum target, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindBuffer, target,buffer)
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindFramebuffer, GLenum target, GLuint framebuffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindFramebuffer, target,framebuffer)
DECLARE_GL_FUNCTION_HEAD(void, BindFramebuffer, GLenum target, GLuint framebuffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindFramebuffer, target,framebuffer)
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindRenderbuffer, GLenum target, GLuint renderbuffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindRenderbuffer, target,renderbuffer)
DECLARE_GL_FUNCTION_HEAD(void, BindTexture, GLenum target, GLuint texture) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindTexture, target,texture)
DECLARE_GL_FUNCTION_STUB_HEAD(void, BlendColor, GLfloat red, GLfloat green, GLfloat blue, GLfloat alpha) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BlendColor, red,green,blue,alpha)
@@ -62,7 +62,7 @@ DECLARE_GL_FUNCTION_HEAD(GLuint, CreateProgram) DECLARE_GL_FUNCTION_END(GLuint,
DECLARE_GL_FUNCTION_HEAD(GLuint, CreateShader, GLenum type) DECLARE_GL_FUNCTION_END(GLuint, CreateShader, type)
DECLARE_GL_FUNCTION_STUB_HEAD(void, CullFace, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, CullFace, mode)
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteBuffers, GLsizei n, const GLuint *buffers) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteBuffers, n,buffers)
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteFramebuffers, GLsizei n, const GLuint *framebuffers) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteFramebuffers, n,framebuffers)
DECLARE_GL_FUNCTION_HEAD(void, DeleteFramebuffers, GLsizei n, const GLuint *framebuffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DeleteFramebuffers, n,framebuffers)
DECLARE_GL_FUNCTION_HEAD(void, DeleteProgram, GLuint program) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DeleteProgram, program)
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteRenderbuffers, GLsizei n, const GLuint *renderbuffers) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteRenderbuffers, n,renderbuffers)
DECLARE_GL_FUNCTION_HEAD(void, DeleteShader, GLuint shader) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DeleteShader, shader)
@@ -80,11 +80,11 @@ DECLARE_GL_FUNCTION_HEAD(void, EnableVertexAttribArray, GLuint index) DECLARE_GL
DECLARE_GL_FUNCTION_STUB_HEAD(void, Finish) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Finish)
DECLARE_GL_FUNCTION_STUB_HEAD(void, Flush) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, Flush)
DECLARE_GL_FUNCTION_STUB_HEAD(void, FramebufferRenderbuffer, GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FramebufferRenderbuffer, target,attachment,renderbuffertarget,renderbuffer)
DECLARE_GL_FUNCTION_STUB_HEAD(void, FramebufferTexture2D, GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FramebufferTexture2D, target,attachment,textarget,texture,level)
DECLARE_GL_FUNCTION_HEAD(void, FramebufferTexture2D, GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) DECLARE_GL_FUNCTION_END_NO_RETURN(void, FramebufferTexture2D, target,attachment,textarget,texture,level)
DECLARE_GL_FUNCTION_STUB_HEAD(void, FrontFace, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FrontFace, mode)
DECLARE_GL_FUNCTION_HEAD(void, GenBuffers, GLsizei n, GLuint *buffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenBuffers, n,buffers)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenerateMipmap, GLenum target) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenerateMipmap, target)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenFramebuffers, GLsizei n, GLuint *framebuffers) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenFramebuffers, n,framebuffers)
DECLARE_GL_FUNCTION_HEAD(void, GenFramebuffers, GLsizei n, GLuint *framebuffers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenFramebuffers, n,framebuffers)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenRenderbuffers, GLsizei n, GLuint *renderbuffers) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenRenderbuffers, n,renderbuffers)
DECLARE_GL_FUNCTION_HEAD(void, GenTextures, GLsizei n, GLuint *textures) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GenTextures, n,textures)
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetActiveAttrib, GLuint program, GLuint index, GLsizei bufSize, GLsizei *length, GLint *size, GLenum *type, GLchar *name) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetActiveAttrib, program,index,bufSize,length,size,type,name)
+30
View File
@@ -10,6 +10,7 @@ CommonState* MG_State_T::commonState = nullptr;
BufferState* MG_State_T::bufferState = nullptr;
VertexArrayState* MG_State_T::vertexArrayState = nullptr;
ProgramState* MG_State_T::programState = nullptr;
FramebufferState* MG_State_T::framebufferState = nullptr;
namespace MG_State {
void Init() {
@@ -18,6 +19,7 @@ namespace MG_State {
MG_State_T::bufferState = new BufferState();
MG_State_T::vertexArrayState = new VertexArrayState();
MG_State_T::programState = new ProgramState();
MG_State_T::framebufferState = new FramebufferState();
}
void Destroy() {
@@ -26,6 +28,7 @@ namespace MG_State {
delete MG_State_T::bufferState;
delete MG_State_T::vertexArrayState;
delete MG_State_T::programState;
delete MG_State_T::framebufferState;
}
void SetError(GLenum error) {
@@ -300,4 +303,31 @@ void UpdateProgramUniformMatrix##suffix##Vector(GLint location, GLsizei count, G
bool SetShaderStatus(GLuint shader, GLboolean status) {
return MG_State_T::programState->SetShaderStatus(shader, status);
}
// Framebuffer
GLenum MG_State::CreateFramebuffer(GLuint* framebuffer) {
return MG_State_T::framebufferState->Create(framebuffer);
}
GLenum MG_State::CreateFramebuffers(GLsizei n, GLuint* framebuffers) {
return MG_State_T::framebufferState->CreateN(n, framebuffers);
}
GLenum MG_State::DeleteFramebuffer(GLuint framebuffer) {
return MG_State_T::framebufferState->Delete(framebuffer);
}
GLenum MG_State::BindFramebuffer(GLenum target, GLuint framebuffer) {
return MG_State_T::framebufferState->Bind(target, framebuffer);
}
GLenum MG_State::AttachTexture2DToFramebuffer(GLenum target, GLenum attachment, GLenum textarget,
GLuint texture, GLint level) {
return MG_State_T::framebufferState->glAttachTexture2D(target, attachment, textarget,
texture, level);
}
GLenum MG_State::ValidateFramebufferCompleteness(GLenum target) {
return MG_State_T::framebufferState->glValidateCompleteness(target);
}
}
+10
View File
@@ -14,6 +14,7 @@ struct MG_State_T {
static BufferState* bufferState;
static VertexArrayState* vertexArrayState;
static ProgramState* programState;
static FramebufferState* framebufferState;
};
namespace MG_State {
@@ -117,6 +118,15 @@ namespace MG_State {
GLuint GetCurrentProgram();
bool SetProgramStatus(GLuint program, GLboolean status);
bool SetShaderStatus(GLuint shader, GLboolean status);
// Framebuffer
GLenum CreateFramebuffer(GLuint* framebuffer);
GLenum CreateFramebuffers(GLsizei n, GLuint* framebuffers);
GLenum DeleteFramebuffer(GLuint framebuffer);
GLenum BindFramebuffer(GLenum target, GLuint framebuffer);
GLenum AttachTexture2DToFramebuffer(GLenum target, GLenum attachment, GLenum textarget,
GLuint texture, GLint level);
GLenum ValidateFramebufferCompleteness(GLenum target);
}
@@ -0,0 +1,163 @@
//
// Created by BZLZHH on 2025/5/3.
//
// TODO: Add more gl error check for framebuffer state manager.
// TODO: Check if the state machine really meets up to OpenGL 3 standard.
#include "FramebufferState.h"
// Re-Include Includes.h, cuz of the absence of GLState.h
#undef MOBILEGL_GLSTATE_H
#include "../../../Includes.h"
GLenum FramebufferState::Create(GLuint* framebuffer) {
if (!framebuffer) return GL_INVALID_VALUE;
GLuint id = freeIds_.empty() ? ++lastId_ : *freeIds_.begin();
if (!freeIds_.empty()) freeIds_.erase(freeIds_.begin());
*framebuffer = id;
framebuffers_[id].generated = true;
return GL_NO_ERROR;
}
GLenum FramebufferState::CreateN(GLsizei n, GLuint* framebuffers) {
if (n < 0) return GL_INVALID_VALUE;
for (GLsizei i = 0; i < n; ++i) {
if (GLenum err = Create(&framebuffers[i])) return err;
}
return GL_NO_ERROR;
}
GLenum FramebufferState::Delete(GLuint framebuffer) {
if (framebuffer == 0) return GL_INVALID_VALUE;
if (framebuffers_.erase(framebuffer)) {
freeIds_.insert(framebuffer);
for (auto& [target, id] : currentBindings_) {
if (id == framebuffer) id = 0;
}
}
return GL_NO_ERROR;
}
GLenum FramebufferState::Bind(GLenum target, GLuint framebuffer) {
if (MG_Constants::Framebuffer::VALID_TARGETS.find(target) == MG_Constants::Framebuffer::VALID_TARGETS.end())
return GL_INVALID_ENUM;
if (framebuffer != 0 && !ValidateHandle(framebuffer))
return GL_INVALID_OPERATION;
if (target == GL_FRAMEBUFFER) {
currentBindings_[GL_READ_FRAMEBUFFER] = framebuffer;
currentBindings_[GL_DRAW_FRAMEBUFFER] = framebuffer;
currentBindings_[GL_FRAMEBUFFER] = framebuffer;
} else {
currentBindings_[target] = framebuffer;
}
return GL_NO_ERROR;
}
GLenum FramebufferState::glAttachTexture2D(GLenum target, GLenum attachment,
GLenum textarget, GLuint texture, GLint level) {
if (MG_Constants::Framebuffer::VALID_ATTACHMENTS.find(attachment) == MG_Constants::Framebuffer::VALID_ATTACHMENTS.end() ||
MG_Constants::Texture::VALID_TARGETS.find(textarget) == MG_Constants::Texture::VALID_TARGETS.end())
return GL_INVALID_ENUM;
FramebufferObject* fbo = GetCurrentFBO(target);
if (!fbo) return GL_INVALID_OPERATION;
if (texture != 0 && !MG_State_T::textureState->IsTexture(texture))
return GL_INVALID_VALUE;
FramebufferAttachment att;
att.type = GL_TEXTURE_2D;
att.handle = texture;
att.mipLevel = level;
fbo->attachments[attachment] = att;
if (texture != 0) {
GLint params = 0;
MG_State_T::textureState->QueryLevelPropertyIntVector(
textarget, level, GL_TEXTURE_WIDTH, &params);
fbo->width = params;
MG_State_T::textureState->QueryLevelPropertyIntVector(
textarget, level, GL_TEXTURE_HEIGHT, &params);
fbo->height = params;
}
UpdateCompleteness(*fbo);
return GL_NO_ERROR;
}
GLenum FramebufferState::glValidateCompleteness(GLenum target) {
FramebufferObject* fbo = GetCurrentFBO(target);
if (!fbo) return GL_INVALID_OPERATION;
return fbo->status;
}
void FramebufferState::UpdateCompleteness(FramebufferObject& fbo) {
if (fbo.attachments.empty()) {
fbo.status = GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
return;
}
bool hasColor = false;
bool hasDepth = false;
bool hasStencil = false;
GLsizei width = 0, height = 0;
for (const auto& [attach, att] : fbo.attachments) {
GLsizei attWidth = 0, attHeight = 0;
if (att.handle != 0) {
GLint params = 0;
MG_State_T::textureState->QueryLevelPropertyIntVector(
GL_TEXTURE_2D, att.mipLevel, GL_TEXTURE_WIDTH, &params);
attWidth = params;
MG_State_T::textureState->QueryLevelPropertyIntVector(
GL_TEXTURE_2D, att.mipLevel, GL_TEXTURE_HEIGHT, &params);
attHeight = params;
}
if (width == 0) {
width = attWidth;
height = attHeight;
} else if (attWidth != width || attHeight != height) {
fbo.status = GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS;
return;
}
if (attach == GL_DEPTH_ATTACHMENT) hasDepth = true;
if (attach == GL_STENCIL_ATTACHMENT) hasStencil = true;
if (attach >= GL_COLOR_ATTACHMENT0 &&
attach <= GL_COLOR_ATTACHMENT31) hasColor = true;
}
if (!ValidateAttachmentCombination(fbo)) {
fbo.status = GL_FRAMEBUFFER_UNSUPPORTED;
return;
}
fbo.status = GL_FRAMEBUFFER_COMPLETE;
}
bool FramebufferState::ValidateAttachmentCombination(const FramebufferObject& fbo) {
bool depthStencilConflict = false;
for (const auto& [attach, att] : fbo.attachments) {
if (attach == GL_DEPTH_STENCIL_ATTACHMENT) {
GLint internalFormat = 0;
MG_State_T::textureState->QueryLevelPropertyIntVector(
GL_TEXTURE_2D, att.mipLevel, GL_TEXTURE_INTERNAL_FORMAT, &internalFormat);
if (internalFormat != GL_DEPTH24_STENCIL8 &&
internalFormat != GL_DEPTH32F_STENCIL8) {
return false;
}
}
}
return true;
}
FramebufferObject* FramebufferState::GetCurrentFBO(GLenum target) {
auto it = currentBindings_.find(target);
if (it == currentBindings_.end()) return nullptr;
GLuint id = it->second;
if (id == 0) return nullptr;
auto fboIt = framebuffers_.find(id);
return (fboIt != framebuffers_.end()) ? &fboIt->second : nullptr;
}
bool FramebufferState::ValidateHandle(GLuint framebuffer) {
return framebuffers_.count(framebuffer) &&
framebuffers_[framebuffer].generated;
}
@@ -0,0 +1,48 @@
//
// Created by BZLZHH on 2025/5/3.
//
#ifndef MOBILEGL_FRAMEBUFFERSTATE_H
#define MOBILEGL_FRAMEBUFFERSTATE_H
#define MOBILEGL_GLSTATE_H
#include "../../../Includes.h"
struct FramebufferAttachment {
GLenum type = GL_NONE;
GLuint handle = 0;
GLint mipLevel = 0;
GLint zoffset = 0;
};
struct FramebufferObject {
bool generated = false;
std::unordered_map<GLenum, FramebufferAttachment> attachments;
GLenum status = GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
GLsizei width = 0;
GLsizei height = 0;
};
class FramebufferState {
public:
// Return: the validity of the operation
GLenum Create(GLuint* framebuffer);
GLenum CreateN(GLsizei n, GLuint* framebuffers);
GLenum Delete(GLuint framebuffer);
GLenum Bind(GLenum target, GLuint framebuffer);
GLenum glAttachTexture2D(GLenum target, GLenum attachment,
GLenum textarget, GLuint texture, GLint level);
GLenum glValidateCompleteness(GLenum target);
FramebufferObject* GetCurrentFBO(GLenum target);
private:
std::unordered_map<GLuint, FramebufferObject> framebuffers_;
std::unordered_map<GLenum, GLuint> currentBindings_; // READ/DRAW/FRAMEBUFFER
std::set<GLuint> freeIds_;
GLuint lastId_ = 0;
bool ValidateHandle(GLuint framebuffer);
static void UpdateCompleteness(FramebufferObject& fbo);
static bool ValidateAttachmentCombination(const FramebufferObject& fbo);
};
#endif //MOBILEGL_FRAMEBUFFERSTATE_H