XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Numerics;
using System.Runtime.InteropServices;
using Vortice.D3DCompiler;
using Vortice.Direct3D;
using Vortice.Direct3D11;
using Vortice.DXGI;
using Vortice.Mathematics;
using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Rendering;

namespace XFEEDToolBox.Rendering;

internal sealed partial class GalaxyGraphics
{
    private const int NebulaBudget=12;
    private ID3D11VertexShader nebulaVs=null!;
    private ID3D11PixelShader nebulaPs=null!,nebulaCompositePs=null!;
    private ID3D11Buffer nebulaConstants=null!;
    private ID3D11Texture2D? fogTexture;
    private ID3D11RenderTargetView? fogTarget;
    private ID3D11ShaderResourceView? fogView;
    private readonly Dictionary<string,NebulaGpu> nebulaTextures=new(StringComparer.OrdinalIgnoreCase);
    private readonly HashSet<string> requestedNebulae=new(StringComparer.OrdinalIgnoreCase);
    private readonly List<NebulaDraw> visibleNebulae=new(NebulaBudget);
    private readonly List<NebulaDraw> nebulaCandidates=new(172);
    private readonly GalaxyStar[] attenuatedStars=new GalaxyStar[StarCapacity];
    private Vector3 nebulaEye;
    private bool disposed;
    internal event Action? ResourcesChanged;
    internal int VisibleNebulae=>visibleNebulae.Count;
    internal int PendingNebulaLoads {get;private set;}
    internal int ResidentNebulaTextures=>nebulaTextures.Count;
    internal long NebulaGpuBytes=>nebulaTextures.Count*(64L*64*64*4+256*16);
    internal bool NebulaeEnabled {get;set;}=true;
    private void InitializeNebulae(string shader)
    {
        nebulaVs=device.CreateVertexShader(Compiler.CompileFromFile(shader,"VSNebula","vs_5_0").Span);
        nebulaPs=device.CreatePixelShader(Compiler.CompileFromFile(shader,"PSNebula","ps_5_0").Span);
        nebulaCompositePs=device.CreatePixelShader(Compiler.CompileFromFile(shader,"PSNebulaComposite","ps_5_0").Span);
        nebulaConstants=device.CreateBuffer(64,BindFlags.ConstantBuffer);
    }
    private void PrepareNebulae(double w,double h,double yaw,double pitch,Vector3 centre,float scale,float panX,float panY,bool showBackground=true)
    {
        visibleNebulae.Clear();nebulaCandidates.Clear();PendingNebulaLoads=0;if(!showBackground||!NebulaeEnabled||scale<=0)return;
        var camera=GalaxyCamera.Create(new(centre.X,centre.Y,centre.Z),w,h,yaw,pitch,scale,panX,panY);
        var right=new Vector3((float)camera.Right.X,(float)camera.Right.Y,(float)camera.Right.Z);
        var up=new Vector3((float)camera.Up.X,(float)camera.Up.Y,(float)camera.Up.Z);
        var depth=new Vector3((float)camera.Forward.X,(float)camera.Forward.Y,(float)camera.Forward.Z);
        nebulaEye=new((float)camera.Eye.X,(float)camera.Eye.Y,(float)camera.Eye.Z);
        var focal=(float)camera.FocalLength;var near=(float)camera.Near;
        foreach(var nebula in EliteNebulae.Reference.Nebulae)
        {
            var relative=nebula.Centre-nebulaEye;var centreDepth=Vector3.Dot(relative,depth);var worldRadius=nebula.HalfSize*1.732051f;
            if(centreDepth+worldRadius<=near)continue;
            Vector4 rect;
            if(centreDepth-worldRadius<=near)rect=new(0,0,(float)w,(float)h);
            else
            {
                var x=(float)w/2+Vector3.Dot(relative,right)*focal/centreDepth;var y=(float)h/2-Vector3.Dot(relative,up)*focal/centreDepth;
                var radius=worldRadius*focal/(centreDepth-worldRadius);
                if(radius<1.5f||x+radius<0||y+radius<0||x-radius>w||y-radius>h)continue;
                rect=new(Math.Max(0,x-radius),Math.Max(0,y-radius),Math.Min((float)w,x+radius),Math.Min((float)h,y+radius));
            }
            nebulaCandidates.Add(new(nebula,relative,rect,centreDepth,right,up,depth));
        }
        // A small distant cloud cannot evict the large cloud currently filling the viewport.
        nebulaCandidates.Sort((a,b)=>((b.Rect.Z-b.Rect.X)*(b.Rect.W-b.Rect.Y)).CompareTo((a.Rect.Z-a.Rect.X)*(a.Rect.W-a.Rect.Y)));
        var selected=nebulaCandidates.Take(NebulaBudget).ToArray();var keep=selected.Select(x=>x.Nebula.Template).ToHashSet(StringComparer.OrdinalIgnoreCase);
        foreach(var key in nebulaTextures.Keys.Where(k=>!keep.Contains(k)).ToArray()){nebulaTextures[key].Dispose();nebulaTextures.Remove(key);}
        foreach(var draw in selected)
        {
            var name=draw.Nebula.Template;
            if(!nebulaTextures.ContainsKey(name))
            {
                var task=LocalShipModels.LoadNebulaAsync(name);
                if(!task.IsCompleted)PendingNebulaLoads++;
                if(task.IsCompletedSuccessfully)nebulaTextures[name]=CreateNebulaTexture(task.Result);
                else if(requestedNebulae.Add(name))_ = task.ContinueWith(completed=>
                {
                    App.MainWindow.DispatcherQueue.TryEnqueue(()=>
                    {
                        if(disposed)return;
                        if(completed.IsFaulted)App.Runtime.Log("星云资源",completed.Exception!.GetBaseException().Message);
                        ResourcesChanged?.Invoke();
                    });
                },TaskScheduler.Default);
            }
            if(nebulaTextures.ContainsKey(name))visibleNebulae.Add(draw);
        }
        visibleNebulae.Sort((a,b)=>b.Depth.CompareTo(a.Depth));
    }
    private NebulaGpu CreateNebulaTexture(NebulaVolume volume)
    {
        var pixels=GCHandle.Alloc(volume.Rgba,GCHandleType.Pinned);var transfer=GCHandle.Alloc(volume.Transfer,GCHandleType.Pinned);
        try
        {
            var texture=device.CreateTexture3D(new Texture3DDescription{Width=64,Height=64,Depth=64,MipLevels=1,Format=Format.R8G8B8A8_UNorm,
                BindFlags=BindFlags.ShaderResource,Usage=ResourceUsage.Immutable},[new SubresourceData(pixels.AddrOfPinnedObject(),256,16384)]);
            var palette=device.CreateTexture2D(new Texture2DDescription{Width=256,Height=1,MipLevels=1,ArraySize=1,Format=Format.R32G32B32A32_Float,
                SampleDescription=new(1,0),BindFlags=BindFlags.ShaderResource,Usage=ResourceUsage.Immutable},[new SubresourceData(transfer.AddrOfPinnedObject(),4096,0)]);
            return new(texture,device.CreateShaderResourceView(texture),palette,device.CreateShaderResourceView(palette),volume);
        }
        finally{pixels.Free();transfer.Free();}
    }
    private void DrawNebulae(ID3D11RenderTargetView? destination=null)
    {
        if(visibleNebulae.Count==0)return;
        var ratio=Math.Min(1,Math.Min(1280d/width,720d/height));var fw=Math.Max(1,(int)Math.Ceiling(width*ratio));var fh=Math.Max(1,(int)Math.Ceiling(height*ratio));
        if(fogTexture is null||fogTexture.Description.Width!=fw||fogTexture.Description.Height!=fh)
        {
            fogView?.Dispose();fogTarget?.Dispose();fogTexture?.Dispose();
            fogTexture=device.CreateTexture2D(Format.R8G8B8A8_UNorm,(uint)fw,(uint)fh,mipLevels:1,bindFlags:BindFlags.ShaderResource|BindFlags.RenderTarget);
            fogTarget=device.CreateRenderTargetView(fogTexture);fogView=device.CreateShaderResourceView(fogTexture);
        }
        context.OMSetRenderTargets(fogTarget!);context.RSSetViewport(new Viewport(fw,fh));context.ClearRenderTargetView(fogTarget!,new Color4(0,0,0,0));
        context.IASetInputLayout(null);context.IASetPrimitiveTopology(PrimitiveTopology.TriangleList);context.VSSetShader(nebulaVs);context.PSSetShader(nebulaPs);
        context.VSSetConstantBuffer(1,nebulaConstants);context.PSSetConstantBuffer(1,nebulaConstants);context.PSSetSampler(0,sampler);
        foreach(var draw in visibleNebulae)
        {
            var data=new NebulaCamera(new(draw.Relative,draw.Nebula.HalfSize),new(draw.Nebula.Orientation.X,draw.Nebula.Orientation.Y,draw.Nebula.Orientation.Z,draw.Nebula.Orientation.W),draw.Rect,new(40,draw.Nebula.Dark?1:0,0,0));
            context.UpdateSubresource(in data,nebulaConstants);var texture=nebulaTextures[draw.Nebula.Template];
            context.PSSetShaderResource(2,texture.VolumeView);context.PSSetShaderResource(3,texture.TransferView);context.Draw(6,0);DrawCalls++;
        }
        context.PSSetShaderResource(2,null!);context.PSSetShaderResource(3,null!);
        context.OMSetRenderTargets(destination??target!);context.RSSetViewport(new Viewport(width,height));context.VSSetShader(skyVs);context.PSSetShader(nebulaCompositePs);
        context.PSSetShaderResource(4,fogView!);context.Draw(3,0);context.PSSetShaderResource(4,null!);DrawCalls++;
    }
    private ReadOnlySpan<GalaxyStar> ApplyNebulaTransmission(ReadOnlySpan<GalaxyStar> stars)
    {
        if(visibleNebulae.Count==0)return stars;
        stars.CopyTo(attenuatedStars);
        for(var i=0;i<stars.Length;i++)
        {
            var star=stars[i];var attenuation=1f;
            foreach(var draw in visibleNebulae)
            {
                if(star.Position.X<draw.Rect.X||star.Position.Y<draw.Rect.Y||star.Position.X>draw.Rect.Z||star.Position.Y>draw.Rect.W)continue;
                var inverse=Quaternion.Conjugate(draw.Nebula.Orientation);
                var world=new Vector3(star.World.X,star.World.Y,star.World.Z);
                var local=Vector3.Transform(world-draw.Nebula.Centre,inverse)/draw.Nebula.HalfSize;
                var towardViewer=nebulaEye-world;if(towardViewer.LengthSquared()<1e-12f)continue;
                var direction=Vector3.Transform(Vector3.Normalize(towardViewer),inverse);
                attenuation*=nebulaTextures[draw.Nebula.Template].Pixels.Transmission(local,direction,towardViewer.Length()/draw.Nebula.HalfSize);
            }
            var transmission=Math.Max(.015f,attenuation);
            attenuatedStars[i]=star with{Colour=star.Colour with{W=star.Colour.W*transmission},
                OriginalColour=star.OriginalColour is{} original?original with{W=original.W*transmission}:null};
        }
        return attenuatedStars.AsSpan(0,stars.Length);
    }
    private void DisposeNebulae()
    {
        disposed=true;foreach(var texture in nebulaTextures.Values)texture.Dispose();nebulaTextures.Clear();
        fogView?.Dispose();fogTarget?.Dispose();fogTexture?.Dispose();nebulaVs.Dispose();nebulaPs.Dispose();nebulaCompositePs.Dispose();nebulaConstants.Dispose();
    }
    [StructLayout(LayoutKind.Sequential)] private readonly record struct NebulaCamera(Vector4 CentreHalf,Vector4 Orientation,Vector4 Rect,Vector4 Settings);
    private sealed record NebulaDraw(GalacticNebula Nebula,Vector3 Relative,Vector4 Rect,float Depth,Vector3 Right,Vector3 Up,Vector3 ViewDepth);
    private sealed record NebulaGpu(ID3D11Texture3D Volume,ID3D11ShaderResourceView VolumeView,ID3D11Texture2D Transfer,ID3D11ShaderResourceView TransferView,NebulaVolume Pixels):IDisposable
    {public void Dispose(){VolumeView.Dispose();Volume.Dispose();TransferView.Dispose();Transfer.Dispose();}}
}