/// <summary> /// Check whether commands implemented by the current driver have a corresponding extension declaring the /// support of them. /// </summary> /// <param name="version"> /// The <see cref="KhronosVersion" /> currently implemented by the current context on this thread. /// </param> /// <param name="extensions"> /// The <see cref="ExtensionsCollection" /> that specifies the extensions supported by the driver. /// </param> /// <param name="enableExtensions"></param> protected static void CheckExtensionCommands(KhronosVersion version, ExtensionsCollection extensions, bool enableExtensions) { if (version == null) { throw new ArgumentNullException(nameof(version)); } if (extensions == null) { throw new ArgumentNullException(nameof(extensions)); } var hiddenVersions = new Dictionary <string, List <Type> >(); var hiddenExtensions = new Dictionary <string, bool>(); foreach (FieldInfo fi in _functionContext.Delegates) { var fiDelegateType = (Delegate)fi.GetValue(null); bool commandDefined = fiDelegateType != null; var supportedByFeature = false; // Get the delegate type Type delegateType = fi.DeclaringType?.GetNestedType(fi.Name.Substring(1), BindingFlags.Public | BindingFlags.NonPublic); if (delegateType == null) { continue; // Support fields names not in sync with delegate types } // TODO Why not use 'fi' directly for getting attributes? They should be in sync IEnumerable <object> requiredByFeatureAttributes = delegateType.GetCustomAttributes(typeof(RequiredByFeatureAttribute), false); foreach (RequiredByFeatureAttribute requiredByFeatureAttribute in requiredByFeatureAttributes) { supportedByFeature |= requiredByFeatureAttribute.IsSupported(version, extensions); } // Find the underlying extension RequiredByFeatureAttribute hiddenVersionAttrib = null; RequiredByFeatureAttribute hiddenExtensionAttrib = null; foreach (RequiredByFeatureAttribute requiredByFeatureAttribute in requiredByFeatureAttributes) { if (requiredByFeatureAttribute.IsSupportedApi(version.Api) == false) { // Version attribute if (hiddenVersionAttrib == null) { hiddenVersionAttrib = requiredByFeatureAttribute; } } else { // Extension attribute if (hiddenExtensionAttrib == null) { hiddenExtensionAttrib = requiredByFeatureAttribute; } } } if (commandDefined != supportedByFeature) { if (commandDefined) { if (hiddenVersionAttrib != null && hiddenExtensionAttrib == null) { if (hiddenVersions.TryGetValue(hiddenVersionAttrib.FeatureName, out List <Type> versionDelegates) == false) { hiddenVersions.Add(hiddenVersionAttrib.FeatureName, versionDelegates = new List <Type>()); } versionDelegates.Add(delegateType); } if (hiddenExtensionAttrib != null) // Eventually leave to false for incomplete extensions { if (hiddenExtensions.ContainsKey(hiddenExtensionAttrib.FeatureName) == false) { hiddenExtensions.Add(hiddenExtensionAttrib.FeatureName, true); } } } } // Partial extensions are not supported if (hiddenExtensionAttrib != null && commandDefined == false && hiddenExtensions.ContainsKey(hiddenExtensionAttrib.FeatureName)) { hiddenExtensions[hiddenExtensionAttrib.FeatureName] = false; } } if (!enableExtensions) { return; } var sync = false; foreach (KeyValuePair <string, bool> hiddenExtension in hiddenExtensions.Where(hiddenExtension => hiddenExtension.Value)) { extensions.EnableExtension(hiddenExtension.Key); sync = true; } if (sync) { extensions.SyncMembers(version); } }
/// <summary> /// Check whether commands implemented by the current driver have a corresponding extension declaring the /// support of them. /// </summary> /// <typeparam name="T"> /// The type of the KhronosApi to inspect for commands. /// </typeparam> /// <param name="version"> /// The <see cref="KhronosVersion"/> currently implemented by the current context on this thread. /// </param> /// <param name="extensions"> /// The <see cref="ExtensionsCollection"/> that specifies the extensions supported by the driver. /// </param> protected static void CheckExtensionCommands <T>(KhronosVersion version, ExtensionsCollection extensions, bool enableExtensions) where T : KhronosApi { if (version == null) { throw new ArgumentNullException(nameof(version)); } if (extensions == null) { throw new ArgumentNullException(nameof(extensions)); } #if NETSTANDARD1_1 || NETSTANDARD1_4 || NETCORE throw new NotImplementedException(); #else FunctionContext functionContext = GetFunctionContext(typeof(T)); Debug.Assert(functionContext != null); LogComment($"Checking commands for {version}"); Dictionary <string, List <Type> > hiddenVersions = new Dictionary <string, List <Type> >(); Dictionary <string, bool> hiddenExtensions = new Dictionary <string, bool>(); foreach (FieldInfo fi in functionContext.Delegates) { Delegate fiDelegateType = (Delegate)fi.GetValue(null); bool commandDefined = fiDelegateType != null; bool supportedByFeature = false; #if DEBUG_VERBOSE string commandName = fi.Name.Substring(3); #endif // Get the delegate type Type delegateType = fi.DeclaringType?.GetNestedType(fi.Name.Substring(1), BindingFlags.Public | BindingFlags.NonPublic); if (delegateType == null) { continue; // Support fields names not in sync with delegate types } // TODO Why not use 'fi' directly for getting attributes? They should be in sync IEnumerable <object> requiredByFeatureAttributes = delegateType.GetCustomAttributes(typeof(RequiredByFeatureAttribute), false); foreach (RequiredByFeatureAttribute requiredByFeatureAttribute in requiredByFeatureAttributes) { supportedByFeature |= requiredByFeatureAttribute.IsSupported(version, extensions); } // Find the underlying extension RequiredByFeatureAttribute hiddenVersionAttrib = null; RequiredByFeatureAttribute hiddenExtensionAttrib = null; foreach (RequiredByFeatureAttribute requiredByFeatureAttribute in requiredByFeatureAttributes) { if (!requiredByFeatureAttribute.IsSupportedApi(version.Api)) { // Version attribute if (hiddenVersionAttrib == null) { hiddenVersionAttrib = requiredByFeatureAttribute; } } else { // Extension attribute if (hiddenExtensionAttrib == null) { hiddenExtensionAttrib = requiredByFeatureAttribute; } } } if (commandDefined != supportedByFeature) { #if DEBUG_VERBOSE string supportString = "any feature"; if (hiddenVersionAttrib != null) { supportString = hiddenVersionAttrib.FeatureName; if (hiddenExtensionAttrib != null) { supportString += " or "; } } if (hiddenExtensionAttrib != null) { if (hiddenVersionAttrib == null) { supportString = string.Empty; } supportString += hiddenExtensionAttrib.FeatureName; } #endif if (commandDefined) { #if DEBUG_VERBOSE LogComment("The command {0} is defined, but {1} support is not advertised.", commandName, supportString); #endif if (hiddenVersionAttrib != null && hiddenExtensionAttrib == null) { List <Type> versionDelegates; if (!hiddenVersions.TryGetValue(hiddenVersionAttrib.FeatureName, out versionDelegates)) { hiddenVersions.Add(hiddenVersionAttrib.FeatureName, versionDelegates = new List <Type>()); } versionDelegates.Add(delegateType); } if (hiddenExtensionAttrib != null) { // Eventually leave to false for incomplete extensions if (!hiddenExtensions.ContainsKey(hiddenExtensionAttrib.FeatureName)) { hiddenExtensions.Add(hiddenExtensionAttrib.FeatureName, true); } } } else { #if DEBUG_VERBOSE LogComment("The command {0} is not defined, but required by some feature.", commandName); #endif } } // Partial extensions are not supported if (hiddenExtensionAttrib != null && !commandDefined && hiddenExtensions.ContainsKey(hiddenExtensionAttrib.FeatureName)) { hiddenExtensions[hiddenExtensionAttrib.FeatureName] = false; } } if (hiddenExtensions.Count > 0) { LogComment($"Found {hiddenExtensions.Count} experimental extensions:"); foreach (KeyValuePair <string, bool> hiddenExtension in hiddenExtensions) { LogComment(string.Format($"- {0}: {1}", hiddenExtension.Key, hiddenExtension.Value ? "experimental" : "experimental (partial, unsupported)")); } } if (hiddenVersions.Count > 0) { LogComment($"Found {hiddenVersions.Count} experimental version commands:"); foreach (KeyValuePair <string, List <Type> > hiddenVersion in hiddenVersions) { LogComment($"- {hiddenVersion.Key}"); foreach (Type delegateType in hiddenVersion.Value) { LogComment($" > {delegateType.Name}"); } } } if (enableExtensions) { bool sync = false; foreach (KeyValuePair <string, bool> hiddenExtension in hiddenExtensions) { if (!hiddenExtension.Value) { continue; // Do not enable partial extension } extensions.EnableExtension(hiddenExtension.Key); sync = true; } if (sync) { extensions.SyncMembers(version); } } #endif }
public static void BindAPIFunction(FieldInfo function, KhronosVersion version, ExtensionsCollection extensions, GraphicsContext context) { Debug.Assert(function != null); RequiredByFeatureAttribute requiredByFeature = null; var requiredByExtensions = new List <RequiredByFeatureAttribute>(); string defaultName = function.Name.Substring(1); // Delegate name always prefixes with 'p' if (version != null || extensions != null) { var removed = false; #region Check Requirement IEnumerable <Attribute> attrRequired = new List <Attribute>(function.GetCustomAttributes(typeof(RequiredByFeatureAttribute))); // ReSharper disable once PossibleInvalidCastExceptionInForeachLoop foreach (RequiredByFeatureAttribute attr in attrRequired) { // Check for API support if (attr.IsSupported(version, extensions) == false) { continue; } // Keep track of the features requiring this command if (attr.FeatureVersion != null) { // Version feature: keep track only of the maximum version if (requiredByFeature == null || requiredByFeature.FeatureVersion < attr.FeatureVersion) { requiredByFeature = attr; } } else { // Extension feature: collect every supporting extension requiredByExtensions.Add(attr); } } #endregion #region Check Deprecation/Removal if (requiredByFeature != null) { // Note: indeed the feature could be supported; check whether it is removed; this is checked only if // a non-extension feature is detected: extensions cannot remove commands Attribute[] attrRemoved = Attribute.GetCustomAttributes(function, typeof(RemovedByFeatureAttribute)); KhronosVersion maxRemovedVersion = null; // ReSharper disable once PossibleInvalidCastExceptionInForeachLoop foreach (RemovedByFeatureAttribute attr in attrRemoved) { // Check for API support if (attr.IsRemoved(version, extensions) == false) { continue; } // Removed! removed = true; // Keep track of the maximum API version removing this command if (maxRemovedVersion == null || maxRemovedVersion < attr.FeatureVersion) { maxRemovedVersion = attr.FeatureVersion; } } // Check for resurrection if (removed) { Debug.Assert(requiredByFeature != null); Debug.Assert(maxRemovedVersion != null); if (requiredByFeature.FeatureVersion > maxRemovedVersion) { removed = false; } } } #endregion // Do not check feature requirements in case of removal. Note: extensions are checked all the same if (removed) { requiredByFeature = null; } } // Load function pointer IntPtr importAddress; Delegate importDelegate; if (requiredByFeature != null || version == null) { // Load command address (version feature) string functionName = defaultName; if (requiredByFeature?.EntryPoint != null) { functionName = requiredByFeature.EntryPoint; } if (context.Native) { if ((importAddress = context.GetProcAddress(functionName)) != IntPtr.Zero) { BindNativeAPIFunction(function, importAddress); return; } } else { if ((importDelegate = context.GetProcAddressNonNative(functionName)) != null) { function.SetValue(null, importDelegate); return; } } } // Load command address (extension features) foreach (RequiredByFeatureAttribute extensionFeature in requiredByExtensions) { string functionName = extensionFeature.EntryPoint ?? defaultName; if (context.Native) { if ((importAddress = context.GetProcAddress(functionName)) != IntPtr.Zero) { BindNativeAPIFunction(function, importAddress); return; } } else { if ((importDelegate = context.GetProcAddressNonNative(functionName)) != null) { function.SetValue(null, importDelegate); return; } } } // Function not implemented: reset function.SetValue(null, null); }
/// <summary> /// Link delegates fields using import declarations. /// </summary> /// <param name="path"> /// A <see cref="String"/> that specifies the assembly file path containing the import functions. /// </param> /// <param name="getAddress"> /// A <see cref="GetAddressDelegate"/> used for getting function pointers. /// </param> /// <param name="functionContext"> /// A <see cref="FunctionContext"/> mapping a <see cref="MethodInfo"/> with the relative function name. /// </param> /// <param name="function"> /// A <see cref="FieldInfo"/> that specifies the underlying function field to be updated. /// </param> /// <exception cref="ArgumentNullException"> /// Exception thrown if <paramref name="path"/>, <paramref name="function"/> or <paramref name="getAddress"/> is null. /// </exception> private static void BindAPIFunction(string path, GetAddressDelegate getAddress, FunctionContext functionContext, FieldInfo function, KhronosVersion version, ExtensionsCollection extensions) { if (path == null) { throw new ArgumentNullException("path"); } if (functionContext == null) { throw new ArgumentNullException("functionContext"); } if (function == null) { throw new ArgumentNullException("function"); } if (getAddress == null) { throw new ArgumentNullException("getAddress"); } RequiredByFeatureAttribute requiredByFeature = null; List <RequiredByFeatureAttribute> requiredByExtensions = new List <RequiredByFeatureAttribute>(); string defaultName = function.Name.Substring(1); // Delegate name always prefixes with 'p' if (version != null || extensions != null) { bool isRemoved = false; #region Check Requirement #if NETSTANDARD1_1 || NETSTANDARD1_4 || NETCORE Attribute[] attrRequired = new List <Attribute>(function.GetCustomAttributes(typeof(RequiredByFeatureAttribute))).ToArray(); #else Attribute[] attrRequired = Attribute.GetCustomAttributes(function, typeof(RequiredByFeatureAttribute)); #endif foreach (RequiredByFeatureAttribute attr in attrRequired) { // Check for API support if (attr.IsSupported(version, extensions) == false) { continue; } // Keep track of the features requiring this command if (attr.FeatureVersion != null) { // Version feature: keep track only of the maximum version if (requiredByFeature == null || requiredByFeature.FeatureVersion < attr.FeatureVersion) { requiredByFeature = attr; } } else { // Extension feature: collect every supporting extension requiredByExtensions.Add(attr); } } #endregion #region Check Deprecation/Removal if (requiredByFeature != null) { // Note: indeed the feature could be supported; check whether it is removed; this is checked only if // a non-extension feature is detected: extensions cannot remove commands #if NETSTANDARD1_1 || NETSTANDARD1_4 || NETCORE Attribute[] attrRemoved = new List <Attribute>(function.GetCustomAttributes(typeof(RemovedByFeatureAttribute))).ToArray(); #else Attribute[] attrRemoved = Attribute.GetCustomAttributes(function, typeof(RemovedByFeatureAttribute)); #endif KhronosVersion maxRemovedVersion = null; foreach (RemovedByFeatureAttribute attr in attrRemoved) { // Check for API support if (attr.IsRemoved(version, extensions) == false) { continue; } // Removed! isRemoved |= true; // Keep track of the maximum API version removing this command if (maxRemovedVersion == null || maxRemovedVersion < attr.FeatureVersion) { maxRemovedVersion = attr.FeatureVersion; } } // Check for resurrection if (isRemoved) { Debug.Assert(requiredByFeature != null); Debug.Assert(maxRemovedVersion != null); if (requiredByFeature.FeatureVersion > maxRemovedVersion) { isRemoved = false; } } } #endregion // Do not check feature requirements in case of removal. Note: extensions are checked all the same if (isRemoved) { requiredByFeature = null; } } // Load function pointer IntPtr importAddress; if (requiredByFeature != null || version == null) { // Load command address (version feature) string functionName = defaultName; if (requiredByFeature != null && requiredByFeature.EntryPoint != null) { functionName = requiredByFeature.EntryPoint; } if ((importAddress = getAddress(path, functionName)) != IntPtr.Zero) { BindAPIFunction(function, importAddress); return; } } // Load command address (extension features) foreach (RequiredByFeatureAttribute extensionFeature in requiredByExtensions) { string functionName = extensionFeature.EntryPoint ?? defaultName; if ((importAddress = getAddress(path, functionName)) != IntPtr.Zero) { BindAPIFunction(function, importAddress); return; } } // Function not implemented: reset function.SetValue(null, null); }