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",