Compare commits

...
16 changed files with 926 additions and 75 deletions
@@ -2150,6 +2150,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glTexParameterf(target, GL_TEXTURE_MAX_LOD, samplerParams.maxLod); g_GLESFuncs.glTexParameterf(target, GL_TEXTURE_MAX_LOD, samplerParams.maxLod);
m_cacheSamplerParameters.maxLod = samplerParams.maxLod; m_cacheSamplerParameters.maxLod = samplerParams.maxLod;
} }
if (m_cacheSamplerParameters.maxAnisotropy != samplerParams.maxAnisotropy) {
if (g_GLESCapabilities.SupportsTextureFilterAnisotropy) {
g_GLESFuncs.glTexParameterf(target, GL_TEXTURE_MAX_ANISOTROPY_EXT,
samplerParams.maxAnisotropy);
}
// Unsupported GLES backends intentionally treat anisotropy as a
// frontend-only no-op; remember the observed value so the cache
// remains coherent without issuing an illegal enum every sync.
m_cacheSamplerParameters.maxAnisotropy = samplerParams.maxAnisotropy;
}
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__](GLenum err) { DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__](GLenum err) {
MGLOG_D("%s(%s:%d) ES error %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str()); MGLOG_D("%s(%s:%d) ES error %s", func, file, line, MG_Util::ConvertGLEnumToString(err).c_str());
}); });
@@ -3017,6 +3027,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
g_GLESFuncs.glSamplerParameterf(m_backendSamplerId, GL_TEXTURE_MAX_LOD, samplerParams.maxLod); g_GLESFuncs.glSamplerParameterf(m_backendSamplerId, GL_TEXTURE_MAX_LOD, samplerParams.maxLod);
m_cacheSamplerParameters.maxLod = samplerParams.maxLod; m_cacheSamplerParameters.maxLod = samplerParams.maxLod;
} }
if (m_cacheSamplerParameters.maxAnisotropy != samplerParams.maxAnisotropy) {
if (g_GLESCapabilities.SupportsTextureFilterAnisotropy) {
g_GLESFuncs.glSamplerParameterf(m_backendSamplerId, GL_TEXTURE_MAX_ANISOTROPY_EXT,
samplerParams.maxAnisotropy);
}
m_cacheSamplerParameters.maxAnisotropy = samplerParams.maxAnisotropy;
}
#undef SYNC_SAMPLER_PARAM_IF_CHANGED #undef SYNC_SAMPLER_PARAM_IF_CHANGED
m_isInitialized = true; m_isInitialized = true;
} }
@@ -99,6 +99,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
XXHASH_VERIFY(XXH64_update(m_hashState, &maxLod, sizeof(maxLod))); XXHASH_VERIFY(XXH64_update(m_hashState, &maxLod, sizeof(maxLod)));
const auto lodBias = sampler.GetLodBias(); const auto lodBias = sampler.GetLodBias();
XXHASH_VERIFY(XXH64_update(m_hashState, &lodBias, sizeof(lodBias))); XXHASH_VERIFY(XXH64_update(m_hashState, &lodBias, sizeof(lodBias)));
// Anisotropy is currently an accepted frontend-only state on DirectVulkan.
// Keep it out of the key so changing this no-op does not manufacture duplicate
// VkSamplers while sampler versioning still exposes the new frontend value.
const auto compareMode = sampler.GetCompareMode(); const auto compareMode = sampler.GetCompareMode();
XXHASH_VERIFY(XXH64_update(m_hashState, &compareMode, sizeof(compareMode))); XXHASH_VERIFY(XXH64_update(m_hashState, &compareMode, sizeof(compareMode)));
const auto compareFunc = ResolveCompareFunc(sampler, texture); const auto compareFunc = ResolveCompareFunc(sampler, texture);
@@ -125,6 +128,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
samplerInfo.addressModeV = ToVkAddressMode(sampler.GetWrapT()); samplerInfo.addressModeV = ToVkAddressMode(sampler.GetWrapT());
samplerInfo.addressModeW = ToVkAddressMode(sampler.GetWrapR()); samplerInfo.addressModeW = ToVkAddressMode(sampler.GetWrapR());
samplerInfo.mipLodBias = sampler.GetLodBias(); samplerInfo.mipLodBias = sampler.GetLodBias();
// DirectVulkan does not yet plumb samplerAnisotropy feature/limit discovery;
// preserve the accepted frontend state without requesting an unsupported feature.
samplerInfo.anisotropyEnable = VK_FALSE; samplerInfo.anisotropyEnable = VK_FALSE;
samplerInfo.maxAnisotropy = 1.0f; samplerInfo.maxAnisotropy = 1.0f;
samplerInfo.compareEnable = sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture ? VK_TRUE : VK_FALSE; samplerInfo.compareEnable = sampler.GetCompareMode() == SamplerCompareMode::CompareToTexture ? VK_TRUE : VK_FALSE;
@@ -2150,8 +2150,8 @@ void main() {
if (!supported) { if (!supported) {
// SetupDraw's pre-flight should have rejected this already; never upload a null payload. // SetupDraw's pre-flight should have rejected this already; never upload a null payload.
MGLOG_E("UploadAndBindVertexStreams skipped: unsupported current generic vertex attribute type: " MGLOG_E("UploadAndBindVertexStreams skipped: unsupported current generic vertex attribute type: "
"program=%u location=%u type=0x%x", "programHash=%llu location=%u type=0x%x",
program.GetExternalIndex(), location, glType); static_cast<unsigned long long>(programObj.hash), location, glType);
return false; return false;
} }
+34 -7
View File
@@ -14,7 +14,13 @@
namespace MobileGL::MG_Impl::GLImpl { namespace MobileGL::MG_Impl::GLImpl {
namespace { namespace {
Bool ValidateSamplerParameterValue(GLenum pname, const void* param, Bool isFloat, Bool isInteger) { Float ReadSamplerScalar(const void* param, Bool isFloat, Bool isUnsignedInteger) {
if (isFloat) return *(const GLfloat*)param;
if (isUnsignedInteger) return static_cast<Float>(*(const GLuint*)param);
return static_cast<Float>(*(const GLint*)param);
}
Bool ValidateSamplerParameterValue(GLenum pname, const void* param, Bool isFloat, Bool isUnsignedInteger) {
if (param == nullptr) return false; if (param == nullptr) return false;
switch (pname) { switch (pname) {
@@ -22,6 +28,13 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_MAX_LOD: case GL_TEXTURE_MAX_LOD:
case GL_TEXTURE_LOD_BIAS: case GL_TEXTURE_LOD_BIAS:
return true; return true;
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
if (ReadSamplerScalar(param, isFloat, isUnsignedInteger) >= 1.0f) return true;
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "SetSamplerParam_State",
"GL_TEXTURE_MAX_ANISOTROPY_EXT must be at least 1.0."));
return false;
default: default:
break; break;
} }
@@ -29,14 +42,15 @@ namespace MobileGL::MG_Impl::GLImpl {
if (isFloat) { if (isFloat) {
return SamplerImpl::ValidateSamplerFloatParam(pname, *(const GLfloat*)param); return SamplerImpl::ValidateSamplerFloatParam(pname, *(const GLfloat*)param);
} }
if (isInteger) { if (isUnsignedInteger) {
return SamplerImpl::ValidateSamplerIntParam(pname, static_cast<GLint>(*(const GLuint*)param)); return SamplerImpl::ValidateSamplerIntParam(pname, static_cast<GLint>(*(const GLuint*)param));
} }
return SamplerImpl::ValidateSamplerIntParam(pname, *(const GLint*)param); return SamplerImpl::ValidateSamplerIntParam(pname, *(const GLint*)param);
} }
} // namespace } // namespace
void SetSamplerParam_State(GLuint sampler, GLenum pname, const void* param, bool isFloat, bool isInteger) { void SetSamplerParam_State(GLuint sampler, GLenum pname, const void* param, bool isFloat,
bool isUnsignedInteger) {
if (param == nullptr) return; if (param == nullptr) return;
if (!SamplerImpl::ValidateSamplerName(sampler)) return; if (!SamplerImpl::ValidateSamplerName(sampler)) return;
@@ -47,7 +61,7 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
auto& samplerObj = MG_State::pGLContext->GetSamplerObject(sampler); auto& samplerObj = MG_State::pGLContext->GetSamplerObject(sampler);
if (!SamplerImpl::ValidateSamplerObject(sampler)) return; if (!SamplerImpl::ValidateSamplerObject(sampler)) return;
if (!ValidateSamplerParameterValue(pname, param, isFloat, isInteger)) return; if (!ValidateSamplerParameterValue(pname, param, isFloat, isUnsignedInteger)) return;
using namespace MG_Util; using namespace MG_Util;
switch (pname) { switch (pname) {
@@ -76,6 +90,9 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_LOD_BIAS: case GL_TEXTURE_LOD_BIAS:
samplerObj->SetLodBias(*(const GLfloat*)param); samplerObj->SetLodBias(*(const GLfloat*)param);
break; break;
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
samplerObj->SetMaxAnisotropy(ReadSamplerScalar(param, isFloat, isUnsignedInteger));
break;
case GL_TEXTURE_COMPARE_MODE: case GL_TEXTURE_COMPARE_MODE:
samplerObj->SetCompareMode(MG_Util::ConvertGLEnumToSamplerCompareMode(*(const GLint*)param)); samplerObj->SetCompareMode(MG_Util::ConvertGLEnumToSamplerCompareMode(*(const GLint*)param));
break; break;
@@ -89,7 +106,8 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
} }
void GetSamplerParam_State(GLuint sampler, GLenum pname, void* params, bool isFloat, bool isInteger) { void GetSamplerParam_State(GLuint sampler, GLenum pname, void* params, bool isFloat,
bool isUnsignedInteger) {
if (params == nullptr) return; if (params == nullptr) return;
if (!SamplerImpl::ValidateSamplerName(sampler)) return; if (!SamplerImpl::ValidateSamplerName(sampler)) return;
@@ -129,6 +147,15 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_LOD_BIAS: case GL_TEXTURE_LOD_BIAS:
*(GLfloat*)params = samplerObj->GetLodBias(); *(GLfloat*)params = samplerObj->GetLodBias();
break; break;
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
if (isFloat) {
*(GLfloat*)params = samplerObj->GetMaxAnisotropy();
} else if (isUnsignedInteger) {
*(GLuint*)params = static_cast<GLuint>(samplerObj->GetMaxAnisotropy());
} else {
*(GLint*)params = static_cast<GLint>(samplerObj->GetMaxAnisotropy());
}
break;
case GL_TEXTURE_COMPARE_MODE: case GL_TEXTURE_COMPARE_MODE:
*(GLuint*)params = MG_Util::ConvertSamplerCompareModeToGLEnum(samplerObj->GetCompareMode()); *(GLuint*)params = MG_Util::ConvertSamplerCompareModeToGLEnum(samplerObj->GetCompareMode());
break; break;
@@ -240,7 +267,7 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
void SamplerParameterIiv(GLuint sampler, GLenum pname, const GLint* param) { void SamplerParameterIiv(GLuint sampler, GLenum pname, const GLint* param) {
SetSamplerParam_State(sampler, pname, param, false, true); SetSamplerParam_State(sampler, pname, param, false, false);
} }
void SamplerParameteriv(GLuint sampler, GLenum pname, const GLint* param) { void SamplerParameteriv(GLuint sampler, GLenum pname, const GLint* param) {
@@ -268,7 +295,7 @@ namespace MobileGL::MG_Impl::GLImpl {
} }
void GetSamplerParameterIiv(GLuint sampler, GLenum pname, GLint* params) { void GetSamplerParameterIiv(GLuint sampler, GLenum pname, GLint* params) {
GetSamplerParam_State(sampler, pname, params, false, true); GetSamplerParam_State(sampler, pname, params, false, false);
} }
void GetSamplerParameterfv(GLuint sampler, GLenum pname, GLfloat* params) { void GetSamplerParameterfv(GLuint sampler, GLenum pname, GLfloat* params) {
@@ -99,6 +99,16 @@ namespace MobileGL::MG_Impl::GLImpl::SamplerImpl {
case GL_TEXTURE_LOD_BIAS: case GL_TEXTURE_LOD_BIAS:
return true; return true;
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
if (!(param >= 1.0f)) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateSamplerFloatParam",
"GL_TEXTURE_MAX_ANISOTROPY_EXT must be at least 1.0."));
return false;
}
return true;
case GL_TEXTURE_BORDER_COLOR: case GL_TEXTURE_BORDER_COLOR:
if (param < 0.0f || param > 1.0f) { if (param < 0.0f || param > 1.0f) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
@@ -125,6 +135,16 @@ namespace MobileGL::MG_Impl::GLImpl::SamplerImpl {
} }
return true; return true;
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
if (param < 1) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "ValidateSamplerIntParam",
"GL_TEXTURE_MAX_ANISOTROPY_EXT must be at least 1."));
return false;
}
return true;
default: default:
return ValidateSamplerParam(pname, static_cast<GLenum>(param)); return ValidateSamplerParam(pname, static_cast<GLenum>(param));
} }
+45 -12
View File
@@ -74,6 +74,16 @@ namespace MobileGL::MG_Impl::GLImpl {
return true; return true;
} }
Bool ValidateMaxAnisotropy(Float maxAnisotropy, const char* caller) {
if (maxAnisotropy >= 1.0f) return true;
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
"GL_TEXTURE_MAX_ANISOTROPY_EXT must be at least 1.0."));
return false;
}
template <typename Fn> template <typename Fn>
void WithTemporarilyBoundNamedTexture(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject, void WithTemporarilyBoundNamedTexture(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
Fn&& fn) { Fn&& fn) {
@@ -458,6 +468,9 @@ namespace MobileGL::MG_Impl::GLImpl {
Bool ValidateTextureParameterForTarget(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject, Bool ValidateTextureParameterForTarget(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject,
GLenum pname, GLint param, const char* caller) { GLenum pname, GLint param, const char* caller) {
const auto target = textureObject->GetTarget(); const auto target = textureObject->GetTarget();
if (pname == GL_TEXTURE_MAX_ANISOTROPY_EXT && !ValidateMaxAnisotropy(param, caller)) {
return false;
}
if ((pname == GL_TEXTURE_BASE_LEVEL || pname == GL_TEXTURE_MAX_LEVEL) && param < 0) { if ((pname == GL_TEXTURE_BASE_LEVEL || pname == GL_TEXTURE_MAX_LEVEL) && param < 0) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue, ErrorCode::InvalidValue,
@@ -487,7 +500,8 @@ namespace MobileGL::MG_Impl::GLImpl {
(pname == GL_TEXTURE_WRAP_S || pname == GL_TEXTURE_WRAP_T || pname == GL_TEXTURE_WRAP_R || (pname == GL_TEXTURE_WRAP_S || pname == GL_TEXTURE_WRAP_T || pname == GL_TEXTURE_WRAP_R ||
pname == GL_TEXTURE_MIN_FILTER || pname == GL_TEXTURE_MAG_FILTER || pname == GL_TEXTURE_MIN_LOD || pname == GL_TEXTURE_MIN_FILTER || pname == GL_TEXTURE_MAG_FILTER || pname == GL_TEXTURE_MIN_LOD ||
pname == GL_TEXTURE_MAX_LOD || pname == GL_TEXTURE_LOD_BIAS || pname == GL_TEXTURE_COMPARE_MODE || pname == GL_TEXTURE_MAX_LOD || pname == GL_TEXTURE_LOD_BIAS || pname == GL_TEXTURE_COMPARE_MODE ||
pname == GL_TEXTURE_COMPARE_FUNC || pname == GL_TEXTURE_BORDER_COLOR)) { pname == GL_TEXTURE_COMPARE_FUNC || pname == GL_TEXTURE_BORDER_COLOR ||
pname == GL_TEXTURE_MAX_ANISOTROPY_EXT)) {
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation, ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller, MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
@@ -578,6 +592,9 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_LOD_BIAS: case GL_TEXTURE_LOD_BIAS:
textureObject->GetSamplerObject()->SetLodBias((GLfloat)param); textureObject->GetSamplerObject()->SetLodBias((GLfloat)param);
break; break;
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
textureObject->GetSamplerObject()->SetMaxAnisotropy(static_cast<GLfloat>(param));
break;
case GL_GENERATE_MIPMAP: case GL_GENERATE_MIPMAP:
g_autoGenerateMipmapByTextureId[textureObject->GetExternalIndex()] = (param != GL_FALSE); g_autoGenerateMipmapByTextureId[textureObject->GetExternalIndex()] = (param != GL_FALSE);
break; break;
@@ -594,7 +611,10 @@ namespace MobileGL::MG_Impl::GLImpl {
void TextureParameterObjectf_State(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject, GLenum pname, void TextureParameterObjectf_State(const SharedPtr<MG_State::GLState::ITextureObject>& textureObject, GLenum pname,
GLfloat param, const char* caller) { GLfloat param, const char* caller) {
if (!textureObject) return; if (!textureObject) return;
if (!ValidateTextureParameterForTarget(textureObject, pname, static_cast<GLint>(param), caller)) return; if (pname == GL_TEXTURE_MAX_ANISOTROPY_EXT && !ValidateMaxAnisotropy(param, caller)) return;
const GLint validationParam =
pname == GL_TEXTURE_MAX_ANISOTROPY_EXT ? 1 : static_cast<GLint>(param);
if (!ValidateTextureParameterForTarget(textureObject, pname, validationParam, caller)) return;
switch (pname) { switch (pname) {
case GL_TEXTURE_MAG_FILTER: case GL_TEXTURE_MAG_FILTER:
@@ -640,6 +660,9 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_LOD_BIAS: case GL_TEXTURE_LOD_BIAS:
textureObject->GetSamplerObject()->SetLodBias(param); textureObject->GetSamplerObject()->SetLodBias(param);
break; break;
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
textureObject->GetSamplerObject()->SetMaxAnisotropy(param);
break;
case GL_GENERATE_MIPMAP: case GL_GENERATE_MIPMAP:
g_autoGenerateMipmapByTextureId[textureObject->GetExternalIndex()] = (param != 0.0f); g_autoGenerateMipmapByTextureId[textureObject->GetExternalIndex()] = (param != 0.0f);
break; break;
@@ -709,6 +732,9 @@ namespace MobileGL::MG_Impl::GLImpl {
*params = (GLint)MG_Util::ConvertSamplerCompareFuncToGLEnum( *params = (GLint)MG_Util::ConvertSamplerCompareFuncToGLEnum(
textureObject->GetSamplerObject()->GetSamplerCompareFunc()); textureObject->GetSamplerObject()->GetSamplerCompareFunc());
break; break;
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
*params = static_cast<GLint>(textureObject->GetSamplerObject()->GetMaxAnisotropy());
break;
default: default:
MG_State::pGLContext->RecordError( MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum, ErrorCode::InvalidEnum,
@@ -865,11 +891,11 @@ namespace MobileGL::MG_Impl::GLImpl {
auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit()); auto& activeUnit = MG_State::pGLContext->GetTextureUnitObject(MG_State::pGLContext->GetActiveTextureUnit());
auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget); auto& bindingSlot = activeUnit.GetBindingSlot(textureTarget);
auto& textureObject = bindingSlot.GetBoundObject(); auto& textureObject = bindingSlot.GetBoundObject();
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
TextureInternalFormat textureInternalFormat = textureObject->GetFormat(); TextureInternalFormat textureInternalFormat = textureObject->GetFormat();
MGLOG_D("%s: working on texture %d", __func__, textureObject->GetExternalIndex()); MGLOG_D("%s: working on texture %d", __func__, textureObject->GetExternalIndex());
// ===================== Error Checking ============================== // ===================== Error Checking ==============================
if (!TextureImpl::ValidateTextureObject(textureObject)) return;
if (!TextureImpl::ValidateTextureSubImageOffsets(textureObject, xoffset, width, yoffset, height)) return; if (!TextureImpl::ValidateTextureSubImageOffsets(textureObject, xoffset, width, yoffset, height)) return;
if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(textureInputFormat, textureInternalFormat, if (!TextureImpl::ValidateTextureInternalFormatCompatibleWithInput(textureInputFormat, textureInternalFormat,
texturePixelDataType)) texturePixelDataType))
@@ -1083,6 +1109,10 @@ namespace MobileGL::MG_Impl::GLImpl {
case GL_TEXTURE_LOD_BIAS: case GL_TEXTURE_LOD_BIAS:
textureObject->GetSamplerObject()->SetLodBias(param); textureObject->GetSamplerObject()->SetLodBias(param);
break; break;
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
if (!ValidateMaxAnisotropy(param, __func__)) return;
textureObject->GetSamplerObject()->SetMaxAnisotropy(param);
break;
case GL_GENERATE_MIPMAP: case GL_GENERATE_MIPMAP:
g_autoGenerateMipmapByTextureId[textureObject->GetExternalIndex()] = (param != 0.0f); g_autoGenerateMipmapByTextureId[textureObject->GetExternalIndex()] = (param != 0.0f);
break; break;
@@ -1883,6 +1913,11 @@ namespace MobileGL::MG_Impl::GLImpl {
textureObject->GetSamplerObject()->GetSamplerCompareFunc()); textureObject->GetSamplerObject()->GetSamplerCompareFunc());
} }
break; break;
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
if (params) {
*params = static_cast<GLint>(textureObject->GetSamplerObject()->GetMaxAnisotropy());
}
break;
case GL_IMAGE_FORMAT_COMPATIBILITY_TYPE: case GL_IMAGE_FORMAT_COMPATIBILITY_TYPE:
if (params) { if (params) {
*params = GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE; *params = GL_IMAGE_FORMAT_COMPATIBILITY_BY_SIZE;
@@ -2029,6 +2064,11 @@ namespace MobileGL::MG_Impl::GLImpl {
textureObject->GetSamplerObject()->GetSamplerCompareFunc()); textureObject->GetSamplerObject()->GetSamplerCompareFunc());
} }
break; break;
case GL_TEXTURE_MAX_ANISOTROPY_EXT:
if (params) {
*params = textureObject->GetSamplerObject()->GetMaxAnisotropy();
}
break;
default: default:
MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum, MG_State::pGLContext->RecordError(ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetTexParameterfv_State", MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "GetTexParameterfv_State",
@@ -2300,7 +2340,7 @@ namespace MobileGL::MG_Impl::GLImpl {
for (SizeT i = 0; i < static_cast<SizeT>(n); ++i) { for (SizeT i = 0; i < static_cast<SizeT>(n); ++i) {
Uint textureName = textures[i]; Uint textureName = textures[i];
if (textureName == 0) continue; if (textureName == 0) continue;
if (!TextureImpl::ValidateTextureName(textureName, true)) continue; if (!MG_State::pGLContext->ValidateTextureName(textureName)) continue;
MG_State::pGLContext->MarkTextureObjectForDeletion(textureName); MG_State::pGLContext->MarkTextureObjectForDeletion(textureName);
} }
} }
@@ -2523,14 +2563,7 @@ namespace MobileGL::MG_Impl::GLImpl {
return; return;
} }
if (!MG_State::pGLContext->ValidateTextureName(texture)) { if (!TextureImpl::ValidateTextureName(texture)) return;
MG_State::pGLContext->RecordError(
ErrorCode::InvalidOperation,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "BindTexture_State", "Invalid texture name"));
return;
}
if (!TextureImpl::ValidateTextureName(texture, true)) return;
// ======================= Processing ================================ // ======================= Processing ================================
Bool doesTextureExist = MG_State::pGLContext->ValidateTextureObject(texture); Bool doesTextureExist = MG_State::pGLContext->ValidateTextureObject(texture);
@@ -78,6 +78,13 @@ namespace MobileGL {
++m_version; ++m_version;
} }
void SamplerObject::SetMaxAnisotropy(Float maxAnisotropy) {
if (maxAnisotropy == m_samplerParameters.maxAnisotropy) return;
m_samplerParameters.maxAnisotropy = maxAnisotropy;
++m_version;
}
void SamplerObject::SetSamplerCompareFunc(SamplerCompareFunc func) { void SamplerObject::SetSamplerCompareFunc(SamplerCompareFunc func) {
if (func == m_samplerParameters.compareFunc) return; if (func == m_samplerParameters.compareFunc) return;
@@ -128,6 +135,10 @@ namespace MobileGL {
return m_samplerParameters.lodBias; return m_samplerParameters.lodBias;
} }
Float SamplerObject::GetMaxAnisotropy() const {
return m_samplerParameters.maxAnisotropy;
}
SamplerCompareMode SamplerObject::GetCompareMode() const { SamplerCompareMode SamplerObject::GetCompareMode() const {
return m_samplerParameters.compareMode; return m_samplerParameters.compareMode;
} }
@@ -65,6 +65,7 @@ namespace MobileGL {
Float minLod = -1000.0f; Float minLod = -1000.0f;
Float maxLod = 1000.0f; Float maxLod = 1000.0f;
Float lodBias = 0.0f; Float lodBias = 0.0f;
Float maxAnisotropy = 1.0f;
SamplerCompareFunc compareFunc = SamplerCompareFunc::Always; SamplerCompareFunc compareFunc = SamplerCompareFunc::Always;
SamplerCompareMode compareMode = SamplerCompareMode::None; SamplerCompareMode compareMode = SamplerCompareMode::None;
}; };
@@ -83,6 +84,7 @@ namespace MobileGL {
void SetMipmapMode(SamplerMipmapMode mode); void SetMipmapMode(SamplerMipmapMode mode);
void SetLodRange(Float minLod, Float maxLod); void SetLodRange(Float minLod, Float maxLod);
void SetLodBias(Float bias); void SetLodBias(Float bias);
void SetMaxAnisotropy(Float maxAnisotropy);
void SetSamplerCompareFunc(SamplerCompareFunc func); void SetSamplerCompareFunc(SamplerCompareFunc func);
void SetCompareMode(SamplerCompareMode mode); void SetCompareMode(SamplerCompareMode mode);
@@ -95,6 +97,7 @@ namespace MobileGL {
Float GetMinLod() const; Float GetMinLod() const;
Float GetMaxLod() const; Float GetMaxLod() const;
Float GetLodBias() const; Float GetLodBias() const;
Float GetMaxAnisotropy() const;
SamplerCompareMode GetCompareMode() const; SamplerCompareMode GetCompareMode() const;
SamplerCompareFunc GetSamplerCompareFunc() const; SamplerCompareFunc GetSamplerCompareFunc() const;
Uint GetExternalIndex() const; Uint GetExternalIndex() const;
@@ -109,8 +109,8 @@ namespace MobileGL::MG_State::GLState {
// re-resolved instead of dangling. // re-resolved instead of dangling.
BumpTextureBindGeneration(); BumpTextureBindGeneration();
m_textureObjects.erase(index); m_textureObjects.erase(index);
m_indexGenerator.Delete(index);
} }
m_indexGenerator.Delete(index);
} }
} }
@@ -9,6 +9,7 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <cstring> #include <cstring>
#include <map> #include <map>
#include <string>
#include <vector> #include <vector>
#include <MG_Util/BackendLoaders/OpenGL/Loader.h> #include <MG_Util/BackendLoaders/OpenGL/Loader.h>
@@ -28,6 +29,7 @@ namespace {
GLenum errorRaisedByDraw = GL_NO_ERROR; GLenum errorRaisedByDraw = GL_NO_ERROR;
GLenum pendingError = GL_NO_ERROR; GLenum pendingError = GL_NO_ERROR;
std::vector<std::string> extensions;
GLuint nextBufferId = 1; GLuint nextBufferId = 1;
GLuint nextShaderId = 1; GLuint nextShaderId = 1;
@@ -86,7 +88,7 @@ namespace {
*data = 1; *data = 1;
break; break;
case GL_NUM_EXTENSIONS: case GL_NUM_EXTENSIONS:
*data = 0; *data = static_cast<GLint>(g_fake.extensions.size());
break; break;
default: default:
// Leave the caller's defaults for every other capability query. // Leave the caller's defaults for every other capability query.
@@ -114,9 +116,10 @@ namespace {
return reinterpret_cast<const GLubyte*>(""); return reinterpret_cast<const GLubyte*>("");
} }
}; };
// GL_NUM_EXTENSIONS reports 0 above, so this is never reached; it exists so the funcs.glGetStringi = [](GLenum name, GLuint index) -> const GLubyte* {
// table stays complete if the extension loop ever runs. if (name != GL_EXTENSIONS || index >= g_fake.extensions.size()) return nullptr;
funcs.glGetStringi = [](GLenum, GLuint) -> const GLubyte* { return nullptr; }; return reinterpret_cast<const GLubyte*>(g_fake.extensions[index].c_str());
};
funcs.glGetFloatv = [](GLenum pname, GLfloat* data) { funcs.glGetFloatv = [](GLenum pname, GLfloat* data) {
switch (pname) { switch (pname) {
// Two-component range queries. // Two-component range queries.
@@ -400,3 +403,20 @@ TEST(IndirectInstanceIdProbe, FillInCapabilitiesWiresProbeResult) {
EXPECT_FALSE(conformingCaps.IndirectDrawInstanceIdIncludesBaseInstance); EXPECT_FALSE(conformingCaps.IndirectDrawInstanceIdIncludesBaseInstance);
ExpectProbeReleasedAllObjects(); ExpectProbeReleasedAllObjects();
} }
TEST(TextureAnisotropyCapabilities, ExtensionPresenceIsDetectedExactly) {
ResetFakeDriver();
g_fake.maxVertexSsboBlocks = 0;
const auto funcs = MakeFakeGLESFunctions();
MobileGL::MG_External::GLESCapabilities absentCaps;
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(absentCaps, funcs));
EXPECT_FALSE(absentCaps.SupportsTextureFilterAnisotropy);
ResetFakeDriver();
g_fake.maxVertexSsboBlocks = 0;
g_fake.extensions.emplace_back("GL_EXT_texture_filter_anisotropic");
MobileGL::MG_External::GLESCapabilities presentCaps;
ASSERT_TRUE(MobileGL::MG_Util::BackendLoader::FillInGLESCapabilities(presentCaps, funcs));
EXPECT_TRUE(presentCaps.SupportsTextureFilterAnisotropy);
}
+242 -5
View File
@@ -107,7 +107,7 @@ void main() {
PreprocessShaderSource(ShaderStage::Vertex, source); PreprocessShaderSource(ShaderStage::Vertex, source);
EXPECT_EQ(source.find("#version 460 core\n"), 0); EXPECT_EQ(source.find("#version 330 core\n"), 0);
EXPECT_NE(source.find("in vec3 position;"), String::npos); EXPECT_NE(source.find("in vec3 position;"), String::npos);
EXPECT_NE(source.find("out vec2 uv;"), String::npos); EXPECT_NE(source.find("out vec2 uv;"), String::npos);
EXPECT_EQ(source.find("attribute"), String::npos); EXPECT_EQ(source.find("attribute"), String::npos);
@@ -136,7 +136,7 @@ void main() {
PreprocessShaderSource(ShaderStage::Fragment, source); PreprocessShaderSource(ShaderStage::Fragment, source);
EXPECT_EQ(source.find("#version 460 core\n"), 0); EXPECT_EQ(source.find("#version 330 core\n"), 0);
EXPECT_NE(source.find("out vec4 mg_FragColor;\n"), String::npos); EXPECT_NE(source.find("out vec4 mg_FragColor;\n"), String::npos);
EXPECT_NE(source.find("in vec2 uv;"), String::npos); EXPECT_NE(source.find("in vec2 uv;"), String::npos);
EXPECT_NE(source.find("texture(texture0, uv)"), String::npos); EXPECT_NE(source.find("texture(texture0, uv)"), String::npos);
@@ -153,6 +153,243 @@ void main() {
} }
} }
TEST_F(ProgramUtilTest, PreprocessMinecraft112BlurShaderKeepsLegacySampleIdentifier) {
using namespace MG_Util::ShaderTranspiler;
// assets/minecraft/shaders/program/blur.fsh from the unmodified Minecraft 1.12 client jar.
String source = R"(#version 120
uniform sampler2D DiffuseSampler;
varying vec2 texCoord;
varying vec2 oneTexel;
uniform vec2 InSize;
uniform vec2 BlurDir;
uniform float Radius;
void main() {
vec4 blurred = vec4(0.0);
float totalStrength = 0.0;
float totalAlpha = 0.0;
float totalSamples = 0.0;
for(float r = -Radius; r <= Radius; r += 1.0) {
vec4 sample = texture2D(DiffuseSampler, texCoord + oneTexel * r * BlurDir);
// Accumulate average alpha
totalAlpha = totalAlpha + sample.a;
totalSamples = totalSamples + 1.0;
// Accumulate smoothed blur
float strength = 1.0 - abs(r / Radius);
totalStrength = totalStrength + strength;
blurred = blurred + sample;
}
gl_FragColor = vec4(blurred.rgb / (Radius * 2.0 + 1.0), totalAlpha);
}
)";
PreprocessShaderSource(ShaderStage::Fragment, source);
EXPECT_EQ(source.find("#version 330 core\n"), 0);
EXPECT_NE(source.find("vec4 sample = texture(DiffuseSampler"), String::npos);
EXPECT_NE(source.find("totalAlpha = totalAlpha + sample.a;"), String::npos);
EXPECT_NE(source.find("float totalSamples = 0.0;"), String::npos);
EXPECT_NE(source.find("totalSamples = totalSamples + 1.0;"), String::npos);
EXPECT_NE(source.find("blurred = blurred + sample;"), String::npos);
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
auto res = ShaderCompiler::CompileShader(attrib);
if (!res) {
FAIL() << "errc: " << res.error().errc << "\nlog: " << res.error().log << "\nsource:\n" << source;
}
}
TEST_F(ProgramUtilTest, PreprocessLegacySampleInterfaceIdentifiersKeepNames) {
using namespace MG_Util::ShaderTranspiler;
String vertexSource = R"(#version 150
attribute vec3 sample;
void main() {
gl_Position = vec4(sample, 1.0);
}
)";
PreprocessShaderSource(ShaderStage::Vertex, vertexSource);
EXPECT_EQ(vertexSource.find("#version 330 core\n"), 0);
EXPECT_NE(vertexSource.find("in vec3 sample;"), String::npos);
ShaderAttrib vertexAttrib{.shaderType = GL_VERTEX_SHADER, .sourceStr = vertexSource};
auto vertexResult = ShaderCompiler::CompileShader(vertexAttrib);
if (!vertexResult) {
FAIL() << "errc: " << vertexResult.error().errc << "\nlog: " << vertexResult.error().log
<< "\nsource:\n" << vertexSource;
}
String fragmentSource = R"(#version 150
uniform sampler2D sample;
varying vec2 texCoord;
void main() {
gl_FragColor = texture2D(sample, texCoord);
}
)";
PreprocessShaderSource(ShaderStage::Fragment, fragmentSource);
EXPECT_EQ(fragmentSource.find("#version 330 core\n"), 0);
EXPECT_NE(fragmentSource.find("uniform sampler2D sample;"), String::npos);
EXPECT_NE(fragmentSource.find("texture(sample, texCoord)"), String::npos);
ShaderAttrib fragmentAttrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = fragmentSource};
auto fragmentResult = ShaderCompiler::CompileShader(fragmentAttrib);
if (!fragmentResult) {
FAIL() << "errc: " << fragmentResult.error().errc << "\nlog: " << fragmentResult.error().log
<< "\nsource:\n" << fragmentSource;
}
}
TEST_F(ProgramUtilTest, PreprocessEsslVersionsRemainVulkanCompatible) {
using namespace MG_Util::ShaderTranspiler;
const auto verifyVersion = [](const char* inputVersion, const char* expectedVersion) {
SCOPED_TRACE(inputVersion);
String source = inputVersion;
source += R"(
precision mediump float;
out vec4 fragColor;
void main() {
fragColor = vec4(1.0);
}
)";
PreprocessShaderSource(ShaderStage::Fragment, source);
EXPECT_EQ(source.find(expectedVersion), 0);
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
auto res = ShaderCompiler::CompileShader(attrib);
if (!res) {
FAIL() << "errc: " << res.error().errc << "\nlog: " << res.error().log << "\nsource:\n" << source;
}
};
// Preserve the pre-existing desktop-core route: the current resource table cannot parse ESSL built-ins.
verifyVersion("#version 300 es", "#version 460 core\n");
verifyVersion("#version 310 es", "#version 460 core\n");
}
TEST_F(ProgramUtilTest, PreprocessModernDesktopVersionsRecognizesUtf8Bom) {
using namespace MG_Util::ShaderTranspiler;
const auto verifyVersion = [](const char* inputVersion) {
SCOPED_TRACE(inputVersion);
String source = "\xef\xbb\xbf";
source += inputVersion;
source += R"(
out vec4 fragColor;
void main() {
fragColor = vec4(1.0);
}
)";
PreprocessShaderSource(ShaderStage::Fragment, source);
EXPECT_EQ(source.find("#version 460 core\n"), 0);
EXPECT_EQ(source.find("\xef\xbb\xbf"), String::npos);
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
auto res = ShaderCompiler::CompileShader(attrib);
if (!res) {
FAIL() << "errc: " << res.error().errc << "\nlog: " << res.error().log << "\nsource:\n" << source;
}
};
verifyVersion("#version 400 core");
verifyVersion("#version 460 core");
}
TEST_F(ProgramUtilTest, PreprocessUsesRealSpacedVersionDirectiveForInjectedOutput) {
using namespace MG_Util::ShaderTranspiler;
String source = R"(// #version 460 core
/* "#version 400 core" */
#line 7 "#version 460 core"
# version 120
varying vec2 uv;
void main() {
gl_FragColor = vec4(uv, 0.0, 1.0);
}
)";
PreprocessShaderSource(ShaderStage::Fragment, source);
const SizeT versionPos = source.find("#version 330 core\n");
const SizeT outputPos = source.find("out vec4 mg_FragColor;\n");
EXPECT_NE(versionPos, String::npos);
EXPECT_EQ(outputPos, versionPos + std::strlen("#version 330 core\n"));
EXPECT_NE(source.find("// #version 460 core"), String::npos);
EXPECT_EQ(source.find("#line"), String::npos);
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
auto res = ShaderCompiler::CompileShader(attrib);
if (!res) {
FAIL() << "errc: " << res.error().errc << "\nlog: " << res.error().log << "\nsource:\n" << source;
}
}
TEST_F(ProgramUtilTest, PreprocessModernSampleQualifierStaysAtVersion460) {
using namespace MG_Util::ShaderTranspiler;
String source = R"(#version 400 core
sample in vec4 interpolatedColor;
out vec4 fragColor;
void main() {
fragColor = interpolatedColor;
}
)";
PreprocessShaderSource(ShaderStage::Fragment, source);
EXPECT_EQ(source.find("#version 460 core\n"), 0);
EXPECT_NE(source.find("sample in vec4 interpolatedColor;"), String::npos);
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
auto res = ShaderCompiler::CompileShader(attrib);
if (!res) {
FAIL() << "errc: " << res.error().errc << "\nlog: " << res.error().log << "\nsource:\n" << source;
}
}
TEST_F(ProgramUtilTest, PreprocessGpuShader5SampleQualifierUsesVersion460) {
using namespace MG_Util::ShaderTranspiler;
for (const char* extension : {"GL_ARB_gpu_shader5", "GL_NV_gpu_shader5"}) {
SCOPED_TRACE(extension);
String source = "#version 150\n#extension ";
source += extension;
source += R"( : enable
sample in vec4 interpolatedColor;
out vec4 fragColor;
void main() {
fragColor = interpolatedColor;
}
)";
PreprocessShaderSource(ShaderStage::Fragment, source);
EXPECT_EQ(source.find("#version 460 core\n"), 0);
EXPECT_NE(source.find("sample in vec4 interpolatedColor;"), String::npos);
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
auto res = ShaderCompiler::CompileShader(attrib);
if (!res) {
FAIL() << "errc: " << res.error().errc << "\nlog: " << res.error().log << "\nsource:\n" << source;
}
}
}
TEST_F(ProgramUtilTest, PreprocessLegacyFragmentShaderModernizesFragData) { TEST_F(ProgramUtilTest, PreprocessLegacyFragmentShaderModernizesFragData) {
using namespace MG_Util::ShaderTranspiler; using namespace MG_Util::ShaderTranspiler;
@@ -164,7 +401,7 @@ void main() {
PreprocessShaderSource(ShaderStage::Fragment, source); PreprocessShaderSource(ShaderStage::Fragment, source);
EXPECT_EQ(source.find("#version 460 core\n"), 0); EXPECT_EQ(source.find("#version 330 core\n"), 0);
EXPECT_NE(source.find("layout(location = 0) out vec4 mg_FragData[8];\n"), String::npos); EXPECT_NE(source.find("layout(location = 0) out vec4 mg_FragData[8];\n"), String::npos);
EXPECT_NE(source.find("mg_FragData[0] = vec4(1.0);"), String::npos); EXPECT_NE(source.find("mg_FragData[0] = vec4(1.0);"), String::npos);
EXPECT_NE(source.find("mg_FragData[1].a = 0.5;"), String::npos); EXPECT_NE(source.find("mg_FragData[1].a = 0.5;"), String::npos);
@@ -182,7 +419,7 @@ TEST_F(ProgramUtilTest, PreprocessKeepsDefaultPrecisionStatements) {
// Mirrors the GL CTS helper shaders (e.g. glcPixelStorageModesTests): the old qualifier strip // Mirrors the GL CTS helper shaders (e.g. glcPixelStorageModesTests): the old qualifier strip
// turned "precision highp float;" into invalid "precision float;". Precision qualifiers are // turned "precision highp float;" into invalid "precision float;". Precision qualifiers are
// legal (and ignored) in the forced 460 core profile, so they now pass through untouched. // legal (and ignored) in the normalized desktop core profile, so they now pass through untouched.
String source = R"(#version 330 String source = R"(#version 330
precision highp float; precision highp float;
precision mediump int; precision mediump int;
@@ -212,7 +449,7 @@ TEST_F(ProgramUtilTest, PreprocessKeepsPrecisionInLegacyShaderForGlslang) {
using namespace MG_Util::ShaderTranspiler; using namespace MG_Util::ShaderTranspiler;
// Legacy ES-style shader: precision statements and qualifier macros are left for glslang // Legacy ES-style shader: precision statements and qualifier macros are left for glslang
// (its preprocessor expands the #define; the 460 core parse ignores the qualifiers). // (its preprocessor expands the #define; the normalized 330 core parse ignores the qualifiers).
String source = R"(#define HIGHP_OR_DEFAULT highp String source = R"(#define HIGHP_OR_DEFAULT highp
precision HIGHP_OR_DEFAULT float; precision HIGHP_OR_DEFAULT float;
precision mediump int; precision mediump int;
+292
View File
@@ -8,14 +8,18 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <limits>
#include "Includes.h" #include "Includes.h"
#include "Init.h" #include "Init.h"
#include <MG_Backend/BackendObjects.h> #include <MG_Backend/BackendObjects.h>
#include <MG_Impl/GLImpl/Getter/GL_Getter.h> #include <MG_Impl/GLImpl/Getter/GL_Getter.h>
#include <MG_Impl/GLImpl/RenderState/GL_RenderState.h> #include <MG_Impl/GLImpl/RenderState/GL_RenderState.h>
#include <MG_Impl/GLImpl/Sampler/GL_Sampler.h>
#include <MG_Impl/GLImpl/Texture/GL_Texture.h> #include <MG_Impl/GLImpl/Texture/GL_Texture.h>
#include <MG_State/GLState/Core.h> #include <MG_State/GLState/Core.h>
#include <MG_State/GLState/TextureState/TextureObject.h> #include <MG_State/GLState/TextureState/TextureObject.h>
#include <MG_Util/Converters/MGToMG/TextureEnumConverter.h>
#include <MG_Util/Texture/TextureFormatProcessor.h> #include <MG_Util/Texture/TextureFormatProcessor.h>
using namespace MobileGL; using namespace MobileGL;
@@ -130,6 +134,244 @@ TEST_F(TextureTest, CreateTexturesCreatesObjectsWithoutBinding) {
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
} }
TEST_F(TextureTest, TextureMaxAnisotropyDefaultsToOneAndRoundTripsWithoutRedundantVersionBumps) {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
const auto textureObject = MG_State::pGLContext->GetTextureObject(texture);
ASSERT_NE(textureObject, nullptr);
const auto& samplerObject = textureObject->GetSamplerObject();
ASSERT_NE(samplerObject, nullptr);
GLfloat floatValue = 0.0f;
MG_Impl::GLImpl::GetTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, &floatValue);
EXPECT_FLOAT_EQ(floatValue, 1.0f);
EXPECT_FLOAT_EQ(samplerObject->GetMaxAnisotropy(), 1.0f);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
const Uint16 initialVersion = samplerObject->GetVersion();
MG_Impl::GLImpl::TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 4.0f);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_FLOAT_EQ(samplerObject->GetMaxAnisotropy(), 4.0f);
EXPECT_EQ(samplerObject->GetVersion(), static_cast<Uint16>(initialVersion + 1));
GLint integerValue = 0;
MG_Impl::GLImpl::GetTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, &integerValue);
EXPECT_EQ(integerValue, 4);
MG_Impl::GLImpl::GetTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, &floatValue);
EXPECT_FLOAT_EQ(floatValue, 4.0f);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
const Uint16 setVersion = samplerObject->GetVersion();
MG_Impl::GLImpl::TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 4.0f);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_EQ(samplerObject->GetVersion(), setVersion);
MG_Impl::GLImpl::TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_FLOAT_EQ(samplerObject->GetMaxAnisotropy(), 8.0f);
EXPECT_EQ(samplerObject->GetVersion(), static_cast<Uint16>(setVersion + 1));
}
TEST_F(TextureTest, TextureMaxAnisotropyBelowOneIsInvalidValueAndPreservesState) {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
const auto textureObject = MG_State::pGLContext->GetTextureObject(texture);
ASSERT_NE(textureObject, nullptr);
const auto& samplerObject = textureObject->GetSamplerObject();
ASSERT_NE(samplerObject, nullptr);
const Uint16 initialVersion = samplerObject->GetVersion();
MG_Impl::GLImpl::TexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 0.5f);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_VALUE);
EXPECT_FLOAT_EQ(samplerObject->GetMaxAnisotropy(), 1.0f);
EXPECT_EQ(samplerObject->GetVersion(), initialVersion);
MG_Impl::GLImpl::TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 0);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_VALUE);
EXPECT_FLOAT_EQ(samplerObject->GetMaxAnisotropy(), 1.0f);
EXPECT_EQ(samplerObject->GetVersion(), initialVersion);
}
TEST_F(TextureTest, SamplerMaxAnisotropyUsesTheSameStateAndValidationSemantics) {
GLuint sampler = 0;
MG_Impl::GLImpl::GenSamplers(1, &sampler);
ASSERT_NE(sampler, 0u);
GLfloat floatValue = 0.0f;
MG_Impl::GLImpl::GetSamplerParameterfv(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, &floatValue);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_FLOAT_EQ(floatValue, 1.0f);
const auto& samplerObject = MG_State::pGLContext->GetSamplerObject(sampler);
ASSERT_NE(samplerObject, nullptr);
const Uint16 initialVersion = samplerObject->GetVersion();
MG_Impl::GLImpl::SamplerParameterf(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, 6.0f);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_FLOAT_EQ(samplerObject->GetMaxAnisotropy(), 6.0f);
EXPECT_EQ(samplerObject->GetVersion(), static_cast<Uint16>(initialVersion + 1));
GLint integerValue = 0;
MG_Impl::GLImpl::GetSamplerParameteriv(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, &integerValue);
EXPECT_EQ(integerValue, 6);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
const Uint16 setVersion = samplerObject->GetVersion();
MG_Impl::GLImpl::SamplerParameterf(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, 6.0f);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_EQ(samplerObject->GetVersion(), setVersion);
MG_Impl::GLImpl::SamplerParameterf(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, 0.25f);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_VALUE);
EXPECT_FLOAT_EQ(samplerObject->GetMaxAnisotropy(), 6.0f);
EXPECT_EQ(samplerObject->GetVersion(), setVersion);
MG_Impl::GLImpl::SamplerParameteri(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, 0);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_VALUE);
EXPECT_FLOAT_EQ(samplerObject->GetMaxAnisotropy(), 6.0f);
EXPECT_EQ(samplerObject->GetVersion(), setVersion);
const GLint signedInvalidValue = -1;
MG_Impl::GLImpl::SamplerParameterIiv(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, &signedInvalidValue);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_VALUE);
EXPECT_FLOAT_EQ(samplerObject->GetMaxAnisotropy(), 6.0f);
EXPECT_EQ(samplerObject->GetVersion(), setVersion);
const GLuint unsignedValue = 10;
MG_Impl::GLImpl::SamplerParameterIuiv(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, &unsignedValue);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_FLOAT_EQ(samplerObject->GetMaxAnisotropy(), 10.0f);
EXPECT_EQ(samplerObject->GetVersion(), static_cast<Uint16>(setVersion + 1));
GLuint queriedUnsignedValue = 0;
MG_Impl::GLImpl::GetSamplerParameterIuiv(sampler, GL_TEXTURE_MAX_ANISOTROPY_EXT, &queriedUnsignedValue);
EXPECT_EQ(queriedUnsignedValue, unsignedValue);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
TEST_F(TextureTest, DeleteGeneratedReservationThenBindCreatesObjectForSubImageUpload) {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
ASSERT_NE(texture, 0u);
ASSERT_TRUE(MG_State::pGLContext->ValidateTextureName(texture));
ASSERT_FALSE(MG_State::pGLContext->ValidateTextureObject(texture));
MG_Impl::GLImpl::DeleteTextures(1, &texture);
EXPECT_TRUE(MG_State::pGLContext->ValidateTextureName(texture));
EXPECT_FALSE(MG_State::pGLContext->ValidateTextureObject(texture));
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
const auto textureObject = MG_State::pGLContext->GetTextureObject(texture);
ASSERT_NE(textureObject, nullptr);
EXPECT_TRUE(MG_State::pGLContext->ValidateTextureName(texture));
EXPECT_TRUE(MG_State::pGLContext->ValidateTextureObject(texture));
EXPECT_EQ(MG_Impl::GLImpl::IsTexture(texture), GL_TRUE);
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 1, 0, GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV, nullptr);
const Uint8 pixels[] = {
10, 20, 30, 40,
50, 60, 70, 80,
};
MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV, pixels);
const auto* stored = GetBoundTexture2DLevelBytes(texture);
ASSERT_NE(stored, nullptr);
const Uint8 expected[] = {
30, 20, 10, 40,
70, 60, 50, 80,
};
EXPECT_EQ(std::memcmp(stored, expected, sizeof(expected)), 0);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
TEST_F(TextureTest, DeleteInstantiatedTextureInvalidatesNameUntilRegenerated) {
GLuint textures[2] = {};
MG_Impl::GLImpl::GenTextures(2, textures);
ASSERT_NE(textures[0], 0u);
ASSERT_NE(textures[1], 0u);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, textures[0]);
ASSERT_TRUE(MG_State::pGLContext->ValidateTextureObject(textures[0]));
MG_Impl::GLImpl::DeleteTextures(1, &textures[0]);
EXPECT_FALSE(MG_State::pGLContext->ValidateTextureName(textures[0]));
EXPECT_FALSE(MG_State::pGLContext->ValidateTextureObject(textures[0]));
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, textures[1]);
const auto fallbackObject = MG_State::pGLContext->GetTextureObject(textures[1]);
ASSERT_NE(fallbackObject, nullptr);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, textures[0]);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_VALUE);
EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(0)
.GetBindingSlot(TextureTarget::Texture2D)
.GetBoundObject(),
fallbackObject);
}
TEST_F(TextureTest, DeleteUnknownNamesIsSilentButBindUnknownNameIsInvalid) {
GLuint validTexture = 0;
MG_Impl::GLImpl::GenTextures(1, &validTexture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, validTexture);
const auto boundObject = MG_State::pGLContext->GetTextureObject(validTexture);
ASSERT_NE(boundObject, nullptr);
constexpr GLuint unknownNames[] = {0, std::numeric_limits<GLuint>::max()};
MG_Impl::GLImpl::DeleteTextures(2, unknownNames);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, unknownNames[1]);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_VALUE);
EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(0)
.GetBindingSlot(TextureTarget::Texture2D)
.GetBoundObject(),
boundObject);
EXPECT_FALSE(MG_State::pGLContext->ValidateTextureName(unknownNames[1]));
EXPECT_FALSE(MG_State::pGLContext->ValidateTextureObject(unknownNames[1]));
}
TEST_F(TextureTest, BindTextureUnitEnumAsNameIsSilentNoOp) {
GLuint validTexture = 0;
MG_Impl::GLImpl::GenTextures(1, &validTexture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, validTexture);
const auto boundObject = MG_State::pGLContext->GetTextureObject(validTexture);
ASSERT_NE(boundObject, nullptr);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
constexpr GLuint textureUnitEnum = GL_TEXTURE7;
ASSERT_FALSE(MG_State::pGLContext->ValidateTextureName(textureUnitEnum));
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, textureUnitEnum);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
EXPECT_EQ(MG_State::pGLContext->GetTextureUnitObject(0)
.GetBindingSlot(TextureTarget::Texture2D)
.GetBoundObject(),
boundObject);
EXPECT_FALSE(MG_State::pGLContext->ValidateTextureName(textureUnitEnum));
EXPECT_FALSE(MG_State::pGLContext->ValidateTextureObject(textureUnitEnum));
}
TEST_F(TextureTest, TexSubImage2DWithoutBoundTextureReportsErrorInsteadOfDereferencingNull) {
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, 0);
ASSERT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
const Uint8 pixel[] = {1, 2, 3, 4};
MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION);
}
TEST_F(TextureTest, TextureStorageAndSubImageModifyNamedObjectOnly) { TEST_F(TextureTest, TextureStorageAndSubImageModifyNamedObjectOnly) {
GLuint namedTexture = 0; GLuint namedTexture = 0;
GLuint boundTexture = 0; GLuint boundTexture = 0;
@@ -368,6 +610,56 @@ TEST_F(TextureTest, BoundTexSubImage2DUnpacksPackedBgra8888RevToRgba8) {
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
} }
TEST_F(TextureTest, UnsizedRgbaInfersRgba8ForPacked8888Types) {
EXPECT_EQ(MG_Util::ConvertInternalFormatToSized(TextureInternalFormat::RGBA, TextureInputFormat::BGRA,
TexturePixelDataType::UnsignedInt8888),
TextureInternalFormat::RGBA8);
EXPECT_EQ(MG_Util::ConvertInternalFormatToSized(TextureInternalFormat::RGBA, TextureInputFormat::BGRA,
TexturePixelDataType::UnsignedInt8888Rev),
TextureInternalFormat::RGBA8);
}
TEST_F(TextureTest, BoundTexImageAndSubImage2DUseInferredRgba8ForPackedBgra8888Rev) {
GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture);
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
const Uint8 initialPixels[] = {
10, 20, 30, 40,
50, 60, 70, 80,
};
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 1, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
initialPixels);
const auto textureObject = MG_State::pGLContext->GetTextureObject(texture);
EXPECT_EQ(textureObject->GetFormat(), TextureInternalFormat::RGBA8);
const auto* stored = GetBoundTexture2DLevelBytes(texture);
const Uint8 expectedInitial[] = {
30, 20, 10, 40,
70, 60, 50, 80,
};
for (SizeT i = 0; i < sizeof(expectedInitial); ++i) {
EXPECT_EQ(stored[i], expectedInitial[i]) << "initial byte " << i;
}
const Uint8 updatedPixels[] = {
90, 100, 110, 120,
130, 140, 150, 160,
};
MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
updatedPixels);
stored = GetBoundTexture2DLevelBytes(texture);
const Uint8 expectedUpdated[] = {
110, 100, 90, 120,
150, 140, 130, 160,
};
for (SizeT i = 0; i < sizeof(expectedUpdated); ++i) {
EXPECT_EQ(stored[i], expectedUpdated[i]) << "updated byte " << i;
}
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
}
TEST_F(TextureTest, BoundTexSubImage2DUnpacksPackedRgba8888ToRgba8) { TEST_F(TextureTest, BoundTexSubImage2DUnpacksPackedRgba8888ToRgba8) {
GLuint texture = 0; GLuint texture = 0;
MG_Impl::GLImpl::GenTextures(1, &texture); MG_Impl::GLImpl::GenTextures(1, &texture);
@@ -799,6 +799,9 @@ namespace MobileGL::MG_Util::BackendLoader {
if (std::strcmp(extension, "GL_EXT_texture_norm16") == 0) { if (std::strcmp(extension, "GL_EXT_texture_norm16") == 0) {
caps.SupportsNorm16Texture = true; caps.SupportsNorm16Texture = true;
} }
if (std::strcmp(extension, "GL_EXT_texture_filter_anisotropic") == 0) {
caps.SupportsTextureFilterAnisotropy = true;
}
if (std::strcmp(extension, "GL_EXT_base_instance") == 0) { if (std::strcmp(extension, "GL_EXT_base_instance") == 0) {
caps.SupportsBaseInstance = true; caps.SupportsBaseInstance = true;
} }
@@ -1031,6 +1031,9 @@ namespace MobileGL {
String GLESShadingLanguageVersionString; String GLESShadingLanguageVersionString;
Bool SupportsPersistentMapping = false; Bool SupportsPersistentMapping = false;
Bool SupportsNorm16Texture = false; Bool SupportsNorm16Texture = false;
// GL_EXT_texture_filter_anisotropic is present, so sampler/texture
// anisotropy may be forwarded without raising GL_INVALID_ENUM in GLES.
Bool SupportsTextureFilterAnisotropy = false;
Bool SupportsBaseInstance = false; Bool SupportsBaseInstance = false;
// GL_EXT_disjoint_timer_query is present in the extension string. // GL_EXT_disjoint_timer_query is present in the extension string.
Bool SupportsDisjointTimerQuery = false; Bool SupportsDisjointTimerQuery = false;
@@ -129,6 +129,8 @@ namespace MobileGL {
case TextureInternalFormat::RGBA: { case TextureInternalFormat::RGBA: {
switch (type) { switch (type) {
case TexturePixelDataType::UnsignedByte: case TexturePixelDataType::UnsignedByte:
case TexturePixelDataType::UnsignedInt8888:
case TexturePixelDataType::UnsignedInt8888Rev:
return TextureInternalFormat::RGBA8; return TextureInternalFormat::RGBA8;
case TexturePixelDataType::UnsignedShort: case TexturePixelDataType::UnsignedShort:
return TextureInternalFormat::RGBA16; return TextureInternalFormat::RGBA16;
@@ -19,6 +19,220 @@ namespace {
return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_'; return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_';
} }
bool IsIdentifierStart(char ch) {
return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_';
}
MobileGL::String MaskCommentsAndQuotedText(const MobileGL::String& source) {
enum class Region { Code, SingleLineComment, MultiLineComment, QuotedText };
MobileGL::String masked = source;
Region region = Region::Code;
char quote = '\0';
bool escaped = false;
for (SizeT pos = 0; pos < source.size(); pos++) {
const char ch = source[pos];
const char next = pos + 1 < source.size() ? source[pos + 1] : '\0';
if (region == Region::Code) {
if (ch == '/' && next == '/') {
masked[pos] = ' ';
masked[pos + 1] = ' ';
pos++;
region = Region::SingleLineComment;
} else if (ch == '/' && next == '*') {
masked[pos] = ' ';
masked[pos + 1] = ' ';
pos++;
region = Region::MultiLineComment;
} else if (ch == '"' || ch == '\'') {
masked[pos] = ' ';
quote = ch;
escaped = false;
region = Region::QuotedText;
}
continue;
}
if (region == Region::SingleLineComment) {
if (ch == '\n' || ch == '\r') {
region = Region::Code;
} else {
masked[pos] = ' ';
}
continue;
}
if (region == Region::MultiLineComment) {
if (ch == '*' && next == '/') {
masked[pos] = ' ';
masked[pos + 1] = ' ';
pos++;
region = Region::Code;
} else if (ch != '\n' && ch != '\r') {
masked[pos] = ' ';
}
continue;
}
if (ch != '\n' && ch != '\r') {
masked[pos] = ' ';
}
if (escaped) {
escaped = false;
} else if (ch == '\\') {
escaped = true;
} else if (ch == quote) {
region = Region::Code;
}
}
return masked;
}
void SkipDirectiveWhitespace(const MobileGL::String& source, SizeT& pos, SizeT lineEnd) {
while (pos < lineEnd && std::isspace(static_cast<unsigned char>(source[pos]))) {
pos++;
}
}
MobileGL::String ReadDirectiveIdentifier(const MobileGL::String& source, SizeT& pos, SizeT lineEnd) {
if (pos >= lineEnd || !IsIdentifierStart(source[pos])) {
return {};
}
const SizeT start = pos++;
while (pos < lineEnd && IsIdentifierChar(source[pos])) {
pos++;
}
return source.substr(start, pos - start);
}
bool HasUtf8Bom(const MobileGL::String& source) {
return source.size() >= 3 && static_cast<unsigned char>(source[0]) == 0xef &&
static_cast<unsigned char>(source[1]) == 0xbb && static_cast<unsigned char>(source[2]) == 0xbf;
}
struct ShaderLanguageInfo {
unsigned version = 110;
MobileGL::ShaderProfile profile = MobileGL::ShaderProfile::Core;
SizeT versionDirectiveStart = MobileGL::String::npos;
SizeT versionDirectiveEnd = MobileGL::String::npos;
bool hasUtf8Bom = false;
bool enablesGpuShader5 = false;
bool HasVersionDirective() const { return versionDirectiveStart != MobileGL::String::npos; }
};
ShaderLanguageInfo InspectShaderLanguage(const MobileGL::String& source) {
const MobileGL::String code = MaskCommentsAndQuotedText(source);
ShaderLanguageInfo info;
info.hasUtf8Bom = HasUtf8Bom(source);
SizeT lineStart = 0;
while (lineStart < code.size()) {
SizeT lineEnd = code.find('\n', lineStart);
const bool hasLineBreak = lineEnd != MobileGL::String::npos;
if (!hasLineBreak) {
lineEnd = code.size();
}
SizeT probe = lineStart;
if (lineStart == 0 && info.hasUtf8Bom) {
probe = 3;
}
SkipDirectiveWhitespace(code, probe, lineEnd);
if (probe < lineEnd && code[probe] == '#') {
const SizeT directiveStart = probe;
probe++;
SkipDirectiveWhitespace(code, probe, lineEnd);
const MobileGL::String directive = ReadDirectiveIdentifier(code, probe, lineEnd);
if (directive == "version" && !info.HasVersionDirective()) {
SkipDirectiveWhitespace(code, probe, lineEnd);
unsigned version = 0;
bool hasVersionDigits = false;
while (probe < lineEnd && code[probe] >= '0' && code[probe] <= '9') {
hasVersionDigits = true;
version = version * 10 + static_cast<unsigned>(code[probe] - '0');
probe++;
}
if (hasVersionDigits) {
info.version = version;
info.versionDirectiveStart = directiveStart;
info.versionDirectiveEnd = lineEnd + (hasLineBreak ? 1 : 0);
SkipDirectiveWhitespace(code, probe, lineEnd);
const MobileGL::String profile = ReadDirectiveIdentifier(code, probe, lineEnd);
if (profile == "es" || profile == "ES") {
info.profile = MobileGL::ShaderProfile::ES;
} else if (profile == "compatibility") {
info.profile = MobileGL::ShaderProfile::Compatibility;
} else {
info.profile = MobileGL::ShaderProfile::Core;
}
}
} else if (directive == "extension") {
SkipDirectiveWhitespace(code, probe, lineEnd);
const MobileGL::String extension = ReadDirectiveIdentifier(code, probe, lineEnd);
SkipDirectiveWhitespace(code, probe, lineEnd);
if (probe < lineEnd && code[probe] == ':') {
probe++;
SkipDirectiveWhitespace(code, probe, lineEnd);
const MobileGL::String behavior = ReadDirectiveIdentifier(code, probe, lineEnd);
const bool isGpuShader5 = extension == "GL_ARB_gpu_shader5" ||
extension == "GL_NV_gpu_shader5";
const bool enablesExtension = behavior == "enable" || behavior == "require" ||
behavior == "warn";
// Gate the whole source if it ever opts into either extension. This is deliberately
// conservative around conditional directives and keeps legal sample qualifiers intact.
info.enablesGpuShader5 = info.enablesGpuShader5 || (isGpuShader5 && enablesExtension);
}
}
}
lineStart = lineEnd + (hasLineBreak ? 1 : 0);
}
return info;
}
MobileGL::String GetNormalizedVersionDirective(const ShaderLanguageInfo& info) {
if (info.profile == MobileGL::ShaderProfile::ES) {
// Preserve the pre-existing behavior for standard lowercase "es" directives. MobileGL's Vulkan
// glslang resource table cannot parse its ESSL built-ins today, even at ESSL 310, whereas the same
// source is accepted through the normalized desktop core path.
return "#version 460 core\n";
}
// Keep compatibility-profile handling on its pre-existing 460 path. Vulkan glslang does not accept that
// profile today, and this legacy-sample fix must not broaden or otherwise alter that separate limitation.
if (info.profile == MobileGL::ShaderProfile::Compatibility) {
return "#version 460 compatibility\n";
}
const bool useLegacyDesktopVersion =
info.version < 400 && !info.enablesGpuShader5;
return useLegacyDesktopVersion ? "#version 330 core\n" : "#version 460 core\n";
}
void NormalizeVersionDirective(MobileGL::String& source, const ShaderLanguageInfo& info) {
const MobileGL::String replacement = GetNormalizedVersionDirective(info);
if (info.HasVersionDirective()) {
source.replace(info.versionDirectiveStart, info.versionDirectiveEnd - info.versionDirectiveStart,
replacement);
if (info.hasUtf8Bom) {
source.erase(0, 3);
}
return;
}
if (info.hasUtf8Bom) {
source.erase(0, 3);
}
source.insert(0, replacement);
}
bool HasSingleLineFunctionDefinition(const MobileGL::String& source, const MobileGL::String& functionName) { bool HasSingleLineFunctionDefinition(const MobileGL::String& source, const MobileGL::String& functionName) {
SizeT lineStart = 0; SizeT lineStart = 0;
while (lineStart < source.size()) { while (lineStart < source.size()) {
@@ -153,12 +367,8 @@ namespace {
} }
SizeT FindAfterVersionDirective(const MobileGL::String& source) { SizeT FindAfterVersionDirective(const MobileGL::String& source) {
const SizeT versionPos = source.find("#version"); const ShaderLanguageInfo info = InspectShaderLanguage(source);
if (versionPos == MobileGL::String::npos) { return info.HasVersionDirective() ? info.versionDirectiveEnd : 0;
return 0;
}
const SizeT lineEnd = source.find('\n', versionPos);
return lineEnd == MobileGL::String::npos ? source.size() : lineEnd + 1;
} }
bool IsExtensionAdvertised(MobileGL::GLExtension extension) { bool IsExtensionAdvertised(MobileGL::GLExtension extension) {
@@ -263,7 +473,7 @@ namespace {
void ModernizeLegacyGLSL(MobileGL::ShaderStage stage, MobileGL::String& source) { void ModernizeLegacyGLSL(MobileGL::ShaderStage stage, MobileGL::String& source) {
// Precision qualifiers (highp/mediump/lowp and default-precision statements) are legal and // Precision qualifiers (highp/mediump/lowp and default-precision statements) are legal and
// ignored in the forced "#version 460 core" profile, so glslang handles them natively. // ignored in the normalized desktop core profiles, so glslang handles them natively.
ReplaceIdentifier(source, "texture2D", "texture"); ReplaceIdentifier(source, "texture2D", "texture");
ReplaceIdentifier(source, "texture2DProj", "textureProj"); ReplaceIdentifier(source, "texture2DProj", "textureProj");
@@ -308,6 +518,11 @@ namespace MobileGL {
namespace MG_Util { namespace MG_Util {
namespace ShaderTranspiler { namespace ShaderTranspiler {
void PreprocessShaderSource(ShaderStage stage, String& source) { void PreprocessShaderSource(ShaderStage stage, String& source) {
// Normalize while the inspector's source span still refers to the untouched input. Later passes
// remove comments and directives, so any subsequent insertion re-inspects the current source.
const ShaderLanguageInfo originalLanguage = InspectShaderLanguage(source);
NormalizeVersionDirective(source, originalLanguage);
// remove multi-line comment // remove multi-line comment
size_t commentStartPos = source.find("/*"); size_t commentStartPos = source.find("/*");
while (commentStartPos != String::npos) { while (commentStartPos != String::npos) {
@@ -345,43 +560,6 @@ namespace MobileGL {
noperspectivePos = source.find(str_np); noperspectivePos = source.find(str_np);
} }
// force #version
ShaderProfile profile = ShaderProfile::Core;
SizeT versionPos = source.find("#version");
SizeT lineEnd = source.find('\n', versionPos);
if (versionPos != String::npos) {
String versionLine = source.substr(versionPos, lineEnd - versionPos);
if (versionLine.find("ES") != String::npos)
profile = ShaderProfile::ES;
else if (versionLine.find("compatibility") != String::npos)
profile = ShaderProfile::Compatibility;
else
profile = ShaderProfile::Core;
} else {
profile = ShaderProfile::Core;
source.insert(0, "#version 460 core\n");
versionPos = 0;
lineEnd = source.find('\n', versionPos);
}
SizeT firstLineEnd = lineEnd;
if (profile != ShaderProfile::ES) {
constexpr const char* versionDirectiveCore = "#version 460 core\n";
constexpr const char* versionDirectiveCompat = "#version 460 compatibility\n";
const char* replacement =
(profile == ShaderProfile::Compatibility) ? versionDirectiveCompat : versionDirectiveCore;
if (firstLineEnd != String::npos) {
source.replace(versionPos, firstLineEnd - versionPos + 1, replacement);
} else {
source = replacement;
}
}
FilterUnsupportedGpuShaderInt64(source); FilterUnsupportedGpuShaderInt64(source);
// Some shader packs define helpers with built-in GLSL names such as round(), tanh(), or fma(). // Some shader packs define helpers with built-in GLSL names such as round(), tanh(), or fma().