// MobileGL - MobileGL/MG_Impl/GLImpl/Texture/GL_Texture.cpp // Copyright (c) 2025-2026 MobileGL-Dev // Licensed under the GNU Lesser General Public License v3.0: // https://www.gnu.org/licenses/gpl-3.0.txt // https://www.gnu.org/licenses/lgpl-3.0.txt // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header #include "GL_Texture.h" #include "Config.h" #include "MG_State/GLState/TextureState/TextureObject2D.h" #include "MG_Util/Types.h" #include "Validators.h" #include "ProxyTexture.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace MobileGL::MG_Impl::GLImpl { static SharedPtr nullTextureObject; const SharedPtr& GetTextureObjectByTarget( TextureUploadTarget textureUploadTarget, TextureTarget textureTarget) { if (TextureImpl::IsProxyTextureTarget(textureUploadTarget)) { auto& textureObject = TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget); if (!TextureImpl::ValidateTextureObject(textureObject)) return nullTextureObject; return textureObject; } else { auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); auto& textureObject = bindingSlot.GetBoundObject(); if (!TextureImpl::ValidateTextureObject(textureObject)) return nullTextureObject; return textureObject; } } void GenerateMipmap_Backend(GLenum target) { MG_Backend::gBackendFunctionsTable.GL.GenerateMipmap(target); } void TexSubImage3D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) { // TODO: implement } void TexSubImage2D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels) { // ======================= Converting ================================ TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); TextureInputFormat textureInputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format); TexturePixelDataType texturePixelDataType = MG_Util::ConvertGLEnumToTexturePixelDataType(type); // TextureInternalFormat textureInternalFormat = // MG_Util::ConvertGLEnumToTextureInternalFormat(format); MGLOG_D("TexSubImage2D_State: target = %s, level = %d, (%d, %d), format = %s, pixels = %p", MG_Util::ConvertGLEnumToString(target).c_str(), level, width, height, MG_Util::ConvertTextureInputFormatToString(textureInputFormat).c_str(), pixels); // ===================== Error Checking ============================== if (!TextureImpl::ValidateTexturePixelDataType(texturePixelDataType)) return; if (!TextureImpl::ValidateTextureInputFormat(textureInputFormat)) return; if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return; if (!TextureImpl::ValidateTextureLevelNumber(level)) return; if (!TextureImpl::ValidateTextureSizeWithTextureUploadTarget(textureUploadTarget, width, height)) return; if (!TextureImpl::ValidateTextureSizeRange(width, height, 1)) return; if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return; // TODO: GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the // GL_PIXEL_UNPACK_BUFFER target and the buffer object's data store is currently mapped. // GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the // GL_PIXEL_UNPACK_BUFFER target and the data would be unpacked from the buffer object such that the // memory reads required would exceed the data store size. GL_INVALID_OPERATION is generated if a // non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER target and data is not evenly // divisible into the number of bytes needed to store in memory a datum indicated by type. // ======================= Processing ================================ auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); auto& textureObject = bindingSlot.GetBoundObject(); TextureInternalFormat textureInternalFormat = textureObject->GetFormat(); MGLOG_D("%s: working on texture %d", __func__, textureObject->GetExternalIndex()); // ===================== Error Checking ============================== if (!TextureImpl::ValidateTextureObject(textureObject)) return; if (!TextureImpl::ValidateTextureSubImageOffsets(textureObject, xoffset, width, yoffset, height)) return; if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(textureInputFormat, textureInternalFormat, texturePixelDataType)) return; // ======================= Processing ================================ // Texture object here should always be an object with mipmap // Assert this for extra safety. // This should automatically compiled out in release, // so that we don't take the perf hit of dyn-cast. MOBILEGL_ASSERT(nullptr != static_cast(textureObject.get()), "Texture object here should always be an object with mipmap"); auto textureMipmapObject = static_cast(textureObject.get()); auto texelSize = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level); SizeT inputSize = 0; const void* originalPixels = pixels; // PBO const auto& pixelUnpackBufferObject = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject(); if (pixelUnpackBufferObject) { MGLOG_D("TexSubImage2D_State: Using Pixel Unpack Buffer Object ID: %u", pixelUnpackBufferObject->GetExternalIndex()); originalPixels = reinterpret_cast(pixelUnpackBufferObject->GetDataReadOnly()->data()) + reinterpret_cast(pixels); } if (!originalPixels) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "No data supplied from pixels parameter and no PBO bound.")); return; } const auto& unpackParams = MG_State::pGLContext->GetPixelStoreParameters(true); void* processedPixels = MG_Util::PixelStoreProcessor::ProcessTexturePixelsDataUnpack( originalPixels, unpackParams, textureInternalFormat, textureInputFormat, texturePixelDataType, {width, height, 1}, false, inputSize); if (!processedPixels || inputSize == 0) { MGLOG_E("TexSubImage2D_State: Failed to process pixel data for TexSubImage2D, width: %d, height: %d", width, height); if (processedPixels) free(processedPixels); return; } const SizeT internalBpp = MG_Util::GetInternalBytesPerPixel(textureInternalFormat, texturePixelDataType); const SizeT srcRowSize = static_cast(width) * internalBpp; const SizeT srcStride = (srcRowSize + unpackParams.Alignment - 1) & ~(unpackParams.Alignment - 1); const SizeT destRowSize = static_cast(texelSize.x()) * internalBpp; if (xoffset + width > static_cast(texelSize.x()) || yoffset + height > static_cast(texelSize.y())) { MGLOG_E("TexSubImage2D_State: Specified region exceeds texture dimensions"); free(processedPixels); return; } const auto* srcData = static_cast(processedPixels); Uint8* destData = static_cast(textureMipmapObject->MapMipmapData(textureUploadTarget, level)); if (destData) { for (GLsizei y = 0; y < height; y++) { const SizeT destRowOffset = (yoffset + y) * destRowSize + xoffset * internalBpp; const SizeT srcRowOffset = y * srcStride; Memcpy(destData + destRowOffset, srcData + srcRowOffset, srcRowSize); } } free(processedPixels); MGLOG_D("%s: mark mip %d as dirty", __func__, level); textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, true); } void TexSubImage1D_State(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid* pixels) { // TODO: implement } // TexParameteriv/TexParameterfv are introduced in OpenGL 4.0, so do not support them for now. void TexParameterf_State(GLenum target, GLenum pname, GLfloat param) { // ======================= Converting ================================ TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); // ======================= Processing ================================ auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget); if (!textureObject) return; switch (pname) { case GL_TEXTURE_MAG_FILTER: textureObject->GetSamplerObject()->SetMagFilter(MG_Util::ConvertGLEnumToSamplerFilterMode((GLenum)param)); break; case GL_TEXTURE_MIN_FILTER: textureObject->GetSamplerObject()->SetMinFilter(MG_Util::ConvertGLEnumToSamplerFilterMode((GLenum)param)); textureObject->GetSamplerObject()->SetMipmapMode(MG_Util::ConvertGLEnumToSamplerMipmapMode((GLenum)param)); break; case GL_TEXTURE_MIN_LOD: { Float maxLod = textureObject->GetSamplerObject()->GetMaxLod(); textureObject->GetSamplerObject()->SetLodRange(param, maxLod); break; } case GL_TEXTURE_MAX_LOD: { Float minLod = textureObject->GetSamplerObject()->GetMinLod(); textureObject->GetSamplerObject()->SetLodRange(minLod, param); break; } case GL_TEXTURE_BASE_LEVEL: textureObject->SetBaseLevel((Uint)param); break; case GL_TEXTURE_MAX_LEVEL: textureObject->SetMaxLevel((Uint)param); break; case GL_TEXTURE_SWIZZLE_R: case GL_TEXTURE_SWIZZLE_G: case GL_TEXTURE_SWIZZLE_B: case GL_TEXTURE_SWIZZLE_A: { auto swizzleParam = MG_Util::ConvertGLEnumPnameToTextureSwizzleParam(pname); auto swizzleValue = MG_Util::ConvertGLEnumToTextureSwizzleParam((GLenum)param); textureObject->SetSwizzleParam(swizzleParam, swizzleValue); break; } case GL_TEXTURE_WRAP_S: textureObject->GetSamplerObject()->SetWrapS(MG_Util::ConvertGLEnumToSamplerWrapMode((GLenum)param)); break; case GL_TEXTURE_WRAP_T: textureObject->GetSamplerObject()->SetWrapT(MG_Util::ConvertGLEnumToSamplerWrapMode((GLenum)param)); break; case GL_TEXTURE_WRAP_R: textureObject->GetSamplerObject()->SetWrapR(MG_Util::ConvertGLEnumToSamplerWrapMode((GLenum)param)); break; case GL_TEXTURE_COMPARE_MODE: textureObject->GetSamplerObject()->SetCompareMode( MG_Util::ConvertGLEnumToSamplerCompareMode((GLenum)param)); break; case GL_TEXTURE_COMPARE_FUNC: textureObject->GetSamplerObject()->SetSamplerCompareFunc( MG_Util::ConvertGLEnumToSamplerCompareFunc((GLenum)param)); break; case GL_TEXTURE_LOD_BIAS: textureObject->GetSamplerObject()->SetLodBias(param); break; case GL_TEXTURE_SWIZZLE_RGBA: // Not supported in this function case GL_TEXTURE_BORDER_COLOR: // Not supported in this function default: MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique( "MG_Impl/GLImpl", __func__, std::format("pname {} is not a valid texture parameter.", MG_Util::ConvertGLEnumToString(pname)))); return; } } void TexParameteri_State(GLenum target, GLenum pname, GLint param) { // ======================= Converting ================================ TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); // ======================= Processing ================================ auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget); if (!textureObject) return; switch (pname) { case GL_TEXTURE_MAG_FILTER: textureObject->GetSamplerObject()->SetMagFilter(MG_Util::ConvertGLEnumToSamplerFilterMode(param)); break; case GL_TEXTURE_MIN_FILTER: textureObject->GetSamplerObject()->SetMinFilter(MG_Util::ConvertGLEnumToSamplerFilterMode(param)); textureObject->GetSamplerObject()->SetMipmapMode(MG_Util::ConvertGLEnumToSamplerMipmapMode(param)); break; case GL_TEXTURE_MIN_LOD: { Float maxLod = textureObject->GetSamplerObject()->GetMaxLod(); textureObject->GetSamplerObject()->SetLodRange(param, maxLod); break; } case GL_TEXTURE_MAX_LOD: { Float minLod = textureObject->GetSamplerObject()->GetMinLod(); textureObject->GetSamplerObject()->SetLodRange(minLod, param); break; } case GL_TEXTURE_BASE_LEVEL: textureObject->SetBaseLevel(param); break; case GL_TEXTURE_MAX_LEVEL: textureObject->SetMaxLevel(param); break; case GL_TEXTURE_SWIZZLE_R: case GL_TEXTURE_SWIZZLE_G: case GL_TEXTURE_SWIZZLE_B: case GL_TEXTURE_SWIZZLE_A: { auto swizzleParam = MG_Util::ConvertGLEnumPnameToTextureSwizzleParam(pname); auto swizzleValue = MG_Util::ConvertGLEnumToTextureSwizzleParam(param); textureObject->SetSwizzleParam(swizzleParam, swizzleValue); break; } case GL_TEXTURE_WRAP_S: textureObject->GetSamplerObject()->SetWrapS(MG_Util::ConvertGLEnumToSamplerWrapMode(param)); break; case GL_TEXTURE_WRAP_T: textureObject->GetSamplerObject()->SetWrapT(MG_Util::ConvertGLEnumToSamplerWrapMode(param)); break; case GL_TEXTURE_WRAP_R: textureObject->GetSamplerObject()->SetWrapR(MG_Util::ConvertGLEnumToSamplerWrapMode(param)); break; case GL_TEXTURE_COMPARE_MODE: textureObject->GetSamplerObject()->SetCompareMode(MG_Util::ConvertGLEnumToSamplerCompareMode(param)); break; case GL_TEXTURE_COMPARE_FUNC: textureObject->GetSamplerObject()->SetSamplerCompareFunc(MG_Util::ConvertGLEnumToSamplerCompareFunc(param)); break; case GL_TEXTURE_LOD_BIAS: textureObject->GetSamplerObject()->SetLodBias((GLfloat)param); break; case GL_TEXTURE_SWIZZLE_RGBA: // Not supported in this function case GL_TEXTURE_BORDER_COLOR: // Not supported in this function default: MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique( "MG_Impl/GLImpl", __func__, std::format("pname {} is not a valid texture parameter.", MG_Util::ConvertGLEnumToString(pname)))); return; } } // Quick and dirty TexParameter*v implementation to make NeoForge happy. // TODO: implement the missing part void TexParameterfv_State(GLenum target, GLenum pname, const GLfloat* params) { switch (pname) { case GL_TEXTURE_BORDER_COLOR: { // ======================= Converting ================================ TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); // ======================= Processing ================================ auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget); if (!textureObject) return; THROW_UNIMPL_EXCEPTION; break; } case GL_TEXTURE_SWIZZLE_RGBA: { // ======================= Converting ================================ TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); // ======================= Processing ================================ auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget); if (!textureObject) return; Vec4 swizzleParams; for (int i = 0; i < 4; i++) { swizzleParams[i] = MG_Util::ConvertGLEnumToTextureSwizzleParam(static_cast(params[i])); if (TextureSwizzleParam::Unknown == swizzleParams[i]) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", __func__, "`params` is not valid.")); return; } } textureObject->SetSwizzleParamRGBA(swizzleParams); break; } default: TexParameterf_State(target, pname, *params); break; } } void TexParameteriv_State(GLenum target, GLenum pname, const GLint* params) { switch (pname) { case GL_TEXTURE_BORDER_COLOR: { // ======================= Converting ================================ TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); // ======================= Processing ================================ auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget); if (!textureObject) return; THROW_UNIMPL_EXCEPTION; break; } case GL_TEXTURE_SWIZZLE_RGBA: { // ======================= Converting ================================ TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); // ======================= Processing ================================ auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget); if (!textureObject) return; Vec4 swizzleParams; for (int i = 0; i < 4; i++) { swizzleParams[i] = MG_Util::ConvertGLEnumToTextureSwizzleParam(static_cast(params[i])); if (TextureSwizzleParam::Unknown == swizzleParams[i]) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", __func__, "`params` is not valid.")); return; } } textureObject->SetSwizzleParamRGBA(swizzleParams); break; } default: TexParameteri_State(target, pname, *params); break; } } void TexParameterIiv_State(GLenum target, GLenum pname, const GLint* params) { switch (pname) { case GL_TEXTURE_BORDER_COLOR: { THROW_UNIMPL_EXCEPTION; break; } case GL_TEXTURE_SWIZZLE_RGBA: { // ======================= Converting ================================ TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); // ======================= Processing ================================ auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget); if (!textureObject) return; Vec4 swizzleParams; for (int i = 0; i < 4; i++) { swizzleParams[i] = MG_Util::ConvertGLEnumToTextureSwizzleParam(static_cast(params[i])); if (TextureSwizzleParam::Unknown == swizzleParams[i]) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", __func__, "`params` is not valid.")); return; } } textureObject->SetSwizzleParamRGBA(swizzleParams); break; } default: TexParameteri_State(target, pname, *params); break; } } void TexParameterIuiv_State(GLenum target, GLenum pname, const GLuint* params) { switch (pname) { case GL_TEXTURE_BORDER_COLOR: { THROW_UNIMPL_EXCEPTION; break; } case GL_TEXTURE_SWIZZLE_RGBA: { // ======================= Converting ================================ TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); // ======================= Processing ================================ auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget); if (!textureObject) return; Vec4 swizzleParams; for (int i = 0; i < 4; i++) { swizzleParams[i] = MG_Util::ConvertGLEnumToTextureSwizzleParam(static_cast(params[i])); if (TextureSwizzleParam::Unknown == swizzleParams[i]) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", __func__, "`params` is not valid.")); return; } } textureObject->SetSwizzleParamRGBA(swizzleParams); break; } default: TexParameteri_State(target, pname, static_cast(*params)); break; } } void TexImage3DMultisample_State(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) { // TODO: implement } void TexImage2DMultisample_State(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations) { // TODO: implement } void TexImage3D_State(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void* pixels) { MGLOG_D( "%s called with target: %s, level: %d, internalformat: %s, width: %d, height: %d, depth: %d, " "border: %d, format: %s, type: %s (%u), pixels: %p", __func__, MG_Util::ConvertTextureUploadTargetToString(MG_Util::ConvertGLEnumToTextureUploadTarget(target)).c_str(), level, MG_Util::ConvertTextureInternalFormatToString(MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat)) .c_str(), width, height, depth, border, MG_Util::ConvertTextureInputFormatToString(MG_Util::ConvertGLEnumToTextureInputFormat(format)).c_str(), MG_Util::ConvertTexturePixelDataTypeToString(MG_Util::ConvertGLEnumToTexturePixelDataType(type)).c_str(), type, pixels); // ======================= Converting ================================ TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); TextureInputFormat textureInputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format); TexturePixelDataType texturePixelDataType = MG_Util::ConvertGLEnumToTexturePixelDataType(type); TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); // ===================== Error Checking ============================== if (!TextureImpl::ValidateTexturePixelDataType(texturePixelDataType)) return; if (!TextureImpl::ValidateTextureInputFormat(textureInputFormat)) return; if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return; if (!TextureImpl::ValidateTextureLevelNumber(level)) return; if (!TextureImpl::ValidateTextureSizeWithTextureUploadTarget(textureUploadTarget, width, height)) return; if (!TextureImpl::ValidateTextureSizeRange(width, height, depth)) return; if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return; if (!TextureImpl::ValidateTextureBorderNumber(border)) return; if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(textureInputFormat, textureInternalFormat, texturePixelDataType)) return; if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return; // TODO: GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the // GL_PIXEL_UNPACK_BUFFER target and the buffer object's data store is currently mapped. // GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER // target and the data would be unpacked from the buffer object such that the memory reads required would // exceed the data store size. // GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER // target and data is not evenly divisible into the number of bytes needed to store in memory a datum // indicated by type. // ======================= Processing ================================ auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); auto& textureObject = isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget) : bindingSlot.GetBoundObject(); // ===================== Error Checking ============================== if (!TextureImpl::ValidateTextureObject(textureObject)) return; // ======================= Processing ================================ SizeT imageSize = 0; const SizeT inputBpp = MG_Util::GetInputBytesPerPixel(textureInputFormat, texturePixelDataType); const SizeT internalBpp = MG_Util::GetInternalBytesPerPixel(textureInternalFormat, texturePixelDataType); const SizeT internalBytes = width * height * depth * internalBpp; textureObject->SetInternalFormat(textureInternalFormat); // if isProxy, no more pixel transfer needed below if (isProxy) return; const void* originalPixels = pixels; // PBO const auto& pixelUnpackBufferObject = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject(); if (pixelUnpackBufferObject) { MGLOG_D("%s: Using Pixel Unpack Buffer Object ID: %u", __func__, pixelUnpackBufferObject->GetExternalIndex()); originalPixels = reinterpret_cast(pixelUnpackBufferObject->GetDataReadOnly()->data()) + reinterpret_cast(pixels); } MOBILEGL_ASSERT(nullptr != static_cast(textureObject.get()), "Texture object here should always be an object with mipmap"); auto textureMipmapObject = static_cast(textureObject.get()); // Allocate in TextureObject textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, height, depth}, internalBytes}); if (!originalPixels) { MGLOG_D("%s: No input pixel and no PBO bound, no pixel transfer", __func__); return; } void* processedPixels = nullptr; processedPixels = MG_Util::PixelStoreProcessor::ProcessTexturePixelsDataUnpack( originalPixels, MG_State::pGLContext->GetPixelStoreParameters(true), textureInternalFormat, textureInputFormat, texturePixelDataType, {width, height, depth}, false, imageSize); if (processedPixels && imageSize > 0) { if (imageSize != internalBytes) { MGLOG_W("%s: Processed pixel data size (%zu) does not match expected size (%zu). " "This may indicate an alignment or processing issue.", __func__, imageSize, internalBytes); } const SizeT copySize = std::min(imageSize, internalBytes); DataPtr texelInput{processedPixels, copySize}; textureMipmapObject->UpdateMipmapSubData(textureUploadTarget, level, texelInput); } textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, true); free(processedPixels); } void TexImage2D_State(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels) { // ======================= Converting ================================ TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); TextureInputFormat textureInputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format); TexturePixelDataType texturePixelDataType = MG_Util::ConvertGLEnumToTexturePixelDataType(type); TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); MGLOG_D("%s called with target: %s (%s), level: %d, internalformat: %s (%s), width: %d, height: %d, " "border: %d, format: %s (%s), type: %s (%s), pixels: %p", __func__, MG_Util::ConvertTextureUploadTargetToString(textureUploadTarget).c_str(), MG_Util::ConvertGLEnumToString(target).c_str(), level, MG_Util::ConvertTextureInternalFormatToString(textureInternalFormat).c_str(), MG_Util::ConvertGLEnumToString(internalformat).c_str(), width, height, border, MG_Util::ConvertTextureInputFormatToString(textureInputFormat).c_str(), MG_Util::ConvertGLEnumToString(format).c_str(), MG_Util::ConvertTexturePixelDataTypeToString(texturePixelDataType).c_str(), MG_Util::ConvertGLEnumToString(type).c_str(), pixels); // ===================== Error Checking ============================== if (!TextureImpl::ValidateTexturePixelDataType(texturePixelDataType)) return; if (!TextureImpl::ValidateTextureInputFormat(textureInputFormat)) return; if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return; if (!TextureImpl::ValidateTextureLevelNumber(level)) return; if (!TextureImpl::ValidateTextureSizeWithTextureUploadTarget(textureUploadTarget, width, height)) return; if (!TextureImpl::ValidateTextureSizeRange(width, height, 1)) return; if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return; if (!TextureImpl::ValidateTextureBorderNumber(border)) return; if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(textureInputFormat, textureInternalFormat, texturePixelDataType)) return; if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return; // TODO: GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the // GL_PIXEL_UNPACK_BUFFER target and the buffer object's data store is currently mapped. // GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER // target and the data would be unpacked from the buffer object such that the memory reads required would // exceed the data store size. // GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER // target and data is not evenly divisible into the number of bytes needed to store in memory a datum // indicated by type. // ======================= Processing ================================ textureInternalFormat = MG_Util::ConvertInternalFormatToSized(textureInternalFormat, textureInputFormat, texturePixelDataType); auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); auto& textureObject = isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget) : bindingSlot.GetBoundObject(); // ===================== Error Checking ============================== if (!TextureImpl::ValidateTextureObject(textureObject)) return; // ======================= Processing ================================ SizeT imageSize = 0; const SizeT inputBpp = MG_Util::GetInputBytesPerPixel(textureInputFormat, texturePixelDataType); const SizeT internalBpp = MG_Util::GetInternalBytesPerPixel(textureInternalFormat, texturePixelDataType); const SizeT internalBytes = width * height * internalBpp; MGLOG_D("%s: working on texture %d", __func__, textureObject->GetExternalIndex()); MGLOG_D("%s: texture object had internal format %s, new format %s", __func__, MG_Util::ConvertTextureInternalFormatToString(textureObject->GetFormat()).c_str(), MG_Util::ConvertTextureInternalFormatToString(textureInternalFormat).c_str()); textureObject->SetInternalFormat(textureInternalFormat); // if isProxy, no more pixel transfer needed below if (isProxy) return; const void* originalPixels = pixels; // PBO const auto& pixelUnpackBufferObject = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject(); if (pixelUnpackBufferObject) { MGLOG_D("%s: Using Pixel Unpack Buffer Object ID: %u", __func__, pixelUnpackBufferObject->GetExternalIndex()); originalPixels = reinterpret_cast(pixelUnpackBufferObject->GetDataReadOnly()->data()) + reinterpret_cast(pixels); } MOBILEGL_ASSERT(nullptr != static_cast(textureObject.get()), "Texture object here should always be an object with mipmap"); auto textureMipmapObject = static_cast(textureObject.get()); // Allocate in TextureObject MGLOG_D("%s: Allocating %d bytes at mip %d", __func__, internalBytes, level); textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, height, 1}, internalBytes}); if (!originalPixels) { MGLOG_D("%s: No input pixel and no PBO bound, no pixel transfer", __func__); return; } void* processedPixels = nullptr; processedPixels = MG_Util::PixelStoreProcessor::ProcessTexturePixelsDataUnpack( originalPixels, MG_State::pGLContext->GetPixelStoreParameters(true), textureInternalFormat, textureInputFormat, texturePixelDataType, {width, height, 1}, false, imageSize); if (processedPixels && imageSize > 0) { if (imageSize != internalBytes) { MGLOG_W("TexImage2D_State: Processed pixel data size (%zu) does not match expected size (%zu). " "This may indicate an alignment or processing issue.", imageSize, internalBytes); } const SizeT copySize = std::min(imageSize, internalBytes); DataPtr texelInput{processedPixels, copySize}; textureMipmapObject->UpdateMipmapSubData(textureUploadTarget, level, texelInput); } free(processedPixels); MGLOG_D("%s: mark mip %d as dirty", __func__, level); textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, true); } void TexImage1D_State(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid* pixels) { // TODO: implement THROW_UNIMPL_EXCEPTION; } void TexBuffer_State(GLenum target, GLenum internalformat, GLuint buffer) { // ======================= Converting ================================ TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureInternalFormat textureInternalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); // ===================== Error Checking ============================== if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return; if (!TextureImpl::ValidateTextureInternalFormat(textureInternalFormat)) return; // TODO: make sure `internalformat` is in one of supported format for TexBuffer auto& bufferObject = MG_State::pGLContext->GetBufferObject(buffer); if (!bufferObject) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", __func__, "`buffer` is not zero and is not the name of an existing buffer object.")); return; } // ======================= Processing ================================ auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); auto& textureObject = bindingSlot.GetBoundObject(); // ===================== Error Checking ============================== if (!TextureImpl::ValidateTextureObject(textureObject)) return; if (textureObject->GetStorageType() != TextureStorageType::Buffer) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", __func__, "The effective target of `texture` is not `GL_TEXTURE_BUFFER`.")); return; } // ======================= Processing ================================ // Now we can rest assured, and down-cast texture object to texture buffer auto* texBufferObject = static_cast(textureObject.get()); auto& bufferSlot = texBufferObject->GetBufferBindingSlot(); bufferSlot.Bind(bufferObject); texBufferObject->SetInternalFormat(textureInternalFormat); } GLboolean IsTexture_State(GLuint texture) { // ======================= Processing ================================ if (!TextureImpl::ValidateTextureName(texture, true)) return GL_FALSE; return MG_State::pGLContext->ValidateTextureObject(texture) ? GL_TRUE : GL_FALSE; } void GetTexParameterIuiv_State(GLenum target, GLenum pname, GLuint* params) { // TODO: implement THROW_UNIMPL_EXCEPTION; } void GetTexParameterIiv_State(GLenum target, GLenum pname, GLint* params) { // TODO: implement THROW_UNIMPL_EXCEPTION; } void GetTexParameteriv_State(GLenum target, GLenum pname, GLint* params) { // ======================= Converting ================================ TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); // ======================= Processing ================================ auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); auto& textureObject = isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget) : bindingSlot.GetBoundObject(); if (!TextureImpl::ValidateTextureObject(textureObject)) return; switch (pname) { case GL_TEXTURE_MAG_FILTER: if (params) { *params = (GLint)MG_Util::ConvertSamplerFilterModeToGLEnum( textureObject->GetSamplerObject()->GetMagFilter(), SamplerMipmapMode::None); } break; case GL_TEXTURE_MIN_FILTER: if (params) { *params = (GLint)MG_Util::ConvertSamplerFilterModeToGLEnum( textureObject->GetSamplerObject()->GetMinFilter(), textureObject->GetSamplerObject()->GetMipmapMode()); } break; case GL_TEXTURE_MIN_LOD: if (params) { *params = static_cast(textureObject->GetSamplerObject()->GetMinLod()); } break; case GL_TEXTURE_MAX_LOD: if (params) { *params = static_cast(textureObject->GetSamplerObject()->GetMaxLod()); } break; case GL_TEXTURE_BASE_LEVEL: case GL_TEXTURE_MAX_LEVEL: case GL_TEXTURE_BORDER_COLOR: break; // TODO case GL_TEXTURE_WRAP_S: if (params) { *params = (GLint)MG_Util::ConvertSamplerWrapModeToGLEnum(textureObject->GetSamplerObject()->GetWrapS()); } break; case GL_TEXTURE_WRAP_T: if (params) { *params = (GLint)MG_Util::ConvertSamplerWrapModeToGLEnum(textureObject->GetSamplerObject()->GetWrapT()); } break; case GL_TEXTURE_WRAP_R: if (params) { *params = (GLint)MG_Util::ConvertSamplerWrapModeToGLEnum(textureObject->GetSamplerObject()->GetWrapR()); } break; case GL_TEXTURE_COMPARE_MODE: if (params) { *params = (GLint)MG_Util::ConvertSamplerCompareModeToGLEnum( textureObject->GetSamplerObject()->GetCompareMode()); } break; case GL_TEXTURE_COMPARE_FUNC: if (params) { *params = (GLint)MG_Util::ConvertSamplerCompareFuncToGLEnum( textureObject->GetSamplerObject()->GetSamplerCompareFunc()); } break; default: MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", "GetTexParameteriv_State", "pname is not a valid texture parameter.")); return; } } void GetTexParameterfv_State(GLenum target, GLenum pname, GLfloat* params) { // ======================= Converting ================================ TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); // ======================= Processing ================================ auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); auto& textureObject = isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget) : bindingSlot.GetBoundObject(); if (!TextureImpl::ValidateTextureObject(textureObject)) return; switch (pname) { case GL_TEXTURE_MAG_FILTER: if (params) { *params = (GLfloat)MG_Util::ConvertSamplerFilterModeToGLEnum( textureObject->GetSamplerObject()->GetMagFilter(), SamplerMipmapMode::None); } break; case GL_TEXTURE_MIN_FILTER: if (params) { *params = (GLfloat)MG_Util::ConvertSamplerFilterModeToGLEnum( textureObject->GetSamplerObject()->GetMinFilter(), textureObject->GetSamplerObject()->GetMipmapMode()); } break; case GL_TEXTURE_MIN_LOD: if (params) { *params = static_cast(textureObject->GetSamplerObject()->GetMinLod()); } break; case GL_TEXTURE_MAX_LOD: if (params) { *params = static_cast(textureObject->GetSamplerObject()->GetMaxLod()); } break; case GL_TEXTURE_BASE_LEVEL: case GL_TEXTURE_MAX_LEVEL: case GL_TEXTURE_SWIZZLE_R: case GL_TEXTURE_SWIZZLE_G: case GL_TEXTURE_SWIZZLE_B: case GL_TEXTURE_SWIZZLE_A: case GL_TEXTURE_SWIZZLE_RGBA: case GL_TEXTURE_BORDER_COLOR: break; // TODO case GL_TEXTURE_WRAP_S: if (params) { *params = (GLfloat)MG_Util::ConvertSamplerWrapModeToGLEnum(textureObject->GetSamplerObject()->GetWrapS()); } break; case GL_TEXTURE_WRAP_T: if (params) { *params = (GLfloat)MG_Util::ConvertSamplerWrapModeToGLEnum(textureObject->GetSamplerObject()->GetWrapT()); } break; case GL_TEXTURE_WRAP_R: if (params) { *params = (GLfloat)MG_Util::ConvertSamplerWrapModeToGLEnum(textureObject->GetSamplerObject()->GetWrapR()); } break; case GL_TEXTURE_COMPARE_MODE: if (params) { *params = (GLfloat)MG_Util::ConvertSamplerCompareModeToGLEnum( textureObject->GetSamplerObject()->GetCompareMode()); } break; case GL_TEXTURE_COMPARE_FUNC: if (params) { *params = (GLfloat)MG_Util::ConvertSamplerCompareFuncToGLEnum( textureObject->GetSamplerObject()->GetSamplerCompareFunc()); } break; default: MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", "GetTexParameterfv_State", "pname is not a valid texture parameter.")); return; } } void GetTexLevelParameteriv_State(GLenum target, GLint level, GLenum pname, GLint* params) { MGLOG_D("GetTexLevelParameteriv_State called"); // ======================= Converting ================================ TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); // ===================== Error Checking ============================== if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return; if (!TextureImpl::ValidateTextureLevelNumber(level)) return; if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return; // ======================= Processing ================================ auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); auto& textureObject = isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget) : bindingSlot.GetBoundObject(); if (!TextureImpl::ValidateTextureObject(textureObject)) return; switch (pname) { case GL_TEXTURE_WIDTH: if (params) { switch (textureObject->GetStorageType()) { case TextureStorageType::Mipmap: { const auto textureMipmapObject = static_cast(textureObject.get()); *params = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).x(); break; } default: THROW_UNIMPL_EXCEPTION; } } break; case GL_TEXTURE_HEIGHT: if (params) { switch (textureObject->GetStorageType()) { case TextureStorageType::Mipmap: { const auto textureMipmapObject = static_cast(textureObject.get()); *params = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).y(); break; } default: THROW_UNIMPL_EXCEPTION; } } break; case GL_TEXTURE_DEPTH: if (params) { switch (textureObject->GetStorageType()) { case TextureStorageType::Mipmap: { const auto textureMipmapObject = static_cast(textureObject.get()); *params = textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).z(); break; } default: THROW_UNIMPL_EXCEPTION; } } break; case GL_TEXTURE_INTERNAL_FORMAT: if (params) { *params = (GLint)MG_Util::ConvertTextureInternalFormatToGLEnum(textureObject->GetFormat()); } break; case GL_TEXTURE_RED_TYPE: case GL_TEXTURE_GREEN_TYPE: case GL_TEXTURE_BLUE_TYPE: case GL_TEXTURE_ALPHA_TYPE: case GL_TEXTURE_DEPTH_TYPE: case GL_TEXTURE_RED_SIZE: case GL_TEXTURE_GREEN_SIZE: case GL_TEXTURE_BLUE_SIZE: case GL_TEXTURE_ALPHA_SIZE: case GL_TEXTURE_DEPTH_SIZE: case GL_TEXTURE_COMPRESSED: case GL_TEXTURE_COMPRESSED_IMAGE_SIZE: break; // TODO default: MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", "GetTexLevelParameteriv_State", "pname is not a valid texture level parameter.")); return; } } void GetTexLevelParameterfv_State(GLenum target, GLint level, GLenum pname, GLfloat* params) { // ======================= Converting ================================ TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); // ===================== Error Checking ============================== if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) return; if (!TextureImpl::ValidateTextureLevelNumber(level)) return; if (!TextureImpl::ValidateTextureLevelWithUploadTarget(textureUploadTarget, level)) return; // ======================= Processing ================================ auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); auto& textureObject = isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget) : bindingSlot.GetBoundObject(); if (!TextureImpl::ValidateTextureObject(textureObject)) return; switch (pname) { case GL_TEXTURE_WIDTH: if (params) { switch (textureObject->GetStorageType()) { case TextureStorageType::Mipmap: { const auto textureMipmapObject = static_cast(textureObject.get()); *params = (GLfloat)textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).x(); break; } default: THROW_UNIMPL_EXCEPTION; } } break; case GL_TEXTURE_HEIGHT: if (params) { switch (textureObject->GetStorageType()) { case TextureStorageType::Mipmap: { const auto textureMipmapObject = static_cast(textureObject.get()); *params = (GLfloat)textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).y(); break; } default: THROW_UNIMPL_EXCEPTION; } } break; case GL_TEXTURE_DEPTH: if (params) { switch (textureObject->GetStorageType()) { case TextureStorageType::Mipmap: { const auto textureMipmapObject = static_cast(textureObject.get()); *params = (GLfloat)textureMipmapObject->GetMipmapTexelSize(textureUploadTarget, level).z(); break; } default: THROW_UNIMPL_EXCEPTION; } } break; case GL_TEXTURE_INTERNAL_FORMAT: if (params) { *params = (GLfloat)MG_Util::ConvertTextureInternalFormatToGLEnum(textureObject->GetFormat()); } break; case GL_TEXTURE_RED_TYPE: case GL_TEXTURE_GREEN_TYPE: case GL_TEXTURE_BLUE_TYPE: case GL_TEXTURE_ALPHA_TYPE: case GL_TEXTURE_DEPTH_TYPE: case GL_TEXTURE_RED_SIZE: case GL_TEXTURE_GREEN_SIZE: case GL_TEXTURE_BLUE_SIZE: case GL_TEXTURE_ALPHA_SIZE: case GL_TEXTURE_DEPTH_SIZE: case GL_TEXTURE_COMPRESSED: case GL_TEXTURE_COMPRESSED_IMAGE_SIZE: break; // TODO default: MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", "GetTexLevelParameterfv_State", "pname is not a valid texture level parameter.")); return; } } void GetCompressedTexImage_State(GLenum target, GLint level, void* img) { // TODO: implement } void GenTextures_State(GLsizei n, GLuint* textures) { // ===================== Error Checking ============================== if (n < 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", "GenTextures_State", "n must be non-negative")); return; } // ======================= Processing ================================ static thread_local Vector textureNames; MG_State::pGLContext->GenTextureNames(n, textureNames); Memcpy(textures, textureNames.data(), n * sizeof(GLuint)); } void DeleteTextures_State(GLsizei n, const GLuint* textures) { // ===================== Error Checking ============================== if (n < 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", "DeleteTextures_State", "n must be non-negative.")); return; } if (!textures) { MG_State::pGLContext->RecordError(ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", "DeleteTextures_State", "Texture names array cannot be null.")); return; } // ======================= Processing ================================ for (SizeT i = 0; i < static_cast(n); ++i) { Uint textureName = textures[i]; if (textureName == 0) continue; if (!TextureImpl::ValidateTextureName(textureName, true)) continue; MG_State::pGLContext->MarkTextureObjectForDeletion(textureName); } } void CopyTexSubImage3D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height) { // TODO: implement } void CopyTexSubImage2D_Backend(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) { MG_Backend::gBackendFunctionsTable.GL.CopyTexSubImage2D(target, level, xoffset, yoffset, x, y, width, height); } void CopyTexSubImage1D_State(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) { // TODO: implement } void CopyTexImage2D_State(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) { auto internalFormat = MG_Util::ConvertGLEnumToTextureInternalFormat(internalformat); const auto& currentReadFBO = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read).GetBoundObject(); if (!currentReadFBO) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", "CopyTexImage2D_State", "No framebuffer is currently bound to the GL_READ_FRAMEBUFFER target.")); return; } Bool isDepth = MG_Util::IsDepthFormatInternalFormat(internalFormat); Bool isStencil = MG_Util::IsStencilFormatInternalFormat(internalFormat); TextureInternalFormat srcInternalFormat = TextureInternalFormat::Unknown; #define GET_SRC_INTERNAL_FORMAT(AttachmentType) \ const auto& srcAttachment = currentReadFBO->GetAttachment(AttachmentType); \ if (srcAttachment.IsTexture()) { \ const auto& texObj = srcAttachment.GetTexture(); \ srcInternalFormat = texObj->GetFormat(); \ } else if (srcAttachment.IsRenderbuffer()) { \ const auto& rboObj = srcAttachment.GetRenderbuffer(); \ srcInternalFormat = rboObj->GetInternalFormat(); \ } else { \ MG_State::pGLContext->RecordError( \ ErrorCode::InvalidOperation, \ MakeUnique("MG_Impl/GLImpl", "CopyTexImage2D_State", \ "The attachment specified by the read buffer is incomplete.")); \ return; \ } if (isDepth) { GET_SRC_INTERNAL_FORMAT(FramebufferAttachmentType::Depth); } else if (isStencil) { GET_SRC_INTERNAL_FORMAT(FramebufferAttachmentType::Stencil); } else { const auto& readBufferType = currentReadFBO->GetReadBuffer(); GET_SRC_INTERNAL_FORMAT(readBufferType); } if (!TextureImpl::ValidateBaseInternalFormatMatch(internalFormat, srcInternalFormat)) THROW_UNIMPL_EXCEPTION; GLenum outInternalFormat = MG_Util::ConvertTextureInternalFormatToGLEnum(srcInternalFormat); GLenum realInternalFormat = GL_RGBA8; GLenum format = GL_DEPTH_COMPONENT; GLenum type = GL_UNSIGNED_INT; MG_Util::TextureFormatProcessor::NormalizePixelFormat(outInternalFormat, PixelFormatNormalizeOptionBit::None, &realInternalFormat, &format, &type); const auto& pixelUnpackBufferObject = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).GetBoundObject(); TexImage2D_State(target, level, (GLint)realInternalFormat, width, height, border, format, type, nullptr); MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelUnpack).Bind(pixelUnpackBufferObject); } void CopyTexImage2D_Backend(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) { MG_Backend::gBackendFunctionsTable.GL.CopyTexImage2D(target, level, internalformat, x, y, width, height, border); } void CopyTexImage1D_State(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border) { // TODO: implement THROW_UNIMPL_EXCEPTION; } void CompressedTexSubImage3D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data) { // TODO: implement THROW_UNIMPL_EXCEPTION; } void CompressedTexSubImage2D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* data) { // TODO: implement THROW_UNIMPL_EXCEPTION; } void CompressedTexSubImage1D_State(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* data) { // TODO: implement THROW_UNIMPL_EXCEPTION; } void CompressedTexImage3D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void* data) { // TODO: implement THROW_UNIMPL_EXCEPTION; } void CompressedTexImage2D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void* data) { // TODO: implement THROW_UNIMPL_EXCEPTION; } void CompressedTexImage1D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void* data) { // TODO: implement THROW_UNIMPL_EXCEPTION; } void BindTexture_State(GLenum target, GLuint texture) { // TODO: deal with condition where texture == 0 MGLOG_D("BindTexture_State called with target: 0x%X, texture: %u", target, texture); // ======================= Converting ================================ TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); // ===================== Error Checking ============================== if (!TextureImpl::ValidateTextureName(texture, true)) return; if (!TextureImpl::ValidateTextureTarget(textureTarget)) return; // ======================= Processing ================================ Bool doesTextureExist = MG_State::pGLContext->ValidateTextureObject(texture); if (!doesTextureExist) { MG_State::pGLContext->CreateTextureObject(texture, textureTarget); } auto& textureObject = MG_State::pGLContext->GetTextureObject(texture); // ===================== Error Checking ============================== if (doesTextureExist && !TextureImpl::ValidateTextureTargetUniformity(textureObject, textureTarget)) return; // ======================= Processing ================================ auto& currentUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = currentUnit.GetBindingSlot(textureTarget); bindingSlot.Bind(textureObject); } void ActiveTexture_State(GLenum texture) { // ===================== Error Checking ============================== if (texture < GL_TEXTURE0 || texture > GL_TEXTURE31) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique( "MG_Impl/GLImpl", "ActiveTexture_State", std::format("Texture must be one of GL_TEXTUREi, where i is in the range 0 to 31, but got " "invalid enum: 0x{:X}, which may stand for unit {}.", texture, texture - GL_TEXTURE0))); return; } // ======================= Processing ================================ MG_State::pGLContext->SetActiveTextureUnit((Int)texture - GL_TEXTURE0); } void GetTexImage_Backend(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) { MG_Backend::gBackendFunctionsTable.GL.GetTexImage(target, level, format, type, pixels); } // Add to GL_Texture.cpp void GetTexImage_State(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) { // ======================= Converting ================================ TextureUploadTarget textureUploadTarget = MG_Util::ConvertGLEnumToTextureUploadTarget(target); TextureTarget textureTarget = MG_Util::ConvertGLEnumToTextureTarget(target); TextureInputFormat textureInputFormat = MG_Util::ConvertGLEnumToTextureInputFormat(format); TexturePixelDataType texturePixelDataType = MG_Util::ConvertGLEnumToTexturePixelDataType(type); // ===================== Error Checking ============================== // Validate target if (!TextureImpl::ValidateTextureUploadTarget(textureUploadTarget)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", "GetTexImage_State", "Invalid texture target")); return; } // Validate level if (level < 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, MakeUnique("MG_Impl/GLImpl", "GetTexImage_State", "Level must be non-negative")); return; } // Validate format if (!TextureImpl::ValidateTextureInputFormat(textureInputFormat)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", "GetTexImage_State", "Invalid format")); return; } // Validate type if (!TextureImpl::ValidateTexturePixelDataType(texturePixelDataType)) { MG_State::pGLContext->RecordError( ErrorCode::InvalidEnum, MakeUnique("MG_Impl/GLImpl", "GetTexImage_State", "Invalid pixel data type")); return; } // Get texture object auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); Bool isProxy = TextureImpl::IsProxyTextureTarget(textureUploadTarget); auto& textureObject = isProxy ? TextureImpl::pProxyTextureManager->CreateOrReplaceProxyTextureObject(textureUploadTarget) : bindingSlot.GetBoundObject(); if (!TextureImpl::ValidateTextureObject(textureObject)) { MG_State::pGLContext->RecordError(ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", "GetTexImage_State", "No valid texture bound to target")); return; } // Check texture completeness if (!textureObject->IsComplete()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", "GetTexImage_State", "Texture is incomplete")); return; } // Check PBO state const auto& pixelPackBufferObject = MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::PixelPack).GetBoundObject(); if (pixelPackBufferObject) { // Check if PBO is mapped if (pixelPackBufferObject->IsMapped()) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", "GetTexImage_State", "Pixel pack buffer is currently mapped")); return; } // Check alignment const SizeT typeSize = MG_Util::GetTexturePixelDataTypeSize(texturePixelDataType); if (reinterpret_cast(pixels) % typeSize != 0) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", "GetTexImage_State", "Pixel data not aligned for pixel pack buffer")); return; } } // Special case for depth/stencil if (textureInputFormat == TextureInputFormat::StencilIndex) { if (textureObject->GetFormat() != TextureInternalFormat::DepthStencil && textureObject->GetFormat() != TextureInternalFormat::Depth24Stencil8 && textureObject->GetFormat() != TextureInternalFormat::Depth32FStencil8) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", "GetTexImage_State", "No stencil buffer for stencil index format")); return; } } // Check for multisampling if (textureObject->GetStorageType() == TextureStorageType::Mipmap) { auto mipmapObject = static_cast(textureObject.get()); if (mipmapObject->GetMipmapLevelCount() > 1) { MG_State::pGLContext->RecordError( ErrorCode::InvalidOperation, MakeUnique("MG_Impl/GLImpl", "GetTexImage_State", "Multisampled textures not supported for GetTexImage")); return; } } } /* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */ void GenerateMipmap(GLenum target) { GenerateMipmap_Backend(target); } void GetTexImage(GLenum target, GLint level, GLenum format, GLenum type, GLvoid* pixels) { GetTexImage_State(target, level, format, type, pixels); GetTexImage_Backend(target, level, format, type, pixels); } void TexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const void* pixels) { TexSubImage3D_State(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, pixels); } void TexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void* pixels) { TexSubImage2D_State(target, level, xoffset, yoffset, width, height, format, type, pixels); } void TexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLenum type, const GLvoid* pixels) { TexSubImage1D_State(target, level, xoffset, width, format, type, pixels); } void TexParameterf(GLenum target, GLenum pname, GLfloat param) { TexParameterf_State(target, pname, param); } void TexParameteri(GLenum target, GLenum pname, GLint param) { TexParameteri_State(target, pname, param); } void TexParameterfv(GLenum target, GLenum pname, const GLfloat* params) { TexParameterfv_State(target, pname, params); } void TexParameteriv(GLenum target, GLenum pname, const GLint* params) { TexParameteriv_State(target, pname, params); } void TexParameterIiv(GLenum target, GLenum pname, const GLint* params) { TexParameterIiv_State(target, pname, params); } void TexParameterIuiv(GLenum target, GLenum pname, const GLuint* params) { TexParameterIuiv_State(target, pname, params); } void TexImage3DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) { TexImage3DMultisample_State(target, samples, internalformat, width, height, depth, fixedsamplelocations); } void TexImage2DMultisample(GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations) { TexImage2DMultisample_State(target, samples, internalformat, width, height, fixedsamplelocations); } void TexImage3D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const void* pixels) { TexImage3D_State(target, level, internalformat, width, height, depth, border, format, type, pixels); } void TexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void* pixels) { TexImage2D_State(target, level, internalformat, width, height, border, format, type, pixels); } void TexImage1D(GLenum target, GLint level, GLint internalFormat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid* pixels) { TexImage1D_State(target, level, internalFormat, width, border, format, type, pixels); } void TexBuffer(GLenum target, GLenum internalformat, GLuint buffer) { TexBuffer_State(target, internalformat, buffer); } GLboolean IsTexture(GLuint texture) { return IsTexture_State(texture); } void GetTexParameterIuiv(GLenum target, GLenum pname, GLuint* params) { GetTexParameterIuiv_State(target, pname, params); } void GetTexParameterIiv(GLenum target, GLenum pname, GLint* params) { GetTexParameterIiv_State(target, pname, params); } void GetTexParameteriv(GLenum target, GLenum pname, GLint* params) { GetTexParameteriv_State(target, pname, params); } void GetTexParameterfv(GLenum target, GLenum pname, GLfloat* params) { GetTexParameterfv_State(target, pname, params); } void GetTexLevelParameteriv(GLenum target, GLint level, GLenum pname, GLint* params) { GetTexLevelParameteriv_State(target, level, pname, params); } void GetTexLevelParameterfv(GLenum target, GLint level, GLenum pname, GLfloat* params) { GetTexLevelParameterfv_State(target, level, pname, params); } void GetCompressedTexImage(GLenum target, GLint level, void* img) { GetCompressedTexImage_State(target, level, img); } void GenTextures(GLsizei n, GLuint* textures) { GenTextures_State(n, textures); } void DeleteTextures(GLsizei n, const GLuint* textures) { DeleteTextures_State(n, textures); } void CopyTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLint x, GLint y, GLsizei width, GLsizei height) { CopyTexSubImage3D_State(target, level, xoffset, yoffset, zoffset, x, y, width, height); } void CopyTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width, GLsizei height) { CopyTexSubImage2D_Backend(target, level, xoffset, yoffset, x, y, width, height); } void CopyTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLint x, GLint y, GLsizei width) { CopyTexSubImage1D_State(target, level, xoffset, x, y, width); } void CopyTexImage2D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLsizei height, GLint border) { CopyTexImage2D_State(target, level, internalformat, x, y, width, height, border); CopyTexImage2D_Backend(target, level, internalformat, x, y, width, height, border); } void CopyTexImage1D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border) { CopyTexImage1D_State(target, level, internalformat, x, y, width, border); } void CompressedTexSubImage3D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize, const void* data) { CompressedTexSubImage3D_State(target, level, xoffset, yoffset, zoffset, width, height, depth, format, imageSize, data); } void CompressedTexSubImage2D(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLsizei imageSize, const void* data) { CompressedTexSubImage2D_State(target, level, xoffset, yoffset, width, height, format, imageSize, data); } void CompressedTexSubImage1D(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format, GLsizei imageSize, const void* data) { CompressedTexSubImage1D_State(target, level, xoffset, width, format, imageSize, data); } void CompressedTexImage3D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLsizei imageSize, const void* data) { CompressedTexImage3D_State(target, level, internalformat, width, height, depth, border, imageSize, data); } void CompressedTexImage2D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const void* data) { CompressedTexImage2D_State(target, level, internalformat, width, height, border, imageSize, data); } void CompressedTexImage1D(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border, GLsizei imageSize, const void* data) { CompressedTexImage1D_State(target, level, internalformat, width, border, imageSize, data); } void BindTexture(GLenum target, GLuint texture) { BindTexture_State(target, texture); } void ActiveTexture(GLenum texture) { ActiveTexture_State(texture); } } // namespace MobileGL::MG_Impl::GLImpl