AtlasEngine: Implement support for custom shaders (#13885)

This commit implements support for custom shaders in AtlasEngine
(`experimental.retroTerminalEffect` and `experimental.pixelShaderPath`).
Setting these properties invalidates the device because that made it
the easiest to implement this less often used feature.
The retro shader was slightly rewritten so that it compiles without warnings.

Additionally we noticed that AtlasEngine works well with D3D 10.0 hardware,
so support for that was added bringing feature parity with DxRenderer.

Closes #13853

## Validation Steps Performed
* Default settings (Independent Flip) 
* ClearType (Independent Flip) 
* Retro Terminal Effect (Composed Flip) 
* Use wallpaper as background image (Composed Flip) 
  * Running `color 40` draws everything red 
  * With Retro Terminal Effect 
This commit is contained in:
Leonard Hecker
2022-08-31 21:54:43 +02:00
committed by GitHub
parent cbbd1e8699
commit 83aff8d6f0
9 changed files with 421 additions and 78 deletions

View File

@@ -2,47 +2,46 @@
Texture2D shaderTexture; Texture2D shaderTexture;
SamplerState samplerState; SamplerState samplerState;
cbuffer PixelShaderSettings { cbuffer PixelShaderSettings
float Time; {
float Scale; float time;
float2 Resolution; float scale;
float4 Background; float2 resolution;
float4 background;
}; };
#define SCANLINE_FACTOR 0.5 #define SCANLINE_FACTOR 0.5f
#define SCALED_SCANLINE_PERIOD Scale #define SCALED_SCANLINE_PERIOD scale
#define SCALED_GAUSSIAN_SIGMA (2.0*Scale) #define SCALED_GAUSSIAN_SIGMA (2.0f * scale)
static const float M_PI = 3.14159265f; static const float M_PI = 3.14159265f;
float Gaussian2D(float x, float y, float sigma) 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) float4 Blur(Texture2D input, float2 tex_coord, float sigma)
{ {
uint width, height; float width, height;
shaderTexture.GetDimensions(width, height); shaderTexture.GetDimensions(width, height);
float texelWidth = 1.0f/width; float texelWidth = 1.0f / width;
float texelHeight = 1.0f/height; float texelHeight = 1.0f / height;
float4 color = { 0, 0, 0, 0 }; 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 }; float2 samplePos = { 0, 0 };
samplePos.x = tex_coord.x + (x - sampleCount / 2.0f) * texelWidth;
samplePos.x = tex_coord.x + (x - sampleCount/2) * texelWidth; for (float y = 0; y < sampleCount; y++)
for (int y = 0; y < sampleCount; y++)
{ {
samplePos.y = tex_coord.y + (y - sampleCount/2) * texelHeight; samplePos.y = tex_coord.y + (y - sampleCount / 2.0f) * texelHeight;
if (samplePos.x <= 0 || samplePos.y <= 0 || samplePos.x >= width || samplePos.y >= height) continue; color += input.Sample(samplerState, samplePos) * Gaussian2D(x - sampleCount / 2.0f, y - sampleCount / 2.0f, sigma);
color += input.Sample(samplerState, samplePos) * Gaussian2D((x - sampleCount/2), (y - sampleCount/2), sigma);
} }
} }
@@ -51,7 +50,7 @@ float4 Blur(Texture2D input, float2 tex_coord, float sigma)
float SquareWave(float y) 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) float4 Scanline(float4 color, float4 pos)
@@ -60,9 +59,9 @@ float4 Scanline(float4 color, float4 pos)
// TODO:GH#3929 make this configurable. // TODO:GH#3929 make this configurable.
// Remove the && false to draw scanlines everywhere. // 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 else
{ {
@@ -70,14 +69,14 @@ float4 Scanline(float4 color, float4 pos)
} }
} }
// clang-format off
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET 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. // TODO:GH#3930 Make these configurable in some way.
float4 color = input.Sample(samplerState, tex); float4 color = shaderTexture.Sample(samplerState, tex);
color += Blur(input, tex, SCALED_GAUSSIAN_SIGMA)*0.3; color += Blur(shaderTexture, tex, SCALED_GAUSSIAN_SIGMA) * 0.3f;
color = Scanline(color, pos); color = Scanline(color, pos);
return color; return color;
} }

View File

@@ -293,7 +293,7 @@ HRESULT AtlasEngine::Enable() noexcept
[[nodiscard]] bool AtlasEngine::GetRetroTerminalEffect() const noexcept [[nodiscard]] bool AtlasEngine::GetRetroTerminalEffect() const noexcept
{ {
return false; return _api.useRetroTerminalEffect;
} }
[[nodiscard]] float AtlasEngine::GetScaling() const noexcept [[nodiscard]] float AtlasEngine::GetScaling() const noexcept
@@ -332,7 +332,7 @@ void AtlasEngine::SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasin
if (_api.antialiasingMode != mode) if (_api.antialiasingMode != mode)
{ {
_api.antialiasingMode = mode; _api.antialiasingMode = mode;
_resolveAntialiasingMode(); _resolveTransparencySettings();
WI_SetFlag(_api.invalidations, ApiInvalidations::Font); WI_SetFlag(_api.invalidations, ApiInvalidations::Font);
} }
} }
@@ -344,11 +344,10 @@ void AtlasEngine::SetCallback(std::function<void()> pfn) noexcept
void AtlasEngine::EnableTransparentBackground(const bool isTransparent) noexcept void AtlasEngine::EnableTransparentBackground(const bool isTransparent) noexcept
{ {
const auto mixin = !isTransparent ? 0xff000000 : 0x00000000; if (_api.enableTransparentBackground != isTransparent)
if (_api.backgroundOpaqueMixin != mixin)
{ {
_api.backgroundOpaqueMixin = mixin; _api.enableTransparentBackground = isTransparent;
_resolveAntialiasingMode(); _resolveTransparencySettings();
WI_SetFlag(_api.invalidations, ApiInvalidations::SwapChain); WI_SetFlag(_api.invalidations, ApiInvalidations::SwapChain);
} }
} }
@@ -369,10 +368,22 @@ void AtlasEngine::SetForceFullRepaintRendering(bool enable) noexcept
void AtlasEngine::SetPixelShaderPath(std::wstring_view value) 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 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 void AtlasEngine::SetSelectionBackground(const COLORREF color, const float alpha) noexcept
@@ -451,13 +462,15 @@ void AtlasEngine::UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept
#pragma endregion #pragma endregion
void AtlasEngine::_resolveAntialiasingMode() noexcept void AtlasEngine::_resolveTransparencySettings() noexcept
{ {
// If the user asks for ClearType, but also for a transparent background // If the user asks for ClearType, but also for a transparent background
// (which our ClearType shader doesn't simultaneously support) // (which our ClearType shader doesn't simultaneously support)
// then we need to sneakily force the renderer to grayscale AA. // 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 = _api.enableTransparentBackground && _api.antialiasingMode == D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE ? D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE : _api.antialiasingMode;
_api.realizedAntialiasingMode = forceGrayscaleAA ? 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) 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)

View File

@@ -4,6 +4,8 @@
#include "pch.h" #include "pch.h"
#include "AtlasEngine.h" #include "AtlasEngine.h"
#include <custom_shader_ps.h>
#include <custom_shader_vs.h>
#include <shader_ps.h> #include <shader_ps.h>
#include <shader_vs.h> #include <shader_vs.h>
@@ -301,25 +303,6 @@ try
} }
CATCH_RETURN() 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 [[nodiscard]] HRESULT AtlasEngine::PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept
{ {
RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint); RETURN_HR_IF_NULL(E_INVALIDARG, pForcePaint);
@@ -607,6 +590,7 @@ void AtlasEngine::_createResources()
D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_1,
D3D_FEATURE_LEVEL_10_0,
}; };
auto hr = S_OK; auto hr = S_OK;
@@ -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->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())); 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
#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,
/* 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); WI_ClearFlag(_api.invalidations, ApiInvalidations::Device);
@@ -711,10 +787,9 @@ void AtlasEngine::_createSwapChain()
desc.BufferCount = 2; desc.BufferCount = 2;
desc.Scaling = DXGI_SCALING_NONE; desc.Scaling = DXGI_SCALING_NONE;
desc.SwapEffect = _sr.isWindows10OrGreater && !_r.d2dMode ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; 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.
// * 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.
// 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.AlphaMode = _api.hwnd || _api.backgroundOpaqueMixin ? DXGI_ALPHA_MODE_IGNORE : DXGI_ALPHA_MODE_PREMULTIPLIED;
desc.Flags = debugGeneralPerformance ? 0 : DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; desc.Flags = debugGeneralPerformance ? 0 : DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT;
wil::com_ptr<IDXGIFactory2> dxgiFactory; wil::com_ptr<IDXGIFactory2> dxgiFactory;
@@ -830,6 +905,20 @@ void AtlasEngine::_recreateSizeDependentResources()
THROW_IF_FAILED(_r.swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), buffer.put_void())); THROW_IF_FAILED(_r.swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), buffer.put_void()));
THROW_IF_FAILED(_r.device->CreateRenderTargetView(buffer.get(), nullptr, _r.renderTargetView.put())); 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. // Tell D3D which parts of the render target will be visible.
// Everything outside of the viewport will be black. // Everything outside of the viewport will be black.

View File

@@ -113,6 +113,16 @@ namespace Microsoft::Console::Render
ATLAS_POD_OPS(vec2) ATLAS_POD_OPS(vec2)
}; };
template<typename T>
struct vec3
{
T x{};
T y{};
T z{};
ATLAS_POD_OPS(vec3)
};
template<typename T> template<typename T>
struct vec4 struct vec4
{ {
@@ -155,6 +165,7 @@ namespace Microsoft::Console::Render
using f32 = float; using f32 = float;
using f32x2 = vec2<f32>; using f32x2 = vec2<f32>;
using f32x3 = vec3<f32>;
using f32x4 = vec4<f32>; using f32x4 = vec4<f32>;
struct TextAnalyzerResult struct TextAnalyzerResult
@@ -857,6 +868,16 @@ namespace Microsoft::Console::Render
#pragma warning(suppress : 4324) // 'ConstBuffer': structure was padded due to alignment specifier #pragma warning(suppress : 4324) // 'ConstBuffer': structure was padded due to alignment specifier
}; };
struct alignas(16) CustomConstBuffer
{
// WARNING: Same rules as for ConstBuffer above apply.
alignas(sizeof(f32)) f32 time = 0;
alignas(sizeof(f32)) f32 scale = 0;
alignas(sizeof(f32x2)) f32x2 resolution;
alignas(sizeof(f32x4)) f32x4 background;
#pragma warning(suppress : 4324) // 'CustomConstBuffer': structure was padded due to alignment specifier
};
// Handled in BeginPaint() // Handled in BeginPaint()
enum class ApiInvalidations : u8 enum class ApiInvalidations : u8
{ {
@@ -904,11 +925,12 @@ namespace Microsoft::Console::Render
bool _emplaceGlyph(IDWriteFontFace* fontFace, size_t bufferPos1, size_t bufferPos2); bool _emplaceGlyph(IDWriteFontFace* fontFace, size_t bufferPos1, size_t bufferPos2);
// AtlasEngine.api.cpp // AtlasEngine.api.cpp
void _resolveAntialiasingMode() noexcept; void _resolveTransparencySettings() noexcept;
void _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); void _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);
void _resolveFontMetrics(const wchar_t* faceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontMetrics* fontMetrics = nullptr) const; void _resolveFontMetrics(const wchar_t* faceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontMetrics* fontMetrics = nullptr) const;
// AtlasEngine.r.cpp // AtlasEngine.r.cpp
void _renderWithCustomShader() const;
void _setShaderResources() const; void _setShaderResources() const;
void _updateConstantBuffer() const noexcept; void _updateConstantBuffer() const noexcept;
void _adjustAtlasSize(); void _adjustAtlasSize();
@@ -974,6 +996,14 @@ namespace Microsoft::Console::Render
wil::com_ptr<ID3D11Buffer> constantBuffer; wil::com_ptr<ID3D11Buffer> constantBuffer;
wil::com_ptr<ID3D11Buffer> cellBuffer; wil::com_ptr<ID3D11Buffer> cellBuffer;
wil::com_ptr<ID3D11ShaderResourceView> cellView; wil::com_ptr<ID3D11ShaderResourceView> cellView;
wil::com_ptr<ID3D11Texture2D> customOffscreenTexture;
wil::com_ptr<ID3D11ShaderResourceView> customOffscreenTextureView;
wil::com_ptr<ID3D11RenderTargetView> customOffscreenTextureTargetView;
wil::com_ptr<ID3D11VertexShader> customVertexShader;
wil::com_ptr<ID3D11PixelShader> customPixelShader;
wil::com_ptr<ID3D11Buffer> customShaderConstantBuffer;
wil::com_ptr<ID3D11SamplerState> customShaderSamplerState;
std::chrono::steady_clock::time_point customShaderStartTime;
// D2D resources // D2D resources
wil::com_ptr<ID3D11Texture2D> atlasBuffer; wil::com_ptr<ID3D11Texture2D> atlasBuffer;
@@ -1013,6 +1043,7 @@ namespace Microsoft::Console::Render
i16 scrollOffset = 0; i16 scrollOffset = 0;
bool d2dMode = false; bool d2dMode = false;
bool waitForPresentation = false; bool waitForPresentation = false;
bool requiresContinuousRedraw = false;
#ifndef NDEBUG #ifndef NDEBUG
// See documentation for IDXGISwapChain2::GetFrameLatencyWaitableObject method: // See documentation for IDXGISwapChain2::GetFrameLatencyWaitableObject method:
@@ -1045,7 +1076,7 @@ namespace Microsoft::Console::Render
u16x2 sizeInPixel; // changes are flagged as ApiInvalidations::Size u16x2 sizeInPixel; // changes are flagged as ApiInvalidations::Size
// UpdateDrawingBrushes() // UpdateDrawingBrushes()
u32 backgroundOpaqueMixin = 0xff000000; // changes are flagged as ApiInvalidations::Device u32 backgroundOpaqueMixin = 0xff000000; // changes are flagged as ApiInvalidations::SwapChain
u32x2 currentColor; u32x2 currentColor;
AtlasKeyAttributes attributes{}; AtlasKeyAttributes attributes{};
u16x2 lastPaintBufferLineCoord; u16x2 lastPaintBufferLineCoord;
@@ -1069,7 +1100,11 @@ namespace Microsoft::Console::Render
HWND hwnd = nullptr; HWND hwnd = nullptr;
u16 dpi = USER_DEFAULT_SCREEN_DPI; // changes are flagged as ApiInvalidations::Font|Size u16 dpi = USER_DEFAULT_SCREEN_DPI; // changes are flagged as ApiInvalidations::Font|Size
u8 antialiasingMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; // changes are flagged as ApiInvalidations::Font u8 antialiasingMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; // changes are flagged as ApiInvalidations::Font
u8 realizedAntialiasingMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; // caches antialiasingMode, depends on antialiasingMode and backgroundOpaqueMixin, see _resolveAntialiasingMode u8 realizedAntialiasingMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; // caches antialiasingMode, depends on antialiasingMode and backgroundOpaqueMixin, see _resolveTransparencySettings
bool enableTransparentBackground = false;
std::wstring customPixelShaderPath; // changes are flagged as ApiInvalidations::Device
bool useRetroTerminalEffect = false; // changes are flagged as ApiInvalidations::Device
ApiInvalidations invalidations = ApiInvalidations::Device; ApiInvalidations invalidations = ApiInvalidations::Device;
} _api; } _api;

View File

@@ -44,7 +44,8 @@ constexpr bool isInInversionList(const std::array<wchar_t, N>& ranges, wchar_t n
return (idx & 1) != 0; return (idx & 1) != 0;
} }
constexpr D2D1_COLOR_F colorFromU32(uint32_t rgba) template<typename T = D2D1_COLOR_F>
constexpr T colorFromU32(uint32_t rgba)
{ {
const auto r = static_cast<float>((rgba >> 0) & 0xff) / 255.0f; const auto r = static_cast<float>((rgba >> 0) & 0xff) / 255.0f;
const auto g = static_cast<float>((rgba >> 8) & 0xff) / 255.0f; const auto g = static_cast<float>((rgba >> 8) & 0xff) / 255.0f;
@@ -92,10 +93,15 @@ try
_r.deviceContext->Unmap(_r.cellBuffer.get(), 0); _r.deviceContext->Unmap(_r.cellBuffer.get(), 0);
} }
// After Present calls, the back buffer needs to explicitly be if (_r.customPixelShader) [[unlikely]]
// re-bound to the D3D11 immediate context before it can be used again. {
_r.deviceContext->OMSetRenderTargets(1, _r.renderTargetView.addressof(), nullptr); _renderWithCustomShader();
_r.deviceContext->Draw(3, 0); }
else
{
_r.deviceContext->OMSetRenderTargets(1, _r.renderTargetView.addressof(), nullptr);
_r.deviceContext->Draw(3, 0);
}
// > IDXGISwapChain::Present: Partial Presentation (using a dirty rects or scroll) is not supported // > IDXGISwapChain::Present: Partial Presentation (using a dirty rects or scroll) is not supported
// > for SwapChains created with DXGI_SWAP_EFFECT_DISCARD or DXGI_SWAP_EFFECT_FLIP_DISCARD. // > for SwapChains created with DXGI_SWAP_EFFECT_DISCARD or DXGI_SWAP_EFFECT_FLIP_DISCARD.
@@ -118,23 +124,103 @@ catch (const wil::ResultException& exception)
} }
CATCH_RETURN() CATCH_RETURN()
[[nodiscard]] bool AtlasEngine::RequiresContinuousRedraw() noexcept
{
return debugGeneralPerformance || _r.requiresContinuousRedraw;
}
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
}
}
#pragma endregion #pragma endregion
void AtlasEngine::_renderWithCustomShader() const
{
// Render with our main shader just like Present().
{
// OM: Output Merger
_r.deviceContext->OMSetRenderTargets(1, _r.customOffscreenTextureTargetView.addressof(), nullptr);
_r.deviceContext->Draw(3, 0);
}
// Update the custom shader's constant buffer.
{
CustomConstBuffer data;
data.time = std::chrono::duration<float>(std::chrono::steady_clock::now() - _r.customShaderStartTime).count();
data.scale = _r.pixelPerDIP;
data.resolution.x = static_cast<float>(_r.cellCount.x * _r.fontMetrics.cellSize.x);
data.resolution.y = static_cast<float>(_r.cellCount.y * _r.fontMetrics.cellSize.y);
data.background = colorFromU32<f32x4>(_r.backgroundColor);
#pragma warning(suppress : 26494) // Variable 'mapped' is uninitialized. Always initialize an object (type.5).
D3D11_MAPPED_SUBRESOURCE mapped;
THROW_IF_FAILED(_r.deviceContext->Map(_r.customShaderConstantBuffer.get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped));
assert(mapped.RowPitch >= sizeof(data));
memcpy(mapped.pData, &data, sizeof(data));
_r.deviceContext->Unmap(_r.customShaderConstantBuffer.get(), 0);
}
// Render with the custom shader.
{
// OM: Output Merger
// customOffscreenTextureView was just rendered to via customOffscreenTextureTargetView and is
// set as the output target. Before we can use it as an input we have to remove it as an output.
_r.deviceContext->OMSetRenderTargets(1, _r.renderTargetView.addressof(), nullptr);
// VS: Vertex Shader
_r.deviceContext->VSSetShader(_r.customVertexShader.get(), nullptr, 0);
// PS: Pixel Shader
_r.deviceContext->PSSetShader(_r.customPixelShader.get(), nullptr, 0);
_r.deviceContext->PSSetConstantBuffers(0, 1, _r.customShaderConstantBuffer.addressof());
_r.deviceContext->PSSetShaderResources(0, 1, _r.customOffscreenTextureView.addressof());
_r.deviceContext->PSSetSamplers(0, 1, _r.customShaderSamplerState.addressof());
_r.deviceContext->Draw(4, 0);
}
// For the next frame we need to restore our context state.
{
// VS: Vertex Shader
_r.deviceContext->VSSetShader(_r.vertexShader.get(), nullptr, 0);
// PS: Pixel Shader
_r.deviceContext->PSSetShader(_r.pixelShader.get(), nullptr, 0);
_r.deviceContext->PSSetConstantBuffers(0, 1, _r.constantBuffer.addressof());
const std::array resources{ _r.cellView.get(), _r.atlasView.get() };
_r.deviceContext->PSSetShaderResources(0, gsl::narrow_cast<UINT>(resources.size()), resources.data());
_r.deviceContext->PSSetSamplers(0, 0, nullptr);
}
}
void AtlasEngine::_setShaderResources() const void AtlasEngine::_setShaderResources() const
{ {
_r.deviceContext->VSSetShader(_r.vertexShader.get(), nullptr, 0); // IA: Input Assembler
_r.deviceContext->PSSetShader(_r.pixelShader.get(), nullptr, 0);
// Our vertex shader uses a trick from Bill Bilodeau published in // Our vertex shader uses a trick from Bill Bilodeau published in
// "Vertex Shader Tricks" at GDC14 to draw a fullscreen triangle // "Vertex Shader Tricks" at GDC14 to draw a fullscreen triangle
// without vertex/index buffers. This prepares our context for this. // without vertex/index buffers. This prepares our context for this.
_r.deviceContext->IASetVertexBuffers(0, 0, nullptr, nullptr, nullptr); _r.deviceContext->IASetVertexBuffers(0, 0, nullptr, nullptr, nullptr);
_r.deviceContext->IASetIndexBuffer(nullptr, DXGI_FORMAT_UNKNOWN, 0); _r.deviceContext->IASetIndexBuffer(nullptr, DXGI_FORMAT_UNKNOWN, 0);
_r.deviceContext->IASetInputLayout(nullptr); _r.deviceContext->IASetInputLayout(nullptr);
_r.deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); _r.deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
// VS: Vertex Shader
_r.deviceContext->VSSetShader(_r.vertexShader.get(), nullptr, 0);
// PS: Pixel Shader
_r.deviceContext->PSSetShader(_r.pixelShader.get(), nullptr, 0);
_r.deviceContext->PSSetConstantBuffers(0, 1, _r.constantBuffer.addressof()); _r.deviceContext->PSSetConstantBuffers(0, 1, _r.constantBuffer.addressof());
const std::array resources{ _r.cellView.get(), _r.atlasView.get() }; const std::array resources{ _r.cellView.get(), _r.atlasView.get() };
_r.deviceContext->PSSetShaderResources(0, gsl::narrow_cast<UINT>(resources.size()), resources.data()); _r.deviceContext->PSSetShaderResources(0, gsl::narrow_cast<UINT>(resources.size()), resources.data());
} }

View File

@@ -27,12 +27,34 @@
<ClInclude Include="AtlasEngine.h" /> <ClInclude Include="AtlasEngine.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<FxCompile Include="custom_shader_ps.hlsl">
<ShaderType>Pixel</ShaderType>
<ShaderModel>4.0</ShaderModel>
<AllResourcesBound>true</AllResourcesBound>
<VariableName>custom_shader_ps</VariableName>
<ObjectFileOutput />
<HeaderFileOutput>$(OutDir)$(ProjectName)\%(Filename).h</HeaderFileOutput>
<TreatWarningAsError>true</TreatWarningAsError>
<AdditionalOptions>/Zpc %(AdditionalOptions)</AdditionalOptions>
<AdditionalOptions Condition="'$(Configuration)'=='Release'">/O3 /Qstrip_debug /Qstrip_reflect %(AdditionalOptions)</AdditionalOptions>
</FxCompile>
<FxCompile Include="custom_shader_vs.hlsl">
<ShaderType>Vertex</ShaderType>
<ShaderModel>4.0</ShaderModel>
<AllResourcesBound>true</AllResourcesBound>
<VariableName>custom_shader_vs</VariableName>
<ObjectFileOutput />
<HeaderFileOutput>$(OutDir)$(ProjectName)\%(Filename).h</HeaderFileOutput>
<TreatWarningAsError>true</TreatWarningAsError>
<AdditionalOptions>/Zpc %(AdditionalOptions)</AdditionalOptions>
<AdditionalOptions Condition="'$(Configuration)'=='Release'">/O3 /Qstrip_debug /Qstrip_reflect %(AdditionalOptions)</AdditionalOptions>
</FxCompile>
<FxCompile Include="dwrite.hlsl"> <FxCompile Include="dwrite.hlsl">
<ExcludedFromBuild>true</ExcludedFromBuild> <ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile> </FxCompile>
<FxCompile Include="shader_ps.hlsl"> <FxCompile Include="shader_ps.hlsl">
<ShaderType>Pixel</ShaderType> <ShaderType>Pixel</ShaderType>
<ShaderModel>4.1</ShaderModel> <ShaderModel>4.0</ShaderModel>
<AllResourcesBound>true</AllResourcesBound> <AllResourcesBound>true</AllResourcesBound>
<VariableName>shader_ps</VariableName> <VariableName>shader_ps</VariableName>
<ObjectFileOutput /> <ObjectFileOutput />
@@ -43,7 +65,7 @@
</FxCompile> </FxCompile>
<FxCompile Include="shader_vs.hlsl"> <FxCompile Include="shader_vs.hlsl">
<ShaderType>Vertex</ShaderType> <ShaderType>Vertex</ShaderType>
<ShaderModel>4.1</ShaderModel> <ShaderModel>4.0</ShaderModel>
<AllResourcesBound>true</AllResourcesBound> <AllResourcesBound>true</AllResourcesBound>
<VariableName>shader_vs</VariableName> <VariableName>shader_vs</VariableName>
<ObjectFileOutput /> <ObjectFileOutput />

View File

@@ -0,0 +1,82 @@
// The original retro pixel shader
Texture2D shaderTexture;
SamplerState samplerState;
cbuffer PixelShaderSettings
{
float time;
float scale;
float2 resolution;
float4 background;
};
#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);
}
float4 Blur(Texture2D input, float2 tex_coord, float sigma)
{
float width, height;
shaderTexture.GetDimensions(width, height);
float texelWidth = 1.0f / width;
float texelHeight = 1.0f / height;
float4 color = { 0, 0, 0, 0 };
float sampleCount = 13;
for (float x = 0; x < sampleCount; x++)
{
float2 samplePos = { 0, 0 };
samplePos.x = tex_coord.x + (x - sampleCount / 2.0f) * texelWidth;
for (float y = 0; y < sampleCount; y++)
{
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);
}
}
return color;
}
float SquareWave(float y)
{
return 1.0f - (floor(y / SCALED_SCANLINE_PERIOD) % 2.0f) * SCANLINE_FACTOR;
}
float4 Scanline(float4 color, float4 pos)
{
float wave = SquareWave(pos.y);
// TODO:GH#3929 make this configurable.
// Remove the && false to draw scanlines everywhere.
if (length(color.rgb) < 0.2f && false)
{
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
{
// TODO:GH#3930 Make these configurable in some way.
float4 color = shaderTexture.Sample(samplerState, tex);
color += Blur(shaderTexture, tex, SCALED_GAUSSIAN_SIGMA) * 0.3f;
color = Scanline(color, pos);
return color;
}

View File

@@ -0,0 +1,17 @@
struct VS_OUTPUT
{
float4 pos : SV_POSITION;
float2 tex : TEXCOORD;
};
// clang-format off
VS_OUTPUT main(uint id : SV_VERTEXID)
// clang-format on
{
VS_OUTPUT output;
// The following two lines are taken from https://gamedev.stackexchange.com/a/77670
// written by János Turánszki, licensed under CC BY-SA 3.0.
output.tex = float2(id % 2, id % 4 / 2);
output.pos = float4((output.tex.x - 0.5f) * 2.0f, -(output.tex.y - 0.5f) * 2.0f, 0, 1);
return output;
}

View File

@@ -446,9 +446,9 @@ HRESULT DxEngine::_SetupTerminalEffects()
// Sampler state is needed to use texture as input to shader. // Sampler state is needed to use texture as input to shader.
D3D11_SAMPLER_DESC samplerDesc{}; D3D11_SAMPLER_DESC samplerDesc{};
samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR; samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP; samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER;
samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP; samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER;
samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP; samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_BORDER;
samplerDesc.MipLODBias = 0.0f; samplerDesc.MipLODBias = 0.0f;
samplerDesc.MaxAnisotropy = 1; samplerDesc.MaxAnisotropy = 1;
samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS; samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;