Skip to content

Commit

Permalink
AtlasEngine: Implement support for custom shaders
Browse files Browse the repository at this point in the history
  • Loading branch information
lhecker committed Aug 30, 2022
1 parent bc12e2f commit 43895da
Show file tree
Hide file tree
Showing 8 changed files with 404 additions and 74 deletions.
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);
}
}

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
122 changes: 99 additions & 23 deletions src/renderer/atlas/AtlasEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
#include <shader_ps.h>
#include <shader_vs.h>

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

#include "../../interactivity/win32/CustomWindowMessages.h"

// #### NOTE ####
Expand Down Expand Up @@ -301,25 +304,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 @@ -668,6 +652,85 @@ 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 auto target = _r.device->GetFeatureLevel() == D3D_FEATURE_LEVEL_10_1 ? "ps_4_1" : "ps_5_0";
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_CLAMP;
desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
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 +774,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 +892,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

0 comments on commit 43895da

Please sign in to comment.