mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
479 lines
23 KiB
C++
479 lines
23 KiB
C++
#include "Managers.h"
|
|
#include "Utils.h"
|
|
#include "DirectGLES.h"
|
|
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
|
|
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
|
#include <MG_Util/Converters/MGToGL/DataTypeConverter.h>
|
|
#include <MG_Util/Converters/MGToGL/BufferEnumConverter.h>
|
|
#include <MG_Util/Converters/MGToGL/TextureEnumConverter.h>
|
|
#include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.h>
|
|
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
|
|
|
|
namespace MobileGL::MG_Backend::DirectGLES {
|
|
namespace BufferImpl {
|
|
BackendBufferObject::BackendBufferObject() {
|
|
MG_External::GLES::glGenBuffers(1, &m_backendBufferId);
|
|
if (m_backendBufferId == 0) {
|
|
MGLOG_E("Failed to generate buffer object.");
|
|
} else {
|
|
MGLOG_D("Generated buffer object with ID: %u.", m_backendBufferId);
|
|
}
|
|
}
|
|
|
|
const GLenum TempBufferTarget = GL_ARRAY_BUFFER;
|
|
void BackendBufferObject::SyncToBackend(SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject) {
|
|
if (!stateBufferObject) {
|
|
MGLOG_E("State buffer object is null, cannot sync to backend.");
|
|
return;
|
|
}
|
|
|
|
SizeT bufferSize = stateBufferObject->GetSize();
|
|
if (bufferSize == 0) {
|
|
MGLOG_W("Buffer size is zero, skipping sync for object with ID: %u", m_backendBufferId);
|
|
return;
|
|
}
|
|
|
|
MGLOG_D("Syncing buffer object with ID: %u to backend for state: %s", m_backendBufferId,
|
|
stateBufferObject.get());
|
|
|
|
Bool needsRegeneration =
|
|
!m_isInitialized || bufferSize > m_prevBufferSize || bufferSize > m_prevBufferSize * 2;
|
|
|
|
if (needsRegeneration) {
|
|
MGLOG_D("Buffer size changed significantly or not initialized, regenerating buffer with ID: %u",
|
|
m_backendBufferId);
|
|
SyncToBackend_glBufferData(stateBufferObject);
|
|
m_isInitialized = true;
|
|
return;
|
|
}
|
|
|
|
switch (stateBufferObject->GetUsage()) {
|
|
case BufferUsage::StaticDraw:
|
|
SyncToBackend_glBufferSubData(stateBufferObject);
|
|
break;
|
|
case BufferUsage::DynamicDraw:
|
|
case BufferUsage::StreamDraw:
|
|
SyncToBackend_glMapBufferRange(stateBufferObject);
|
|
break;
|
|
default:
|
|
SyncToBackend_glBufferSubData(stateBufferObject);
|
|
break;
|
|
}
|
|
|
|
m_prevBufferSize = bufferSize;
|
|
}
|
|
|
|
void BackendBufferObject::SyncToBackend_glBufferData(
|
|
SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject) {
|
|
BackendBufferBindingProtector backendBufferBindingProtector(TempBufferTarget);
|
|
|
|
MGLOG_D("Syncing buffer data (glBufferData) for object with ID : %u", m_backendBufferId);
|
|
|
|
const void* data = stateBufferObject->GetDataReadOnly().get();
|
|
SizeT size = stateBufferObject->GetSize();
|
|
GLenum usage = MG_Util::ConvertBufferUsageToGLEnum(stateBufferObject->GetUsage());
|
|
|
|
MG_External::GLES::glBindBuffer(TempBufferTarget, m_backendBufferId);
|
|
MG_External::GLES::glBufferData(TempBufferTarget, size, data, usage);
|
|
|
|
stateBufferObject->ClearDirty();
|
|
}
|
|
|
|
void BackendBufferObject::SyncToBackend_glBufferSubData(
|
|
SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject) {
|
|
BackendBufferBindingProtector backendBufferBindingProtector(TempBufferTarget);
|
|
|
|
MGLOG_D("Syncing buffer sub-data (glBufferSubData) for object with ID : %u", m_backendBufferId);
|
|
|
|
const void* data = stateBufferObject->GetDataReadOnly().get();
|
|
auto range = stateBufferObject->GetDirtyRange();
|
|
// dirty range: [range.start, range.end)
|
|
|
|
MG_External::GLES::glBindBuffer(TempBufferTarget, m_backendBufferId);
|
|
MG_External::GLES::glBufferSubData(TempBufferTarget, range.start, range.end - range.start,
|
|
reinterpret_cast<const char*>(data) + range.start);
|
|
}
|
|
|
|
void BackendBufferObject::SyncToBackend_glMapBufferRange(
|
|
SharedPtr<MG_State::GLState::BufferObject>& stateBufferObject, Bool invalidate) {
|
|
BackendBufferBindingProtector backendBufferBindingProtector(TempBufferTarget);
|
|
|
|
MGLOG_D("Syncing buffer map (glMapBuffer) for object with ID : %u", m_backendBufferId);
|
|
MGLOG_D("Mapping buffer with ID: %u", m_backendBufferId);
|
|
MG_External::GLES::glBindBuffer(TempBufferTarget, m_backendBufferId);
|
|
auto range = stateBufferObject->GetDirtyRange();
|
|
void* mappedData = MG_External::GLES::glMapBufferRange(
|
|
TempBufferTarget, range.start, range.end - range.start,
|
|
(invalidate ? GL_MAP_INVALIDATE_BUFFER_BIT : 0) | GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
|
|
const void* data = stateBufferObject->GetDataReadOnly().get();
|
|
if (mappedData) {
|
|
Memcpy(reinterpret_cast<const void*>(reinterpret_cast<const char*>(data) + range.start), mappedData,
|
|
range.end - range.start);
|
|
MGLOG_D("Mapped buffer data successfully for object with ID: %u", m_backendBufferId);
|
|
MG_External::GLES::glUnmapBuffer(TempBufferTarget);
|
|
} else {
|
|
MGLOG_E("Failed to map buffer with ID: %u", m_backendBufferId);
|
|
}
|
|
}
|
|
|
|
void BackendBufferObject::Bind() {
|
|
MG_External::GLES::glBindBuffer(TempBufferTarget, m_backendBufferId);
|
|
}
|
|
|
|
void BackendBufferObject::Bind(GLenum target) {
|
|
MG_External::GLES::glBindBuffer(target, m_backendBufferId);
|
|
}
|
|
|
|
UnorderedMap<SharedPtr<MG_State::GLState::BufferObject>, SharedPtr<BackendBufferObject>> g_backendBufferObjects;
|
|
} // namespace BufferImpl
|
|
|
|
namespace VertexArrayImpl {
|
|
BackendVertexArrayObject::BackendVertexArrayObject() {
|
|
MG_External::GLES::glGenVertexArrays(1, &m_backendVAOId);
|
|
if (m_backendVAOId == 0) {
|
|
MGLOG_E("Failed to generate vertex array object.");
|
|
} else {
|
|
MGLOG_D("Generated vertex array object with ID: %u.", m_backendVAOId);
|
|
}
|
|
}
|
|
|
|
void BackendVertexArrayObject::Bind() {
|
|
MG_External::GLES::glBindVertexArray(m_backendVAOId);
|
|
}
|
|
|
|
void BackendVertexArrayObject::SyncToBackend(SharedPtr<MG_State::GLState::VertexArrayObject>& stateVAOObject) {
|
|
if (!stateVAOObject) {
|
|
MGLOG_E("State VAO object is null, cannot sync to backend.");
|
|
return;
|
|
}
|
|
|
|
MGLOG_D("Syncing VAO object with ID: %u to backend for state: %s", m_backendVAOId, stateVAOObject.get());
|
|
|
|
BufferImpl::BackendBufferBindingProtector backendBufferBindingProtector(BufferImpl::TempBufferTarget);
|
|
BackendVertexArrayBindingProtector backendVAOBindingProtector;
|
|
|
|
Bind();
|
|
|
|
for (const auto& attribIndex : stateVAOObject->GetDirtyAttributeIndices()) {
|
|
const auto& attrib = stateVAOObject->GetAttribute(attribIndex);
|
|
|
|
auto bufferObject = attrib.Buffer;
|
|
if (!bufferObject) {
|
|
MGLOG_W("Attribute has no bound buffer, skipping.");
|
|
continue;
|
|
}
|
|
|
|
auto backendBufferIt = BufferImpl::g_backendBufferObjects.find(bufferObject);
|
|
if (backendBufferIt == BufferImpl::g_backendBufferObjects.end()) {
|
|
MGLOG_E("No backend buffer found for attribute's buffer, cannot bind attribute.");
|
|
continue;
|
|
}
|
|
auto backendBufferObject = backendBufferIt->second;
|
|
|
|
backendBufferObject->Bind(GL_ARRAY_BUFFER);
|
|
MG_External::GLES::glEnableVertexAttribArray(attribIndex);
|
|
MG_External::GLES::glVertexAttribPointer(
|
|
attribIndex, attrib.Size, MG_Util::ConvertDataTypeToGLEnum(attrib.Type),
|
|
attrib.Normalized ? GL_TRUE : GL_FALSE, attrib.Stride,
|
|
reinterpret_cast<const void*>(static_cast<SizeT>(attrib.Offset)));
|
|
|
|
// TODO: divisor
|
|
}
|
|
|
|
auto indexBufferBinding = stateVAOObject->GetIndexBufferBindingSlot().GetBoundObject();
|
|
if (indexBufferBinding) {
|
|
auto backendBufferIt = BufferImpl::g_backendBufferObjects.find(indexBufferBinding);
|
|
if (backendBufferIt != BufferImpl::g_backendBufferObjects.end()) {
|
|
auto backendBufferObject = backendBufferIt->second;
|
|
backendBufferObject->Bind(GL_ELEMENT_ARRAY_BUFFER);
|
|
} else {
|
|
MGLOG_E("No backend buffer found for index buffer binding, cannot bind index buffer.");
|
|
}
|
|
}
|
|
|
|
stateVAOObject->ClearDirtyAttributes();
|
|
}
|
|
|
|
UnorderedMap<SharedPtr<MG_State::GLState::VertexArrayObject>, SharedPtr<BackendVertexArrayObject>>
|
|
g_backendVertexArrayObjects;
|
|
} // namespace VertexArrayImpl
|
|
|
|
namespace TextureImpl {
|
|
BackendTextureObject::BackendTextureObject() {
|
|
MG_External::GLES::glGenTextures(1, &m_backendTextureId);
|
|
if (m_backendTextureId == 0) {
|
|
MGLOG_E("Failed to generate texture object.");
|
|
} else {
|
|
MGLOG_D("Generated texture object with ID: %u.", m_backendTextureId);
|
|
}
|
|
}
|
|
|
|
void BackendTextureObject::Bind(GLenum target) {
|
|
MG_External::GLES::glBindTexture(target, m_backendTextureId);
|
|
}
|
|
|
|
Uint BackendTextureObject::GetBackendTextureId() {
|
|
return m_backendTextureId;
|
|
}
|
|
|
|
void BackendTextureObject::SyncToBackend(SharedPtr<MG_State::GLState::ITextureObject>& stateTextureObject) {
|
|
if (!stateTextureObject) {
|
|
MGLOG_E("State texture object is null, cannot sync to backend.");
|
|
return;
|
|
}
|
|
|
|
MGLOG_D("Syncing texture object with ID: %u to backend for state: %s", m_backendTextureId,
|
|
stateTextureObject.get());
|
|
|
|
GLenum target = MG_Util::ConvertTextureTargetToGLEnum(stateTextureObject->GetTarget());
|
|
|
|
// The texture needs to be regenerated completely with glTexImage* calls if:
|
|
// 1. Not initialized
|
|
// 2. InternalFormat changed
|
|
// 3. Size changed
|
|
// 4. Mipmap levels changed
|
|
|
|
if (!stateTextureObject->IsComplete()) {
|
|
MGLOG_E("Texture object with ID: %u is not complete, skipping sync.", m_backendTextureId);
|
|
return;
|
|
}
|
|
|
|
BackendTextureBindingProtector backendTextureBindingProtector(target);
|
|
Bind(target);
|
|
|
|
StateTextureBasicInfo currentTextureInfo = {
|
|
stateTextureObject->GetFormat(), static_cast<SizeT>(stateTextureObject->GetBaseSize().x()),
|
|
static_cast<SizeT>(stateTextureObject->GetBaseSize().y()),
|
|
static_cast<SizeT>(stateTextureObject->GetBaseSize().z()), stateTextureObject->GetMipmaps().size()};
|
|
|
|
Bool needsRegeneration = !m_isInitialized || (currentTextureInfo != m_prevTextureInfo);
|
|
|
|
if (needsRegeneration) {
|
|
MGLOG_D("Texture state changed significantly or not initialized, regenerating texture with ID: %u",
|
|
m_backendTextureId);
|
|
|
|
// Regenerate all mipmap levels
|
|
const auto& mipmaps = stateTextureObject->GetMipmaps();
|
|
GLenum glInternalFormat, glType, glFormat;
|
|
TextureImpl::GenerateTextureFormatInfo(stateTextureObject->GetFormat(), &glInternalFormat, &glType,
|
|
&glFormat);
|
|
for (SizeT level = 0; level < mipmaps.size(); ++level) {
|
|
const auto& mipmap = mipmaps[level];
|
|
|
|
MG_External::GLES::glTexImage2D(GL_TEXTURE_2D, static_cast<GLint>(level), glInternalFormat,
|
|
static_cast<GLsizei>(mipmap.size.x()),
|
|
static_cast<GLsizei>(mipmap.size.y()), 0, glFormat, glType,
|
|
mipmap.data.data());
|
|
|
|
MGLOG_D("Regenerated mipmap level %d for texture with ID: %u", level, m_backendTextureId);
|
|
stateTextureObject->UnmarkMipmapDirty(level);
|
|
}
|
|
|
|
m_isInitialized = true;
|
|
}
|
|
|
|
{ // Update sampler parameters; TODO: always use sampler objects in backend
|
|
|
|
auto samplerObject = stateTextureObject->GetSamplerObject();
|
|
if (samplerObject) {
|
|
MG_External::GLES::glTexParameteri(
|
|
target, GL_TEXTURE_MIN_FILTER,
|
|
MG_Util::ConvertSamplerFilterModeToGLEnum(samplerObject->GetMinFilter()));
|
|
MG_External::GLES::glTexParameteri(
|
|
target, GL_TEXTURE_MAG_FILTER,
|
|
MG_Util::ConvertSamplerFilterModeToGLEnum(samplerObject->GetMagFilter()));
|
|
MG_External::GLES::glTexParameteri(
|
|
target, GL_TEXTURE_WRAP_S, MG_Util::ConvertSamplerWrapModeToGLEnum(samplerObject->GetWrapS()));
|
|
MG_External::GLES::glTexParameteri(
|
|
target, GL_TEXTURE_WRAP_T, MG_Util::ConvertSamplerWrapModeToGLEnum(samplerObject->GetWrapT()));
|
|
MG_External::GLES::glTexParameteri(
|
|
target, GL_TEXTURE_WRAP_R, MG_Util::ConvertSamplerWrapModeToGLEnum(samplerObject->GetWrapR()));
|
|
}
|
|
}
|
|
|
|
{ // Update all dirty mipmap levels
|
|
const auto& mipmaps = stateTextureObject->GetMipmaps();
|
|
GLenum glInternalFormat, glType, glFormat;
|
|
TextureImpl::GenerateTextureFormatInfo(stateTextureObject->GetFormat(), &glInternalFormat, &glType,
|
|
&glFormat);
|
|
for (const auto& mipmap : stateTextureObject->GetMipmaps()) {
|
|
if (!mipmap.dirty) {
|
|
continue;
|
|
}
|
|
|
|
if (mipmap.data.empty()) {
|
|
MGLOG_W("Mipmap level %d has no data, skipping update.", mipmap.level);
|
|
continue;
|
|
}
|
|
|
|
MG_External::GLES::glTexSubImage2D(
|
|
GL_TEXTURE_2D, static_cast<GLint>(mipmap.level), 0, 0, static_cast<GLsizei>(mipmap.size.x()),
|
|
static_cast<GLsizei>(mipmap.size.y()), glFormat, glType, mipmap.data.data());
|
|
stateTextureObject->UnmarkMipmapDirty(mipmap.level);
|
|
}
|
|
}
|
|
|
|
m_prevTextureInfo = currentTextureInfo;
|
|
}
|
|
|
|
UnorderedMap<SharedPtr<MG_State::GLState::ITextureObject>, SharedPtr<BackendTextureObject>>
|
|
g_backendTextureObjects;
|
|
} // namespace TextureImpl
|
|
|
|
namespace FramebufferImpl {
|
|
BackendFramebufferObject::BackendFramebufferObject() {
|
|
MG_External::GLES::glGenFramebuffers(1, &m_backendFBOId);
|
|
if (m_backendFBOId == 0) {
|
|
MGLOG_E("Failed to generate framebuffer object.");
|
|
} else {
|
|
MGLOG_D("Generated framebuffer object with ID: %u.", m_backendFBOId);
|
|
}
|
|
}
|
|
|
|
void BackendFramebufferObject::Bind() {
|
|
MG_External::GLES::glBindFramebuffer(GL_FRAMEBUFFER, m_backendFBOId);
|
|
}
|
|
|
|
void BackendFramebufferObject::SyncToBackend(SharedPtr<MG_State::GLState::FramebufferObject>& stateFBOObject) {
|
|
if (!stateFBOObject) {
|
|
MGLOG_E("State FBO object is null, cannot sync to backend.");
|
|
return;
|
|
}
|
|
|
|
MGLOG_D("Syncing FBO object with ID: %u to backend for state: %s", m_backendFBOId, stateFBOObject.get());
|
|
|
|
BackendFramebufferBindingProtector backendFBOBindingProtector(GL_FRAMEBUFFER);
|
|
Bind();
|
|
|
|
// TODO: add dirty check
|
|
// Sync all attachments
|
|
const auto& attachments = stateFBOObject->GetAllAttachments();
|
|
for (SizeT index = 0; index < attachments.size(); ++index) {
|
|
FramebufferAttachmentType attachmentType = static_cast<FramebufferAttachmentType>(index);
|
|
const auto& attachment = attachments[index];
|
|
if (!attachment.IsComplete()) {
|
|
MG_External::GLES::glFramebufferRenderbuffer(
|
|
GL_FRAMEBUFFER, MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(attachmentType),
|
|
GL_RENDERBUFFER, 0);
|
|
continue;
|
|
}
|
|
|
|
if (attachment.IsTexture()) {
|
|
auto textureObject = attachment.GetTexture();
|
|
auto backendTextureIt = TextureImpl::g_backendTextureObjects.find(textureObject);
|
|
if (backendTextureIt == TextureImpl::g_backendTextureObjects.end()) {
|
|
MGLOG_E("No backend texture found for FBO attachment, cannot bind texture.");
|
|
continue;
|
|
}
|
|
auto backendTextureObject = backendTextureIt->second;
|
|
backendTextureObject->Bind(MG_Util::ConvertTextureTargetToGLEnum(textureObject->GetTarget()));
|
|
MG_External::GLES::glFramebufferTexture2D(
|
|
GL_FRAMEBUFFER, MG_Util::ConvertFramebufferAttachmentTypeToGLEnum(attachmentType),
|
|
MG_Util::ConvertTextureTargetToGLEnum(textureObject->GetTarget()),
|
|
backendTextureObject->GetBackendTextureId(), static_cast<GLint>(attachment.GetTextureLevel()));
|
|
} else if (attachment.IsRenderbuffer()) {
|
|
// TODO
|
|
}
|
|
}
|
|
}
|
|
|
|
UnorderedMap<SharedPtr<MG_State::GLState::FramebufferObject>, SharedPtr<BackendFramebufferObject>>
|
|
g_backendFramebufferObjects;
|
|
} // namespace FramebufferImpl
|
|
|
|
namespace PrgramImpl {
|
|
UnorderedMap<SharedPtr<MG_State::GLState::ShaderObject>, SharedPtr<BackendShaderObjectImpl>>
|
|
g_backendShaderObjects;
|
|
UnorderedMap<SharedPtr<MG_State::GLState::ProgramObject>, SharedPtr<BackendProgramObjectImpl>>
|
|
g_backendProgramObjects;
|
|
BackendShaderObjectImpl::BackendShaderObjectImpl() {
|
|
m_backendShaderId = MG_External::GLES::glCreateShader(GL_VERTEX_SHADER);
|
|
if (m_backendShaderId == 0) {
|
|
MGLOG_E("Failed to create shader object in backend.");
|
|
}
|
|
}
|
|
BackendShaderObjectImpl::~BackendShaderObjectImpl() {
|
|
if (m_backendShaderId != 0) {
|
|
MG_External::GLES::glDeleteShader(m_backendShaderId);
|
|
}
|
|
}
|
|
void BackendShaderObjectImpl::SyncToBackend(SharedPtr<MG_State::GLState::ShaderObject>& stateShaderObject) {
|
|
if (!stateShaderObject || !stateShaderObject->GetCompileStatus()) {
|
|
MGLOG_E("Shader object is null or not compiled, skipping backend sync.");
|
|
return;
|
|
}
|
|
String source = stateShaderObject->GetShaderSource();
|
|
source = removeLayoutBinding(source);
|
|
source = ProcessOutColorLocations(source);
|
|
source = ForceSupporterOutput(source);
|
|
GLenum shaderType = ConvertGLShaderTypeByMGLShaderStage(stateShaderObject->GetShaderStage());
|
|
const char* sourceCStr = source.c_str();
|
|
|
|
MG_External::GLES::glShaderSource(m_backendShaderId, 1, &sourceCStr, nullptr);
|
|
MG_External::GLES::glCompileShader(m_backendShaderId);
|
|
GLint compileStatus;
|
|
MG_External::GLES::glGetShaderiv(m_backendShaderId, GL_COMPILE_STATUS, &compileStatus);
|
|
if (compileStatus != GL_TRUE) {
|
|
GLint logLength;
|
|
MG_External::GLES::glGetShaderiv(m_backendShaderId, GL_INFO_LOG_LENGTH, &logLength);
|
|
Vector<GLchar> log(logLength);
|
|
MG_External::GLES::glGetShaderInfoLog(m_backendShaderId, logLength, nullptr, log.data());
|
|
MGLOG_E("Shader compilation failed: %s", log.data());
|
|
}
|
|
m_isInitialized = true;
|
|
}
|
|
BackendProgramObjectImpl::BackendProgramObjectImpl() {
|
|
m_backendProgramId = MG_External::GLES::glCreateProgram();
|
|
if (m_backendProgramId == 0) {
|
|
MGLOG_E("Failed to create program object in backend.");
|
|
}
|
|
}
|
|
BackendProgramObjectImpl::~BackendProgramObjectImpl() {
|
|
if (m_backendProgramId != 0) {
|
|
MG_External::GLES::glDeleteProgram(m_backendProgramId);
|
|
}
|
|
}
|
|
void BackendProgramObjectImpl::SyncToBackend(SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject) {
|
|
if (!stateProgramObject || !stateProgramObject->GetLinkStatus()) {
|
|
MGLOG_E("Program object is null or not linked, skipping backend sync.");
|
|
return;
|
|
}
|
|
// Detach all existing shaders
|
|
GLint attachedCount = 0;
|
|
MG_External::GLES::glGetProgramiv(m_backendProgramId, GL_ATTACHED_SHADERS, &attachedCount);
|
|
if (attachedCount > 0) {
|
|
Vector<GLuint> attachedShaders(attachedCount);
|
|
GLsizei actualCount;
|
|
MG_External::GLES::glGetAttachedShaders(m_backendProgramId, attachedCount, &actualCount,
|
|
attachedShaders.data());
|
|
for (GLsizei i = 0; i < actualCount; ++i) {
|
|
MG_External::GLES::glDetachShader(m_backendProgramId, attachedShaders[i]);
|
|
}
|
|
}
|
|
// Attach current shaders
|
|
for (auto& shader : stateProgramObject->GetAttachedShaders()) {
|
|
auto it = g_backendShaderObjects.find(shader);
|
|
if (it != g_backendShaderObjects.end() && it->second) {
|
|
MG_External::GLES::glAttachShader(m_backendProgramId, it->second->GetBackendShaderId());
|
|
}
|
|
}
|
|
// Link program
|
|
MG_External::GLES::glLinkProgram(m_backendProgramId);
|
|
GLint linkStatus;
|
|
MG_External::GLES::glGetProgramiv(m_backendProgramId, GL_LINK_STATUS, &linkStatus);
|
|
if (linkStatus != GL_TRUE) {
|
|
GLint logLength;
|
|
MG_External::GLES::glGetProgramiv(m_backendProgramId, GL_INFO_LOG_LENGTH, &logLength);
|
|
Vector<GLchar> log(logLength);
|
|
MG_External::GLES::glGetProgramInfoLog(m_backendProgramId, logLength, nullptr, log.data());
|
|
MGLOG_E("Program linking failed: %s", log.data());
|
|
}
|
|
m_isInitialized = true;
|
|
}
|
|
void BackendProgramObjectImpl::Use() {
|
|
MG_External::GLES::glUseProgram(m_backendProgramId);
|
|
}
|
|
} // namespace PrgramImpl
|
|
|
|
namespace Utils {} // namespace Utils
|
|
} // namespace MobileGL::MG_Backend::DirectGLES
|