diff --git a/.github/actions/spell-check/dictionary/apis.txt b/.github/actions/spell-check/dictionary/apis.txt index 31df31940..627ddbf08 100644 --- a/.github/actions/spell-check/dictionary/apis.txt +++ b/.github/actions/spell-check/dictionary/apis.txt @@ -8,9 +8,12 @@ cmdletbinding COLORPROPERTY CXICON CYICON +D2DERR_SHADER_COMPILE_FAILED +DERR environstrings EXPCMDFLAGS EXPCMDSTATE +frac fullkbd futex GETDESKWALLPAPER @@ -34,7 +37,6 @@ IObject IStorage ITab ITaskbar -llabs LCID llabs localtime @@ -64,6 +66,7 @@ semver serializer shobjidl SIZENS +smoothstep GETDESKWALLPAPER snprintf spsc @@ -83,6 +86,7 @@ UPDATEINIFILE userenv wcsstr wcstoui +wpc wsregex XDocument XElement diff --git a/.github/actions/spell-check/dictionary/names.txt b/.github/actions/spell-check/dictionary/names.txt index dd4eaae18..2a8a684c7 100644 --- a/.github/actions/spell-check/dictionary/names.txt +++ b/.github/actions/spell-check/dictionary/names.txt @@ -12,16 +12,20 @@ ekg ethanschoonover Firefox Gatta +glsl Grie Griese Hernan Howett Illhardt +iquilezles jantari jerrysh Kaiyu kimwalisch KMehrain +KODELIFE +Kodelife Kourosh kowalczyk leonmsft @@ -30,6 +34,7 @@ lukesampson Manandhar mbadolato Mehrain +menger mgravell michaelniksa michkap @@ -43,6 +48,7 @@ nvaccess nvda oising oldnewthing +opengl osgwiki paulcam pauldotknopf @@ -51,6 +57,7 @@ Pham Rincewind rprichard Schoonover +shadertoy Somuah sonph sonpham @@ -60,8 +67,8 @@ Wirt Wojciech zadjii Zamor -Zamora zamora +Zamora Zoey zorio Zverovich diff --git a/.github/actions/spell-check/expect/expect.txt b/.github/actions/spell-check/expect/expect.txt index 339a55ebe..86f6cf297 100644 --- a/.github/actions/spell-check/expect/expect.txt +++ b/.github/actions/spell-check/expect/expect.txt @@ -24,8 +24,8 @@ ADDREF addressof ADDSTRING ADDTOOL -AEnd aef +AEnd AFew AFill AFX @@ -628,6 +628,7 @@ doskey dotnet doubleclick downlevel +DOWNSCALE dpg dpi DPIAPI @@ -832,6 +833,7 @@ fuzzwrapper fwdecl fwe fwlink +GAUSSIAN gb gci gcx @@ -909,6 +911,7 @@ Goldmine gonce Google goutput +GPUs Gravell's grayscale GREENSCROLL @@ -1109,6 +1112,7 @@ INTERCEPTCOPYPASTE INTERNALNAME interop interoperability +intersectors inthread intptr intsafe @@ -1207,6 +1211,7 @@ KJ KLF KLMNOPQRST KLMNOPQRSTQQQQQ +Kode KU KVM KX @@ -1591,6 +1596,7 @@ OACR oauth objbase ocf +ocolor odl oem oemcp @@ -1789,6 +1795,7 @@ pragma prc prealigned prebuilt +precendence precomp prect prefast @@ -1834,6 +1841,7 @@ pshn PSHNOTIFY PSHORT pshpack +psin PSINGLE psl psldl @@ -1882,10 +1890,12 @@ QWER qzmp RAII RALT +rasterbar rasterfont rasterization rawinput RAWPATH +raytracers razzlerc rbar rbegin @@ -1956,9 +1966,11 @@ RESETCONTENT resheader resizable resmimetype +reso restrictedcapabilities resw resx +RETROII retval rfa rfc @@ -2029,11 +2041,14 @@ SBCSDBCS sbi sbiex sbold +sbri +scanbri scancode scanline schemename SCL scm +scol scprintf SCRBUF SCRBUFSIZE @@ -2167,6 +2182,9 @@ SOURCESDIRECTORY SPACEBAR spammy spand +spe +sph +spherefunctions splashscreen sprintf sqlproj @@ -2296,6 +2314,7 @@ tellp telnet telnetd templated +teraflop terminalcore TERMINALSCROLLING terminfo @@ -2544,6 +2563,7 @@ vga vgaoem viewkind viewports +Viginetting Virt VIRTTERM Virtualizing @@ -2760,6 +2780,7 @@ wwaproj WWith wx wxh +wz xa xact xamarin @@ -2809,7 +2830,10 @@ xutr xvalue XVIRTUALSCREEN XWalk +XWV xy +xyw +Xzn yact YAML YCast @@ -2825,6 +2849,7 @@ YVIRTUALSCREEN Yw YWalk yx +yzx Zc ZCmd ZCtrl diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 24954049e..a0113974b 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -103,7 +103,7 @@ "toggleFocusMode", "toggleFullscreen", "togglePaneZoom", - "toggleRetroEffect", + "toggleShaderEffects", "wt", "unbound" ], @@ -936,6 +936,10 @@ "description": "When set to true, enable retro terminal effects. This is an experimental feature, and its continued existence is not guaranteed.", "type": "boolean" }, + "experimental.pixelShaderPath": { + "description": "Use to set a path to a pixel shader to use with the Terminal. Overrides `experimental.retroTerminalEffect`. This is an experimental feature, and its continued existence is not guaranteed.", + "type": "string" + }, "fontFace": { "default": "Cascadia Mono", "description": "Name of the font face used in the profile.", diff --git a/samples/PixelShaders/Broken.hlsl b/samples/PixelShaders/Broken.hlsl new file mode 100644 index 000000000..2756bab6f --- /dev/null +++ b/samples/PixelShaders/Broken.hlsl @@ -0,0 +1,18 @@ +// Broken, can be used for explorative testing of pixel shader error handling +Texture2D shaderTexture; +SamplerState samplerState; + +cbuffer PixelShaderSettings { + float Time; + float Scale; + float2 Resolution; + float4 Background; +}; + +float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET +{ + // OOPS; vec4 is not a hlsl but a glsl datatype! + vec4 color = shaderTexture.Sample(samplerState, tex); + + return color; +} \ No newline at end of file diff --git a/samples/PixelShaders/Error.hlsl b/samples/PixelShaders/Error.hlsl new file mode 100644 index 000000000..2d227916f --- /dev/null +++ b/samples/PixelShaders/Error.hlsl @@ -0,0 +1,18 @@ +// Shader used to indicate something went wrong during shader loading +Texture2D shaderTexture; +SamplerState samplerState; + +cbuffer PixelShaderSettings { + float Time; + float Scale; + float2 Resolution; + float4 Background; +}; + +float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET +{ + float4 color = shaderTexture.Sample(samplerState, tex); + float bars = 0.5+0.5*sin(tex.y*100); + color.x += pow(bars, 20.0); + return color; +} \ No newline at end of file diff --git a/samples/PixelShaders/Grayscale.hlsl b/samples/PixelShaders/Grayscale.hlsl new file mode 100644 index 000000000..cb6357e64 --- /dev/null +++ b/samples/PixelShaders/Grayscale.hlsl @@ -0,0 +1,32 @@ +// A minimal pixel shader that inverts the colors + +// The terminal graphics as a texture +Texture2D shaderTexture; +SamplerState samplerState; + +// Terminal settings such as the resolution of the texture +cbuffer PixelShaderSettings { + // Time since pixel shader was enabled + float Time; + // UI Scale + float Scale; + // Resolution of the shaderTexture + float2 Resolution; + // Background color as rgba + float4 Background; +}; + +// A pixel shader is a program that given a texture coordinate (tex) produces a color +// Just ignore the pos parameter +float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET +{ + // Read the color value at the current texture coordinate (tex) + // float4 is tuple of 4 floats, rgba + float4 color = shaderTexture.Sample(samplerState, tex); + float avg = (color.x + color.y + color.z) / 3.0; + // Inverts the rgb values (xyz) but don't touch the alpha (w) + color.xyz = avg; + + // Return the final color + return color; +} diff --git a/samples/PixelShaders/Invert.hlsl b/samples/PixelShaders/Invert.hlsl new file mode 100644 index 000000000..650eac756 --- /dev/null +++ b/samples/PixelShaders/Invert.hlsl @@ -0,0 +1,32 @@ +// A minimal pixel shader that inverts the colors + +// The terminal graphics as a texture +Texture2D shaderTexture; +SamplerState samplerState; + +// Terminal settings such as the resolution of the texture +cbuffer PixelShaderSettings { + // Time since pixel shader was enabled + float Time; + // UI Scale + float Scale; + // Resolution of the shaderTexture + float2 Resolution; + // Background color as rgba + float4 Background; +}; + +// A pixel shader is a program that given a texture coordinate (tex) produces a color +// Just ignore the pos parameter +float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET +{ + // Read the color value at the current texture coordinate (tex) + // float4 is tuple of 4 floats, rgba + float4 color = shaderTexture.Sample(samplerState, tex); + + // Inverts the rgb values (xyz) but don't touch the alpha (w) + color.xyz = 1.0 - color.xyz; + + // Return the final color + return color; +} \ No newline at end of file diff --git a/samples/PixelShaders/Nop.hlsl b/samples/PixelShaders/Nop.hlsl new file mode 100644 index 000000000..55c4c8b1a --- /dev/null +++ b/samples/PixelShaders/Nop.hlsl @@ -0,0 +1,17 @@ +// Does nothing, serves as an example of a minimal pixel shader +Texture2D shaderTexture; +SamplerState samplerState; + +cbuffer PixelShaderSettings { + float Time; + float Scale; + float2 Resolution; + float4 Background; +}; + +float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET +{ + float4 color = shaderTexture.Sample(samplerState, tex); + + return color; +} \ No newline at end of file diff --git a/samples/PixelShaders/README.md b/samples/PixelShaders/README.md new file mode 100644 index 000000000..6b6884000 --- /dev/null +++ b/samples/PixelShaders/README.md @@ -0,0 +1,136 @@ +# Pixel Shaders in Windows Terminal + +Due to the sheer amount of computing power in GPUs, one can do awesome things with pixel shaders such as real-time fractal zoom, ray tracers and image processing. + +Windows Terminal allows user to provide a pixel shader which will be applied to the terminal. To try it out, add the following setting to one of your profiles: + +``` +"experimental.pixelShaderPath": "" +``` +> **Note**: if you specify a shader with `experimental.pixelShaderPath`, the Terminal will use that instead of the `experimental.retroTerminalEffect`. + +To get started using pixel shaders in the Terminal, start with the following sample shader. This is `Invert.hlsl` in this directory: + +```hlsl +// A minimal pixel shader that inverts the colors + +// The terminal graphics as a texture +Texture2D shaderTexture; +SamplerState samplerState; + +// Terminal settings such as the resolution of the texture +cbuffer PixelShaderSettings { + // Time since pixel shader was enabled + float Time; + // UI Scale + float Scale; + // Resolution of the shaderTexture + float2 Resolution; + // Background color as rgba + float4 Background; +}; + +// A pixel shader is a program that given a texture coordinate (tex) produces a color +// Just ignore the pos parameter +float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET +{ + // Read the color value at the current texture coordinate (tex) + // float4 is tuple of 4 floats, rgba + float4 color = shaderTexture.Sample(samplerState, tex); + + // Inverts the rgb values (xyz) but don't touch the alpha (w) + color.xyz = 1.0 - color.xyz; + + // Return the final color + return color; +} +``` + +Save this file as `C:\temp\invert.hlsl`, then update a profile with the setting: + +``` +"experimental.pixelShaderEffect": "C:\\temp\\invert.hlsl" +``` + +Once the settings file is saved, open a terminal with the changed profile. It should now invert the colors of the screen! + +If your shader fails to compile, the Terminal will display a warning dialog and ignore it temporarily. After fixing your shader, touch the `settings.json` file again, or open a new tab, and the Terminal will try loading the shader again. + +## HLSL + +The language we use to write pixel shaders is called `HLSL`. It a `C`-like language, with some restrictions.You can't allocate memory, use pointers or recursion. +What you get access to is computing power in the teraflop range on decently recent GPUs. This means writing real-time raytracers or other cool effects are in the realm of possibility. + +[shadertoy](https://shadertoy.com/) is a great site that show case what's possible with pixel shaders (albeit in `GLSL`). For example this [menger sponge](https://www.shadertoy.com/view/4scXzn). Converting from `GLSL` to `HLSL` isn't overly hard once you gotten the hang of it. + +## Adding some retro raster bars + +Let's try a more complicated example. Raster bars was cool in the 80's, so let's add that. Start by modifying shader like so: (This is `Rasterbars.hlsl`) + +```hlsl +// A minimal pixel shader that shows some raster bars + +// The terminal graphics as a texture +Texture2D shaderTexture; +SamplerState samplerState; + +// Terminal settings such as the resolution of the texture +cbuffer PixelShaderSettings { + // Time since pixel shader was enabled + float Time; + // UI Scale + float Scale; + // Resolution of the shaderTexture + float2 Resolution; + // Background color as rgba + float4 Background; +}; + +// A pixel shader is a program that given a texture coordinate (tex) produces a color +// Just ignore the pos parameter +float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET +{ + // Read the color value at the current texture coordinate (tex) + // float4 is tuple of 4 floats, rgba + float4 color = shaderTexture.Sample(samplerState, tex); + + // Read the color value at some offset, will be used as shadow + float4 ocolor = shaderTexture.Sample(samplerState, tex+2.0*Scale*float2(-1.0, -1.0)/Resolution.y); + + // Thickness of raster + const float thickness = 0.1; + + float ny = floor(tex.y/thickness); + float my = tex.y%thickness; + const float pi = 3.141592654; + + + // ny is used to compute the rasterbar base color + float cola = ny*2.0*pi; + float3 col = 0.75+0.25*float3(sin(cola*0.111), sin(cola*0.222), sin(cola*0.333)); + + // my is used to compute the rasterbar brightness + // smoothstep is a great little function: https://en.wikipedia.org/wiki/Smoothstep + float brightness = 1.0-smoothstep(0.0, thickness*0.5, abs(my - 0.5*thickness)); + + float3 rasterColor = col*brightness; + + // lerp(x, y, a) is another very useful function: https://en.wikipedia.org/wiki/Linear_interpolation + float3 final = rasterColor; + // Create the drop shadow of the terminal graphics + // .w is the alpha channel, 0 is fully transparent and 1 is fully opaque + final = lerp(final, float(0.0), ocolor.w); + // Draw the terminal graphics + final = lerp(final, color.xyz, color.w); + + // Return the final color, set alpha to 1 (ie opaque) + return float4(final, 1.0); +} +``` + +Once reloaded, it should show some retro raster bars in the background, with a drop shadow to make the text more readable. + +## Retro Terminal Effect + +As a more complicated example, the Terminal's built-in `experimental.retroTerminalEffect` is included as the `Retro.hlsl` file in this directory. Feel free to modify and experiment! + diff --git a/samples/PixelShaders/Rasterbars.hlsl b/samples/PixelShaders/Rasterbars.hlsl new file mode 100644 index 000000000..2be2b01fb --- /dev/null +++ b/samples/PixelShaders/Rasterbars.hlsl @@ -0,0 +1,58 @@ +// A minimal pixel shader that shows some raster bars + +// The terminal graphics as a texture +Texture2D shaderTexture; +SamplerState samplerState; + +// Terminal settings such as the resolution of the texture +cbuffer PixelShaderSettings { + // Time since pixel shader was enabled + float Time; + // UI Scale + float Scale; + // Resolution of the shaderTexture + float2 Resolution; + // Background color as rgba + float4 Background; +}; + +// A pixel shader is a program that given a texture coordinate (tex) produces a color +// Just ignore the pos parameter +float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET +{ + // Read the color value at the current texture coordinate (tex) + // float4 is tuple of 4 floats, rgba + float4 color = shaderTexture.Sample(samplerState, tex); + + // Read the color value at some offset, will be used as shadow + float4 ocolor = shaderTexture.Sample(samplerState, tex+2.0*Scale*float2(-1.0, -1.0)/Resolution.y); + + // Thickness of raster + const float thickness = 0.1; + + float ny = floor(tex.y/thickness); + float my = tex.y%thickness; + const float pi = 3.141592654; + + + // ny is used to compute the rasterbar base color + float cola = ny*2.0*pi; + float3 col = 0.75+0.25*float3(sin(cola*0.111), sin(cola*0.222), sin(cola*0.333)); + + // my is used to compute the rasterbar brightness + // smoothstep is a great little function: https://en.wikipedia.org/wiki/Smoothstep + float brightness = 1.0-smoothstep(0.0, thickness*0.5, abs(my - 0.5*thickness)); + + float3 rasterColor = col*brightness; + + // lerp(x, y, a) is another very useful function: https://en.wikipedia.org/wiki/Linear_interpolation + float3 final = rasterColor; + // Create the drop shadow of the terminal graphics + // .w is the alpha channel, 0 is fully transparent and 1 is fully opaque + final = lerp(final, float(0.0), ocolor.w); + // Draw the terminal graphics + final = lerp(final, color.xyz, color.w); + + // Return the final color, set alpha to 1 (ie opaque) + return float4(final, 1.0); +} \ No newline at end of file diff --git a/samples/PixelShaders/Retro.hlsl b/samples/PixelShaders/Retro.hlsl new file mode 100644 index 000000000..cfefe04e7 --- /dev/null +++ b/samples/PixelShaders/Retro.hlsl @@ -0,0 +1,83 @@ +// The original retro pixel shader +Texture2D shaderTexture; +SamplerState samplerState; + +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) + +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) +{ + uint width, height; + shaderTexture.GetDimensions(width, height); + + float texelWidth = 1.0f/width; + float texelHeight = 1.0f/height; + + float4 color = { 0, 0, 0, 0 }; + + int sampleCount = 13; + + for (int x = 0; x < sampleCount; x++) + { + float2 samplePos = { 0, 0 }; + + samplePos.x = tex_coord.x + (x - sampleCount/2) * texelWidth; + for (int 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); + } + } + + return color; +} + +float SquareWave(float y) +{ + return 1 - (floor(y / SCALED_SCANLINE_PERIOD) % 2) * 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.2 && false) + { + return color + wave*0.1; + } + else + { + return color * wave; + } +} + +float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET +{ + 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; + color = Scanline(color, pos); + + return color; +} \ No newline at end of file diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index e7e0c45c0..f51ea8c2a 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -295,11 +295,11 @@ namespace winrt::TerminalApp::implementation args.Handled(true); } - void TerminalPage::_HandleToggleRetroEffect(const IInspectable& /*sender*/, - const ActionEventArgs& args) + void TerminalPage::_HandleToggleShaderEffects(const IInspectable& /*sender*/, + const ActionEventArgs& args) { const auto termControl = _GetActiveControl(); - termControl.ToggleRetroEffect(); + termControl.ToggleShaderEffects(); args.Handled(true); } diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp index b9cb8a675..839d36d4e 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp @@ -176,9 +176,9 @@ namespace winrt::TerminalApp::implementation _ResetFontSizeHandlers(*this, eventArgs); break; } - case ShortcutAction::ToggleRetroEffect: + case ShortcutAction::ToggleShaderEffects: { - _ToggleRetroEffectHandlers(*this, eventArgs); + _ToggleShaderEffectsHandlers(*this, eventArgs); break; } case ShortcutAction::ToggleFocusMode: diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.h b/src/cascadia/TerminalApp/ShortcutActionDispatch.h index c908c8360..787bc4d6c 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.h +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.h @@ -49,7 +49,7 @@ namespace winrt::TerminalApp::implementation TYPED_EVENT(ResizePane, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs); TYPED_EVENT(Find, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs); TYPED_EVENT(MoveFocus, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs); - TYPED_EVENT(ToggleRetroEffect, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs); + TYPED_EVENT(ToggleShaderEffects, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs); TYPED_EVENT(ToggleFocusMode, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs); TYPED_EVENT(ToggleFullscreen, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs); TYPED_EVENT(ToggleAlwaysOnTop, TerminalApp::ShortcutActionDispatch, Microsoft::Terminal::Settings::Model::ActionEventArgs); diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.idl b/src/cascadia/TerminalApp/ShortcutActionDispatch.idl index e035c6985..c4e8780e6 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.idl +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.idl @@ -35,7 +35,7 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler ResizePane; event Windows.Foundation.TypedEventHandler Find; event Windows.Foundation.TypedEventHandler MoveFocus; - event Windows.Foundation.TypedEventHandler ToggleRetroEffect; + event Windows.Foundation.TypedEventHandler ToggleShaderEffects; event Windows.Foundation.TypedEventHandler ToggleFocusMode; event Windows.Foundation.TypedEventHandler ToggleFullscreen; event Windows.Foundation.TypedEventHandler ToggleAlwaysOnTop; diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 6d55956f7..acee061ee 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -976,7 +976,7 @@ namespace winrt::TerminalApp::implementation _actionDispatch->AdjustFontSize({ this, &TerminalPage::_HandleAdjustFontSize }); _actionDispatch->Find({ this, &TerminalPage::_HandleFind }); _actionDispatch->ResetFontSize({ this, &TerminalPage::_HandleResetFontSize }); - _actionDispatch->ToggleRetroEffect({ this, &TerminalPage::_HandleToggleRetroEffect }); + _actionDispatch->ToggleShaderEffects({ this, &TerminalPage::_HandleToggleShaderEffects }); _actionDispatch->ToggleFocusMode({ this, &TerminalPage::_HandleToggleFocusMode }); _actionDispatch->ToggleFullscreen({ this, &TerminalPage::_HandleToggleFullscreen }); _actionDispatch->ToggleAlwaysOnTop({ this, &TerminalPage::_HandleToggleAlwaysOnTop }); diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 675d0efa4..3edc7ac40 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -282,7 +282,7 @@ namespace winrt::TerminalApp::implementation void _HandleAdjustFontSize(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); void _HandleFind(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); void _HandleResetFontSize(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); - void _HandleToggleRetroEffect(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); + void _HandleToggleShaderEffects(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); void _HandleToggleFocusMode(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); void _HandleToggleFullscreen(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); void _HandleToggleAlwaysOnTop(const IInspectable& sender, const Microsoft::Terminal::Settings::Model::ActionEventArgs& args); diff --git a/src/cascadia/TerminalApp/TerminalSettings.cpp b/src/cascadia/TerminalApp/TerminalSettings.cpp index 681e99ecd..38ccfe45b 100644 --- a/src/cascadia/TerminalApp/TerminalSettings.cpp +++ b/src/cascadia/TerminalApp/TerminalSettings.cpp @@ -182,6 +182,7 @@ namespace winrt::TerminalApp::implementation std::tie(_BackgroundImageHorizontalAlignment, _BackgroundImageVerticalAlignment) = ConvertConvergedAlignment(profile.BackgroundImageAlignment()); _RetroTerminalEffect = profile.RetroTerminalEffect(); + _PixelShaderPath = winrt::hstring{ wil::ExpandEnvironmentStringsW(profile.PixelShaderPath().c_str()) }; _AntialiasingMode = profile.AntialiasingMode(); diff --git a/src/cascadia/TerminalApp/TerminalSettings.h b/src/cascadia/TerminalApp/TerminalSettings.h index db4db5242..f8ec65e2e 100644 --- a/src/cascadia/TerminalApp/TerminalSettings.h +++ b/src/cascadia/TerminalApp/TerminalSettings.h @@ -121,6 +121,7 @@ namespace winrt::TerminalApp::implementation GETSET_PROPERTY(bool, SoftwareRendering, false); GETSET_PROPERTY(bool, ForceVTInput, false); + GETSET_PROPERTY(hstring, PixelShaderPath); #pragma warning(pop) private: diff --git a/src/cascadia/TerminalControl/IControlSettings.idl b/src/cascadia/TerminalControl/IControlSettings.idl index 2f140a0a3..cfca032ce 100644 --- a/src/cascadia/TerminalControl/IControlSettings.idl +++ b/src/cascadia/TerminalControl/IControlSettings.idl @@ -54,8 +54,11 @@ namespace Microsoft.Terminal.TerminalControl TextAntialiasingMode AntialiasingMode; + // Experimental Settings Boolean RetroTerminalEffect; Boolean ForceFullRepaintRendering; Boolean SoftwareRendering; + String PixelShaderPath; + }; } diff --git a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw index 9497cf403..e94aa14bd 100644 --- a/src/cascadia/TerminalControl/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalControl/Resources/en-US/Resources.resw @@ -1,17 +1,17 @@ - @@ -179,11 +179,22 @@ Ctrl+Click to follow link - Unable to find the selected font "{0}". + Unable to find the selected font "{0}". -"{1}" has been selected instead. +"{1}" has been selected instead. Please either install the missing font or choose another one. 0 and 1 are names of fonts provided by the user and system respectively. - \ No newline at end of file + + Unable to find the provided shader "{0}". + {0} is a file name + + + Unable to compile the specified pixel shader. + + + Renderer encountered an unexpected error: {0} + {0} is an error code. + + diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 3a3a6d3ff..e0dc8217a 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -308,7 +308,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // Update DxEngine settings under the lock _renderEngine->SetSelectionBackground(_settings.SelectionBackground()); - _renderEngine->SetRetroTerminalEffects(_settings.RetroTerminalEffect()); + _renderEngine->SetRetroTerminalEffect(_settings.RetroTerminalEffect()); + _renderEngine->SetPixelShaderPath(_settings.PixelShaderPath()); _renderEngine->SetForceFullRepaintRendering(_settings.ForceFullRepaintRendering()); _renderEngine->SetSoftwareRendering(_settings.SoftwareRendering()); @@ -348,10 +349,27 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _SendInputToConnection(wstr); } - void TermControl::ToggleRetroEffect() + void TermControl::ToggleShaderEffects() { auto lock = _terminal->LockForWriting(); - _renderEngine->SetRetroTerminalEffects(!_renderEngine->GetRetroTerminalEffects()); + // Originally, this action could be used to enable the retro effects + // even when they're set to `false` in the settings. If the user didn't + // specify a custom pixel shader, manually enable the legacy retro + // effect first. This will ensure that a toggle off->on will still work, + // even if they currently have retro effect off. + if (_settings.PixelShaderPath().empty() && !_renderEngine->GetRetroTerminalEffect()) + { + // SetRetroTerminalEffect to true will enable the effect. In this + // case, the shader effect will already be disabled (because neither + // a pixel shader nor the retro effects were originally requested). + // So we _don't_ want to toggle it again below, because that would + // toggle it back off. + _renderEngine->SetRetroTerminalEffect(true); + } + else + { + _renderEngine->ToggleShaderEffects(); + } } // Method Description: @@ -616,6 +634,45 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation } } + // Method Description: + // - Called when the renderer triggers a warning. It might do this when it + // fails to find a shader file, or fails to compile a shader. We'll take + // that renderer warning, and display a dialog to the user with and + // appropriate error message. WE'll display the dialog with our + // RaiseNotice event. + // Arguments: + // - hr: an HRESULT describing the warning + // Return Value: + // - + winrt::fire_and_forget TermControl::_RendererWarning(const HRESULT hr) + { + auto weakThis{ get_weak() }; + co_await winrt::resume_foreground(Dispatcher()); + + if (auto control{ weakThis.get() }) + { + winrt::hstring message; + if (HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) == hr || + HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr) + { + message = { fmt::format(std::wstring_view{ RS_(L"PixelShaderNotFound") }, + _settings.PixelShaderPath()) }; + } + else if (D2DERR_SHADER_COMPILE_FAILED == hr) + { + message = { fmt::format(std::wstring_view{ RS_(L"PixelShaderCompileFailed") }) }; + } + else + { + message = { fmt::format(std::wstring_view{ RS_(L"UnexpectedRendererError") }, + hr) }; + } + + auto noticeArgs = winrt::make(NoticeLevel::Warning, std::move(message)); + control->_raiseNoticeHandlers(*control, std::move(noticeArgs)); + } + } + void TermControl::_AttachDxgiSwapChainToXaml(IDXGISwapChain1* swapChain) { auto nativePanel = SwapChainPanel().as(); @@ -695,7 +752,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation _terminal->CreateFromSettings(_settings, renderTarget); - dxEngine->SetRetroTerminalEffects(_settings.RetroTerminalEffect()); + // IMPORTANT! Set this callback up sooner than later. If we do it + // after Enable, then it'll be possible to paint the frame once + // _before_ the warning handler is set up, and then warnings from + // the first paint will be ignored! + dxEngine->SetWarningCallback(std::bind(&TermControl::_RendererWarning, this, std::placeholders::_1)); + + dxEngine->SetRetroTerminalEffect(_settings.RetroTerminalEffect()); + dxEngine->SetPixelShaderPath(_settings.PixelShaderPath()); dxEngine->SetForceFullRepaintRendering(_settings.ForceFullRepaintRendering()); dxEngine->SetSoftwareRendering(_settings.SoftwareRendering()); diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 4e2ebba54..a90bea21c 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -122,12 +122,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation void ResetFontSize(); void SendInput(const winrt::hstring& input); - void ToggleRetroEffect(); + void ToggleShaderEffects(); winrt::fire_and_forget RenderEngineSwapChainChanged(); void _AttachDxgiSwapChainToXaml(IDXGISwapChain1* swapChain); winrt::fire_and_forget _RendererEnteredErrorState(); void _RenderRetryButton_Click(IInspectable const& button, IInspectable const& args); + winrt::fire_and_forget _RendererWarning(const HRESULT hr); void CreateSearchBoxControl(); diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index 22a3345d3..bbce21748 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -17,7 +17,7 @@ namespace Microsoft.Terminal.TerminalControl // If you press F7 or Alt and get a runtime error, go make sure both copies are the same. [uuid("0ddf4edc-3fda-4dee-97ca-a417ee3dd510")] interface IDirectKeyListener { Boolean OnDirectKeyEvent(UInt32 vkey, UInt8 scanCode, Boolean down); - } + }; [flags] enum CopyFormat @@ -103,8 +103,8 @@ namespace Microsoft.Terminal.TerminalControl void AdjustFontSize(Int32 fontSizeDelta); void ResetFontSize(); + void ToggleShaderEffects(); void SendInput(String input); - void ToggleRetroEffect(); void TaskbarProgressChanged(); UInt64 TaskbarState { get; }; diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index f3156105e..479c404dc 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -47,7 +47,8 @@ static constexpr std::string_view ToggleCommandPaletteKey{ "commandPalette" }; static constexpr std::string_view ToggleFocusModeKey{ "toggleFocusMode" }; static constexpr std::string_view ToggleFullscreenKey{ "toggleFullscreen" }; static constexpr std::string_view TogglePaneZoomKey{ "togglePaneZoom" }; -static constexpr std::string_view ToggleRetroEffectKey{ "toggleRetroEffect" }; +static constexpr std::string_view LegacyToggleRetroEffectKey{ "toggleRetroEffect" }; +static constexpr std::string_view ToggleShaderEffectsKey{ "toggleShaderEffects" }; static constexpr std::string_view MoveTabKey{ "moveTab" }; static constexpr std::string_view BreakIntoDebuggerKey{ "breakIntoDebugger" }; @@ -109,7 +110,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { ToggleFocusModeKey, ShortcutAction::ToggleFocusMode }, { ToggleFullscreenKey, ShortcutAction::ToggleFullscreen }, { TogglePaneZoomKey, ShortcutAction::TogglePaneZoom }, - { ToggleRetroEffectKey, ShortcutAction::ToggleRetroEffect }, + { LegacyToggleRetroEffectKey, ShortcutAction::ToggleShaderEffects }, + { ToggleShaderEffectsKey, ShortcutAction::ToggleShaderEffects }, { MoveTabKey, ShortcutAction::MoveTab }, { BreakIntoDebuggerKey, ShortcutAction::BreakIntoDebugger }, { UnboundKey, ShortcutAction::Invalid }, @@ -309,7 +311,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { ShortcutAction::ToggleFocusMode, RS_(L"ToggleFocusModeCommandKey") }, { ShortcutAction::ToggleFullscreen, RS_(L"ToggleFullscreenCommandKey") }, { ShortcutAction::TogglePaneZoom, RS_(L"TogglePaneZoomCommandKey") }, - { ShortcutAction::ToggleRetroEffect, RS_(L"ToggleRetroEffectCommandKey") }, + { ShortcutAction::ToggleShaderEffects, RS_(L"ToggleShaderEffectsCommandKey") }, { ShortcutAction::MoveTab, L"" }, // Intentionally omitted, must be generated by GenerateName { ShortcutAction::BreakIntoDebugger, RS_(L"BreakIntoDebuggerCommandKey") }, }; diff --git a/src/cascadia/TerminalSettingsModel/KeyMapping.idl b/src/cascadia/TerminalSettingsModel/KeyMapping.idl index 5571aa795..f28e0c467 100644 --- a/src/cascadia/TerminalSettingsModel/KeyMapping.idl +++ b/src/cascadia/TerminalSettingsModel/KeyMapping.idl @@ -36,7 +36,7 @@ namespace Microsoft.Terminal.Settings.Model ResizePane, MoveFocus, Find, - ToggleRetroEffect, + ToggleShaderEffects, ToggleFocusMode, ToggleFullscreen, ToggleAlwaysOnTop, diff --git a/src/cascadia/TerminalSettingsModel/Profile.cpp b/src/cascadia/TerminalSettingsModel/Profile.cpp index 75b27be99..16fa759a9 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.cpp +++ b/src/cascadia/TerminalSettingsModel/Profile.cpp @@ -58,6 +58,7 @@ static constexpr std::string_view RetroTerminalEffectKey{ "experimental.retroTer static constexpr std::string_view AntialiasingModeKey{ "antialiasingMode" }; static constexpr std::string_view TabColorKey{ "tabColor" }; static constexpr std::string_view BellStyleKey{ "bellStyle" }; +static constexpr std::string_view PixelShaderPathKey{ "experimental.pixelShaderPath" }; static constexpr std::wstring_view DesktopWallpaperEnum{ L"desktopWallpaper" }; @@ -109,6 +110,7 @@ winrt::com_ptr Profile::CopySettings(winrt::com_ptr source) profile->_CursorShape = source->_CursorShape; profile->_CursorHeight = source->_CursorHeight; profile->_BellStyle = source->_BellStyle; + profile->_PixelShaderPath = source->_PixelShaderPath; profile->_BackgroundImageAlignment = source->_BackgroundImageAlignment; profile->_ConnectionType = source->_ConnectionType; @@ -334,10 +336,9 @@ void Profile::LayerJson(const Json::Value& json) JsonUtils::GetValueForKey(json, BackgroundImageAlignmentKey, _BackgroundImageAlignment); JsonUtils::GetValueForKey(json, RetroTerminalEffectKey, _RetroTerminalEffect); JsonUtils::GetValueForKey(json, AntialiasingModeKey, _AntialiasingMode); - JsonUtils::GetValueForKey(json, TabColorKey, _TabColor); - JsonUtils::GetValueForKey(json, BellStyleKey, _BellStyle); + JsonUtils::GetValueForKey(json, PixelShaderPathKey, _PixelShaderPath); } // Method Description: @@ -529,6 +530,7 @@ Json::Value Profile::ToJson() const JsonUtils::SetValueForKey(json, AntialiasingModeKey, _AntialiasingMode); JsonUtils::SetValueForKey(json, TabColorKey, _TabColor); JsonUtils::SetValueForKey(json, BellStyleKey, _BellStyle); + JsonUtils::SetValueForKey(json, PixelShaderPathKey, _PixelShaderPath); return json; } diff --git a/src/cascadia/TerminalSettingsModel/Profile.h b/src/cascadia/TerminalSettingsModel/Profile.h index f254d92bc..b7964abe5 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.h +++ b/src/cascadia/TerminalSettingsModel/Profile.h @@ -94,6 +94,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation GETSET_SETTING(Microsoft::Terminal::TerminalControl::TextAntialiasingMode, AntialiasingMode, Microsoft::Terminal::TerminalControl::TextAntialiasingMode::Grayscale); GETSET_SETTING(bool, RetroTerminalEffect, false); + GETSET_SETTING(hstring, PixelShaderPath, L""); GETSET_SETTING(bool, ForceFullRepaintRendering, false); GETSET_SETTING(bool, SoftwareRendering, false); diff --git a/src/cascadia/TerminalSettingsModel/Profile.idl b/src/cascadia/TerminalSettingsModel/Profile.idl index 16a2492ab..f65b34e5c 100644 --- a/src/cascadia/TerminalSettingsModel/Profile.idl +++ b/src/cascadia/TerminalSettingsModel/Profile.idl @@ -135,6 +135,10 @@ namespace Microsoft.Terminal.Settings.Model void ClearRetroTerminalEffect(); Boolean RetroTerminalEffect; + Boolean HasPixelShaderPath(); + void ClearPixelShaderPath(); + String PixelShaderPath; + Boolean HasForceFullRepaintRendering(); void ClearForceFullRepaintRendering(); Boolean ForceFullRepaintRendering; diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index d872e8f89..da183b249 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -357,8 +357,8 @@ Toggle pane zoom - - Toggle retro terminal effect + + Toggle terminal visual effects Break into the debugger diff --git a/src/cascadia/TerminalSettingsModel/defaults-universal.json b/src/cascadia/TerminalSettingsModel/defaults-universal.json index 8ecc53a1b..320c6b0e7 100644 --- a/src/cascadia/TerminalSettingsModel/defaults-universal.json +++ b/src/cascadia/TerminalSettingsModel/defaults-universal.json @@ -112,7 +112,6 @@ { "command": "openSettings", "keys": "ctrl+," }, { "command": { "action": "openSettings", "target": "defaultsFile" }, "keys": "ctrl+alt+," }, { "command": "find", "keys": "ctrl+shift+f" }, - { "command": "toggleRetroEffect" }, { "command": "openTabColorPicker" }, { "command": "commandPalette", "keys":"ctrl+shift+p" }, diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index cb862728e..1d3226f27 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -285,7 +285,7 @@ { "command": "openSettings", "keys": "ctrl+," }, { "command": { "action": "openSettings", "target": "defaultsFile" }, "keys": "ctrl+alt+," }, { "command": "find", "keys": "ctrl+shift+f" }, - { "command": "toggleRetroEffect" }, + { "command": "toggleShaderEffects" }, { "command": "openTabColorPicker" }, { "command": "renameTab" }, { "command": "openTabRenamer" }, diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index 78d323d12..265223d71 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -87,7 +87,9 @@ DxEngine::DxEngine() : _swapChainDesc{ 0 }, _swapChainFrameLatencyWaitableObject{ INVALID_HANDLE_VALUE }, _recreateDeviceRequested{ false }, - _retroTerminalEffects{ false }, + _terminalEffectsEnabled{ false }, + _retroTerminalEffect{ false }, + _pixelShaderPath{}, _forceFullRepaintRendering{ false }, _softwareRendering{ false }, _antialiasingMode{ D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE }, @@ -229,6 +231,85 @@ _CompileShader( #endif } +// Routine Description: +// - Checks if terminal effects are enabled. +// Arguments: +// Return Value: +// - True if terminal effects are enabled +bool DxEngine::_HasTerminalEffects() const noexcept +{ + return _terminalEffectsEnabled && (_retroTerminalEffect || !_pixelShaderPath.empty()); +} + +// Routine Description: +// - Toggles terminal effects off and on. If no terminal effect is configured has no effect +// Arguments: +// Return Value: +// - Void +void DxEngine::ToggleShaderEffects() +{ + _terminalEffectsEnabled = !_terminalEffectsEnabled; + LOG_IF_FAILED(InvalidateAll()); +} + +// Routine Description: +// - Loads pixel shader source depending on _retroTerminalEffect and _pixelShaderPath +// Arguments: +// Return Value: +// - Pixel shader source code +std::string DxEngine::_LoadPixelShaderFile() const +{ + // If the user specified the new pixel shader, it has precedence + if (!_pixelShaderPath.empty()) + { + try + { + wil::unique_hfile hFile{ CreateFileW(_pixelShaderPath.c_str(), + GENERIC_READ, + FILE_SHARE_READ, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr) }; + + THROW_LAST_ERROR_IF(!hFile); // This will be caught below. + + // fileSize is in bytes + const auto fileSize = GetFileSize(hFile.get(), nullptr); + THROW_LAST_ERROR_IF(fileSize == INVALID_FILE_SIZE); + + std::vector utf8buffer; + utf8buffer.reserve(fileSize); + + DWORD bytesRead = 0; + THROW_LAST_ERROR_IF(!ReadFile(hFile.get(), utf8buffer.data(), fileSize, &bytesRead, nullptr)); + + // convert buffer to UTF-8 string + std::string utf8string(utf8buffer.data(), fileSize); + + return utf8string; + } + catch (...) + { + // If we ran into any problems during loading pixel shader, call to + // the warning callback to surface the file not found error + const auto exceptionHr = LOG_CAUGHT_EXCEPTION(); + if (_pfnWarningCallback) + { + _pfnWarningCallback(exceptionHr); + } + + return std::string{}; + } + } + else if (_retroTerminalEffect) + { + return std::string{ retroPixelShaderString }; + } + + return std::string{}; +} + // Routine Description: // - Setup D3D objects for doing shader things for terminal effects. // Arguments: @@ -236,6 +317,19 @@ _CompileShader( // - HRESULT status. HRESULT DxEngine::_SetupTerminalEffects() { + _pixelShaderLoaded = false; + + const auto pixelShaderSource = _LoadPixelShaderFile(); + if (pixelShaderSource.empty()) + { + // There's no shader to compile. This might be due to failing to load, + // or because there's just no shader enabled at all. + // Turn the effects off for now. + _terminalEffectsEnabled = false; + + return S_FALSE; + } + ::Microsoft::WRL::ComPtr swapBuffer; RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&swapBuffer)); @@ -260,12 +354,26 @@ HRESULT DxEngine::_SetupTerminalEffects() // Prepare shaders. auto vertexBlob = _CompileShader(screenVertexShaderString, "vs_5_0"); - auto pixelBlob = _CompileShader(screenPixelShaderString, "ps_5_0"); - // TODO:GH#3928 move the shader files to to hlsl files and package their - // build output to UWP app and load with these. - // ::Microsoft::WRL::ComPtr vertexBlob, pixelBlob; - // RETURN_IF_FAILED(D3DReadFileToBlob(L"ScreenVertexShader.cso", &vertexBlob)); - // RETURN_IF_FAILED(D3DReadFileToBlob(L"ScreenPixelShader.cso", &pixelBlob)); + Microsoft::WRL::ComPtr pixelBlob; + // As the pixel shader source is user provided it's possible there's a problem with it + // so load it inside a try catch, on any error log and fallback on the error pixel shader + // If even the error pixel shader fails to load rely on standard exception handling + try + { + pixelBlob = _CompileShader(pixelShaderSource, "ps_5_0"); + } + catch (...) + { + // Call to the warning callback to surface the shader compile error + const auto exceptionHr = LOG_CAUGHT_EXCEPTION(); + if (_pfnWarningCallback) + { + // If this fails, it'll return E_FAIL, which is terribly + // uninformative. Instead, raise something more useful. + _pfnWarningCallback(D2DERR_SHADER_COMPILE_FAILED); + } + return exceptionHr; + } RETURN_IF_FAILED(_d3dDevice->CreateVertexShader( vertexBlob->GetBufferPointer(), @@ -329,23 +437,47 @@ HRESULT DxEngine::_SetupTerminalEffects() // Create the texture sampler state. RETURN_IF_FAILED(_d3dDevice->CreateSamplerState(&samplerDesc, &_samplerState)); + _pixelShaderLoaded = true; return S_OK; } // Routine Description: // - Puts the correct values in _pixelShaderSettings, so the struct can be -// passed the GPU. +// passed the GPU and updates the GPU resource. // Arguments: // - // Return Value: // - void DxEngine::_ComputePixelShaderSettings() noexcept { - // Retro scan lines alternate every pixel row at 100% scaling. - _pixelShaderSettings.ScaledScanLinePeriod = _scale * 1.0f; + if (_HasTerminalEffects() && _d3dDeviceContext && _pixelShaderSettingsBuffer) + { + try + { + // Set the time + // TODO:GH#7013 Grab timestamp + _pixelShaderSettings.Time = 0.0f; - // Gaussian distribution sigma used for blurring. - _pixelShaderSettings.ScaledGaussianSigma = _scale * 2.0f; + // Set the UI Scale + _pixelShaderSettings.Scale = _scale; + + // Set the display resolution + const float w = 1.0f * _displaySizePixels.width(); + const float h = 1.0f * _displaySizePixels.height(); + _pixelShaderSettings.Resolution = XMFLOAT2{ w, h }; + + // Set the background + DirectX::XMFLOAT4 background{}; + background.x = _backgroundColor.r; + background.y = _backgroundColor.g; + background.z = _backgroundColor.b; + background.w = _backgroundColor.a; + _pixelShaderSettings.Background = background; + + _d3dDeviceContext->UpdateSubresource(_pixelShaderSettingsBuffer.Get(), 0, nullptr, &_pixelShaderSettings, 0, 0); + } + CATCH_LOG(); + } } // Routine Description; @@ -521,13 +653,13 @@ try } } - if (_retroTerminalEffects) + if (_HasTerminalEffects()) { const HRESULT hr = _SetupTerminalEffects(); if (FAILED(hr)) { - _retroTerminalEffects = false; LOG_HR_MSG(hr, "Failed to setup terminal effects. Disabling."); + _terminalEffectsEnabled = false; } } @@ -560,6 +692,8 @@ try CATCH_LOG(); // A failure in the notification function isn't a failure to prepare, so just log it and go on. } + _recreateDeviceRequested = false; + return S_OK; } CATCH_RETURN(); @@ -675,7 +809,15 @@ void DxEngine::_ReleaseDeviceResources() noexcept { _haveDeviceResources = false; + // Destroy Terminal Effect resources + _renderTargetView.Reset(); + _vertexShader.Reset(); + _pixelShader.Reset(); + _vertexLayout.Reset(); + _screenQuadVertexBuffer.Reset(); _pixelShaderSettingsBuffer.Reset(); + _samplerState.Reset(); + _framebufferCapture.Reset(); _d2dBrushForeground.Reset(); _d2dBrushBackground.Reset(); @@ -802,17 +944,38 @@ void DxEngine::SetCallback(std::function pfn) _pfn = pfn; } -bool DxEngine::GetRetroTerminalEffects() const noexcept +void DxEngine::SetWarningCallback(std::function pfn) { - return _retroTerminalEffects; + _pfnWarningCallback = pfn; } -void DxEngine::SetRetroTerminalEffects(bool enable) noexcept +bool DxEngine::GetRetroTerminalEffect() const noexcept +{ + return _retroTerminalEffect; +} + +void DxEngine::SetRetroTerminalEffect(bool enable) noexcept try { - if (_retroTerminalEffects != enable) + if (_retroTerminalEffect != enable) { - _retroTerminalEffects = enable; + // Enable shader effects if the path isn't empty. Otherwise leave it untouched. + _terminalEffectsEnabled = enable ? true : _terminalEffectsEnabled; + _retroTerminalEffect = enable; + _recreateDeviceRequested = true; + LOG_IF_FAILED(InvalidateAll()); + } +} +CATCH_LOG() + +void DxEngine::SetPixelShaderPath(std::wstring_view value) noexcept +try +{ + if (_pixelShaderPath != value) + { + // Enable shader effects if the path isn't empty. Otherwise leave it untouched. + _terminalEffectsEnabled = value.empty() ? _terminalEffectsEnabled : true; + _pixelShaderPath = { value }; _recreateDeviceRequested = true; LOG_IF_FAILED(InvalidateAll()); } @@ -1085,14 +1248,9 @@ try { RETURN_HR_IF(E_NOT_VALID_STATE, _isPainting); // invalid to start a paint while painting. - // If someone explicitly requested differential rendering off, then we need to invalidate everything + // If full repaints are needed then we need to invalidate everything // so the entire frame is repainted. - // - // If retro terminal effects are on, we must invalidate everything for them to draw correctly. - // Yes, this will further impact the performance of retro terminal effects. - // But we're talking about running the entire display pipeline through a shader for - // cosmetic effect, so performance isn't likely the top concern with this feature. - if (_forceFullRepaintRendering || _retroTerminalEffects) + if (_FullRepaintNeeded()) { _invalidMap.set_all(); } @@ -1118,7 +1276,6 @@ try if (!_haveDeviceResources || _recreateDeviceRequested) { RETURN_IF_FAILED(_CreateDeviceResources(true)); - _recreateDeviceRequested = false; } else if (_displaySizePixels != clientSize || _prevScale != _scale) { @@ -1312,12 +1469,12 @@ void DxEngine::WaitUntilCanRender() noexcept { if (_presentReady) { - if (_retroTerminalEffects) + if (_HasTerminalEffects() && _pixelShaderLoaded) { const HRESULT hr2 = _PaintTerminalEffects(); if (FAILED(hr2)) { - _retroTerminalEffects = false; + _pixelShaderLoaded = false; LOG_HR_MSG(hr2, "Failed to paint terminal effects. Disabling."); } } @@ -1378,10 +1535,15 @@ void DxEngine::WaitUntilCanRender() noexcept } } - // Finally copy the front image (being presented now) onto the backing buffer - // (where we are about to draw the next frame) so we can draw only the differences - // next frame. - RETURN_IF_FAILED(_CopyFrontToBack()); + // If we are doing full repaints we don't need to copy front buffer to back buffer + if (!_FullRepaintNeeded()) + { + // Finally copy the front image (being presented now) onto the backing buffer + // (where we are about to draw the next frame) so we can draw only the differences + // next frame. + RETURN_IF_FAILED(_CopyFrontToBack()); + } + _presentReady = false; _presentDirty.clear(); @@ -1687,6 +1849,18 @@ try } CATCH_RETURN() +[[nodiscard]] bool DxEngine::_FullRepaintNeeded() const noexcept +{ + // If someone explicitly requested differential rendering off, then we need to invalidate everything + // so the entire frame is repainted. + // + // If terminal effects are on, we must invalidate everything for them to draw correctly. + // Yes, this will further impact the performance of terminal effects. + // But we're talking about running the entire display pipeline through a shader for + // cosmetic effect, so performance isn't likely the top concern with this feature. + return _forceFullRepaintRendering || _HasTerminalEffects(); +} + // Routine Description: // - Updates the default brush colors used for drawing // Arguments: @@ -1749,6 +1923,9 @@ CATCH_RETURN() _hyperlinkStrokeStyle = (textAttributes.GetHyperlinkId() == _hyperlinkHoveredId) ? _strokeStyle : _dashStrokeStyle; } + // Update pixel shader settings as background color might have changed + _ComputePixelShaderSettings(); + return S_OK; } @@ -1813,15 +1990,8 @@ CATCH_RETURN(); RETURN_IF_FAILED(InvalidateAll()); - if (_retroTerminalEffects && _d3dDeviceContext && _pixelShaderSettingsBuffer) - { - _ComputePixelShaderSettings(); - try - { - _d3dDeviceContext->UpdateSubresource(_pixelShaderSettingsBuffer.Get(), 0, nullptr, &_pixelShaderSettings, 0, 0); - } - CATCH_RETURN(); - } + // Update pixel shader settings as scale might have changed + _ComputePixelShaderSettings(); return S_OK; } diff --git a/src/renderer/dx/DxRenderer.hpp b/src/renderer/dx/DxRenderer.hpp index c59b89c5a..601289d4e 100644 --- a/src/renderer/dx/DxRenderer.hpp +++ b/src/renderer/dx/DxRenderer.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -55,9 +56,14 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT SetWindowSize(const SIZE pixels) noexcept; void SetCallback(std::function pfn); + void SetWarningCallback(std::function pfn); - bool GetRetroTerminalEffects() const noexcept; - void SetRetroTerminalEffects(bool enable) noexcept; + void ToggleShaderEffects(); + + bool GetRetroTerminalEffect() const noexcept; + void SetRetroTerminalEffect(bool enable) noexcept; + + void SetPixelShaderPath(std::wstring_view value) noexcept; void SetForceFullRepaintRendering(bool enable) noexcept; @@ -124,6 +130,7 @@ namespace Microsoft::Console::Render protected: [[nodiscard]] HRESULT _DoUpdateTitle(_In_ const std::wstring& newTitle) noexcept override; [[nodiscard]] HRESULT _PaintTerminalEffects() noexcept; + [[nodiscard]] bool _FullRepaintNeeded() const noexcept; private: enum class SwapChainMode @@ -141,6 +148,7 @@ namespace Microsoft::Console::Render float _prevScale; std::function _pfn; + std::function _pfnWarningCallback; bool _isEnabled; bool _isPainting; @@ -221,7 +229,22 @@ namespace Microsoft::Console::Render std::unique_ptr _drawingContext; // Terminal effects resources. - bool _retroTerminalEffects; + + // Controls if configured terminal effects are enabled + bool _terminalEffectsEnabled; + + // Experimental and deprecated retro terminal effect + // Preserved for backwards compatibility + // Implemented in terms of the more generic pixel shader effect + // Has precendence over pixel shader effect + bool _retroTerminalEffect; + + // Experimental and pixel shader effect + // Allows user to load a pixel shader from a few presets or from a file path + std::wstring _pixelShaderPath; + bool _pixelShaderLoaded{ false }; + + // DX resources needed for terminal effects ::Microsoft::WRL::ComPtr _renderTargetView; ::Microsoft::WRL::ComPtr _vertexShader; ::Microsoft::WRL::ComPtr _pixelShader; @@ -242,12 +265,19 @@ namespace Microsoft::Console::Render // DirectX constant buffers need to be a multiple of 16; align to pad the size. __declspec(align(16)) struct { - float ScaledScanLinePeriod; - float ScaledGaussianSigma; + // Note: This can be seen as API endpoint towards user provided pixel shaders. + // Changes here can break existing pixel shaders so be careful with changing datatypes + // and order of parameters + float Time; + float Scale; + DirectX::XMFLOAT2 Resolution; + DirectX::XMFLOAT4 Background; #pragma warning(suppress : 4324) // structure was padded due to __declspec(align()) } _pixelShaderSettings; [[nodiscard]] HRESULT _CreateDeviceResources(const bool createSwapChain) noexcept; + bool _HasTerminalEffects() const noexcept; + std::string _LoadPixelShaderFile() const; HRESULT _SetupTerminalEffects(); void _ComputePixelShaderSettings() noexcept; diff --git a/src/renderer/dx/ScreenPixelShader.h b/src/renderer/dx/ScreenPixelShader.h index 48b1d157f..0cbf185e2 100644 --- a/src/renderer/dx/ScreenPixelShader.h +++ b/src/renderer/dx/ScreenPixelShader.h @@ -1,19 +1,23 @@ #pragma once #ifdef __INSIDE_WINDOWS -const char screenPixelShaderString[] = ""; +constexpr std::string_view retroPixelShaderString{ "" }; #else -const char screenPixelShaderString[] = R"( +constexpr std::string_view retroPixelShaderString{ R"( +// The original retro pixel shader Texture2D shaderTexture; SamplerState samplerState; -cbuffer PixelShaderSettings -{ - float ScaledScanLinePeriod; - float ScaledGaussianSigma; +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) static const float M_PI = 3.14159265f; @@ -53,7 +57,7 @@ float4 Blur(Texture2D input, float2 tex_coord, float sigma) float SquareWave(float y) { - return 1 - (floor(y / ScaledScanLinePeriod) % 2) * SCANLINE_FACTOR; + return 1 - (floor(y / SCALED_SCANLINE_PERIOD) % 2) * SCANLINE_FACTOR; } float4 Scanline(float4 color, float4 pos) @@ -78,10 +82,10 @@ float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET // TODO:GH#3930 Make these configurable in some way. float4 color = input.Sample(samplerState, tex); - color += Blur(input, tex, ScaledGaussianSigma)*0.3; + color += Blur(input, tex, SCALED_GAUSSIAN_SIGMA)*0.3; color = Scanline(color, pos); return color; } -)"; +)" }; #endif