Files
RobustToolbox/Robust.Client/Graphics/Clyde/Clyde.Shaders.cs
Paul Ritter 80f9f24243 Serialization v3 aka constant suffering (#1606)
* oops

* fixes serialization il

* copytest

* typo & misc fixes

* 139 moment

* boxing

* mesa dum

* stuff

* goodbye bad friend

* last commit before the big (4) rewrite

* adds datanodes

* kills yamlobjserializer in favor of the new system

* adds more serializers, actually implements them & removes most of the last of the old system

* changed yamlfieldattribute namespace

* adds back iselfserialize

* refactors consts&flags

* renames everything to data(field/definition)

* adds afterserialization

* help

* dataclassgen

* fuggen help me mannen

* Fix most errors on content

* Fix engine errors except map loader

* maploader & misc fix

* misc fixes

* thing

* help

* refactors datanodes

* help me mannen

* Separate ITypeSerializer into reader and writer

* Convert all type serializers

* priority

* adds alot

* il fixes

* adds robustgen

* argh

* adds array & enum serialization

* fixes dataclasses

* adds vec2i / misc fixes

* fixes inheritance

* a very notcursed todo

* fixes some custom dataclasses

* push dis

* Remove data classes

* boutta box

* yes

* Add angle and regex serializer tests

* Make TypeSerializerTest abstract

* sets up ioc etc

* remove pushinheritance

* fixes

* Merge fixes, fix yaml hot reloading

* General fixes2

* Make enum serialization ignore case

* Fix the tag not being copied in data nodes

* Fix not properly serializing flag enums

* Fix component serialization on startup

* Implement ValueDataNode ToString

* Serialization IL fixes, fix return and string equality

* Remove async from prototype manager

* Make serializing unsupported node as enum exception more descriptive

* Fix serv3 tryread casting to serializer instead of reader

* Add constructor for invalid node type exception

* Temporary fix for SERV3: Turn populate delegate into regular code

* Fix not copying the data of non primitive types

* Fix not using the data definition found in copying

* Make ISerializationHooks require explicit implementations

* Add test for serialization inheritance

* Improve IsOverridenIn method

* Fix error message when a data definition is null

* Add method to cast a read value in Serv3Manager

* Rename IServ3Manager to ISerializationManager

* Rename usages of serv3manager, add generic copy method

* Fix IL copy method lookup

* Rename old usages of serv3manager

* Add ITypeCopier

* resistance is futile

* we will conquer this codebase

* Add copy method to all serializers

* Make primitive mismatch error message more descriptive

* bing bong im going to freacking heck

* oopsie moment

* hello are you interested in my wares

* does generic serializers under new architecture

* Convert every non generic serializer to the new format, general fixes

* Update usgaes of generic serializers, cleanup

* does some pushinheritance logic

* finishes pushinheritance FRAMEWORK

* shed

* Add box2, color and component registry serializer tests

* Create more deserialized types and store prototypes with their deserialized results

* Fixes and serializer updates

* Add serialization manager extensions

* adds pushinheritance

* Update all prototypes to have a parent and have consistent id/parent properties

* Fix grammar component serialization

* Add generic serializer tests

* thonk

* Add array serializer test

* Replace logger warning calls with exceptions

* fixes

* Move redundant methods to serialization manager extensions, cleanup

* Add array serialization

* fixes context

* more fixes

* argh

* inheritance

* this should do it

* fixes

* adds copiers & fixes some stuff

* copiers use context v1

* finishing copy context

* more context fixes

* Test fixes

* funky maps

* Fix server user interface component serialization

* Fix value tuple serialization

* Add copying for value types and arrays. Fix copy internal for primitives, enums and strings

* fixes

* fixes more stuff

* yes

* Make abstract/interface skips debugs instead of warnings

* Fix typo

* Make some dictionaries readonly

* Add checks for the serialization manager initializing and already being initialized

* Add base type required and usage for MeansDataDefinition and ImplicitDataDefinitionForInheritorsAttribute

* copy by ref

* Fix exception wording

* Update data field required summary with the new forbidden docs

* Use extension in map loader

* wanna erp

* Change serializing to not use il temporarily

* Make writing work with nullable types

* pushing

* check

* cuddling slaps HARD

* Add serialization priority test

* important fix

* a serialization thing

* serializer moment

* Add validation for some type serializers

* adds context

* moar context

* fixes

* Do the thing for appearance

* yoo lmao

* push haha pp

* Temporarily make copy delegate regular c# code

* Create deserialized component registry to handle not inheriting conflicting references

* YAML LINTER BABY

* ayes

* Fix sprite component norot not being default true like in latest master

* Remove redundant todos

* Add summary doc to every ISerializationManager method

* icon fixes

* Add skip hook argument to readers and copiers

* Merge fixes

* Fix ordering of arguments in read and copy reflection call

* Fix user interface components deserialization

* pew pew

* i am going to HECK

* Add MustUseReturnValue to copy-over methods

* Make serialization log calls use the same sawmill

* gamin

* Fix doc errors in ISerializationManager.cs

* goodbye brave soldier

* fixes

* WIP merge fixes and entity serialization

* aaaaaaaaaaaaaaa

* aaaaaaaaaaaaaaa

* adds inheritancebehaviour

* test/datafield fixes

* forgot that one

* adds more verbose validation

* This fixes the YAML hot reloading

* Replace yield break with Enumerable.Empty

* adds copiers

* aaaaaaaaaaaaa

* array fix
priority fix
misc fixes

* fix(?)

* fix.

* funny map serialization (wip)

* funny map serialization (wip)

* Add TODO

* adds proper info the validation

* Make yaml linter 5 times faster (~80% less execution time)

* Improves the error message for missing fields in the linter

* Include component name in unknown component type error node

* adds alwaysrelevant usa

* fixes mapsaving

* moved surpressor to analyzers proj

* warning cleanup & moves surpressor

* removes old msbuild targets

* Revert "Make yaml linter 5 times faster (~80% less execution time)"

This reverts commit 2ee4cc2c26.

* Add serialization to RobustServerSimulation and mock reflection methods
Fixes container tests

* Fix nullability warnings

* Improve yaml linter message feedback

* oops moment

* Add IEquatable, IComparable, ToString and operators to DataPosition
Rename it to NodeMark
Make it a readonly struct

* Remove try catch from enum parsing

* Make dependency management in serialization less bad

* Make dependencies an argument instead of a property on the serialization manager

* Clean up type serializers

* Improve validation messages and resourc epath checking

* Fix sprite error message

* reached perfection

Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
Co-authored-by: DrSmugleaf <DrSmugleaf@users.noreply.github.com>
Co-authored-by: Vera Aguilera Puerto <zddm@outlook.es>
2021-03-04 15:59:14 -08:00

492 lines
17 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Text;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Client.ResourceManagement;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using StencilOp = Robust.Client.Graphics.StencilOp;
namespace Robust.Client.Graphics.Clyde
{
internal partial class Clyde
{
private ClydeShaderInstance _defaultShader = default!;
private string _shaderLibrary = default!;
private string _shaderWrapCodeDefaultFrag = default!;
private string _shaderWrapCodeDefaultVert = default!;
private string _shaderWrapCodeRawFrag = default!;
private string _shaderWrapCodeRawVert = default!;
private readonly Dictionary<ClydeHandle, LoadedShader> _loadedShaders =
new();
private readonly Dictionary<ClydeHandle, LoadedShaderInstance> _shaderInstances =
new();
private readonly ConcurrentQueue<ClydeHandle> _deadShaderInstances = new();
private class LoadedShader
{
public GLShaderProgram Program = default!;
public bool HasLighting = true;
public ShaderBlendMode BlendMode;
public string? Name;
}
private class LoadedShaderInstance
{
public ClydeHandle ShaderHandle;
// TODO(perf): Maybe store these parameters not boxed with a tagged union.
public readonly Dictionary<string, object> Parameters = new();
public StencilParameters Stencil = StencilParameters.Default;
}
public ClydeHandle LoadShader(ParsedShader shader, string? name = null)
{
var (vertBody, fragBody) = GetShaderCode(shader);
var program = _compileProgram(vertBody, fragBody, BaseShaderAttribLocations, name);
if (_hasGLUniformBuffers)
{
program.BindBlock(UniProjViewMatrices, BindingIndexProjView);
program.BindBlock(UniUniformConstants, BindingIndexUniformConstants);
}
var loaded = new LoadedShader
{
Program = program,
HasLighting = shader.LightMode != ShaderLightMode.Unshaded,
BlendMode = shader.BlendMode,
Name = name
};
var handle = AllocRid();
_loadedShaders.Add(handle, loaded);
return handle;
}
public void ReloadShader(ClydeHandle handle, ParsedShader newShader)
{
var loaded = _loadedShaders[handle];
loaded.HasLighting = newShader.LightMode != ShaderLightMode.Unshaded;
loaded.BlendMode = newShader.BlendMode;
var (vertBody, fragBody) = GetShaderCode(newShader);
var program = _compileProgram(vertBody, fragBody, BaseShaderAttribLocations, loaded.Name);
loaded.Program.Delete();
loaded.Program = program;
if (_hasGLUniformBuffers)
{
program.BindBlock(UniProjViewMatrices, BindingIndexProjView);
program.BindBlock(UniUniformConstants, BindingIndexUniformConstants);
}
}
public ShaderInstance InstanceShader(ClydeHandle handle)
{
var newHandle = AllocRid();
var loaded = new LoadedShaderInstance
{
ShaderHandle = handle
};
var instance = new ClydeShaderInstance(newHandle, this);
_shaderInstances.Add(newHandle, loaded);
return instance;
}
private void LoadStockShaders()
{
_shaderLibrary = ReadEmbeddedShader("z-library.glsl");
_shaderWrapCodeDefaultFrag = ReadEmbeddedShader("base-default.frag");
_shaderWrapCodeDefaultVert = ReadEmbeddedShader("base-default.vert");
_shaderWrapCodeRawVert = ReadEmbeddedShader("base-raw.vert");
_shaderWrapCodeRawFrag = ReadEmbeddedShader("base-raw.frag");
var defaultLoadedShader = _resourceCache
.GetResource<ShaderSourceResource>("/Shaders/Internal/default-sprite.swsl").ClydeHandle;
_defaultShader = (ClydeShaderInstance) InstanceShader(defaultLoadedShader);
_queuedShader = _defaultShader.Handle;
}
private string ReadEmbeddedShader(string fileName)
{
var assembly = typeof(Clyde).Assembly;
using var stream = assembly.GetManifestResourceStream($"Robust.Client.Graphics.Clyde.Shaders.{fileName}")!;
DebugTools.AssertNotNull(stream);
using var reader = new StreamReader(stream, EncodingHelpers.UTF8);
return reader.ReadToEnd();
}
private GLShaderProgram _compileProgram(string vertexSource, string fragmentSource,
(string, uint)[] attribLocations, string? name = null)
{
GLShader? vertexShader = null;
GLShader? fragmentShader = null;
var versionHeader = "#version 140\n#define HAS_MOD\n";
if (_isGLES)
{
// GLES2 uses a different GLSL versioning scheme to desktop GL.
versionHeader = "#version 100\n#define HAS_VARYING_ATTRIBUTE\n";
if (_hasGLStandardDerivatives)
{
versionHeader += "#extension GL_OES_standard_derivatives : enable\n";
}
}
if (_hasGLStandardDerivatives)
{
versionHeader += "#define HAS_DFDX\n";
}
if (_hasGLFloatFramebuffers)
{
versionHeader += "#define HAS_FLOAT_TEXTURES\n";
}
if (_hasGLSrgb)
{
versionHeader += "#define HAS_SRGB\n";
}
if (_hasGLUniformBuffers)
{
versionHeader += "#define HAS_UNIFORM_BUFFERS\n";
}
vertexSource = versionHeader + "#define VERTEX_SHADER\n" + _shaderLibrary + vertexSource;
fragmentSource = versionHeader + "#define FRAGMENT_SHADER\n" + _shaderLibrary + fragmentSource;
try
{
try
{
vertexShader = new GLShader(this, ShaderType.VertexShader, vertexSource, name == null ? $"{name}-vertex" : null);
}
catch (ShaderCompilationException e)
{
File.WriteAllText("error.glsl", vertexSource);
throw new ShaderCompilationException(
"Failed to compile vertex shader, see inner for details (and error.glsl for formatted source).", e);
}
try
{
fragmentShader = new GLShader(this, ShaderType.FragmentShader, fragmentSource, name == null ? $"{name}-fragment" : null);
}
catch (ShaderCompilationException e)
{
File.WriteAllText("error.glsl", fragmentSource);
throw new ShaderCompilationException(
"Failed to compile fragment shader, see inner for details (and error.glsl for formatted source).", e);
}
var program = new GLShaderProgram(this, name);
program.Add(vertexShader);
program.Add(fragmentShader);
try
{
program.Link(attribLocations);
}
catch (ShaderCompilationException e)
{
program.Delete();
throw new ShaderCompilationException("Failed to link shaders. See inner for details.", e);
}
return program;
}
finally
{
vertexShader?.Delete();
fragmentShader?.Delete();
}
}
private (string vertBody, string fragBody) GetShaderCode(ParsedShader shader)
{
var headerUniforms = new StringBuilder();
foreach (var constant in shader.Constants.Values)
{
headerUniforms.AppendFormat("const {0} {1} = {2};\n", constant.Type.GetNativeType(), constant.Name,
constant.Value);
}
foreach (var uniform in shader.Uniforms.Values)
{
if (uniform.DefaultValue != null)
{
headerUniforms.AppendFormat("uniform {0} {1} = {2};\n", uniform.Type.GetNativeType(), uniform.Name,
uniform.DefaultValue);
}
else
{
headerUniforms.AppendFormat("uniform {0} {1};\n", uniform.Type.GetNativeType(), uniform.Name);
}
}
var varyingsFragment = new StringBuilder();
var varyingsVertex = new StringBuilder();
foreach (var (name, varying) in shader.Varyings)
{
varyingsFragment.AppendFormat("varying {0} {1};\n", varying.Type.GetNativeType(), name);
varyingsVertex.AppendFormat("varying {0} {1};\n", varying.Type.GetNativeType(), name);
}
ShaderFunctionDefinition? fragmentMain = null;
ShaderFunctionDefinition? vertexMain = null;
var functionsBuilder = new StringBuilder();
foreach (var function in shader.Functions)
{
if (function.Name == "fragment")
{
fragmentMain = function;
continue;
}
if (function.Name == "vertex")
{
vertexMain = function;
continue;
}
functionsBuilder.AppendFormat("{0} {1}(", function.ReturnType.GetNativeType(), function.Name);
var first = true;
foreach (var parameter in function.Parameters)
{
if (!first)
{
functionsBuilder.Append(", ");
}
first = false;
functionsBuilder.AppendFormat("{0} {1} {2}", parameter.Qualifiers.GetString(), parameter.Type.GetNativeType(),
parameter.Name);
}
functionsBuilder.AppendFormat(") {{\n{0}\n}}\n", function.Body);
}
var (wrapVert, wrapFrag) = shader.Preset switch
{
ShaderPreset.Default => (_shaderWrapCodeDefaultVert, _shaderWrapCodeDefaultFrag),
ShaderPreset.Raw => (_shaderWrapCodeRawVert, _shaderWrapCodeRawFrag),
_ => throw new NotSupportedException()
};
var vertexHeader = $"{headerUniforms}\n{varyingsVertex}\n{functionsBuilder}";
var fragmentHeader = $"{headerUniforms}\n{varyingsFragment}\n{functionsBuilder}";
// These are prefixed with comments because the syntax highlighter I'm using doesn't like the brackets.
// And it's producing a lot of squigly lines.
var vertexSource = wrapVert.Replace("// [SHADER_HEADER_CODE]", vertexHeader);
var fragmentSource = wrapFrag.Replace("// [SHADER_HEADER_CODE]", fragmentHeader);
if (vertexMain != null)
{
vertexSource = vertexSource.Replace("// [SHADER_CODE]", vertexMain.Body);
}
if (fragmentMain != null)
{
fragmentSource = fragmentSource.Replace("// [SHADER_CODE]", fragmentMain.Body);
}
return (vertexSource, fragmentSource);
}
private void FlushShaderInstanceDispose()
{
while (_deadShaderInstances.TryDequeue(out var handle))
{
_shaderInstances.Remove(handle);
}
}
private sealed class ClydeShaderInstance : ShaderInstance
{
public readonly ClydeHandle Handle;
public readonly Clyde Parent;
public ClydeShaderInstance(ClydeHandle handle, Clyde parent)
{
Handle = handle;
Parent = parent;
}
private protected override ShaderInstance DuplicateImpl()
{
var instanceData = Parent._shaderInstances[Handle];
var newData = new LoadedShaderInstance
{
ShaderHandle = instanceData.ShaderHandle
};
foreach (var (name, value) in instanceData.Parameters)
{
newData.Parameters.Add(name, value);
}
newData.Stencil = instanceData.Stencil;
var newHandle = Parent.AllocRid();
Parent._shaderInstances.Add(newHandle, newData);
return new ClydeShaderInstance(newHandle, Parent);
}
protected override void Dispose(bool disposing)
{
Parent._deadShaderInstances.Enqueue(Handle);
}
// TODO: Verify that parameters actually exist before assigning them like this.
private protected override void SetParameterImpl(string name, float value)
{
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, Vector2 value)
{
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, Vector3 value)
{
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, Vector4 value)
{
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, Color value)
{
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, int value)
{
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, Vector2i value)
{
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, bool value)
{
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, in Matrix3 value)
{
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, in Matrix4 value)
{
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetParameterImpl(string name, Texture value)
{
throw new NotImplementedException();
}
private protected override void SetStencilOpImpl(StencilOp op)
{
var data = Parent._shaderInstances[Handle];
data.Stencil.Op = op;
}
private protected override void SetStencilFuncImpl(StencilFunc func)
{
var data = Parent._shaderInstances[Handle];
data.Stencil.Func = func;
}
private protected override void SetStencilTestEnabledImpl(bool enabled)
{
var data = Parent._shaderInstances[Handle];
data.Stencil.Enabled = enabled;
}
private protected override void SetStencilRefImpl(int @ref)
{
var data = Parent._shaderInstances[Handle];
data.Stencil.Ref = @ref;
}
private protected override void SetStencilWriteMaskImpl(int mask)
{
var data = Parent._shaderInstances[Handle];
data.Stencil.WriteMask = mask;
}
private protected override void SetStencilReadMaskRefImpl(int mask)
{
var data = Parent._shaderInstances[Handle];
data.Stencil.ReadMask = mask;
}
}
private struct StencilParameters
{
public static readonly StencilParameters Default = new()
{
Enabled = false,
Ref = 0,
Op = StencilOp.Keep,
Func = StencilFunc.Always,
ReadMask = unchecked((int)uint.MaxValue),
WriteMask = unchecked((int)uint.MaxValue),
};
public bool Enabled;
public int Ref;
public int WriteMask;
public int ReadMask;
public StencilOp Op;
public StencilFunc Func;
}
}
}