[Fix]: fix Chocapic V6 Lite

- prune unused SPIR-V interface variables before GLES transpilation

- remap shader varyings through glslang IO resolver bindings

- initialize opaque uniforms from explicit sampler bindings only

- avoid side effects in texture binding assertions

- register Chocapic V6 Lite retrace fixture
This commit is contained in:
2026-06-19 18:42:32 +08:00
parent 7d101182cd
commit 19e4ba386d
11 changed files with 231 additions and 39 deletions
@@ -1589,8 +1589,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
MOBILEGL_ASSERT(mipmapTexture != nullptr, "GenerateMipmap requires mipmap texture storage.");
Bool allocatedStorage = false;
for (const TextureUploadTarget uploadTarget : texture->GetUploadTargets()) {
MOBILEGL_ASSERT(EnsureGenerateMipmapStorageAllocated(*mipmapTexture, uploadTarget, allocatedStorage),
"GenerateMipmap could not allocate generated mipmap storage.");
const Bool allocated = EnsureGenerateMipmapStorageAllocated(*mipmapTexture, uploadTarget, allocatedStorage);
MOBILEGL_ASSERT(allocated, "GenerateMipmap could not allocate generated mipmap storage.");
}
return allocatedStorage;
}
@@ -554,11 +554,79 @@ namespace MobileGL::MG_Backend::DirectGLES {
currentTextureInfo.mipmapLevels = mipmapCount;
Bool needsRegeneration = !m_isInitialized || (currentTextureInfo != m_prevTextureInfo);
const Bool canAppendMipmaps =
m_isInitialized &&
currentTextureInfo.internalFormat == m_prevTextureInfo.internalFormat &&
currentTextureInfo.width == m_prevTextureInfo.width &&
currentTextureInfo.height == m_prevTextureInfo.height &&
currentTextureInfo.depth == m_prevTextureInfo.depth &&
currentTextureInfo.bufferExternalIndex == m_prevTextureInfo.bufferExternalIndex &&
currentTextureInfo.samples == m_prevTextureInfo.samples &&
currentTextureInfo.fixedSampleLocations == m_prevTextureInfo.fixedSampleLocations &&
currentTextureInfo.mipmapLevels > m_prevTextureInfo.mipmapLevels &&
!TextureImpl::IsMultisampleTextureTarget(targetInternal);
MGLOG_D("%s: Got texture info: %dx%dx%d, mips %d, format %s", __func__, baseSize.x(), baseSize.y(),
baseSize.z(), mipmapCount,
MG_Util::ConvertTextureInternalFormatToString(textureMipmapObject->GetFormat()).c_str());
if (canAppendMipmaps) {
MGLOG_D("Texture mip count increased for backend ID %u, appending levels %zu..%zu",
m_backendTextureId, m_prevTextureInfo.mipmapLevels, mipmapCount - 1);
GLenum glInternalFormat, glType, glFormat;
TextureImpl::GenerateTextureFormatInfo(textureMipmapObject->GetFormat(), &glInternalFormat,
&glFormat, &glType);
const auto& uploadTargets = textureMipmapObject->GetUploadTargets();
for (auto& uploadTarget : uploadTargets) {
for (SizeT level = m_prevTextureInfo.mipmapLevels; level < mipmapCount; ++level) {
auto levelTexelSize = textureMipmapObject->GetMipmapTexelSize(uploadTarget, level);
auto levelByteSize = textureMipmapObject->GetMipmapByteSize(uploadTarget, level);
bool levelDirty = textureMipmapObject->IsStorageDirty(uploadTarget, level);
auto glUploadTarget = MG_Util::ConvertTextureUploadTargetToGLEnum(uploadTarget);
auto* pData = (levelDirty && levelByteSize != 0)
? textureMipmapObject->MapMipmapData(uploadTarget, level)
: nullptr;
DebugImpl::ErrorLopper::Clear();
g_GLESFuncs.glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
switch (stateTextureObject->GetTarget()) {
case TextureTarget::Texture2D:
case TextureTarget::TextureCubeMap:
g_GLESFuncs.glTexImage2D(
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
static_cast<GLsizei>(levelTexelSize.x()), static_cast<GLsizei>(levelTexelSize.y()),
0, glFormat, glType, pData);
break;
case TextureTarget::Texture3D:
g_GLESFuncs.glTexImage3D(
glUploadTarget, static_cast<GLint>(level), (GLint)glInternalFormat,
static_cast<GLsizei>(levelTexelSize.x()), static_cast<GLsizei>(levelTexelSize.y()),
static_cast<GLsizei>(levelTexelSize.z()), 0, glFormat, glType, pData);
break;
default:
MGLOG_E("Unhandled texture target %s",
MG_Util::ConvertTextureTargetToString(stateTextureObject->GetTarget()).c_str());
break;
}
DebugImpl::ErrorLopper::Loop([file = __FILE__, line = __LINE__, func = __func__,
glUploadTarget, glInternalFormat, glFormat, glType,
pData](GLenum err) {
MGLOG_D("%s(%s:%d) ES error: %s. glTexImage*: target=%s, internalformat=%s, format=%s, "
"type=%s, pixels=%p",
func, file, line, MG_Util::ConvertGLEnumToString(err).c_str(),
MG_Util::ConvertGLEnumToString(glUploadTarget).c_str(),
MG_Util::ConvertGLEnumToString(glInternalFormat).c_str(),
MG_Util::ConvertGLEnumToString(glFormat).c_str(),
MG_Util::ConvertGLEnumToString(glType).c_str(), pData);
});
textureMipmapObject->MarkStorageDirty(uploadTarget, level, false);
}
}
needsRegeneration = false;
}
if (needsRegeneration) {
MGLOG_D("Texture state changed significantly or not initialized, regenerating texture with ID: %u",
m_backendTextureId);
@@ -4212,8 +4212,9 @@ void main() {
static_cast<Int>(resource->format));
}
MOBILEGL_ASSERT(EnsureGenerateMipmapStorageAllocated(*mipmapTexture, uploadTarget, baseMipLevel),
"GenerateMipmap could not allocate a full mip chain for this texture.");
const Bool allocatedMipmapStorage =
EnsureGenerateMipmapStorageAllocated(*mipmapTexture, uploadTarget, baseMipLevel);
MOBILEGL_ASSERT(allocatedMipmapStorage, "GenerateMipmap could not allocate a full mip chain for this texture.");
resource = m_textureManager->SyncTextureAndGetDescriptor(*texture);
MOBILEGL_ASSERT(resource != nullptr && resource->image != VK_NULL_HANDLE,
@@ -2837,16 +2837,16 @@ namespace MobileGL::MG_Impl::GLImpl {
if (textureObject) {
auto* mipmapTexture = dynamic_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
MOBILEGL_ASSERT(mipmapTexture != nullptr, "GenerateMipmap requires mipmap texture storage.");
for (const TextureUploadTarget uploadTarget : textureObject->GetUploadTargets()) {
MOBILEGL_ASSERT(EnsureGeneratedMipmapStorageAllocated(*mipmapTexture, uploadTarget),
"GenerateMipmap could not allocate generated mipmap state.");
}
}
GenerateMipmap_Backend(target);
}
void GenerateTextureMipmap(GLuint texture) {
auto textureObject = GetTextureObjectByName(texture, __func__);
if (textureObject) {
auto* mipmapTexture = dynamic_cast<MG_State::GLState::TextureObjectMipmap*>(textureObject.get());
MOBILEGL_ASSERT(mipmapTexture != nullptr, "GenerateTextureMipmap requires mipmap texture storage.");
}
WithTemporarilyBoundNamedTexture(textureObject, [&](GLenum target) { GenerateMipmap_Backend(target); });
}
@@ -66,6 +66,7 @@ namespace MobileGL::MG_State::GLState {
m_uniformLocations.clear();
m_uniformIndexInTProgram.clear();
m_uniformSamplerOrImageUnitIndex.clear();
m_explicitOpaqueUniformBindings.clear();
m_uniformBlockIndexByName.clear();
m_uniformBlockBinding.clear();
m_uniformOffsets.clear();
@@ -199,7 +200,9 @@ namespace MobileGL::MG_State::GLState {
MG_Util::ShaderTranspiler::ProgramAttrib attrib{.shaders = Move(shaders),
.explicitVertexInLocations = m_explicitAttribLocations,
.explicitFragmentOutLocations = m_explicitFragDataLocation};
.explicitFragmentOutLocations = m_explicitFragDataLocation,
.explicitOpaqueUniformBindings =
&m_explicitOpaqueUniformBindings};
MGLOG_D("ProgramObject %u: Calling ShaderCompiler::LinkProgram", m_externalIndex);
auto result = MG_Util::ShaderTranspiler::ShaderCompiler::LinkProgram(attrib);
@@ -306,6 +309,11 @@ namespace MobileGL::MG_State::GLState {
}
SizeT locNeedle = 0;
std::sort(unallocatedUniformIndex.begin(), unallocatedUniformIndex.end(), [this](Int lhs, Int rhs) {
const auto& lhsUniform = m_program->getUniform(lhs);
const auto& rhsUniform = m_program->getUniform(rhs);
return lhsUniform.name < rhsUniform.name;
});
for (auto index : unallocatedUniformIndex) {
auto& uniform = m_program->getUniform(index);
for (; locNeedle <= m_maxUniformLocation; locNeedle++) {
@@ -334,12 +342,12 @@ namespace MobileGL::MG_State::GLState {
continue;
}
const int binding = uniform.getBinding();
if (binding >= 0 && binding != static_cast<int>(glslang::TQualifier::layoutBindingEnd)) {
m_uniformSamplerOrImageUnitIndex[location] = binding;
MGLOG_D("ProgramObject %u: Reflection - opaque uniform '%s' location=%u initialUnit=%d",
m_externalIndex, uniform.name.c_str(), location, binding);
}
const auto explicitBinding = m_explicitOpaqueUniformBindings.find(uniform.name);
const int initialUnit =
explicitBinding != m_explicitOpaqueUniformBindings.end() ? static_cast<int>(explicitBinding->second) : 0;
m_uniformSamplerOrImageUnitIndex[location] = initialUnit;
MGLOG_D("ProgramObject %u: Reflection - opaque uniform '%s' location=%u initialUnit=%d",
m_externalIndex, uniform.name.c_str(), location, initialUnit);
}
// ------------ attributes (vertex in) ---------------
@@ -461,7 +469,8 @@ namespace MobileGL::MG_State::GLState {
// 2. Do actual linking
ProgramAttrib attrib{.shaders = Move(shaders),
.explicitVertexInLocations = m_explicitAttribLocations,
.explicitFragmentOutLocations = m_explicitFragDataLocation};
.explicitFragmentOutLocations = m_explicitFragDataLocation,
.explicitOpaqueUniformBindings = &m_explicitOpaqueUniformBindings};
MGLOG_D("ProgramObject %u: GenerateBinary - linking program for binary", m_externalIndex);
auto programResult = ShaderCompiler::LinkProgram(attrib);
if (!programResult) {
@@ -495,6 +504,8 @@ namespace MobileGL::MG_State::GLState {
m_uniformSizesInBytes.clear();
m_uniformOffsets.clear();
m_globalUboScratch.clear();
m_uniformOffsets.resize(m_maxUniformLocation + 1);
m_uniformSizesInBytes.resize(m_maxUniformLocation + 1);
for (SizeT i = 0; i < m_generatedSpirv.size(); i++) {
auto& spv = m_generatedSpirv[i];
@@ -520,37 +531,34 @@ namespace MobileGL::MG_State::GLState {
if (size == 0) {
continue;
}
m_globalUboScratch.resize(size);
m_uniformOffsets.resize(m_maxUniformLocation + 1);
if (m_globalUboScratch.size() < size) {
m_globalUboScratch.resize(size);
}
for (const auto& [name, offset] : meta.plainUniformOffsetsInUBO) {
if (m_uniformLocations.find(name) != m_uniformLocations.end()) {
m_uniformOffsets[m_uniformLocations[name]] = offset;
MGLOG_D("ProgramObject %u: GenerateBinary - uniform '%s' offset=%u assigned to location %u",
m_externalIndex, name.c_str(), offset, m_uniformLocations[name]);
} else {
MGLOG_D("ProgramObject %u: GenerateBinary - uniform '%s' offset=%u but not found in "
"m_uniformLocations",
m_externalIndex, name.c_str(), offset);
}
} else {
MGLOG_D("ProgramObject %u: GenerateBinary - uniform '%s' offset=%u but not found in "
"m_uniformLocations",
m_externalIndex, name.c_str(), offset);
}
}
m_uniformSizesInBytes.resize(m_maxUniformLocation + 1);
for (const auto& [name, size] : meta.plainUniformMemberSizesInBytes) {
if (m_uniformLocations.find(name) != m_uniformLocations.end()) {
m_uniformSizesInBytes[m_uniformLocations[name]] = size;
MGLOG_D("ProgramObject %u: GenerateBinary - uniform '%s' size=%u assigned to location %u",
m_externalIndex, name.c_str(), size, m_uniformLocations[name]);
} else {
MGLOG_D("ProgramObject %u: GenerateBinary - uniform '%s' size=%u but not found in "
"m_uniformLocations",
m_externalIndex, name.c_str(), size);
}
} else {
MGLOG_D("ProgramObject %u: GenerateBinary - uniform '%s' size=%u but not found in "
"m_uniformLocations",
m_externalIndex, name.c_str(), size);
}
}
// Only parse first module that contains uniform metadata
MGLOG_D("ProgramObject %u: GenerateBinary - finished parsing module %zu; breaking after first "
"valid metadata",
MGLOG_D("ProgramObject %u: GenerateBinary - finished parsing module %zu metadata",
m_externalIndex, i);
}
break;
}
}
@@ -162,7 +162,12 @@ namespace MobileGL::MG_State::GLState {
Uint32 GetBackendStateVersion() const { return m_backendStateVersion; }
void SetUniformSamplerOrImageUnitIndex(Uint location, Int unit) {
if (location >= m_uniformSamplerOrImageUnitIndex.size() ||
m_uniformSamplerOrImageUnitIndex[location] == unit) {
return;
}
m_uniformSamplerOrImageUnitIndex[location] = unit;
++m_backendStateVersion;
}
Int GetUniformSamplerOrImageUnitIndex(Uint location) const {
@@ -260,6 +265,7 @@ namespace MobileGL::MG_State::GLState {
Vector<Int> m_uniformIndexInTProgram;
// ditto. Will be set at glUniform1i
Vector<Int> m_uniformSamplerOrImageUnitIndex;
UnorderedMap<String, Uint> m_explicitOpaqueUniformBindings;
// Ordered by uniform block index
// index is DIFFERENT from binding!!!
@@ -197,7 +197,8 @@ namespace MobileGL {
if (program->getIntermediate((EShLanguage)stage) == nullptr) continue;
resolver =
MakeUnique<TMglGlslIoResolver>(*program, (EShLanguage)stage, attrib.explicitVertexInLocations,
attrib.explicitFragmentOutLocations);
attrib.explicitFragmentOutLocations,
attrib.explicitOpaqueUniformBindings);
break;
}
auto ioMapper = UniquePtr<glslang::TIoMapper>(glslang::GetGlslIoMapper());
@@ -235,6 +236,8 @@ namespace MobileGL {
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
optimizer.RegisterPass(CreateAggressiveDCEPass(false));
optimizer.RegisterPass(CreateRemoveUnusedInterfaceVariablesPass());
optimizer.RegisterPass(FlattenInterfaceStructPass::CreateFlattenInterfaceStructPass());
optimizer.RegisterPass(EliminateFloatEqualsZeroPass::CreateEliminateFloatEqualsZeroPass());
@@ -31,6 +31,7 @@ namespace MobileGL {
Vector<SharedPtr<glslang::TShader>> shaders;
UnorderedMap<String, Uint> explicitVertexInLocations;
UnorderedMap<String, Uint> explicitFragmentOutLocations;
UnorderedMap<String, Uint>* explicitOpaqueUniformBindings = nullptr;
};
struct ProgramBinaryAttrib {
@@ -13,6 +13,53 @@
#include "TMglGlslIoResolver.h"
namespace MobileGL {
bool TMglGlslIoResolver::ShouldAssignPlainUniformLocation(const glslang::TType& type) const {
if (!doAutoLocationMapping()) {
return false;
}
if (type.getQualifier().hasLocation()) {
return false;
}
if (type.isBuiltIn() || type.getBasicType() == glslang::EbtBlock || type.isAtomic() || type.isSpirvType() ||
(type.containsOpaque() && referenceIntermediate.getSpv().openGl == 0)) {
return false;
}
if (type.isStruct()) {
if (type.getStruct()->size() < 1) {
return false;
}
if ((*type.getStruct())[0].type->isBuiltIn()) {
return false;
}
}
return true;
}
void TMglGlslIoResolver::EnsurePlainUniformLocationsAssigned() {
if (m_plainUniformLocationsAssigned) {
return;
}
m_plainUniformLocationsAssigned = true;
const int resourceKey = buildStorageKey(EShLangCount, glslang::EvqUniform);
auto& slotMap = storageSlotMap[resourceKey];
for (const auto& [name, size] : m_plainUniformLocationSizeByName) {
const auto existingLocation = slotMap.find(name);
if (existingLocation != slotMap.end()) {
m_plainUniformLocationByName[name] = existingLocation->second;
continue;
}
const int location = getFreeSlot(resourceKey, 0, size);
slotMap[name] = location;
m_plainUniformLocationByName[name] = location;
}
}
void TMglGlslIoResolver::reserverStorageSlot(glslang::TVarEntryInfo& ent, TInfoSink& infoSink) {
const glslang::TType& type = ent.symbol->getType();
const glslang::TString& name = ent.symbol->getAccessName();
@@ -30,6 +77,43 @@ namespace MobileGL {
writableType.getQualifier().layoutLocation = it->second;
}
}
if (ShouldAssignPlainUniformLocation(type)) {
const int size = glslang::TIntermediate::computeTypeUniformLocationSize(type);
auto& recordedSize = m_plainUniformLocationSizeByName[name];
recordedSize = std::max(recordedSize, size);
}
TDefaultGlslIoResolver::reserverStorageSlot(ent, infoSink);
}
} // namespace MobileGL
void TMglGlslIoResolver::reserverResourceSlot(glslang::TVarEntryInfo& ent, TInfoSink& infoSink) {
const glslang::TType& type = ent.symbol->getType();
if (m_explicitOpaqueUniformBindings != nullptr && type.getBasicType() == glslang::EbtSampler &&
type.getQualifier().hasBinding()) {
const glslang::TString& name = ent.symbol->getAccessName();
(*m_explicitOpaqueUniformBindings)[name.c_str()] = type.getQualifier().layoutBinding;
}
TDefaultGlslIoResolver::reserverResourceSlot(ent, infoSink);
}
int TMglGlslIoResolver::resolveUniformLocation(EShLanguage stage, glslang::TVarEntryInfo& ent) {
const glslang::TType& type = ent.symbol->getType();
if (type.getQualifier().hasLocation()) {
return TDefaultGlslIoResolver::resolveUniformLocation(stage, ent);
}
if (!ShouldAssignPlainUniformLocation(type)) {
return TDefaultGlslIoResolver::resolveUniformLocation(stage, ent);
}
EnsurePlainUniformLocationsAssigned();
const glslang::TString& name = ent.symbol->getAccessName();
const auto location = m_plainUniformLocationByName.find(name);
if (location == m_plainUniformLocationByName.end()) {
return ent.newLocation = -1;
}
return ent.newLocation = location->second;
}
} // namespace MobileGL
@@ -26,15 +26,26 @@ namespace MobileGL {
public:
using ExplicitVarSlotMap = UnorderedMap<String, Uint>;
TMglGlslIoResolver(const glslang::TIntermediate& intermediate, const ExplicitVarSlotMap& vertexIns,
const ExplicitVarSlotMap& fragOuts)
: TDefaultGlslIoResolver(intermediate), m_explicitVertexIns(vertexIns), m_explicitFragOuts(fragOuts) {}
const ExplicitVarSlotMap& fragOuts, ExplicitVarSlotMap* opaqueUniformBindings)
: TDefaultGlslIoResolver(intermediate), m_explicitVertexIns(vertexIns), m_explicitFragOuts(fragOuts),
m_explicitOpaqueUniformBindings(opaqueUniformBindings) {}
TMglGlslIoResolver(const glslang::TProgram& program, const EShLanguage stage,
const ExplicitVarSlotMap& vertexIns, const ExplicitVarSlotMap& fragOuts)
: TMglGlslIoResolver(*program.getIntermediate(stage), vertexIns, fragOuts) {}
const ExplicitVarSlotMap& vertexIns, const ExplicitVarSlotMap& fragOuts,
ExplicitVarSlotMap* opaqueUniformBindings)
: TMglGlslIoResolver(*program.getIntermediate(stage), vertexIns, fragOuts, opaqueUniformBindings) {}
void reserverStorageSlot(glslang::TVarEntryInfo& ent, TInfoSink& infoSink) override;
void reserverResourceSlot(glslang::TVarEntryInfo& ent, TInfoSink& infoSink) override;
int resolveUniformLocation(EShLanguage stage, glslang::TVarEntryInfo& ent) override;
protected:
bool ShouldAssignPlainUniformLocation(const glslang::TType& type) const;
void EnsurePlainUniformLocationsAssigned();
const ExplicitVarSlotMap& m_explicitVertexIns;
const ExplicitVarSlotMap& m_explicitFragOuts;
ExplicitVarSlotMap* m_explicitOpaqueUniformBindings = nullptr;
std::map<glslang::TString, int> m_plainUniformLocationSizeByName;
std::map<glslang::TString, int> m_plainUniformLocationByName;
bool m_plainUniformLocationsAssigned = false;
};
} // namespace MobileGL
+10
View File
@@ -445,6 +445,16 @@ add_trace_replay_test_for_backends(minecraft-1.21.4-fabric-iris-mellow-in-world
TOLERANCE 20
FUZZ_PERCENT 20)
add_trace_replay_test_for_backends(minecraft-1.21.4-fabric-iris-chocapic-v6-lite-in-world
TRACE_ARCHIVE ${MOBILEGL_TRACE_ROOT}/fixtures/minecraft-1.21.4-fabric-iris-chocapic-v6-lite-in-world.tgz
TRACE_FILE trace.trace
GOLDEN ${MOBILEGL_TRACE_ROOT}/fixtures/minecraft-1.21.4-fabric-iris-chocapic-v6-lite-in-world.0000125124.png
TARGET_CALL 125124
WIDTH 854
HEIGHT 480
TOLERANCE 700
FUZZ_PERCENT 20)
add_trace_replay_test_for_backends(minecraft-1.21.4-fabric-iris-iterationt-in-world
TRACE_ARCHIVE ${MOBILEGL_TRACE_ROOT}/fixtures/minecraft-1.21.4-fabric-iris-iterationt-in-world.tgz
TRACE_FILE trace.trace