mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 22:28:32 +09:00
Compare commits
10
Commits
65ea54da9e
...
b6a7807a3a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6a7807a3a | ||
|
|
48ba622387 | ||
|
|
05260d1262 | ||
|
|
6eb5ff51c5 | ||
|
|
e526f8e8ac | ||
|
|
e724e88eec | ||
|
|
3b175fb88a | ||
|
|
b5a4e7075a | ||
|
|
520c2b6750 | ||
|
|
57cc652b1d |
@@ -2216,11 +2216,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TextureImpl::IsMultisampleTextureTarget(targetInternal)) {
|
// Multisample targets reject the *sampler* parameters (LOD range, border color) but
|
||||||
|
// GL_TEXTURE_SWIZZLE_* is texture state, not sampler state, and ES accepts it on them.
|
||||||
|
// Bailing out entirely used to drop every swizzle write on the floor, which is what the
|
||||||
|
// frontend already assumes is legal (see GL_Texture.cpp's MS-invalid pname list, which
|
||||||
|
// deliberately omits the swizzle enums). Note the caches for the skipped parameters are
|
||||||
|
// still refreshed so they never look stale, but m_cacheSwizzleParams must NOT be, or the
|
||||||
|
// change detection below would swallow the very writes we came here to emit.
|
||||||
|
const Bool isMultisampleTarget = TextureImpl::IsMultisampleTextureTarget(targetInternal);
|
||||||
|
if (isMultisampleTarget) {
|
||||||
m_cacheLodRange = stateTextureObject->GetLevelRange();
|
m_cacheLodRange = stateTextureObject->GetLevelRange();
|
||||||
m_cacheSwizzleParams = stateTextureObject->GetAllSwizzleParams();
|
|
||||||
m_cacheBorderColor = stateTextureObject->GetBorderColor();
|
m_cacheBorderColor = stateTextureObject->GetBorderColor();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Bind(target);
|
Bind(target);
|
||||||
@@ -2233,14 +2239,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
|
|
||||||
const auto& levelRange = stateTextureObject->GetLevelRange();
|
const auto& levelRange = stateTextureObject->GetLevelRange();
|
||||||
|
|
||||||
if (m_cacheLodRange.x() != levelRange.x()) {
|
if (!isMultisampleTarget && m_cacheLodRange.x() != levelRange.x()) {
|
||||||
g_GLESFuncs.glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, static_cast<GLint>(levelRange.x()));
|
g_GLESFuncs.glTexParameteri(target, GL_TEXTURE_BASE_LEVEL, static_cast<GLint>(levelRange.x()));
|
||||||
m_cacheLodRange.x() = levelRange.x();
|
m_cacheLodRange.x() = levelRange.x();
|
||||||
}
|
}
|
||||||
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());
|
||||||
});
|
});
|
||||||
if (m_cacheLodRange.y() != levelRange.y()) {
|
if (!isMultisampleTarget && m_cacheLodRange.y() != levelRange.y()) {
|
||||||
g_GLESFuncs.glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, static_cast<GLint>(levelRange.y()));
|
g_GLESFuncs.glTexParameteri(target, GL_TEXTURE_MAX_LEVEL, static_cast<GLint>(levelRange.y()));
|
||||||
m_cacheLodRange.y() = levelRange.y();
|
m_cacheLodRange.y() = levelRange.y();
|
||||||
}
|
}
|
||||||
@@ -2266,7 +2272,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_cacheBorderColor != stateTextureObject->GetBorderColor()) {
|
if (!isMultisampleTarget && m_cacheBorderColor != stateTextureObject->GetBorderColor()) {
|
||||||
const auto& borderColor = stateTextureObject->GetBorderColor();
|
const auto& borderColor = stateTextureObject->GetBorderColor();
|
||||||
GLfloat borderColorArray[4] = {borderColor.x(), borderColor.y(), borderColor.z(), borderColor.w()};
|
GLfloat borderColorArray[4] = {borderColor.x(), borderColor.y(), borderColor.z(), borderColor.w()};
|
||||||
g_GLESFuncs.glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, borderColorArray);
|
g_GLESFuncs.glTexParameterfv(target, GL_TEXTURE_BORDER_COLOR, borderColorArray);
|
||||||
|
|||||||
@@ -295,7 +295,13 @@ DECLARE_GL_FUNCTION_HEAD(void, VertexAttribDivisor, GLuint index, GLuint divisor
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindTransformFeedback, GLenum target, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindTransformFeedback, target, id)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindTransformFeedback, GLenum target, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindTransformFeedback, target, id)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteTransformFeedbacks, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteTransformFeedbacks, n, ids)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteTransformFeedbacks, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteTransformFeedbacks, n, ids)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenTransformFeedbacks, n, ids)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenTransformFeedbacks, n, ids)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLboolean, IsTransformFeedback, GLuint id) DECLARE_GL_FUNCTION_STUB_END(GLboolean, IsTransformFeedback, id)
|
// Transform feedback objects are not implemented, so no name is ever a live object. The shared
|
||||||
|
// stub returns (type)1, telling a probing caller that every id it invents already exists; GL_FALSE
|
||||||
|
// is both truthful and what the spec requires for a name that was never generated.
|
||||||
|
MOBILEGL_GL_API GLboolean glIsTransformFeedback(GLuint id) {
|
||||||
|
MGLOG_W("Stub function: %s(...)", __FUNCTION__);
|
||||||
|
return GL_FALSE;
|
||||||
|
}
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PauseTransformFeedback) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PauseTransformFeedback)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PauseTransformFeedback) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PauseTransformFeedback)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ResumeTransformFeedback) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ResumeTransformFeedback)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ResumeTransformFeedback) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ResumeTransformFeedback)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramBinary, GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, void* binary) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramBinary, program, bufSize, length, binaryFormat, binary)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GetProgramBinary, GLuint program, GLsizei bufSize, GLsizei* length, GLenum* binaryFormat, void* binary) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetProgramBinary, program, bufSize, length, binaryFormat, binary)
|
||||||
@@ -2583,7 +2589,10 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, TransformFeedbackStreamAttribsNV, GLsizei co
|
|||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindTransformFeedbackNV, GLenum target, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindTransformFeedbackNV, target, id)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindTransformFeedbackNV, GLenum target, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindTransformFeedbackNV, target, id)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteTransformFeedbacksNV, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteTransformFeedbacksNV, n, ids)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, DeleteTransformFeedbacksNV, GLsizei n, const GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DeleteTransformFeedbacksNV, n, ids)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenTransformFeedbacksNV, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenTransformFeedbacksNV, n, ids)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, GenTransformFeedbacksNV, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GenTransformFeedbacksNV, n, ids)
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(GLboolean, IsTransformFeedbackNV, GLuint id) DECLARE_GL_FUNCTION_STUB_END(GLboolean, IsTransformFeedbackNV, id)
|
MOBILEGL_GL_API GLboolean glIsTransformFeedbackNV(GLuint id) {
|
||||||
|
MGLOG_W("Stub function: %s(...)", __FUNCTION__);
|
||||||
|
return GL_FALSE;
|
||||||
|
}
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, PauseTransformFeedbackNV, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PauseTransformFeedbackNV, )
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, PauseTransformFeedbackNV, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PauseTransformFeedbackNV, )
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, ResumeTransformFeedbackNV, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ResumeTransformFeedbackNV, )
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, ResumeTransformFeedbackNV, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ResumeTransformFeedbackNV, )
|
||||||
DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackNV, GLenum mode, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackNV, mode, id)
|
DECLARE_GL_FUNCTION_STUB_HEAD(void, DrawTransformFeedbackNV, GLenum mode, GLuint id) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DrawTransformFeedbackNV, mode, id)
|
||||||
|
|||||||
@@ -350,6 +350,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
texture.AllocateStorage(uploadTarget, level, {levelTexelSize, levelByteSize});
|
texture.AllocateStorage(uploadTarget, level, {levelTexelSize, levelByteSize});
|
||||||
texture.MarkStorageDirty(uploadTarget, level, false);
|
texture.MarkStorageDirty(uploadTarget, level, false);
|
||||||
}
|
}
|
||||||
|
// glGenerateMipmap defines exactly levels 0..requiredLevelCount-1. AllocateStorage only
|
||||||
|
// grows, so a previously longer chain (a bigger base image before respecification) would
|
||||||
|
// otherwise keep a tail of stale levels here and read as incomplete.
|
||||||
|
texture.TruncateMipmapLevels(uploadTarget, requiredLevelCount);
|
||||||
// Mip generation grows/regenerates the level set on the GPU without marking any CPU
|
// Mip generation grows/regenerates the level set on the GPU without marking any CPU
|
||||||
// level dirty (MarkStorageDirty(...,false) above). Bump the content version so the
|
// level dirty (MarkStorageDirty(...,false) above). Bump the content version so the
|
||||||
// backend re-syncs: a cached sampled VkImageView built for the pre-generate level
|
// backend re-syncs: a cached sampled VkImageView built for the pre-generate level
|
||||||
@@ -429,8 +433,10 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
|
|
||||||
const Int maxSamples = GetMaxSupportedTextureSamples(textureInternalFormat);
|
const Int maxSamples = GetMaxSupportedTextureSamples(textureInternalFormat);
|
||||||
if (samples > maxSamples) {
|
if (samples > maxSamples) {
|
||||||
|
// GL specifies INVALID_OPERATION - not INVALID_VALUE - when the sample count
|
||||||
|
// exceeds what the format supports, and the native Adreno driver agrees.
|
||||||
MG_State::pGLContext->RecordError(
|
MG_State::pGLContext->RecordError(
|
||||||
ErrorCode::InvalidValue,
|
ErrorCode::InvalidOperation,
|
||||||
MakeUnique<GenericErrorInfo>(
|
MakeUnique<GenericErrorInfo>(
|
||||||
"MG_Impl/GLImpl", caller,
|
"MG_Impl/GLImpl", caller,
|
||||||
std::format("Sample count {} exceeds the supported maximum {} for this texture format.",
|
std::format("Sample count {} exceeds the supported maximum {} for this texture format.",
|
||||||
@@ -454,8 +460,56 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
textureObject->SetSamples(samples);
|
textureObject->SetSamples(samples);
|
||||||
textureObject->SetFixedSampleLocations(fixedsamplelocations == GL_TRUE);
|
textureObject->SetFixedSampleLocations(fixedsamplelocations == GL_TRUE);
|
||||||
textureMipmapObject->AllocateStorage(textureUploadTarget, 0, {{width, height, depth}, 0});
|
textureMipmapObject->AllocateStorage(textureUploadTarget, 0, {{width, height, depth}, 0});
|
||||||
|
// Multisample textures are single-level by definition, so a name that previously held a
|
||||||
|
// mip chain must not keep its tail now that AllocateStorage only grows.
|
||||||
|
textureMipmapObject->TruncateMipmapLevels(textureUploadTarget, 1);
|
||||||
textureMipmapObject->MarkStorageDirty(textureUploadTarget, 0, false);
|
textureMipmapObject->MarkStorageDirty(textureUploadTarget, 0, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Redefining level 0 of a texture that already had a base image drops the rest of the chain,
|
||||||
|
// which is exactly what AllocateLevel used to do implicitly for every level. Keeping that
|
||||||
|
// behaviour for level 0 - and only for level 0 - is what makes the grow-only change safe:
|
||||||
|
// any level-0 respecification leaves the chain in precisely the state it would have had
|
||||||
|
// before, while an upload to level N no longer destroys the levels beneath it.
|
||||||
|
//
|
||||||
|
// Why it has to be *every* level-0 respecification and not just a size change: Minecraft's
|
||||||
|
// Mipmap Levels setting rebuilds the block atlas at the SAME dimensions with a different
|
||||||
|
// level count. A size-only test would leave the old tail in place, and because Mojang
|
||||||
|
// terminates its chains with a 0x0 level the result is the zero-then-nonzero pattern that
|
||||||
|
// IsComplete() rejects (TextureObject.cpp) - whereupon DirectGLES skips syncing the texture
|
||||||
|
// entirely (Managers.cpp) and the atlas samples black.
|
||||||
|
//
|
||||||
|
// The "already has a base image" test is what lets the fix work at all: a level that was
|
||||||
|
// never written reads back as {0,0,0}, so building a chain top-down - upload level N first,
|
||||||
|
// then level 0 - must not discard the levels just uploaded. That ordering is what
|
||||||
|
// KHR-GL33.texture_repeat_mode does.
|
||||||
|
// Scoped to the respecified upload target only, which is what AllocateLevel already did.
|
||||||
|
// Cube maps keep six independent chains while reporting a single level count (face +X), so
|
||||||
|
// respecifying a face other than +X can leave the count longer than that face - but that
|
||||||
|
// asymmetry predates this change and widening the truncation to all six faces would destroy
|
||||||
|
// mip data for faces the application never touched. Left alone deliberately.
|
||||||
|
void DiscardMipmapChainOnBaseRespecification(MG_State::GLState::TextureObjectMipmap* texture,
|
||||||
|
TextureUploadTarget uploadTarget, Uint level) {
|
||||||
|
if (level != 0) return;
|
||||||
|
|
||||||
|
const IntVec3 existingBaseSize = texture->GetMipmapTexelSize(uploadTarget, 0);
|
||||||
|
const Bool hasExistingBaseImage =
|
||||||
|
existingBaseSize.x() > 0 && existingBaseSize.y() > 0 && existingBaseSize.z() > 0;
|
||||||
|
if (!hasExistingBaseImage) return;
|
||||||
|
|
||||||
|
texture->TruncateMipmapLevels(uploadTarget, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compressed texture upload is not implemented yet. GL_NUM_COMPRESSED_TEXTURE_FORMATS
|
||||||
|
// reports 0, so every compressed internalformat is by definition unsupported and
|
||||||
|
// GL_INVALID_ENUM is the specified error - unlike THROW_UNIMPL_EXCEPTION, which unwinds
|
||||||
|
// a C++ exception through the C GL ABI and takes the process down.
|
||||||
|
void RecordUnsupportedCompressedFormat(const char* caller) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidEnum,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", caller,
|
||||||
|
"Compressed texture formats are not supported."));
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
const SharedPtr<MG_State::GLState::ITextureObject>& GetTextureObjectByName(GLuint texture, const char* caller) {
|
const SharedPtr<MG_State::GLState::ITextureObject>& GetTextureObjectByName(GLuint texture, const char* caller) {
|
||||||
@@ -1670,6 +1724,21 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RGTC is a 2D-only compression scheme, so a 3D target rejects it. This has to be tested on
|
||||||
|
// the raw enum: the RGTC formats resolve to plain R8/RG8/SNORM storage on the way in (see
|
||||||
|
// GLToMG's TextureEnumConverter), so once the internal format is converted there is nothing
|
||||||
|
// left to distinguish them from an ordinary one- or two-channel upload.
|
||||||
|
if ((textureUploadTarget == TextureUploadTarget::Texture3D ||
|
||||||
|
textureUploadTarget == TextureUploadTarget::ProxyTexture3D) &&
|
||||||
|
(internalformat == GL_COMPRESSED_RED_RGTC1 || internalformat == GL_COMPRESSED_SIGNED_RED_RGTC1 ||
|
||||||
|
internalformat == GL_COMPRESSED_RG_RGTC2 || internalformat == GL_COMPRESSED_SIGNED_RG_RGTC2)) {
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"RGTC compressed formats are invalid for 3D texture targets"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the
|
// 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_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
|
// GL_INVALID_OPERATION is generated if a non-zero buffer object name is bound to the GL_PIXEL_UNPACK_BUFFER
|
||||||
@@ -1727,6 +1796,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
if (isProxy) {
|
if (isProxy) {
|
||||||
MGLOG_D("%s: isProxy = true, not allocating", __func__);
|
MGLOG_D("%s: isProxy = true, not allocating", __func__);
|
||||||
} else {
|
} else {
|
||||||
|
DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level);
|
||||||
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, height, depth}, internalBytes});
|
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, height, depth}, internalBytes});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1854,6 +1924,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
MGLOG_D("%s: isProxy = true, not allocating", __func__);
|
MGLOG_D("%s: isProxy = true, not allocating", __func__);
|
||||||
} else {
|
} else {
|
||||||
MGLOG_D("%s: Allocating %d bytes at mip %d", __func__, internalBytes, level);
|
MGLOG_D("%s: Allocating %d bytes at mip %d", __func__, internalBytes, level);
|
||||||
|
DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level);
|
||||||
textureMipmapObject->AllocateStorage(textureUploadTarget, level,
|
textureMipmapObject->AllocateStorage(textureUploadTarget, level,
|
||||||
{{width, height, 1}, internalBytes});
|
{{width, height, 1}, internalBytes});
|
||||||
}
|
}
|
||||||
@@ -1942,6 +2013,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
"Texture object here should always be an object with mipmap");
|
"Texture object here should always be an object with mipmap");
|
||||||
auto textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
auto textureMipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
||||||
if (!isProxy) {
|
if (!isProxy) {
|
||||||
|
DiscardMipmapChainOnBaseRespecification(textureMipmapObject, textureUploadTarget, level);
|
||||||
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, 1, 1}, internalBytes});
|
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{width, 1, 1}, internalBytes});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2594,7 +2666,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GetCompressedTexImage_State(GLenum target, GLint level, void* img) {
|
void GetCompressedTexImage_State(GLenum target, GLint level, void* img) {
|
||||||
// TODO: implement
|
// TODO: implement compressed readback. Reporting success while writing nothing hands
|
||||||
|
// the caller stale memory with GL_NO_ERROR; no texture can be compressed yet, and GL
|
||||||
|
// specifies GL_INVALID_OPERATION when the bound level is not compressed.
|
||||||
|
MG_State::pGLContext->RecordError(
|
||||||
|
ErrorCode::InvalidOperation,
|
||||||
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
||||||
|
"Texture level is not stored in a compressed format."));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GenTextures_State(GLsizei n, GLuint* textures) {
|
void GenTextures_State(GLsizei n, GLuint* textures) {
|
||||||
@@ -2781,20 +2859,20 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void CompressedTexSubImage3D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset,
|
void CompressedTexSubImage3D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLint zoffset,
|
||||||
GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize,
|
GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLsizei imageSize,
|
||||||
const void* data) {
|
const void* data) {
|
||||||
// TODO: implement
|
// TODO: implement compressed upload - see CompressedTexImage2D_State.
|
||||||
THROW_UNIMPL_EXCEPTION;
|
RecordUnsupportedCompressedFormat(__func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CompressedTexSubImage2D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
|
void CompressedTexSubImage2D_State(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width,
|
||||||
GLsizei height, GLenum format, GLsizei imageSize, const void* data) {
|
GLsizei height, GLenum format, GLsizei imageSize, const void* data) {
|
||||||
// TODO: implement
|
// TODO: implement compressed upload - see CompressedTexImage2D_State.
|
||||||
THROW_UNIMPL_EXCEPTION;
|
RecordUnsupportedCompressedFormat(__func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CompressedTexSubImage1D_State(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format,
|
void CompressedTexSubImage1D_State(GLenum target, GLint level, GLint xoffset, GLsizei width, GLenum format,
|
||||||
GLsizei imageSize, const void* data) {
|
GLsizei imageSize, const void* data) {
|
||||||
// TODO: implement
|
// TODO: implement compressed upload - see CompressedTexImage2D_State.
|
||||||
THROW_UNIMPL_EXCEPTION;
|
RecordUnsupportedCompressedFormat(__func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CompressedTexImage3D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height,
|
void CompressedTexImage3D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height,
|
||||||
@@ -2804,8 +2882,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
||||||
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
||||||
|
|
||||||
// TODO: implement
|
// TODO: implement compressed upload - see CompressedTexImage2D_State.
|
||||||
THROW_UNIMPL_EXCEPTION;
|
RecordUnsupportedCompressedFormat(__func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CompressedTexImage2D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height,
|
void CompressedTexImage2D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height,
|
||||||
@@ -2815,8 +2893,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
||||||
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
||||||
|
|
||||||
// TODO: implement
|
// TODO: implement compressed upload. Until then report the spec error for an
|
||||||
THROW_UNIMPL_EXCEPTION;
|
// unsupported compressed format rather than throwing - a C++ exception unwinding
|
||||||
|
// through the C GL ABI is a hard crash for the caller, while GL_INVALID_ENUM is
|
||||||
|
// exactly what GL_NUM_COMPRESSED_TEXTURE_FORMATS == 0 promises.
|
||||||
|
RecordUnsupportedCompressedFormat(__func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CompressedTexImage1D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border,
|
void CompressedTexImage1D_State(GLenum target, GLint level, GLenum internalformat, GLsizei width, GLint border,
|
||||||
@@ -2826,8 +2907,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
auto& textureObject = GetTextureObjectByTarget(textureUploadTarget, textureTarget);
|
||||||
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
if (!ValidateTextureMutable(textureObject, __func__)) return;
|
||||||
|
|
||||||
// TODO: implement
|
// TODO: implement compressed upload - see CompressedTexImage2D_State.
|
||||||
THROW_UNIMPL_EXCEPTION;
|
RecordUnsupportedCompressedFormat(__func__);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BindTexture_State(GLenum target, GLuint texture) {
|
void BindTexture_State(GLenum target, GLuint texture) {
|
||||||
@@ -3173,6 +3254,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{levelWidth, 1, 1}, byteSize});
|
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{levelWidth, 1, 1}, byteSize});
|
||||||
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, false);
|
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, false);
|
||||||
}
|
}
|
||||||
|
// Immutable storage defines exactly `levels` levels; AllocateStorage only grows, so a
|
||||||
|
// longer pre-existing chain has to be dropped explicitly.
|
||||||
|
textureMipmapObject->TruncateMipmapLevels(textureUploadTarget, static_cast<Uint>(levels));
|
||||||
textureObject->SetImmutableLevels(static_cast<Uint>(levels));
|
textureObject->SetImmutableLevels(static_cast<Uint>(levels));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3225,6 +3309,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{levelWidth, levelHeight, 1}, byteSize});
|
textureMipmapObject->AllocateStorage(textureUploadTarget, level, {{levelWidth, levelHeight, 1}, byteSize});
|
||||||
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, false);
|
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, false);
|
||||||
}
|
}
|
||||||
|
// See TextureStorage1D.
|
||||||
|
textureMipmapObject->TruncateMipmapLevels(textureUploadTarget, static_cast<Uint>(levels));
|
||||||
textureObject->SetImmutableLevels(static_cast<Uint>(levels));
|
textureObject->SetImmutableLevels(static_cast<Uint>(levels));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3277,6 +3363,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
{{levelWidth, levelHeight, levelDepth}, byteSize});
|
{{levelWidth, levelHeight, levelDepth}, byteSize});
|
||||||
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, false);
|
textureMipmapObject->MarkStorageDirty(textureUploadTarget, level, false);
|
||||||
}
|
}
|
||||||
|
// See TextureStorage1D.
|
||||||
|
textureMipmapObject->TruncateMipmapLevels(textureUploadTarget, static_cast<Uint>(levels));
|
||||||
textureObject->SetImmutableLevels(static_cast<Uint>(levels));
|
textureObject->SetImmutableLevels(static_cast<Uint>(levels));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,17 +16,32 @@ namespace MobileGL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MipmapStorage::AllocateLevel(Uint level, MipmapInput input) {
|
void MipmapStorage::AllocateLevel(Uint level, MipmapInput input) {
|
||||||
m_data.reserve(std::bit_ceil(level + 1));
|
// Grow only. GL respecifies exactly the level it is handed, so allocating level 0
|
||||||
m_data.resize(level + 1);
|
// must not disturb the levels above it - but resize() shrinks as readily as it
|
||||||
m_texelSizes.reserve(std::bit_ceil(level + 1));
|
// grows, so this used to truncate the whole chain to a single level. Callers that
|
||||||
m_texelSizes.resize(level + 1);
|
// genuinely redefine the complete level set say so with TruncateToLevelCount.
|
||||||
m_texelSizes[level] = input.texelSize;
|
const SizeT requiredLevelCount = static_cast<SizeT>(level) + 1;
|
||||||
m_isDirty.resize(level + 1, false);
|
if (m_data.size() < requiredLevelCount) {
|
||||||
|
m_data.reserve(std::bit_ceil(requiredLevelCount));
|
||||||
|
m_data.resize(requiredLevelCount);
|
||||||
|
m_texelSizes.reserve(std::bit_ceil(requiredLevelCount));
|
||||||
|
m_texelSizes.resize(requiredLevelCount);
|
||||||
|
m_isDirty.resize(requiredLevelCount, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_texelSizes[level] = input.texelSize;
|
||||||
auto& data = m_data[level];
|
auto& data = m_data[level];
|
||||||
data.resize(input.byteSize, 0);
|
data.resize(input.byteSize, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MipmapStorage::TruncateToLevelCount(SizeT levelCount) {
|
||||||
|
if (levelCount >= m_data.size()) return;
|
||||||
|
|
||||||
|
m_data.resize(levelCount);
|
||||||
|
m_texelSizes.resize(levelCount);
|
||||||
|
m_isDirty.resize(levelCount);
|
||||||
|
}
|
||||||
|
|
||||||
void MipmapStorage::UpdateSubData(Uint level, DataPtr input) {
|
void MipmapStorage::UpdateSubData(Uint level, DataPtr input) {
|
||||||
auto& targetData = m_data;
|
auto& targetData = m_data;
|
||||||
MOBILEGL_ASSERT(level < targetData.size(), "UpdateSubData: level out of range");
|
MOBILEGL_ASSERT(level < targetData.size(), "UpdateSubData: level out of range");
|
||||||
@@ -55,6 +70,7 @@ namespace MobileGL {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SizeT MipmapStorage::GetByteSize(Uint level) const {
|
SizeT MipmapStorage::GetByteSize(Uint level) const {
|
||||||
|
if (level >= m_data.size()) return 0;
|
||||||
return m_data[level].size();
|
return m_data[level].size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ namespace MobileGL {
|
|||||||
public:
|
public:
|
||||||
SizeT GetLevelCount() const;
|
SizeT GetLevelCount() const;
|
||||||
void AllocateLevel(Uint level, MipmapInput input);
|
void AllocateLevel(Uint level, MipmapInput input);
|
||||||
|
// Discard every level at or above levelCount. AllocateLevel never shrinks, so this
|
||||||
|
// is the only way a chain gets shorter - use it where the caller defines the whole
|
||||||
|
// level set (glTexStorage*, mip regeneration, atlas respecification).
|
||||||
|
void TruncateToLevelCount(SizeT levelCount);
|
||||||
void UpdateSubData(Uint level, DataPtr input);
|
void UpdateSubData(Uint level, DataPtr input);
|
||||||
void* MapData(Uint level);
|
void* MapData(Uint level);
|
||||||
IntVec3 GetTexelSize(Uint level) const;
|
IntVec3 GetTexelSize(Uint level) const;
|
||||||
|
|||||||
@@ -29,6 +29,14 @@ namespace MobileGL {
|
|||||||
m_storage[targetIndex].AllocateLevel(level, input);
|
m_storage[targetIndex].AllocateLevel(level, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Per-target, like AllocateLevel: cube-map faces are respecified independently, so
|
||||||
|
// truncating one face must not disturb the others.
|
||||||
|
void TruncateToLevelCount(Uint targetIndex, SizeT levelCount) {
|
||||||
|
MOBILEGL_ASSERT(targetIndex < TargetCount, "TruncateToLevelCount: target invalid");
|
||||||
|
|
||||||
|
m_storage[targetIndex].TruncateToLevelCount(levelCount);
|
||||||
|
}
|
||||||
|
|
||||||
void UpdateSubData(Uint targetIndex, Uint level, DataPtr input) {
|
void UpdateSubData(Uint targetIndex, Uint level, DataPtr input) {
|
||||||
MOBILEGL_ASSERT(targetIndex < TargetCount, "UpdateSubData: target invalid");
|
MOBILEGL_ASSERT(targetIndex < TargetCount, "UpdateSubData: target invalid");
|
||||||
m_storage[targetIndex].UpdateSubData(level, input);
|
m_storage[targetIndex].UpdateSubData(level, input);
|
||||||
|
|||||||
@@ -271,6 +271,10 @@ namespace MobileGL {
|
|||||||
m_textureStorage.AllocateLevel(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, input);
|
m_textureStorage.AllocateLevel(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextureObjectWithOneMipmap::TruncateMipmapLevels(TextureUploadTarget uploadTarget, Uint levelCount) {
|
||||||
|
m_textureStorage.TruncateToLevelCount(GetIndexOfTextureUploadTarget(uploadTarget), levelCount);
|
||||||
|
}
|
||||||
|
|
||||||
void TextureObjectWithOneMipmap::UpdateMipmapSubData(TextureUploadTarget uploadTarget, Uint mipmapLevel,
|
void TextureObjectWithOneMipmap::UpdateMipmapSubData(TextureUploadTarget uploadTarget, Uint mipmapLevel,
|
||||||
DataPtr input) {
|
DataPtr input) {
|
||||||
m_textureStorage.UpdateSubData(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, input);
|
m_textureStorage.UpdateSubData(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, input);
|
||||||
|
|||||||
@@ -134,6 +134,10 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
virtual const IntVec3 GetMipmapTexelSize(TextureUploadTarget target, Uint mipmapLevel) const = 0;
|
virtual const IntVec3 GetMipmapTexelSize(TextureUploadTarget target, Uint mipmapLevel) const = 0;
|
||||||
virtual const SizeT GetMipmapByteSize(TextureUploadTarget target, Uint mipmapLevel) const = 0;
|
virtual const SizeT GetMipmapByteSize(TextureUploadTarget target, Uint mipmapLevel) const = 0;
|
||||||
virtual void AllocateStorage(TextureUploadTarget uploadTarget, Uint mipmapLevel, MipmapInput input) = 0;
|
virtual void AllocateStorage(TextureUploadTarget uploadTarget, Uint mipmapLevel, MipmapInput input) = 0;
|
||||||
|
// AllocateStorage only ever grows the chain. Callers that define the complete level set -
|
||||||
|
// glTexStorage*, mip regeneration, or a level-0 respecification at a new size - drop the
|
||||||
|
// leftovers explicitly, so a stale tail can never make the texture silently incomplete.
|
||||||
|
virtual void TruncateMipmapLevels(TextureUploadTarget uploadTarget, Uint levelCount) = 0;
|
||||||
virtual void UpdateMipmapSubData(TextureUploadTarget uploadTarget, Uint mipmapLevel, DataPtr input) = 0;
|
virtual void UpdateMipmapSubData(TextureUploadTarget uploadTarget, Uint mipmapLevel, DataPtr input) = 0;
|
||||||
virtual void* MapMipmapData(TextureUploadTarget uploadTarget, Uint mipmapLevel) = 0;
|
virtual void* MapMipmapData(TextureUploadTarget uploadTarget, Uint mipmapLevel) = 0;
|
||||||
virtual void MarkStorageDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel, Bool dirty = true) = 0;
|
virtual void MarkStorageDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel, Bool dirty = true) = 0;
|
||||||
@@ -175,6 +179,7 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
const IntVec3 GetMipmapTexelSize(TextureUploadTarget target, Uint mipmapLevel) const override;
|
const IntVec3 GetMipmapTexelSize(TextureUploadTarget target, Uint mipmapLevel) const override;
|
||||||
const SizeT GetMipmapByteSize(TextureUploadTarget target, Uint mipmapLevel) const override;
|
const SizeT GetMipmapByteSize(TextureUploadTarget target, Uint mipmapLevel) const override;
|
||||||
void AllocateStorage(TextureUploadTarget uploadTarget, Uint mipmapLevel, MipmapInput input) override;
|
void AllocateStorage(TextureUploadTarget uploadTarget, Uint mipmapLevel, MipmapInput input) override;
|
||||||
|
void TruncateMipmapLevels(TextureUploadTarget uploadTarget, Uint levelCount) override;
|
||||||
void UpdateMipmapSubData(TextureUploadTarget uploadTarget, Uint mipmapLevel, DataPtr input) override;
|
void UpdateMipmapSubData(TextureUploadTarget uploadTarget, Uint mipmapLevel, DataPtr input) override;
|
||||||
void* MapMipmapData(TextureUploadTarget uploadTarget, Uint mipmapLevel) override;
|
void* MapMipmapData(TextureUploadTarget uploadTarget, Uint mipmapLevel) override;
|
||||||
void MarkStorageDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel, Bool dirty) override;
|
void MarkStorageDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel, Bool dirty) override;
|
||||||
|
|||||||
@@ -31,6 +31,10 @@ namespace MobileGL {
|
|||||||
m_textureStorage.AllocateLevel(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, input);
|
m_textureStorage.AllocateLevel(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TextureObject2DCube::TruncateMipmapLevels(TextureUploadTarget uploadTarget, Uint levelCount) {
|
||||||
|
m_textureStorage.TruncateToLevelCount(GetIndexOfTextureUploadTarget(uploadTarget), levelCount);
|
||||||
|
}
|
||||||
|
|
||||||
void TextureObject2DCube::UpdateMipmapSubData(TextureUploadTarget uploadTarget, Uint mipmapLevel,
|
void TextureObject2DCube::UpdateMipmapSubData(TextureUploadTarget uploadTarget, Uint mipmapLevel,
|
||||||
DataPtr input) {
|
DataPtr input) {
|
||||||
m_textureStorage.UpdateSubData(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, input);
|
m_textureStorage.UpdateSubData(GetIndexOfTextureUploadTarget(uploadTarget), mipmapLevel, input);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ namespace MobileGL {
|
|||||||
const IntVec3 GetMipmapTexelSize(TextureUploadTarget target, Uint mipmapLevel) const override;
|
const IntVec3 GetMipmapTexelSize(TextureUploadTarget target, Uint mipmapLevel) const override;
|
||||||
const SizeT GetMipmapByteSize(TextureUploadTarget target, Uint mipmapLevel) const override;
|
const SizeT GetMipmapByteSize(TextureUploadTarget target, Uint mipmapLevel) const override;
|
||||||
void AllocateStorage(TextureUploadTarget uploadTarget, Uint mipmapLevel, MipmapInput input) override;
|
void AllocateStorage(TextureUploadTarget uploadTarget, Uint mipmapLevel, MipmapInput input) override;
|
||||||
|
void TruncateMipmapLevels(TextureUploadTarget uploadTarget, Uint levelCount) override;
|
||||||
void UpdateMipmapSubData(TextureUploadTarget uploadTarget, Uint mipmapLevel, DataPtr input) override;
|
void UpdateMipmapSubData(TextureUploadTarget uploadTarget, Uint mipmapLevel, DataPtr input) override;
|
||||||
void* MapMipmapData(TextureUploadTarget uploadTarget, Uint mipmapLevel) override;
|
void* MapMipmapData(TextureUploadTarget uploadTarget, Uint mipmapLevel) override;
|
||||||
void MarkStorageDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel, bool dirty) override;
|
void MarkStorageDirty(TextureUploadTarget uploadTarget, Uint mipmapLevel, bool dirty) override;
|
||||||
|
|||||||
@@ -426,6 +426,62 @@ void main() {
|
|||||||
verifyVersion("#version 460 core");
|
verifyVersion("#version 460 core");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KHR-GL33.shaders.preprocessor.directive.version_* (also re-run verbatim under GL40-GL44): the
|
||||||
|
// compiler must REJECT a malformed #version line. MobileGL used to rewrite the whole line to
|
||||||
|
// "#version 330 core" whenever it could scrape a leading integer - or treat an unknown profile token
|
||||||
|
// as core - which silently legalized every form below. CTS compiles the shader's own #version
|
||||||
|
// verbatim, so the rejection has to survive preprocessing (and the 460 retry).
|
||||||
|
TEST_F(ProgramUtilTest, PreprocessRejectsMalformedVersionDirectives) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
const char* body = "\nout vec4 fragColor;\nvoid main() { fragColor = vec4(1.0); }\n";
|
||||||
|
const auto rejects = [](const String& fullSource) {
|
||||||
|
String src = fullSource;
|
||||||
|
PreprocessShaderSource(ShaderStage::Fragment, src);
|
||||||
|
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = src};
|
||||||
|
auto res = ShaderCompiler::CompileShader(attrib);
|
||||||
|
return res ? false : true; // "rejects" == compile failed
|
||||||
|
};
|
||||||
|
|
||||||
|
// Silently legalized today - the five this fix must flip to rejection:
|
||||||
|
EXPECT_TRUE(rejects(String("#version 329") + body)) << "329 is not a real version";
|
||||||
|
EXPECT_TRUE(rejects(String("#version 331") + body)) << "331 is not a real version";
|
||||||
|
EXPECT_TRUE(rejects(String("#version 330 foo") + body)) << "unknown profile keyword";
|
||||||
|
EXPECT_TRUE(rejects(String("#version 330.0") + body)) << "float literal, not an int token";
|
||||||
|
EXPECT_TRUE(rejects(String("#version 330 foobar") + body)) << "trailing tokens after a valid decl";
|
||||||
|
|
||||||
|
// Already rejected (no leading integer, or #version is not the first token) - pinned so a future
|
||||||
|
// change to the normalizer cannot start legalizing them either:
|
||||||
|
EXPECT_TRUE(rejects(String("#version") + body)) << "missing version number";
|
||||||
|
EXPECT_TRUE(rejects(String("#version foobar") + body)) << "identifier where the int belongs";
|
||||||
|
EXPECT_TRUE(rejects(String("#version AAA") + body)) << "identifier where the int belongs";
|
||||||
|
EXPECT_TRUE(rejects(String("precision mediump float;\n#version 330") + body))
|
||||||
|
<< "#version must be the first statement";
|
||||||
|
EXPECT_TRUE(rejects(String("#define FOO BAR\n#version 330") + body))
|
||||||
|
<< "#version must precede a #define";
|
||||||
|
}
|
||||||
|
|
||||||
|
// The PASS half of the same CTS group: a valid decl, and #version preceded only by whitespace or a
|
||||||
|
// comment, must still compile. Guards the fix above from over-rejecting.
|
||||||
|
TEST_F(ProgramUtilTest, PreprocessKeepsValidVersionDirectivesCompiling) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
const char* body = "\nout vec4 fragColor;\nvoid main() { fragColor = vec4(1.0); }\n";
|
||||||
|
const auto compiles = [](const String& fullSource) {
|
||||||
|
String src = fullSource;
|
||||||
|
PreprocessShaderSource(ShaderStage::Fragment, src);
|
||||||
|
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = src};
|
||||||
|
auto res = ShaderCompiler::CompileShader(attrib);
|
||||||
|
return res ? true : false;
|
||||||
|
};
|
||||||
|
|
||||||
|
EXPECT_TRUE(compiles(String("#version 330 core") + body));
|
||||||
|
EXPECT_TRUE(compiles(String("\n#version 330 core") + body))
|
||||||
|
<< "leading whitespace is legal before #version";
|
||||||
|
EXPECT_TRUE(compiles(String("// test\n#version 330 core") + body))
|
||||||
|
<< "a leading comment is legal before #version";
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(ProgramUtilTest, PreprocessUsesRealSpacedVersionDirectiveForInjectedOutput) {
|
TEST_F(ProgramUtilTest, PreprocessUsesRealSpacedVersionDirectiveForInjectedOutput) {
|
||||||
using namespace MG_Util::ShaderTranspiler;
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
@@ -446,6 +502,9 @@ void main() {
|
|||||||
EXPECT_NE(versionPos, String::npos);
|
EXPECT_NE(versionPos, String::npos);
|
||||||
EXPECT_EQ(outputPos, versionPos + std::strlen("#version 330 core\n"));
|
EXPECT_EQ(outputPos, versionPos + std::strlen("#version 330 core\n"));
|
||||||
EXPECT_NE(source.find("// #version 460 core"), String::npos);
|
EXPECT_NE(source.find("// #version 460 core"), String::npos);
|
||||||
|
// This #line sits ahead of the version directive, where GLSL would never have honoured it, so
|
||||||
|
// it is still dropped. Directives that follow the version line are kept - see
|
||||||
|
// PreprocessKeepsPlainLineDirectivesAndSparesLookalikeIdentifiers.
|
||||||
EXPECT_EQ(source.find("#line"), String::npos);
|
EXPECT_EQ(source.find("#line"), String::npos);
|
||||||
|
|
||||||
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
|
ShaderAttrib attrib{.shaderType = GL_FRAGMENT_SHADER, .sourceStr = source};
|
||||||
@@ -455,6 +514,105 @@ void main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A banner line like "//*** NOTE ***" contains "/*" at offset 1 and no "*/" anywhere after it. The
|
||||||
|
// old hand-rolled comment stripper searched for "/*" with no lexical state, found that, failed to
|
||||||
|
// find a terminator, and erased everything from there to the end of the file - deleting the entire
|
||||||
|
// shader. Banner comments in that exact shape are common in Iris and OptiFine packs.
|
||||||
|
TEST_F(ProgramUtilTest, PreprocessKeepsShaderBodyAfterAStarredLineComment) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
String source = R"(#version 330 core
|
||||||
|
//*** lighting pass ***
|
||||||
|
out vec4 fragColor;
|
||||||
|
void main() {
|
||||||
|
fragColor = vec4(1.0);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
PreprocessShaderSource(ShaderStage::Fragment, source);
|
||||||
|
|
||||||
|
EXPECT_NE(source.find("void main()"), String::npos) << "shader body was truncated:\n" << source;
|
||||||
|
EXPECT_NE(source.find("fragColor = vec4(1.0);"), 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The builtin-shadowing rename only fires when the shader really defines its own round/tanh/etc.
|
||||||
|
// Deciding that from a commented-out definition renames every genuine call to the builtin to a
|
||||||
|
// mg_ name that nothing defines, which fails to link.
|
||||||
|
TEST_F(ProgramUtilTest, PreprocessIgnoresCommentedOutBuiltinShadowingDefinition) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
String source = R"(#version 330 core
|
||||||
|
// float round(float x) { return floor(x + 0.5); }
|
||||||
|
out vec4 fragColor;
|
||||||
|
void main() {
|
||||||
|
fragColor = vec4(round(1.25));
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
PreprocessShaderSource(ShaderStage::Fragment, source);
|
||||||
|
|
||||||
|
EXPECT_NE(source.find("round(1.25)"), String::npos) << "call was renamed from a comment:\n" << source;
|
||||||
|
EXPECT_EQ(source.find("mg_round"), String::npos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// A block-commented extension directive must not be treated as a real one - the int64 filter turns
|
||||||
|
// unsupported directives into #error, so reading one out of a comment manufactures a compile
|
||||||
|
// failure for a shader that never asked for the extension.
|
||||||
|
TEST_F(ProgramUtilTest, PreprocessIgnoresBlockCommentedExtensionDirectives) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
String source = R"(#version 330 core
|
||||||
|
/*
|
||||||
|
#extension GL_ARB_gpu_shader_int64 : require
|
||||||
|
*/
|
||||||
|
out vec4 fragColor;
|
||||||
|
void main() {
|
||||||
|
fragColor = vec4(1.0);
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
PreprocessShaderSource(ShaderStage::Fragment, source);
|
||||||
|
|
||||||
|
EXPECT_EQ(source.find("#error"), String::npos) << "#error synthesized from a comment:\n" << source;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// KHR-GL33.shaders.preprocessor.builtin.line_* checks that __LINE__ follows #line. That only works
|
||||||
|
// if the directive reaches glslang, so a plain integer form must pass through untouched - while
|
||||||
|
// "#linear" and friends must not be mistaken for it.
|
||||||
|
TEST_F(ProgramUtilTest, PreprocessKeepsPlainLineDirectivesAndSparesLookalikeIdentifiers) {
|
||||||
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
|
String source = R"(#version 330 core
|
||||||
|
out vec4 fragColor;
|
||||||
|
#line 42
|
||||||
|
float linear(float x) { return x; }
|
||||||
|
void main() {
|
||||||
|
#line 100
|
||||||
|
fragColor = vec4(linear(float(__LINE__)));
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
PreprocessShaderSource(ShaderStage::Fragment, source);
|
||||||
|
|
||||||
|
EXPECT_NE(source.find("#line 42"), String::npos) << source;
|
||||||
|
EXPECT_NE(source.find("#line 100"), String::npos) << source;
|
||||||
|
EXPECT_NE(source.find("float linear(float x)"), String::npos) << "identifier lookalike was eaten:\n" << source;
|
||||||
|
|
||||||
|
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) {
|
TEST_F(ProgramUtilTest, PreprocessModernSampleQualifierStaysAtVersion460) {
|
||||||
using namespace MG_Util::ShaderTranspiler;
|
using namespace MG_Util::ShaderTranspiler;
|
||||||
|
|
||||||
@@ -745,6 +903,16 @@ TEST_F(ProgramUtilTest, RetargetLegacyVersionDirectiveOnlyTouchesNormalizedDeskt
|
|||||||
String commented = "// #version 330 core\nvoid main() {}\n";
|
String commented = "// #version 330 core\nvoid main() {}\n";
|
||||||
EXPECT_FALSE(RetargetLegacyVersionDirectiveTo460(commented));
|
EXPECT_FALSE(RetargetLegacyVersionDirectiveTo460(commented));
|
||||||
EXPECT_EQ(commented.find("#version 460"), String::npos);
|
EXPECT_EQ(commented.find("#version 460"), String::npos);
|
||||||
|
|
||||||
|
// A malformed directive must NOT be rescued to 460 - that is what silently legalized the CTS
|
||||||
|
// directive.version_* rejection cases. The bad version stays put so glslang keeps rejecting it.
|
||||||
|
String badNumber = "#version 331\nvoid main() {}\n";
|
||||||
|
EXPECT_FALSE(RetargetLegacyVersionDirectiveTo460(badNumber));
|
||||||
|
EXPECT_EQ(badNumber.find("#version 460"), String::npos);
|
||||||
|
|
||||||
|
String badProfile = "#version 330 foo\nvoid main() {}\n";
|
||||||
|
EXPECT_FALSE(RetargetLegacyVersionDirectiveTo460(badProfile));
|
||||||
|
EXPECT_EQ(badProfile.find("#version 460"), String::npos);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* fs = R"(#version 150
|
const char* fs = R"(#version 150
|
||||||
|
|||||||
@@ -1427,6 +1427,158 @@ TEST_F(TextureTest, TextureStorage1DAndSubImageModifyNamedObjectOnly) {
|
|||||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Building a mip chain top-down - upload level N, then level 0 - must not destroy the levels
|
||||||
|
// already uploaded. AllocateLevel used to resize() the storage down to level+1 on every call, so
|
||||||
|
// the level-0 upload truncated the chain to a single level; the higher level then read back as
|
||||||
|
// {0,0,0}, IsComplete() rejected the zero-then-nonzero pattern, and DirectGLES answered that by
|
||||||
|
// skipping the texture's sync entirely. This is the shape KHR-GL33.texture_repeat_mode uses, and
|
||||||
|
// it accounted for 108 CTS failures in every GL version.
|
||||||
|
TEST_F(TextureTest, TexImage2DOnLevelZeroKeepsAnAlreadyUploadedHigherLevel) {
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 49, 23, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 98, 46, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
|
||||||
|
const auto textureObject = MG_State::pGLContext->GetTextureObject(texture);
|
||||||
|
auto* mipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
||||||
|
ASSERT_NE(mipmapObject, nullptr);
|
||||||
|
EXPECT_EQ(mipmapObject->GetMipmapLevelCount(), 2u);
|
||||||
|
EXPECT_EQ(mipmapObject->GetMipmapTexelSize(TextureUploadTarget::Texture2D, 0), IntVec3(98, 46, 1));
|
||||||
|
EXPECT_EQ(mipmapObject->GetMipmapTexelSize(TextureUploadTarget::Texture2D, 1), IntVec3(49, 23, 1));
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The other half of the contract: respecifying a level 0 that already held an image still drops
|
||||||
|
// the chain, exactly as before. Minecraft rebinds the block-atlas name and calls glTexImage2D on
|
||||||
|
// level 0 before uploading the new levels; leaving the previous chain in place would strand a tail
|
||||||
|
// at the wrong sizes and - because Mojang terminates its chains with a 0x0 level - reproduce the
|
||||||
|
// same incomplete-texture black atlas the fix above exists to prevent.
|
||||||
|
TEST_F(TextureTest, TexImage2DRespecifyingAnExistingLevelZeroDropsTheStaleChain) {
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
|
||||||
|
const auto textureObject = MG_State::pGLContext->GetTextureObject(texture);
|
||||||
|
auto* mipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
||||||
|
ASSERT_NE(mipmapObject, nullptr);
|
||||||
|
ASSERT_EQ(mipmapObject->GetMipmapLevelCount(), 3u);
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
|
||||||
|
EXPECT_EQ(mipmapObject->GetMipmapLevelCount(), 1u);
|
||||||
|
EXPECT_EQ(mipmapObject->GetMipmapTexelSize(TextureUploadTarget::Texture2D, 0), IntVec3(16, 16, 1));
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same-size respecification has to drop the chain too. The Mipmap Levels video setting rebuilds
|
||||||
|
// the atlas at identical dimensions with a different level count, so a size-change-only test would
|
||||||
|
// let the old tail survive.
|
||||||
|
TEST_F(TextureTest, TexImage2DRespecifyingLevelZeroAtTheSameSizeStillDropsTheChain) {
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
|
||||||
|
const auto textureObject = MG_State::pGLContext->GetTextureObject(texture);
|
||||||
|
auto* mipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
||||||
|
ASSERT_NE(mipmapObject, nullptr);
|
||||||
|
EXPECT_EQ(mipmapObject->GetMipmapLevelCount(), 1u);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// glTexStorage2D defines exactly `levels` levels. AllocateStorage only grows now, so the immutable
|
||||||
|
// path has to drop a longer pre-existing chain explicitly.
|
||||||
|
TEST_F(TextureTest, TexStorage2DTrimsALongerPreExistingMipChain) {
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 2, GL_RGBA8, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 3, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::TexStorage2D(GL_TEXTURE_2D, 2, GL_RGBA8, 8, 8);
|
||||||
|
|
||||||
|
const auto textureObject = MG_State::pGLContext->GetTextureObject(texture);
|
||||||
|
auto* mipmapObject = static_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
|
||||||
|
ASSERT_NE(mipmapObject, nullptr);
|
||||||
|
EXPECT_EQ(mipmapObject->GetMipmapLevelCount(), 2u);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// glTexImage2D used to reject every GL_COMPRESSED_* internal format with GL_INVALID_ENUM, because
|
||||||
|
// none of them mapped to a TextureInternalFormat and the "unknown format" gate fired. They now
|
||||||
|
// resolve to the uncompressed storage that backs them - what GL prescribes for the generic formats,
|
||||||
|
// and a deliberate deviation for RGTC, which ES cannot compress. The (format, type) pairs below are
|
||||||
|
// the ones KHR-GL33.packed_pixels uploads with, so this table doubles as a pin for those 480 cases.
|
||||||
|
TEST_F(TextureTest, CompressedInternalFormatsResolveToTheirUncompressedStorage) {
|
||||||
|
struct Case {
|
||||||
|
GLenum internalFormat;
|
||||||
|
GLenum format;
|
||||||
|
GLenum type;
|
||||||
|
TextureInternalFormat expected;
|
||||||
|
};
|
||||||
|
const Case cases[] = {
|
||||||
|
{GL_COMPRESSED_RED, GL_RED, GL_UNSIGNED_BYTE, TextureInternalFormat::R8},
|
||||||
|
{GL_COMPRESSED_RG, GL_RG, GL_UNSIGNED_BYTE, TextureInternalFormat::RG8},
|
||||||
|
{GL_COMPRESSED_RGB, GL_RGB, GL_UNSIGNED_BYTE, TextureInternalFormat::RGB8},
|
||||||
|
{GL_COMPRESSED_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, TextureInternalFormat::RGBA8},
|
||||||
|
{GL_COMPRESSED_SRGB, GL_RGB, GL_UNSIGNED_BYTE, TextureInternalFormat::SRGB8},
|
||||||
|
{GL_COMPRESSED_SRGB_ALPHA, GL_RGBA, GL_UNSIGNED_BYTE, TextureInternalFormat::SRGB8Alpha8},
|
||||||
|
{GL_COMPRESSED_RED_RGTC1, GL_RED, GL_UNSIGNED_BYTE, TextureInternalFormat::R8},
|
||||||
|
{GL_COMPRESSED_RG_RGTC2, GL_RG, GL_UNSIGNED_BYTE, TextureInternalFormat::RG8},
|
||||||
|
// The signed RGTC pair is uploaded as GL_BYTE and must land on SNORM storage - resolving
|
||||||
|
// them to plain R8/RG8 would silently reinterpret negative texels.
|
||||||
|
{GL_COMPRESSED_SIGNED_RED_RGTC1, GL_RED, GL_BYTE, TextureInternalFormat::R8Snorm},
|
||||||
|
{GL_COMPRESSED_SIGNED_RG_RGTC2, GL_RG, GL_BYTE, TextureInternalFormat::RG8Snorm},
|
||||||
|
};
|
||||||
|
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
for (const auto& c : cases) {
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, c.internalFormat, 4, 4, 0, c.format, c.type, nullptr);
|
||||||
|
|
||||||
|
const auto textureObject = MG_State::pGLContext->GetTextureObject(texture);
|
||||||
|
ASSERT_NE(textureObject, nullptr) << "internalFormat 0x" << std::hex << c.internalFormat;
|
||||||
|
EXPECT_EQ(textureObject->GetFormat(), c.expected) << "internalFormat 0x" << std::hex << c.internalFormat;
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR) << "internalFormat 0x" << std::hex << c.internalFormat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RGTC compresses 4x4 blocks of a 2D image and has no 3D form, so glTexImage3D must reject it even
|
||||||
|
// though the same enum is accepted on a 2D target. The generic compressed formats carry no such
|
||||||
|
// restriction and stay legal in 3D.
|
||||||
|
TEST_F(TextureTest, RgtcInternalFormatsAreRejectedOnThreeDimensionalTargets) {
|
||||||
|
const GLenum rgtc[] = {GL_COMPRESSED_RED_RGTC1, GL_COMPRESSED_SIGNED_RED_RGTC1, GL_COMPRESSED_RG_RGTC2,
|
||||||
|
GL_COMPRESSED_SIGNED_RG_RGTC2};
|
||||||
|
for (const GLenum internalFormat : rgtc) {
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_3D, texture);
|
||||||
|
MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_3D, 0, internalFormat, 4, 4, 4, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_INVALID_OPERATION)
|
||||||
|
<< "internalFormat 0x" << std::hex << internalFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint generic = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &generic);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_3D, generic);
|
||||||
|
MG_Impl::GLImpl::TexImage3D(GL_TEXTURE_3D, 0, GL_COMPRESSED_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(TextureTest, TextureStorage3DAndSubImageModifyNamedObjectOnly) {
|
TEST_F(TextureTest, TextureStorage3DAndSubImageModifyNamedObjectOnly) {
|
||||||
GLuint texture = 0;
|
GLuint texture = 0;
|
||||||
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_3D, 1, &texture);
|
MG_Impl::GLImpl::CreateTextures(GL_TEXTURE_3D, 1, &texture);
|
||||||
|
|||||||
@@ -255,6 +255,37 @@ namespace MobileGL {
|
|||||||
return TextureInternalFormat::DepthComponent;
|
return TextureInternalFormat::DepthComponent;
|
||||||
case GL_DEPTH_STENCIL:
|
case GL_DEPTH_STENCIL:
|
||||||
return TextureInternalFormat::DepthStencil;
|
return TextureInternalFormat::DepthStencil;
|
||||||
|
// Compressed internal formats resolve to the uncompressed storage that backs them.
|
||||||
|
//
|
||||||
|
// For the six generic formats this is exactly what GL prescribes: the implementation
|
||||||
|
// picks a specific compressed format, and when none is available it falls back to the
|
||||||
|
// corresponding base format. Nothing downstream ever sees a compressed enum, so the
|
||||||
|
// metrics, pixel-store and backend tables keep their "one format, N bytes per texel"
|
||||||
|
// invariant instead of each needing a compressed-aware arm.
|
||||||
|
//
|
||||||
|
// The four RGTC formats are a deliberate deviation: they are specific formats that GL
|
||||||
|
// 3.3 requires, but ES exposes no RGTC compressor to hand the data to. Storing the
|
||||||
|
// texels uncompressed keeps them renderable at the cost of the memory saving, which is
|
||||||
|
// strictly better than the INVALID_ENUM the application used to get. Note the signed
|
||||||
|
// variants must land on SNORM storage - CTS uploads them as GL_BYTE.
|
||||||
|
case GL_COMPRESSED_RED:
|
||||||
|
case GL_COMPRESSED_RED_RGTC1:
|
||||||
|
return TextureInternalFormat::R8;
|
||||||
|
case GL_COMPRESSED_SIGNED_RED_RGTC1:
|
||||||
|
return TextureInternalFormat::R8Snorm;
|
||||||
|
case GL_COMPRESSED_RG:
|
||||||
|
case GL_COMPRESSED_RG_RGTC2:
|
||||||
|
return TextureInternalFormat::RG8;
|
||||||
|
case GL_COMPRESSED_SIGNED_RG_RGTC2:
|
||||||
|
return TextureInternalFormat::RG8Snorm;
|
||||||
|
case GL_COMPRESSED_RGB:
|
||||||
|
return TextureInternalFormat::RGB8;
|
||||||
|
case GL_COMPRESSED_RGBA:
|
||||||
|
return TextureInternalFormat::RGBA8;
|
||||||
|
case GL_COMPRESSED_SRGB:
|
||||||
|
return TextureInternalFormat::SRGB8;
|
||||||
|
case GL_COMPRESSED_SRGB_ALPHA:
|
||||||
|
return TextureInternalFormat::SRGB8Alpha8;
|
||||||
case GL_ALPHA:
|
case GL_ALPHA:
|
||||||
case GL_RED:
|
case GL_RED:
|
||||||
return TextureInternalFormat::Red;
|
return TextureInternalFormat::Red;
|
||||||
|
|||||||
@@ -96,6 +96,77 @@ namespace {
|
|||||||
return masked;
|
return masked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Blank out block comments in place, leaving line comments and every other byte where it is.
|
||||||
|
//
|
||||||
|
// The passes that follow scan the source as raw text, so block comments have to stop being
|
||||||
|
// visible to them - but they must not be *deleted*: replacing the bytes with spaces keeps every
|
||||||
|
// later offset valid and keeps newlines, so glslang's diagnostics still point at the line the
|
||||||
|
// application wrote. It also has to be lexically aware. A banner line such as
|
||||||
|
//
|
||||||
|
// //*** lighting pass ***
|
||||||
|
//
|
||||||
|
// contains "/*" one byte in, and a naive search for that opener treats the rest of the file as
|
||||||
|
// an unterminated comment.
|
||||||
|
void BlankBlockComments(MobileGL::String& source) {
|
||||||
|
enum class Region { Code, SingleLineComment, MultiLineComment, QuotedText };
|
||||||
|
|
||||||
|
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 == '/') {
|
||||||
|
pos++;
|
||||||
|
region = Region::SingleLineComment;
|
||||||
|
} else if (ch == '/' && next == '*') {
|
||||||
|
source[pos] = ' ';
|
||||||
|
source[pos + 1] = ' ';
|
||||||
|
pos++;
|
||||||
|
region = Region::MultiLineComment;
|
||||||
|
} else if (ch == '"' || ch == '\'') {
|
||||||
|
quote = ch;
|
||||||
|
escaped = false;
|
||||||
|
region = Region::QuotedText;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (region == Region::SingleLineComment) {
|
||||||
|
if (ch == '\n' || ch == '\r') region = Region::Code;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (region == Region::MultiLineComment) {
|
||||||
|
if (ch == '*' && next == '/') {
|
||||||
|
source[pos] = ' ';
|
||||||
|
source[pos + 1] = ' ';
|
||||||
|
pos++;
|
||||||
|
region = Region::Code;
|
||||||
|
} else if (ch != '\n' && ch != '\r') {
|
||||||
|
source[pos] = ' ';
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// GLSL has no multi-line string literals, so a quote that reaches end of line was never
|
||||||
|
// a literal to begin with - most likely an apostrophe in a #error or #pragma message.
|
||||||
|
// Ending the region here keeps one stray apostrophe from swallowing the rest of the file.
|
||||||
|
if (ch == '\n' || ch == '\r') {
|
||||||
|
region = Region::Code;
|
||||||
|
} else if (escaped) {
|
||||||
|
escaped = false;
|
||||||
|
} else if (ch == '\\') {
|
||||||
|
escaped = true;
|
||||||
|
} else if (ch == quote) {
|
||||||
|
region = Region::Code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct CodeToken {
|
struct CodeToken {
|
||||||
String text;
|
String text;
|
||||||
SizeT begin = 0;
|
SizeT begin = 0;
|
||||||
@@ -502,6 +573,23 @@ namespace {
|
|||||||
static_cast<unsigned char>(source[1]) == 0xbb && static_cast<unsigned char>(source[2]) == 0xbf;
|
static_cast<unsigned char>(source[1]) == 0xbb && static_cast<unsigned char>(source[2]) == 0xbf;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The GLSL versions MobileGL is willing to normalize. Anything else in a #version line - a number
|
||||||
|
// that is not a real language version (329, 331), a bad profile keyword, a float/identifier where
|
||||||
|
// the integer belongs, or trailing tokens - is left untouched so glslang rejects it, matching
|
||||||
|
// KHR-GL33.shaders.preprocessor.directive.version_*. The set is deliberately generous (every real
|
||||||
|
// desktop and ES version) so the normalizer never starts rejecting a form it used to accept.
|
||||||
|
bool IsRecognizedGlslVersion(unsigned version) {
|
||||||
|
switch (version) {
|
||||||
|
case 100: case 110: case 120: case 130: case 140: case 150:
|
||||||
|
case 300: case 310: case 320:
|
||||||
|
case 330: case 400: case 410: case 420: case 430:
|
||||||
|
case 440: case 450: case 460:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct ShaderLanguageInfo {
|
struct ShaderLanguageInfo {
|
||||||
unsigned version = 110;
|
unsigned version = 110;
|
||||||
MobileGL::ShaderProfile profile = MobileGL::ShaderProfile::Core;
|
MobileGL::ShaderProfile profile = MobileGL::ShaderProfile::Core;
|
||||||
@@ -509,6 +597,9 @@ namespace {
|
|||||||
SizeT versionDirectiveEnd = MobileGL::String::npos;
|
SizeT versionDirectiveEnd = MobileGL::String::npos;
|
||||||
bool hasUtf8Bom = false;
|
bool hasUtf8Bom = false;
|
||||||
bool enablesGpuShader5 = false;
|
bool enablesGpuShader5 = false;
|
||||||
|
// Whether the parsed #version directive is a well-formed one MobileGL should rewrite. A
|
||||||
|
// malformed directive (see IsRecognizedGlslVersion) is left alone for glslang to reject.
|
||||||
|
bool hasValidVersionDirective = false;
|
||||||
|
|
||||||
bool HasVersionDirective() const { return versionDirectiveStart != MobileGL::String::npos; }
|
bool HasVersionDirective() const { return versionDirectiveStart != MobileGL::String::npos; }
|
||||||
};
|
};
|
||||||
@@ -552,13 +643,25 @@ namespace {
|
|||||||
info.versionDirectiveEnd = lineEnd + (hasLineBreak ? 1 : 0);
|
info.versionDirectiveEnd = lineEnd + (hasLineBreak ? 1 : 0);
|
||||||
SkipDirectiveWhitespace(code, probe, lineEnd);
|
SkipDirectiveWhitespace(code, probe, lineEnd);
|
||||||
const MobileGL::String profile = ReadDirectiveIdentifier(code, probe, lineEnd);
|
const MobileGL::String profile = ReadDirectiveIdentifier(code, probe, lineEnd);
|
||||||
if (profile == "es" || profile == "ES") {
|
bool profileTokenValid = true;
|
||||||
|
if (profile.empty() || profile == "core") {
|
||||||
|
info.profile = MobileGL::ShaderProfile::Core;
|
||||||
|
} else if (profile == "es" || profile == "ES") {
|
||||||
info.profile = MobileGL::ShaderProfile::ES;
|
info.profile = MobileGL::ShaderProfile::ES;
|
||||||
} else if (profile == "compatibility") {
|
} else if (profile == "compatibility") {
|
||||||
info.profile = MobileGL::ShaderProfile::Compatibility;
|
info.profile = MobileGL::ShaderProfile::Compatibility;
|
||||||
} else {
|
} else {
|
||||||
|
// "#version 330 foo": an unrecognized profile keyword. Keep Core for any
|
||||||
|
// downstream routing, but mark the directive malformed.
|
||||||
info.profile = MobileGL::ShaderProfile::Core;
|
info.profile = MobileGL::ShaderProfile::Core;
|
||||||
|
profileTokenValid = false;
|
||||||
}
|
}
|
||||||
|
// Comments are already masked to spaces, so anything non-blank left on the
|
||||||
|
// line is real trailing garbage: "#version 330 foobar" / "#version 330.0".
|
||||||
|
SkipDirectiveWhitespace(code, probe, lineEnd);
|
||||||
|
const bool hasTrailingTokens = probe < lineEnd;
|
||||||
|
info.hasValidVersionDirective =
|
||||||
|
IsRecognizedGlslVersion(info.version) && profileTokenValid && !hasTrailingTokens;
|
||||||
}
|
}
|
||||||
} else if (directive == "extension") {
|
} else if (directive == "extension") {
|
||||||
SkipDirectiveWhitespace(code, probe, lineEnd);
|
SkipDirectiveWhitespace(code, probe, lineEnd);
|
||||||
@@ -605,6 +708,17 @@ namespace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void NormalizeVersionDirective(MobileGL::String& source, const ShaderLanguageInfo& info) {
|
void NormalizeVersionDirective(MobileGL::String& source, const ShaderLanguageInfo& info) {
|
||||||
|
// A malformed #version (329, 331, bad profile, float/trailing tokens) is left exactly as the
|
||||||
|
// application wrote it so glslang rejects it - rewriting it to "#version 330 core" would
|
||||||
|
// silently legalize the CTS directive.version_* rejection cases. Still drop a leading BOM so
|
||||||
|
// the reported error is the bad version rather than a stray byte-order mark.
|
||||||
|
if (info.HasVersionDirective() && !info.hasValidVersionDirective) {
|
||||||
|
if (info.hasUtf8Bom) {
|
||||||
|
source.erase(0, 3);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const MobileGL::String replacement = GetNormalizedVersionDirective(info);
|
const MobileGL::String replacement = GetNormalizedVersionDirective(info);
|
||||||
if (info.HasVersionDirective()) {
|
if (info.HasVersionDirective()) {
|
||||||
source.replace(info.versionDirectiveStart, info.versionDirectiveEnd - info.versionDirectiveStart,
|
source.replace(info.versionDirectiveStart, info.versionDirectiveEnd - info.versionDirectiveStart,
|
||||||
@@ -686,7 +800,10 @@ namespace {
|
|||||||
|
|
||||||
void RenameBuiltinShadowingFunction(MobileGL::String& source, const char* from, const char* to) {
|
void RenameBuiltinShadowingFunction(MobileGL::String& source, const char* from, const char* to) {
|
||||||
const MobileGL::String fromName = from;
|
const MobileGL::String fromName = from;
|
||||||
if (!HasSingleLineFunctionDefinition(source, fromName)) {
|
// Decide from a comment-free view. A commented-out definition is not a definition, and
|
||||||
|
// acting on one renames every genuine call to the builtin to a name nothing defines - which
|
||||||
|
// then fails to resolve. Line comments survive BlankBlockComments, so this matters.
|
||||||
|
if (!HasSingleLineFunctionDefinition(MaskCommentsAndQuotedText(source), fromName)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -759,6 +876,58 @@ namespace {
|
|||||||
return info.HasVersionDirective() ? info.versionDirectiveEnd : 0;
|
return info.HasVersionDirective() ? info.versionDirectiveEnd : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GLSL's #line takes integer expressions only, but plenty of shader-pack preprocessors emit the
|
||||||
|
// C form with a quoted filename. Deleting every #line outright made those harmless - at the cost
|
||||||
|
// of __LINE__ reporting the position in MobileGL's rewritten text rather than the one the pack
|
||||||
|
// author wrote, and of every later diagnostic pointing at the wrong line. Dropping just the
|
||||||
|
// quoted operand keeps the directive doing its job and still hands glslang something it accepts.
|
||||||
|
void NormalizeLineDirectives(MobileGL::String& source) {
|
||||||
|
const MobileGL::String masked = MaskCommentsAndQuotedText(source);
|
||||||
|
const SizeT versionEnd = FindAfterVersionDirective(source);
|
||||||
|
MobileGL::String result;
|
||||||
|
result.reserve(source.size());
|
||||||
|
|
||||||
|
SizeT lineStart = 0;
|
||||||
|
while (lineStart <= source.size()) {
|
||||||
|
SizeT lineEnd = source.find('\n', lineStart);
|
||||||
|
const bool lastLine = lineEnd == MobileGL::String::npos;
|
||||||
|
if (lastLine) lineEnd = source.size();
|
||||||
|
|
||||||
|
SizeT probe = lineStart;
|
||||||
|
while (probe < lineEnd && (source[probe] == ' ' || source[probe] == '\t')) probe++;
|
||||||
|
|
||||||
|
const bool isLineDirective = masked.compare(probe, 5, "#line") == 0 &&
|
||||||
|
(probe + 5 >= lineEnd || !IsIdentifierChar(source[probe + 5]));
|
||||||
|
if (isLineDirective && lineStart < versionEnd) {
|
||||||
|
// #version has to be the first token in the shader, so a #line ahead of it could
|
||||||
|
// never have taken effect. Drop it rather than hand glslang a source it must reject
|
||||||
|
// - some pack preprocessors emit their directives before the version line.
|
||||||
|
} else if (isLineDirective) {
|
||||||
|
// Keep everything up to the first quote that the masker identified as string text.
|
||||||
|
SizeT quotePos = MobileGL::String::npos;
|
||||||
|
for (SizeT i = probe + 5; i < lineEnd; i++) {
|
||||||
|
if (source[i] == '"' || source[i] == '\'') {
|
||||||
|
quotePos = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (quotePos != MobileGL::String::npos) {
|
||||||
|
result.append(source, lineStart, quotePos - lineStart);
|
||||||
|
} else {
|
||||||
|
result.append(source, lineStart, lineEnd - lineStart);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result.append(source, lineStart, lineEnd - lineStart);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastLine) break;
|
||||||
|
result.push_back('\n');
|
||||||
|
lineStart = lineEnd + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
source = std::move(result);
|
||||||
|
}
|
||||||
|
|
||||||
bool IsExtensionAdvertised(MobileGL::GLExtension extension) {
|
bool IsExtensionAdvertised(MobileGL::GLExtension extension) {
|
||||||
const auto& activeBackendObject = MobileGL::MG_Backend::pActiveBackendObject;
|
const auto& activeBackendObject = MobileGL::MG_Backend::pActiveBackendObject;
|
||||||
if (!activeBackendObject) {
|
if (!activeBackendObject) {
|
||||||
@@ -1078,32 +1247,9 @@ namespace MobileGL {
|
|||||||
const ShaderLanguageInfo originalLanguage = InspectShaderLanguage(source);
|
const ShaderLanguageInfo originalLanguage = InspectShaderLanguage(source);
|
||||||
NormalizeVersionDirective(source, originalLanguage);
|
NormalizeVersionDirective(source, originalLanguage);
|
||||||
|
|
||||||
// remove multi-line comment
|
BlankBlockComments(source);
|
||||||
size_t commentStartPos = source.find("/*");
|
|
||||||
while (commentStartPos != String::npos) {
|
|
||||||
size_t commentEndPos = source.find("*/", commentStartPos);
|
|
||||||
if (commentEndPos == String::npos) {
|
|
||||||
source.erase(commentStartPos);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// + length of "*/"
|
|
||||||
source = source.replace(commentStartPos, commentEndPos - commentStartPos + 2, "");
|
|
||||||
commentStartPos = source.find("/*", commentStartPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove #line directives
|
NormalizeLineDirectives(source);
|
||||||
SizeT linedirPos = source.find("#line");
|
|
||||||
while (linedirPos != String::npos) {
|
|
||||||
SizeT newlinePos = source.find('\n', linedirPos);
|
|
||||||
if (newlinePos == String::npos) {
|
|
||||||
source.erase(linedirPos);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Preserve a line break so adjacent preprocessor directives do not merge.
|
|
||||||
source = source.replace(linedirPos, newlinePos - linedirPos + 1, "\n");
|
|
||||||
linedirPos = source.find("#line", linedirPos);
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove "noperspective"
|
// remove "noperspective"
|
||||||
const char* str_np = "noperspective";
|
const char* str_np = "noperspective";
|
||||||
@@ -1137,6 +1283,10 @@ namespace MobileGL {
|
|||||||
// must not be mistaken for the real one.
|
// must not be mistaken for the real one.
|
||||||
const ShaderLanguageInfo info = InspectShaderLanguage(source);
|
const ShaderLanguageInfo info = InspectShaderLanguage(source);
|
||||||
if (!info.HasVersionDirective()) return false;
|
if (!info.HasVersionDirective()) return false;
|
||||||
|
// Never rescue a malformed directive to 460: that is precisely what re-legalized the
|
||||||
|
// CTS directive.version_* rejection cases after the first compile failed. The shader-
|
||||||
|
// pack retry this exists for only ever sees a valid low version (a real "#version 330").
|
||||||
|
if (!info.hasValidVersionDirective) return false;
|
||||||
// Only the set NormalizeVersionDirective downgraded: desktop core below 400. ES and
|
// Only the set NormalizeVersionDirective downgraded: desktop core below 400. ES and
|
||||||
// compatibility shaders keep whatever they declared.
|
// compatibility shaders keep whatever they declared.
|
||||||
if (info.profile != ShaderProfile::Core || info.version >= 400) return false;
|
if (info.profile != ShaderProfile::Core || info.version >= 400) return false;
|
||||||
|
|||||||
Reference in New Issue
Block a user