Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AtlasEngine: Implement support for custom shaders #13885

Merged
merged 12 commits into from
Aug 31, 2022
55 changes: 27 additions & 28 deletions samples/PixelShaders/Retro.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -2,47 +2,46 @@
Texture2D shaderTexture;
SamplerState samplerState;

cbuffer PixelShaderSettings {
float Time;
float Scale;
float2 Resolution;
float4 Background;
cbuffer PixelShaderSettings
{
float time;
float scale;
float2 resolution;
float4 background;
};

#define SCANLINE_FACTOR 0.5
#define SCALED_SCANLINE_PERIOD Scale
#define SCALED_GAUSSIAN_SIGMA (2.0*Scale)
#define SCANLINE_FACTOR 0.5f
#define SCALED_SCANLINE_PERIOD scale
#define SCALED_GAUSSIAN_SIGMA (2.0f * scale)

static const float M_PI = 3.14159265f;

float Gaussian2D(float x, float y, float sigma)
{
return 1/(sigma*sqrt(2*M_PI)) * exp(-0.5*(x*x + y*y)/sigma/sigma);
return 1 / (sigma * sqrt(2 * M_PI)) * exp(-0.5 * (x * x + y * y) / sigma / sigma);
}

float4 Blur(Texture2D input, float2 tex_coord, float sigma)
{
uint width, height;
float width, height;
shaderTexture.GetDimensions(width, height);

float texelWidth = 1.0f/width;
float texelHeight = 1.0f/height;
float texelWidth = 1.0f / width;
float texelHeight = 1.0f / height;

float4 color = { 0, 0, 0, 0 };

int sampleCount = 13;
float sampleCount = 13;

for (int x = 0; x < sampleCount; x++)
for (float x = 0; x < sampleCount; x++)
{
float2 samplePos = { 0, 0 };
samplePos.x = tex_coord.x + (x - sampleCount / 2.0f) * texelWidth;

samplePos.x = tex_coord.x + (x - sampleCount/2) * texelWidth;
for (int y = 0; y < sampleCount; y++)
for (float y = 0; y < sampleCount; y++)
{
samplePos.y = tex_coord.y + (y - sampleCount/2) * texelHeight;
if (samplePos.x <= 0 || samplePos.y <= 0 || samplePos.x >= width || samplePos.y >= height) continue;

color += input.Sample(samplerState, samplePos) * Gaussian2D((x - sampleCount/2), (y - sampleCount/2), sigma);
samplePos.y = tex_coord.y + (y - sampleCount / 2.0f) * texelHeight;
color += input.Sample(samplerState, samplePos) * Gaussian2D(x - sampleCount / 2.0f, y - sampleCount / 2.0f, sigma);
Comment on lines +43 to +44
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI with D3D11_TEXTURE_ADDRESS_BORDER we already ensure that Sample() returns float4(0, 0, 0, 0). This removes the need for the if condition, which is one of the things that produces a warning by default (and for a good reason - loops like that are impossible on GPUs).

}
}

Expand All @@ -51,7 +50,7 @@ float4 Blur(Texture2D input, float2 tex_coord, float sigma)

float SquareWave(float y)
{
return 1 - (floor(y / SCALED_SCANLINE_PERIOD) % 2) * SCANLINE_FACTOR;
return 1.0f - (floor(y / SCALED_SCANLINE_PERIOD) % 2.0f) * SCANLINE_FACTOR;
}

float4 Scanline(float4 color, float4 pos)
Expand All @@ -60,24 +59,24 @@ float4 Scanline(float4 color, float4 pos)

// TODO:GH#3929 make this configurable.
// Remove the && false to draw scanlines everywhere.
if (length(color.rgb) < 0.2 && false)
if (length(color.rgb) < 0.2f && false)
{
return color + wave*0.1;
return color + wave * 0.1f;
}
else
{
return color * wave;
}
}

// clang-format off
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
// clang-format on
{
Texture2D input = shaderTexture;

// TODO:GH#3930 Make these configurable in some way.
float4 color = input.Sample(samplerState, tex);
color += Blur(input, tex, SCALED_GAUSSIAN_SIGMA)*0.3;
float4 color = shaderTexture.Sample(samplerState, tex);
color += Blur(shaderTexture, tex, SCALED_GAUSSIAN_SIGMA) * 0.3f;
color = Scanline(color, pos);

return color;
}
}
31 changes: 22 additions & 9 deletions src/renderer/atlas/AtlasEngine.api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ HRESULT AtlasEngine::Enable() noexcept

[[nodiscard]] bool AtlasEngine::GetRetroTerminalEffect() const noexcept
{
return false;
return _api.useRetroTerminalEffect;
}

[[nodiscard]] float AtlasEngine::GetScaling() const noexcept
Expand Down Expand Up @@ -332,7 +332,7 @@ void AtlasEngine::SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasin
if (_api.antialiasingMode != mode)
{
_api.antialiasingMode = mode;
_resolveAntialiasingMode();
_resolveTransparencySettings();
WI_SetFlag(_api.invalidations, ApiInvalidations::Font);
}
}
Expand All @@ -344,11 +344,10 @@ void AtlasEngine::SetCallback(std::function<void()> pfn) noexcept

void AtlasEngine::EnableTransparentBackground(const bool isTransparent) noexcept
{
const auto mixin = !isTransparent ? 0xff000000 : 0x00000000;
if (_api.backgroundOpaqueMixin != mixin)
if (_api.enableTransparentBackground != isTransparent)
{
_api.backgroundOpaqueMixin = mixin;
_resolveAntialiasingMode();
_api.enableTransparentBackground = isTransparent;
_resolveTransparencySettings();
WI_SetFlag(_api.invalidations, ApiInvalidations::SwapChain);
}
}
Expand All @@ -369,10 +368,22 @@ void AtlasEngine::SetForceFullRepaintRendering(bool enable) noexcept

void AtlasEngine::SetPixelShaderPath(std::wstring_view value) noexcept
{
if (_api.customPixelShaderPath != value)
{
_api.customPixelShaderPath = value;
_resolveTransparencySettings();
WI_SetFlag(_api.invalidations, ApiInvalidations::Device);
}
}

void AtlasEngine::SetRetroTerminalEffect(bool enable) noexcept
{
if (_api.useRetroTerminalEffect != enable)
{
_api.useRetroTerminalEffect = enable;
_resolveTransparencySettings();
WI_SetFlag(_api.invalidations, ApiInvalidations::Device);
}
}

void AtlasEngine::SetSelectionBackground(const COLORREF color, const float alpha) noexcept
Expand Down Expand Up @@ -451,13 +462,15 @@ void AtlasEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept

#pragma endregion

void AtlasEngine::_resolveAntialiasingMode() noexcept
void AtlasEngine::_resolveTransparencySettings() noexcept
{
// If the user asks for ClearType, but also for a transparent background
// (which our ClearType shader doesn't simultaneously support)
// then we need to sneakily force the renderer to grayscale AA.
const auto forceGrayscaleAA = _api.antialiasingMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE && !_api.backgroundOpaqueMixin;
_api.realizedAntialiasingMode = forceGrayscaleAA ? D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE : _api.antialiasingMode;
_api.realizedAntialiasingMode = _api.enableTransparentBackground && _api.antialiasingMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE ? D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE : _api.antialiasingMode;
// An opaque background allows us to use true "independent" flips. See AtlasEngine::_createSwapChain().
// We can't enable them if custom shaders are specified, because it's unknown, whether they support opaque inputs.
_api.backgroundOpaqueMixin = _api.enableTransparentBackground || !_api.customPixelShaderPath.empty() || _api.useRetroTerminalEffect ? 0x00000000 : 0xff000000;
}

void AtlasEngine::_updateFont(const wchar_t* faceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes)
Expand Down
135 changes: 112 additions & 23 deletions src/renderer/atlas/AtlasEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include "pch.h"
#include "AtlasEngine.h"

#include <custom_shader_ps.h>
#include <custom_shader_vs.h>
#include <shader_ps.h>
#include <shader_vs.h>

Expand Down Expand Up @@ -301,25 +303,6 @@ try
}
CATCH_RETURN()

[[nodiscard]] bool AtlasEngine::RequiresContinuousRedraw() noexcept
{
return debugGeneralPerformance;
}

void AtlasEngine::WaitUntilCanRender() noexcept
{
// IDXGISwapChain2::GetFrameLatencyWaitableObject returns an auto-reset event.
// Once we've waited on the event, waiting on it again will block until the timeout elapses.
// _r.waitForPresentation guards against this.
if (!debugGeneralPerformance && std::exchange(_r.waitForPresentation, false))
{
WaitForSingleObjectEx(_r.frameLatencyWaitableObject.get(), 100, true);
#ifndef NDEBUG
_r.frameLatencyWaitableObjectUsed = true;
#endif
}
}

[[nodiscard]] HRESULT AtlasEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept
{
RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint);
Expand Down Expand Up @@ -607,6 +590,7 @@ void AtlasEngine::_createResources()
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
};

auto hr = S_OK;
Expand Down Expand Up @@ -668,6 +652,98 @@ void AtlasEngine::_createResources()

THROW_IF_FAILED(_r.device->CreateVertexShader(&shader_vs[0], sizeof(shader_vs), nullptr, _r.vertexShader.put()));
THROW_IF_FAILED(_r.device->CreatePixelShader(&shader_ps[0], sizeof(shader_ps), nullptr, _r.pixelShader.put()));

if (!_api.customPixelShaderPath.empty())
{
const char* target = nullptr;
switch (_r.device->GetFeatureLevel())
{
case D3D_FEATURE_LEVEL_10_0:
target = "ps_4_0";
break;
case D3D_FEATURE_LEVEL_10_1:
target = "ps_4_1";
break;
default:
target = "ps_5_0";
break;
}

static constexpr auto flags = D3DCOMPILE_PACK_MATRIX_COLUMN_MAJOR | D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_WARNINGS_ARE_ERRORS
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

D3DCOMPILE_ENABLE_STRICTNESS | D3DCOMPILE_WARNINGS_ARE_ERRORS

We need to very clearly add to the release notes that we've added these, so that pixel shaders in atlas engine will enforce them now.

#ifdef NDEBUG
| D3DCOMPILE_OPTIMIZATION_LEVEL3;
#else
| D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif

wil::com_ptr<ID3DBlob> error;
wil::com_ptr<ID3DBlob> blob;
const auto hr = D3DCompileFromFile(
/* pFileName */ _api.customPixelShaderPath.c_str(),
/* pDefines */ nullptr,
/* pInclude */ D3D_COMPILE_STANDARD_FILE_INCLUDE,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh neat, so now folks can relative-include other files in their shaders? that's neat

/* pEntrypoint */ "main",
/* pTarget */ target,
/* Flags1 */ flags,
/* Flags2 */ 0,
/* ppCode */ blob.addressof(),
/* ppErrorMsgs */ error.addressof());

if (SUCCEEDED(hr))
{
THROW_IF_FAILED(_r.device->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), nullptr, _r.customPixelShader.put()));
}
else
{
if (error)
{
LOG_HR_MSG(hr, "%*hs", error->GetBufferSize(), error->GetBufferPointer());
}
else
{
LOG_HR(hr);
}

if (_api.warningCallback)
{
_api.warningCallback(D2DERR_SHADER_COMPILE_FAILED);
}
}

_r.requiresContinuousRedraw = true;
}
else if (_api.useRetroTerminalEffect)
{
THROW_IF_FAILED(_r.device->CreatePixelShader(&custom_shader_ps[0], sizeof(custom_shader_ps), nullptr, _r.customPixelShader.put()));
}

if (_r.customPixelShader)
{
THROW_IF_FAILED(_r.device->CreateVertexShader(&custom_shader_vs[0], sizeof(custom_shader_vs), nullptr, _r.customVertexShader.put()));

{
D3D11_BUFFER_DESC desc{};
desc.ByteWidth = sizeof(CustomConstBuffer);
desc.Usage = D3D11_USAGE_DYNAMIC;
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
THROW_IF_FAILED(_r.device->CreateBuffer(&desc, nullptr, _r.customShaderConstantBuffer.put()));
}

{
D3D11_SAMPLER_DESC desc{};
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
desc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER;
desc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER;
desc.AddressW = D3D11_TEXTURE_ADDRESS_BORDER;
desc.MaxAnisotropy = 1;
desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
desc.MaxLOD = D3D11_FLOAT32_MAX;
THROW_IF_FAILED(_r.device->CreateSamplerState(&desc, _r.customShaderSamplerState.put()));
}

_r.customShaderStartTime = std::chrono::steady_clock::now();
}
}

WI_ClearFlag(_api.invalidations, ApiInvalidations::Device);
Expand Down Expand Up @@ -711,10 +787,9 @@ void AtlasEngine::_createSwapChain()
desc.BufferCount = 2;
desc.Scaling = DXGI_SCALING_NONE;
desc.SwapEffect = _sr.isWindows10OrGreater && !_r.d2dMode ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
// * HWND swap chains can't do alpha.
// * If our background is opaque we can enable "independent" flips by setting DXGI_SWAP_EFFECT_FLIP_DISCARD and DXGI_ALPHA_MODE_IGNORE.
// As our swap chain won't have to compose with DWM anymore it reduces the display latency dramatically.
desc.AlphaMode = _api.hwnd || _api.backgroundOpaqueMixin ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
// If our background is opaque we can enable "independent" flips by setting DXGI_SWAP_EFFECT_FLIP_DISCARD and DXGI_ALPHA_MODE_IGNORE.
// As our swap chain won't have to compose with DWM anymore it reduces the display latency dramatically.
desc.AlphaMode = _api.backgroundOpaqueMixin ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
desc.Flags = debugGeneralPerformance ? 0 : DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;

wil::com_ptr<IDXGIFactory2> dxgiFactory;
Expand Down Expand Up @@ -830,6 +905,20 @@ void AtlasEngine::_recreateSizeDependentResources()
THROW_IF_FAILED(_r.swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), buffer.put_void()));
THROW_IF_FAILED(_r.device->CreateRenderTargetView(buffer.get(), nullptr, _r.renderTargetView.put()));
}
if (_r.customPixelShader)
{
D3D11_TEXTURE2D_DESC desc{};
desc.Width = _api.sizeInPixel.x;
desc.Height = _api.sizeInPixel.y;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.SampleDesc = { 1, 0 };
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET;
THROW_IF_FAILED(_r.device->CreateTexture2D(&desc, nullptr, _r.customOffscreenTexture.addressof()));
THROW_IF_FAILED(_r.device->CreateShaderResourceView(_r.customOffscreenTexture.get(), nullptr, _r.customOffscreenTextureView.addressof()));
THROW_IF_FAILED(_r.device->CreateRenderTargetView(_r.customOffscreenTexture.get(), nullptr, _r.customOffscreenTextureTargetView.addressof()));
}

// Tell D3D which parts of the render target will be visible.
// Everything outside of the viewport will be black.
Expand Down
Loading