Рендер-пайплайны

Интерфейс IGraphicsPipeline

Каждый рендер-бэкэнд (OpenGL, Vulkan, D3D12) реализует IGraphicsPipeline:

public interface IGraphicsPipeline
{
    IRenderContext Context { get; }
    void OnInit(ref Silk.NET.Windowing.WindowOptions options);
    void OnLoad(GameWindow window);
    void Cleanup();
}

Реализация пайплайна

public class MyGraphicsPipeline : IGraphicsPipeline
{
    public IRenderContext Context { get; private set; }
    
    private readonly List<GraphicsLoadingTask> _loaders;

    public MyGraphicsPipeline(List<GraphicsLoadingTask> loaders)
    {
        _loaders = loaders;
        // не создавайте контекст здесь — это до создания окна
    }

    public void OnInit(ref Silk.NET.Windowing.WindowOptions options)
    {
        // Опционально: изменить опции Silk.NET перед созданием окна
        // options.PreferredStencilBufferBits = 8;
        // options.Samples = 4;
    }

    public void OnLoad(GameWindow window)
    {
        // Создаём контекст графики (окно уже создано)
        Context = new MyRenderContext(window.Window);
        
        // Загружаем все задачи из очереди
        foreach (var task in _loaders)
        {
            try
            {
                task.Load();
            }
            catch (Exception ex)
            {
                Igdrasil.Logger.Error("Failed to load task {0}: {1}", task, ex.Message);
            }
        }
    }

    public void Cleanup()
    {
        // Выгружаем все задачи в обратном порядке
        for (int i = _loaders.Count - 1; i >= 0; i--)
        {
            try
            {
                _loaders[i].Unload();
            }
            catch (Exception ex)
            {
                Igdrasil.Logger.Error("Failed to unload task: {0}", ex.Message);
            }
        }
        
        Context?.Dispose();
        Context = null;
    }
}

Порядок инициализации

Igdrasil.InitializeWindow(options)
    ↓
GameWindow создаётся с выбранным пайплайном
    ↓
Пайплайн.OnInit (может изменить Silk.NET опции)
    ↓
Silk.NET окно создаётся
    ↓
Пайплайн.OnLoad (загрузка GPU-ресурсов)
    ↓
Igdrasil.Start()
    ↓
Igdrasil.OnLoad (пользовательский код)
    ↓
Основной цикл:
  - Igdrasil.OnUpdate
  - Пайплайн.OnRender (через GameWindow.OnRender)
  - SwapBuffers

IRenderContext

Контекст содержит низкоуровневый доступ к API (OpenGL, Vulkan и т.д.):

// Получение контекста в пользовательском коде
var context = Igdrasil.Context;

// Типичное использование в пайплайне рендера
Igdrasil.OnRender += (ctx, dt) =>
{
    ctx.Clear(Colors.Black);
    // рисование сцены
    ctx.Present();
};

Реальные методы IRenderContext определены в конкретной реализации (например, OpenGL context), не в интерфейсе.

Регистрация и переключение

// Регистрируем несколько движков
GameWindow.AddRenderEngine("OpenGL", loaders => new OpenGLPipeline(loaders));
GameWindow.AddRenderEngine("Vulkan", loaders => new VulkanPipeline(loaders));

// Выбираем при создании окна
var options = new WindowOptions { Engine = "OpenGL" };
Igdrasil.InitializeWindow(options);

// Переключаемся во время работы
Igdrasil.Window.Engine = "Vulkan"; // старый очищен, новый инициализирован

Обработка ошибок

public class SafePipeline : IGraphicsPipeline
{
    public IRenderContext Context { get; private set; }
    
    private readonly List<GraphicsLoadingTask> _loaders;
    private readonly ILogger _logger;

    public SafePipeline(List<GraphicsLoadingTask> loaders)
    {
        _loaders = loaders;
        _logger = Igdrasil.GetLogger("Pipeline");
    }

    public void OnLoad(GameWindow window)
    {
        try
        {
            Context = CreateContext(window.Window);
            _logger.Info("Context created");
        }
        catch (Exception ex)
        {
            _logger.Fatal("Failed to create context: {0}", ex.Message);
            throw;
        }

        // Загружаем с обработкой ошибок
        foreach (var task in _loaders)
        {
            try
            {
                task.Load();
                _logger.Debug("Loaded: {0}", task);
            }
            catch (Exception ex)
            {
                _logger.Warning("Failed to load {0}: {1}", task, ex.Message);
                // решить: отменить всё или продолжить?
            }
        }
    }
    
    // ... остаток реализации
}

Версия: 1.0