kunzmi / managedCuda

ManagedCUDA aims an easy integration of NVidia's CUDA in .net applications written in C#, Visual Basic or any other .net language.
Other
440 stars 79 forks source link

Exception with CudaContext.CreateOpenGLContext #115

Closed Wassermann-nl closed 1 year ago

Wassermann-nl commented 1 year ago

Hi, I'm in the process of changing my program from using ManagedCuda.NETStandard 9.1.300 to ManagedCuda-12 (12.0.48).

I get the following runtime exception when I use CudaContext.CreateOpenGLContext for context creation :

      Unhandled exception. System.TypeInitializationException: The type initializer for 'CUDA3' threw an exception.
       ---> System.TypeInitializationException: The type initializer for 'ManagedCuda.OpenGLNativeMethods' threw an exception.
       ---> System.InvalidOperationException: A resolver is already set for the assembly.
         at System.Runtime.InteropServices.NativeLibrary.SetDllImportResolver(Assembly assembly, DllImportResolver resolver)
         at ManagedCuda.OpenGLNativeMethods..cctor()
         --- End of inner exception stack trace ---
         at ManagedCuda.OpenGLNativeMethods.Init()
         at ManagedCuda.OpenGLNativeMethods.CUDA3..cctor()
         --- End of inner exception stack trace ---
         at ManagedCuda.OpenGLNativeMethods.CUDA3.cuGLCtxCreate(CUcontext& pCtx, CUCtxFlags Flags, CUdevice device)
         at ManagedCuda.CudaContext..ctor(CUCtxFlags flags, Int32 deviceId)
         at ManagedCuda.CudaContext.CreateOpenGLContext(Int32 deviceId, CUCtxFlags flags)

if I create the context with the CudaContext constructor instead, then it works without any problems as long as I don't use gl interoperability.

But if I then use CudaOpenGLBufferInteropResource, for example, I get the following similar exception.

          Unhandled exception. System.TypeInitializationException: The type initializer for 'CUDA3' threw an exception.
           ---> System.TypeInitializationException: The type initializer for 'ManagedCuda.OpenGLNativeMethods' threw an exception.
           ---> System.InvalidOperationException: A resolver is already set for the assembly.
             at System.Runtime.InteropServices.NativeLibrary.SetDllImportResolver(Assembly assembly, DllImportResolver resolver)
             at ManagedCuda.OpenGLNativeMethods..cctor()
             --- End of inner exception stack trace ---
             at ManagedCuda.OpenGLNativeMethods.Init()
             at ManagedCuda.OpenGLNativeMethods.CUDA3..cctor()
             --- End of inner exception stack trace ---
             at ManagedCuda.OpenGLNativeMethods.CUDA3.cuGraphicsGLRegisterBuffer(CUgraphicsResource& pCudaResource, 
             UInt32 buffer, CUGraphicsRegisterFlags Flags)
             at ManagedCuda.CudaOpenGLBufferInteropResource..ctor(UInt32 resource, CUGraphicsRegisterFlags flags)
             at ManagedCuda.CudaOpenGLBufferInteropResource..ctor(UInt32 resource, CUGraphicsRegisterFlags flags, 
            CUGraphicsMapResourceFlags mapFlags)
             at TexturePixelBuffer.PixelBuffer..ctor()

Do you have any idea what is causing the problem and how to fix it?

Wassermann-nl commented 1 year ago

I cloned managedCuda and changed the following in the file OpenGL.cs, then my program runs properly. I don't know if that's the best solution, but it works.

can you please change it in the nuget package so that i can use the nuget package instead of the clone.

i am a git noob, otherwise I would make an pull request.

    // from
    static OpenGLNativeMethods()
    {
         NativeLibrary.SetDllImportResolver(typeof(OpenGLNativeMethods).Assembly, ImportResolver);
    }

    // to
    static OpenGLNativeMethods()
    {

        try
        {
            NativeLibrary.SetDllImportResolver(typeof(OpenGLNativeMethods).Assembly, ImportResolver);
        }
        catch (Exception)
        {

        }
    }  
kunzmi commented 1 year ago

Hi,

indeed, the call to NativeLibrary.SetDllImportResolver(...) is the cause of the troubles, as a loader is already set for the main CUDA-dll. I'm just hesitating to use a try/catch as regular control flow, on the other side I heaven't seen any API that allows to query if a delegate is already set. I guess the best solution would be to replace NativeLibrary.SetDllImportResolver by DriverAPINativeMethods.Init(): this way everthing is well centralised in one static method. I'll have to check if that works and, if not, what would be a good solution to fix that issue, if you have any other ideas, don't hesitate to post them here!

Cheers, Michael

Wassermann-nl commented 1 year ago

Contrary to the description of the method SetDllImportResolver, you can see in the code that the InvalidOperationException is thrown if the resolver for the assembly was already set, so you could only intercept exactly these.

        /// <summary>
        /// Set a callback for resolving native library imports from an assembly.
        /// This per-assembly resolver is the first attempt to resolve native library loads
        /// initiated by this assembly.
        ///
        /// Only one resolver can be registered per assembly.
        /// Trying to register a second resolver fails with InvalidOperationException.
        /// </summary>
        /// <param name="assembly">The assembly for which the resolver is registered.</param>
        /// <param name="resolver">The resolver callback to register.</param>
        /// <exception cref="System.ArgumentNullException">If assembly or resolver is null</exception>
        /// <exception cref="System.ArgumentException">If a resolver is already set for this assembly</exception>
        public static void SetDllImportResolver(Assembly assembly, DllImportResolver resolver)
        {
            ArgumentNullException.ThrowIfNull(assembly);
            ArgumentNullException.ThrowIfNull(resolver);

            if (assembly is not RuntimeAssembly)
                throw new ArgumentException(SR.Argument_MustBeRuntimeAssembly);

            if (s_nativeDllResolveMap == null)
            {
                Interlocked.CompareExchange(ref s_nativeDllResolveMap,
                    new ConditionalWeakTable<Assembly, DllImportResolver>(), null);
            }

            if (!s_nativeDllResolveMap.TryAdd(assembly, resolver))
            {
                throw new InvalidOperationException(SR.InvalidOperation_CannotRegisterSecondResolver);
            }
        }

    static OpenGLNativeMethods()
    {
        try
        {
            NativeLibrary.SetDllImportResolver(typeof(OpenGLNativeMethods).Assembly, ImportResolver);
        }
        catch (InvalidOperationException)
        {

        }
    }
kunzmi commented 1 year ago

I just pushed a correction for that bug that seems to fix the issue in my tests. Could you please try it out and check if that also works in your case? If so, I'll create new Nuget images (also including then Cuda12.1).

Cheers, Michael

Wassermann-nl commented 1 year ago

i will try it out today.

Wassermann-nl commented 1 year ago

hi Michael, it works !

kunzmi commented 1 year ago

Great, thanks! I'll update the nuget packages soon