Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/class-registration.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Use when:
Registers an already-constructed instance. No source generator required.

```csharp
services.AddSingleton(new AppConfig { Width = 1280, Height = 720 });
services.AddSingleton(new WindowOptions(Size: (1280, 720), Title: "Game"));
services.AddSingleton<ILogger>(new ConsoleLogger());
```

Expand Down Expand Up @@ -360,15 +360,15 @@ A child provider flattens the parent's singleton service array and registration

```csharp
ServiceCollection rootCollection = new();
rootCollection.AddSingleton(new AppConfig());
rootCollection.AddSingleton(new GameKitConfig());
ServiceProvider root = rootCollection.BuildServiceProvider();

ServiceCollection stageCollection = root.CreateServiceCollection();
stageCollection.AddSingleton<IView>(new GameplayView());
ServiceProvider stage = stageCollection.BuildServiceProvider();

// stage can resolve both its own and parent services
AppConfig config = stage.GetRequiredService<AppConfig>();
GameKitConfig config = stage.GetRequiredService<GameKitConfig>();
IView view = stage.GetRequiredService<IView>();
```

Expand Down
4 changes: 2 additions & 2 deletions docs/subrenderers.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
Subrenderers are renderers that receive an existing RenderPass instead of creating their own. They're used for internal composition within an `IRenderPhase<T>`.

```
DefaultRenderManager<T>
DefaultRenderCoordinator<T>
└─ IRenderPhase<T>[] (geometry, lighting, post-process phases)
└─ Subrenderers (multiple renderers sharing the same RenderPass)
```
Expand Down Expand Up @@ -129,7 +129,7 @@ public class GeometryPhase : IRenderPhase<GameRenderContext>
}
```

**Note:** `IRenderPhase<T>` is managed by `DefaultRenderManager<T>`, which orchestrates multiple phases (geometry, lighting, post-process) in order.
**Note:** `IRenderPhase<T>` is coordinated by `DefaultRenderCoordinator<T>`, which executes multiple phases (geometry, lighting, post-process) in order.

## Key Points

Expand Down
7 changes: 1 addition & 6 deletions src/GameKit.Pencuil/Pencil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,11 @@ public void UpdateCursor(Vector2Int position, bool pressed)
internal bool FocusClaimedThisFrame;
internal TextFieldEditingState? EditingState;

public Pencil(IFontSystem fontSystem, IClipboardService clipboardService, GuiStyle guiStyle, AppConfig appConfig)
public Pencil(IFontSystem fontSystem, IClipboardService clipboardService, GuiStyle guiStyle)
{
_fontSystem = fontSystem;
_clipboardService = clipboardService;
Style = guiStyle;
if (appConfig.Size is { } size)
{
_viewportWidth = (int)size.Width;
_viewportHeight = (int)size.Height;
}
}

public void AddHoverTest(Rectangle test)
Expand Down
2 changes: 1 addition & 1 deletion src/GameKit.Pencuil/PencilSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public PencilSystem(Pencil pencil, ViewRegistry viewRegistry, Window window, IMo

public void Update()
{
ShortSize renderSize = _window.RenderSizeInPixels;
ShortSize renderSize = _window.RequireActivation().RenderSizeInPixels;
_pencil.UpdateViewport(renderSize.Width, renderSize.Height);

bool needsBuild = _pencil.NeedsUpdate | _viewRegistry.ConsumeDirty();
Expand Down
8 changes: 5 additions & 3 deletions src/GameKit.Pencuil/PencuilRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@ public class PencuilRenderer

private int _maxDepthValue;

// TODO: This constructor performs substantial GPU resource creation and may become a static factory method.
public PencuilRenderer(
GraphicsPipelineBuilder graphicsPipelineBuilder,
GpuMemorySystem gpuMemorySystem,
ShaderLoader shaderLoader,
GpuDevice gpuDevice,
WindowManager windowManager)
Window window)
{
ReadOnlySpan<PositionTextureVertex> quad =
[
Expand All @@ -54,8 +55,9 @@ public PencuilRenderer(
FragmentShader tintedTextureFragmentShader = shaderLoader.LoadFragmentShader("shaders/pencuil_tinted_texture_fragment");
FragmentShader textureFragmentShader = shaderLoader.LoadFragmentShader("shaders/pencuil_texture_fragment");

TextureFormat colorTargetFormat = windowManager.PrimaryWindow.ColorTargetFormat;
ShortSize renderSize = windowManager.PrimaryWindow.RenderSizeInPixels;
ActivationWindow activation = window.RequireActivation();
TextureFormat colorTargetFormat = activation.ColorTargetFormat;
ShortSize renderSize = activation.RenderSizeInPixels;

_colorPipeline = graphicsPipelineBuilder
.SetPrimitiveType(PrimitiveType.TriangleStrip)
Expand Down
10 changes: 6 additions & 4 deletions src/GameKit.RenderOrchestration/DefaultRenderContextProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@ namespace GameKit.RenderOrchestration;
/// </summary>
public class DefaultRenderContextProvider : IRenderContextProvider<DefaultRenderContext>
{
private readonly WindowManager _windowManager;
private readonly Window _window;
private readonly GpuDevice _gpuDevice;

public DefaultRenderContextProvider(WindowManager windowManager, GpuDevice gpuDevice)
public DefaultRenderContextProvider(Window window, GpuDevice gpuDevice)
{
_windowManager = windowManager;
_window = window;
_gpuDevice = gpuDevice;
}

Expand All @@ -27,7 +27,9 @@ public bool TryProvide([NotNullWhen(true)] out DefaultRenderContext? renderConte
{
CommandBuffer renderCommandBuffer = _gpuDevice.AcquireCommandBuffer();

if (!_windowManager.PrimaryWindow.TryWaitAndAcquireSwapchainTexture(renderCommandBuffer, out SwapchainTexture swapchainTexture))
ActivationWindow? activation = _window.Activation;
if (activation == null ||
!activation.TryWaitAndAcquireSwapchainTexture(renderCommandBuffer, out SwapchainTexture swapchainTexture))
{
renderContext = null;
renderCommandBuffer.Dispose();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@
namespace GameKit.RenderOrchestration;

/// <summary>
/// Manages the overall rendering process by coordinating multiple render phases.
/// Coordinates rendering across multiple render phases.
/// </summary>
/// <typeparam name="TRenderContext">The type of the render context used by the render phases.</typeparam>
public class DefaultRenderManager<TRenderContext> : IRenderManager
public class DefaultRenderCoordinator<TRenderContext> : RenderCoordinator
where TRenderContext: IRenderContext
{
private readonly GpuMemorySystem _gpuMemorySystem;
private readonly IRenderContextProvider<TRenderContext> _renderContextProvider;
private readonly RenderPhaseRegistry<TRenderContext> _renderPhaseRegistry;

internal DefaultRenderManager(
internal DefaultRenderCoordinator(
Window window,
GpuMemorySystem gpuMemorySystem,
IRenderContextProvider<TRenderContext> renderContextProvider,
RenderPhaseRegistry<TRenderContext> renderPhaseRegistry)
: base(window)
{
_gpuMemorySystem = gpuMemorySystem;
_renderContextProvider = renderContextProvider;
Expand All @@ -27,7 +29,7 @@ internal DefaultRenderManager(
/// <summary>
/// Executes the rendering pipeline for a single frame.
/// </summary>
public void Execute()
public override void Execute()
{
if (!_renderContextProvider.TryProvide(out TRenderContext? renderContext))
{
Expand All @@ -37,7 +39,7 @@ public void Execute()
using (renderContext)
{
_renderPhaseRegistry.Render(renderContext);

// submit all pending changes before renderContext is disposed
_gpuMemorySystem.Submit();
}
Expand Down
58 changes: 44 additions & 14 deletions src/GameKit.RenderOrchestration/GameKitAppBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,63 @@ namespace GameKit.RenderOrchestration;

public static class GameKitAppBuilderExtensions
{
public static GameKitAppBuilder UseDefaultRenderManager<TRenderContext>(this GameKitAppBuilder builder) where TRenderContext: IRenderContext
public static GameKitAppBuilder UseDefaultRenderCoordinator<TRenderContext>(
this GameKitAppBuilder builder)
where TRenderContext : IRenderContext
{
ConfigureDefaultRenderCoordinator<TRenderContext>(builder);
return builder;
}

public static ServiceCollection UseDefaultRenderCoordinator<TRenderContext>(
this ServiceCollection services)
where TRenderContext : IRenderContext
{
ConfigureDefaultRenderCoordinator<TRenderContext>(services);
return services;
}

public static GameKitAppBuilder UseDefaultRenderCoordinator(this GameKitAppBuilder builder)
{
ConfigureDefaultRenderContext(builder);
return builder;
}

public static ServiceCollection UseDefaultRenderCoordinator(this ServiceCollection services)
{
ConfigureDefaultRenderContext(services);
return services;
}

private static void ConfigureDefaultRenderContext(ServiceCollection services)
{
services.AddSingleton<IRenderContextProvider<DefaultRenderContext>, DefaultRenderContextProvider>();
ConfigureDefaultRenderCoordinator<DefaultRenderContext>(services);
}

private static void ConfigureDefaultRenderCoordinator<TRenderContext>(ServiceCollection services)
where TRenderContext : IRenderContext
{
RenderPhaseRegistry<TRenderContext> renderPhaseRegistry = new();
builder.OnActivated((instance, _) =>
services.OnActivated((instance, _) =>
{
if (instance is IRenderPhase<TRenderContext> renderPhase)
{
renderPhaseRegistry.Register(renderPhase);
}
});
builder.OnDisposing((instance, _) =>
services.OnDisposing((instance, _) =>
{
if (instance is IRenderPhase<TRenderContext> renderPhase)
{
renderPhaseRegistry.Unregister(renderPhase);
}
});
builder.AddSingleton<IRenderManager>(sp => new DefaultRenderManager<TRenderContext>(
sp.GetRequiredService<GpuMemorySystem>(),
sp.GetRequiredService<IRenderContextProvider<TRenderContext>>(),
renderPhaseRegistry));
return builder;
}

public static GameKitAppBuilder UseDefaultRenderManager(this GameKitAppBuilder builder)
{
builder.AddSingleton<IRenderContextProvider<DefaultRenderContext>, DefaultRenderContextProvider>();
return builder.UseDefaultRenderManager<DefaultRenderContext>();
services.AddSingleton<RenderCoordinator>(provider =>
new DefaultRenderCoordinator<TRenderContext>(
provider.GetRequiredService<Window>(),
provider.GetRequiredService<GpuMemorySystem>(),
provider.GetRequiredService<IRenderContextProvider<TRenderContext>>(),
renderPhaseRegistry));
}
}
5 changes: 3 additions & 2 deletions src/GameKit.RenderOrchestration/IRenderContextProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ namespace GameKit.RenderOrchestration;
/// Defines a provider that creates and supplies a render context for a single frame.
/// </summary>
/// <typeparam name="TRenderContext">The type of the render context to provide.</typeparam>
public interface IRenderContextProvider<TRenderContext> where TRenderContext: IRenderContext
public interface IRenderContextProvider<TRenderContext>
where TRenderContext : IRenderContext
{
/// <summary>
/// Attempts to create and provide a render context.
/// </summary>
/// <param name="renderContext">When this method returns, contains the created render context, or null if creation failed.</param>
/// <returns>True if the render context was successfully provided, false otherwise.</returns>
public bool TryProvide([NotNullWhen(true)] out TRenderContext? renderContext);
}
}
7 changes: 6 additions & 1 deletion src/GameKit.RenderOrchestration/IRenderPhase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ namespace GameKit.RenderOrchestration;
/// culling, shadow map generation, deferred shading (e.g., lighting, ambient occlusion),
/// post-processing, and UI rendering.
/// </summary>
/// <remarks>
/// Register a phase in the same service container as its default render coordinator or in a descendant
/// container. Phase discovery follows service activation: a phase already activated in an ancestor
/// is not retroactively attached to a render coordinator created by a child container.
/// </remarks>
/// <typeparam name="TRenderContext">The type of the render context required by this phase.</typeparam>
public interface IRenderPhase<in TRenderContext>: IOrderable
public interface IRenderPhase<in TRenderContext> : IOrderable
{
/// <summary>
/// Executes the rendering logic for this phase.
Expand Down
4 changes: 2 additions & 2 deletions src/GameKit.Utils/OrthographicCameraFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ public static class OrthographicCameraFactory
{
public static Camera Create(Window window, IViewConfiguration viewConfiguration)
{
ShortSize windowSize = window.RenderSizeInPixels;
ShortSize windowSize = window.RequireActivation().RenderSizeInPixels;

float width = windowSize.Width / viewConfiguration.PixelsPerUnit;
float height = windowSize.Height / viewConfiguration.PixelsPerUnit;
Expand All @@ -19,4 +19,4 @@ public static Camera Create(Window window, IViewConfiguration viewConfiguration)
FarPlane = 1000f
};
}
}
}
102 changes: 102 additions & 0 deletions src/GameKit/ActivationWindow.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using GameKit.Gpu;
using GameKit.Utilities;
using SDL;

namespace GameKit;

public sealed class ActivationWindow : IDisposable
{
internal Pointer<SDL_GPUDevice> SdlGpuDevice { get; }
internal Pointer<SDL_Window> SdlWindow { get; private set; }

public uint Id { get; }

internal ActivationWindow(
Pointer<SDL_Window> sdlWindow,
Pointer<SDL_GPUDevice> sdlGpuDevice,
uint id)
{
SdlWindow = sdlWindow;
SdlGpuDevice = sdlGpuDevice;
Id = id;
}

public ShortSize RenderSizeInPixels
{
get
{
int width;
int height;
unsafe
{
SDL3.SDL_GetWindowSizeInPixels(SdlWindow, &width, &height);
}

return new ShortSize((ushort)width, (ushort)height);
}
}

public TextureFormat ColorTargetFormat
{
get
{
unsafe
{
return (TextureFormat)SDL3.SDL_GetGPUSwapchainTextureFormat(SdlGpuDevice, SdlWindow);
}
}
}

public bool TryWaitAndAcquireSwapchainTexture(
CommandBuffer commandBuffer,
out SwapchainTexture swapchainTexture)
{
swapchainTexture = default!;
uint width;
uint height;

unsafe
{
SDL_GPUTexture* swapchainTexturePointer;
if (!SDL3.SDL_WaitAndAcquireGPUSwapchainTexture(
commandBuffer.SdlGpuCommandBuffer,
SdlWindow,
&swapchainTexturePointer,
&width,
&height))
{
throw new GameKitException(
$"SDL_WaitAndAcquireGPUSwapchainTexture failed: {SDL3.SDL_GetError()}");
}

if (swapchainTexturePointer == null)
{
return false;
}

TextureFormat textureFormat =
(TextureFormat)SDL3.SDL_GetGPUSwapchainTextureFormat(SdlGpuDevice, SdlWindow);
swapchainTexture = new SwapchainTexture(
swapchainTexturePointer,
new ShortSize((ushort)width, (ushort)height),
textureFormat);
}

return true;
}

public void Dispose()
{
if (SdlWindow.IsNull)
{
return;
}

unsafe
{
SDL3.SDL_ReleaseWindowFromGPUDevice(SdlGpuDevice, SdlWindow);
SDL3.SDL_DestroyWindow(SdlWindow);
SdlWindow = Pointer<SDL_Window>.Null;
}
}
}
Loading
Loading