Compare commits

...
Author SHA1 Message Date
lightningterror 74ba2fb2ac GS/DX11: Add pixel shader based CAS.
It's much faster than uav and it's backwards compatible across all feature levels.
2026-09-14 14:10:55 +02:00
lightningterror f4fe213647 GS: Add PSTypeCAS function for switching texture usage for CAS. 2026-09-14 14:10:50 +02:00
10 changed files with 75 additions and 57 deletions
+49 -23
View File
@@ -19,13 +19,16 @@
cbuffer cb : register(b0)
{
uint4 const0;
uint4 const1;
int2 srcOffset;
uint4 const0;
uint4 const1;
int2 srcOffset;
};
Texture2D InputTexture : register(t0);
RWTexture2D<float4> OutputTexture : register(u0);
#if PS_CAS == 0
RWTexture2D<float4> OutputTexture : register(u0);
#endif
#define A_GPU 1
#define A_HLSL 1
@@ -34,7 +37,7 @@ RWTexture2D<float4> OutputTexture : register(u0);
AF3 CasLoad(ASU2 p)
{
return InputTexture.Load(int3(srcOffset, 0) + int3(p, 0)).rgb;
return InputTexture.Load(int3(srcOffset, 0) + int3(p, 0)).rgb;
}
// Lets you transform input from the load into a linear color space between 0 and 1. See ffx_cas.h
@@ -43,33 +46,56 @@ void CasInput(inout AF1 r, inout AF1 g, inout AF1 b) {}
#include "ffx_cas.h"
#if PS_CAS == 0
[numthreads(64, 1, 1)]
void main(uint3 LocalThreadId : SV_GroupThreadID, uint3 WorkGroupId : SV_GroupID)
void cs_main(uint3 LocalThreadId : SV_GroupThreadID, uint3 WorkGroupId : SV_GroupID)
{
// Do remapping of local xy in workgroup for a more PS-like swizzle pattern.
AU2 gxy = ARmp8x8(LocalThreadId.x) + AU2(WorkGroupId.x << 4u, WorkGroupId.y << 4u);
// Do remapping of local xy in workgroup for a more PS-like swizzle pattern.
AU2 gxy = ARmp8x8(LocalThreadId.x) + AU2(WorkGroupId.x << 4u, WorkGroupId.y << 4u);
#if CAS_SHARPEN_ONLY
const bool sharpenOnly = true;
const bool sharpenOnly = true;
#else
const bool sharpenOnly = false;
const bool sharpenOnly = false;
#endif
// Filter.
AF3 c = (float3)0.0f;
// Filter.
AF3 c = (float3)0.0f;
CasFilter(c.r, c.g, c.b, gxy, const0, const1, sharpenOnly);
OutputTexture[ASU2(gxy)] = AF4(c, 1);
gxy.x += 8u;
CasFilter(c.r, c.g, c.b, gxy, const0, const1, sharpenOnly);
OutputTexture[ASU2(gxy)] = AF4(c, 1);
gxy.x += 8u;
CasFilter(c.r, c.g, c.b, gxy, const0, const1, sharpenOnly);
OutputTexture[ASU2(gxy)] = AF4(c, 1);
gxy.y += 8u;
CasFilter(c.r, c.g, c.b, gxy, const0, const1, sharpenOnly);
OutputTexture[ASU2(gxy)] = AF4(c, 1);
gxy.y += 8u;
CasFilter(c.r, c.g, c.b, gxy, const0, const1, sharpenOnly);
OutputTexture[ASU2(gxy)] = AF4(c, 1);
gxy.x -= 8u;
CasFilter(c.r, c.g, c.b, gxy, const0, const1, sharpenOnly);
OutputTexture[ASU2(gxy)] = AF4(c, 1);
gxy.x -= 8u;
CasFilter(c.r, c.g, c.b, gxy, const0, const1, sharpenOnly);
OutputTexture[ASU2(gxy)] = AF4(c, 1);
CasFilter(c.r, c.g, c.b, gxy, const0, const1, sharpenOnly);
OutputTexture[ASU2(gxy)] = AF4(c, 1);
}
#elif PS_CAS == 1
float4 ps_main(float4 position : SV_Position, float2 uv : TEXCOORD0) : SV_Target
{
// Pixel coordinate for the current fragment
AU2 gxy = AU2(position.xy);
#if CAS_SHARPEN_ONLY
const bool sharpenOnly = true;
#else
const bool sharpenOnly = false;
#endif
AF3 c = (float3)0.0f;
CasFilter(c.r, c.g, c.b, gxy, const0, const1, sharpenOnly);
return AF4(c, 1.0f);
}
#endif
+3 -3
View File
@@ -1236,10 +1236,10 @@ void GSDevice::CAS(GSTexture*& tex, GSVector4i& src_rect, GSVector4& src_uv, con
if (!m_cas || m_cas->GetWidth() != dst_width || m_cas->GetHeight() != dst_height)
{
delete m_cas;
m_cas = CreateSurface(GSTexture::ShaderWriteTexture, dst_width, dst_height, 1, GSTexture::Format::Color);
m_cas = CreateSurface(PSTypeCAS() ? GSTexture::ShaderWriteTarget : GSTexture::ShaderWriteTexture, dst_width, dst_height, 1, GSTexture::Format::Color);
if (!m_cas)
{
Console.Error("Failed to allocate CAS RW texture.");
Console.Error("GS: Failed to allocate CAS RW texture.");
return;
}
}
@@ -1254,7 +1254,7 @@ void GSDevice::CAS(GSTexture*& tex, GSVector4i& src_rect, GSVector4& src_uv, con
if (!DoCAS(src_tex, m_cas, sharpen_only, consts))
{
// leave textures intact if we failed
Console.Warning("Applying CAS failed.");
Console.Warning("GS: Applying CAS failed.");
return;
}
+1
View File
@@ -1514,6 +1514,7 @@ protected:
/// Applies CAS and writes to the destination texture, which should be a shader writeable texture.
virtual bool DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array<u32, NUM_CAS_CONSTANTS>& constants) = 0;
virtual bool PSTypeCAS() = 0;
/// Perform texture operations for ImGui
void UpdateImGuiTextures();
+11 -26
View File
@@ -715,7 +715,6 @@ void GSDevice11::SetFeatures(IDXGIAdapter1* adapter)
SupportsTextureFormat(m_dev.get(), DXGI_FORMAT_BC3_UNORM);
m_features.bptc_textures = SupportsTextureFormat(m_dev.get(), DXGI_FORMAT_BC7_UNORM);
m_features.cas_sharpening = (m_feature_level >= D3D_FEATURE_LEVEL_11_0);
m_features.test_and_sample_depth = (m_feature_level >= D3D_FEATURE_LEVEL_11_0);
m_features.depth_feedback = m_features.multidraw_fb_copy && GSConfig.DepthFeedbackMode == GSDepthFeedbackMode::Depth;
m_features.aa1 = GSConfig.HWAA1 && m_features.vs_expand && m_features.feedback_loops();
@@ -2273,7 +2272,7 @@ void GSDevice11::SetupOM(OMDepthStencilSelector dssel, OMBlendSelector bsel, u8
bool GSDevice11::CreateCASShaders()
{
CD3D11_BUFFER_DESC desc(NUM_CAS_CONSTANTS * sizeof(u32), D3D11_BIND_CONSTANT_BUFFER, D3D11_USAGE_DEFAULT);
HRESULT hr = m_dev->CreateBuffer(&desc, nullptr, m_cas.cb.put());
const HRESULT hr = m_dev->CreateBuffer(&desc, nullptr, m_cas.cb.put());
if (FAILED(hr))
return false;
@@ -2281,15 +2280,14 @@ bool GSDevice11::CreateCASShaders()
if (!cas_source.has_value() || !GetCASShaderSource(&cas_source.value()))
return false;
static constexpr D3D_SHADER_MACRO sharpen_only_macros[] = {
{"CAS_SHARPEN_ONLY", "1"},
{nullptr, nullptr}};
static constexpr D3D_SHADER_MACRO sharpen_macros[] = {{"CAS_SHARPEN_ONLY", "1"}, {"PS_CAS", "1"}, {nullptr, nullptr}};
static constexpr D3D_SHADER_MACRO upscale_macros[] = {{"PS_CAS", "1"}, {nullptr, nullptr}};
m_cas.cs_sharpen = m_shader_cache.GetComputeShader(m_dev.get(), cas_source.value(), sharpen_only_macros, "main");
m_cas.cs_upscale = m_shader_cache.GetComputeShader(m_dev.get(), cas_source.value(), nullptr, "main");
if (!m_cas.cs_sharpen || !m_cas.cs_upscale)
m_cas.ps_sharpen = m_shader_cache.GetPixelShader(m_dev.get(), cas_source.value(), sharpen_macros, "ps_main");
m_cas.ps_upscale = m_shader_cache.GetPixelShader(m_dev.get(), cas_source.value(), upscale_macros, "ps_main");
if (!m_cas.ps_sharpen || !m_cas.ps_upscale)
{
Console.Error("D3D11: Failed to create CAS compute shaders.");
Console.Error("D3D11: Failed to create CAS pixel shaders.");
return false;
}
@@ -2298,27 +2296,14 @@ bool GSDevice11::CreateCASShaders()
bool GSDevice11::DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array<u32, NUM_CAS_CONSTANTS>& constants)
{
g_perfmon.Put(GSPerfMon::TextureCopies, 1);
const GSVector2i s = dTex->GetSize();
static constexpr int threadGroupWorkRegionDim = 16;
const int dispatchX = (dTex->GetWidth() + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
const int dispatchY = (dTex->GetHeight() + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
const GSVector4 sRect(0, 0, 1, 1);
const GSVector4 dRect(0, 0, s.x, s.y);
ID3D11ShaderResourceView* srvs[1] = {*static_cast<GSTexture11*>(sTex)};
ID3D11UnorderedAccessView* uavs[1] = {*static_cast<GSTexture11*>(dTex)};
OMSetRenderTargets(nullptr, nullptr, nullptr);
m_ctx->UpdateSubresource(m_cas.cb.get(), 0, nullptr, constants.data(), 0, 0);
m_ctx->CSSetConstantBuffers(0, 1, m_cas.cb.addressof());
m_ctx->CSSetShader(sharpen_only ? m_cas.cs_sharpen.get() : m_cas.cs_upscale.get(), nullptr, 0);
m_ctx->CSSetShaderResources(0, std::size(srvs), srvs);
m_ctx->CSSetUnorderedAccessViews(0, std::size(uavs), uavs, nullptr);
m_ctx->Dispatch(dispatchX, dispatchY, 1);
// clear bindings out to prevent hazards
uavs[0] = nullptr;
srvs[0] = nullptr;
m_ctx->CSSetShaderResources(0, std::size(srvs), srvs);
m_ctx->CSSetUnorderedAccessViews(0, std::size(uavs), uavs, nullptr);
DoStretchRect(sTex, sRect, dTex, dRect, sharpen_only ? m_cas.ps_sharpen.get() : m_cas.ps_upscale.get(), m_cas.cb.get(), Biln);
return true;
}
+3 -2
View File
@@ -120,6 +120,7 @@ private:
bool CreateCASShaders();
bool DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array<u32, NUM_CAS_CONSTANTS>& constants) override;
bool PSTypeCAS() override { return true; }
bool CreateImGuiResources();
void RenderImGui();
@@ -271,8 +272,8 @@ private:
struct
{
wil::com_ptr_nothrow<ID3D11Buffer> cb;
wil::com_ptr_nothrow<ID3D11ComputeShader> cs_upscale;
wil::com_ptr_nothrow<ID3D11ComputeShader> cs_sharpen;
wil::com_ptr_nothrow<ID3D11PixelShader> ps_upscale;
wil::com_ptr_nothrow<ID3D11PixelShader> ps_sharpen;
} m_cas;
struct
+4 -3
View File
@@ -2311,10 +2311,11 @@ bool GSDevice12::CompileCASPipelines()
if (!cas_source.has_value() || !GetCASShaderSource(&cas_source.value()))
return false;
static constexpr D3D_SHADER_MACRO sharpen_only_macros[] = {{"CAS_SHARPEN_ONLY", "1"}, {nullptr, nullptr}};
static constexpr D3D_SHADER_MACRO upscale_macros[] = {{"PS_CAS", "0"}, {nullptr, nullptr}};
static constexpr D3D_SHADER_MACRO sharpen_macros[] = {{"CAS_SHARPEN_ONLY", "1"}, {"PS_CAS", "0"}, {nullptr, nullptr}};
const ComPtr<ID3DBlob> cs_upscale(m_shader_cache.GetComputeShader(cas_source.value(), nullptr, "main"));
const ComPtr<ID3DBlob> cs_sharpen(m_shader_cache.GetComputeShader(cas_source.value(), sharpen_only_macros, "main"));
const ComPtr<ID3DBlob> cs_upscale(m_shader_cache.GetComputeShader(cas_source.value(), nullptr, "cs_main"));
const ComPtr<ID3DBlob> cs_sharpen(m_shader_cache.GetComputeShader(cas_source.value(), sharpen_macros, "cs_main"));
if (!cs_upscale || !cs_sharpen)
return false;
+1
View File
@@ -460,6 +460,7 @@ private:
bool DoCAS(
GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array<u32, NUM_CAS_CONSTANTS>& constants) final;
bool PSTypeCAS() final { return false; }
bool GetSampler(D3D12DescriptorHandle* cpu_handle, GSHWDrawConfig::SamplerSelector ss);
void ClearSamplerCache() final;
+1
View File
@@ -394,6 +394,7 @@ public:
void DoShadeBoost(GSTexture* sTex, GSTexture* dTex, const float params[4]) override;
bool DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array<u32, NUM_CAS_CONSTANTS>& constants) override;
bool PSTypeCAS() override { return false; }
MRCOwned<id<MTLFunction>> LoadShader(NSString* name);
MRCOwned<id<MTLRenderPipelineState>> MakePipeline(MTLRenderPipelineDescriptor* desc, id<MTLFunction> vertex, id<MTLFunction> fragment, NSString* name);
+1
View File
@@ -300,6 +300,7 @@ private:
bool CreateCASPrograms();
bool DoCAS(GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array<u32, NUM_CAS_CONSTANTS>& constants) override;
bool PSTypeCAS() override { return false; }
bool CreateImGuiProgram();
void RenderImGui();
+1
View File
@@ -494,6 +494,7 @@ private:
bool DoCAS(
GSTexture* sTex, GSTexture* dTex, bool sharpen_only, const std::array<u32, NUM_CAS_CONSTANTS>& constants) final;
bool PSTypeCAS() final { return false; }
VkSampler GetSampler(GSHWDrawConfig::SamplerSelector ss);
void ClearSamplerCache() final;