mirror of
https://github.com/aharabada/glitchy-engine-beef.git
synced 2026-09-05 21:01:52 +00:00
Replaced IntX by intX, Basic AssetViewer (can view Textures, kind of)
This commit is contained in:
@@ -77,7 +77,31 @@ namespace GlitchyEngine.Renderer
|
||||
{
|
||||
case typeof(bool):
|
||||
EnsureTypeMatch(1, 1, .Bool);
|
||||
case typeof(bool2):
|
||||
EnsureTypeMatch(1, 2, .Bool);
|
||||
case typeof(bool3):
|
||||
EnsureTypeMatch(1, 3, .Bool);
|
||||
case typeof(bool4):
|
||||
EnsureTypeMatch(1, 4, .Bool);
|
||||
|
||||
case typeof(int32):
|
||||
EnsureTypeMatch(1, 1, .Int);
|
||||
case typeof(int2):
|
||||
EnsureTypeMatch(1, 2, .Int);
|
||||
case typeof(int3):
|
||||
EnsureTypeMatch(1, 3, .Int);
|
||||
case typeof(int4):
|
||||
EnsureTypeMatch(1, 4, .Int);
|
||||
|
||||
case typeof(uint32):
|
||||
EnsureTypeMatch(1, 1, .UInt);
|
||||
case typeof(uint2):
|
||||
EnsureTypeMatch(1, 2, .UInt);
|
||||
case typeof(uint3):
|
||||
EnsureTypeMatch(1, 3, .UInt);
|
||||
case typeof(uint4):
|
||||
EnsureTypeMatch(1, 4, .UInt);
|
||||
|
||||
case typeof(float):
|
||||
EnsureTypeMatch(1, 1, .Float);
|
||||
case typeof(float2):
|
||||
@@ -87,18 +111,6 @@ namespace GlitchyEngine.Renderer
|
||||
case typeof(float4):
|
||||
EnsureTypeMatch(1, 4, .Float);
|
||||
|
||||
case typeof(int32):
|
||||
EnsureTypeMatch(1, 1, .Int);
|
||||
case typeof(Int2):
|
||||
EnsureTypeMatch(1, 2, .Int);
|
||||
case typeof(Int3):
|
||||
EnsureTypeMatch(1, 3, .Int);
|
||||
case typeof(Int4):
|
||||
EnsureTypeMatch(1, 4, .Int);
|
||||
|
||||
case typeof(uint32):
|
||||
EnsureTypeMatch(1, 1, .UInt);
|
||||
|
||||
case typeof(Matrix4x3):
|
||||
EnsureTypeMatch(4, 3, .Float);
|
||||
case typeof(Matrix3x3):
|
||||
@@ -130,19 +142,25 @@ namespace GlitchyEngine.Renderer
|
||||
}
|
||||
|
||||
public void SetData(bool value) => SetData<bool>(value);
|
||||
public void SetData(bool2 value) => SetData<bool2>(value);
|
||||
public void SetData(bool3 value) => SetData<bool3>(value);
|
||||
public void SetData(bool4 value) => SetData<bool4>(value);
|
||||
|
||||
public void SetData(int32 value) => SetData<int32>(value);
|
||||
public void SetData(int2 value) => SetData<int2>(value);
|
||||
public void SetData(int3 value) => SetData<int3>(value);
|
||||
public void SetData(int4 value) => SetData<int4>(value);
|
||||
|
||||
public void SetData(uint32 value) => SetData<uint32>(value);
|
||||
public void SetData(uint2 value) => SetData<uint2>(value);
|
||||
public void SetData(uint3 value) => SetData<uint3>(value);
|
||||
public void SetData(uint4 value) => SetData<uint4>(value);
|
||||
|
||||
public void SetData(float value) => SetData<float>(value);
|
||||
public void SetData(float2 value) => SetData<float2>(value);
|
||||
public void SetData(float3 value) => SetData<float3>(value);
|
||||
public void SetData(float4 value) => SetData<float4>(value);
|
||||
|
||||
public void SetData(int32 value) => SetData<int32>(value);
|
||||
public void SetData(Int2 value) => SetData<Int2>(value);
|
||||
public void SetData(Int3 value) => SetData<Int3>(value);
|
||||
public void SetData(Int4 value) => SetData<Int4>(value);
|
||||
|
||||
public void SetData(uint32 value) => SetData<uint32>(value);
|
||||
|
||||
public void SetData(ColorRGB value) => SetData<ColorRGB>(value);
|
||||
public void SetData(ColorRGBA value) => SetData<ColorRGBA>(value);
|
||||
public void SetData(Color value) => SetData<ColorRGBA>((ColorRGBA)value);
|
||||
|
||||
@@ -53,4 +53,57 @@ namespace GlitchyEngine.Renderer
|
||||
RenderCommand.DrawIndexed(s_fullscreenQuadGeometry);
|
||||
}
|
||||
}
|
||||
|
||||
static class Quad
|
||||
{
|
||||
static GeometryBinding s_quadGeometry;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
s_quadGeometry = new GeometryBinding();
|
||||
s_quadGeometry.SetPrimitiveTopology(.TriangleList);
|
||||
|
||||
using(var quadVertices = new VertexBuffer(typeof(float4), 4, .Immutable))
|
||||
{
|
||||
float4[4] vertices = .(
|
||||
.(-0.5f,-0.5f, 0, 1),
|
||||
.(-0.5f, 0.5f, 0, 0),
|
||||
.( 0.5f, 0.5f, 1, 0),
|
||||
.( 0.5f,-0.5f, 1, 1)
|
||||
);
|
||||
|
||||
quadVertices.SetData(vertices);
|
||||
s_quadGeometry.SetVertexBufferSlot(quadVertices, 0);
|
||||
}
|
||||
|
||||
using(var quadIndices = new IndexBuffer(6, .Immutable))
|
||||
{
|
||||
uint16[6] indices = .(0, 1, 2, 2, 3, 0);
|
||||
|
||||
quadIndices.SetData(indices);
|
||||
s_quadGeometry.SetIndexBuffer(quadIndices);
|
||||
}
|
||||
|
||||
VertexElement[] vertexElements = new .(
|
||||
VertexElement(.R32G32_Float, "POSITION"),
|
||||
VertexElement(.R32G32_Float, "TEXCOORD")
|
||||
);
|
||||
|
||||
using (var quadBatchLayout = new VertexLayout(vertexElements, true))
|
||||
{
|
||||
s_quadGeometry.SetVertexLayout(quadBatchLayout);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Deinit()
|
||||
{
|
||||
s_quadGeometry.ReleaseRef();
|
||||
}
|
||||
|
||||
public static void Draw()
|
||||
{
|
||||
s_quadGeometry.Bind();
|
||||
RenderCommand.DrawIndexed(s_quadGeometry);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -144,17 +144,30 @@ public class Material : Asset
|
||||
}
|
||||
}
|
||||
|
||||
public void SetVariable(String name, bool value) => SetVariable<bool>(name, value);
|
||||
public void SetVariable(String name, bool2 value) => SetVariable<bool2>(name, value);
|
||||
public void SetVariable(String name, bool3 value) => SetVariable<bool3>(name, value);
|
||||
public void SetVariable(String name, bool4 value) => SetVariable<bool4>(name, value);
|
||||
|
||||
public void SetVariable(String name, int32 value) => SetVariable<int32>(name, value);
|
||||
public void SetVariable(String name, int2 value) => SetVariable<int2>(name, value);
|
||||
public void SetVariable(String name, int3 value) => SetVariable<int3>(name, value);
|
||||
public void SetVariable(String name, int4 value) => SetVariable<int4>(name, value);
|
||||
|
||||
public void SetVariable(String name, uint32 value) => SetVariable<uint32>(name, value);
|
||||
public void SetVariable(String name, uint2 value) => SetVariable<uint2>(name, value);
|
||||
public void SetVariable(String name, uint3 value) => SetVariable<uint3>(name, value);
|
||||
public void SetVariable(String name, uint4 value) => SetVariable<uint4>(name, value);
|
||||
|
||||
public void SetVariable(String name, float value) => SetVariable<float>(name, value);
|
||||
public void SetVariable(String name, float2 value) => SetVariable<float2>(name, value);
|
||||
public void SetVariable(String name, float3 value) => SetVariable<float3>(name, value);
|
||||
public void SetVariable(String name, float4 value) => SetVariable<float4>(name, value);
|
||||
|
||||
public void SetVariable(String name, int32 value) => SetVariable<int32>(name, value);
|
||||
public void SetVariable(String name, Int2 value) => SetVariable<Int2>(name, value);
|
||||
public void SetVariable(String name, Int3 value) => SetVariable<Int3>(name, value);
|
||||
public void SetVariable(String name, Int4 value) => SetVariable<Int4>(name, value);
|
||||
|
||||
public void SetVariable(String name, uint32 value) => SetVariable<uint32>(name, value);
|
||||
/*public void SetVariable(String name, float value) => SetVariable<double>(name, value);
|
||||
public void SetVariable(String name, float2 value) => SetVariable<float2>(name, value);
|
||||
public void SetVariable(String name, float3 value) => SetVariable<float3>(name, value);
|
||||
public void SetVariable(String name, float4 value) => SetVariable<float4>(name, value);*/
|
||||
|
||||
public void SetVariable(String name, Color value) => SetVariable<ColorRGBA>(name, (ColorRGBA)value);
|
||||
public void SetVariable(String name, ColorRGB value) => SetVariable<ColorRGB>(name, value);
|
||||
@@ -216,15 +229,14 @@ public class Material : Asset
|
||||
}
|
||||
|
||||
// Supporeted types
|
||||
// Float, Float2, Float3, Float4
|
||||
// Bool, Bool2, Bool3, Bool4
|
||||
// Int, int2, int3, int4
|
||||
// UInt, UInt2, UInt3, UInt4
|
||||
// Color, ColorRGB, ColorRGBA
|
||||
// Int, Int2, Int3, Int4
|
||||
// UInt
|
||||
// Float, Float2, Float3, Float4
|
||||
// Matrix3x3, Matrix4x3, Matrix
|
||||
|
||||
// TODO: Add missing variable types
|
||||
// UInt2, UInt3, UInt4
|
||||
// Bool, Bool2, Bool3, Bool4
|
||||
// Half, Half2, Half3, Half4
|
||||
// Byte, Byte2, Byte3, Byte4
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using GlitchyEngine.Core;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using GlitchyEngine.Math;
|
||||
using GlitchyEngine.Content;
|
||||
|
||||
namespace GlitchyEngine.Renderer
|
||||
{
|
||||
@@ -107,6 +108,28 @@ namespace GlitchyEngine.Renderer
|
||||
case Depth = D24_UNorm_S8_UInt;
|
||||
|
||||
public bool IsDepth => HasFlag(DepthMarker);
|
||||
|
||||
public bool IsInt()
|
||||
{
|
||||
switch(this)
|
||||
{
|
||||
case R8_SInt:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsUInt()
|
||||
{
|
||||
switch(this)
|
||||
{
|
||||
case R32_UInt:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ClearColor
|
||||
@@ -129,7 +152,7 @@ namespace GlitchyEngine.Renderer
|
||||
}
|
||||
}
|
||||
|
||||
public struct TargetDescription
|
||||
public struct TargetDescription : IDisposable
|
||||
{
|
||||
public RenderTargetFormat Format = .None;
|
||||
|
||||
@@ -140,22 +163,30 @@ namespace GlitchyEngine.Renderer
|
||||
public ClearColor ClearColor = .Default;
|
||||
|
||||
public SamplerStateDescription SamplerDescription = .();
|
||||
|
||||
public String DebugName = null;
|
||||
|
||||
public this() { }
|
||||
|
||||
public this(RenderTargetFormat format, bool isSwapchainTarget = false, bool isShaderReadable = true, ClearColor clearColor = .Default, SamplerStateDescription samplerDescription = .())
|
||||
public this(RenderTargetFormat format, bool isSwapchainTarget = false, bool isShaderReadable = true, ClearColor clearColor = .Default, SamplerStateDescription samplerDescription = .(), String ownDebugName = null)
|
||||
{
|
||||
Format = format;
|
||||
IsSwapchainTarget = isSwapchainTarget;
|
||||
IsShaderReadable = isShaderReadable;
|
||||
SamplerDescription = samplerDescription;
|
||||
ClearColor = clearColor;
|
||||
DebugName = ownDebugName;
|
||||
}
|
||||
|
||||
public static implicit operator Self(RenderTargetFormat format)
|
||||
{
|
||||
return Self(format);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
delete DebugName;
|
||||
}
|
||||
}
|
||||
|
||||
public struct RenderTargetGroupDescription
|
||||
@@ -181,15 +212,22 @@ namespace GlitchyEngine.Renderer
|
||||
}
|
||||
}
|
||||
|
||||
public class RenderTargetGroup : RefCounter
|
||||
public class RenderTargetGroup : Asset
|
||||
{
|
||||
internal RenderTargetGroupDescription _description;
|
||||
|
||||
internal TargetDescription[] _colorTargetDescriptions ~ delete _;
|
||||
internal TargetDescription[] _colorTargetDescriptions ~ {
|
||||
for (var desc in _)
|
||||
{
|
||||
desc.Dispose();
|
||||
}
|
||||
|
||||
delete _;
|
||||
};
|
||||
internal SamplerState[] _colorSamplerStates ~ DeleteContainerAndReleaseItems!(_);
|
||||
internal SamplerState _depthSamplerState ~ _?.ReleaseRef();
|
||||
|
||||
internal TargetDescription _depthTargetDescription;
|
||||
internal TargetDescription _depthTargetDescription ~ _.Dispose();
|
||||
|
||||
public uint32 Width => _description.Width;
|
||||
public uint32 Height => _description.Height;
|
||||
@@ -201,6 +239,8 @@ namespace GlitchyEngine.Renderer
|
||||
public int TargetCount => _colorTargetDescriptions.Count + (_depthTargetDescription.Format.IsDepth ? 1 : 0);
|
||||
public int ColorTargetCount => _colorTargetDescriptions.Count;
|
||||
|
||||
public bool HasDepth => _depthTargetDescription.Format.IsDepth;
|
||||
|
||||
[AllowAppend]
|
||||
public this(RenderTargetGroupDescription description)
|
||||
{
|
||||
@@ -265,6 +305,18 @@ namespace GlitchyEngine.Renderer
|
||||
return PlatformGetViewBinding(index);
|
||||
}
|
||||
|
||||
public TargetDescription GetTargetDescription(int index)
|
||||
{
|
||||
if (index == -1 && HasDepth)
|
||||
{
|
||||
return _depthTargetDescription;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _colorTargetDescriptions[index];
|
||||
}
|
||||
}
|
||||
|
||||
protected extern TextureViewBinding PlatformGetViewBinding(int index);
|
||||
|
||||
protected extern Result<void> PlatformGetData(void* destination, uint32 elementSize,
|
||||
@@ -278,6 +330,6 @@ namespace GlitchyEngine.Renderer
|
||||
return PlatformGetData(data, (.)sizeof(T), left, top, width, height, renderTarget, arraySlice, mipSlice);
|
||||
}
|
||||
|
||||
public extern void CopyTo(RenderTargetGroup destination, int dstTarget, Int2 dstTopLeft, Int2 size, Int2 srcTopLeft, int srcTarget);
|
||||
public extern void CopyTo(RenderTargetGroup destination, int dstTarget, int2 dstTopLeft, int2 size, int2 srcTopLeft, int srcTarget);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace GlitchyEngine.Renderer
|
||||
public uint32 Width => _width;
|
||||
public uint32 Height => _height;
|
||||
|
||||
public Int2 Size => .(_width, _height);
|
||||
public uint2 Size => .(_width, _height);
|
||||
|
||||
public RenderTargetGroup Target ~ _?.ReleaseRef();
|
||||
|
||||
@@ -47,25 +47,28 @@ namespace GlitchyEngine.Renderer
|
||||
RenderTargetGroupDescription targetDesc = .(width, height,
|
||||
TargetDescription[](
|
||||
// RGB: Albedo A: Transparency
|
||||
.(RenderTargetFormat.R8G8B8A8_UNorm){SamplerDescription = samplerDesc},
|
||||
.(RenderTargetFormat.R8G8B8A8_UNorm, ownDebugName: new String("RGB: Albedo A: Alpha")){SamplerDescription = samplerDesc},
|
||||
// RG: TextureNormal.XY | BA: GeoNrm.XY
|
||||
.(RenderTargetFormat.R16G16B16A16_SNorm){SamplerDescription = samplerDesc},
|
||||
.(RenderTargetFormat.R16G16B16A16_SNorm, ownDebugName: new String("RG: TextureNormal.XY | BA: GeoNrm.XY")){SamplerDescription = samplerDesc},
|
||||
// R: GeoNrm.Z | GBA: GeoTan.XYZ
|
||||
.(RenderTargetFormat.R16G16B16A16_SNorm){SamplerDescription = samplerDesc},
|
||||
.(RenderTargetFormat.R16G16B16A16_SNorm, ownDebugName: new String("R: GeoNrm.Z | GBA: GeoTan.XYZ")){SamplerDescription = samplerDesc},
|
||||
// RGB: world position XYZ | A: 1.0
|
||||
.(RenderTargetFormat.R32G32B32A32_Float){SamplerDescription = samplerDesc},
|
||||
.(RenderTargetFormat.R32G32B32A32_Float, ownDebugName: new String("RGB: world position XYZ | A: 1.0")){SamplerDescription = samplerDesc},
|
||||
// RGB: emissive light and color | A: unused
|
||||
.(RenderTargetFormat.R16G16B16A16_Float){SamplerDescription = samplerDesc},
|
||||
.(RenderTargetFormat.R16G16B16A16_Float, ownDebugName: new String("RGB: emissive light and color | A: unused")){SamplerDescription = samplerDesc},
|
||||
// R: Metallicity | G: Roughness | B: Ambient | A: Unused
|
||||
.(RenderTargetFormat.R8G8B8A8_UNorm){SamplerDescription = samplerDesc},
|
||||
.(RenderTargetFormat.R8G8B8A8_UNorm, ownDebugName: new String("R: Metallicity | G: Roughness | B: Ambient | A: Unused")){SamplerDescription = samplerDesc},
|
||||
// EntityId : Needed for editor picking. Simply pass through the EntityId.
|
||||
.(RenderTargetFormat.R32_UInt){SamplerDescription = samplerDesc, ClearColor = .UInt(uint32.MaxValue)},
|
||||
.(RenderTargetFormat.R32_UInt, ownDebugName: new String("EntityId")){SamplerDescription = samplerDesc, ClearColor = .UInt(uint32.MaxValue)},
|
||||
),
|
||||
TargetDescription(.D24_UNorm_S8_UInt){
|
||||
TargetDescription(.D24_UNorm_S8_UInt, ownDebugName: new String("DepthStencil")){
|
||||
SamplerDescription = samplerDesc,
|
||||
ClearColor = .DepthStencil(1.0f, 0)
|
||||
});
|
||||
Target = new RenderTargetGroup(targetDesc);
|
||||
// TODO: there needs to be a proper way to do it
|
||||
Target.[Friend]Identifier = "GBuffer";
|
||||
Content.ManageAsset(Target, null);
|
||||
}
|
||||
|
||||
_width = width;
|
||||
@@ -120,6 +123,7 @@ namespace GlitchyEngine.Renderer
|
||||
RenderCommand.Init();
|
||||
Renderer2D.Init();
|
||||
FullscreenQuad.Init();
|
||||
Quad.Init();
|
||||
|
||||
InitLineRenderer();
|
||||
InitDeferredRenderer();
|
||||
@@ -135,6 +139,7 @@ namespace GlitchyEngine.Renderer
|
||||
|
||||
FullscreenQuad.Deinit();
|
||||
Renderer2D.Deinit();
|
||||
Quad.Deinit();
|
||||
}
|
||||
|
||||
static void InitLineRenderer()
|
||||
@@ -301,7 +306,6 @@ namespace GlitchyEngine.Renderer
|
||||
|
||||
RenderCommand.SetBlendState(_gBufferBlend);
|
||||
|
||||
|
||||
for (SubmittedMesh entry in _queue)
|
||||
{
|
||||
Debug.Profiler.ProfileRendererScope!("Draw Mesh");
|
||||
@@ -378,7 +382,7 @@ namespace GlitchyEngine.Renderer
|
||||
_lights.Clear();
|
||||
|
||||
// Copy EntityIDs to compositionTarget
|
||||
_gBuffer.Target.CopyTo(_sceneConstants.CompositionTarget, 1, Int2.Zero, Int2(_sceneConstants.CameraTarget.Width, _sceneConstants.CameraTarget.Height), Int2.Zero, 6);
|
||||
_gBuffer.Target.CopyTo(_sceneConstants.CompositionTarget, 1, int2.Zero, int2((int32)_sceneConstants.CameraTarget.Width, (int32)_sceneConstants.CameraTarget.Height), int2.Zero, 6);
|
||||
|
||||
RenderCommand.SetBlendState(_gBufferBlend);
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace GlitchyEngine.Renderer
|
||||
|
||||
public this(Texture2D texture) : this(_texture, .(0, 0, 1, 1)) { }
|
||||
|
||||
public this(Texture2D texture, Int2 topLeft, Int2 size) :
|
||||
public this(Texture2D texture, int2 topLeft, int2 size) :
|
||||
this(texture,
|
||||
{
|
||||
float2 texSize = float2(texture.Width, texture.Height);
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace GlitchyEngine.Renderer.Text
|
||||
public FT_UInt GlyphIndex;
|
||||
|
||||
// TODO: Consider using floats
|
||||
public Int3 MapCoord;
|
||||
public int3 MapCoord;
|
||||
public int32 Width, Height;
|
||||
|
||||
public double TranslationX, TranslationY;
|
||||
@@ -49,10 +49,10 @@ namespace GlitchyEngine.Renderer.Text
|
||||
private int32 _faceIndex;
|
||||
private bool _hasColor;
|
||||
|
||||
private Int3 _penPos;
|
||||
private int3 _penPos;
|
||||
private int32 _lastRowHeight;
|
||||
internal Texture2D _atlas ~ _?.ReleaseRef();
|
||||
private Int3 _atlasSize;
|
||||
private int3 _atlasSize;
|
||||
|
||||
private Dictionary<char32, GlyphDescriptor> _glyphs = new .() ~ delete _;//DeleteDictionaryAndValues!(_);
|
||||
private Dictionary<uint32, GlyphDescriptor> _glyphsById = new .() ~ DeleteDictionaryAndValues!(_);
|
||||
@@ -335,14 +335,14 @@ namespace GlitchyEngine.Renderer.Text
|
||||
UpdateAtlas();
|
||||
}
|
||||
|
||||
Int3 PrepareAtlas()
|
||||
int3 PrepareAtlas()
|
||||
{
|
||||
Debug.Profiler.ProfileResourceFunction!();
|
||||
|
||||
const uint32 maxRes = 16384; // D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION
|
||||
const uint32 maxArray = 2048; // D3D11_REQ_TEXTURE2D_ARRAY_AXIS_DIMENSION
|
||||
|
||||
ref Int3 pen = ref _penPos;
|
||||
ref int3 pen = ref _penPos;
|
||||
ref int32 rowHeight = ref _lastRowHeight;
|
||||
|
||||
int32 atlasWidth = _atlasSize.X;
|
||||
@@ -422,10 +422,10 @@ namespace GlitchyEngine.Renderer.Text
|
||||
{
|
||||
Debug.Profiler.ProfileResourceFunction!();
|
||||
|
||||
Int3 oldAtlasSize = _atlasSize;
|
||||
int3 oldAtlasSize = _atlasSize;
|
||||
_atlasSize = PrepareAtlas();
|
||||
|
||||
if(_atlasSize != oldAtlasSize)
|
||||
if(any(_atlasSize != oldAtlasSize))
|
||||
{
|
||||
Debug.Profiler.ProfileResourceScope!("Recreate Atlas");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user