Skip to content

Commit

Permalink
Added experimental.pixelShaderImagePath (#14073)
Browse files Browse the repository at this point in the history
I realize I might be one of the few developers that care about custom
shader support in terminal but I thought it's worth proposing it and see
what you think.

This is to support custom shaders with custom textures.

I was thinking of exposing the background image to the shader but that
felt complicated after looking into it.

I have tested exploratively. I think the texture loader is possible to
unit test so that is a possible improvement.

The error reporting (as with other custom pixel shader code) is not very
good. That is also an area that I could improve upon.

I do think the risk of adding this is rather low as the new code is only
executed when experimental.pixelShaderImagePath is set.

### Details

Only added to the Atlas engine.

Instead I load the texture using WIC into a shader resource view. When
binding shader resources I test for presence of custom texture and bind
it to register t1.

The image loading code was found in [the D3D Texture documentation].
It's a mouthful but seems rather robust.


Tested setting: "experimental.pixelShaderImagePath"

1. Tested not specifying it.
2. Tested setting it.
3. Tested changing it (the changes are picked up)
4. Tested invalid path
5. Tested a custom shader that made use of the custom texture.

[the D3D Texture documentation]: https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-textures-how-to

Co-authored-by: Mike Griese <[email protected]>
Co-authored-by: Leonard Hecker <[email protected]>
  • Loading branch information
3 people authored Mar 8, 2024
1 parent 8a1e8ac commit 0ba680a
Show file tree
Hide file tree
Showing 21 changed files with 250 additions and 61 deletions.
23 changes: 23 additions & 0 deletions samples/PixelShaders/BackgroundImage.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Demo shader to show passing in an image using
// experimental.pixelShaderImagePath. This shader simply displays the Terminal
// contents on top of the given image.
//
// The image loaded by the terminal will be placed into the `image` texture.

SamplerState samplerState;
Texture2D shaderTexture : register(t0);
Texture2D image : register(t1);

cbuffer PixelShaderSettings {
float Time;
float Scale;
float2 Resolution;
float4 Background;
};

float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
float4 terminalColor = shaderTexture.Sample(samplerState, tex);
float4 imageColor = image.Sample(samplerState, tex);
return lerp(imageColor, terminalColor, terminalColor.a);
}
2 changes: 2 additions & 0 deletions src/cascadia/TerminalControl/ControlCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation

_renderEngine->SetRetroTerminalEffect(_settings->RetroTerminalEffect());
_renderEngine->SetPixelShaderPath(_settings->PixelShaderPath());
_renderEngine->SetPixelShaderImagePath(_settings->PixelShaderImagePath());
_renderEngine->SetForceFullRepaintRendering(_settings->ForceFullRepaintRendering());
_renderEngine->SetSoftwareRendering(_settings->SoftwareRendering());

Expand Down Expand Up @@ -914,6 +915,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_renderEngine->SetSelectionBackground(til::color{ newAppearance->SelectionBackground() });
_renderEngine->SetRetroTerminalEffect(newAppearance->RetroTerminalEffect());
_renderEngine->SetPixelShaderPath(newAppearance->PixelShaderPath());
_renderEngine->SetPixelShaderImagePath(newAppearance->PixelShaderImagePath());

// Incase EnableUnfocusedAcrylic is disabled and Focused Acrylic is set to true,
// the terminal should ignore the unfocused opacity from settings.
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/IControlAppearance.idl
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ namespace Microsoft.Terminal.Control
// Experimental settings
Boolean RetroTerminalEffect { get; };
String PixelShaderPath { get; };
String PixelShaderImagePath { get; };
};
}
2 changes: 1 addition & 1 deletion src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1602,7 +1602,7 @@ til::point Terminal::GetViewportRelativeCursorPosition() const noexcept
// These functions are used by TerminalInput, which must build in conhost
// against OneCore compatible signatures. See the definitions in
// VtApiRedirection.hpp (which we cannot include cross-project.)
// Since we do nto run on OneCore, we can dispense with the compatibility
// Since we don't run on OneCore, we can dispense with the compatibility
// shims.
extern "C" UINT OneCoreSafeMapVirtualKeyW(_In_ UINT uCode, _In_ UINT uMapType)
{
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalSettingsModel/IAppearanceConfig.idl
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ namespace Microsoft.Terminal.Settings.Model

INHERITABLE_APPEARANCE_SETTING(Boolean, RetroTerminalEffect);
INHERITABLE_APPEARANCE_SETTING(String, PixelShaderPath);
INHERITABLE_APPEARANCE_SETTING(String, PixelShaderImagePath);
INHERITABLE_APPEARANCE_SETTING(IntenseStyle, IntenseTextStyle);
INHERITABLE_APPEARANCE_SETTING(Microsoft.Terminal.Core.AdjustTextMode, AdjustIndistinguishableColors);
INHERITABLE_APPEARANCE_SETTING(Double, Opacity);
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalSettingsModel/MTSMSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ Author(s):
X(winrt::Windows::UI::Xaml::Media::Stretch, BackgroundImageStretchMode, "backgroundImageStretchMode", winrt::Windows::UI::Xaml::Media::Stretch::UniformToFill) \
X(bool, RetroTerminalEffect, "experimental.retroTerminalEffect", false) \
X(hstring, PixelShaderPath, "experimental.pixelShaderPath") \
X(hstring, PixelShaderImagePath, "experimental.pixelShaderImagePath") \
X(ConvergedAlignment, BackgroundImageAlignment, "backgroundImageAlignment", ConvergedAlignment::Horizontal_Center | ConvergedAlignment::Vertical_Center) \
X(hstring, BackgroundImagePath, "backgroundImage") \
X(Model::IntenseStyle, IntenseTextStyle, "intenseTextStyle", Model::IntenseStyle::Bright) \
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalSettingsModel/TerminalSettings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation

_RetroTerminalEffect = appearance.RetroTerminalEffect();
_PixelShaderPath = winrt::hstring{ wil::ExpandEnvironmentStringsW<std::wstring>(appearance.PixelShaderPath().c_str()) };
_PixelShaderImagePath = winrt::hstring{ wil::ExpandEnvironmentStringsW<std::wstring>(appearance.PixelShaderImagePath().c_str()) };

_IntenseIsBold = WI_IsFlagSet(appearance.IntenseTextStyle(), Microsoft::Terminal::Settings::Model::IntenseStyle::Bold);
_IntenseIsBright = WI_IsFlagSet(appearance.IntenseTextStyle(), Microsoft::Terminal::Settings::Model::IntenseStyle::Bright);
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalSettingsModel/TerminalSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
INHERITABLE_SETTING(Model::TerminalSettings, bool, ForceVTInput, false);

INHERITABLE_SETTING(Model::TerminalSettings, hstring, PixelShaderPath);
INHERITABLE_SETTING(Model::TerminalSettings, hstring, PixelShaderImagePath);

INHERITABLE_SETTING(Model::TerminalSettings, bool, Elevate, false);

Expand Down
3 changes: 2 additions & 1 deletion src/cascadia/inc/ControlProperties.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
X(winrt::Windows::UI::Xaml::HorizontalAlignment, BackgroundImageHorizontalAlignment, winrt::Windows::UI::Xaml::HorizontalAlignment::Center) \
X(winrt::Windows::UI::Xaml::VerticalAlignment, BackgroundImageVerticalAlignment, winrt::Windows::UI::Xaml::VerticalAlignment::Center) \
X(bool, RetroTerminalEffect, false) \
X(winrt::hstring, PixelShaderPath)
X(winrt::hstring, PixelShaderPath) \
X(winrt::hstring, PixelShaderImagePath)

// --------------------------- Core Settings ---------------------------
// All of these settings are defined in ICoreSettings.
Expand Down
16 changes: 16 additions & 0 deletions src/renderer/atlas/AtlasEngine.api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,11 @@ HRESULT AtlasEngine::Enable() noexcept
return _api.s->misc->customPixelShaderPath;
}

[[nodiscard]] std::wstring_view AtlasEngine::GetPixelShaderImagePath() noexcept
{
return _api.s->misc->customPixelShaderImagePath;
}

[[nodiscard]] bool AtlasEngine::GetRetroTerminalEffect() const noexcept
{
return _api.s->misc->useRetroTerminalEffect;
Expand Down Expand Up @@ -400,6 +405,17 @@ try
}
CATCH_LOG()

void AtlasEngine::SetPixelShaderImagePath(std::wstring_view value) noexcept
try
{
if (_api.s->misc->customPixelShaderImagePath != value)
{
_api.s.write()->misc.write()->customPixelShaderImagePath = value;
_resolveTransparencySettings();
}
}
CATCH_LOG()

void AtlasEngine::SetRetroTerminalEffect(bool enable) noexcept
{
if (_api.s->misc->useRetroTerminalEffect != enable)
Expand Down
2 changes: 1 addition & 1 deletion src/renderer/atlas/AtlasEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ void AtlasEngine::_recreateCellCountDependentResources()
// and 40x (AMD) faster for allocations with an alignment of 32 or greater.
// backgroundBitmapStride is a "count" of u32 and not in bytes,
// so we round up to multiple of 8 because 8 * sizeof(u32) == 32.
_p.colorBitmapRowStride = (static_cast<size_t>(_p.s->viewportCellCount.x) + 7) & ~7;
_p.colorBitmapRowStride = alignForward<size_t>(_p.s->viewportCellCount.x, 8);
_p.colorBitmapDepthStride = _p.colorBitmapRowStride * _p.s->viewportCellCount.y;
_p.colorBitmap = Buffer<u32, 32>(_p.colorBitmapDepthStride * 2);
_p.backgroundBitmap = { _p.colorBitmap.data(), _p.colorBitmapDepthStride };
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/atlas/AtlasEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ namespace Microsoft::Console::Render::Atlas
// DxRenderer - getter
HRESULT Enable() noexcept override;
[[nodiscard]] std::wstring_view GetPixelShaderPath() noexcept override;
[[nodiscard]] std::wstring_view GetPixelShaderImagePath() noexcept override;
[[nodiscard]] bool GetRetroTerminalEffect() const noexcept override;
[[nodiscard]] float GetScaling() const noexcept override;
[[nodiscard]] Types::Viewport GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept override;
Expand All @@ -72,6 +73,7 @@ namespace Microsoft::Console::Render::Atlas
void SetForceFullRepaintRendering(bool enable) noexcept override;
[[nodiscard]] HRESULT SetHwnd(HWND hwnd) noexcept override;
void SetPixelShaderPath(std::wstring_view value) noexcept override;
void SetPixelShaderImagePath(std::wstring_view value) noexcept override;
void SetRetroTerminalEffect(bool enable) noexcept override;
void SetSelectionBackground(COLORREF color, float alpha = 0.5f) noexcept override;
void SetSoftwareRendering(bool enable) noexcept override;
Expand Down
7 changes: 7 additions & 0 deletions src/renderer/atlas/Backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,13 @@ namespace Microsoft::Console::Render::Atlas
return val < min ? min : (max < val ? max : val);
}

template<typename T>
constexpr T alignForward(T val, T alignment) noexcept
{
assert((alignment & (alignment - 1)) == 0); // alignment should be a power of 2
return (val + alignment - 1) & ~(alignment - 1);
}

inline constexpr D2D1_RECT_F GlyphRunEmptyBounds{ 1e38f, 1e38f, -1e38f, -1e38f };
void GlyphRunAccumulateBounds(const ID2D1DeviceContext* d2dRenderTarget, D2D1_POINT_2F baselineOrigin, const DWRITE_GLYPH_RUN* glyphRun, D2D1_RECT_F& bounds);

Expand Down
32 changes: 26 additions & 6 deletions src/renderer/atlas/BackendD3D.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,13 @@

#include "BuiltinGlyphs.h"
#include "dwrite.h"
#include "wic.h"
#include "../../types/inc/ColorFix.hpp"

#if ATLAS_DEBUG_SHOW_DIRTY || ATLAS_DEBUG_COLORIZE_GLYPH_ATLAS
#include "colorbrewer.h"
#endif

#if ATLAS_DEBUG_DUMP_RENDER_TARGET
#include "wic.h"
#endif

TIL_FAST_MATH_BEGIN

#pragma warning(disable : 4100) // '...': unreferenced formal parameter
Expand Down Expand Up @@ -345,6 +342,8 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p)
_customPixelShader.reset();
_customShaderConstantBuffer.reset();
_customShaderSamplerState.reset();
_customShaderTexture.reset();
_customShaderTextureView.reset();
_requiresContinuousRedraw = false;

if (!p.s->misc->customPixelShaderPath.empty())
Expand Down Expand Up @@ -436,6 +435,23 @@ void BackendD3D::_recreateCustomShader(const RenderingPayload& p)
p.warningCallback(D2DERR_SHADER_COMPILE_FAILED);
}
}

if (!p.s->misc->customPixelShaderImagePath.empty())
{
try
{
WIC::LoadTextureFromFile(p.device.get(), p.s->misc->customPixelShaderImagePath.c_str(), _customShaderTexture.addressof(), _customShaderTextureView.addressof());
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
_customPixelShader.reset();
if (p.warningCallback)
{
p.warningCallback(D2DERR_SHADER_COMPILE_FAILED);
}
}
}
}
else if (p.s->misc->useRetroTerminalEffect)
{
Expand Down Expand Up @@ -899,7 +915,7 @@ void BackendD3D::_recreateInstanceBuffers(const RenderingPayload& p)
auto newSize = newCapacity * sizeof(QuadInstance);
// Round up to multiples of 64kB to avoid reallocating too often.
// 64kB is the minimum alignment for committed resources in D3D12.
newSize = (newSize + 0xffff) & ~size_t{ 0xffff };
newSize = alignForward<size_t>(newSize, 64 * 1024);
newCapacity = newSize / sizeof(QuadInstance);

_instanceBuffer.reset();
Expand Down Expand Up @@ -2121,7 +2137,11 @@ void BackendD3D::_executeCustomShader(RenderingPayload& p)
// PS: Pixel Shader
p.deviceContext->PSSetShader(_customPixelShader.get(), nullptr, 0);
p.deviceContext->PSSetConstantBuffers(0, 1, _customShaderConstantBuffer.addressof());
p.deviceContext->PSSetShaderResources(0, 1, _customOffscreenTextureView.addressof());
ID3D11ShaderResourceView* const resourceViews[]{
_customOffscreenTextureView.get(), // The terminal contents
_customShaderTextureView.get(), // the experimental.pixelShaderImagePath, if there is one
};
p.deviceContext->PSSetShaderResources(0, resourceViews[1] ? 2 : 1, &resourceViews[0]);
p.deviceContext->PSSetSamplers(0, 1, _customShaderSamplerState.addressof());

// OM: Output Merger
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/atlas/BackendD3D.h
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,8 @@ namespace Microsoft::Console::Render::Atlas
wil::com_ptr<ID3D11PixelShader> _customPixelShader;
wil::com_ptr<ID3D11Buffer> _customShaderConstantBuffer;
wil::com_ptr<ID3D11SamplerState> _customShaderSamplerState;
wil::com_ptr<ID3D11Texture2D> _customShaderTexture;
wil::com_ptr<ID3D11ShaderResourceView> _customShaderTextureView;
std::chrono::steady_clock::time_point _customShaderStartTime;

wil::com_ptr<ID3D11Texture2D> _backgroundBitmap;
Expand Down
5 changes: 3 additions & 2 deletions src/renderer/atlas/atlas.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<Import Project="$(SolutionDir)src\common.nugetversions.props" />
<ItemGroup>
<ClCompile Include="AtlasEngine.cpp" />
<ClCompile Include="AtlasEngine.api.cpp" />
<ClCompile Include="AtlasEngine.r.cpp" />
<ClCompile Include="Backend.cpp" />
Expand All @@ -22,10 +23,11 @@
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="AtlasEngine.cpp" />
<ClCompile Include="stb_rect_pack.cpp" />
<ClCompile Include="wic.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="AtlasEngine.h" />
<ClInclude Include="Backend.h" />
<ClInclude Include="BackendD2D.h" />
<ClInclude Include="BackendD3D.h" />
Expand All @@ -35,7 +37,6 @@
<ClInclude Include="dwrite.h" />
<ClInclude Include="DWriteTextAnalysis.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="AtlasEngine.h" />
<ClInclude Include="wic.h" />
</ItemGroup>
<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions src/renderer/atlas/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ namespace Microsoft::Console::Render::Atlas
u32 backgroundColor = 0;
u32 selectionColor = 0x7fffffff;
std::wstring customPixelShaderPath;
std::wstring customPixelShaderImagePath;
bool useRetroTerminalEffect = false;
};

Expand Down
1 change: 1 addition & 0 deletions src/renderer/atlas/pch.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <dxgi1_3.h>
#include <dxgidebug.h>
#include <VersionHelpers.h>
#include <wincodec.h>

#include <gsl/gsl_util>
#include <gsl/pointers>
Expand Down
Loading

0 comments on commit 0ba680a

Please sign in to comment.