using System.Numerics;
using System.Runtime.InteropServices;
using Vortice.D3DCompiler;
using Vortice.Direct3D;
using Vortice.Direct3D11;
using Vortice.DXGI;
using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Rendering;
namespace XFEEDToolBox.Rendering;
[StructLayout(LayoutKind.Sequential)]
internal readonly record struct GalaxyMilkyWaySprite(Vector4 WorldAlpha,Vector4 Shape,Vector4 Atlas,Vector4 Layer);
internal sealed partial class GalaxyGraphics
{
private static readonly Lazy<Task<GalaxyOriginalCloudLayer>> originalMilkyWayCpu=new(()=>Task.Run(()=>
GalaxyOriginalClouds.Generate(GalaxyOriginalClouds.Window(new(0,0,0)))));
private ID3D11VertexShader milkyWayVs=null!;
private ID3D11PixelShader milkyWayPs=null!;
private ID3D11InputLayout milkyWayLayout=null!;
private ID3D11Buffer milkyWayBuffer=null!,milkyWayConstants=null!;
private ID3D11BlendState milkyWayBlend=null!;
private readonly List<ID3D11Texture2D> milkyWayTextures=[];
private readonly List<ID3D11ShaderResourceView> milkyWayViews=[];
private int milkyWayInstances;
internal bool OriginalMilkyWayReady=>milkyWayInstances>0;
internal int OriginalMilkyWayInstances=>milkyWayInstances;
internal bool PendingOriginalMilkyWay=>!originalMilkyWayCpu.Value.IsCompleted;
internal bool OriginalMilkyWayFullRestored=>false;
internal bool OriginalDustEnabled=>false;
private void InitializeOriginalMilkyWay(string shader)
{
var vertex=Compiler.CompileFromFile(shader,"VSMilkyWay","vs_5_0");milkyWayVs=device.CreateVertexShader(vertex.Span);
milkyWayPs=device.CreatePixelShader(Compiler.CompileFromFile(shader,"PSMilkyWay","ps_5_0").Span);
milkyWayLayout=device.CreateInputLayout([
new InputElementDescription("POSITION",0,Format.R32G32B32A32_Float,0,0,InputClassification.PerInstanceData,1),
new InputElementDescription("SHAPE",0,Format.R32G32B32A32_Float,16,0,InputClassification.PerInstanceData,1),
new InputElementDescription("ATLAS",0,Format.R32G32B32A32_Float,32,0,InputClassification.PerInstanceData,1),
new InputElementDescription("LAYER",0,Format.R32G32B32A32_Float,48,0,InputClassification.PerInstanceData,1)],vertex.Span);
milkyWayConstants=device.CreateBuffer(96,BindFlags.ConstantBuffer);
milkyWayBlend=device.CreateBlendState(new BlendDescription(Blend.SourceAlpha,Blend.One));
var source=GalaxyOriginalMilkyWay.Reference;
if(!MemoryMarshal.TryGetArray(source.Payload,out ArraySegment<byte> bytes)||bytes.Array is null)throw new InvalidDataException("原银河背景纹理缓冲区不可用。");
var pin=GCHandle.Alloc(bytes.Array,GCHandleType.Pinned);
try
{
foreach(var texture in source.Textures)
{
var slices=texture.Mips.Select(m=>new SubresourceData(pin.AddrOfPinnedObject()+bytes.Offset+texture.PayloadOffset+m.Offset,(uint)m.RowPitch,(uint)m.DepthPitch)).ToArray();
var gpu=device.CreateTexture2D(new Texture2DDescription{Width=(uint)texture.Width,Height=(uint)texture.Height,ArraySize=1,
MipLevels=(uint)texture.Mips.Count,Format=Format.BC1_UNorm,SampleDescription=new(1,0),Usage=ResourceUsage.Immutable,BindFlags=BindFlags.ShaderResource},slices);
milkyWayTextures.Add(gpu);milkyWayViews.Add(device.CreateShaderResourceView(gpu));
}
}
finally{pin.Free();}
_=originalMilkyWayCpu.Value.ContinueWith(task=>
{
if(disposed)return;
App.MainWindow.DispatcherQueue.TryEnqueue(()=>
{
if(disposed)return;
if(task.IsFaulted)App.Runtime.Log("银河背景",task.Exception!.GetBaseException().Message);
ResourcesChanged?.Invoke();
});
},TaskScheduler.Default);
}
private void DrawOriginalMilkyWay(GalaxyCamera camera,bool showBackground,float hdrWrite=1)
{
if(!showBackground||!originalMilkyWayCpu.Value.IsCompletedSuccessfully)return;
if(milkyWayInstances==0)
{
var layer=originalMilkyWayCpu.Value.Result;var sprites=new GalaxyMilkyWaySprite[layer.Clouds.Count];
var atlas=GalaxyOriginalMilkyWay.Reference.AtlasTiles;
// Original renderer constructor 3A06740/3B118B0 defaults:
// alpha=min(raw,.046)*10; world half-size=rawSize*31*32;
// near200, far300000 and cell transition width1066*100.
for(var i=0;i<sprites.Length;i++)
{
var cloud=layer.Clouds[i];var tile=atlas[cloud.AtlasIndex];
sprites[i]=new(new(cloud.AbsolutePosition,Math.Clamp(Math.Min(cloud.Alpha,.046f)*10,0,1)),
new((float)(Half)cloud.Rotation,cloud.Size*31*32,200,300000),
new(tile>>8,tile&255,GalaxyOriginalMilkyWayResources.AtlasColumns,GalaxyOriginalMilkyWayResources.AtlasRows),new(106600,1,0,0));
}
milkyWayBuffer=device.CreateBuffer((uint)Math.Max(64,sprites.Length*64),BindFlags.VertexBuffer,ResourceUsage.Dynamic,CpuAccessFlags.Write);
Upload<GalaxyMilkyWaySprite>(milkyWayBuffer,sprites);milkyWayInstances=sprites.Length;
}
var eye=camera.Eye;var right=camera.Right;var up=camera.Up;var forward=camera.Forward;
var frame=new MilkyWayCamera(new((float)eye.X+49985,(float)eye.Y+40985,(float)eye.Z+24105,(float)camera.FocalLength),
new((float)right.X,(float)right.Y,(float)right.Z,(float)camera.Width),new((float)up.X,(float)up.Y,(float)up.Z,(float)camera.Height),
new((float)forward.X,(float)forward.Y,(float)forward.Z,(float)camera.Near),
// FGM material values; the original PS's shared Write multiplier
// is explicit so its SourceAlpha/One blend and final HDR Read
// remain a linear reciprocal pair, unlike Core's square blend.
new(.122f,1.96f,44902.80078125f,0),new(0,0,0,hdrWrite));
context.UpdateSubresource(in frame,milkyWayConstants);context.VSSetConstantBuffer(3,milkyWayConstants);context.PSSetConstantBuffer(3,milkyWayConstants);
context.VSSetShaderResource(10,milkyWayViews[1]);context.VSSetShaderResource(11,milkyWayViews[2]);context.VSSetSampler(0,sampler);
context.PSSetShaderResource(9,milkyWayViews[0]);context.PSSetSampler(0,sampler);context.OMSetBlendState(milkyWayBlend);
context.IASetInputLayout(milkyWayLayout);context.IASetPrimitiveTopology(PrimitiveTopology.TriangleList);context.IASetVertexBuffer(0,milkyWayBuffer,64);
context.VSSetShader(milkyWayVs);context.PSSetShader(milkyWayPs);context.DrawInstanced(6,(uint)milkyWayInstances,0,0);DrawCalls++;
context.VSSetShaderResource(10,null!);context.VSSetShaderResource(11,null!);context.PSSetShaderResource(9,null!);context.OMSetBlendState(additive);
}
private void DisposeOriginalMilkyWay()
{
milkyWayVs.Dispose();milkyWayPs.Dispose();milkyWayLayout.Dispose();milkyWayBuffer?.Dispose();milkyWayConstants.Dispose();milkyWayBlend.Dispose();
foreach(var view in milkyWayViews)view.Dispose();foreach(var texture in milkyWayTextures)texture.Dispose();
}
[StructLayout(LayoutKind.Sequential)]
private readonly record struct MilkyWayCamera(Vector4 EyeFocal,Vector4 RightWidth,Vector4 UpHeight,Vector4 ForwardNear,Vector4 Material,Vector4 Output);
}
using System.Numerics;
using System.Runtime.InteropServices;
using Vortice.D3DCompiler;
using Vortice.Direct3D;
using Vortice.Direct3D11;
using Vortice.DXGI;
using XFEEDToolBox.Core.Galaxy;
using XFEEDToolBox.Core.Rendering;
namespace XFEEDToolBox.Rendering;
[StructLayout(LayoutKind.Sequential)]
internal readonly record struct GalaxyMilkyWaySprite(Vector4 WorldAlpha,Vector4 Shape,Vector4 Atlas,Vector4 Layer);
internal sealed partial class GalaxyGraphics
{
private static readonly Lazy<Task<GalaxyOriginalCloudLayer>> originalMilkyWayCpu=new(()=>Task.Run(()=>
GalaxyOriginalClouds.Generate(GalaxyOriginalClouds.Window(new(0,0,0)))));
private ID3D11VertexShader milkyWayVs=null!;
private ID3D11PixelShader milkyWayPs=null!;
private ID3D11InputLayout milkyWayLayout=null!;
private ID3D11Buffer milkyWayBuffer=null!,milkyWayConstants=null!;
private ID3D11BlendState milkyWayBlend=null!;
private readonly List<ID3D11Texture2D> milkyWayTextures=[];
private readonly List<ID3D11ShaderResourceView> milkyWayViews=[];
private int milkyWayInstances;
internal bool OriginalMilkyWayReady=>milkyWayInstances>0;
internal int OriginalMilkyWayInstances=>milkyWayInstances;
internal bool PendingOriginalMilkyWay=>!originalMilkyWayCpu.Value.IsCompleted;
internal bool OriginalMilkyWayFullRestored=>false;
internal bool OriginalDustEnabled=>false;
private void InitializeOriginalMilkyWay(string shader)
{
var vertex=Compiler.CompileFromFile(shader,"VSMilkyWay","vs_5_0");milkyWayVs=device.CreateVertexShader(vertex.Span);
milkyWayPs=device.CreatePixelShader(Compiler.CompileFromFile(shader,"PSMilkyWay","ps_5_0").Span);
milkyWayLayout=device.CreateInputLayout([
new InputElementDescription("POSITION",0,Format.R32G32B32A32_Float,0,0,InputClassification.PerInstanceData,1),
new InputElementDescription("SHAPE",0,Format.R32G32B32A32_Float,16,0,InputClassification.PerInstanceData,1),
new InputElementDescription("ATLAS",0,Format.R32G32B32A32_Float,32,0,InputClassification.PerInstanceData,1),
new InputElementDescription("LAYER",0,Format.R32G32B32A32_Float,48,0,InputClassification.PerInstanceData,1)],vertex.Span);
milkyWayConstants=device.CreateBuffer(96,BindFlags.ConstantBuffer);
milkyWayBlend=device.CreateBlendState(new BlendDescription(Blend.SourceAlpha,Blend.One));
var source=GalaxyOriginalMilkyWay.Reference;
if(!MemoryMarshal.TryGetArray(source.Payload,out ArraySegment<byte> bytes)||bytes.Array is null)throw new InvalidDataException("原银河背景纹理缓冲区不可用。");
var pin=GCHandle.Alloc(bytes.Array,GCHandleType.Pinned);
try
{
foreach(var texture in source.Textures)
{
var slices=texture.Mips.Select(m=>new SubresourceData(pin.AddrOfPinnedObject()+bytes.Offset+texture.PayloadOffset+m.Offset,(uint)m.RowPitch,(uint)m.DepthPitch)).ToArray();
var gpu=device.CreateTexture2D(new Texture2DDescription{Width=(uint)texture.Width,Height=(uint)texture.Height,ArraySize=1,
MipLevels=(uint)texture.Mips.Count,Format=Format.BC1_UNorm,SampleDescription=new(1,0),Usage=ResourceUsage.Immutable,BindFlags=BindFlags.ShaderResource},slices);
milkyWayTextures.Add(gpu);milkyWayViews.Add(device.CreateShaderResourceView(gpu));
}
}
finally{pin.Free();}
_=originalMilkyWayCpu.Value.ContinueWith(task=>
{
if(disposed)return;
App.MainWindow.DispatcherQueue.TryEnqueue(()=>
{
if(disposed)return;
if(task.IsFaulted)App.Runtime.Log("银河背景",task.Exception!.GetBaseException().Message);
ResourcesChanged?.Invoke();
});
},TaskScheduler.Default);
}
private void DrawOriginalMilkyWay(GalaxyCamera camera,bool showBackground,float hdrWrite=1)
{
if(!showBackground||!originalMilkyWayCpu.Value.IsCompletedSuccessfully)return;
if(milkyWayInstances==0)
{
var layer=originalMilkyWayCpu.Value.Result;var sprites=new GalaxyMilkyWaySprite[layer.Clouds.Count];
var atlas=GalaxyOriginalMilkyWay.Reference.AtlasTiles;
// Original renderer constructor 3A06740/3B118B0 defaults:
// alpha=min(raw,.046)*10; world half-size=rawSize*31*32;
// near200, far300000 and cell transition width1066*100.
for(var i=0;i<sprites.Length;i++)
{
var cloud=layer.Clouds[i];var tile=atlas[cloud.AtlasIndex];
sprites[i]=new(new(cloud.AbsolutePosition,Math.Clamp(Math.Min(cloud.Alpha,.046f)*10,0,1)),
new((float)(Half)cloud.Rotation,cloud.Size*31*32,200,300000),
new(tile>>8,tile&255,GalaxyOriginalMilkyWayResources.AtlasColumns,GalaxyOriginalMilkyWayResources.AtlasRows),new(106600,1,0,0));
}
milkyWayBuffer=device.CreateBuffer((uint)Math.Max(64,sprites.Length*64),BindFlags.VertexBuffer,ResourceUsage.Dynamic,CpuAccessFlags.Write);
Upload<GalaxyMilkyWaySprite>(milkyWayBuffer,sprites);milkyWayInstances=sprites.Length;
}
var eye=camera.Eye;var right=camera.Right;var up=camera.Up;var forward=camera.Forward;
var frame=new MilkyWayCamera(new((float)eye.X+49985,(float)eye.Y+40985,(float)eye.Z+24105,(float)camera.FocalLength),
new((float)right.X,(float)right.Y,(float)right.Z,(float)camera.Width),new((float)up.X,(float)up.Y,(float)up.Z,(float)camera.Height),
new((float)forward.X,(float)forward.Y,(float)forward.Z,(float)camera.Near),
// FGM material values; the original PS's shared Write multiplier
// is explicit so its SourceAlpha/One blend and final HDR Read
// remain a linear reciprocal pair, unlike Core's square blend.
new(.122f,1.96f,44902.80078125f,0),new(0,0,0,hdrWrite));
context.UpdateSubresource(in frame,milkyWayConstants);context.VSSetConstantBuffer(3,milkyWayConstants);context.PSSetConstantBuffer(3,milkyWayConstants);
context.VSSetShaderResource(10,milkyWayViews[1]);context.VSSetShaderResource(11,milkyWayViews[2]);context.VSSetSampler(0,sampler);
context.PSSetShaderResource(9,milkyWayViews[0]);context.PSSetSampler(0,sampler);context.OMSetBlendState(milkyWayBlend);
context.IASetInputLayout(milkyWayLayout);context.IASetPrimitiveTopology(PrimitiveTopology.TriangleList);context.IASetVertexBuffer(0,milkyWayBuffer,64);
context.VSSetShader(milkyWayVs);context.PSSetShader(milkyWayPs);context.DrawInstanced(6,(uint)milkyWayInstances,0,0);DrawCalls++;
context.VSSetShaderResource(10,null!);context.VSSetShaderResource(11,null!);context.PSSetShaderResource(9,null!);context.OMSetBlendState(additive);
}
private void DisposeOriginalMilkyWay()
{
milkyWayVs.Dispose();milkyWayPs.Dispose();milkyWayLayout.Dispose();milkyWayBuffer?.Dispose();milkyWayConstants.Dispose();milkyWayBlend.Dispose();
foreach(var view in milkyWayViews)view.Dispose();foreach(var texture in milkyWayTextures)texture.Dispose();
}
[StructLayout(LayoutKind.Sequential)]
private readonly record struct MilkyWayCamera(Vector4 EyeFocal,Vector4 RightWidth,Vector4 UpHeight,Vector4 ForwardNear,Vector4 Material,Vector4 Output);
}