From 7e59589f375a7373e71f8f21c1b3f498df53f97a Mon Sep 17 00:00:00 2001 From: Eugene Ivchenko Date: Tue, 30 Aug 2022 03:45:18 +0300 Subject: [PATCH] Properly reimplement taking screenshots --- NitroSharp.sln.DotSettings | 1 + .../VM/BuiltInFunctionDispatcher.cs | 8 + .../VM/BuiltInFunctions.cs | 2 + src/NitroSharp/Builtins.Graphics.cs | 17 +- src/NitroSharp/Content/ContentManager.cs | 7 +- src/NitroSharp/Entity.cs | 2 +- src/NitroSharp/GameContext.cs | 57 ++++--- src/NitroSharp/Graphics/Core/PooledTexture.cs | 23 +++ src/NitroSharp/Graphics/DrawBatch.cs | 19 ++- src/NitroSharp/Graphics/Icon.cs | 4 +- src/NitroSharp/Graphics/RenderContext.cs | 161 +++++++++--------- src/NitroSharp/Graphics/RenderItem.cs | 82 ++++----- .../Graphics/RenderItems/BacklogView.cs | 4 +- src/NitroSharp/Graphics/RenderItems/Cube.cs | 7 +- .../Graphics/RenderItems/DialoguePage.cs | 4 +- .../Graphics/RenderItems/Scrollbar.cs | 4 +- src/NitroSharp/Graphics/RenderItems/Sprite.cs | 105 +++++++----- .../Graphics/RenderItems/TextBlock.cs | 2 +- src/NitroSharp/Graphics/RenderItems/Video.cs | 12 +- src/NitroSharp/ResourcePool.cs | 53 ++++++ src/NitroSharp/Saving/GameSaveManager.cs | 21 ++- src/NitroSharp/Text/GlyphRasterizer.cs | 2 +- 22 files changed, 372 insertions(+), 225 deletions(-) create mode 100644 src/NitroSharp/Graphics/Core/PooledTexture.cs create mode 100644 src/NitroSharp/ResourcePool.cs diff --git a/NitroSharp.sln.DotSettings b/NitroSharp.sln.DotSettings index 0fa7397a..c7392820 100644 --- a/NitroSharp.sln.DotSettings +++ b/NitroSharp.sln.DotSettings @@ -13,6 +13,7 @@ True True True + True True True True diff --git a/src/NitroSharp.NsScript/VM/BuiltInFunctionDispatcher.cs b/src/NitroSharp.NsScript/VM/BuiltInFunctionDispatcher.cs index 4f8eff77..fe8225d0 100644 --- a/src/NitroSharp.NsScript/VM/BuiltInFunctionDispatcher.cs +++ b/src/NitroSharp.NsScript/VM/BuiltInFunctionDispatcher.cs @@ -162,6 +162,9 @@ private void SetResult(in ConstantValue value) case BuiltInFunction.WaitText: WaitText(ref args); break; + case BuiltInFunction.Draw: + Draw(); + break; case BuiltInFunction.CreateSound: CreateSound(ref args); @@ -311,6 +314,11 @@ private void SetResult(in ConstantValue value) return result; } + private void Draw() + { + _impl.Draw(); + } + private void Shake(ref ArgConsumer args) { EntityQuery query = args.TakeEntityQuery(); diff --git a/src/NitroSharp.NsScript/VM/BuiltInFunctions.cs b/src/NitroSharp.NsScript/VM/BuiltInFunctions.cs index 8663382e..f48f6794 100644 --- a/src/NitroSharp.NsScript/VM/BuiltInFunctions.cs +++ b/src/NitroSharp.NsScript/VM/BuiltInFunctions.cs @@ -171,5 +171,7 @@ public virtual void DeleteSave(uint slot) { } public virtual float X360_GetTriggerAxis(XboxTrigger trigger) => 0; public virtual void Reset() { } + + public virtual void Draw() { } } } diff --git a/src/NitroSharp/Builtins.Graphics.cs b/src/NitroSharp/Builtins.Graphics.cs index 7edb29d1..2f50c92f 100644 --- a/src/NitroSharp/Builtins.Graphics.cs +++ b/src/NitroSharp/Builtins.Graphics.cs @@ -2,11 +2,11 @@ using System.Collections.Immutable; using System.Numerics; using NitroSharp.Graphics; +using NitroSharp.Graphics.Core; using NitroSharp.NsScript; using NitroSharp.NsScript.Primitives; using NitroSharp.NsScript.VM; using NitroSharp.Text; -using Veldrid; namespace NitroSharp { @@ -225,10 +225,12 @@ public override void CreateSprite( { if (src is "SCREEN" or "Screen" or "VIDEO" or "Video") { - Texture screenshotTexture = _renderCtx.CreateFullscreenTexture(); - var result = SpriteTexture.FromStandalone(screenshotTexture); - _ctx.Defer(DeferredOperation.CaptureFramebuffer(screenshotTexture)); - return result; + GameProcess process = src.Equals("SCREEN", StringComparison.OrdinalIgnoreCase) + ? _ctx.ActiveProcess + : _ctx.MainProcess; + + PooledTexture texture = _ctx.RenderToTexture(process); + return SpriteTexture.FromPooledTexture(texture); } if (_ctx.Content.RequestTexture(src) is { } asset) @@ -396,6 +398,11 @@ public override void WaitText(EntityQuery query, TimeSpan timeout) _ctx.Wait(CurrentThread, WaitCondition.EntityIdle, null, query); } + public override void Draw() + { + Delay(TimeSpan.FromMicroseconds(1)); + } + public override void CreateAlphaMask( in EntityPath entityPath, int priority, diff --git a/src/NitroSharp/Content/ContentManager.cs b/src/NitroSharp/Content/ContentManager.cs index 313b7dd1..08adff34 100644 --- a/src/NitroSharp/Content/ContentManager.cs +++ b/src/NitroSharp/Content/ContentManager.cs @@ -108,10 +108,13 @@ public Texture LoadTexture(string path, bool staging) return _textureLoader.LoadTexture(stream, staging); } + public T? TryGet(AssetRef assetRef) where T : class, IDisposable + => _cache.Get(assetRef.Handle).Asset as T; + public T Get(AssetRef assetRef) where T : class, IDisposable { - if (!(_cache.Get(assetRef.Handle).Asset is T loadedAsset)) + if (_cache.Get(assetRef.Handle).Asset is not T loadedAsset) { throw new InvalidOperationException( $"BUG: asset '{assetRef.Path}' is missing from the cache." @@ -220,7 +223,7 @@ public bool ResolveAssets() } } - public Stream OpenStream(string path) + private Stream OpenStream(string path) { string fsPath = path; if (Path.GetDirectoryName(path) is { } dir && Path.GetFileName(path) is { } filename) diff --git a/src/NitroSharp/Entity.cs b/src/NitroSharp/Entity.cs index 89fa1857..a09e5824 100644 --- a/src/NitroSharp/Entity.cs +++ b/src/NitroSharp/Entity.cs @@ -186,7 +186,7 @@ internal static class MouseStateEntities public static readonly string[] All = { MouseUsual, MouseClick, MouseOver, MouseLeave }; public static bool IsMouseStateEntity(this Entity? entity) - => entity is { Id.Name: MouseUsual or MouseClick or MouseOver or MouseLeave }; + => entity is { Id.Name: MouseUsual or MouseClick or MouseOver or MouseLeave }; } internal interface EntityInternal diff --git a/src/NitroSharp/GameContext.cs b/src/NitroSharp/GameContext.cs index 4ff81d24..f6d46b9c 100644 --- a/src/NitroSharp/GameContext.cs +++ b/src/NitroSharp/GameContext.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using NitroSharp.Content; using NitroSharp.Graphics; +using NitroSharp.Graphics.Core; using NitroSharp.Media; using NitroSharp.NsScript; using NitroSharp.NsScript.Compiler; @@ -31,7 +32,6 @@ internal readonly record struct FrameStamp(long FrameId, long StopwatchTicks) internal enum DeferredOperationKind { - CaptureFramebuffer, SaveGame, LoadGame } @@ -39,27 +39,17 @@ internal enum DeferredOperationKind internal readonly struct DeferredOperation { public DeferredOperationKind Kind { get; private init; } - public Texture? ScreenshotTexture { get; private init; } public uint? SaveSlot { get; private init; } - public static DeferredOperation CaptureFramebuffer(Texture dstTexture) => new() - { - Kind = DeferredOperationKind.CaptureFramebuffer, - ScreenshotTexture = dstTexture, - SaveSlot = null - }; - public static DeferredOperation SaveGame(uint slot) => new() { Kind = DeferredOperationKind.SaveGame, - ScreenshotTexture = null, SaveSlot = slot }; public static DeferredOperation LoadGame(uint slot) => new() { Kind = DeferredOperationKind.LoadGame, - ScreenshotTexture = null, SaveSlot = slot }; } @@ -74,6 +64,7 @@ public sealed class GameContext : IAsyncDisposable private (string, MediaStream?) _activeVoice; private readonly Queue _deferredOperations = new(); private bool _clearFramebuffer; + private FrameStamp _now; private GameContext( Logger logger, @@ -126,7 +117,7 @@ private GameContext( internal bool Skipping { get; private set; } internal bool Advance { get; set; } - internal Texture? LastScreenshot { get; private set; } + internal EntityId FocusedUiElement { get; set; } internal NsFocusDirection? RequestedFocusChange { get; set; } @@ -152,7 +143,6 @@ public static async Task Create(GameWindow window, Config config, G var saveManager = new GameSaveManager(profile); var renderContext = new RenderContext( window, - config, profile, gd, swapchain, @@ -473,6 +463,7 @@ private void MainLoop() // necessary or until the engine is both feature-complete and stable. private void Tick(FrameStamp framestamp, float dt) { + _now = framestamp; RenderContext.BeginFrame(framestamp, _clearFramebuffer); _clearFramebuffer = true; InputContext.Update(VM.SystemVariables); @@ -518,15 +509,21 @@ private void Tick(FrameStamp framestamp, float dt) ProcessSounds(world, dt); RenderFrame(framestamp, world.RenderItems, dt, assetsReady); HandleInput(); - RunDeferredOperations(); if (assetsReady) { ActiveProcess.ProcessWaitOperations(this); } + + if (assetsReady) + { + RunDeferredOperations(); + } + + RenderContext.EndFrame(); + try { - RenderContext.EndFrame(); RenderContext.Present(); } catch (VeldridException e) @@ -556,7 +553,7 @@ private void RenderFrame( foreach (RenderItem ri in active) { - ri.Render(RenderContext, assetsReady); + ri.Render(RenderContext); } RenderContext.TextureCache.BeginFrame(frameStamp); @@ -626,8 +623,7 @@ private void CreateSysProcess(string mainModule) //_context.SysProcess?.Dispose(); if (SysProcess is null) { - LastScreenshot ??= RenderContext.CreateFullscreenTexture(staging: true); - RenderContext.CaptureFramebuffer(LastScreenshot); + //LastScreenshot ??= TakeScreenshot(MainProcess); MainProcess.VmProcess.Suspend(); SysProcess = CreateProcess(VM, mainModule, _fontSettings); _clearFramebuffer = false; @@ -641,10 +637,6 @@ private void RunDeferredOperations() { switch (op.Kind) { - case DeferredOperationKind.CaptureFramebuffer: - Debug.Assert(op.ScreenshotTexture is not null); - RenderContext.CaptureFramebuffer(op.ScreenshotTexture); - break; case DeferredOperationKind.SaveGame: Debug.Assert(op.SaveSlot is not null); SaveManager.Save(this, op.SaveSlot.Value); @@ -662,6 +654,27 @@ private void RunDeferredOperations() } } + internal PooledTexture RenderToTexture(GameProcess process) + { + return RenderContext.RenderToTexture(batch => + { + ReadOnlySpan activeItems = process.World.RenderItems.SortActive(); + foreach (RenderItem renderItem in activeItems) + { + renderItem.PerformLayout(this); + } + + RenderContext.ResolveGlyphs(); + + foreach (RenderItem renderItem in activeItems) + { + renderItem.Render(RenderContext, batch); + } + + RenderContext.TextureCache.BeginFrame(_now); + }); + } + internal void Defer(in DeferredOperation operation) { ActiveProcess.VmProcess.Suspend(); diff --git a/src/NitroSharp/Graphics/Core/PooledTexture.cs b/src/NitroSharp/Graphics/Core/PooledTexture.cs new file mode 100644 index 00000000..ab515f65 --- /dev/null +++ b/src/NitroSharp/Graphics/Core/PooledTexture.cs @@ -0,0 +1,23 @@ +using System; +using Veldrid; + +namespace NitroSharp.Graphics.Core; + +internal readonly struct PooledTexture : IDisposable +{ + private readonly ResourcePool _pool; + private readonly Texture _texture; + + public PooledTexture(ResourcePool pool, Texture texture) + { + _pool = pool; + _texture = texture; + } + + public Texture Get() => _texture; + + public void Dispose() + { + _pool.Return(_texture); + } +} diff --git a/src/NitroSharp/Graphics/DrawBatch.cs b/src/NitroSharp/Graphics/DrawBatch.cs index f311e74f..0328ec4b 100644 --- a/src/NitroSharp/Graphics/DrawBatch.cs +++ b/src/NitroSharp/Graphics/DrawBatch.cs @@ -173,7 +173,7 @@ internal sealed class DrawBatch : IDisposable { private readonly RenderContext _ctx; private CommandList? _commandList; - + private bool _began; private Draw _lastDraw; private Vector2 _lastAlphaMaskPosition = new(float.NaN); @@ -187,6 +187,7 @@ public DrawBatch(RenderContext context) public void Begin(CommandList commandList, RenderTarget target, RgbaFloat? clearColor) { + Debug.Assert(!_began); _commandList = commandList; commandList.SetFramebuffer(target.Framebuffer); Target = target; @@ -194,6 +195,8 @@ public void Begin(CommandList commandList, RenderTarget target, RgbaFloat? clear { commandList.ClearColorTarget(0, clear); } + + _began = true; } public void UpdateBuffer(GpuBuffer buffer, in T data) @@ -218,7 +221,7 @@ public void PushQuad( QuadShaderResources resources = _ctx.ShaderResources.Quad; if (alphaMaskPosition != _lastAlphaMaskPosition) { - Vector4 newValue = new Vector4(alphaMaskPosition, 0, 0); + var newValue = new Vector4(alphaMaskPosition, 0, 0); UpdateBuffer(resources.AlphaMaskPositionBuffer, newValue); _lastAlphaMaskPosition = alphaMaskPosition; } @@ -357,7 +360,15 @@ void setResources(uint slot, ResourceSetKey? rsKeyOpt) _lastDraw = default; } - public void End() => Flush(); - public void Dispose() => Flush(); + public void End() + { + Flush(); + _began = false; + } + + public void Dispose() + { + End(); + } } } diff --git a/src/NitroSharp/Graphics/Icon.cs b/src/NitroSharp/Graphics/Icon.cs index 3449fb95..be839732 100644 --- a/src/NitroSharp/Graphics/Icon.cs +++ b/src/NitroSharp/Graphics/Icon.cs @@ -56,7 +56,7 @@ public static Icon Load(RenderContext renderContext, IconPathPattern pathPattern ContentManager content = renderContext.Content; ResourceFactory rf = renderContext.ResourceFactory; Texture? texture = null; - CommandList cl = renderContext.RentCommandList(); + CommandList cl = renderContext.CommandListPool.Rent(); cl.Begin(); uint layer = 0; foreach (string path in pathPattern.EnumeratePaths()) @@ -80,7 +80,7 @@ public static Icon Load(RenderContext renderContext, IconPathPattern pathPattern } cl.End(); renderContext.GraphicsDevice.SubmitCommands(cl); - renderContext.ReturnCommandList(cl); + renderContext.CommandListPool.Return(cl); Debug.Assert(texture is not null); return new Icon(texture); diff --git a/src/NitroSharp/Graphics/RenderContext.cs b/src/NitroSharp/Graphics/RenderContext.cs index d9c23977..519aa3fb 100644 --- a/src/NitroSharp/Graphics/RenderContext.cs +++ b/src/NitroSharp/Graphics/RenderContext.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Numerics; using NitroSharp.Content; using NitroSharp.Graphics.Core; @@ -29,19 +28,16 @@ internal sealed class RenderContext : IDisposable private readonly CommandList _drawCommands; private readonly CommandList _secondaryCommandList; - private readonly Queue _commandListPool = new(); + private readonly ResourcePool _commandListPool; - private readonly RenderTarget _swapchainTarget; private readonly Swapchain _mainSwapchain; - + private readonly RenderTarget _swapchainTarget; + private readonly RenderTarget _offscreenTarget; + private readonly ResourcePool _offscreenTexturePool; private readonly DrawBatch _offscreenBatch; - public ViewProjection OrthoProjection { get; } - public ViewProjection PerspectiveViewProjection { get; } - public RenderContext( GameWindow window, - Config config, GameProfile gameProfile, GraphicsDevice graphicsDevice, Swapchain swapchain, @@ -50,13 +46,22 @@ public RenderContext( SystemVariableLookup systemVariables) { DesignResolution = gameProfile.DesignResolution; - Window = window; GraphicsDevice = graphicsDevice; ResourceFactory = graphicsDevice.ResourceFactory; - + Content = contentManager; + GlyphRasterizer = glyphRasterizer; + SystemVariables = systemVariables; _mainSwapchain = swapchain; + _shaderLibrary = new ShaderLibrary(graphicsDevice); + _swapchainTarget = RenderTarget.Swapchain(graphicsDevice, swapchain.Framebuffer); + _offscreenTarget = new RenderTarget(graphicsDevice, _swapchainTarget.Size); + _offscreenTexturePool = new ResourcePool( + CreateOffscreenTexture, + x => x.Dispose(), + initialSize: 4 + ); TransferCommands = ResourceFactory.CreateCommandList(); TransferCommands.Name = "Transfer commands"; @@ -64,16 +69,16 @@ public RenderContext( _drawCommands.Name = "Draw commands (primary)"; _secondaryCommandList = ResourceFactory.CreateCommandList(); _secondaryCommandList.Name = "Secondary"; - Content = contentManager; - GlyphRasterizer = glyphRasterizer; - SystemVariables = systemVariables; - _shaderLibrary = new ShaderLibrary(graphicsDevice); + _commandListPool = new ResourcePool( + ResourceFactory.CreateCommandList, + static x => x.Dispose(), + initialSize: 2 + ); OrthoProjection = ViewProjection.CreateOrtho( graphicsDevice, new RectangleF(Vector2.Zero, DesignResolution) ); - var view = Matrix4x4.CreateLookAt(Vector3.Zero, Vector3.UnitZ, Vector3.UnitY); var projection = Matrix4x4.CreatePerspectiveFieldOfView( MathF.PI / 3.0f, @@ -124,6 +129,8 @@ public RenderContext( public GameWindow Window { get; } public Size DesignResolution { get; } + public ViewProjection OrthoProjection { get; } + public ViewProjection PerspectiveViewProjection { get; } public MeshList Quads { get; } public MeshList QuadsUV3 { get; } public MeshList Cubes { get; } @@ -132,6 +139,7 @@ public RenderContext( public ResourceFactory ResourceFactory { get; } public CommandList TransferCommands { get; } + public ref readonly ResourcePool CommandListPool => ref _commandListPool; public ContentManager Content { get; } public GlyphRasterizer GlyphRasterizer { get; } @@ -149,25 +157,51 @@ public RenderContext( public SystemVariableLookup SystemVariables { get; } - public CommandList RentCommandList() + private Texture CreateWhiteTexture() { - if (!_commandListPool.TryDequeue(out CommandList? cl)) - { - cl = ResourceFactory.CreateCommandList(); - } + var textureDesc = TextureDescription.Texture2D( + width: 1, height: 1, mipLevels: 1, arrayLayers: 1, + PixelFormat.R8_G8_B8_A8_UNorm, TextureUsage.Staging + ); + Texture stagingWhite = ResourceFactory.CreateTexture(ref textureDesc); + MappedResourceView pixels = GraphicsDevice.Map( + stagingWhite, MapMode.Write + ); + pixels[0] = RgbaByte.White; + GraphicsDevice.Unmap(stagingWhite); - return cl; + textureDesc.Usage = TextureUsage.Sampled; + Texture texture = ResourceFactory.CreateTexture(ref textureDesc); + + TransferCommands.Begin(); + TransferCommands.CopyTexture(stagingWhite, texture); + TransferCommands.End(); + GraphicsDevice.SubmitCommands(TransferCommands); + stagingWhite.Dispose(); + return texture; } - public void ReturnCommandList(CommandList cl) + private Texture CreateOffscreenTexture() { - _commandListPool.Enqueue(cl); + Size size = _swapchainTarget.Size; + var desc = TextureDescription.Texture2D( + size.Width, size.Height, + mipLevels: 1, arrayLayers: 1, + PixelFormat.B8_G8_R8_A8_UNorm, + TextureUsage.Sampled + ); + return ResourceFactory.CreateTexture(ref desc); } - public DrawBatch BeginBatch(RenderTarget renderTarget, RgbaFloat? clearColor) + private AnimatedIcons LoadIcons(GameProfile gameProfile) { - _offscreenBatch.Begin(_secondaryCommandList, renderTarget, clearColor); - return _offscreenBatch; + CommandList cl = _commandListPool.Rent(); + cl.Begin(); + var waitLine = Icon.Load(this, gameProfile.IconPathPatterns.WaitLine); + cl.End(); + GraphicsDevice.SubmitCommands(cl); + _commandListPool.Return(cl); + return new AnimatedIcons(waitLine); } public void BeginFrame(in FrameStamp frameStamp, bool clear) @@ -187,44 +221,34 @@ public void BeginFrame(in FrameStamp frameStamp, bool clear) TransferCommands.Begin(); } - private AnimatedIcons LoadIcons(GameProfile gameProfile) + public DrawBatch BeginOffscreenBatch(RenderTarget renderTarget, RgbaFloat? clearColor) { - CommandList cl = RentCommandList(); - cl.Begin(); - var waitLine = Icon.Load(this, gameProfile.IconPathPatterns.WaitLine); - cl.End(); - GraphicsDevice.SubmitCommands(cl); - ReturnCommandList(cl); - return new AnimatedIcons(waitLine); + _offscreenBatch.Begin(_secondaryCommandList, renderTarget, clearColor); + return _offscreenBatch; } - public void ResolveGlyphs() + public PooledTexture RenderToTexture(Action renderFunc) { - Text.ResolveGlyphs(); - TextureCache.EndFrame(TransferCommands); - } + using (DrawBatch batch = BeginOffscreenBatch(_offscreenTarget, RgbaFloat.Black)) + { + renderFunc(batch); + } - public Texture CreateFullscreenTexture(bool staging = false) - { - Size size = _swapchainTarget.Size; - var desc = TextureDescription.Texture2D( - size.Width, size.Height, - mipLevels: 1, arrayLayers: 1, - PixelFormat.B8_G8_R8_A8_UNorm, - staging ? TextureUsage.Staging : TextureUsage.Sampled - ); - return ResourceFactory.CreateTexture(ref desc); + Texture destination = _offscreenTexturePool.Rent(); + _secondaryCommandList.CopyTexture(source: _offscreenTarget.ColorTarget, destination); + return new PooledTexture(_offscreenTexturePool, destination); } - public void CaptureFramebuffer(Texture dstTexture) + public void ResolveGlyphs() { - _drawCommands.CopyTexture(_swapchainTarget.ColorTarget, dstTexture); + Text.ResolveGlyphs(); + TextureCache.EndFrame(TransferCommands); } public Texture ReadbackTexture(CommandList cl, Texture texture) { Texture staging = ResourceFactory.CreateTexture(TextureDescription.Texture2D( - texture.Width, texture.Height, texture.MipLevels, texture.ArrayLayers, + texture.Width, texture.Height,texture.MipLevels, texture.ArrayLayers, texture.Format, TextureUsage.Staging )); @@ -258,38 +282,12 @@ public void Present() } public Sampler GetSampler(FilterMode filterMode) - { - return filterMode switch + => filterMode switch { FilterMode.Linear => GraphicsDevice.LinearSampler, FilterMode.Point => GraphicsDevice.PointSampler, _ => ThrowHelper.Unreachable() }; - } - - private Texture CreateWhiteTexture() - { - var textureDesc = TextureDescription.Texture2D( - width: 1, height: 1, mipLevels: 1, arrayLayers: 1, - PixelFormat.R8_G8_B8_A8_UNorm, TextureUsage.Staging - ); - Texture stagingWhite = ResourceFactory.CreateTexture(ref textureDesc); - MappedResourceView pixels = GraphicsDevice.Map( - stagingWhite, MapMode.Write - ); - pixels[0] = RgbaByte.White; - GraphicsDevice.Unmap(stagingWhite); - - textureDesc.Usage = TextureUsage.Sampled; - Texture texture = ResourceFactory.CreateTexture(ref textureDesc); - - TransferCommands.Begin(); - TransferCommands.CopyTexture(stagingWhite, texture); - TransferCommands.End(); - GraphicsDevice.SubmitCommands(TransferCommands); - stagingWhite.Dispose(); - return texture; - } public void Dispose() { @@ -299,10 +297,7 @@ public void Dispose() TransferCommands.Dispose(); _drawCommands.Dispose(); _secondaryCommandList.Dispose(); - while (_commandListPool.TryDequeue(out CommandList? cl)) - { - cl.Dispose(); - } + _commandListPool.Dispose(); ShaderResources.Dispose(); WhiteTexture.Dispose(); Text.Dispose(); @@ -311,6 +306,8 @@ public void Dispose() TextureCache.Dispose(); ResourceSetCache.Dispose(); _swapchainTarget.Dispose(); + _offscreenTarget.Dispose(); + _offscreenTexturePool.Dispose(); _shaderLibrary.Dispose(); _mainSwapchain.Dispose(); GraphicsDevice.Dispose(); diff --git a/src/NitroSharp/Graphics/RenderItem.cs b/src/NitroSharp/Graphics/RenderItem.cs index 30aa801b..56800db1 100644 --- a/src/NitroSharp/Graphics/RenderItem.cs +++ b/src/NitroSharp/Graphics/RenderItem.cs @@ -115,18 +115,27 @@ public void Update(GameContext ctx, float dt, bool assetsReady) { AdvanceAnimations(ctx.RenderContext, dt, assetsReady); Update(ctx); - LayoutPass(ctx); + PerformLayout(ctx); } protected virtual void Update(GameContext ctx) { } - protected virtual void LayoutPass(GameContext ctx) + public virtual void PerformLayout(GameContext ctx) { } - public virtual void Render(RenderContext ctx, bool assetsReady) + public void Render(RenderContext ctx) + { + Render(ctx, ctx.MainBatch); + } + + public virtual void Render(RenderContext ctx, DrawBatch drawBatch) + { + } + + protected virtual void RenderCore(RenderContext ctx, DrawBatch drawBatch) { } @@ -284,18 +293,18 @@ protected override void AdvanceAnimations(RenderContext ctx, float dt, bool asse AdvanceAnimation(ref _bezierMoveAnim, dt); } - protected override void LayoutPass(GameContext ctx) + public override void PerformLayout(GameContext ctx) { - base.LayoutPass(ctx); + base.PerformLayout(ctx); if (Parent is ConstraintBox || TryGetOwningChoice() is { Parent: ConstraintBox }) { return; } - Layout(ctx, constraintRect: null); + PerformLayout(ctx, constraintRect: null); } - private void Layout(GameContext ctx, RectangleF? constraintRect) + private void PerformLayout(GameContext ctx, RectangleF? constraintRect) { Size unconstrainedBounds = GetUnconstrainedBounds(ctx.RenderContext); WorldMatrix = Transform.GetMatrix(unconstrainedBounds); @@ -319,23 +328,23 @@ private void Layout(GameContext ctx, RectangleF? constraintRect) { if (child is RenderItem2D renderItem) { - renderItem.Layout(ctx, constraintRect); + renderItem.PerformLayout(ctx, constraintRect); } else if (child.UiElement is Choice choice) { if (choice.TryGetMouseUsualVisual(world) is { } mouseUsual) { - mouseUsual.Layout(ctx, constraintRect); + mouseUsual.PerformLayout(ctx, constraintRect); } foreach (RenderItem2D mouseOver in choice.QueryMouseOverVisuals(world)) { - mouseOver.Layout(ctx, constraintRect); + mouseOver.PerformLayout(ctx, constraintRect); } foreach (RenderItem2D mouseClick in choice.QueryMouseClickVisuals(world)) { - mouseClick.Layout(ctx, constraintRect); + mouseClick.PerformLayout(ctx, constraintRect); } } } @@ -351,14 +360,14 @@ public bool HitTest(RenderContext ctx, InputContext input) private bool PixelPerfectHitTest(RenderContext ctx, RenderTarget offscreenTarget, InputContext input) { _fence ??= ctx.ResourceFactory.CreateFence(signaled: false); - CommandList cl = ctx.RentCommandList(); + CommandList cl = ctx.CommandListPool.Rent(); cl.Begin(); Texture tex = offscreenTarget.ReadBack(cl, ctx.ResourceFactory); cl.End(); ctx.GraphicsDevice.SubmitCommands(cl, _fence); ctx.GraphicsDevice.WaitForFence(_fence); _fence.Reset(); - ctx.ReturnCommandList(cl); + ctx.CommandListPool.Return(cl); MappedResourceView map = ctx.GraphicsDevice.Map(tex, MapMode.Read); Vector2 pos = input.MousePosition - _worldMatrix.Translation.XY(); @@ -372,35 +381,22 @@ private bool PixelPerfectHitTest(RenderContext ctx, RenderTarget offscreenTarget return !pixel.Equals(default); } - public override void Render(RenderContext ctx, bool assetsReady) + public override void Render(RenderContext ctx, DrawBatch drawBatch) { - //if (Color.A > 0.0f) + SizeF actualSize = BoundingRect.Size; + if (actualSize.Width <= 0.0f || actualSize.Height <= 0.0f) { - if (!PreciseHitTest && !IsHidden) - { - Render(ctx, ctx.MainBatch); - } - else - { - SizeF actualSize = BoundingRect.Size; - if (actualSize.Width <= 0.0f || actualSize.Height <= 0.0f) - { - return; - } + return; + } - if (RenderOffscreen(ctx) is not null && !IsHidden) - { - Render(ctx, ctx.MainBatch); - //ctx.MainBatch.PushQuad( - // Quad, - // tex, - // alphaMask: ctx.WhiteTexture, - // Vector2.Zero, - // BlendMode, - // FilterMode - //); - } - } + if (PreciseHitTest && ReferenceEquals(drawBatch, ctx.MainBatch)) + { + RenderOffscreen(ctx); + } + + if (!IsHidden) + { + RenderCore(ctx, drawBatch); } } @@ -413,7 +409,7 @@ protected Texture RenderOffscreen(RenderContext ctx) _offscreenTarget = new RenderTarget(ctx.GraphicsDevice, actualSize); } - using (DrawBatch batch = ctx.BeginBatch(_offscreenTarget, RgbaFloat.Clear)) + using (DrawBatch batch = ctx.BeginOffscreenBatch(_offscreenTarget, RgbaFloat.Clear)) { Matrix4x4 world = _worldMatrix; world.Translation = Vector3.Zero; @@ -427,7 +423,7 @@ protected Texture RenderOffscreen(RenderContext ctx) color: Vector4.One ); - Render(ctx, batch); + RenderCore(ctx, batch); Quad = originalQuad; } @@ -442,10 +438,6 @@ protected Texture RenderOffscreen(RenderContext ctx) return _offscreenTarget.ColorTarget; } - protected virtual void Render(RenderContext ctx, DrawBatch batch) - { - } - public Vector3 Point(RenderContext ctx, NsCoordinate x, NsCoordinate y) { Vector3 pos = Transform.Position; diff --git a/src/NitroSharp/Graphics/RenderItems/BacklogView.cs b/src/NitroSharp/Graphics/RenderItems/BacklogView.cs index 1b5599a6..3ce017fd 100644 --- a/src/NitroSharp/Graphics/RenderItems/BacklogView.cs +++ b/src/NitroSharp/Graphics/RenderItems/BacklogView.cs @@ -91,7 +91,7 @@ protected override void Update(GameContext ctx) ctx.RenderContext.Text.RequestGlyphs(_textLayout, _glyphRun); } - protected override void Render(RenderContext ctx, DrawBatch batch) + protected override void RenderCore(RenderContext ctx, DrawBatch drawBatch) { var offset = new Vector2(0, - _range.start * _lineHeight); @@ -104,7 +104,7 @@ protected override void Render(RenderContext ctx, DrawBatch batch) ); ctx.Text.Render( ctx, - batch, + drawBatch, _textLayout, _glyphRun, WorldMatrix, diff --git a/src/NitroSharp/Graphics/RenderItems/Cube.cs b/src/NitroSharp/Graphics/RenderItems/Cube.cs index da2374e9..6bada65f 100644 --- a/src/NitroSharp/Graphics/RenderItems/Cube.cs +++ b/src/NitroSharp/Graphics/RenderItems/Cube.cs @@ -153,9 +153,8 @@ protected override void Update(GameContext ctx) { } - public override void Render(RenderContext ctx, bool assetsReady) + protected override void RenderCore(RenderContext ctx, DrawBatch drawBatch) { - DrawBatch batch = ctx.MainBatch; CubeShaderResources resources = ctx.ShaderResources.Cube; ViewProjection vp = ctx.PerspectiveViewProjection; @@ -167,8 +166,8 @@ public override void Render(RenderContext ctx, bool assetsReady) } Mesh mesh = ctx.Cubes.Append(vertices); - batch.UpdateBuffer(resources.TransformBuffer, Transform.GetMatrix()); - batch.PushDraw(new Draw + drawBatch.UpdateBuffer(resources.TransformBuffer, Transform.GetMatrix()); + drawBatch.PushDraw(new Draw { Pipeline = resources.Pipeline, ResourceBindings = new ResourceBindings( diff --git a/src/NitroSharp/Graphics/RenderItems/DialoguePage.cs b/src/NitroSharp/Graphics/RenderItems/DialoguePage.cs index 48f73774..26b3286d 100644 --- a/src/NitroSharp/Graphics/RenderItems/DialoguePage.cs +++ b/src/NitroSharp/Graphics/RenderItems/DialoguePage.cs @@ -165,11 +165,11 @@ protected override void Update(GameContext ctx) ctx.RenderContext.Text.RequestGlyphs(_layout); } - protected override void Render(RenderContext ctx, DrawBatch batch) + protected override void RenderCore(RenderContext ctx, DrawBatch drawBatch) { RectangleF br = BoundingRect; var rect = new RectangleU((uint)br.X, (uint)br.Y, (uint)br.Width, (uint)br.Height); - ctx.Text.Render(ctx, batch, _layout, WorldMatrix, _margin.XY(), rect, Color.A); + ctx.Text.Render(ctx, drawBatch, _layout, WorldMatrix, _margin.XY(), rect, Color.A); if (_animation is null && !_skipping) { diff --git a/src/NitroSharp/Graphics/RenderItems/Scrollbar.cs b/src/NitroSharp/Graphics/RenderItems/Scrollbar.cs index 5dc0b4ca..22247de4 100644 --- a/src/NitroSharp/Graphics/RenderItems/Scrollbar.cs +++ b/src/NitroSharp/Graphics/RenderItems/Scrollbar.cs @@ -94,9 +94,9 @@ public bool HandleEvents(GameContext ctx) return held; } - protected override void Render(RenderContext ctx, DrawBatch batch) + protected override void RenderCore(RenderContext ctx, DrawBatch drawBatch) { - batch.PushQuad( + drawBatch.PushQuad( Quad, _knob.Resolve(ctx), ctx.WhiteTexture, diff --git a/src/NitroSharp/Graphics/RenderItems/Sprite.cs b/src/NitroSharp/Graphics/RenderItems/Sprite.cs index 03f08bce..704618a1 100644 --- a/src/NitroSharp/Graphics/RenderItems/Sprite.cs +++ b/src/NitroSharp/Graphics/RenderItems/Sprite.cs @@ -14,28 +14,33 @@ internal enum SpriteTextureKind { SolidColor, Asset, - StandaloneTexture + Pooled, + Standalone } internal readonly struct SpriteTexture : IDisposable { + private readonly AssetRef? _assetRef; + private readonly PooledTexture? _pooledTexture; + private readonly Texture? _standaloneTexture; + public readonly SpriteTextureKind Kind; - public readonly AssetRef? AssetRef; public readonly RectangleU? SourceRectangle; - public readonly Texture? Standalone; public readonly RgbaFloat Color; private SpriteTexture( SpriteTextureKind kind, AssetRef? assetRef, RectangleU? sourceRectangle, - Texture? standalone, + PooledTexture? pooledTexture, + Texture? standaloneTexture, in RgbaFloat color) { Kind = kind; - AssetRef = assetRef; + _assetRef = assetRef; SourceRectangle = sourceRectangle; - Standalone = standalone; + _pooledTexture = pooledTexture; + _standaloneTexture = standaloneTexture; Color = color; } @@ -48,7 +53,8 @@ public static SpriteTexture FromSaveData(in SpriteTextureSaveData saveData, Game SpriteTextureKind.SolidColor, assetRef: null, saveData.SourceRectangle, - standalone: null, + pooledTexture: null, + standaloneTexture: null, new RgbaFloat(saveData.Color) ); case SpriteTextureKind.Asset: @@ -56,10 +62,10 @@ public static SpriteTexture FromSaveData(in SpriteTextureSaveData saveData, Game // TODO: error handling? AssetRef assetRef = ctx.Content.RequestTexture(saveData.AssetPath)!.Value; return FromAsset(assetRef, saveData.SourceRectangle); - case SpriteTextureKind.StandaloneTexture: + case SpriteTextureKind.Standalone: Debug.Assert(saveData.StandaloneTextureId is not null); int id = saveData.StandaloneTextureId.Value; - return FromStandalone(ctx.StandaloneTextures[id]); + return FromStandaloneTexture(ctx.StandaloneTextures[id]); default: return ThrowHelper.Unreachable(); } @@ -67,57 +73,74 @@ public static SpriteTexture FromSaveData(in SpriteTextureSaveData saveData, Game public SpriteTextureSaveData ToSaveData(GameSavingContext ctx) { - int? standaloneTextureId = this is { Kind: SpriteTextureKind.StandaloneTexture, Standalone: { } standalone } - ? ctx.AddStandaloneTexture(standalone) + Texture? texture = this switch + { + { Kind: SpriteTextureKind.Pooled } => _pooledTexture!.Value.Get(), + { Kind: SpriteTextureKind.Standalone } => _standaloneTexture, + _ => null + }; + + int? standaloneTextureId = texture is not null + ? ctx.AddStandaloneTexture(texture) : null; return new SpriteTextureSaveData { Kind = Kind, Color = Color.ToVector4(), - AssetPath = AssetRef?.Path, + AssetPath = _assetRef?.Path, SourceRectangle = SourceRectangle, StandaloneTextureId = standaloneTextureId }; } public static SpriteTexture FromAsset(AssetRef assetRef, RectangleU? srcRectangle = null) - => new(SpriteTextureKind.Asset, assetRef, srcRectangle, null, RgbaFloat.White); + => new(SpriteTextureKind.Asset, assetRef, srcRectangle, null, null, RgbaFloat.White); public static SpriteTexture SolidColor(in RgbaFloat color, Size size) => new( SpriteTextureKind.SolidColor, null, new RectangleU(0, 0, size.Width, size.Height), - null, + null, null, color ); - public static SpriteTexture FromStandalone(Texture texture) => new( - SpriteTextureKind.StandaloneTexture, + public static SpriteTexture FromPooledTexture(PooledTexture texture) => new( + SpriteTextureKind.Pooled, + null, + new RectangleU(0, 0, texture.Get().Width, texture.Get().Height), + texture, + standaloneTexture: null, + RgbaFloat.White + ); + + public static SpriteTexture FromStandaloneTexture(Texture texture) => new( + SpriteTextureKind.Pooled, null, new RectangleU(0, 0, texture.Width, texture.Height), + pooledTexture: null, texture, RgbaFloat.White ); public Texture Resolve(RenderContext ctx) => this switch { - { Kind: SpriteTextureKind.Asset, AssetRef: { } assetRef } => ctx.Content.Get(assetRef), + { Kind: SpriteTextureKind.Asset, _assetRef: { } assetRef } => ctx.Content.Get(assetRef), { Kind: SpriteTextureKind.SolidColor } => ctx.WhiteTexture, - { Kind: SpriteTextureKind.StandaloneTexture, Standalone: { } standalone } => standalone, + { Kind: SpriteTextureKind.Pooled, _pooledTexture: { } pooledTexture } => pooledTexture.Get(), _ => ThrowHelper.Unreachable() }; public Size GetSize(RenderContext ctx) => this switch { { SourceRectangle: { } srcRect } => srcRect.Size, - { AssetRef: { } assetRef } => ctx.Content.GetTextureSize(assetRef), + { _assetRef: { } assetRef } => ctx.Content.GetTextureSize(assetRef), _ => ThrowHelper.Unreachable() }; public (Vector2, Vector2) GetTexCoords(RenderContext ctx) { - if (this is { AssetRef: { } assetRef, SourceRectangle: { } srcRect }) + if (this is { _assetRef: { } assetRef, SourceRectangle: { } srcRect }) { var texSize = ctx.Content.GetTextureSize(assetRef).ToVector2(); var tl = new Vector2(srcRect.Left, srcRect.Top) / texSize; @@ -130,27 +153,31 @@ public static SpriteTexture FromAsset(AssetRef assetRef, RectangleU? sr public SpriteTexture WithSourceRectangle(RectangleU? sourceRect) { - return this is { Kind: SpriteTextureKind.Asset, AssetRef: { } assetRef } + return this is { Kind: SpriteTextureKind.Asset, _assetRef: { } assetRef } ? FromAsset(assetRef.Clone(), sourceRect) - : new SpriteTexture(Kind, AssetRef, sourceRect ?? SourceRectangle, Standalone, Color); + : new SpriteTexture(Kind, _assetRef, sourceRect ?? SourceRectangle, _pooledTexture, _standaloneTexture, Color); } public SpriteTexture Clone() { - return this is { Kind: SpriteTextureKind.Asset, AssetRef: { } assetRef } + return this is { Kind: SpriteTextureKind.Asset, _assetRef: { } assetRef } ? FromAsset(assetRef.Clone(), SourceRectangle) : this; } public void Dispose() { - if (this is { Kind: SpriteTextureKind.Asset, AssetRef: { } assetRef }) - { - assetRef.Dispose(); - } - if (this is { Kind: SpriteTextureKind.StandaloneTexture, Standalone: { } tex}) + switch (this) { - tex.Dispose(); + case { Kind: SpriteTextureKind.Asset, _assetRef: { } assetRef }: + assetRef.Dispose(); + break; + case { Kind: SpriteTextureKind.Pooled, _pooledTexture: { } pooledTexture}: + pooledTexture.Dispose(); + break; + case { Kind: SpriteTextureKind.Standalone, _standaloneTexture: { } standaloneTexture }: + standaloneTexture.Dispose(); + break; } } } @@ -206,13 +233,15 @@ public override Size GetUnconstrainedBounds(RenderContext ctx) protected override (Vector2, Vector2) GetTexCoords(RenderContext ctx) => _texture.GetTexCoords(ctx); - public override void Render(RenderContext ctx, bool assetsReady) + public override void Render(RenderContext ctx, DrawBatch drawBatch) { - if (assetsReady && _transition is { } transition) + if (_transition is { } transition) { + Texture? mask = ctx.Content.TryGet(transition.Mask); + if (mask is null) { return; } + Texture src = RenderOffscreen(ctx); - Texture mask = ctx.Content.Get(transition.Mask); - RenderTransition(ctx, ctx.MainBatch, src, mask, transition.FadeAmount); + RenderTransition(ctx, drawBatch, src, mask, transition.FadeAmount); if (_transition.HasCompleted) { _transition.Dispose(); @@ -221,11 +250,11 @@ public override void Render(RenderContext ctx, bool assetsReady) } else { - base.Render(ctx, assetsReady); + base.Render(ctx, drawBatch); } } - protected override void Render(RenderContext ctx, DrawBatch drawBatch) + protected override void RenderCore(RenderContext ctx, DrawBatch drawBatch) { Texture alphaMaskTex = ctx.WhiteTexture; Vector2 alphaMaskPos = Vector2.Zero; @@ -345,7 +374,7 @@ public SpriteTextureSaveData(ref MessagePackReader reader) Color = reader.ReadVector4(); SourceRectangle = new RectangleU(Point2DU.Zero, new Size(ref reader)); break; - case SpriteTextureKind.StandaloneTexture: + case SpriteTextureKind.Pooled: StandaloneTextureId = reader.ReadNullableInt32(); break; case SpriteTextureKind.Asset: @@ -363,7 +392,7 @@ public void Serialize(ref MessagePackWriter writer) int fieldCount = Kind switch { SpriteTextureKind.SolidColor => 3, - SpriteTextureKind.StandaloneTexture => 2, + SpriteTextureKind.Pooled => 2, SpriteTextureKind.Asset => 3, _ => ThrowHelper.Unreachable() }; @@ -377,7 +406,7 @@ public void Serialize(ref MessagePackWriter writer) writer.Write(Color); SourceRectangle.Value.Size.Serialize(ref writer); break; - case SpriteTextureKind.StandaloneTexture: + case SpriteTextureKind.Pooled: writer.Write(StandaloneTextureId); break; case SpriteTextureKind.Asset: diff --git a/src/NitroSharp/Graphics/RenderItems/TextBlock.cs b/src/NitroSharp/Graphics/RenderItems/TextBlock.cs index 71d78b0a..e92e2090 100644 --- a/src/NitroSharp/Graphics/RenderItems/TextBlock.cs +++ b/src/NitroSharp/Graphics/RenderItems/TextBlock.cs @@ -72,7 +72,7 @@ protected override void Update(GameContext ctx) ctx.RenderContext.Text.RequestGlyphs(_layout); } - protected override void Render(RenderContext ctx, DrawBatch drawBatch) + protected override void RenderCore(RenderContext ctx, DrawBatch drawBatch) { RectangleF br = BoundingRect; var rect = new RectangleU((uint)br.X, (uint)br.Y, (uint)br.Width, (uint)br.Height); diff --git a/src/NitroSharp/Graphics/RenderItems/Video.cs b/src/NitroSharp/Graphics/RenderItems/Video.cs index 6f04bdb6..1e77bbeb 100644 --- a/src/NitroSharp/Graphics/RenderItems/Video.cs +++ b/src/NitroSharp/Graphics/RenderItems/Video.cs @@ -54,7 +54,7 @@ public override Size GetUnconstrainedBounds(RenderContext ctx) return res; } - protected override void Render(RenderContext ctx, DrawBatch batch) + protected override void RenderCore(RenderContext ctx, DrawBatch drawBatch) { if (!Stream.IsPlaying) { @@ -67,12 +67,12 @@ protected override void Render(RenderContext ctx, DrawBatch batch) _playbackStarted = true; using (frame) { - CommandList cl = ctx.RentCommandList(); + CommandList cl = ctx.CommandListPool.Rent(); cl.Begin(); frame.CopyToDeviceMemory(cl); cl.End(); gd.SubmitCommands(cl); - ctx.ReturnCommandList(cl); + ctx.CommandListPool.Return(cl); } } @@ -80,11 +80,11 @@ protected override void Render(RenderContext ctx, DrawBatch batch) (Texture luma, Texture chroma) = Stream.VideoFrames.GetDeviceTextures(); VideoShaderResources shaderResources = ctx.ShaderResources.Video; - ViewProjection vp = batch.Target.OrthoProjection; + ViewProjection vp = drawBatch.Target.OrthoProjection; Vector4 enableAlpha = _enableAlpha ? Vector4.One : Vector4.Zero; - batch.UpdateBuffer(shaderResources.EnableAlphaBuffer, enableAlpha); - batch.PushQuad(Quad, + drawBatch.UpdateBuffer(shaderResources.EnableAlphaBuffer, enableAlpha); + drawBatch.PushQuad(Quad, shaderResources.GetPipeline(BlendMode), new ResourceBindings( new ResourceSetKey(vp.ResourceLayout, vp.Buffer.VdBuffer), diff --git a/src/NitroSharp/ResourcePool.cs b/src/NitroSharp/ResourcePool.cs new file mode 100644 index 00000000..9d46ee97 --- /dev/null +++ b/src/NitroSharp/ResourcePool.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; + +namespace NitroSharp; + +internal readonly struct ResourcePool : IDisposable + where T : class +{ + private readonly Func _resourceFactory; + private readonly Action _disposeFunc; + private readonly List _allResources; + private readonly Queue _pool; + + public ResourcePool(Func resourceFactory, Action disposeFunc, int initialSize) + { + _resourceFactory = resourceFactory; + _disposeFunc = disposeFunc; + _allResources = new List(initialSize); + _pool = new Queue(initialSize); + for (int i = 0; i < initialSize; i++) + { + T resource = resourceFactory(); + _allResources.Add(resource); + _pool.Enqueue(resource); + } + } + + public T Rent() + { + if (!_pool.TryDequeue(out T? resource)) + { + resource = _resourceFactory(); + _allResources.Add(resource); + } + + return resource; + } + + public void Return(T resource) + { + _pool.Enqueue(resource); + } + + public void Dispose() + { + _pool.Clear(); + foreach (T resource in _allResources) + { + _disposeFunc(resource); + } + _allResources.Clear(); + } +} diff --git a/src/NitroSharp/Saving/GameSaveManager.cs b/src/NitroSharp/Saving/GameSaveManager.cs index 63a8ae1f..147f591a 100644 --- a/src/NitroSharp/Saving/GameSaveManager.cs +++ b/src/NitroSharp/Saving/GameSaveManager.cs @@ -6,6 +6,7 @@ using System.Linq; using MessagePack; using NitroSharp.Graphics; +using NitroSharp.Graphics.Core; using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; using Veldrid; @@ -63,7 +64,7 @@ public void Save(GameContext ctx, uint slot) var writer = new MessagePackWriter(buffer); RenderContext rc = ctx.RenderContext; - CommandList cl = rc.RentCommandList(); + CommandList cl = rc.CommandListPool.Rent(); cl.Begin(); GameProcessSaveData process = ctx.MainProcess.Dump(savingCtx); Texture[] standaloneTextures = savingCtx.StandaloneTextures @@ -73,7 +74,6 @@ public void Save(GameContext ctx, uint slot) Fence fence = rc.ResourceFactory.CreateFence(signaled: false); rc.GraphicsDevice.SubmitCommands(cl, fence); rc.GraphicsDevice.WaitForFence(fence); - rc.ReturnCommandList(cl); var saveData = new GameSaveData { @@ -91,7 +91,7 @@ public void Save(GameContext ctx, uint slot) for (int i = 0; i < standaloneTextures.Length; i++) { - Texture texture = standaloneTextures[i]; + using (Texture texture = standaloneTextures[i]) using (FileStream fileStream = File.Create(Path.Combine(saveDir, $"{i:D4}.png"))) { SaveAsPng(rc, texture, fileStream, texture.Width, texture.Height); @@ -100,14 +100,23 @@ public void Save(GameContext ctx, uint slot) if (slot != AutosaveSlot) { - Debug.Assert(ctx.LastScreenshot is not null); using FileStream thumbStream = File.Create(Path.Combine(saveDir, "thum.npf")); - SaveAsPng(ctx.RenderContext, ctx.LastScreenshot, thumbStream, 128, 72); + using PooledTexture gpuScreenshot = ctx.RenderToTexture(ctx.MainProcess); + cl.Begin(); + using Texture screenshot = rc.ReadbackTexture(cl, gpuScreenshot.Get()); + cl.End(); + fence.Reset(); + rc.GraphicsDevice.SubmitCommands(cl, fence); + rc.GraphicsDevice.WaitForFence(fence); + SaveAsPng(ctx.RenderContext, screenshot, thumbStream, 128, 72); + File.Create(Path.Combine(saveDir, "val.npf")).Close(); File.Create(Path.Combine(saveDir, "script.npf")).Close(); File.Create(Path.Combine(saveDir, "frames.npf")).Close(); File.Create(Path.Combine(saveDir, "bklg.npf")).Close(); } + + rc.CommandListPool.Return(cl); } public void Load(GameContext ctx, uint slot) @@ -159,7 +168,7 @@ private void WriteCommonSaveData(GameContext ctx) File.Create(Path.Combine(_commonDir, "cqst.npf")).Close(); } - public void ReadCommonSaveData(GameContext ctx) + private void ReadCommonSaveData(GameContext ctx) { string filePath = Path.Combine(_commonDir, "val.npf"); if (!File.Exists(filePath)) { return; } diff --git a/src/NitroSharp/Text/GlyphRasterizer.cs b/src/NitroSharp/Text/GlyphRasterizer.cs index e264ce32..7abf5f43 100644 --- a/src/NitroSharp/Text/GlyphRasterizer.cs +++ b/src/NitroSharp/Text/GlyphRasterizer.cs @@ -592,7 +592,7 @@ public bool AddFont(string path, out ImmutableArray<(FontFaceKey, FontFace)> fac int idxFace = 0; int newFaces = 0; faces = ImmutableArray.Create<(FontFaceKey, FontFace)>(); - while ((FT.FT_New_Face(_freetypeLib, path, idxFace, out Face* ftFace)) == Error.Ok) + while (FT.FT_New_Face(_freetypeLib, path, idxFace, out Face* ftFace) == Error.Ok) { var face = new FontFace(ftFace); var key = new FontFaceKey(face.FontFamily, face.Style);