diff --git a/CustomResolution2782/ConfigurationEnums.cs b/CustomResolution2782/ConfigurationEnums.cs index 68b11b0..6f3c948 100644 --- a/CustomResolution2782/ConfigurationEnums.cs +++ b/CustomResolution2782/ConfigurationEnums.cs @@ -115,7 +115,7 @@ public static class MinSizeModeExt }; } -public enum ResolutionScalingMode +public enum ResolutionScalingMode : byte { Fast = 0, FSR = 1, diff --git a/CustomResolution2782/CustomResolution2782.csproj b/CustomResolution2782/CustomResolution2782.csproj index d3de3a4..4f54da6 100644 --- a/CustomResolution2782/CustomResolution2782.csproj +++ b/CustomResolution2782/CustomResolution2782.csproj @@ -19,6 +19,7 @@ false true CustomResolution + true @@ -28,9 +29,17 @@ $(DALAMUD_HOME)/ + + + + + + + + + - $(DalamudLibPath)FFXIVClientStructs.dll false @@ -71,6 +80,10 @@ $(DalamudLibPath)SharpDX.Mathematics.dll false + + $(DalamudLibPath)TerraFX.Interop.Windows.dll + false + diff --git a/CustomResolution2782/DisplaySizeState.cs b/CustomResolution2782/DisplaySizeState.cs index a16a048..e1bd7d5 100644 --- a/CustomResolution2782/DisplaySizeState.cs +++ b/CustomResolution2782/DisplaySizeState.cs @@ -6,6 +6,7 @@ using FloppyUtils; using System; using System.Runtime.InteropServices; using System.Text; +using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; using static TerraFX.Interop.Windows.Windows; @@ -421,13 +422,14 @@ public unsafe struct DeviceEx public ref uint NewHeight => ref _.NewHeight; // C# doesn't let us use ptr types as generic arguments, oh well. + public readonly ID3D11DeviceContext* D3D11DeviceContext => (ID3D11DeviceContext*) _.D3D11DeviceContext; public readonly ImmediateContextEx* ImmediateContext => (ImmediateContextEx*) _.ImmediateContext; } // ImmediateContextEx in FFXIVClientStructs is a bare minimum. // https://github.com/aers/FFXIVClientStructs/blob/ef5e9a5997671fb2c9a72cb9d57d841855f62085/FFXIVClientStructs/FFXIV/Client/Graphics/Kernel/ImmediateContext.cs [StructLayout(LayoutKind.Explicit)] -public struct ImmediateContextEx +public unsafe struct ImmediateContextEx { [FieldOffset(0)] public ImmediateContext _; diff --git a/CustomResolution2782/GameSizeState.cs b/CustomResolution2782/GameSizeState.cs index 04afe9c..5d9f7a8 100644 --- a/CustomResolution2782/GameSizeState.cs +++ b/CustomResolution2782/GameSizeState.cs @@ -1,13 +1,18 @@ -using Dalamud.Game.Config; +using CustomResolution.Shaders; +using Dalamud.Game.Config; using Dalamud.Hooking; using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; using FFXIVClientStructs.FFXIV.Client.Graphics.Render; using FFXIVClientStructs.FFXIV.Client.System.Framework; using FloppyUtils; +using SharpDX; using System; using System.Runtime.InteropServices; using System.Text; +using TerraFX.Interop.DirectX; using TerraFX.Interop.Windows; +using static System.Net.Mime.MediaTypeNames; +using Device = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Device; namespace CustomResolution; @@ -20,14 +25,26 @@ public unsafe class GameSizeState : IDisposable private Hook _icdx11ProcessCommandsHook; private Hook _ddx11PostTickHook; private Hook _taskRenderGraphicsRenderHook; + private Hook _prepareTextureHook; + private Hook _immediatePreparePSHook; + private Hook _immediatePrepareCSHook; + private Hook _createTexture2DHook; + private Hook _drawHook; private bool _wasEnabled = false; private object _renderLock = new(); private ForceUpdateRTMState _forceUpdateRTM = ForceUpdateRTMState._0_Idle; + private uint _gameplayDrawCount = 0; + private bool _drawIsGameplay = false; + + private BlitShader _blitShader; + public GameSizeState() { + _blitShader = new(); + /* Writes DynamicResolutionTargetHeight to size[1] near the end, * but if in (gpose || main menu), only scale < 1?? */ @@ -100,6 +117,45 @@ public unsafe class GameSizeState : IDisposable TaskRenderGraphicsRenderDetour ); _taskRenderGraphicsRenderHook.Enable(); + + /* Texture preparation used in a lot of places, but most notable for + * writing to the mip level count of the gameplay texture and also updating the D3D11 ptrs. + */ + _prepareTextureHook = Service.GameInteropProvider.HookFromAddress( + Service.SigScanner.ScanText("40 53 48 83 EC 20 48 8B 02 48 8B D9 48 89 41 38 48 8B 02 48 89 41 40"), + PrepareTextureDetour + ); + _prepareTextureHook.Enable(); + + /* The shader resource view of GameplayTexture gets read by these two. + */ + _immediatePreparePSHook = Service.GameInteropProvider.HookFromAddress( + Service.SigScanner.ScanText("48 8B C4 4C 89 40 18 48 89 48 08 53 56 48 83 EC 68 44 8B 5A 44 48 8D B1 E8 03 00 00 4C 89 68 D8"), + ImmediatePreparePSDetour + ); + _immediatePreparePSHook.Enable(); + _immediatePrepareCSHook = Service.GameInteropProvider.HookFromAddress( + Service.SigScanner.ScanText("48 8B C4 4C 89 40 18 48 89 48 08 53 56 48 83 EC 68 44 8B 5A 44 48 8D B1 E8 13 00 00 4C 89 68 D8"), + ImmediatePrepareCSDetour + ); + _immediatePrepareCSHook.Enable(); + + /* Hook ID3D11Device funcs based on their vtable indices, + * because FFXIV might not set some flags we would like to have set on the textures., + * and because aaaaaaaaaaaaaaaaaaaaaaaaaaaaa + */ + ID3D11Device* d3ddev = null; + dev->D3D11DeviceContext->GetDevice(&d3ddev); + _createTexture2DHook = Service.GameInteropProvider.HookFromAddress( + d3ddev->lpVtbl[5], + CreateTexture2DDetour + ); + _createTexture2DHook.Enable(); + _drawHook = Service.GameInteropProvider.HookFromAddress( + dev->D3D11DeviceContext->lpVtbl[13], + DrawDetour + ); + _drawHook.Enable(); } public bool ConfigDynRezo { get; private set; } @@ -129,6 +185,19 @@ public unsafe class GameSizeState : IDisposable // [UnmanagedFunctionPointer(CallingConvention.FastCall)] private delegate void TaskRenderGraphicsRender(); + // [UnmanagedFunctionPointer(CallingConvention.FastCall)] + private delegate ulong PrepareTexture(Texture* tex, uint* size, byte mips, uint format); + + // [UnmanagedFunctionPointer(CallingConvention.FastCall)] + private delegate void ImmediatePreparePS(ImmediateContextEx* im, PixelShader* ps, ImmediateBlitResource* data); + + // [UnmanagedFunctionPointer(CallingConvention.FastCall)] + private delegate void ImmediatePrepareCS(ImmediateContextEx* im, Shader* cs, ImmediateBlitResource* data); + + private delegate int CreateTexture2D(ID3D11Device* d3ddev, D3D11_TEXTURE2D_DESC* desc, D3D11_SUBRESOURCE_DATA* initialData, ID3D11Texture2D** tex); + + private delegate void Draw(ID3D11DeviceContext* d3dctx, uint vertexCount, uint startVertexPosition); + public void Dispose() { _rtmApplyScalingHook.Dispose(); @@ -137,6 +206,12 @@ public unsafe class GameSizeState : IDisposable _icdx11ProcessCommandsHook.Dispose(); _ddx11PostTickHook.Dispose(); _taskRenderGraphicsRenderHook.Dispose(); + _prepareTextureHook.Dispose(); + _immediatePreparePSHook.Dispose(); + _immediatePrepareCSHook.Dispose(); + _createTexture2DHook.Dispose(); + _drawHook.Dispose(); + _blitShader.Dispose(); Service.Framework.RunOnFrameworkThread(Update); } @@ -196,7 +271,7 @@ RR {dev->RequestRender} 0x{(long) dev->ImmediateContext->IfNonZeroSkipPostTickPr // TODO: Figure out a more consistent way to get to the currently allocated size. // Check if the backing RTs are the expected size. - var tex = rtm->_.Unk20[0].Value; + var tex = rtm->GameplayTexture; if (tex->AllocatedWidth != widthS || tex->AllocatedHeight != heightS) { if (!unloading) @@ -295,7 +370,7 @@ RR {dev->RequestRender} 0x{(long) dev->ImmediateContext->IfNonZeroSkipPostTickPr Service.PluginLog.Debug($"Destroying RTM resources"); byte rv = _rtmDestroyAfterResizeHook.OriginalDisposeSafe(); - Service.PluginLog.Debug($"After: 0x{(long) (nint) rtm->_.Unk20[0].Value->D3D11Texture2D:X16}"); + Service.PluginLog.Debug($"After: {rv} 0x{(long) (nint) rtm->GameplayTexture->D3D11Texture2D:X16}"); return rv; } @@ -314,11 +389,13 @@ RR {dev->RequestRender} 0x{(long) dev->ImmediateContext->IfNonZeroSkipPostTickPr using var scale = new ScaleState(); Service.PluginLog.Debug($"Regenerating RTM resources: {dev->Width} x {dev->Height}"); _rtmRegenAfterResizeHook.OriginalDisposeSafe(); - Service.PluginLog.Debug($"After: 0x{(long) (nint) rtm->_.Unk20[0].Value->D3D11Texture2D:X16}"); + Service.PluginLog.Debug($"After: 0x{(long) (nint) rtm->GameplayTexture->D3D11Texture2D:X16}"); } private void ICDX11ProcessCommandsDetour(ImmediateContext* ctx, RenderCommandBufferGroup* cmds, uint count) { + _gameplayDrawCount = 0; + lock (_renderLock) { _icdx11ProcessCommandsHook.OriginalDisposeSafe(ctx, cmds, count); @@ -346,6 +423,122 @@ RR {dev->RequestRender} 0x{(long) dev->ImmediateContext->IfNonZeroSkipPostTickPr } } + private ulong PrepareTextureDetour(Texture* tex, uint* size, byte mips, uint format) + { + var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance(); + if (tex != rtm->GameplayTexture) + { + return _prepareTextureHook.OriginalDisposeSafe(tex, size, mips, format); + } + + ref var cfg = ref Service.Config._.Game; + + // TODO: MathF.Log + mips = 1; + if (!Service.Plugin.Unloading && cfg.IsEnabled) + { + for (var s = cfg.Scale; s > 2f; s /= 2f) + { + mips++; + } + } + + Service.PluginLog.Debug($"Preparing gameplay texture 0x{(long) (nint) tex:X16} with {mips} mips, currently 0x{(long) (nint) tex->D3D11Texture2D:X16}"); + var rv = _prepareTextureHook.OriginalDisposeSafe(tex, size, mips, format); + Service.PluginLog.Debug($"After: 0x{rv:X16} 0x{(long) (nint) tex->D3D11Texture2D:X16} 0x{(long) (nint) tex->D3D11ShaderResourceView:X16}"); + return rv; + } + + private void ImmediatePreparePSDetour(ImmediateContextEx* im, PixelShader* ps, ImmediateBlitResource* data) + { + //Service.PluginLog.Debug($"ImmediatePreparePS 0x{(long) im:X16} 0x{(long) ps:X16} 0x{(long) unk3:X16}"); + + var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance(); + var tex = rtm->GameplayTexture; + if (ps->Shader.SamplerCount >= 1 && data[0].Texture == tex) + { + _gameplayDrawCount++; + // Service.PluginLog.Debug($"ImmediatePreparePS #{_gameplayDrawCount} 0x{(long) im:X16} 0x{(long) ps:X16} 0x{(long) data[0].Unk1:X16} 0x{(long) (nint) data[0].Texture:X16} 0x{data[0].Unk2:X8} 0x{data[0].Unk3:X8}"); + + var gfx = (GraphicsConfigEx*) GraphicsConfig.Instance(); + if ((gfx->GraphicsRezoUpscaleType == (byte) ResolutionScalingMode.FSR && gfx->GraphicsRezoScale < 1f) || + gfx->GraphicsRezoUpscaleType == (byte) ResolutionScalingMode.DLSS) + { + _drawIsGameplay = _gameplayDrawCount == 1; + } + else + { + _drawIsGameplay = _gameplayDrawCount == 3; + } + } + + _immediatePreparePSHook.OriginalDisposeSafe(im, ps, data); + } + + private void ImmediatePrepareCSDetour(ImmediateContextEx* im, Shader* cs, ImmediateBlitResource* data) + { + //Service.PluginLog.Debug($"ImmediatePrepareCS 0x{(long) im:X16} 0x{(long) cs:X16} 0x{(long) unk3:X16}"); + + var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance(); + if (((PixelShader*) cs)->Shader.SamplerCount >= 1 && data[0].Texture == rtm->GameplayTexture) + { + _gameplayDrawCount++; + } + + _immediatePrepareCSHook.OriginalDisposeSafe(im, cs, data); + } + + private int CreateTexture2DDetour(ID3D11Device* d3ddev, D3D11_TEXTURE2D_DESC* desc, D3D11_SUBRESOURCE_DATA* initialData, ID3D11Texture2D** tex) + { + var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance(); + if (tex != &rtm->GameplayTexture->D3D11Texture2D) + { + return _createTexture2DHook.OriginalDisposeSafe(d3ddev, desc, initialData, tex); + } + + if (rtm->GameplayTexture->MipLevel != 1) + { + desc->MiscFlags |= (uint) D3D11_RESOURCE_MISC_FLAG.D3D11_RESOURCE_MISC_GENERATE_MIPS; + } + + return _createTexture2DHook.OriginalDisposeSafe(d3ddev, desc, initialData, tex); + } + + private void DrawDetour(ID3D11DeviceContext* d3dctx, uint vertexCount, uint startVertexPosition) + { + ref var cfg = ref Service.Config._.Game; + if (!_drawIsGameplay) + { + _drawHook.OriginalDisposeSafe(d3dctx, vertexCount, startVertexPosition); + return; + } + + _drawIsGameplay = false; + + if (!cfg.IsEnabled) + { + _drawHook.OriginalDisposeSafe(d3dctx, vertexCount, startVertexPosition); + return; + } + + var rtm = (RenderTargetManagerEx*) RenderTargetManager.Instance(); + + SharpDX.Direct3D11.SamplerStateDescription description = SharpDX.Direct3D11.SamplerStateDescription.Default(); + description.Filter = SharpDX.Direct3D11.Filter.MinMagMipLinear; + using SharpDX.Direct3D11.SamplerState sampler = new(_blitShader.Device, description); + _blitShader.Ctx.PixelShader.SetSampler(0, sampler); + + /* + _blitShader.Draw( + CppObject.FromPointer((nint) rtm->GameplayTexture->D3D11ShaderResourceView) + ); + */ + + _blitShader.Ctx.GenerateMips(CppObject.FromPointer((nint) rtm->GameplayTexture->D3D11ShaderResourceView)); + + _drawHook.OriginalDisposeSafe(d3dctx, vertexCount, startVertexPosition); + } + private enum ForceUpdateRTMState { _0_Idle, @@ -406,6 +599,18 @@ public unsafe struct RenderTargetManagerEx [FieldOffset(0)] public RenderTargetManager _; + // Gets blitted FROM after actual gameplay texture. Actual purpose unknown. + [FieldOffset(0x68)] + public Texture* GameplayTextureUnk1; + + // Gets blitted TO after actual gameplay texture, using Unk1. Actual purpose unknown. + [FieldOffset(0x100)] + public Texture* GameplayTextureUnk2; + + // Gets blitted to backbuffer. + [FieldOffset(0x258)] + public Texture* GameplayTexture; + [FieldOffset(0x428)] public uint Resolution_Width; [FieldOffset(0x428 + 0x4)] @@ -431,3 +636,21 @@ public unsafe struct RenderTargetManagerEx [FieldOffset(0x70C + 4 * 4)] public float GraphicsRezoScaleUnk2; } + +[StructLayout(LayoutKind.Explicit, Size = 0x18)] +public unsafe struct ImmediateBlitResource +{ + // D3D11ShaderResourceView must be at 0x58 + (GlobalIndex % 3) * 8 + // Smells like something swapchain-adjacent, but have yet to analyze anything with != null. + [FieldOffset(0x0)] + public nint Unk1; + + [FieldOffset(0x8)] + public Texture* Texture; + + [FieldOffset(0x10)] + public uint Unk2; + + [FieldOffset(0x14)] + public uint Unk3; +} diff --git a/CustomResolution2782/MiniShader.cs b/CustomResolution2782/MiniShader.cs new file mode 100644 index 0000000..b0aa3a3 --- /dev/null +++ b/CustomResolution2782/MiniShader.cs @@ -0,0 +1,184 @@ +using FFXIVClientStructs.FFXIV.Client.Graphics.Kernel; +using FFXIVClientStructs.FFXIV.Common.Lua; +using MonoMod.Utils; +using Newtonsoft.Json; +using SharpDX; +using SharpDX.D3DCompiler; +using SharpDX.Direct3D; +using SharpDX.Direct3D11; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using System.Xml.Linq; +using TerraFX.Interop.DirectX; +using SDXDevice = SharpDX.Direct3D11.Device; +using XIVDevice = FFXIVClientStructs.FFXIV.Client.Graphics.Kernel.Device; + +namespace CustomResolution; + +public unsafe class MiniShader : IDisposable +{ + public MiniShader(string name) + { + Name = name; + + var asm = Assembly.GetExecutingAssembly(); + var path = $"{nameof(CustomResolution)}.Shaders.{name}.hlsl"; + + using (var stream = asm.GetManifestResourceStream(path) ?? throw new FileNotFoundException(path)) + using (var streamReader = new StreamReader(stream)) + { + Source = streamReader.ReadToEnd(); + } + + var dev = (DeviceEx*) XIVDevice.Instance(); + Ctx = CppObject.FromPointer((nint) dev->D3D11DeviceContext); + Device = Ctx.Device; + + VS = new(Device, ShaderBytecode.Compile(Source, "vs", "vs_5_0")); + PS = new(Device, ShaderBytecode.Compile(Source, "ps", "ps_5_0")); + } + + public string Name { get; private set; } + public string Source { get; private set; } + + public DeviceContext Ctx { get; private set; } + public SDXDevice Device { get; private set; } + + public SharpDX.Direct3D11.VertexShader VS { get; private set; } + public SharpDX.Direct3D11.PixelShader PS { get; private set; } + + public virtual void Dispose() + { + PS.Dispose(); + VS.Dispose(); + Device.Dispose(); + } +} + +public class MiniShaderConstants : IDisposable where T : unmanaged +{ + private readonly MiniShader _shader; + private T _value; + + public MiniShaderConstants(MiniShader shader) + { + _shader = shader; + + Buffer = new( + _shader.Device, + Math.Max(16, Marshal.SizeOf()), + ResourceUsage.Default, + BindFlags.ConstantBuffer, + CpuAccessFlags.None, + ResourceOptionFlags.None, + Marshal.SizeOf() + ); + } + + public SharpDX.Direct3D11.Buffer Buffer { get; private set; } + public ref T Value => ref _value; + + public void Dispose() + { + Buffer.Dispose(); + } + + public void Update() + { + Set(Value); + } + + public void Set(T data) + { + _value = data; + _shader.Ctx.UpdateSubresource(ref data, Buffer); + } +} + +public class MiniShaderState : IDisposable +{ + private List _undos = []; + + public MiniShaderState() + { + } + + public void Dispose() + { + foreach (var undo in _undos) + { + undo(); + } + } + + public void Set(TOwner owner, string name, TValue value) + { + var prev = ReflCache.Get(owner, name); + _undos.Add(() => ReflCache.Set(owner, name, prev)); + ReflCache.Set(owner, name, value); + } + + public void SetShader(CommonShaderStage stage, T? shader) where T : DeviceChild + { + var prev = stage.Get(); + _undos.Add(() => stage.Set(prev)); + stage.Set(shader!); + } + + public void SetConstantBuffer(CommonShaderStage stage, int slot, SharpDX.Direct3D11.Buffer buffer) where T : DeviceChild + { + var prev = stage.GetConstantBuffers(slot, 1); + _undos.Add(() => stage.SetConstantBuffer(slot, prev[0])); + stage.SetConstantBuffer(slot, buffer); + } + + public void SetShaderResource(CommonShaderStage stage, int slot, ShaderResourceView srv) where T : DeviceChild + { + var prev = stage.GetShaderResources(slot, 1); + _undos.Add(() => stage.SetShaderResource(slot, prev[0])); + stage.SetShaderResource(slot, srv); + } + + private static class ReflCache + { + private static readonly Type _tOwner = typeof(TOwner); + private static readonly Dictionary _props = []; + + private static PropertyInfo? GetProperty(string name) + { + if (_props.TryGetValue(name, out var prop)) + { + return prop; + } + + return _props[name] = _tOwner.GetProperty(name); + } + + public static TValue Get(TOwner owner, string name) + { + if (GetProperty(name) is { } prop) + { + return (TValue) prop.GetValue(owner)!; + } + + return default!; + } + + public static void Set(TOwner owner, string name, TValue value) + { + if (GetProperty(name) is { } prop) + { + prop.SetValue(owner, value); + return; + } + } + } +} diff --git a/CustomResolution2782/Shaders/Blit.hlsl b/CustomResolution2782/Shaders/Blit.hlsl new file mode 100644 index 0000000..7db8d90 --- /dev/null +++ b/CustomResolution2782/Shaders/Blit.hlsl @@ -0,0 +1,23 @@ +Texture2D inputTexture : register(t0); +SamplerState TextureSampler; + +struct VSOutput +{ + float4 pos : SV_POSITION; + float2 uv : TEXCOORD; +}; + +VSOutput vs(uint id : SV_VertexID) +{ + VSOutput output; + float2 uv = float2((id << 1) & 2, id & 2); + output.pos = float4(uv * 2 - 1, 0, 1); + output.uv = uv * float2(1, -1) + float2(0, 1); + return output; +} + +float4 ps(VSOutput input) : SV_Target +{ + float4 color = inputTexture.Sample(TextureSampler, input.uv); + return color; +} diff --git a/CustomResolution2782/Shaders/BlitShader.cs b/CustomResolution2782/Shaders/BlitShader.cs new file mode 100644 index 0000000..d0c2f6b --- /dev/null +++ b/CustomResolution2782/Shaders/BlitShader.cs @@ -0,0 +1,51 @@ +using SharpDX.Direct3D; +using SharpDX.Direct3D11; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using TerraFX.Interop.DirectX; +using TerraFX.Interop.Windows; + +namespace CustomResolution.Shaders; + +public class BlitShader : MiniShader +{ + public BlitShader() : base("Blit") + { + } + + public override void Dispose() + { + base.Dispose(); + } + + public void Draw(ShaderResourceView srv) + { + using MiniShaderState state = new(); + + state.SetShader(Ctx.VertexShader, VS); + state.SetShader(Ctx.PixelShader, PS); + state.SetShader(Ctx.GeometryShader, null); + + state.Set(Ctx.InputAssembler, nameof(Ctx.InputAssembler.PrimitiveTopology), PrimitiveTopology.TriangleList); + + var blendDesc = Ctx.OutputMerger.BlendState.Description; + blendDesc.RenderTarget[0].IsBlendEnabled = false; + + using var blendState = new BlendState(Device, blendDesc); + state.Set(Ctx.OutputMerger, nameof(Ctx.OutputMerger.BlendState), blendState); + + state.SetShaderResource(Ctx.PixelShader, 0, srv); + + Ctx.Draw(3, 0); + } + + [StructLayout(LayoutKind.Sequential)] + public struct TConstants + { + public float Time; + } +} diff --git a/CustomResolution2782/Shaders/Example.hlsl b/CustomResolution2782/Shaders/Example.hlsl new file mode 100644 index 0000000..64d097d --- /dev/null +++ b/CustomResolution2782/Shaders/Example.hlsl @@ -0,0 +1,26 @@ +struct Constants +{ + float time; +}; +Constants k : register(b0); + +struct VSOutput +{ + float4 pos : SV_POSITION; + float2 uv : TEXCOORD; +}; + +VSOutput vs(uint id : SV_VertexID) +{ + VSOutput output; + float2 uv = float2((id << 1) & 2, id & 2); + output.pos = float4(uv - 1, 0, 1); + output.uv = uv; + return output; +} + +float4 ps(VSOutput input) : SV_Target +{ + float4 color = float4(0.5, 0, frac(k.time), 0.5); + return color; +} diff --git a/CustomResolution2782/Shaders/ExampleShader.cs b/CustomResolution2782/Shaders/ExampleShader.cs new file mode 100644 index 0000000..84faae9 --- /dev/null +++ b/CustomResolution2782/Shaders/ExampleShader.cs @@ -0,0 +1,64 @@ +using SharpDX.Direct3D; +using SharpDX.Direct3D11; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using TerraFX.Interop.DirectX; +using TerraFX.Interop.Windows; + +namespace CustomResolution.Shaders; + +public class ExampleShader : MiniShader +{ + public ExampleShader() : base("Example") + { + Constants = new(this); + } + + public MiniShaderConstants Constants { get; private set; } + + public override void Dispose() + { + Constants.Dispose(); + base.Dispose(); + } + + public void Draw() + { + using MiniShaderState state = new(); + + state.SetShader(Ctx.VertexShader, VS); + state.SetShader(Ctx.PixelShader, PS); + state.SetShader(Ctx.GeometryShader, null); + + state.Set(Ctx.InputAssembler, nameof(Ctx.InputAssembler.PrimitiveTopology), PrimitiveTopology.TriangleList); + + state.SetConstantBuffer(Ctx.PixelShader, 0, Constants.Buffer); + + Ctx.PixelShader.SetConstantBuffer(0, Constants.Buffer); + + var blendDesc = Ctx.OutputMerger.BlendState.Description; + blendDesc.RenderTarget[0].IsBlendEnabled = true; + blendDesc.RenderTarget[0].SourceBlend = BlendOption.SourceAlpha; + blendDesc.RenderTarget[0].DestinationBlend = BlendOption.InverseSourceAlpha; + blendDesc.RenderTarget[0].BlendOperation = BlendOperation.Add; + blendDesc.RenderTarget[0].SourceAlphaBlend = BlendOption.SourceAlpha; + blendDesc.RenderTarget[0].DestinationAlphaBlend = BlendOption.InverseSourceAlpha; + blendDesc.RenderTarget[0].AlphaBlendOperation = BlendOperation.Maximum; + blendDesc.RenderTarget[0].RenderTargetWriteMask = ColorWriteMaskFlags.All; + + using var blendState = new BlendState(Device, blendDesc); + state.Set(Ctx.OutputMerger, nameof(Ctx.OutputMerger.BlendState), blendState); + + Ctx.Draw(3, 0); + } + + [StructLayout(LayoutKind.Sequential)] + public struct TConstants + { + public float Time; + } +} diff --git a/CustomResolution2782/packages.lock.json b/CustomResolution2782/packages.lock.json index 6b40e5f..85ea729 100644 --- a/CustomResolution2782/packages.lock.json +++ b/CustomResolution2782/packages.lock.json @@ -40,12 +40,6 @@ "SharpDX.DXGI": "4.2.0" } }, - "TerraFX.Interop.Windows": { - "type": "Direct", - "requested": "[10.0.22621.2, )", - "resolved": "10.0.22621.2", - "contentHash": "lORoYCoURS33Vi7PDvubRugLF2+l5v3rX2oVEqNhpBLjs3aZpqapRvTHPKVwsC+dGrsGuqJ/3yXuxVUb0vl3vg==" - }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "1.1.0",