using System.Numerics;
using System.Runtime.InteropServices;
using Microsoft.UI.Composition;
using Vortice.D3DCompiler;
using Vortice.Direct3D;
using Vortice.Direct3D11;
using Vortice.DXGI;
using Vortice.Mathematics;
using Vortice.WinUI.Composition;
using XFEEDToolBox.Core.Rendering;
using XFEEDToolBox.Core.Galaxy;
using static Vortice.Direct3D11.D3D11;
namespace XFEEDToolBox.Rendering;
[StructLayout(LayoutKind.Sequential)]
internal readonly record struct GalaxyStar(Vector2 Position,float Radius,float Kind,Vector4 Colour,Vector4 World=default,float OriginalSize=0,bool PowerTerritorySize=false,int OriginalSprite=13,Vector4? OriginalColour=null,bool OriginalBlackHole=false);
[StructLayout(LayoutKind.Sequential)]
internal readonly record struct GalaxyStellarSprite(Vector2 Position,float Radius,float Kind,Vector4 Colour,Vector4 Appearance);
[StructLayout(LayoutKind.Sequential)]
internal readonly record struct GalaxyLineVertex(Vector2 Position,float Distance,float Dashed,Vector4 Colour);
/// <summary>One instanced star draw and one line draw, independent of the number of retained XAML/Composition nodes.</summary>
internal sealed partial class GalaxyGraphics : IDisposable
{
private readonly ID3D11Device device;
private readonly ID3D11DeviceContext context;
private readonly ID3D11Buffer starBuffer,lineBuffer,constants;
private readonly ID3D11VertexShader starsVs,linesVs,skyVs,overviewVs;
private readonly ID3D11PixelShader starsPs,linesPs,skyPs,overviewPs,inspectSkyPs;
private readonly ID3D11InputLayout starsLayout,linesLayout;
private readonly ID3D11BlendState additive;
private readonly ID3D11SamplerState sampler;
private readonly ID3D11RasterizerState rasterizer;
private readonly ID3D11DepthStencilState noDepth;
private readonly CompositionGraphicsDevice compositionDevice;
private readonly ICompositionDrawingSurfaceInterop interop;
private readonly BeginDraw beginDraw;
private readonly ResizeDraw resizeDraw;
private ID3D11Texture2D? pixels,skyTexture;
private ID3D11RenderTargetView? target;
private ID3D11ShaderResourceView? sky;
private ID3D11Texture2D? densityTexture;
private ID3D11ShaderResourceView? density;
private GalaxyDensityMap? densityMap;
private int width,height;
private long nextSkyCheck;
private readonly GalaxyStellarSprite[] stellarSprites=new GalaxyStellarSprite[StarCapacity];
public CompositionDrawingSurface Surface { get; }
public const int StarCapacity=65536,LineVertexCapacity=52000;
public long Frames { get; private set; }
public int DrawCalls { get; private set; }
internal bool UnresolvedStarBackgroundEnabled {get;private set;}
public string SkyboxSource { get; private set; }="";
public GalaxyDensityMap? Overview=>densityMap;
internal (int Width,int Height,byte[] Pixels) ReadPixels()
{
if(pixels is null)throw new InvalidOperationException("星图尚未绘制。");
var description=pixels.Description;description.BindFlags=BindFlags.None;description.CPUAccessFlags=CpuAccessFlags.Read;
description.Usage=ResourceUsage.Staging;description.MiscFlags=ResourceOptionFlags.None;
using var staging=device.CreateTexture2D(description);context.CopyResource(staging,pixels);
var mapped=context.Map(staging,0,MapMode.Read);
try
{
var result=new byte[width*height*4];
for(var y=0;y<height;y++)Marshal.Copy(mapped.DataPointer+(int)(y*mapped.RowPitch),result,y*width*4,width*4);
return(width,height,result);
}
finally{context.Unmap(staging,0);}
}
internal GalaxyGraphics(Compositor compositor)
{
D3D11CreateDevice(IntPtr.Zero,DriverType.Hardware,DeviceCreationFlags.BgraSupport,
[FeatureLevel.Level_11_1,FeatureLevel.Level_11_0],out device,out _,out context).CheckError();
using(var unknown=new SharpGen.Runtime.ComObject(WinRT.MarshalInspectable<Compositor>.FromManaged(compositor)))
using(var api=unknown.QueryInterface<ICompositorInterop>())
{api.CreateGraphicsDevice(device,out var created).CheckError();compositionDevice=created!;}
Surface=compositionDevice.CreateDrawingSurface(new Windows.Foundation.Size(1,1),
Microsoft.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized,Microsoft.Graphics.DirectX.DirectXAlphaMode.Premultiplied);
using(var unknown=new SharpGen.Runtime.ComObject(WinRT.MarshalInspectable<CompositionDrawingSurface>.FromManaged(Surface)))
interop=unknown.QueryInterface<ICompositionDrawingSurfaceInterop>();
beginDraw=Marshal.GetDelegateForFunctionPointer<BeginDraw>(Marshal.ReadIntPtr(Marshal.ReadIntPtr(interop.NativePointer),3*IntPtr.Size));
resizeDraw=Marshal.GetDelegateForFunctionPointer<ResizeDraw>(Marshal.ReadIntPtr(Marshal.ReadIntPtr(interop.NativePointer),5*IntPtr.Size));
starBuffer=device.CreateBuffer(StarCapacity*48,BindFlags.VertexBuffer,ResourceUsage.Dynamic,CpuAccessFlags.Write);
lineBuffer=device.CreateBuffer(LineVertexCapacity*32,BindFlags.VertexBuffer,ResourceUsage.Dynamic,CpuAccessFlags.Write);
constants=device.CreateBuffer(80,BindFlags.ConstantBuffer);
var shader=Path.Combine(AppContext.BaseDirectory,"Assets","GalaxyMap.hlsl");
InitializeNebulae(shader);
InitializeGalacticZones(shader);
InitializePowerContours(shader);
InitializeOriginalSprites();
InitializeOriginalBlackHoles(Path.Combine(Path.GetDirectoryName(shader)!,"GalaxyBlackHole.hlsl"));
InitializeOriginalPowerActivityRing(shader);
InitializeOriginalPowerActivityBillboard(shader);
InitializeOriginalMilkyWay(shader);
InitializeOriginalGrid(shader);
InitializeOriginalCurrentTrade(shader);
InitializeOriginalCore(Path.Combine(Path.GetDirectoryName(shader)!,"GalaxyCore.hlsl"));
InitializeOriginalHdr(Path.Combine(Path.GetDirectoryName(shader)!,"GalaxyHdr.hlsl"));
InitializeOriginalExposure(shader);
InitializeOriginalPlane(Path.Combine(Path.GetDirectoryName(shader)!,"GalaxyPlane.hlsl"));
var a=Compiler.CompileFromFile(shader,"VSStar","vs_5_0");starsVs=device.CreateVertexShader(a.Span);
starsLayout=device.CreateInputLayout([
new InputElementDescription("POSITION",0,Format.R32G32_Float,0,0,InputClassification.PerInstanceData,1),
new InputElementDescription("RADIUS",0,Format.R32G32_Float,8,0,InputClassification.PerInstanceData,1),
new InputElementDescription("COLOR",0,Format.R32G32B32A32_Float,16,0,InputClassification.PerInstanceData,1),
new InputElementDescription("APPEARANCE",0,Format.R32G32B32A32_Float,32,0,InputClassification.PerInstanceData,1)],a.Span);
var b=Compiler.CompileFromFile(shader,"VSLine","vs_5_0");linesVs=device.CreateVertexShader(b.Span);
linesLayout=device.CreateInputLayout([
new InputElementDescription("POSITION",0,Format.R32G32_Float,0,0),
new InputElementDescription("TEXCOORD",0,Format.R32G32_Float,8,0),
new InputElementDescription("COLOR",0,Format.R32G32B32A32_Float,16,0)],b.Span);
starsPs=device.CreatePixelShader(Compiler.CompileFromFile(shader,"PSStar","ps_5_0").Span);
linesPs=device.CreatePixelShader(Compiler.CompileFromFile(shader,"PSLine","ps_5_0").Span);
skyVs=device.CreateVertexShader(Compiler.CompileFromFile(shader,"VSSky","vs_5_0").Span);
skyPs=device.CreatePixelShader(Compiler.CompileFromFile(shader,"PSSky","ps_5_0").Span);
inspectSkyPs=device.CreatePixelShader(Compiler.CompileFromFile(shader,"PSInspectSky","ps_5_0").Span);
overviewVs=device.CreateVertexShader(Compiler.CompileFromFile(shader,"VSOverview","vs_5_0").Span);
overviewPs=device.CreatePixelShader(Compiler.CompileFromFile(shader,"PSOverview","ps_5_0").Span);
additive=device.CreateBlendState(new BlendDescription(Blend.One,Blend.InverseSourceAlpha));
sampler=device.CreateSamplerState(new SamplerDescription(Filter.MinMagMipLinear,TextureAddressMode.Clamp,TextureAddressMode.Clamp,TextureAddressMode.Clamp));
rasterizer=device.CreateRasterizerState(new RasterizerDescription(CullMode.None,FillMode.Solid));
noDepth=device.CreateDepthStencilState(new DepthStencilDescription(false,DepthWriteMask.Zero,ComparisonFunction.Always));
}
private void Resize(int w,int h)
{
if(width==w&&height==h)return;
context.OMSetRenderTargets(Array.Empty<ID3D11RenderTargetView>());target?.Dispose();pixels?.Dispose();
Marshal.ThrowExceptionForHR(resizeDraw(interop.NativePointer,new(w,h)));width=w;height=h;
pixels=device.CreateTexture2D(Format.B8G8R8A8_UNorm,(uint)w,(uint)h,mipLevels:1,bindFlags:BindFlags.RenderTarget);
target=device.CreateRenderTargetView(pixels);
}
private unsafe void Upload<T>(ID3D11Buffer buffer,ReadOnlySpan<T> data) where T:unmanaged
{
if(data.Length==0)return;
var mapped=context.Map(buffer,0,MapMode.WriteDiscard);
try{var bytes=MemoryMarshal.AsBytes(data);bytes.CopyTo(new Span<byte>((void*)mapped.DataPointer,bytes.Length));}
finally{context.Unmap(buffer,0);}
}
public void Draw(ReadOnlySpan<GalaxyStar> stars,ReadOnlySpan<GalaxyLineVertex> lines,double w,double h,double raster,double yaw,double pitch,Vector3 centre,float scale,float panX,float panY,bool realistic=true,bool showBackground=true,bool showUnresolvedStars=true,bool navigationMarkers=true)
{
UnresolvedStarBackgroundEnabled=showBackground&&showUnresolvedStars;
Resize(Math.Clamp((int)Math.Ceiling(w*raster),1,7680),Math.Clamp((int)Math.Ceiling(h*raster),1,4320));
RefreshSky();
PrepareNebulae(w,h,yaw,pitch,centre,scale,panX,panY,showBackground);
var backgroundCamera=GalaxyCamera.Create(new(centre.X,centre.Y,centre.Z),w,h,yaw,pitch,scale,panX,panY);
// Pass the same recovered Core lens/clip values to every world pass.
// optionsZW and densityExtentZW were unused in the normal map b0.
var camera=new Camera(new((float)w,(float)h,(float)yaw,(float)pitch),new(sky is null?0:1,density is null?0:1,(float)backgroundCamera.FocalLength,(float)backgroundCamera.Distance),
new(centre,scale),new(panX,panY,(float)(densityMap?.OriginX??0),(float)(densityMap?.OriginZ??0)),
new((float)(densityMap?.ExtentX??1),(float)(densityMap?.ExtentZ??1),(float)backgroundCamera.Near,(float)backgroundCamera.ProjectionFar));
context.UpdateSubresource(in camera,constants);context.VSSetConstantBuffer(0,constants);context.PSSetConstantBuffer(0,constants);
context.OMSetRenderTargets(target!);context.RSSetViewport(new Viewport(width,height));context.RSSetState(rasterizer);context.OMSetDepthStencilState(noDepth);context.OMSetBlendState(null);
context.ClearRenderTargetView(target!,new Color4(.011f,.014f,.019f,1));DrawCalls=0;
// The captured cockpit cube is only 256px/face and includes the local
// resolved star field. Magnifying it here creates ten-pixel square stars
// and duplicates catalogue stars. Keep it available for resource
// inspection, but draw map stars exclusively from the real catalogue.
context.OMSetBlendState(additive);
OriginalSceneHdrDrawn=false;
OriginalBackgroundHdrDrawn=false;
BeginOriginalHdr(width,height,new Color4(0,0,0,1));
BindOriginalSourceOutput(GalaxyOriginalHdr.WriteScale,false);
if(showBackground)
{
DrawOriginalPlane(backgroundCamera,true,GalaxyOriginalHdr.WriteScale);
DrawOriginalCore(backgroundCamera,true,GalaxyOriginalHdr.WriteScale,GalaxyOriginalHdr.ReadScale);
DrawOriginalMilkyWay(backgroundCamera,true,GalaxyOriginalHdr.WriteScale);
OriginalBackgroundHdrDrawn=true;
}
if(showBackground&&showUnresolvedStars&&!OriginalMilkyWayReady&&density is not null&&scale<.2f)
{
context.IASetInputLayout(null);context.IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
context.VSSetShader(overviewVs);context.PSSetShader(overviewPs);context.PSSetShaderResource(1,density);context.PSSetSampler(0,sampler);context.Draw(6,0);
context.PSSetShaderResource(1,null!);DrawCalls++;
}
// The legacy nebula/zone paths explicitly adapt RGB to scene storage;
// their source materials remain incomplete. Recovered passes below
// use their actual RGB-only Write and independent alpha/blend state.
DrawNebulae(originalHdrTarget);
DrawGalacticZones();
DrawPowerContours();
ComputeOriginalManualExposure();
DrawOriginalPowerActivityBillboard(backgroundCamera,GalaxyOriginalHdr.WriteScale);
DrawOriginalPowerActivityRing(backgroundCamera,GalaxyOriginalHdr.WriteScale);
DrawOriginalGrid(backgroundCamera,GalaxyOriginalHdr.WriteScale);
DrawOriginalCurrentTrade(backgroundCamera,GalaxyOriginalHdr.WriteScale);
if(stars.Length>0)
{
BindOriginalSourceOutput(GalaxyOriginalHdr.WriteScale);
Upload(starBuffer,PrepareStellarSprites(ApplyNebulaTransmission(stars),backgroundCamera,realistic));context.IASetInputLayout(starsLayout);context.IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
context.IASetVertexBuffer(0,starBuffer,48);context.VSSetShader(starsVs);context.PSSetShader(starsPs);
context.PSSetShaderResource(7,originalSprites!);context.PSSetSampler(0,sampler);
context.DrawInstanced(6,(uint)stars.Length,0,0);context.PSSetShaderResource(7,null!);DrawCalls++;
}
DrawOriginalBlackHoles(stars,backgroundCamera,realistic,navigationMarkers,GalaxyOriginalHdr.WriteScale);
EndOriginalHdr(target!);context.OMSetBlendState(additive);OriginalSceneHdrDrawn=true;
// Tool navigation strokes are screen overlays, after scene tone mapping.
if(lines.Length>0)
{
Upload(lineBuffer,lines);context.IASetInputLayout(linesLayout);context.IASetPrimitiveTopology(PrimitiveTopology.LineList);
context.IASetVertexBuffer(0,lineBuffer,32);context.VSSetShader(linesVs);context.PSSetShader(linesPs);context.Draw((uint)lines.Length,0);DrawCalls++;
}
context.OMSetRenderTargets(Array.Empty<ID3D11RenderTargetView>());
var iid=typeof(ID3D11Texture2D).GUID;Marshal.ThrowExceptionForHR(beginDraw(interop.NativePointer,IntPtr.Zero,in iid,out var texture,out var offset));
using(var destination=new ID3D11Texture2D(texture))
{
try{context.CopySubresourceRegion(destination,0,(uint)offset.X,(uint)offset.Y,0,pixels!,0,new Box(0,0,0,width,height,1));context.Flush();}
finally{interop.EndDraw().CheckError();}
}
Frames++;
}
private ReadOnlySpan<GalaxyStellarSprite> PrepareStellarSprites(ReadOnlySpan<GalaxyStar> stars,GalaxyCamera camera,bool realistic)
{
var focus=camera.Focus;var forward=camera.Forward;var eye=camera.Eye;
for(var i=0;i<stars.Length;i++)
{
var star=stars[i];
// Original RGB is floating point (some palette entries exceed1).
// Its alpha0 is a real hidden style, not an absent colour.
var colour=star.OriginalColour??star.Colour;
if(star.Radius<=0||colour.W<=0||!float.IsFinite(colour.X+colour.Y+colour.Z+colour.W))
{stellarSprites[i]=new(star.Position,0,0,Vector4.Zero,Vector4.Zero);continue;}
// Actual physical types41/42 use their separate BackgroundBlackHole
// material. Retain only the tool's independent navigation glyph here.
if(realistic&&star.OriginalBlackHole)
{stellarSprites[i]=new(star.Position,star.Kind>=2?26:0,star.Kind,Vector4.Zero,Vector4.Zero);continue;}
var dx=star.World.X-focus.X;var dy=star.World.Y-focus.Y;var dz=star.World.Z-focus.Z;
var spectral=star.World.W>0?(int)MathF.Round(star.World.W)-1:13;
var projectedScale=camera.FocalLength/Math.Max(camera.Near,camera.Distance+dx*forward.X+dy*forward.Y+dz*forward.Z);
var originalPhysical=realistic&&float.IsFinite(star.OriginalSize)&&star.OriginalSize>0&&spectral!=12;
if(!realistic||originalPhysical)
{
// Actual Galaxy chain3A441A0→39FB140→3C01470→3C10980
// supplies six UV/corner vertices with cornerZW=±1. Producer
// 3A431C0 supplies half(styleSize); original VS multiplies them.
// No estimated profile, size floor, zoom curve or4x expansion.
var size=originalPhysical?star.OriginalSize:star.Radius;
if(star.PowerTerritorySize&&!realistic)
{
var ex=star.World.X-eye.X;var ey=star.World.Y-eye.Y;var ez=star.World.Z-eye.Z;
size*=StellarAppearance.PowerTerritoryDistanceScale(Math.Sqrt(ex*ex+ey*ey+ez*ez));
}
var halfExtent=StellarAppearance.OriginalProjectedHalfExtent(size,projectedScale);
stellarSprites[i]=new(star.Position,halfExtent>0?Math.Max(halfExtent,star.Kind>=2?26:0):0,star.Kind,colour,
new(halfExtent,Math.Clamp(star.OriginalSprite,0,27),originalPhysical?2:0,0));
}
else
{
// Public catalogue rows without native physical appearance
// retain an explicit estimate; they cannot claim a native branch.
var profile=StellarAppearance.Sprite(spectral,Math.Sqrt(dx*dx+dy*dy+dz*dz),projectedScale,(int)star.Kind);
var relative=float.IsFinite(star.Radius)?Math.Clamp(star.Radius,.01f,8):1;
profile=profile with{CoreRadius=profile.CoreRadius*relative,Radius=Math.Max(profile.Radius*relative,star.Kind>=2?26:0)};
stellarSprites[i]=new(star.Position,profile.Radius,star.Kind,colour with{W=colour.W*profile.Opacity},
new(profile.CoreRadius,(float)profile.Morphology,1,profile.Corona));
}
}
return stellarSprites.AsSpan(0,stars.Length);
}
private void RefreshSky()
{
if(Environment.TickCount64<nextSkyCheck)return;nextSkyCheck=Environment.TickCount64+2000;
if(density is null)
{
var mapPath=Path.Combine(App.Runtime.DataDirectory,"galaxy","galaxy-density.bin");
if(File.Exists(mapPath))
{
var map=GalaxyDensityMap.Read(mapPath);var pinned=GCHandle.Alloc(map.Values,GCHandleType.Pinned);
try{densityTexture=device.CreateTexture2D(new Texture2DDescription{Width=(uint)map.Width,Height=(uint)map.Height,MipLevels=1,ArraySize=1,Format=map.FloatValues?Format.R32_Float:Format.R16_UNorm,
SampleDescription=new(1,0),Usage=ResourceUsage.Immutable,BindFlags=BindFlags.ShaderResource},[new SubresourceData(pinned.AddrOfPinnedObject(),(uint)(map.Width*(map.FloatValues?4:2)),0)]);
density=device.CreateShaderResourceView(densityTexture);densityMap=map;}
finally{pinned.Free();}
}
}
}
private void LoadSky(string path)
{
var cube=GameSkyboxCube.Read(path);
var description=new Texture2DDescription{Width=(uint)cube.Size,Height=(uint)cube.Size,ArraySize=6,MipLevels=1,Format=(Format)cube.Format,
SampleDescription=new(1,0),Usage=ResourceUsage.Immutable,BindFlags=BindFlags.ShaderResource,MiscFlags=ResourceOptionFlags.TextureCube};
var handles=new GCHandle[6];var data=new SubresourceData[6];
try
{
for(var i=0;i<6;i++){handles[i]=GCHandle.Alloc(cube.Faces[i],GCHandleType.Pinned);data[i]=new(handles[i].AddrOfPinnedObject(),(uint)cube.RowPitch,0);}
var texture=device.CreateTexture2D(description,data);var view=device.CreateShaderResourceView(texture);
sky?.Dispose();skyTexture?.Dispose();skyTexture=texture;sky=view;SkyboxSource="游戏实际天空立方体";
}
finally{foreach(var handle in handles)if(handle.IsAllocated)handle.Free();}
}
internal SkyboxPixels.Metrics[] InspectSky(string path)
{
LoadSky(path);Resize(256,256);var measurements=new SkyboxPixels.Metrics[6];
context.OMSetRenderTargets(target!);context.RSSetViewport(new Viewport(width,height));context.RSSetState(rasterizer);
context.OMSetDepthStencilState(noDepth);context.OMSetBlendState(null);context.IASetInputLayout(null);context.IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
context.VSSetShader(skyVs);context.PSSetShader(inspectSkyPs);context.PSSetShaderResource(0,sky!);context.PSSetSampler(0,sampler);
for(var face=0;face<6;face++)
{
var camera=new Camera(new(256,256,0,0),new(0,0,face,0),default,default,default);
context.UpdateSubresource(in camera,constants);context.VSSetConstantBuffer(0,constants);context.PSSetConstantBuffer(0,constants);context.Draw(3,0);
var pixels=ReadPixels();measurements[face]=SkyboxPixels.Inspect(pixels.Pixels,pixels.Width,pixels.Height);
}
context.PSSetShaderResource(0,null!);context.OMSetRenderTargets(Array.Empty<ID3D11RenderTargetView>());return measurements;
}
public void Dispose()
{
context.ClearState();context.Flush();target?.Dispose();pixels?.Dispose();sky?.Dispose();skyTexture?.Dispose();
DisposeNebulae();
DisposeGalacticZones();
DisposePowerContours();
DisposeOriginalSprites();
DisposeOriginalBlackHoles();
DisposeOriginalExposure();
DisposeOriginalPowerActivityRing();
DisposeOriginalPowerActivityBillboard();
DisposeOriginalMilkyWay();
DisposeOriginalGrid();
DisposeOriginalCurrentTrade();
DisposeOriginalCore();
DisposeOriginalHdr();
DisposeOriginalPlane();
interop.Dispose();Surface.Dispose();compositionDevice.Dispose();sampler.Dispose();rasterizer.Dispose();noDepth.Dispose();additive.Dispose();starsLayout.Dispose();linesLayout.Dispose();
starsVs.Dispose();linesVs.Dispose();skyVs.Dispose();overviewVs.Dispose();starsPs.Dispose();linesPs.Dispose();skyPs.Dispose();overviewPs.Dispose();inspectSkyPs.Dispose();density?.Dispose();densityTexture?.Dispose();starBuffer.Dispose();lineBuffer.Dispose();constants.Dispose();context.Dispose();device.Dispose();
}
[StructLayout(LayoutKind.Sequential)] private readonly record struct Camera(Vector4 Viewport,Vector4 Options,Vector4 CentreScale,Vector4 PanOrigin,Vector4 DensityExtent);
[StructLayout(LayoutKind.Sequential)] private readonly record struct PixelSize(int Width,int Height);
[UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int BeginDraw(nint surface,nint rectangle,in Guid iid,out nint texture,out Int2 offset);
[UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int ResizeDraw(nint surface,PixelSize size);
}
using System.Numerics;
using System.Runtime.InteropServices;
using Microsoft.UI.Composition;
using Vortice.D3DCompiler;
using Vortice.Direct3D;
using Vortice.Direct3D11;
using Vortice.DXGI;
using Vortice.Mathematics;
using Vortice.WinUI.Composition;
using XFEEDToolBox.Core.Rendering;
using XFEEDToolBox.Core.Galaxy;
using static Vortice.Direct3D11.D3D11;
namespace XFEEDToolBox.Rendering;
[StructLayout(LayoutKind.Sequential)]
internal readonly record struct GalaxyStar(Vector2 Position,float Radius,float Kind,Vector4 Colour,Vector4 World=default,float OriginalSize=0,bool PowerTerritorySize=false,int OriginalSprite=13,Vector4? OriginalColour=null,bool OriginalBlackHole=false);
[StructLayout(LayoutKind.Sequential)]
internal readonly record struct GalaxyStellarSprite(Vector2 Position,float Radius,float Kind,Vector4 Colour,Vector4 Appearance);
[StructLayout(LayoutKind.Sequential)]
internal readonly record struct GalaxyLineVertex(Vector2 Position,float Distance,float Dashed,Vector4 Colour);
/// <summary>One instanced star draw and one line draw, independent of the number of retained XAML/Composition nodes.</summary>
internal sealed partial class GalaxyGraphics : IDisposable
{
private readonly ID3D11Device device;
private readonly ID3D11DeviceContext context;
private readonly ID3D11Buffer starBuffer,lineBuffer,constants;
private readonly ID3D11VertexShader starsVs,linesVs,skyVs,overviewVs;
private readonly ID3D11PixelShader starsPs,linesPs,skyPs,overviewPs,inspectSkyPs;
private readonly ID3D11InputLayout starsLayout,linesLayout;
private readonly ID3D11BlendState additive;
private readonly ID3D11SamplerState sampler;
private readonly ID3D11RasterizerState rasterizer;
private readonly ID3D11DepthStencilState noDepth;
private readonly CompositionGraphicsDevice compositionDevice;
private readonly ICompositionDrawingSurfaceInterop interop;
private readonly BeginDraw beginDraw;
private readonly ResizeDraw resizeDraw;
private ID3D11Texture2D? pixels,skyTexture;
private ID3D11RenderTargetView? target;
private ID3D11ShaderResourceView? sky;
private ID3D11Texture2D? densityTexture;
private ID3D11ShaderResourceView? density;
private GalaxyDensityMap? densityMap;
private int width,height;
private long nextSkyCheck;
private readonly GalaxyStellarSprite[] stellarSprites=new GalaxyStellarSprite[StarCapacity];
public CompositionDrawingSurface Surface { get; }
public const int StarCapacity=65536,LineVertexCapacity=52000;
public long Frames { get; private set; }
public int DrawCalls { get; private set; }
internal bool UnresolvedStarBackgroundEnabled {get;private set;}
public string SkyboxSource { get; private set; }="";
public GalaxyDensityMap? Overview=>densityMap;
internal (int Width,int Height,byte[] Pixels) ReadPixels()
{
if(pixels is null)throw new InvalidOperationException("星图尚未绘制。");
var description=pixels.Description;description.BindFlags=BindFlags.None;description.CPUAccessFlags=CpuAccessFlags.Read;
description.Usage=ResourceUsage.Staging;description.MiscFlags=ResourceOptionFlags.None;
using var staging=device.CreateTexture2D(description);context.CopyResource(staging,pixels);
var mapped=context.Map(staging,0,MapMode.Read);
try
{
var result=new byte[width*height*4];
for(var y=0;y<height;y++)Marshal.Copy(mapped.DataPointer+(int)(y*mapped.RowPitch),result,y*width*4,width*4);
return(width,height,result);
}
finally{context.Unmap(staging,0);}
}
internal GalaxyGraphics(Compositor compositor)
{
D3D11CreateDevice(IntPtr.Zero,DriverType.Hardware,DeviceCreationFlags.BgraSupport,
[FeatureLevel.Level_11_1,FeatureLevel.Level_11_0],out device,out _,out context).CheckError();
using(var unknown=new SharpGen.Runtime.ComObject(WinRT.MarshalInspectable<Compositor>.FromManaged(compositor)))
using(var api=unknown.QueryInterface<ICompositorInterop>())
{api.CreateGraphicsDevice(device,out var created).CheckError();compositionDevice=created!;}
Surface=compositionDevice.CreateDrawingSurface(new Windows.Foundation.Size(1,1),
Microsoft.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized,Microsoft.Graphics.DirectX.DirectXAlphaMode.Premultiplied);
using(var unknown=new SharpGen.Runtime.ComObject(WinRT.MarshalInspectable<CompositionDrawingSurface>.FromManaged(Surface)))
interop=unknown.QueryInterface<ICompositionDrawingSurfaceInterop>();
beginDraw=Marshal.GetDelegateForFunctionPointer<BeginDraw>(Marshal.ReadIntPtr(Marshal.ReadIntPtr(interop.NativePointer),3*IntPtr.Size));
resizeDraw=Marshal.GetDelegateForFunctionPointer<ResizeDraw>(Marshal.ReadIntPtr(Marshal.ReadIntPtr(interop.NativePointer),5*IntPtr.Size));
starBuffer=device.CreateBuffer(StarCapacity*48,BindFlags.VertexBuffer,ResourceUsage.Dynamic,CpuAccessFlags.Write);
lineBuffer=device.CreateBuffer(LineVertexCapacity*32,BindFlags.VertexBuffer,ResourceUsage.Dynamic,CpuAccessFlags.Write);
constants=device.CreateBuffer(80,BindFlags.ConstantBuffer);
var shader=Path.Combine(AppContext.BaseDirectory,"Assets","GalaxyMap.hlsl");
InitializeNebulae(shader);
InitializeGalacticZones(shader);
InitializePowerContours(shader);
InitializeOriginalSprites();
InitializeOriginalBlackHoles(Path.Combine(Path.GetDirectoryName(shader)!,"GalaxyBlackHole.hlsl"));
InitializeOriginalPowerActivityRing(shader);
InitializeOriginalPowerActivityBillboard(shader);
InitializeOriginalMilkyWay(shader);
InitializeOriginalGrid(shader);
InitializeOriginalCurrentTrade(shader);
InitializeOriginalCore(Path.Combine(Path.GetDirectoryName(shader)!,"GalaxyCore.hlsl"));
InitializeOriginalHdr(Path.Combine(Path.GetDirectoryName(shader)!,"GalaxyHdr.hlsl"));
InitializeOriginalExposure(shader);
InitializeOriginalPlane(Path.Combine(Path.GetDirectoryName(shader)!,"GalaxyPlane.hlsl"));
var a=Compiler.CompileFromFile(shader,"VSStar","vs_5_0");starsVs=device.CreateVertexShader(a.Span);
starsLayout=device.CreateInputLayout([
new InputElementDescription("POSITION",0,Format.R32G32_Float,0,0,InputClassification.PerInstanceData,1),
new InputElementDescription("RADIUS",0,Format.R32G32_Float,8,0,InputClassification.PerInstanceData,1),
new InputElementDescription("COLOR",0,Format.R32G32B32A32_Float,16,0,InputClassification.PerInstanceData,1),
new InputElementDescription("APPEARANCE",0,Format.R32G32B32A32_Float,32,0,InputClassification.PerInstanceData,1)],a.Span);
var b=Compiler.CompileFromFile(shader,"VSLine","vs_5_0");linesVs=device.CreateVertexShader(b.Span);
linesLayout=device.CreateInputLayout([
new InputElementDescription("POSITION",0,Format.R32G32_Float,0,0),
new InputElementDescription("TEXCOORD",0,Format.R32G32_Float,8,0),
new InputElementDescription("COLOR",0,Format.R32G32B32A32_Float,16,0)],b.Span);
starsPs=device.CreatePixelShader(Compiler.CompileFromFile(shader,"PSStar","ps_5_0").Span);
linesPs=device.CreatePixelShader(Compiler.CompileFromFile(shader,"PSLine","ps_5_0").Span);
skyVs=device.CreateVertexShader(Compiler.CompileFromFile(shader,"VSSky","vs_5_0").Span);
skyPs=device.CreatePixelShader(Compiler.CompileFromFile(shader,"PSSky","ps_5_0").Span);
inspectSkyPs=device.CreatePixelShader(Compiler.CompileFromFile(shader,"PSInspectSky","ps_5_0").Span);
overviewVs=device.CreateVertexShader(Compiler.CompileFromFile(shader,"VSOverview","vs_5_0").Span);
overviewPs=device.CreatePixelShader(Compiler.CompileFromFile(shader,"PSOverview","ps_5_0").Span);
additive=device.CreateBlendState(new BlendDescription(Blend.One,Blend.InverseSourceAlpha));
sampler=device.CreateSamplerState(new SamplerDescription(Filter.MinMagMipLinear,TextureAddressMode.Clamp,TextureAddressMode.Clamp,TextureAddressMode.Clamp));
rasterizer=device.CreateRasterizerState(new RasterizerDescription(CullMode.None,FillMode.Solid));
noDepth=device.CreateDepthStencilState(new DepthStencilDescription(false,DepthWriteMask.Zero,ComparisonFunction.Always));
}
private void Resize(int w,int h)
{
if(width==w&&height==h)return;
context.OMSetRenderTargets(Array.Empty<ID3D11RenderTargetView>());target?.Dispose();pixels?.Dispose();
Marshal.ThrowExceptionForHR(resizeDraw(interop.NativePointer,new(w,h)));width=w;height=h;
pixels=device.CreateTexture2D(Format.B8G8R8A8_UNorm,(uint)w,(uint)h,mipLevels:1,bindFlags:BindFlags.RenderTarget);
target=device.CreateRenderTargetView(pixels);
}
private unsafe void Upload<T>(ID3D11Buffer buffer,ReadOnlySpan<T> data) where T:unmanaged
{
if(data.Length==0)return;
var mapped=context.Map(buffer,0,MapMode.WriteDiscard);
try{var bytes=MemoryMarshal.AsBytes(data);bytes.CopyTo(new Span<byte>((void*)mapped.DataPointer,bytes.Length));}
finally{context.Unmap(buffer,0);}
}
public void Draw(ReadOnlySpan<GalaxyStar> stars,ReadOnlySpan<GalaxyLineVertex> lines,double w,double h,double raster,double yaw,double pitch,Vector3 centre,float scale,float panX,float panY,bool realistic=true,bool showBackground=true,bool showUnresolvedStars=true,bool navigationMarkers=true)
{
UnresolvedStarBackgroundEnabled=showBackground&&showUnresolvedStars;
Resize(Math.Clamp((int)Math.Ceiling(w*raster),1,7680),Math.Clamp((int)Math.Ceiling(h*raster),1,4320));
RefreshSky();
PrepareNebulae(w,h,yaw,pitch,centre,scale,panX,panY,showBackground);
var backgroundCamera=GalaxyCamera.Create(new(centre.X,centre.Y,centre.Z),w,h,yaw,pitch,scale,panX,panY);
// Pass the same recovered Core lens/clip values to every world pass.
// optionsZW and densityExtentZW were unused in the normal map b0.
var camera=new Camera(new((float)w,(float)h,(float)yaw,(float)pitch),new(sky is null?0:1,density is null?0:1,(float)backgroundCamera.FocalLength,(float)backgroundCamera.Distance),
new(centre,scale),new(panX,panY,(float)(densityMap?.OriginX??0),(float)(densityMap?.OriginZ??0)),
new((float)(densityMap?.ExtentX??1),(float)(densityMap?.ExtentZ??1),(float)backgroundCamera.Near,(float)backgroundCamera.ProjectionFar));
context.UpdateSubresource(in camera,constants);context.VSSetConstantBuffer(0,constants);context.PSSetConstantBuffer(0,constants);
context.OMSetRenderTargets(target!);context.RSSetViewport(new Viewport(width,height));context.RSSetState(rasterizer);context.OMSetDepthStencilState(noDepth);context.OMSetBlendState(null);
context.ClearRenderTargetView(target!,new Color4(.011f,.014f,.019f,1));DrawCalls=0;
// The captured cockpit cube is only 256px/face and includes the local
// resolved star field. Magnifying it here creates ten-pixel square stars
// and duplicates catalogue stars. Keep it available for resource
// inspection, but draw map stars exclusively from the real catalogue.
context.OMSetBlendState(additive);
OriginalSceneHdrDrawn=false;
OriginalBackgroundHdrDrawn=false;
BeginOriginalHdr(width,height,new Color4(0,0,0,1));
BindOriginalSourceOutput(GalaxyOriginalHdr.WriteScale,false);
if(showBackground)
{
DrawOriginalPlane(backgroundCamera,true,GalaxyOriginalHdr.WriteScale);
DrawOriginalCore(backgroundCamera,true,GalaxyOriginalHdr.WriteScale,GalaxyOriginalHdr.ReadScale);
DrawOriginalMilkyWay(backgroundCamera,true,GalaxyOriginalHdr.WriteScale);
OriginalBackgroundHdrDrawn=true;
}
if(showBackground&&showUnresolvedStars&&!OriginalMilkyWayReady&&density is not null&&scale<.2f)
{
context.IASetInputLayout(null);context.IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
context.VSSetShader(overviewVs);context.PSSetShader(overviewPs);context.PSSetShaderResource(1,density);context.PSSetSampler(0,sampler);context.Draw(6,0);
context.PSSetShaderResource(1,null!);DrawCalls++;
}
// The legacy nebula/zone paths explicitly adapt RGB to scene storage;
// their source materials remain incomplete. Recovered passes below
// use their actual RGB-only Write and independent alpha/blend state.
DrawNebulae(originalHdrTarget);
DrawGalacticZones();
DrawPowerContours();
ComputeOriginalManualExposure();
DrawOriginalPowerActivityBillboard(backgroundCamera,GalaxyOriginalHdr.WriteScale);
DrawOriginalPowerActivityRing(backgroundCamera,GalaxyOriginalHdr.WriteScale);
DrawOriginalGrid(backgroundCamera,GalaxyOriginalHdr.WriteScale);
DrawOriginalCurrentTrade(backgroundCamera,GalaxyOriginalHdr.WriteScale);
if(stars.Length>0)
{
BindOriginalSourceOutput(GalaxyOriginalHdr.WriteScale);
Upload(starBuffer,PrepareStellarSprites(ApplyNebulaTransmission(stars),backgroundCamera,realistic));context.IASetInputLayout(starsLayout);context.IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
context.IASetVertexBuffer(0,starBuffer,48);context.VSSetShader(starsVs);context.PSSetShader(starsPs);
context.PSSetShaderResource(7,originalSprites!);context.PSSetSampler(0,sampler);
context.DrawInstanced(6,(uint)stars.Length,0,0);context.PSSetShaderResource(7,null!);DrawCalls++;
}
DrawOriginalBlackHoles(stars,backgroundCamera,realistic,navigationMarkers,GalaxyOriginalHdr.WriteScale);
EndOriginalHdr(target!);context.OMSetBlendState(additive);OriginalSceneHdrDrawn=true;
// Tool navigation strokes are screen overlays, after scene tone mapping.
if(lines.Length>0)
{
Upload(lineBuffer,lines);context.IASetInputLayout(linesLayout);context.IASetPrimitiveTopology(PrimitiveTopology.LineList);
context.IASetVertexBuffer(0,lineBuffer,32);context.VSSetShader(linesVs);context.PSSetShader(linesPs);context.Draw((uint)lines.Length,0);DrawCalls++;
}
context.OMSetRenderTargets(Array.Empty<ID3D11RenderTargetView>());
var iid=typeof(ID3D11Texture2D).GUID;Marshal.ThrowExceptionForHR(beginDraw(interop.NativePointer,IntPtr.Zero,in iid,out var texture,out var offset));
using(var destination=new ID3D11Texture2D(texture))
{
try{context.CopySubresourceRegion(destination,0,(uint)offset.X,(uint)offset.Y,0,pixels!,0,new Box(0,0,0,width,height,1));context.Flush();}
finally{interop.EndDraw().CheckError();}
}
Frames++;
}
private ReadOnlySpan<GalaxyStellarSprite> PrepareStellarSprites(ReadOnlySpan<GalaxyStar> stars,GalaxyCamera camera,bool realistic)
{
var focus=camera.Focus;var forward=camera.Forward;var eye=camera.Eye;
for(var i=0;i<stars.Length;i++)
{
var star=stars[i];
// Original RGB is floating point (some palette entries exceed1).
// Its alpha0 is a real hidden style, not an absent colour.
var colour=star.OriginalColour??star.Colour;
if(star.Radius<=0||colour.W<=0||!float.IsFinite(colour.X+colour.Y+colour.Z+colour.W))
{stellarSprites[i]=new(star.Position,0,0,Vector4.Zero,Vector4.Zero);continue;}
// Actual physical types41/42 use their separate BackgroundBlackHole
// material. Retain only the tool's independent navigation glyph here.
if(realistic&&star.OriginalBlackHole)
{stellarSprites[i]=new(star.Position,star.Kind>=2?26:0,star.Kind,Vector4.Zero,Vector4.Zero);continue;}
var dx=star.World.X-focus.X;var dy=star.World.Y-focus.Y;var dz=star.World.Z-focus.Z;
var spectral=star.World.W>0?(int)MathF.Round(star.World.W)-1:13;
var projectedScale=camera.FocalLength/Math.Max(camera.Near,camera.Distance+dx*forward.X+dy*forward.Y+dz*forward.Z);
var originalPhysical=realistic&&float.IsFinite(star.OriginalSize)&&star.OriginalSize>0&&spectral!=12;
if(!realistic||originalPhysical)
{
// Actual Galaxy chain3A441A0→39FB140→3C01470→3C10980
// supplies six UV/corner vertices with cornerZW=±1. Producer
// 3A431C0 supplies half(styleSize); original VS multiplies them.
// No estimated profile, size floor, zoom curve or4x expansion.
var size=originalPhysical?star.OriginalSize:star.Radius;
if(star.PowerTerritorySize&&!realistic)
{
var ex=star.World.X-eye.X;var ey=star.World.Y-eye.Y;var ez=star.World.Z-eye.Z;
size*=StellarAppearance.PowerTerritoryDistanceScale(Math.Sqrt(ex*ex+ey*ey+ez*ez));
}
var halfExtent=StellarAppearance.OriginalProjectedHalfExtent(size,projectedScale);
stellarSprites[i]=new(star.Position,halfExtent>0?Math.Max(halfExtent,star.Kind>=2?26:0):0,star.Kind,colour,
new(halfExtent,Math.Clamp(star.OriginalSprite,0,27),originalPhysical?2:0,0));
}
else
{
// Public catalogue rows without native physical appearance
// retain an explicit estimate; they cannot claim a native branch.
var profile=StellarAppearance.Sprite(spectral,Math.Sqrt(dx*dx+dy*dy+dz*dz),projectedScale,(int)star.Kind);
var relative=float.IsFinite(star.Radius)?Math.Clamp(star.Radius,.01f,8):1;
profile=profile with{CoreRadius=profile.CoreRadius*relative,Radius=Math.Max(profile.Radius*relative,star.Kind>=2?26:0)};
stellarSprites[i]=new(star.Position,profile.Radius,star.Kind,colour with{W=colour.W*profile.Opacity},
new(profile.CoreRadius,(float)profile.Morphology,1,profile.Corona));
}
}
return stellarSprites.AsSpan(0,stars.Length);
}
private void RefreshSky()
{
if(Environment.TickCount64<nextSkyCheck)return;nextSkyCheck=Environment.TickCount64+2000;
if(density is null)
{
var mapPath=Path.Combine(App.Runtime.DataDirectory,"galaxy","galaxy-density.bin");
if(File.Exists(mapPath))
{
var map=GalaxyDensityMap.Read(mapPath);var pinned=GCHandle.Alloc(map.Values,GCHandleType.Pinned);
try{densityTexture=device.CreateTexture2D(new Texture2DDescription{Width=(uint)map.Width,Height=(uint)map.Height,MipLevels=1,ArraySize=1,Format=map.FloatValues?Format.R32_Float:Format.R16_UNorm,
SampleDescription=new(1,0),Usage=ResourceUsage.Immutable,BindFlags=BindFlags.ShaderResource},[new SubresourceData(pinned.AddrOfPinnedObject(),(uint)(map.Width*(map.FloatValues?4:2)),0)]);
density=device.CreateShaderResourceView(densityTexture);densityMap=map;}
finally{pinned.Free();}
}
}
}
private void LoadSky(string path)
{
var cube=GameSkyboxCube.Read(path);
var description=new Texture2DDescription{Width=(uint)cube.Size,Height=(uint)cube.Size,ArraySize=6,MipLevels=1,Format=(Format)cube.Format,
SampleDescription=new(1,0),Usage=ResourceUsage.Immutable,BindFlags=BindFlags.ShaderResource,MiscFlags=ResourceOptionFlags.TextureCube};
var handles=new GCHandle[6];var data=new SubresourceData[6];
try
{
for(var i=0;i<6;i++){handles[i]=GCHandle.Alloc(cube.Faces[i],GCHandleType.Pinned);data[i]=new(handles[i].AddrOfPinnedObject(),(uint)cube.RowPitch,0);}
var texture=device.CreateTexture2D(description,data);var view=device.CreateShaderResourceView(texture);
sky?.Dispose();skyTexture?.Dispose();skyTexture=texture;sky=view;SkyboxSource="游戏实际天空立方体";
}
finally{foreach(var handle in handles)if(handle.IsAllocated)handle.Free();}
}
internal SkyboxPixels.Metrics[] InspectSky(string path)
{
LoadSky(path);Resize(256,256);var measurements=new SkyboxPixels.Metrics[6];
context.OMSetRenderTargets(target!);context.RSSetViewport(new Viewport(width,height));context.RSSetState(rasterizer);
context.OMSetDepthStencilState(noDepth);context.OMSetBlendState(null);context.IASetInputLayout(null);context.IASetPrimitiveTopology(PrimitiveTopology.TriangleList);
context.VSSetShader(skyVs);context.PSSetShader(inspectSkyPs);context.PSSetShaderResource(0,sky!);context.PSSetSampler(0,sampler);
for(var face=0;face<6;face++)
{
var camera=new Camera(new(256,256,0,0),new(0,0,face,0),default,default,default);
context.UpdateSubresource(in camera,constants);context.VSSetConstantBuffer(0,constants);context.PSSetConstantBuffer(0,constants);context.Draw(3,0);
var pixels=ReadPixels();measurements[face]=SkyboxPixels.Inspect(pixels.Pixels,pixels.Width,pixels.Height);
}
context.PSSetShaderResource(0,null!);context.OMSetRenderTargets(Array.Empty<ID3D11RenderTargetView>());return measurements;
}
public void Dispose()
{
context.ClearState();context.Flush();target?.Dispose();pixels?.Dispose();sky?.Dispose();skyTexture?.Dispose();
DisposeNebulae();
DisposeGalacticZones();
DisposePowerContours();
DisposeOriginalSprites();
DisposeOriginalBlackHoles();
DisposeOriginalExposure();
DisposeOriginalPowerActivityRing();
DisposeOriginalPowerActivityBillboard();
DisposeOriginalMilkyWay();
DisposeOriginalGrid();
DisposeOriginalCurrentTrade();
DisposeOriginalCore();
DisposeOriginalHdr();
DisposeOriginalPlane();
interop.Dispose();Surface.Dispose();compositionDevice.Dispose();sampler.Dispose();rasterizer.Dispose();noDepth.Dispose();additive.Dispose();starsLayout.Dispose();linesLayout.Dispose();
starsVs.Dispose();linesVs.Dispose();skyVs.Dispose();overviewVs.Dispose();starsPs.Dispose();linesPs.Dispose();skyPs.Dispose();overviewPs.Dispose();inspectSkyPs.Dispose();density?.Dispose();densityTexture?.Dispose();starBuffer.Dispose();lineBuffer.Dispose();constants.Dispose();context.Dispose();device.Dispose();
}
[StructLayout(LayoutKind.Sequential)] private readonly record struct Camera(Vector4 Viewport,Vector4 Options,Vector4 CentreScale,Vector4 PanOrigin,Vector4 DensityExtent);
[StructLayout(LayoutKind.Sequential)] private readonly record struct PixelSize(int Width,int Height);
[UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int BeginDraw(nint surface,nint rectangle,in Guid iid,out nint texture,out Int2 offset);
[UnmanagedFunctionPointer(CallingConvention.StdCall)] private delegate int ResizeDraw(nint surface,PixelSize size);
}