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;
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);
}
}
@@ -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)
@@ -60,9 +59,9 @@ 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
{
@@ -70,14 +69,14 @@ float4 Scanline(float4 color, float4 pos)
}
}
// 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;
}
}

View File

@@ -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
@@ -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);
}
}
@@ -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);
}
}
@@ -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
@@ -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)

View File

@@ -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>
@@ -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);
@@ -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;
@@ -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
#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);
@@ -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;
@@ -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.

View File

@@ -113,6 +113,16 @@ namespace Microsoft::Console::Render
ATLAS_POD_OPS(vec2)
};
template<typename T>
struct vec3
{
T x{};
T y{};
T z{};
ATLAS_POD_OPS(vec3)
};
template<typename T>
struct vec4
{
@@ -155,6 +165,7 @@ namespace Microsoft::Console::Render
using f32 = float;
using f32x2 = vec2<f32>;
using f32x3 = vec3<f32>;
using f32x4 = vec4<f32>;
struct TextAnalyzerResult
@@ -857,6 +868,16 @@ namespace Microsoft::Console::Render
#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()
enum class ApiInvalidations : u8
{
@@ -904,11 +925,12 @@ namespace Microsoft::Console::Render
bool _emplaceGlyph(IDWriteFontFace* fontFace, size_t bufferPos1, size_t bufferPos2);
// 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 _resolveFontMetrics(const wchar_t* faceName, const FontInfoDesired& fontInfoDesired, FontInfo& fontInfo, FontMetrics* fontMetrics = nullptr) const;
// AtlasEngine.r.cpp
void _renderWithCustomShader() const;
void _setShaderResources() const;
void _updateConstantBuffer() const noexcept;
void _adjustAtlasSize();
@@ -974,6 +996,14 @@ namespace Microsoft::Console::Render
wil::com_ptr<ID3D11Buffer> constantBuffer;
wil::com_ptr<ID3D11Buffer> cellBuffer;
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
wil::com_ptr<ID3D11Texture2D> atlasBuffer;
@@ -1013,6 +1043,7 @@ namespace Microsoft::Console::Render
i16 scrollOffset = 0;
bool d2dMode = false;
bool waitForPresentation = false;
bool requiresContinuousRedraw = false;
#ifndef NDEBUG
// See documentation for IDXGISwapChain2::GetFrameLatencyWaitableObject method:
@@ -1045,7 +1076,7 @@ namespace Microsoft::Console::Render
u16x2 sizeInPixel; // changes are flagged as ApiInvalidations::Size
// UpdateDrawingBrushes()
u32 backgroundOpaqueMixin = 0xff000000; // changes are flagged as ApiInvalidations::Device
u32 backgroundOpaqueMixin = 0xff000000; // changes are flagged as ApiInvalidations::SwapChain
u32x2 currentColor;
AtlasKeyAttributes attributes{};
u16x2 lastPaintBufferLineCoord;
@@ -1069,7 +1100,11 @@ namespace Microsoft::Console::Render
HWND hwnd = nullptr;
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 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;
} _api;

View File

@@ -44,7 +44,8 @@ constexpr bool isInInversionList(const std::array<wchar_t, N>& ranges, wchar_t n
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 g = static_cast<float>((rgba >> 8) & 0xff) / 255.0f;
@@ -92,10 +93,15 @@ try
_r.deviceContext->Unmap(_r.cellBuffer.get(), 0);
}
// After Present calls, the back buffer needs to explicitly be
// re-bound to the D3D11 immediate context before it can be used again.
_r.deviceContext->OMSetRenderTargets(1, _r.renderTargetView.addressof(), nullptr);
_r.deviceContext->Draw(3, 0);
if (_r.customPixelShader) [[unlikely]]
{
_renderWithCustomShader();
}
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
// > 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()
[[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
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
{
_r.deviceContext->VSSetShader(_r.vertexShader.get(), nullptr, 0);
_r.deviceContext->PSSetShader(_r.pixelShader.get(), nullptr, 0);
// IA: Input Assembler
// Our vertex shader uses a trick from Bill Bilodeau published in
// "Vertex Shader Tricks" at GDC14 to draw a fullscreen triangle
// without vertex/index buffers. This prepares our context for this.
_r.deviceContext->IASetVertexBuffers(0, 0, nullptr, nullptr, nullptr);
_r.deviceContext->IASetIndexBuffer(nullptr, DXGI_FORMAT_UNKNOWN, 0);
_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());
const std::array resources{ _r.cellView.get(), _r.atlasView.get() };
_r.deviceContext->PSSetShaderResources(0, gsl::narrow_cast<UINT>(resources.size()), resources.data());
}

View File

@@ -27,12 +27,34 @@
<ClInclude Include="AtlasEngine.h" />
</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">
<ExcludedFromBuild>true</ExcludedFromBuild>
</FxCompile>
<FxCompile Include="shader_ps.hlsl">
<ShaderType>Pixel</ShaderType>
<ShaderModel>4.1</ShaderModel>
<ShaderModel>4.0</ShaderModel>
<AllResourcesBound>true</AllResourcesBound>
<VariableName>shader_ps</VariableName>
<ObjectFileOutput />
@@ -43,7 +65,7 @@
</FxCompile>
<FxCompile Include="shader_vs.hlsl">
<ShaderType>Vertex</ShaderType>
<ShaderModel>4.1</ShaderModel>
<ShaderModel>4.0</ShaderModel>
<AllResourcesBound>true</AllResourcesBound>
<VariableName>shader_vs</VariableName>
<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.
D3D11_SAMPLER_DESC samplerDesc{};
samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER;
samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER;
samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_BORDER;
samplerDesc.MipLODBias = 0.0f;
samplerDesc.MaxAnisotropy = 1;
samplerDesc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;