Skip to content

Crash while reloading scripts because "no serializer available" #3353

Description

@ds5678

Release Type: Official Release

Version: 4.4 beta 4

Platform(s): Windows

Describe the bug

When I reload scripts after changing them in the IDE, the Game Studio crashes.

To Reproduce

This started when I created a split screen renderer and used it in my graphics compositor.

[DataContract]
public class SplitScreenRenderer : SceneRendererBase
{
    /// <summary>
    /// Gets or sets the camera.
    /// </summary>
    /// <value>The camera.</value>
    /// <userdoc>The camera to use to render the scene.</userdoc>
    public required SceneCameraSlot LeftCamera { get; init; }
    public required SceneCameraSlot RightCamera { get; init; }

    public required ISceneRenderer ForwardRenderer { get; init; }
    public required ISceneRenderer DebugRenderer { get; init; }

    [Range(0.0f, 1.0f)]
    public float SplitPosition { get; set; } = 0.5f;
    [Range(0.0f, 1.0f)]
    public float SeparatorWidth { get; set; } = 0f;
    public Vector4 SeparatorColor { get; set; } = new Vector4(0, 0, 0, 1);

#nullable disable
    private SceneCameraRenderer leftCameraRenderer;
    private SceneCameraRenderer rightCameraRenderer;
    private RenderTextureSceneRenderer leftRenderTextureSceneRenderer;
    private RenderTextureSceneRenderer rightRenderTextureSceneRenderer;
    private SceneRendererCollection leftRendererCollection;
    private SceneRendererCollection rightRendererCollection;

    private SplitScreenCompositeEffect compositeEffect;
#nullable restore

    protected override void InitializeCore()
    {
        compositeEffect = ToLoadAndUnload(new SplitScreenCompositeEffect()); // hooks lifecycle into the renderer

        leftRendererCollection = ToLoadAndUnload(new SceneRendererCollection()
        {
            Children = { ForwardRenderer, DebugRenderer }
        });
        rightRendererCollection = ToLoadAndUnload(new SceneRendererCollection()
        {
            Children = { ForwardRenderer, DebugRenderer }
        });

        Texture leftRenderTexture = Texture.New2D(GraphicsDevice, 1, 1, PixelFormat.R8G8B8A8_UNorm_SRgb, TextureFlags.ShaderResource | TextureFlags.RenderTarget);
        Texture rightRenderTexture = Texture.New2D(GraphicsDevice, 1, 1, PixelFormat.R8G8B8A8_UNorm_SRgb, TextureFlags.ShaderResource | TextureFlags.RenderTarget);

        leftRenderTextureSceneRenderer = ToLoadAndUnload(new RenderTextureSceneRenderer()
        {
            RenderTexture = leftRenderTexture,
            Child = leftRendererCollection,
        });
        rightRenderTextureSceneRenderer = ToLoadAndUnload(new RenderTextureSceneRenderer()
        {
            RenderTexture = rightRenderTexture,
            Child = rightRendererCollection,
        });

        leftCameraRenderer = ToLoadAndUnload(new SceneCameraRenderer()
        {
            Camera = LeftCamera,
            Child = leftRenderTextureSceneRenderer,
        });
        rightCameraRenderer = ToLoadAndUnload(new SceneCameraRenderer()
        {
            Camera = RightCamera,
            Child = rightRenderTextureSceneRenderer,
        });
    }

    protected override void CollectCore(RenderContext context)
    {
        leftCameraRenderer.Collect(context);
        rightCameraRenderer.Collect(context);
    }

    protected override void DrawCore(RenderContext context, RenderDrawContext drawContext)
    {
        // Reallocate offscreen targets matching the output size if needed
        // TODO: We currently render twice as many pixels as we need to, since we render the left and right cameras at full resolution.
        // We could optimize this by rendering at half resolution, but that requires changes to the shader.
        var outputTarget = drawContext.CommandList.RenderTarget;
        MaybeReplaceTexture(leftRenderTextureSceneRenderer, GraphicsDevice, outputTarget.Width, outputTarget.Height, outputTarget.Format);
        MaybeReplaceTexture(rightRenderTextureSceneRenderer, GraphicsDevice, outputTarget.Width, outputTarget.Height, outputTarget.Format);

        leftCameraRenderer.Draw(drawContext);
        rightCameraRenderer.Draw(drawContext);
        DrawCompositeEffect(drawContext);
    }

    private void DrawCompositeEffect(RenderDrawContext drawContext)
    {
        compositeEffect.CameraATexture = leftRenderTextureSceneRenderer.RenderTexture;
        compositeEffect.CameraBTexture = rightRenderTextureSceneRenderer.RenderTexture;
        compositeEffect.SplitPosition = SplitPosition;
        compositeEffect.SeparatorWidth = SeparatorWidth;
        compositeEffect.SeparatorColor = SeparatorColor;
        compositeEffect.Draw(drawContext);
    }

    private static void MaybeReplaceTexture(RenderTextureSceneRenderer renderer, GraphicsDevice graphicsDevice, int width, int height, PixelFormat format)
    {
        var texture = renderer.RenderTexture;
        if (texture.Width != width || texture.Height != height || texture.Format != format)
        {
            texture.Dispose();
            renderer.RenderTexture = Texture.New2D(graphicsDevice, width, height, format, TextureFlags.ShaderResource | TextureFlags.RenderTarget);
        }
    }
}

public class SplitScreenCompositeEffect : ImageEffectShader
{
    public SplitScreenCompositeEffect() : base("SplitScreenComposite") { }
#nullable disable
    public Texture CameraATexture { get; set; }
    public Texture CameraBTexture { get; set; }
    public float SplitPosition { get; set; } = 0.5f;
    public float SeparatorWidth { get; set; } = 0f;
    public Vector4 SeparatorColor { get; set; } = new Vector4(0, 0, 0, 1);
#nullable restore
    protected override void UpdateParameters()
    {
        base.UpdateParameters();
        Parameters.Set(SplitScreenCompositeKeys.SplitPosition, SplitPosition);
        Parameters.Set(SplitScreenCompositeKeys.SeparatorWidth, SeparatorWidth);
        Parameters.Set(SplitScreenCompositeKeys.SeparatorColor, SeparatorColor);
    }

    protected override void DrawCore(RenderDrawContext context)
    {
        SetInput(0, CameraATexture);
        SetInput(1, CameraBTexture);
        base.DrawCore(context);
    }
}
namespace GraphicsTest.Compositing
{
    shader SplitScreenComposite : ImageEffectShader
    {
        stage float SplitPosition = 0.5f;
        stage float SeparatorWidth = 0.0f;
        stage float4 SeparatorColor = float4(0, 0, 0, 1);

        stage override float4 Shading()
        {
            float leftEdge_X  = SplitPosition - SeparatorWidth * 0.5f;
            float rightEdge_X = SplitPosition + SeparatorWidth * 0.5f;

            float2 uv = streams.TexCoord;

            float screen_X = uv.x;

            float camA_X = saturate(screen_X - (0.5f * leftEdge_X) + 0.5f);

            float camB_X = saturate(screen_X + (0.5f * rightEdge_X) - 0.5f);

            float4 camA = Texture0.Sample(Sampler, float2(camA_X, uv.y));
            float4 camB = Texture1.Sample(Sampler, float2(camB_X, uv.y));

            float4 temp = lerp(camA, SeparatorColor, step(leftEdge_X, uv.x));
            return lerp(temp, camB, step(rightEdge_X, uv.x));
        }
    };
}

Expected behavior

Reload scripts should work the same as it did before I created this class.

Log and callstacks

Exception: ArgumentException: No serializer available for type GraphicsTest.Compositing.SplitScreenRenderer
   at Stride.Core.Serialization.MemberReuseSerializer`1.Serialize(T& obj, ArchiveMode mode, SerializationStream stream) in /_/sources/core/Stride.Core/Serialization/MemberSerializerGenerated.cs:line 1183
   at Stride.Core.DataSerializers.StrideAssetsRendering_GraphicsCompositorAssetSerializer.Serialize(GraphicsCompositorAsset& obj, ArchiveMode mode, SerializationStream stream)
   at Stride.Core.Serialization.DataSerializer`1.Serialize(Object& obj, ArchiveMode mode, SerializationStream stream) in /_/sources/core/Stride.Core/Serialization/DataSerializer.cs:line 80
   at Stride.Core.Serialization.MemberReuseSerializer`1.SerializeExtended(T& obj, ArchiveMode mode, SerializationStream stream, DataSerializer`1 dataSerializer) in /_/sources/core/Stride.Core/Serialization/MemberSerializerGenerated.cs:line 1429
   at Stride.Core.Assets.AssetCloner..ctor(Object value, AssetClonerFlags flags, IEnumerable`1 externalIdentifiables) in /_/sources/assets/Stride.Core.Assets/AssetCloner.cs:line 73
   at Stride.Core.Assets.Analysis.AssetDependencyManager.Session_AssetDirtyChanged(AssetItem asset, Boolean oldValue, Boolean newValue) in /_/sources/assets/Stride.Core.Assets/Analysis/AssetDependencyManager.cs:line 519
   at Stride.Core.Presentation.ViewModels.DirtiableEditableViewModel.UpdateDirtiness(Boolean value) in /_/sources/presentation/Stride.Core.Presentation/ViewModels/DirtiableEditableViewModel.cs:line 47
   at Stride.Core.Presentation.Dirtiables.DirtiableManager.UpdateDirtiables(HashSet`1 dirtiables) in /_/sources/presentation/Stride.Core.Presentation/Dirtiables/DirtiableManager.cs:line 121
   at Stride.Core.Presentation.Dirtiables.DirtiableManager.TransactionCompleted(Object sender, TransactionEventArgs e) in /_/sources/presentation/Stride.Core.Presentation/Dirtiables/DirtiableManager.cs:line 138
   at Stride.Core.Transactions.TransactionStack.CompleteTransaction(Transaction transaction) in /_/sources/core/Stride.Core.Design/Transactions/TransactionStack.cs:line 202
   at Stride.Core.Transactions.TransactionStack.CompleteTransaction(Transaction transaction) in /_/sources/core/Stride.Core.Design/Transactions/TransactionStack.cs:line 134
   at Stride.Core.Transactions.Transaction.Dispose() in /_/sources/core/Stride.Core.Design/Transactions/Transaction.cs:line 45
   at Stride.GameStudio.ViewModels.DebuggingViewModel.ReloadAssemblies() in /_/sources/editor/Stride.GameStudio/ViewModels/DebuggingViewModel.cs:line 397
   at Stride.GameStudio.ViewModels.DebuggingViewModel.ReloadAssemblies()
   at Stride.Core.Presentation.Commands.AnonymousTaskCommand.<>c__DisplayClass0_0.<<-ctor>b__0>d.MoveNext() in /_/sources/presentation/Stride.Core.Presentation/Commands/AnonymousCommand.cs:line 84
--- End of stack trace from previous location ---
   at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__124_0(Object state)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)

Report.txt

Additional context

This happens every time I try to reload scripts, not just the first time.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions