سؤال

After watching this video, I started thinking about how I could implement something like that in my current project. I think it would be too difficult to make the bulk of my code editable in real-time, but I thought I could at least make my OpenGL shaders editable as I play the game.

So I set up a FileSystemWatcher:

protected void WatchShaders()
{
    _uiDispatcher = Dispatcher.CurrentDispatcher;
    const string shaderDir = @"path\to\my\shaders";
    _shaderFileWatcher = new FileSystemWatcher(shaderDir);
    _shaderFileWatcher.NotifyFilter = NotifyFilters.LastWrite;
    //fw.Filter = "*.frag;*.vert";
    _shaderFileWatcher.Changed += ShaderChanged;
    _shaderFileWatcher.EnableRaisingEvents = true;
}

And now I want to update the shader whenever a file changes:

void ShaderChanged(object sender, FileSystemEventArgs e)
{
    _shaderFileWatcher.EnableRaisingEvents = false; // prevent more/duplicate events from firing before we've finished processing the current one

    lock (_bfShader)
    {
        _bfShader.AttachShader(Shader.FromFile(e.FullPath));
        _bfShader.Link();
        _bfShader.Use();

        _bfProjUniform = new Uniform(_bfShader, "ProjectionMatrix");
        _bfSampler = new Uniform(_bfShader, "TexSampler");
        _bfSampler.Set1(0);
    }
    _shaderFileWatcher.EnableRaisingEvents = true;
}

The problem is as soon as I edit my shader file, an exception is throwing saying:

No context is current in the calling thread

So I did some digging and found out that the OpenGL context is essentially bound to a single thread. AFAIK, there are 2 workarounds for this:

  1. Disable the OpenGL context on the main UI thread, enable it on the other thread, do my stuff, and then reset it
  2. Dispatch the event back to the main UI thread

I'm not sure how I would implement (1) because the main thread is riddled with OpenGL calls... I wouldn't know where to enable and disable it.

So I'm left with option (2), except I can't figure out how to dispatch the file changed event back to the main thread.

This article says:

The GLControl provides the GLControl.BeginInvoke() method to simplify asynchronous method calls from secondary threads to the main System.Windows.Forms.Application thread. The GameWindow does not provide a similar API.

Unfortunately I am using a GameWindow, so I'm not sure how to access that functionality.

So what's the easiest way to dispatch my event back to the main UI thread? Either using the OpenTK library or another preferably non-windows-only library?

هل كانت مفيدة؟

المحلول

Figured out I could just use a queue and pull things off it:

void ShaderChanged(object sender, FileSystemEventArgs e)
{
    _shaderFileWatcher.EnableRaisingEvents = false; // prevent more/duplicate events from firing before we've finished processing the current one

    lock (_renderQueue)
    {
        _renderQueue.Enqueue(() =>
            {
                switch(e.Name)
                {
                    case "block.frag":
                        _bfShader.DetachShader(_blockFragShader);
                        _blockFragShader = Shader.FromFile(e.FullPath);
                        _bfShader.AttachShader(_blockFragShader);
                        break;
                    default:
                        return;
                }

                Trace.TraceInformation("Updating shader '{0}'", e.Name);

                _bfShader.Link();
                _bfShader.Use();

                _bfProjUniform = new Uniform(_bfShader, "ProjectionMatrix");
                _bfSampler = new Uniform(_bfShader, "TexSampler");
                _bfSampler.Set1(0);
            });
    }

    _shaderFileWatcher.EnableRaisingEvents = true;
}

And then I modify my render loop slightly:

lock(_renderQueue)
{
    while(_renderQueue.Count > 0)
    {
        _renderQueue.Dequeue().Invoke();
    }
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top