[Fix] (MG_Backend/DirectVulkan, MG_Impl): a malformed graphics pipeline shape skips the draw instead of faulting inside the driver

This commit is contained in:
2026-08-11 20:10:18 -04:00
parent 2ce0595fab
commit 43bcd03dca
2 changed files with 44 additions and 0 deletions
@@ -4623,6 +4623,43 @@ void main() {
}
}
// Shape gate. Behind the memo probe deliberately: only a pipeline that was created
// successfully is ever memoized, so a program refused here can never be sitting in the
// memo, and the steady-state draw keeps paying nothing for the check.
//
// vkCreateGraphicsPipelines is not a validating entry point: a stage set that a
// conformant implementation would reject with VK_ERROR_* is, on Adreno 830, a SIGSEGV
// inside the driver - process death instead of a failed draw. The separable-program path
// is what made these shapes reachable at all (a monolithic glUseProgram program cannot
// hold a compute stage together with graphics ones, a pipeline object can), so the three
// it can produce are named and refused here. Same philosophy as the VK_NULL_HANDLE gate
// in SetupDraw: hostile input degrades to a broken draw, never to a dead process. GL
// leaves all three undefined for a draw, so nothing legal is being turned away.
// MGLOG_I because the INFO builds CTS runs against keep only I and F.
{
Bool hasVertexStage = false;
for (const auto& stage : programObj.stages) {
if (stage.module == VK_NULL_HANDLE) {
MGLOG_I("GetOrCreatePipeline skipped: program=%u has a null shader module for stage 0x%x",
program.GetExternalIndex(), static_cast<unsigned>(stage.stage));
return VK_NULL_HANDLE;
}
if (stage.stage == VK_SHADER_STAGE_COMPUTE_BIT) {
MGLOG_I("GetOrCreatePipeline skipped: program=%u carries a compute stage, which no graphics "
"pipeline may contain",
program.GetExternalIndex());
return VK_NULL_HANDLE;
}
if (stage.stage == VK_SHADER_STAGE_VERTEX_BIT) {
hasVertexStage = true;
}
}
if (!hasVertexStage) {
MGLOG_I("GetOrCreatePipeline skipped: program=%u has no vertex stage", program.GetExternalIndex());
return VK_NULL_HANDLE;
}
}
#if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG
const auto& limits = m_physicalDevice.properties.limits;
if (programObj.fragmentInputComponentCount != 0) {
@@ -670,6 +670,11 @@ namespace MobileGL::MG_Impl::GLImpl {
5 * sizeof(Uint32), __func__)) {
return;
}
// The only two draw entry points that were missing this. Every backend draw path
// dereferences GetProgramForDraw() unconditionally, so "no current program" has to be
// stopped here or it is a null dereference rather than the INVALID_OPERATION the spec
// asks for - reachable through a bound pipeline that supplies no graphics stage.
if (!ValidateCurrentProgramForExecution(__func__)) return;
auto multiDrawElementsIndirectCount = MG_Backend::gBackendFunctionsTable.GL.MultiDrawElementsIndirectCount;
if (!multiDrawElementsIndirectCount) {
MG_State::pGLContext->RecordError(
@@ -689,6 +694,8 @@ namespace MobileGL::MG_Impl::GLImpl {
4 * sizeof(Uint32), __func__)) {
return;
}
// See MultiDrawElementsIndirectCount.
if (!ValidateCurrentProgramForExecution(__func__)) return;
auto multiDrawArraysIndirectCount = MG_Backend::gBackendFunctionsTable.GL.MultiDrawArraysIndirectCount;
if (!multiDrawArraysIndirectCount) {
MG_State::pGLContext->RecordError(