diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp index 1f17a194..0671c09b 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp @@ -51,6 +51,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool isMember = false; }; + ShaderStage PickClipFixupStage(const Vector>& shaders); + Bool IsVec4Float32(spvtools::opt::IRContext* context, Uint32 typeId, Uint32* outFloatTypeId) { auto* vecInst = context->get_def_use_mgr()->GetDef(typeId); if (!vecInst || vecInst->opcode() != spv::Op::OpTypeVector) return false; @@ -65,6 +67,588 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } + spvc_basetype MapReflectInterfaceToSpvcBasetype(const SpvReflectInterfaceVariable& variable) { + if (variable.type_description == nullptr) { + return SPVC_BASETYPE_UNKNOWN; + } + + const auto flags = variable.type_description->type_flags; + const auto width = variable.numeric.scalar.width; + const auto signedness = variable.numeric.scalar.signedness; + if ((flags & SPV_REFLECT_TYPE_FLAG_FLOAT) != 0) { + switch (width) { + case 16: return SPVC_BASETYPE_FP16; + case 32: return SPVC_BASETYPE_FP32; + case 64: return SPVC_BASETYPE_FP64; + default: return SPVC_BASETYPE_UNKNOWN; + } + } + if ((flags & SPV_REFLECT_TYPE_FLAG_INT) != 0) { + if (signedness != 0) { + switch (width) { + case 8: return SPVC_BASETYPE_INT8; + case 16: return SPVC_BASETYPE_INT16; + case 32: return SPVC_BASETYPE_INT32; + case 64: return SPVC_BASETYPE_INT64; + default: return SPVC_BASETYPE_UNKNOWN; + } + } + + switch (width) { + case 8: return SPVC_BASETYPE_UINT8; + case 16: return SPVC_BASETYPE_UINT16; + case 32: return SPVC_BASETYPE_UINT32; + case 64: return SPVC_BASETYPE_UINT64; + default: return SPVC_BASETYPE_UNKNOWN; + } + } + if ((flags & SPV_REFLECT_TYPE_FLAG_BOOL) != 0) { + return SPVC_BASETYPE_BOOLEAN; + } + return SPVC_BASETYPE_UNKNOWN; + } + + Uint32 GetReflectInterfaceLocationSpan(const SpvReflectInterfaceVariable& variable) { + Uint32 locationSpan = variable.numeric.matrix.column_count; + if (locationSpan == 0) { + locationSpan = 1; + } + + for (Uint32 dimIndex = 0; dimIndex < variable.array.dims_count; ++dimIndex) { + const Uint32 dim = variable.array.dims[dimIndex]; + if (dim == 0 || dim == SPV_REFLECT_ARRAY_DIM_RUNTIME) { + continue; + } + locationSpan *= dim; + } + + return locationSpan; + } + + GLenum GetReflectInterfaceLocationType(const SpvReflectInterfaceVariable& variable) { + MG_Util::ShaderTranspiler::SpvcType spvcType{}; + spvcType.basetype = MapReflectInterfaceToSpvcBasetype(variable); + spvcType.vectorSize = variable.numeric.vector.component_count; + if (spvcType.vectorSize == 0) { + spvcType.vectorSize = variable.numeric.matrix.row_count; + } + if (spvcType.vectorSize == 0) { + spvcType.vectorSize = 1; + } + spvcType.matCol = 1; + + if (spvcType.vectorSize < 1 || spvcType.vectorSize > 4) { + return GL_FALSE; + } + + switch (spvcType.basetype) { + case SPVC_BASETYPE_BOOLEAN: + switch (spvcType.vectorSize) { + case 1: return GL_BOOL; + case 2: return GL_BOOL_VEC2; + case 3: return GL_BOOL_VEC3; + case 4: return GL_BOOL_VEC4; + default: return GL_FALSE; + } + case SPVC_BASETYPE_INT32: + switch (spvcType.vectorSize) { + case 1: return GL_INT; + case 2: return GL_INT_VEC2; + case 3: return GL_INT_VEC3; + case 4: return GL_INT_VEC4; + default: return GL_FALSE; + } + case SPVC_BASETYPE_UINT32: + switch (spvcType.vectorSize) { + case 1: return GL_UNSIGNED_INT; + case 2: return GL_UNSIGNED_INT_VEC2; + case 3: return GL_UNSIGNED_INT_VEC3; + case 4: return GL_UNSIGNED_INT_VEC4; + default: return GL_FALSE; + } + case SPVC_BASETYPE_FP32: + switch (spvcType.vectorSize) { + case 1: return GL_FLOAT; + case 2: return GL_FLOAT_VEC2; + case 3: return GL_FLOAT_VEC3; + case 4: return GL_FLOAT_VEC4; + default: return GL_FALSE; + } + case SPVC_BASETYPE_FP64: + switch (spvcType.vectorSize) { + case 1: return GL_DOUBLE; + case 2: return GL_DOUBLE_VEC2; + case 3: return GL_DOUBLE_VEC3; + case 4: return GL_DOUBLE_VEC4; + default: return GL_FALSE; + } + default: + return GL_FALSE; + } + } + + Uint32 GetReflectInterfaceLocationSignature(const SpvReflectInterfaceVariable& variable) { + Uint32 vectorSize = variable.numeric.vector.component_count; + if (vectorSize == 0) { + vectorSize = variable.numeric.matrix.row_count; + } + if (vectorSize == 0) { + vectorSize = 1; + } + if (vectorSize < 1 || vectorSize > 4) { + return 0; + } + + Uint32 typeClass = 0; + Uint32 scalarWidth = 0; + switch (MapReflectInterfaceToSpvcBasetype(variable)) { + case SPVC_BASETYPE_BOOLEAN: + typeClass = 1; + scalarWidth = 1; + break; + case SPVC_BASETYPE_INT8: + typeClass = 2; + scalarWidth = 8; + break; + case SPVC_BASETYPE_INT16: + typeClass = 2; + scalarWidth = 16; + break; + case SPVC_BASETYPE_INT32: + typeClass = 2; + scalarWidth = 32; + break; + case SPVC_BASETYPE_INT64: + typeClass = 2; + scalarWidth = 64; + break; + case SPVC_BASETYPE_UINT8: + typeClass = 3; + scalarWidth = 8; + break; + case SPVC_BASETYPE_UINT16: + typeClass = 3; + scalarWidth = 16; + break; + case SPVC_BASETYPE_UINT32: + typeClass = 3; + scalarWidth = 32; + break; + case SPVC_BASETYPE_UINT64: + typeClass = 3; + scalarWidth = 64; + break; + case SPVC_BASETYPE_FP16: + typeClass = 4; + scalarWidth = 16; + break; + case SPVC_BASETYPE_FP32: + typeClass = 4; + scalarWidth = 32; + break; + case SPVC_BASETYPE_FP64: + typeClass = 4; + scalarWidth = 64; + break; + default: + return 0; + } + + return (typeClass << 24) | (scalarWidth << 8) | vectorSize; + } + + Uint32 GetReflectInterfaceVectorSize(const SpvReflectInterfaceVariable& variable) { + Uint32 vectorSize = variable.numeric.vector.component_count; + if (vectorSize == 0) { + vectorSize = variable.numeric.matrix.row_count; + } + if (vectorSize == 0) { + vectorSize = 1; + } + return vectorSize; + } + + struct StageInterfaceCursor { + Uint32 location = 0; + Uint32 component = 0; + }; + + struct StageInterfaceSummary { + static constexpr Uint32 kMaxComponentSlots = ProgramFactory::VkProgramObject::kMaxVertexInputLocations * 4; + + Array slotSignatures{}; + Array slotDebugNames{}; + }; + + Uint32 CountOccupiedStageInterfaceSlots(const StageInterfaceSummary& summary) { + Uint32 occupiedSlotCount = 0; + for (Uint32 slotIndex = 0; slotIndex < StageInterfaceSummary::kMaxComponentSlots; ++slotIndex) { + if (summary.slotSignatures[slotIndex] != 0) { + ++occupiedSlotCount; + } + } + return occupiedSlotCount; + } + + void ValidateTransformedSpirv(const Vector& spirv, ShaderStage shaderStage, Uint programExternalIndex) { + if (spirv.empty()) { + return; + } + + spv_const_binary_t binary = {spirv.data(), spirv.size()}; + spv_target_env targetEnv = SPV_ENV_VULKAN_1_0; + if (spirv.size() > 1) { + const Uint32 versionWord = spirv[1]; + const Uint32 major = (versionWord >> 16) & 0xffu; + const Uint32 minor = (versionWord >> 8) & 0xffu; + if (major > 1 || (major == 1 && minor >= 6)) { + targetEnv = SPV_ENV_VULKAN_1_3; + } else if (major == 1 && minor >= 5) { + targetEnv = SPV_ENV_VULKAN_1_2; + } else if (major == 1 && minor >= 4) { + targetEnv = SPV_ENV_VULKAN_1_1_SPIRV_1_4; + } else if (major == 1 && minor >= 3) { + targetEnv = SPV_ENV_VULKAN_1_1; + } + } + + spv_context context = spvContextCreate(targetEnv); + MOBILEGL_ASSERT(context != nullptr, + "ProgramFactory::ValidateTransformedSpirv: failed to create validator context for stage=%d program=%u", + static_cast(shaderStage), + programExternalIndex); + + spv_validator_options options = spvValidatorOptionsCreate(); + MOBILEGL_ASSERT(options != nullptr, + "ProgramFactory::ValidateTransformedSpirv: failed to create validator options for stage=%d program=%u", + static_cast(shaderStage), + programExternalIndex); + spvValidatorOptionsSetFriendlyNames(options, true); + + spv_diagnostic diagnostic = nullptr; + const spv_result_t result = spvValidateWithOptions(context, options, &binary, &diagnostic); + MOBILEGL_ASSERT( + result == SPV_SUCCESS, + "ProgramFactory::ValidateTransformedSpirv: validation failed for stage=%d program=%u result=%d line=%zu column=%zu index=%zu msg=%s", + static_cast(shaderStage), + programExternalIndex, + static_cast(result), + diagnostic != nullptr ? diagnostic->position.line : 0, + diagnostic != nullptr ? diagnostic->position.column : 0, + diagnostic != nullptr ? diagnostic->position.index : 0, + diagnostic != nullptr && diagnostic->error != nullptr ? diagnostic->error : ""); + + spvDiagnosticDestroy(diagnostic); + spvValidatorOptionsDestroy(options); + spvContextDestroy(context); + } + + void ReflectStageInterfaceVariable(const SpvReflectInterfaceVariable& variable, + Bool reflectInputs, + StageInterfaceSummary& outSummary, + Uint programExternalIndex, + const char* stageLabel, + StageInterfaceCursor& cursor, + Uint32 locationBase = 0, + Bool allowImplicitPacking = false, + const char* inheritedName = nullptr) { + if ((variable.decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) != 0) { + return; + } + + const char* debugName = variable.name; + if (debugName == nullptr || debugName[0] == '\0') { + debugName = inheritedName; + } + if (debugName == nullptr || debugName[0] == '\0') { + debugName = ""; + } + + const Bool hasConcreteLocation = + allowImplicitPacking ? (variable.location != 0 || variable.component != 0) + : variable.location != std::numeric_limits::max(); + const Bool hasConcreteComponent = + allowImplicitPacking ? (variable.component != 0) + : variable.component != std::numeric_limits::max(); + const Uint32 explicitLocationBase = locationBase + (hasConcreteLocation ? variable.location : 0u); + + if (variable.member_count > 0 && variable.members != nullptr) { + StageInterfaceCursor memberCursor = cursor; + if (hasConcreteLocation) { + memberCursor.location = explicitLocationBase; + memberCursor.component = 0; + } + for (Uint32 memberIndex = 0; memberIndex < variable.member_count; ++memberIndex) { + ReflectStageInterfaceVariable(variable.members[memberIndex], reflectInputs, outSummary, + programExternalIndex, stageLabel, memberCursor, + explicitLocationBase, true, debugName); + } + if (memberCursor.location > cursor.location || + (memberCursor.location == cursor.location && memberCursor.component > cursor.component)) { + cursor = memberCursor; + } + return; + } + + MOBILEGL_ASSERT( + hasConcreteLocation || allowImplicitPacking || locationBase != 0, + "ProgramFactory::ReflectStageInterface: missing concrete %s %s location for name='%s' program=%u", + stageLabel, + reflectInputs ? "input" : "output", + debugName, + programExternalIndex); + + const Uint32 component = variable.component; + MOBILEGL_ASSERT( + component < 4 || component == std::numeric_limits::max(), + "ProgramFactory::ReflectStageInterface: unsupported %s %s component=%u at location=%u name='%s' program=%u", + stageLabel, + reflectInputs ? "input" : "output", + component, + explicitLocationBase, + debugName, + programExternalIndex); + + const Uint32 locationSignature = GetReflectInterfaceLocationSignature(variable); + MOBILEGL_ASSERT( + locationSignature != 0, + "ProgramFactory::ReflectStageInterface: unsupported %s %s type at location=%u name='%s' flags=0x%x width=%u signed=%u vec=%u rows=%u cols=%u program=%u", + stageLabel, + reflectInputs ? "input" : "output", + explicitLocationBase, + debugName, + static_cast(variable.type_description != nullptr ? variable.type_description->type_flags : 0), + variable.numeric.scalar.width, + variable.numeric.scalar.signedness, + variable.numeric.vector.component_count, + variable.numeric.matrix.row_count, + variable.numeric.matrix.column_count, + programExternalIndex); + + const Uint32 vectorSize = GetReflectInterfaceVectorSize(variable); + const Uint32 locationSpan = GetReflectInterfaceLocationSpan(variable); + Uint32 startLocation = explicitLocationBase; + Uint32 startComponent = hasConcreteComponent ? component : 0u; + const Bool useImplicitPacking = allowImplicitPacking && !hasConcreteLocation && !hasConcreteComponent; + if (useImplicitPacking) { + startLocation = cursor.location; + startComponent = cursor.component; + if (locationSpan > 1 || startComponent + vectorSize > 4) { + if (startComponent != 0) { + ++startLocation; + startComponent = 0; + } + if (locationSpan == 1 && startComponent + vectorSize > 4) { + ++startLocation; + startComponent = 0; + } + } + } + + MOBILEGL_ASSERT( + startComponent < 4, + "ProgramFactory::ReflectStageInterface: %s %s component overflow at location=%u component=%u name='%s' program=%u", + stageLabel, + reflectInputs ? "input" : "output", + startLocation, + startComponent, + debugName, + programExternalIndex); + MOBILEGL_ASSERT( + locationSpan == 1 || startComponent == 0, + "ProgramFactory::ReflectStageInterface: %s %s multi-location variable starts at non-zero component location=%u component=%u name='%s' program=%u", + stageLabel, + reflectInputs ? "input" : "output", + startLocation, + startComponent, + debugName, + programExternalIndex); + + for (Uint32 locationOffset = 0; locationOffset < locationSpan; ++locationOffset) { + const Uint32 expandedLocation = startLocation + locationOffset; + const Uint32 componentBase = (locationOffset == 0) ? startComponent : 0u; + MOBILEGL_ASSERT( + expandedLocation < ProgramFactory::VkProgramObject::kMaxVertexInputLocations, + "ProgramFactory::ReflectStageInterface: %s %s location=%u span=%u exceeds tracked limit for name='%s' program=%u", + stageLabel, + reflectInputs ? "input" : "output", + startLocation, + locationSpan, + debugName, + programExternalIndex); + MOBILEGL_ASSERT( + componentBase + vectorSize <= 4, + "ProgramFactory::ReflectStageInterface: %s %s component span overflow at location=%u component=%u vec=%u name='%s' program=%u", + stageLabel, + reflectInputs ? "input" : "output", + expandedLocation, + componentBase, + vectorSize, + debugName, + programExternalIndex); + for (Uint32 componentOffset = 0; componentOffset < vectorSize; ++componentOffset) { + const Uint32 expandedComponent = componentBase + componentOffset; + const Uint32 slotIndex = expandedLocation * 4 + expandedComponent; + MOBILEGL_ASSERT( + outSummary.slotSignatures[slotIndex] == 0 || outSummary.slotSignatures[slotIndex] == locationSignature, + "ProgramFactory::ReflectStageInterface: conflicting %s %s type at location=%u component=%u existingSignature=0x%x existingName='%s' newSignature=0x%x newName='%s' program=%u", + stageLabel, + reflectInputs ? "input" : "output", + expandedLocation, + expandedComponent, + outSummary.slotSignatures[slotIndex], + outSummary.slotDebugNames[slotIndex].empty() ? "" : outSummary.slotDebugNames[slotIndex].c_str(), + locationSignature, + debugName, + programExternalIndex); + outSummary.slotSignatures[slotIndex] = locationSignature; + outSummary.slotDebugNames[slotIndex] = debugName; + } + } + + StageInterfaceCursor endCursor{}; + if (locationSpan > 1) { + endCursor.location = startLocation + locationSpan; + endCursor.component = 0; + } else { + endCursor.location = startLocation; + endCursor.component = startComponent + vectorSize; + if (endCursor.component >= 4) { + endCursor.location += endCursor.component / 4; + endCursor.component %= 4; + } + } + if (endCursor.location > cursor.location || + (endCursor.location == cursor.location && endCursor.component > cursor.component)) { + cursor = endCursor; + } + } + + void ReflectStageInterface(ShaderStage targetStage, + Bool reflectInputs, + const Vector>& shaders, + const Vector>& spirv, + StageInterfaceSummary& outSummary, + Uint programExternalIndex, + const char* stageLabel) { + outSummary.slotSignatures.fill(0); + + for (SizeT moduleIndex = 0; moduleIndex < shaders.size() && moduleIndex < spirv.size(); ++moduleIndex) { + if (!shaders[moduleIndex] || shaders[moduleIndex]->GetShaderStage() != targetStage) { + continue; + } + + const auto& module = spirv[moduleIndex]; + if (module.empty()) { + continue; + } + + SpvReflectShaderModule reflectModule{}; + const SpvReflectResult createResult = + spvReflectCreateShaderModule(module.size() * sizeof(Uint), module.data(), &reflectModule); + MOBILEGL_ASSERT( + createResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory::ReflectStageInterface: failed to create reflection module for %s %s (result=%d program=%u)", + stageLabel, + reflectInputs ? "input" : "output", + static_cast(createResult), + programExternalIndex); + if (createResult != SPV_REFLECT_RESULT_SUCCESS) { + continue; + } + + uint32_t variableCount = 0; + SpvReflectResult reflectResult = reflectInputs + ? spvReflectEnumerateInputVariables(&reflectModule, &variableCount, nullptr) + : spvReflectEnumerateOutputVariables(&reflectModule, &variableCount, nullptr); + MOBILEGL_ASSERT( + reflectResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory::ReflectStageInterface: failed to enumerate %s %s variables (result=%d program=%u)", + stageLabel, + reflectInputs ? "input" : "output", + static_cast(reflectResult), + programExternalIndex); + + Vector variables(variableCount); + if (reflectResult == SPV_REFLECT_RESULT_SUCCESS && variableCount > 0) { + reflectResult = reflectInputs + ? spvReflectEnumerateInputVariables(&reflectModule, &variableCount, variables.data()) + : spvReflectEnumerateOutputVariables(&reflectModule, &variableCount, variables.data()); + MOBILEGL_ASSERT( + reflectResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory::ReflectStageInterface: failed to fetch %s %s variables (result=%d program=%u)", + stageLabel, + reflectInputs ? "input" : "output", + static_cast(reflectResult), + programExternalIndex); + } + + if (reflectResult == SPV_REFLECT_RESULT_SUCCESS) { + StageInterfaceCursor stageCursor{}; + for (auto* variable : variables) { + if (variable == nullptr) { + continue; + } + ReflectStageInterfaceVariable(*variable, reflectInputs, outSummary, programExternalIndex, + stageLabel, stageCursor); + } + } + + spvReflectDestroyShaderModule(&reflectModule); + break; + } + } + + void ValidateRasterizationStageInterface(const Vector>& shaders, + const Vector>& spirv, + ProgramFactory::VkProgramObject& entry, + Uint programExternalIndex) { + const ShaderStage producerStage = PickClipFixupStage(shaders); + entry.rasterizationProducerStage = producerStage; + entry.producerOutputComponentCount = 0; + entry.fragmentInputComponentCount = 0; + if (producerStage == ShaderStage::Unknown) { + return; + } + + Bool hasFragmentStage = false; + for (const auto& shader : shaders) { + if (shader && shader->GetShaderStage() == ShaderStage::Fragment) { + hasFragmentStage = true; + break; + } + } + if (!hasFragmentStage) { + return; + } + + StageInterfaceSummary producerOutputs{}; + StageInterfaceSummary fragmentInputs{}; + ReflectStageInterface(producerStage, false, shaders, spirv, producerOutputs, programExternalIndex, + "producer"); + ReflectStageInterface(ShaderStage::Fragment, true, shaders, spirv, fragmentInputs, programExternalIndex, + "fragment"); + entry.producerOutputComponentCount = CountOccupiedStageInterfaceSlots(producerOutputs); + entry.fragmentInputComponentCount = CountOccupiedStageInterfaceSlots(fragmentInputs); + + for (Uint32 slotIndex = 0; slotIndex < StageInterfaceSummary::kMaxComponentSlots; ++slotIndex) { + if (fragmentInputs.slotSignatures[slotIndex] == 0) { + continue; + } + + MOBILEGL_ASSERT( + producerOutputs.slotSignatures[slotIndex] == fragmentInputs.slotSignatures[slotIndex], + "ProgramFactory::ValidateRasterizationStageInterface: location=%u component=%u producerSignature=0x%x producerName='%s' fragmentSignature=0x%x fragmentName='%s' program=%u", + slotIndex / 4, + slotIndex % 4, + producerOutputs.slotSignatures[slotIndex], + producerOutputs.slotDebugNames[slotIndex].empty() ? "" : producerOutputs.slotDebugNames[slotIndex].c_str(), + fragmentInputs.slotSignatures[slotIndex], + fragmentInputs.slotDebugNames[slotIndex].empty() ? "" : fragmentInputs.slotDebugNames[slotIndex].c_str(), + programExternalIndex); + } + } + Bool ResolveDirectPositionTarget(spvtools::opt::IRContext* context, Uint32 variableId, PositionTargetInfo* outTarget) { auto* varInst = context->get_def_use_mgr()->GetDef(variableId); @@ -620,6 +1204,142 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } + void ProgramFactory::ReflectVertexInputs(const Vector>& shaders, + const Vector>& spirv, + VkProgramObject& entry) const { + entry.activeVertexInputLocationMask = 0; + entry.vertexInputTypes.fill(0); + + for (SizeT moduleIndex = 0; moduleIndex < shaders.size() && moduleIndex < spirv.size(); ++moduleIndex) { + if (!shaders[moduleIndex] || shaders[moduleIndex]->GetShaderStage() != ShaderStage::Vertex) { + continue; + } + + const auto& module = spirv[moduleIndex]; + if (module.empty()) { + continue; + } + + SpvReflectShaderModule reflectModule{}; + const SpvReflectResult createResult = + spvReflectCreateShaderModule(module.size() * sizeof(Uint), module.data(), &reflectModule); + MOBILEGL_ASSERT(createResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory::ReflectVertexInputs: failed to create reflection module (result=%d)", + static_cast(createResult)); + if (createResult != SPV_REFLECT_RESULT_SUCCESS) { + continue; + } + + uint32_t inputCount = 0; + SpvReflectResult reflectResult = spvReflectEnumerateInputVariables(&reflectModule, &inputCount, nullptr); + MOBILEGL_ASSERT(reflectResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory::ReflectVertexInputs: failed to enumerate input variables (result=%d)", + static_cast(reflectResult)); + Vector inputs(inputCount); + if (reflectResult == SPV_REFLECT_RESULT_SUCCESS && inputCount > 0) { + reflectResult = spvReflectEnumerateInputVariables(&reflectModule, &inputCount, inputs.data()); + MOBILEGL_ASSERT(reflectResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory::ReflectVertexInputs: failed to fetch input variables (result=%d)", + static_cast(reflectResult)); + } + + if (reflectResult == SPV_REFLECT_RESULT_SUCCESS) { + for (auto* input : inputs) { + if (input == nullptr || (input->decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) != 0) { + continue; + } + + const GLenum locationType = GetReflectInterfaceLocationType(*input); + MOBILEGL_ASSERT(locationType != GL_FALSE, + "ProgramFactory::ReflectVertexInputs: unsupported vertex input type at location=%u name='%s'", + input->location, + input->name ? input->name : ""); + const Uint32 locationSpan = GetReflectInterfaceLocationSpan(*input); + for (Uint32 locationOffset = 0; locationOffset < locationSpan; ++locationOffset) { + const Uint32 expandedLocation = input->location + locationOffset; + if (expandedLocation >= VkProgramObject::kMaxVertexInputLocations) { + break; + } + + entry.activeVertexInputLocationMask |= (1u << expandedLocation); + entry.vertexInputTypes[expandedLocation] = locationType; + } + } + } + + spvReflectDestroyShaderModule(&reflectModule); + break; + } + } + + void ProgramFactory::ReflectFragmentOutputs(const Vector>& shaders, + const Vector>& spirv, + VkProgramObject& entry) const { + entry.activeFragmentOutputLocationMask = 0; + entry.fragmentOutputTypes.fill(0); + + for (SizeT moduleIndex = 0; moduleIndex < shaders.size() && moduleIndex < spirv.size(); ++moduleIndex) { + if (!shaders[moduleIndex] || shaders[moduleIndex]->GetShaderStage() != ShaderStage::Fragment) { + continue; + } + + const auto& module = spirv[moduleIndex]; + if (module.empty()) { + continue; + } + + SpvReflectShaderModule reflectModule{}; + const SpvReflectResult createResult = + spvReflectCreateShaderModule(module.size() * sizeof(Uint), module.data(), &reflectModule); + MOBILEGL_ASSERT(createResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory::ReflectFragmentOutputs: failed to create reflection module (result=%d)", + static_cast(createResult)); + if (createResult != SPV_REFLECT_RESULT_SUCCESS) { + continue; + } + + uint32_t outputCount = 0; + SpvReflectResult reflectResult = spvReflectEnumerateOutputVariables(&reflectModule, &outputCount, nullptr); + MOBILEGL_ASSERT(reflectResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory::ReflectFragmentOutputs: failed to enumerate output variables (result=%d)", + static_cast(reflectResult)); + Vector outputs(outputCount); + if (reflectResult == SPV_REFLECT_RESULT_SUCCESS && outputCount > 0) { + reflectResult = spvReflectEnumerateOutputVariables(&reflectModule, &outputCount, outputs.data()); + MOBILEGL_ASSERT(reflectResult == SPV_REFLECT_RESULT_SUCCESS, + "ProgramFactory::ReflectFragmentOutputs: failed to fetch output variables (result=%d)", + static_cast(reflectResult)); + } + + if (reflectResult == SPV_REFLECT_RESULT_SUCCESS) { + for (auto* output : outputs) { + if (output == nullptr || (output->decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) != 0) { + continue; + } + + const GLenum locationType = GetReflectInterfaceLocationType(*output); + MOBILEGL_ASSERT(locationType != GL_FALSE, + "ProgramFactory::ReflectFragmentOutputs: unsupported fragment output type at location=%u name='%s'", + output->location, + output->name ? output->name : ""); + const Uint32 locationSpan = GetReflectInterfaceLocationSpan(*output); + for (Uint32 locationOffset = 0; locationOffset < locationSpan; ++locationOffset) { + const Uint32 expandedLocation = output->location + locationOffset; + if (expandedLocation >= VkProgramObject::kMaxVertexInputLocations) { + break; + } + + entry.activeFragmentOutputLocationMask |= (1u << expandedLocation); + entry.fragmentOutputTypes[expandedLocation] = locationType; + } + } + } + + spvReflectDestroyShaderModule(&reflectModule); + break; + } + } + void ProgramFactory::ReflectLayout(const MG_State::GLState::ProgramObject& program, const Vector>& spirv, VkProgramObject& entry) const { // Initialize layout vectors @@ -829,6 +1549,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto& moduleSpv = moduleSpirvs[i]; if (moduleSpv.empty()) continue; + ValidateTransformedSpirv(moduleSpv, shaders[i]->GetShaderStage(), program.GetExternalIndex()); + VkShaderModuleCreateInfo smci{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; smci.codeSize = moduleSpv.size() * sizeof(Uint); smci.pCode = moduleSpv.data(); @@ -847,6 +1569,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { } // Reflect and create layout as part of the program object + ValidateRasterizationStageInterface(shaders, moduleSpirvs, entry, program.GetExternalIndex()); + ReflectVertexInputs(shaders, moduleSpirvs, entry); + ReflectFragmentOutputs(shaders, moduleSpirvs, entry); ReflectLayout(program, moduleSpirvs, entry); return entry; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h index 4ab42602..f7eaa715 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h @@ -36,6 +36,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { using HashType = Uint64; struct VkProgramObject { + static constexpr Uint32 kMaxVertexInputLocations = 32; + HashType hash = 0; Vector stages; Vector modules; @@ -50,6 +52,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector samplerUniformLocationByBinding; Vector samplerTextureTargetByBinding; Int globalUboBinding = -1; + Uint32 activeVertexInputLocationMask = 0; + Array vertexInputTypes{}; + Uint32 activeFragmentOutputLocationMask = 0; + Array fragmentOutputTypes{}; + ShaderStage rasterizationProducerStage = ShaderStage::Unknown; + Uint32 producerOutputComponentCount = 0; + Uint32 fragmentInputComponentCount = 0; static inline VkDevice s_device = VK_NULL_HANDLE; @@ -69,10 +78,22 @@ namespace MobileGL::MG_Backend::DirectVulkan { samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding); samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding); globalUboBinding = other.globalUboBinding; + activeVertexInputLocationMask = other.activeVertexInputLocationMask; + vertexInputTypes = other.vertexInputTypes; + activeFragmentOutputLocationMask = other.activeFragmentOutputLocationMask; + fragmentOutputTypes = other.fragmentOutputTypes; + rasterizationProducerStage = other.rasterizationProducerStage; + producerOutputComponentCount = other.producerOutputComponentCount; + fragmentInputComponentCount = other.fragmentInputComponentCount; other.hash = 0; other.descriptorSetLayout = VK_NULL_HANDLE; other.pipelineLayout = VK_NULL_HANDLE; other.globalUboBinding = -1; + other.activeVertexInputLocationMask = 0; + other.activeFragmentOutputLocationMask = 0; + other.rasterizationProducerStage = ShaderStage::Unknown; + other.producerOutputComponentCount = 0; + other.fragmentInputComponentCount = 0; } VkProgramObject& operator=(VkProgramObject&& other) noexcept { if (this == &other) { @@ -91,10 +112,22 @@ namespace MobileGL::MG_Backend::DirectVulkan { samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding); samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding); globalUboBinding = other.globalUboBinding; + activeVertexInputLocationMask = other.activeVertexInputLocationMask; + vertexInputTypes = other.vertexInputTypes; + activeFragmentOutputLocationMask = other.activeFragmentOutputLocationMask; + fragmentOutputTypes = other.fragmentOutputTypes; + rasterizationProducerStage = other.rasterizationProducerStage; + producerOutputComponentCount = other.producerOutputComponentCount; + fragmentInputComponentCount = other.fragmentInputComponentCount; other.hash = 0; other.descriptorSetLayout = VK_NULL_HANDLE; other.pipelineLayout = VK_NULL_HANDLE; other.globalUboBinding = -1; + other.activeVertexInputLocationMask = 0; + other.activeFragmentOutputLocationMask = 0; + other.rasterizationProducerStage = ShaderStage::Unknown; + other.producerOutputComponentCount = 0; + other.fragmentInputComponentCount = 0; return *this; } @@ -139,6 +172,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { private: static TextureTarget UniformTypeToTextureTarget(GLenum glType); + void ReflectVertexInputs(const Vector>& shaders, + const Vector>& spirv, + VkProgramObject& entry) const; + void ReflectFragmentOutputs(const Vector>& shaders, + const Vector>& spirv, + VkProgramObject& entry) const; void ReflectLayout(const MG_State::GLState::ProgramObject& program, const Vector>& spirv, VkProgramObject& entry) const; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index b53f0bba..420804cf 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -170,34 +170,30 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool isDefaultFbo = (&fbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get()); // Color attachment auto& drawbufs = fbo.GetDrawBuffers(); - Int validDrawBufCount = 0; - for (Int i = 0; i < drawbufs.size(); ++i) { - auto drawbuf = drawbufs[i]; - if (drawbuf != FramebufferAttachmentType::None) - validDrawBufCount = std::max(validDrawBufCount, i + 1); - } + const Uint32 colorAttachmentSlotCount = static_cast(drawbufs.size()); Int width = 0; Int height = 0; Vector attachmentDescriptions; - attachmentDescriptions.reserve(validDrawBufCount + 1); - Vector colorAttachmentRefs(validDrawBufCount); + attachmentDescriptions.reserve(colorAttachmentSlotCount + 1); + // Keep the full GL draw buffer slot span so fragment outputs targeting GL_NONE map to VK_ATTACHMENT_UNUSED. + Vector colorAttachmentRefs(colorAttachmentSlotCount); for (auto& attachmentRef : colorAttachmentRefs) { attachmentRef.attachment = VK_ATTACHMENT_UNUSED; attachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; } Vector pendingClearAttachments; - pendingClearAttachments.reserve(validDrawBufCount + 1); + pendingClearAttachments.reserve(colorAttachmentSlotCount + 1); Vector trackedAttachmentLayouts; - trackedAttachmentLayouts.reserve(validDrawBufCount + 1); + trackedAttachmentLayouts.reserve(colorAttachmentSlotCount + 1); auto& textureResources = RenderPassEntry::s_textureResourcesScratch; textureResources.clear(); - textureResources.reserve(validDrawBufCount + 1); + textureResources.reserve(colorAttachmentSlotCount + 1); Vector attachmentViews; - attachmentViews.reserve(validDrawBufCount + 1); + attachmentViews.reserve(colorAttachmentSlotCount + 1); // This should automatically work on default & offscreen FBO // assuming default FBO has the right param - for (Int i = 0; i < validDrawBufCount; ++i) { + for (Uint32 i = 0; i < colorAttachmentSlotCount; ++i) { auto drawbuf = drawbufs[i]; if (drawbuf == FramebufferAttachmentType::None || fbo.GetAttachment(drawbuf).IsRenderbuffer()) @@ -385,6 +381,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { attachmentDescriptions.emplace_back(depthAttachmentDescription); depthAttachmentRef.attachment = depthAttachmentIndex; } + const Bool hasDepthStencilAttachment = depthAttachmentRef.attachment != VK_ATTACHMENT_UNUSED; // Subpass VkSubpassDescription subpassDesc; @@ -437,6 +434,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Move(trackedAttachmentLayouts), static_cast(attachmentViews.size()), static_cast(colorAttachmentRefs.size()), + hasDepthStencilAttachment, extent, 1 }; MGLOG_D("VkRenderPassManager::GetOrCreateRenderPass: hash=0x%llx compatibilityHash=0x%llx attachmentCount=%u colorAttachmentCount=%u extent=%dx%d", diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h index 1a100b1b..190bf60a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h @@ -48,6 +48,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { Vector trackedAttachmentLayouts; Uint32 attachmentCount = 0; Uint32 colorAttachmentCount = 0; + Bool hasDepthStencilAttachment = false; IntVec2 extent = {0, 0}; Uint32 subpass = 0; @@ -62,6 +63,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { std::swap(trackedAttachmentLayouts, that.trackedAttachmentLayouts); std::swap(attachmentCount, that.attachmentCount); std::swap(colorAttachmentCount, that.colorAttachmentCount); + std::swap(hasDepthStencilAttachment, that.hasDepthStencilAttachment); std::swap(extent, that.extent); std::swap(subpass, that.subpass); } @@ -74,6 +76,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Vector& trackedAttachmentLayouts, Uint32 attachmentCount, Uint32 colorAttachmentCount, + Bool hasDepthStencilAttachment, IntVec2 extent, int subpass): hash(hash), renderPass(renderpass), @@ -83,6 +86,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { trackedAttachmentLayouts(Move(trackedAttachmentLayouts)), attachmentCount(attachmentCount), colorAttachmentCount(colorAttachmentCount), + hasDepthStencilAttachment(hasDepthStencilAttachment), extent(extent), subpass(subpass) {} diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index e8a8c1f2..9e0fce05 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -18,6 +18,7 @@ #include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h" #include "MG_Util/Converters/GLToMG/TextureEnumConverter.h" #include "MG_Util/Converters/MGToVk/RenderStateEnumConverter.h" +#include "MG_Util/Metrics/TextureMetrics.h" #include namespace MobileGL::MG_Backend::DirectVulkan { @@ -57,12 +58,411 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } + static VkColorComponentFlags GetSupportedColorWriteMaskForComponentCount(SizeT componentCount) { + switch (componentCount) { + case 1: + return VK_COLOR_COMPONENT_R_BIT; + case 2: + return VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT; + case 3: + return VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT; + case 4: + return VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + default: + MOBILEGL_ASSERT(false, + "GetSupportedColorWriteMaskForComponentCount: unsupported componentCount=%zu", + componentCount); + return 0; + } + } + + enum class NumericDomain { + Unknown, + FloatLike, + Sint, + Uint, + }; + + static NumericDomain GetNumericDomainForShaderValueType(GLenum glType) { + switch (glType) { + case GL_FLOAT: + case GL_FLOAT_VEC2: + case GL_FLOAT_VEC3: + case GL_FLOAT_VEC4: + return NumericDomain::FloatLike; + case GL_INT: + case GL_INT_VEC2: + case GL_INT_VEC3: + case GL_INT_VEC4: + return NumericDomain::Sint; + case GL_UNSIGNED_INT: + case GL_UNSIGNED_INT_VEC2: + case GL_UNSIGNED_INT_VEC3: + case GL_UNSIGNED_INT_VEC4: + return NumericDomain::Uint; + default: + return NumericDomain::Unknown; + } + } + + static SizeT GetComponentCountForShaderValueType(GLenum glType) { + switch (glType) { + case GL_FLOAT: + case GL_INT: + case GL_UNSIGNED_INT: + return 1; + case GL_FLOAT_VEC2: + case GL_INT_VEC2: + case GL_UNSIGNED_INT_VEC2: + return 2; + case GL_FLOAT_VEC3: + case GL_INT_VEC3: + case GL_UNSIGNED_INT_VEC3: + return 3; + case GL_FLOAT_VEC4: + case GL_INT_VEC4: + case GL_UNSIGNED_INT_VEC4: + return 4; + default: + return 0; + } + } + + static NumericDomain GetNumericDomainForVertexFormat(VkFormat format) { + switch (format) { + case VK_FORMAT_R32_SFLOAT: + case VK_FORMAT_R32G32_SFLOAT: + case VK_FORMAT_R32G32B32_SFLOAT: + case VK_FORMAT_R32G32B32A32_SFLOAT: + case VK_FORMAT_R16_SNORM: + case VK_FORMAT_R16G16_SNORM: + case VK_FORMAT_R16G16B16_SNORM: + case VK_FORMAT_R16G16B16A16_SNORM: + case VK_FORMAT_R16_UNORM: + case VK_FORMAT_R16G16_UNORM: + case VK_FORMAT_R16G16B16_UNORM: + case VK_FORMAT_R16G16B16A16_UNORM: + case VK_FORMAT_R16_SSCALED: + case VK_FORMAT_R16G16_SSCALED: + case VK_FORMAT_R16G16B16_SSCALED: + case VK_FORMAT_R16G16B16A16_SSCALED: + case VK_FORMAT_R16_USCALED: + case VK_FORMAT_R16G16_USCALED: + case VK_FORMAT_R16G16B16_USCALED: + case VK_FORMAT_R16G16B16A16_USCALED: + case VK_FORMAT_R8_SNORM: + case VK_FORMAT_R8G8_SNORM: + case VK_FORMAT_R8G8B8_SNORM: + case VK_FORMAT_R8G8B8A8_SNORM: + case VK_FORMAT_R8_UNORM: + case VK_FORMAT_R8G8_UNORM: + case VK_FORMAT_R8G8B8_UNORM: + case VK_FORMAT_R8G8B8A8_UNORM: + case VK_FORMAT_R8_SSCALED: + case VK_FORMAT_R8G8_SSCALED: + case VK_FORMAT_R8G8B8_SSCALED: + case VK_FORMAT_R8G8B8A8_SSCALED: + case VK_FORMAT_R8_USCALED: + case VK_FORMAT_R8G8_USCALED: + case VK_FORMAT_R8G8B8_USCALED: + case VK_FORMAT_R8G8B8A8_USCALED: + return NumericDomain::FloatLike; + case VK_FORMAT_R32_SINT: + case VK_FORMAT_R32G32_SINT: + case VK_FORMAT_R32G32B32_SINT: + case VK_FORMAT_R32G32B32A32_SINT: + case VK_FORMAT_R16_SINT: + case VK_FORMAT_R16G16_SINT: + case VK_FORMAT_R16G16B16_SINT: + case VK_FORMAT_R16G16B16A16_SINT: + case VK_FORMAT_R8_SINT: + case VK_FORMAT_R8G8_SINT: + case VK_FORMAT_R8G8B8_SINT: + case VK_FORMAT_R8G8B8A8_SINT: + return NumericDomain::Sint; + case VK_FORMAT_R32_UINT: + case VK_FORMAT_R32G32_UINT: + case VK_FORMAT_R32G32B32_UINT: + case VK_FORMAT_R32G32B32A32_UINT: + case VK_FORMAT_R16_UINT: + case VK_FORMAT_R16G16_UINT: + case VK_FORMAT_R16G16B16_UINT: + case VK_FORMAT_R16G16B16A16_UINT: + case VK_FORMAT_R8_UINT: + case VK_FORMAT_R8G8_UINT: + case VK_FORMAT_R8G8B8_UINT: + case VK_FORMAT_R8G8B8A8_UINT: + return NumericDomain::Uint; + default: + return NumericDomain::Unknown; + } + } + + static Bool TryCoerceVertexFormatNumericDomain(VkFormat sourceFormat, + NumericDomain targetDomain, + VkFormat& outFormat) { + const NumericDomain sourceDomain = GetNumericDomainForVertexFormat(sourceFormat); + if (sourceDomain == targetDomain || targetDomain == NumericDomain::Unknown) { + outFormat = sourceFormat; + return true; + } + if (sourceDomain == NumericDomain::FloatLike || targetDomain == NumericDomain::FloatLike) { + return false; + } + + switch (sourceFormat) { + case VK_FORMAT_R32_SINT: + outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R32_UINT : sourceFormat; + return true; + case VK_FORMAT_R32G32_SINT: + outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R32G32_UINT : sourceFormat; + return true; + case VK_FORMAT_R32G32B32_SINT: + outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R32G32B32_UINT : sourceFormat; + return true; + case VK_FORMAT_R32G32B32A32_SINT: + outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R32G32B32A32_UINT : sourceFormat; + return true; + case VK_FORMAT_R32_UINT: + outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R32_SINT : sourceFormat; + return true; + case VK_FORMAT_R32G32_UINT: + outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R32G32_SINT : sourceFormat; + return true; + case VK_FORMAT_R32G32B32_UINT: + outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R32G32B32_SINT : sourceFormat; + return true; + case VK_FORMAT_R32G32B32A32_UINT: + outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R32G32B32A32_SINT : sourceFormat; + return true; + case VK_FORMAT_R16_SINT: + outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R16_UINT : sourceFormat; + return true; + case VK_FORMAT_R16G16_SINT: + outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R16G16_UINT : sourceFormat; + return true; + case VK_FORMAT_R16G16B16_SINT: + outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R16G16B16_UINT : sourceFormat; + return true; + case VK_FORMAT_R16G16B16A16_SINT: + outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R16G16B16A16_UINT : sourceFormat; + return true; + case VK_FORMAT_R16_UINT: + outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R16_SINT : sourceFormat; + return true; + case VK_FORMAT_R16G16_UINT: + outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R16G16_SINT : sourceFormat; + return true; + case VK_FORMAT_R16G16B16_UINT: + outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R16G16B16_SINT : sourceFormat; + return true; + case VK_FORMAT_R16G16B16A16_UINT: + outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R16G16B16A16_SINT : sourceFormat; + return true; + case VK_FORMAT_R8_SINT: + outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R8_UINT : sourceFormat; + return true; + case VK_FORMAT_R8G8_SINT: + outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R8G8_UINT : sourceFormat; + return true; + case VK_FORMAT_R8G8B8_SINT: + outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R8G8B8_UINT : sourceFormat; + return true; + case VK_FORMAT_R8G8B8A8_SINT: + outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R8G8B8A8_UINT : sourceFormat; + return true; + case VK_FORMAT_R8_UINT: + outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R8_SINT : sourceFormat; + return true; + case VK_FORMAT_R8G8_UINT: + outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R8G8_SINT : sourceFormat; + return true; + case VK_FORMAT_R8G8B8_UINT: + outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R8G8B8_SINT : sourceFormat; + return true; + case VK_FORMAT_R8G8B8A8_UINT: + outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R8G8B8A8_SINT : sourceFormat; + return true; + default: + return false; + } + } + + static NumericDomain GetNumericDomainForTextureInternalFormat(TextureInternalFormat format) { + switch (format) { + case TextureInternalFormat::R8I: + case TextureInternalFormat::R16I: + case TextureInternalFormat::R32I: + case TextureInternalFormat::RG8I: + case TextureInternalFormat::RG16I: + case TextureInternalFormat::RG32I: + case TextureInternalFormat::RGB8I: + case TextureInternalFormat::RGB16I: + case TextureInternalFormat::RGB32I: + case TextureInternalFormat::RGBA8I: + case TextureInternalFormat::RGBA16I: + case TextureInternalFormat::RGBA32I: + return NumericDomain::Sint; + case TextureInternalFormat::R8UI: + case TextureInternalFormat::R16UI: + case TextureInternalFormat::R32UI: + case TextureInternalFormat::RG8UI: + case TextureInternalFormat::RG16UI: + case TextureInternalFormat::RG32UI: + case TextureInternalFormat::RGB8UI: + case TextureInternalFormat::RGB16UI: + case TextureInternalFormat::RGB32UI: + case TextureInternalFormat::RGBA8UI: + case TextureInternalFormat::RGBA16UI: + case TextureInternalFormat::RGBA32UI: + case TextureInternalFormat::RGB10A2UI: + return NumericDomain::Uint; + case TextureInternalFormat::DepthComponent: + case TextureInternalFormat::DepthComponent16: + case TextureInternalFormat::DepthComponent24: + case TextureInternalFormat::DepthComponent32: + case TextureInternalFormat::DepthComponent32F: + case TextureInternalFormat::Depth24Stencil8: + case TextureInternalFormat::Depth32FStencil8: + case TextureInternalFormat::DepthStencil: + return NumericDomain::Unknown; + default: + return NumericDomain::FloatLike; + } + } + static Bool HasTransientVertexIndexBufferThisFrame( const Vector& buffers, const MG_State::GLState::BufferObject* buffer) { return std::find(buffers.begin(), buffers.end(), buffer) != buffers.end(); } + static Uint32 BuildVertexInputAttributeMask(const Vector& attributes) { + Uint32 attributeMask = 0; + for (const auto& attribute : attributes) { + if (attribute.location < 32) { + attributeMask |= (1u << attribute.location); + } + } + return attributeMask; + } + + static Bool TryGetCurrentVertexAttributeFormat(GLenum glType, VkFormat& outFormat) { + switch (glType) { + case GL_FLOAT: + outFormat = VK_FORMAT_R32_SFLOAT; + return true; + case GL_FLOAT_VEC2: + outFormat = VK_FORMAT_R32G32_SFLOAT; + return true; + case GL_FLOAT_VEC3: + outFormat = VK_FORMAT_R32G32B32_SFLOAT; + return true; + case GL_FLOAT_VEC4: + outFormat = VK_FORMAT_R32G32B32A32_SFLOAT; + return true; + case GL_INT: + outFormat = VK_FORMAT_R32_SINT; + return true; + case GL_INT_VEC2: + outFormat = VK_FORMAT_R32G32_SINT; + return true; + case GL_INT_VEC3: + outFormat = VK_FORMAT_R32G32B32_SINT; + return true; + case GL_INT_VEC4: + outFormat = VK_FORMAT_R32G32B32A32_SINT; + return true; + case GL_UNSIGNED_INT: + outFormat = VK_FORMAT_R32_UINT; + return true; + case GL_UNSIGNED_INT_VEC2: + outFormat = VK_FORMAT_R32G32_UINT; + return true; + case GL_UNSIGNED_INT_VEC3: + outFormat = VK_FORMAT_R32G32B32_UINT; + return true; + case GL_UNSIGNED_INT_VEC4: + outFormat = VK_FORMAT_R32G32B32A32_UINT; + return true; + default: + return false; + } + } + + static Bool TryGetCurrentVertexAttributeUploadPayload( + const MG_State::GLState::CurrentVertexAttributeValue& currentValue, + GLenum glType, + VkFormat& outFormat, + const void*& outData, + VkDeviceSize& outSize) { + switch (glType) { + case GL_FLOAT: + outFormat = VK_FORMAT_R32_SFLOAT; + outData = currentValue.floatValue.data(); + outSize = sizeof(Float); + return true; + case GL_FLOAT_VEC2: + outFormat = VK_FORMAT_R32G32_SFLOAT; + outData = currentValue.floatValue.data(); + outSize = sizeof(Float) * 2; + return true; + case GL_FLOAT_VEC3: + outFormat = VK_FORMAT_R32G32B32_SFLOAT; + outData = currentValue.floatValue.data(); + outSize = sizeof(Float) * 3; + return true; + case GL_FLOAT_VEC4: + outFormat = VK_FORMAT_R32G32B32A32_SFLOAT; + outData = currentValue.floatValue.data(); + outSize = sizeof(Float) * 4; + return true; + case GL_INT: + outFormat = VK_FORMAT_R32_SINT; + outData = currentValue.intValue.data(); + outSize = sizeof(Int32); + return true; + case GL_INT_VEC2: + outFormat = VK_FORMAT_R32G32_SINT; + outData = currentValue.intValue.data(); + outSize = sizeof(Int32) * 2; + return true; + case GL_INT_VEC3: + outFormat = VK_FORMAT_R32G32B32_SINT; + outData = currentValue.intValue.data(); + outSize = sizeof(Int32) * 3; + return true; + case GL_INT_VEC4: + outFormat = VK_FORMAT_R32G32B32A32_SINT; + outData = currentValue.intValue.data(); + outSize = sizeof(Int32) * 4; + return true; + case GL_UNSIGNED_INT: + outFormat = VK_FORMAT_R32_UINT; + outData = currentValue.uintValue.data(); + outSize = sizeof(Uint32); + return true; + case GL_UNSIGNED_INT_VEC2: + outFormat = VK_FORMAT_R32G32_UINT; + outData = currentValue.uintValue.data(); + outSize = sizeof(Uint32) * 2; + return true; + case GL_UNSIGNED_INT_VEC3: + outFormat = VK_FORMAT_R32G32B32_UINT; + outData = currentValue.uintValue.data(); + outSize = sizeof(Uint32) * 3; + return true; + case GL_UNSIGNED_INT_VEC4: + outFormat = VK_FORMAT_R32G32B32A32_UINT; + outData = currentValue.uintValue.data(); + outSize = sizeof(Uint32) * 4; + return true; + default: + return false; + } + } + static const char* VkImageLayoutToString(VkImageLayout layout) { switch (layout) { case VK_IMAGE_LAYOUT_UNDEFINED: @@ -890,8 +1290,14 @@ void main() { Bool VulkanRenderer::UploadAndBindVertexBuffers( VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao) { auto& vertexInputState = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao); + const auto& program = *MG_State::pGLContext->GetCurrentProgram(); + const auto transformFlags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform()); + const auto& programObj = m_programFactory->GetOrCreateProgram(program, transformFlags); + const Uint32 activeAttribMask = programObj.activeVertexInputLocationMask; + const Uint32 vertexInputAttribMask = BuildVertexInputAttributeMask(vertexInputState.attributes); + const Uint32 missingAttribMask = activeAttribMask & ~vertexInputAttribMask; - const auto bindingCount = vertexInputState.bindings.size(); + const auto bindingCount = vertexInputState.bindings.size() + static_cast(std::popcount(missingAttribMask)); Vector vkBuffers(bindingCount, VK_NULL_HANDLE); Vector vkOffsets(bindingCount, 0); @@ -912,6 +1318,9 @@ void main() { }; for (SizeT binding = 0; binding < bindingCount; ++binding) { + if (binding >= vertexInputState.bindings.size()) { + break; + } const SizeT bufferKey = vertexInputState.bindingBufferKeys[binding]; const MG_State::GLState::BufferObject* sourceBuffer = findBufferByKey(bufferKey); MOBILEGL_ASSERT(sourceBuffer != nullptr, "UploadAndBindVertexStreams failed to resolve source buffer"); @@ -946,6 +1355,37 @@ void main() { vkOffsets[binding] = slice.offset; } + SizeT syntheticBinding = vertexInputState.bindings.size(); + for (Uint32 location = 0; location < 32; ++location) { + if ((missingAttribMask & (1u << location)) == 0) { + continue; + } + + const auto glType = programObj.vertexInputTypes[location]; + const auto& currentValue = MG_State::pGLContext->GetCurrentVertexAttribute(location); + VkFormat format = VK_FORMAT_UNDEFINED; + const void* sourceData = nullptr; + VkDeviceSize sourceSize = 0; + const Bool supported = TryGetCurrentVertexAttributeUploadPayload(currentValue, glType, format, + sourceData, sourceSize); + MOBILEGL_ASSERT(supported, + "DirectVulkan does not support current generic vertex attribute type yet: program=%u location=%u type=0x%x", + program.GetExternalIndex(), location, glType); + + BufferSlice slice{}; + if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(), + sourceData, sourceSize, 16, slice)) { + MOBILEGL_ASSERT(false, + "UploadAndBindVertexStreams skipped: failed to upload current attribute binding for location %u", + location); + return false; + } + + vkBuffers[syntheticBinding] = slice.buffer; + vkOffsets[syntheticBinding] = slice.offset; + ++syntheticBinding; + } + vkCmdBindVertexBuffers(commandBuffer, 0, static_cast(bindingCount), vkBuffers.data(), vkOffsets.data()); return true; } @@ -1558,8 +1998,111 @@ void main() { return VK_NULL_HANDLE; } + const auto& limits = m_physicalDevice.properties.limits; + if (programObj.fragmentInputComponentCount != 0) { + MOBILEGL_ASSERT( + programObj.fragmentInputComponentCount <= limits.maxFragmentInputComponents, + "GetOrCreatePipeline: fragmentInputComponents=%u exceeds device limit=%u program=%u producerStage=%d", + programObj.fragmentInputComponentCount, + limits.maxFragmentInputComponents, + program.GetExternalIndex(), + static_cast(programObj.rasterizationProducerStage)); + } + if (programObj.producerOutputComponentCount != 0) { + Uint32 producerOutputLimit = 0; + switch (programObj.rasterizationProducerStage) { + case ShaderStage::Vertex: + producerOutputLimit = limits.maxVertexOutputComponents; + break; + case ShaderStage::Geometry: + producerOutputLimit = limits.maxGeometryOutputComponents; + break; + case ShaderStage::TessEval: + producerOutputLimit = limits.maxTessellationEvaluationOutputComponents; + break; + default: + break; + } + if (producerOutputLimit != 0) { + MOBILEGL_ASSERT( + programObj.producerOutputComponentCount <= producerOutputLimit, + "GetOrCreatePipeline: producerOutputComponents=%u exceeds stage limit=%u program=%u producerStage=%d", + programObj.producerOutputComponentCount, + producerOutputLimit, + program.GetExternalIndex(), + static_cast(programObj.rasterizationProducerStage)); + } + } + auto vertexInputHash = m_vertexInputStateFactory->ComputeHash(vao); auto& vis = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao); + const Uint32 vertexInputAttribMask = BuildVertexInputAttributeMask(vis.attributes); + const Uint32 activeAttribMask = programObj.activeVertexInputLocationMask; + const Uint32 missingAttribMask = activeAttribMask & ~vertexInputAttribMask; + Vector patchedAttributes = vis.attributes; + Bool hasPatchedVertexAttributes = false; + for (auto& attribute : patchedAttributes) { + if (attribute.location >= 32 || (activeAttribMask & (1u << attribute.location)) == 0) { + continue; + } + + const GLenum shaderInputType = programObj.vertexInputTypes[attribute.location]; + const NumericDomain shaderInputDomain = GetNumericDomainForShaderValueType(shaderInputType); + const NumericDomain vertexInputDomain = GetNumericDomainForVertexFormat(attribute.format); + if (shaderInputDomain == NumericDomain::Unknown || vertexInputDomain == NumericDomain::Unknown || + shaderInputDomain == vertexInputDomain) { + continue; + } + + VkFormat patchedFormat = VK_FORMAT_UNDEFINED; + const Bool canPatch = TryCoerceVertexFormatNumericDomain(attribute.format, shaderInputDomain, patchedFormat); + MOBILEGL_ASSERT( + canPatch, + "GetOrCreatePipeline: vertex input location=%u format=%d mismatches shader input type=%u program=%u", + attribute.location, + static_cast(attribute.format), + static_cast(shaderInputType), + program.GetExternalIndex()); + + MGLOG_W("GetOrCreatePipeline: patching vertex input location=%u format=%d -> %d to match shader input type=%u for program=%u", + attribute.location, + static_cast(attribute.format), + static_cast(patchedFormat), + static_cast(shaderInputType), + program.GetExternalIndex()); + attribute.format = patchedFormat; + hasPatchedVertexAttributes = true; + } + VertexInputStateBuilder syntheticVertexInputBuilder; + const VkPipelineVertexInputStateCreateInfo* pipelineVertexInputState = &vis.state; + if (missingAttribMask != 0 || hasPatchedVertexAttributes) { + for (const auto& binding : vis.bindings) { + syntheticVertexInputBuilder.AddBinding(binding.binding, binding.stride, binding.inputRate); + } + for (const auto& attribute : patchedAttributes) { + syntheticVertexInputBuilder.AddAttribute(attribute.location, attribute.binding, attribute.format, + attribute.offset); + } + + Uint32 syntheticBinding = static_cast(vis.bindings.size()); + for (Uint32 location = 0; location < 32; ++location) { + if ((missingAttribMask & (1u << location)) == 0) { + continue; + } + + VkFormat format = VK_FORMAT_UNDEFINED; + const Bool supported = TryGetCurrentVertexAttributeFormat(programObj.vertexInputTypes[location], format); + MOBILEGL_ASSERT(supported, + "DirectVulkan does not support current generic vertex attribute type yet: program=%u location=%u type=0x%x activeAttribMask=0x%x vertexInputAttribMask=0x%x", + program.GetExternalIndex(), location, programObj.vertexInputTypes[location], + activeAttribMask, vertexInputAttribMask); + + syntheticVertexInputBuilder.AddBinding(syntheticBinding, 0, VK_VERTEX_INPUT_RATE_VERTEX); + syntheticVertexInputBuilder.AddAttribute(location, syntheticBinding, format, 0); + ++syntheticBinding; + } + pipelineVertexInputState = &syntheticVertexInputBuilder.Build(); + } auto cullFaceEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::CullFace); auto depthTestEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::DepthTest); auto mask = MG_State::pGLContext->GetColorMask(); @@ -1585,24 +2128,164 @@ void main() { .depthWriteEnable = depthTestEnabled && MG_State::pGLContext->GetDepthMask(), .depthCompareOp = MG_Util::ConvertDepthTestFuncToVkEnum(MG_State::pGLContext->GetDepthFunc()), .stages = &programObj.stages, - .vertexInputState = &vis.state + .vertexInputState = pipelineVertexInputState }; + const Bool hasDepthStencilAttachment = renderPassEntry.hasDepthStencilAttachment; + MOBILEGL_ASSERT( + hasDepthStencilAttachment || (!payload.depthTestEnable && !payload.depthWriteEnable), + "GetOrCreatePipeline: render pass has no depth attachment but depthTestEnable=%d depthWriteEnable=%d program=%u attachmentCount=%u colorAttachmentCount=%u", + payload.depthTestEnable ? 1 : 0, + payload.depthWriteEnable ? 1 : 0, + program.GetExternalIndex(), + renderPassEntry.attachmentCount, + renderPassEntry.colorAttachmentCount); + const Uint32 fragmentOutputMask = programObj.activeFragmentOutputLocationMask; + MOBILEGL_ASSERT( + (fragmentOutputMask >> payload.colorAttachmentCount) == 0, + "GetOrCreatePipeline: fragmentOutputMask=0x%x exceeds colorAttachmentCount=%u for program=%u", + fragmentOutputMask, + payload.colorAttachmentCount, + program.GetExternalIndex()); MOBILEGL_ASSERT(payload.colorAttachmentCount <= PipelineFactory::PipelineCreatePayload::kMaxColorAttachments, "GetOrCreatePipeline: colorAttachmentCount=%u exceeds payload capacity", payload.colorAttachmentCount); + const auto& drawFboBinding = + MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject(); + MOBILEGL_ASSERT(drawFboBinding != nullptr, "GetOrCreatePipeline: draw framebuffer is null"); + const Bool isDefaultDrawFbo = + drawFboBinding.get() == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get(); + const auto& drawBuffers = drawFboBinding->GetDrawBuffers(); for (Uint32 i = 0; i < payload.colorAttachmentCount; ++i) { BlendFactor srcRGB = BlendFactor::One; BlendFactor dstRGB = BlendFactor::Zero; BlendFactor srcAlpha = BlendFactor::One; BlendFactor dstAlpha = BlendFactor::Zero; MG_State::pGLContext->GetBlendFuncIndexed(i, srcRGB, dstRGB, srcAlpha, dstAlpha); + const Bool blendEnabled = MG_State::pGLContext->IsCapabilityEnabledIndexed(CapabilityInput::Blend, i); + VkColorComponentFlags attachmentColorWriteMask = colorWriteMask; + Bool effectiveBlendEnabled = blendEnabled; + if (!isDefaultDrawFbo && i < drawBuffers.size()) { + const auto drawBuffer = drawBuffers[i]; + if (drawBuffer == FramebufferAttachmentType::None) { + // GL ignores writes and per-target blend state for GL_NONE draw buffer slots. + attachmentColorWriteMask = 0; + effectiveBlendEnabled = false; + } + if (drawBuffer != FramebufferAttachmentType::None) { + const auto& attachment = drawFboBinding->GetAttachment(drawBuffer); + if (attachment.IsTexture()) { + auto* texture = attachment.GetTexture().get(); + MOBILEGL_ASSERT(texture != nullptr, + "GetOrCreatePipeline: color attachment %u texture is null", + i); + const auto* textureResource = m_textureManager->SyncTextureAndGetDescriptor(*texture); + MOBILEGL_ASSERT(textureResource != nullptr, + "GetOrCreatePipeline: failed to sync color attachment textureId=%d", + texture->GetExternalIndex()); + VkFormatProperties attachmentFormatProperties{}; + vkGetPhysicalDeviceFormatProperties( + m_physicalDevice.handle, + textureResource->format, + &attachmentFormatProperties); + MOBILEGL_ASSERT( + (attachmentFormatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0, + "GetOrCreatePipeline: color attachment %u format=%d textureId=%d lacks VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT (program=%u)", + i, + static_cast(textureResource->format), + texture->GetExternalIndex(), + program.GetExternalIndex()); + const SizeT componentCount = MG_Util::GetBaseInternalFormatComponentCount(texture->GetFormat()); + const NumericDomain attachmentNumericDomain = + GetNumericDomainForTextureInternalFormat(texture->GetFormat()); + for (Uint32 outputLocation = 0; + outputLocation < ProgramFactory::VkProgramObject::kMaxVertexInputLocations; + ++outputLocation) { + if ((programObj.activeFragmentOutputLocationMask & (1u << outputLocation)) == 0 || + outputLocation != i) { + continue; + } + + const GLenum fragmentOutputType = programObj.fragmentOutputTypes[outputLocation]; + const NumericDomain fragmentOutputDomain = + GetNumericDomainForShaderValueType(fragmentOutputType); + // GL allows fragment outputs with more components than the bound color attachment; + // excess components are discarded during conversion to the attachment format. + MOBILEGL_ASSERT( + attachmentNumericDomain == NumericDomain::Unknown || + fragmentOutputDomain == NumericDomain::Unknown || + attachmentNumericDomain == fragmentOutputDomain, + "GetOrCreatePipeline: fragment output location=%d type=%u mismatches color attachment %u internalFormat=%d textureId=%d program=%u", + static_cast(outputLocation), + static_cast(fragmentOutputType), + i, + static_cast(texture->GetFormat()), + texture->GetExternalIndex(), + program.GetExternalIndex()); + } + const VkColorComponentFlags supportedColorWriteMask = + GetSupportedColorWriteMaskForComponentCount(componentCount); + if ((attachmentColorWriteMask & ~supportedColorWriteMask) != 0) { + MGLOG_W( + "GetOrCreatePipeline: clamping colorWriteMask=0x%x to 0x%x on color attachment %u (componentCount=%zu textureId=%d internalFormat=%d program=%u blendEnabled=%d)", + static_cast(attachmentColorWriteMask), + static_cast(attachmentColorWriteMask & supportedColorWriteMask), + i, + componentCount, + texture->GetExternalIndex(), + static_cast(texture->GetFormat()), + program.GetExternalIndex(), + effectiveBlendEnabled ? 1 : 0); + attachmentColorWriteMask &= supportedColorWriteMask; + } + } + } + } + if (effectiveBlendEnabled) { + MOBILEGL_ASSERT(i < drawBuffers.size(), + "GetOrCreatePipeline: color attachment %u is out of draw buffer range %zu", + i, drawBuffers.size()); + + VkFormat colorAttachmentFormat = VK_FORMAT_UNDEFINED; + Int textureExternalIndex = -1; + if (isDefaultDrawFbo) { + colorAttachmentFormat = m_swapchainObject.GetSurfaceFormat().format; + } else { + const auto drawBuffer = drawBuffers[i]; + MOBILEGL_ASSERT(drawBuffer != FramebufferAttachmentType::None, + "GetOrCreatePipeline: blend is enabled on draw buffer %u but the attachment is None", + i); + const auto& attachment = drawFboBinding->GetAttachment(drawBuffer); + MOBILEGL_ASSERT(attachment.IsTexture(), + "GetOrCreatePipeline: blend validation currently expects texture color attachments only"); + auto* texture = attachment.GetTexture().get(); + MOBILEGL_ASSERT(texture != nullptr, + "GetOrCreatePipeline: color attachment %u texture is null", + i); + textureExternalIndex = texture->GetExternalIndex(); + const auto* textureResource = m_textureManager->SyncTextureAndGetDescriptor(*texture); + MOBILEGL_ASSERT(textureResource != nullptr, + "GetOrCreatePipeline: failed to sync color attachment textureId=%d", + textureExternalIndex); + colorAttachmentFormat = textureResource->format; + } + + VkFormatProperties formatProperties{}; + vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle, colorAttachmentFormat, &formatProperties); + MOBILEGL_ASSERT( + (formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT) != 0, + "GetOrCreatePipeline: blend is enabled on color attachment %u for format=%d textureId=%d, but the format lacks VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT (program=%u)", + i, + static_cast(colorAttachmentFormat), + textureExternalIndex, + program.GetExternalIndex()); + } payload.colorBlendAttachments[i] = MakeColorBlendAttachmentState( - MG_State::pGLContext->IsCapabilityEnabledIndexed(CapabilityInput::Blend, i), + effectiveBlendEnabled, MG_Util::ConvertBlendFactorToVkEnum(srcRGB), MG_Util::ConvertBlendFactorToVkEnum(dstRGB), MG_Util::ConvertBlendFactorToVkEnum(srcAlpha), MG_Util::ConvertBlendFactorToVkEnum(dstAlpha), - colorWriteMask); + attachmentColorWriteMask); } return m_pipelineFactory->GetOrCreatePipeline(payload); } diff --git a/MobileGL/MG_State/GLState/Core.cpp b/MobileGL/MG_State/GLState/Core.cpp index 234f9b66..71e15d53 100644 --- a/MobileGL/MG_State/GLState/Core.cpp +++ b/MobileGL/MG_State/GLState/Core.cpp @@ -140,6 +140,48 @@ namespace MobileGL::MG_State { return m_vertexArrayState.GetBoundVertexArray(); } + void GLContext::SetCurrentVertexAttributeFloat(Uint index, const Array& value) { + MOBILEGL_ASSERT(index < m_currentVertexAttributes.size(), + "SetCurrentVertexAttributeFloat: index %u is out of range", index); + + auto& current = m_currentVertexAttributes[index]; + current.floatValue = value; + for (SizeT component = 0; component < value.size(); ++component) { + current.intValue[component] = static_cast(value[component]); + current.uintValue[component] = static_cast(value[component]); + } + } + + void GLContext::SetCurrentVertexAttributeInt(Uint index, const Array& value) { + MOBILEGL_ASSERT(index < m_currentVertexAttributes.size(), + "SetCurrentVertexAttributeInt: index %u is out of range", index); + + auto& current = m_currentVertexAttributes[index]; + current.intValue = value; + for (SizeT component = 0; component < value.size(); ++component) { + current.floatValue[component] = static_cast(value[component]); + current.uintValue[component] = static_cast(value[component]); + } + } + + void GLContext::SetCurrentVertexAttributeUint(Uint index, const Array& value) { + MOBILEGL_ASSERT(index < m_currentVertexAttributes.size(), + "SetCurrentVertexAttributeUint: index %u is out of range", index); + + auto& current = m_currentVertexAttributes[index]; + current.uintValue = value; + for (SizeT component = 0; component < value.size(); ++component) { + current.floatValue[component] = static_cast(value[component]); + current.intValue[component] = static_cast(value[component]); + } + } + + const CurrentVertexAttributeValue& GLContext::GetCurrentVertexAttribute(Uint index) const { + MOBILEGL_ASSERT(index < m_currentVertexAttributes.size(), + "GetCurrentVertexAttribute: index %u is out of range", index); + return m_currentVertexAttributes[index]; + } + // Texture void GLContext::GenTextureNames(Uint number, Vector& textures) { m_textureState.GenerateNames(number, textures); diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index ebcb4a12..65cee4b6 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -25,6 +25,12 @@ namespace MobileGL { void Init(); namespace GLState { + struct CurrentVertexAttributeValue { + Array floatValue{0.f, 0.f, 0.f, 1.f}; + Array intValue{0, 0, 0, 1}; + Array uintValue{0u, 0u, 0u, 1u}; + }; + class GLContext { public: GLContext() = default; @@ -61,6 +67,10 @@ namespace MobileGL { Bool ValidateVertexArrayName(Uint index) const; Bool ValidateVertexArrayObject(Uint index) const; const SharedPtr& GetBoundVertexArray(); + void SetCurrentVertexAttributeFloat(Uint index, const Array& value); + void SetCurrentVertexAttributeInt(Uint index, const Array& value); + void SetCurrentVertexAttributeUint(Uint index, const Array& value); + const CurrentVertexAttributeValue& GetCurrentVertexAttribute(Uint index) const; // Texture void GenTextureNames(Uint number, Vector& textures); @@ -152,6 +162,7 @@ namespace MobileGL { ErrorState m_errorState; BufferState m_bufferState; VertexArrayState m_vertexArrayState; + Array m_currentVertexAttributes{}; TextureState m_textureState; ProgramState m_programState; RenderState m_renderState; diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index 0edf6125..03fe6e96 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -18,6 +18,46 @@ layout(location = 0) out vec4 FragColor; void main() {} )"; +namespace { + static int GetVertexInputLocationSpan(GLenum glType) { + switch (glType) { + case GL_FLOAT_MAT2: + case GL_FLOAT_MAT2x3: + case GL_FLOAT_MAT2x4: + return 2; + case GL_FLOAT_MAT3: + case GL_FLOAT_MAT3x2: + case GL_FLOAT_MAT3x4: + return 3; + case GL_FLOAT_MAT4: + case GL_FLOAT_MAT4x2: + case GL_FLOAT_MAT4x3: + return 4; + default: + return 1; + } + } + + static GLenum GetVertexInputLocationType(GLenum glType) { + switch (glType) { + case GL_FLOAT_MAT2: + case GL_FLOAT_MAT3x2: + case GL_FLOAT_MAT4x2: + return GL_FLOAT_VEC2; + case GL_FLOAT_MAT3: + case GL_FLOAT_MAT2x3: + case GL_FLOAT_MAT4x3: + return GL_FLOAT_VEC3; + case GL_FLOAT_MAT4: + case GL_FLOAT_MAT2x4: + case GL_FLOAT_MAT3x4: + return GL_FLOAT_VEC4; + default: + return glType; + } + } +} + namespace MobileGL::MG_State::GLState { bool ProgramObject::ShaderIsAttached(const SharedPtr& shader) { MGLOG_D("ProgramObject %u: ShaderIsAttached check for shader %p", m_externalIndex, shader.get()); @@ -264,7 +304,10 @@ namespace MobileGL::MG_State::GLState { Int maxLoc = -1; for (int i = 0; i < inCount; ++i) { Int loc = (Int)m_program->getPipeInput(i).layoutLocation(); - if (loc >= 0 && loc != glslang::TQualifier::layoutLocationEnd) maxLoc = std::max(maxLoc, loc); + if (loc >= 0 && loc != glslang::TQualifier::layoutLocationEnd) { + const Int locationSpan = GetVertexInputLocationSpan(m_program->getPipeInput(i).glDefineType); + maxLoc = std::max(maxLoc, loc + locationSpan - 1); + } MGLOG_D("ProgramObject %u: Reflection - pipe input[%d] name='%s' layoutLocation=%d glType=%u", m_externalIndex, i, m_program->getPipeInput(i).name.c_str(), loc, m_program->getPipeInput(i).glDefineType); @@ -294,10 +337,25 @@ namespace MobileGL::MG_State::GLState { m_attribInNameMaxLength = std::max(m_attribInNameMaxLength, (Int)inVar.name.length()); if (location >= 0 && location < (int)m_attribs.size()) { - m_attribs[location] = inVar.name; - m_attribTypes[location] = inVar.glDefineType; - MGLOG_D("ProgramObject %u: Reflection - got attrib '%s' at explicit location %d", m_externalIndex, - inVar.name.c_str(), location); + const Int locationSpan = GetVertexInputLocationSpan(inVar.glDefineType); + const GLenum locationType = GetVertexInputLocationType(inVar.glDefineType); + for (Int locationOffset = 0; locationOffset < locationSpan; ++locationOffset) { + const Int expandedLocation = location + locationOffset; + if (expandedLocation < 0 || expandedLocation >= static_cast(m_attribs.size())) { + break; + } + + m_attribs[expandedLocation] = inVar.name; + m_attribTypes[expandedLocation] = locationType; + MGLOG_D( + "ProgramObject %u: Reflection - got attrib '%s' at expanded location %d (baseLocation=%d glType=%u expandedType=%u)", + m_externalIndex, + inVar.name.c_str(), + expandedLocation, + location, + inVar.glDefineType, + static_cast(locationType)); + } } // else if (location >= (int)m_attribs.size()) { // MGLOG_W("ProgramObject %u: ProgramObject::DoReflection - attrib location %d >= attribs.size() diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h index 26f22bd9..81acc38f 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h @@ -63,6 +63,48 @@ namespace MobileGL::MG_State::GLState { const auto it = std::find(m_attribs.begin(), m_attribs.end(), name); return (it == m_attribs.end()) ? -1 : (Int)std::distance(m_attribs.begin(), it); } + Uint32 GetActiveAttributeLocationMask() const { + Uint32 mask = 0; + const SizeT count = std::min(m_attribs.size(), 32); + for (SizeT index = 0; index < count; ++index) { + if (!m_attribs[index].empty()) { + mask |= (1u << index); + } + } + return mask; + } + Uint32 GetActiveFragmentOutputLocationMask() const { + if (!m_program) { + return 0; + } + + Uint32 mask = 0; + const Int outputCount = m_program->getNumPipeOutputs(); + for (Int index = 0; index < outputCount; ++index) { + const Int location = static_cast(m_program->getPipeOutput(index).layoutLocation()); + if (location >= 0 && location < 32) { + mask |= (1u << location); + } + } + return mask; + } + Int GetActiveFragmentOutputCount() const { + return m_program ? m_program->getNumPipeOutputs() : 0; + } + Int GetFragmentOutputLocation(Uint index) const { + MOBILEGL_ASSERT(m_program != nullptr, "ProgramObject::GetFragmentOutputLocation: program is null"); + MOBILEGL_ASSERT(index < static_cast(m_program->getNumPipeOutputs()), + "ProgramObject::GetFragmentOutputLocation: index=%u out of range", + index); + return static_cast(m_program->getPipeOutput(static_cast(index)).layoutLocation()); + } + GLenum GetFragmentOutputType(Uint index) const { + MOBILEGL_ASSERT(m_program != nullptr, "ProgramObject::GetFragmentOutputType: program is null"); + MOBILEGL_ASSERT(index < static_cast(m_program->getNumPipeOutputs()), + "ProgramObject::GetFragmentOutputType: index=%u out of range", + index); + return m_program->getPipeOutput(static_cast(index)).glDefineType; + } GLenum GetAttribType(Uint index) const { return m_attribTypes[index]; } const String& GetAttribName(Uint index) const { return m_attribs[index]; } void* MapUBO() { return m_globalUboScratch.data(); } diff --git a/MobileGL/MG_Util/Metrics/TextureMetrics.cpp b/MobileGL/MG_Util/Metrics/TextureMetrics.cpp index c5f343d7..d4a1a364 100644 --- a/MobileGL/MG_Util/Metrics/TextureMetrics.cpp +++ b/MobileGL/MG_Util/Metrics/TextureMetrics.cpp @@ -153,6 +153,9 @@ namespace MobileGL { case TextureInternalFormat::R16I: case TextureInternalFormat::R16UI: case TextureInternalFormat::R16F: + case TextureInternalFormat::R32F: + case TextureInternalFormat::R32I: + case TextureInternalFormat::R32UI: return 1; case TextureInternalFormat::RG: case TextureInternalFormat::RG8: @@ -173,6 +176,9 @@ namespace MobileGL { case TextureInternalFormat::RGB5: case TextureInternalFormat::RGB8: case TextureInternalFormat::RGB8Snorm: + case TextureInternalFormat::RGB10: + case TextureInternalFormat::RGB12: + case TextureInternalFormat::RGB16: case TextureInternalFormat::SRGB8: case TextureInternalFormat::RGB8I: case TextureInternalFormat::RGB8UI: @@ -192,6 +198,7 @@ namespace MobileGL { case TextureInternalFormat::RGBA8Snorm: case TextureInternalFormat::RGBA8I: case TextureInternalFormat::RGBA8UI: + case TextureInternalFormat::SRGB8Alpha8: case TextureInternalFormat::RGBA12: case TextureInternalFormat::RGBA16: case TextureInternalFormat::RGBA16I: diff --git a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp index 9a693344..d4023cef 100644 --- a/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp +++ b/MobileGL/MG_Util/ShaderTranspiler/ShaderSourceProcessor.cpp @@ -13,10 +13,253 @@ namespace { using MobileGL::SizeT; + struct FlattenedVaryingMember { + const char* typeName; + const char* memberName; + }; + + constexpr FlattenedVaryingMember kDailyWeatherVariationMembers[] = { + {"vec2", "clouds_cumulus_coverage"}, + {"vec2", "clouds_altocumulus_coverage"}, + {"vec2", "clouds_cirrus_coverage"}, + {"float", "clouds_cumulus_congestus_amount"}, + {"float", "clouds_stratus_amount"}, + {"float", "fogginess"}, + {"float", "aurora_amount"}, + {"float", "nlc_amount"}, + {"mat2x3", "aurora_colors"}, + }; + bool IsIdentifierChar(char ch) { return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_'; } + bool HasIdentifierBoundaries(const MobileGL::String& source, SizeT pos, SizeT length) { + const bool hasLeftBoundary = pos == 0 || !IsIdentifierChar(source[pos - 1]); + const SizeT end = pos + length; + const bool hasRightBoundary = end >= source.size() || !IsIdentifierChar(source[end]); + return hasLeftBoundary && hasRightBoundary; + } + + SizeT FindToken(const MobileGL::String& source, const MobileGL::String& token, SizeT start = 0) { + SizeT pos = start; + while ((pos = source.find(token, pos)) != MobileGL::String::npos) { + if (HasIdentifierBoundaries(source, pos, token.size())) { + return pos; + } + pos += token.size(); + } + return MobileGL::String::npos; + } + + void ReplaceTokenOccurrencesInRange(MobileGL::String& source, SizeT rangeStart, SizeT rangeEnd, + const MobileGL::String& from, const MobileGL::String& to) { + SizeT pos = rangeStart; + while ((pos = source.find(from, pos)) != MobileGL::String::npos && pos < rangeEnd) { + if (!HasIdentifierBoundaries(source, pos, from.size())) { + pos += from.size(); + continue; + } + + source.replace(pos, from.size(), to); + const auto delta = static_cast(to.size()) - static_cast(from.size()); + rangeEnd = static_cast(static_cast(rangeEnd) + delta); + pos += to.size(); + } + } + + void ReplaceAll(MobileGL::String& source, const MobileGL::String& from, const MobileGL::String& to) { + SizeT pos = 0; + while ((pos = source.find(from, pos)) != MobileGL::String::npos) { + source.replace(pos, from.size(), to); + pos += to.size(); + } + } + + MobileGL::String TrimWhitespace(const MobileGL::String& input) { + SizeT begin = 0; + while (begin < input.size() && std::isspace(static_cast(input[begin]))) { + begin++; + } + + SizeT end = input.size(); + while (end > begin && std::isspace(static_cast(input[end - 1]))) { + end--; + } + + return input.substr(begin, end - begin); + } + + bool FindFunctionBody(const MobileGL::String& source, const MobileGL::String& signature, SizeT* bodyStart, + SizeT* bodyEnd) { + const SizeT signaturePos = source.find(signature); + if (signaturePos == MobileGL::String::npos) { + return false; + } + + const SizeT bracePos = source.find('{', signaturePos + signature.size()); + if (bracePos == MobileGL::String::npos) { + return false; + } + + int depth = 1; + for (SizeT pos = bracePos + 1; pos < source.size(); pos++) { + if (source[pos] == '{') { + depth++; + } else if (source[pos] == '}') { + depth--; + if (depth == 0) { + *bodyStart = bracePos + 1; + *bodyEnd = pos; + return true; + } + } + } + + return false; + } + + void RenameDailyWeatherVariationHelperLocal(MobileGL::String& source) { + constexpr const char* kHelperSignature = "DailyWeatherVariation get_daily_weather_variation()"; + constexpr const char* kInterfaceName = "daily_weather_variation"; + constexpr const char* kLocalName = "mg_daily_weather_variation_local"; + + SizeT bodyStart = 0; + SizeT bodyEnd = 0; + if (!FindFunctionBody(source, kHelperSignature, &bodyStart, &bodyEnd)) { + return; + } + + ReplaceTokenOccurrencesInRange(source, bodyStart, bodyEnd, kInterfaceName, kLocalName); + } + + bool RewriteDailyWeatherVariationInterface(MobileGL::ShaderStage stage, MobileGL::String& source) { + using MobileGL::ShaderStage; + + if (stage != ShaderStage::Vertex && stage != ShaderStage::Fragment) { + return false; + } + + constexpr const char* kTypeName = "DailyWeatherVariation"; + constexpr const char* kInterfaceName = "daily_weather_variation"; + constexpr const char* kTempName = "mg_daily_weather_variation_tmp"; + const MobileGL::String declarationNeedle = MobileGL::String(kTypeName) + " " + kInterfaceName + ";"; + + RenameDailyWeatherVariationHelperLocal(source); + + const SizeT declarationPos = source.find(declarationNeedle); + if (declarationPos == MobileGL::String::npos) { + return false; + } + + SizeT lineStart = source.rfind('\n', declarationPos); + lineStart = (lineStart == MobileGL::String::npos) ? 0 : lineStart + 1; + SizeT lineEnd = source.find('\n', declarationPos); + if (lineEnd == MobileGL::String::npos) { + lineEnd = source.size(); + } + + const MobileGL::String declarationLine = source.substr(lineStart, lineEnd - lineStart); + MOBILEGL_ASSERT(declarationLine.find("layout(") == MobileGL::String::npos, + "PreprocessShaderSource: unexpected explicit layout on DailyWeatherVariation interface in stage=%d", + static_cast(stage)); + + const bool hasInputQualifier = declarationLine.find(" in ") != MobileGL::String::npos || + declarationLine.rfind("in ", 0) == 0; + const bool hasOutputQualifier = declarationLine.find(" out ") != MobileGL::String::npos || + declarationLine.rfind("out ", 0) == 0; + MOBILEGL_ASSERT(hasInputQualifier != hasOutputQualifier, + "PreprocessShaderSource: expected a single in/out qualifier on DailyWeatherVariation interface in stage=%d line='%s'", + static_cast(stage), declarationLine.c_str()); + + const MobileGL::String qualifierPrefix = source.substr(lineStart, declarationPos - lineStart); + MobileGL::String replacementDeclaration; + for (const auto& member : kDailyWeatherVariationMembers) { + replacementDeclaration += qualifierPrefix; + replacementDeclaration += member.typeName; + replacementDeclaration += " "; + replacementDeclaration += kInterfaceName; + replacementDeclaration += "_"; + replacementDeclaration += member.memberName; + replacementDeclaration += ";\n"; + } + source.replace(lineStart, lineEnd - lineStart + (lineEnd < source.size() ? 1 : 0), replacementDeclaration); + + SizeT assignPos = FindToken(source, kInterfaceName); + while (assignPos != MobileGL::String::npos) { + SizeT probe = assignPos + strlen(kInterfaceName); + while (probe < source.size() && std::isspace(static_cast(source[probe]))) { + probe++; + } + + if (probe >= source.size() || source[probe] != '=') { + assignPos = FindToken(source, kInterfaceName, assignPos + strlen(kInterfaceName)); + continue; + } + + SizeT statementStart = source.rfind('\n', assignPos); + statementStart = (statementStart == MobileGL::String::npos) ? 0 : statementStart + 1; + for (SizeT i = statementStart; i < assignPos; i++) { + MOBILEGL_ASSERT(std::isspace(static_cast(source[i])), + "PreprocessShaderSource: unexpected inline DailyWeatherVariation assignment in stage=%d", + static_cast(stage)); + } + + const MobileGL::String indentation = source.substr(statementStart, assignPos - statementStart); + const SizeT statementEnd = source.find(';', probe); + MOBILEGL_ASSERT(statementEnd != MobileGL::String::npos, + "PreprocessShaderSource: missing ';' after DailyWeatherVariation assignment in stage=%d", + static_cast(stage)); + + const MobileGL::String rhsExpression = TrimWhitespace(source.substr(probe + 1, statementEnd - probe - 1)); + MobileGL::String replacementStatement; + replacementStatement += indentation; + replacementStatement += "{\n"; + replacementStatement += indentation; + replacementStatement += " DailyWeatherVariation "; + replacementStatement += kTempName; + replacementStatement += " = "; + replacementStatement += rhsExpression; + replacementStatement += ";\n"; + for (const auto& member : kDailyWeatherVariationMembers) { + replacementStatement += indentation; + replacementStatement += " "; + replacementStatement += kInterfaceName; + replacementStatement += "_"; + replacementStatement += member.memberName; + replacementStatement += " = "; + replacementStatement += kTempName; + replacementStatement += "."; + replacementStatement += member.memberName; + replacementStatement += ";\n"; + } + replacementStatement += indentation; + replacementStatement += "}"; + if (statementEnd + 1 < source.size() && source[statementEnd + 1] == '\n') { + replacementStatement += "\n"; + source.replace(statementStart, statementEnd - statementStart + 2, replacementStatement); + } else { + source.replace(statementStart, statementEnd - statementStart + 1, replacementStatement); + } + + assignPos = FindToken(source, kInterfaceName, statementStart + replacementStatement.size()); + } + + for (const auto& member : kDailyWeatherVariationMembers) { + const MobileGL::String from = MobileGL::String(kInterfaceName) + "." + member.memberName; + const MobileGL::String to = MobileGL::String(kInterfaceName) + "_" + member.memberName; + ReplaceAll(source, from, to); + } + + MOBILEGL_ASSERT(source.find(MobileGL::String(kTypeName) + " " + kInterfaceName + ";") == MobileGL::String::npos, + "PreprocessShaderSource: unrewritten DailyWeatherVariation interface declaration remained in stage=%d", + static_cast(stage)); + MOBILEGL_ASSERT(source.find(MobileGL::String(kInterfaceName) + ".") == MobileGL::String::npos, + "PreprocessShaderSource: unrewritten DailyWeatherVariation member access remained in stage=%d", + static_cast(stage)); + return true; + } + bool HasSingleLineFunctionDefinition(const MobileGL::String& source, const MobileGL::String& functionName) { SizeT lineStart = 0; while (lineStart < source.size()) { @@ -164,6 +407,8 @@ namespace MobileGL { RenameBuiltinShadowingFunction(source, "round", "mg_round"); RenameBuiltinShadowingFunction(source, "tanh", "mg_tanh"); RenameBuiltinShadowingFunction(source, "fma", "mg_fma"); + + RewriteDailyWeatherVariationInterface(stage, source); } } // namespace ShaderTranspiler