diff --git a/src/cascadia/TerminalApp/Profile.cpp b/src/cascadia/TerminalApp/Profile.cpp index 3e04ae22d8a..512ea930f3b 100644 --- a/src/cascadia/TerminalApp/Profile.cpp +++ b/src/cascadia/TerminalApp/Profile.cpp @@ -36,6 +36,11 @@ static const std::wstring CLOSEONEXIT_KEY{ L"closeOnExit" }; static const std::wstring PADDING_KEY{ L"padding" }; static const std::wstring STARTINGDIRECTORY_KEY{ L"startingDirectory" }; static const std::wstring ICON_KEY{ L"icon" }; +static const std::wstring USESHADOW_KEY{ L"useShadow" }; +static const std::wstring SHADOWBLUR_KEY{ L"shadowBlur" }; +static const std::wstring SHADOWOFFSETX_KEY{ L"shadowOffsetX" }; +static const std::wstring SHADOWOFFSETY_KEY{ L"shadowOffsetY" }; +static const std::wstring SHADOWCOLOR_KEY{ L"shadowColor" }; // Possible values for Scrollbar state static const std::wstring ALWAYS_VISIBLE{ L"visible" }; @@ -71,7 +76,12 @@ Profile::Profile() : _scrollbarState{ }, _closeOnExit{ true }, _padding{ DEFAULT_PADDING }, - _icon{ } + _icon{ }, + _useShadow{ false }, + _shadowBlur{ 3.0 }, + _shadowOffsetX{ 0.0 }, + _shadowOffsetY{ 0.0 }, + _shadowColor{ DEFAULT_SHADOW_COLOR } { UuidCreate(&_guid); } @@ -163,6 +173,12 @@ TerminalSettings Profile::CreateTerminalSettings(const std::vector& terminalSettings.DefaultBackground(_defaultBackground.value()); } + terminalSettings.UseShadow(_useShadow); + terminalSettings.ShadowBlur(_shadowBlur); + terminalSettings.ShadowOffsetX(_shadowOffsetX); + terminalSettings.ShadowOffsetY(_shadowOffsetY); + terminalSettings.ShadowColor(_shadowColor); + if (_scrollbarState) { ScrollbarState result = ParseScrollbarState(_scrollbarState.value()); @@ -200,6 +216,11 @@ JsonObject Profile::ToJson() const const auto useAcrylic = JsonValue::CreateBooleanValue(_useAcrylic); const auto closeOnExit = JsonValue::CreateBooleanValue(_closeOnExit); const auto padding = JsonValue::CreateStringValue(_padding); + const auto useShadow = JsonValue::CreateBooleanValue(_useShadow); + const auto shadowBlur = JsonValue::CreateNumberValue(_shadowBlur); + const auto shadowOffsetX = JsonValue::CreateNumberValue(_shadowOffsetX); + const auto shadowOffsetY = JsonValue::CreateNumberValue(_shadowOffsetY); + const auto shadowColor = JsonValue::CreateStringValue(Utils::ColorToHexString(_shadowColor)); if (_startingDirectory) { @@ -270,6 +291,12 @@ JsonObject Profile::ToJson() const jsonObject.Insert(ICON_KEY, icon); } + jsonObject.Insert(USESHADOW_KEY, useShadow); + jsonObject.Insert(SHADOWBLUR_KEY, shadowBlur); + jsonObject.Insert(SHADOWOFFSETX_KEY, shadowOffsetX); + jsonObject.Insert(SHADOWOFFSETY_KEY, shadowOffsetY); + jsonObject.Insert(SHADOWCOLOR_KEY, shadowColor); + return jsonObject; } @@ -401,6 +428,29 @@ Profile Profile::FromJson(winrt::Windows::Data::Json::JsonObject json) { result._icon = json.GetNamedString(ICON_KEY); } + if (json.HasKey(USESHADOW_KEY)) + { + result._useShadow = json.GetNamedBoolean(USESHADOW_KEY); + } + if (json.HasKey(SHADOWBLUR_KEY)) + { + result._shadowBlur = json.GetNamedNumber(SHADOWBLUR_KEY); + } + if (json.HasKey(SHADOWOFFSETX_KEY)) + { + result._shadowOffsetX = json.GetNamedNumber(SHADOWOFFSETX_KEY); + } + if (json.HasKey(SHADOWOFFSETY_KEY)) + { + result._shadowOffsetY = json.GetNamedNumber(SHADOWOFFSETY_KEY); + } + if (json.HasKey(SHADOWCOLOR_KEY)) + { + const auto colorString = json.GetNamedString(SHADOWCOLOR_KEY); + // TODO: MSFT:20737698 - if this fails, display an approriate error + const auto color = Utils::ColorFromHexString(colorString.c_str()); + result._shadowColor = color; + } return result; } diff --git a/src/cascadia/TerminalApp/Profile.h b/src/cascadia/TerminalApp/Profile.h index 1605ba0d7ad..1f7f579fa2e 100644 --- a/src/cascadia/TerminalApp/Profile.h +++ b/src/cascadia/TerminalApp/Profile.h @@ -85,5 +85,11 @@ class TerminalApp::Profile final bool _closeOnExit; std::wstring _padding; + bool _useShadow; + double _shadowBlur; + double _shadowOffsetX; + double _shadowOffsetY; + uint32_t _shadowColor; + std::optional _icon; }; diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 644f457abf8..ef299eba86e 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -139,6 +139,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // Refresh our font with the renderer _UpdateFont(); + THROW_IF_FAILED(_renderEngine->UpdateShadow(_settings.UseShadow(), + static_cast(_settings.ShadowBlur()), + _settings.ShadowColor())); + const auto width = _swapChainPanel.ActualWidth(); const auto height = _swapChainPanel.ActualHeight(); if (width != 0 && height != 0) @@ -323,6 +327,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // Tell the DX Engine to notify us when the swap chain changes. dxEngine->SetCallback(std::bind(&TermControl::SwapChainChanged, this)); + // FIXME: pull out renderer settings to pass to DxEngine. + THROW_IF_FAILED(dxEngine->UpdateShadow(_settings.UseShadow(), + static_cast(_settings.ShadowBlur()), + _settings.ShadowColor())); + THROW_IF_FAILED(dxEngine->Enable()); _renderEngine = std::move(dxEngine); diff --git a/src/cascadia/TerminalControl/TerminalControl.vcxproj b/src/cascadia/TerminalControl/TerminalControl.vcxproj index 60b8538b289..2d5ef0132a8 100644 --- a/src/cascadia/TerminalControl/TerminalControl.vcxproj +++ b/src/cascadia/TerminalControl/TerminalControl.vcxproj @@ -50,7 +50,7 @@ - dwrite.lib;dxgi.lib;d2d1.lib;d3d11.lib;shcore.lib;winmm.lib;pathcch.lib;propsys.lib;uiautomationcore.lib;Shlwapi.lib;ntdll.lib;user32.lib;kernel32.lib;%(AdditionalDependencies) + dwrite.lib;dxgi.lib;dxguid.lib;d2d1.lib;d3d11.lib;shcore.lib;winmm.lib;pathcch.lib;propsys.lib;uiautomationcore.lib;Shlwapi.lib;ntdll.lib;user32.lib;kernel32.lib;%(AdditionalDependencies) $(OpenConsoleDir)src\types\inc;%(AdditionalIncludeDirectories) @@ -66,4 +66,4 @@ - + \ No newline at end of file diff --git a/src/cascadia/TerminalControl/TerminalControl.vcxproj.filters b/src/cascadia/TerminalControl/TerminalControl.vcxproj.filters index f2869cf5bea..a1038089070 100644 --- a/src/cascadia/TerminalControl/TerminalControl.vcxproj.filters +++ b/src/cascadia/TerminalControl/TerminalControl.vcxproj.filters @@ -11,20 +11,15 @@ - - - - - diff --git a/src/cascadia/TerminalSettings/IControlSettings.idl b/src/cascadia/TerminalSettings/IControlSettings.idl index e552e3ac4cf..136298c5008 100644 --- a/src/cascadia/TerminalSettings/IControlSettings.idl +++ b/src/cascadia/TerminalSettings/IControlSettings.idl @@ -34,5 +34,10 @@ namespace Microsoft.Terminal.Settings String StartingDirectory; String EnvironmentVariables; + Boolean UseShadow; + Double ShadowBlur; + Double ShadowOffsetX; + Double ShadowOffsetY; + UInt32 ShadowColor; }; } diff --git a/src/cascadia/TerminalSettings/TerminalSettings.cpp b/src/cascadia/TerminalSettings/TerminalSettings.cpp index 04c8a46c9a2..115d7c64409 100644 --- a/src/cascadia/TerminalSettings/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettings/TerminalSettings.cpp @@ -25,7 +25,11 @@ namespace winrt::Microsoft::Terminal::Settings::implementation _fontFace{ DEFAULT_FONT_FACE }, _fontSize{ DEFAULT_FONT_SIZE }, _keyBindings{ nullptr }, - _scrollbarState{ ScrollbarState::Visible } + _scrollbarState{ ScrollbarState::Visible }, + _shadowBlur{ 3.0 }, + _shadowColor{ DEFAULT_SHADOW_COLOR }, + _shadowOffsetX{ 0 }, + _shadowOffsetY{ 0 } { } @@ -231,6 +235,56 @@ namespace winrt::Microsoft::Terminal::Settings::implementation _envVars = value; } + bool TerminalSettings::UseShadow() + { + return _useShadow; + } + + void TerminalSettings::UseShadow(bool const value) + { + _useShadow = value; + } + + double TerminalSettings::ShadowBlur() + { + return _shadowBlur; + } + + void TerminalSettings::ShadowBlur(double const value) + { + _shadowBlur = value; + } + + double TerminalSettings::ShadowOffsetX() + { + return _shadowOffsetX; + } + + void TerminalSettings::ShadowOffsetX(double const value) + { + _shadowOffsetX = value; + } + + double TerminalSettings::ShadowOffsetY() + { + return _shadowOffsetY; + } + + void TerminalSettings::ShadowOffsetY(double const value) + { + _shadowOffsetY = value; + } + + uint32_t TerminalSettings::ShadowColor() + { + return _shadowColor; + } + + void TerminalSettings::ShadowColor(uint32_t const value) + { + _shadowColor = value; + } + Settings::ScrollbarState TerminalSettings::ScrollState() const noexcept { return _scrollbarState; diff --git a/src/cascadia/TerminalSettings/terminalsettings.h b/src/cascadia/TerminalSettings/terminalsettings.h index 847da3a767f..2e3ef416848 100644 --- a/src/cascadia/TerminalSettings/terminalsettings.h +++ b/src/cascadia/TerminalSettings/terminalsettings.h @@ -73,6 +73,21 @@ namespace winrt::Microsoft::Terminal::Settings::implementation hstring EnvironmentVariables(); void EnvironmentVariables(hstring const& value); + bool UseShadow(); + void UseShadow(bool const value); + + double ShadowBlur(); + void ShadowBlur(double const value); + + double ShadowOffsetX(); + void ShadowOffsetX(double const value); + + double ShadowOffsetY(); + void ShadowOffsetY(double const value); + + uint32_t ShadowColor(); + void ShadowColor(uint32_t const value); + ScrollbarState ScrollState() const noexcept; void ScrollState(winrt::Microsoft::Terminal::Settings::ScrollbarState const& value) noexcept; @@ -97,6 +112,13 @@ namespace winrt::Microsoft::Terminal::Settings::implementation hstring _commandline; hstring _startingDir; hstring _envVars; + + bool _useShadow; + uint32_t _shadowColor; + double _shadowBlur; + double _shadowOffsetX; + double _shadowOffsetY; + Settings::IKeyBindings _keyBindings; Settings::ScrollbarState _scrollbarState; }; diff --git a/src/inc/DefaultSettings.h b/src/inc/DefaultSettings.h index d959e41f7dc..2823e91c65d 100644 --- a/src/inc/DefaultSettings.h +++ b/src/inc/DefaultSettings.h @@ -34,3 +34,5 @@ const std::wstring DEFAULT_STARTING_DIRECTORY{ L"%USERPROFILE%" }; constexpr COLORREF DEFAULT_CURSOR_COLOR = COLOR_WHITE; constexpr COLORREF DEFAULT_CURSOR_HEIGHT = 25; + +constexpr COLORREF DEFAULT_SHADOW_COLOR = OPACITY_OPAQUE | COLOR_BLACK; diff --git a/src/renderer/dx/CustomTextRenderer.cpp b/src/renderer/dx/CustomTextRenderer.cpp index 8ab09321f6c..4e9abdbf573 100644 --- a/src/renderer/dx/CustomTextRenderer.cpp +++ b/src/renderer/dx/CustomTextRenderer.cpp @@ -43,8 +43,11 @@ HRESULT CustomTextRenderer::GetPixelsPerDip(void* clientDrawingContext, { DrawingContext* drawingContext = static_cast(clientDrawingContext); + // Note that d2dContext could in theory be rendering to a different DPI than the device + // render target, if the bitmap render target used when rendering effects is at a different DPI. + // Perhaps useful to overscale some odd effects? float dpiX, dpiY; - drawingContext->renderTarget->GetDpi(&dpiX, &dpiY); + drawingContext->d2dContext->GetDpi(&dpiX, &dpiY); *pixelsPerDip = dpiX / USER_DEFAULT_SCREEN_DPI; return S_OK; } @@ -65,7 +68,7 @@ HRESULT CustomTextRenderer::GetCurrentTransform(void* clientDrawingContext, DrawingContext* drawingContext = static_cast(clientDrawingContext); // Matrix structures are defined identically - drawingContext->renderTarget->GetTransform((D2D1_MATRIX_3X2_F*)transform); + drawingContext->d2dContext->GetTransform((D2D1_MATRIX_3X2_F*)transform); return S_OK; } #pragma endregion @@ -170,7 +173,7 @@ void CustomTextRenderer::_FillRectangle(void* clientDrawingContext, } D2D1_RECT_F rect = D2D1::RectF(x, y, x + width, y + thickness); - drawingContext->renderTarget->FillRectangle(&rect, brush); + drawingContext->d2dContext->FillRectangle(&rect, brush); } // Routine Description: @@ -246,9 +249,6 @@ HRESULT CustomTextRenderer::DrawGlyphRun( D2D1_POINT_2F baselineOrigin = origin; baselineOrigin.y += drawingContext->spacing.baseline; - ::Microsoft::WRL::ComPtr d2dContext4; - RETURN_IF_FAILED(drawingContext->renderTarget->QueryInterface(d2dContext4.GetAddressOf())); - // Draw the background D2D1_RECT_F rect; rect.top = origin.y; @@ -261,7 +261,7 @@ HRESULT CustomTextRenderer::DrawGlyphRun( rect.right += glyphRun->glyphAdvances[i]; } - d2dContext4->FillRectangle(rect, drawingContext->backgroundBrush); + drawingContext->d2dContext->FillRectangle(rect, drawingContext->backgroundBrush); // Now go onto drawing the text. @@ -332,22 +332,24 @@ HRESULT CustomTextRenderer::DrawGlyphRun( case DWRITE_GLYPH_IMAGE_FORMATS_PREMULTIPLIED_B8G8R8A8: { // This run is bitmap glyphs. Use Direct2D to draw them. - d2dContext4->DrawColorBitmapGlyphRun(colorRun->glyphImageFormat, - currentBaselineOrigin, - &colorRun->glyphRun, - measuringMode); + drawingContext->d2dContext->DrawColorBitmapGlyphRun( + colorRun->glyphImageFormat, + currentBaselineOrigin, + &colorRun->glyphRun, + measuringMode); } break; case DWRITE_GLYPH_IMAGE_FORMATS_SVG: { // This run is SVG glyphs. Use Direct2D to draw them. - d2dContext4->DrawSvgGlyphRun(currentBaselineOrigin, - &colorRun->glyphRun, - drawingContext->foregroundBrush, - nullptr, // svgGlyphStyle - 0, // colorPaletteIndex - measuringMode); + drawingContext->d2dContext->DrawSvgGlyphRun( + currentBaselineOrigin, + &colorRun->glyphRun, + drawingContext->foregroundBrush, + nullptr, // svgGlyphStyle + 0, // colorPaletteIndex + measuringMode); } break; @@ -371,7 +373,7 @@ HRESULT CustomTextRenderer::DrawGlyphRun( { if (!tempBrush) { - RETURN_IF_FAILED(d2dContext4->CreateSolidColorBrush(colorRun->runColor, &tempBrush)); + RETURN_IF_FAILED(drawingContext->d2dContext->CreateSolidColorBrush(colorRun->runColor, &tempBrush)); } else { @@ -406,6 +408,7 @@ HRESULT CustomTextRenderer::DrawGlyphRun( glyphRunDescription, drawingContext->foregroundBrush)); } + return S_OK; } #pragma endregion @@ -418,11 +421,8 @@ HRESULT CustomTextRenderer::_DrawBasicGlyphRun(DrawingContext* clientDrawingCont _In_ const DWRITE_GLYPH_RUN_DESCRIPTION* glyphRunDescription, ID2D1Brush* brush) { - ::Microsoft::WRL::ComPtr d2dContext4; - RETURN_IF_FAILED(clientDrawingContext->renderTarget->QueryInterface(d2dContext4.GetAddressOf())); - // Using the context is the easiest/default way of drawing. - d2dContext4->DrawGlyphRun(baselineOrigin, glyphRun, glyphRunDescription, brush, measuringMode); + clientDrawingContext->d2dContext->DrawGlyphRun(baselineOrigin, glyphRun, glyphRunDescription, brush, measuringMode); // However, we could probably add options here and switch out to one of these other drawing methods (making it // conditional based on the IUnknown* clientDrawingEffect or on some other switches and try these out instead: @@ -442,7 +442,7 @@ HRESULT CustomTextRenderer::_DrawBasicGlyphRunManually(DrawingContext* clientDra { // This is regular text but manually ::Microsoft::WRL::ComPtr d2dFactory; - clientDrawingContext->renderTarget->GetFactory(d2dFactory.GetAddressOf()); + clientDrawingContext->d2dContext->GetFactory(d2dFactory.GetAddressOf()); ::Microsoft::WRL::ComPtr pathGeometry; d2dFactory->CreatePathGeometry(pathGeometry.GetAddressOf()); @@ -470,7 +470,7 @@ HRESULT CustomTextRenderer::_DrawBasicGlyphRunManually(DrawingContext* clientDra &matrixAlign, transformedGeometry.GetAddressOf()); - clientDrawingContext->renderTarget->FillGeometry(transformedGeometry.Get(), clientDrawingContext->foregroundBrush); + clientDrawingContext->d2dContext->FillGeometry(transformedGeometry.Get(), clientDrawingContext->foregroundBrush); return S_OK; } @@ -484,7 +484,7 @@ HRESULT CustomTextRenderer::_DrawGlowGlyphRun(DrawingContext* clientDrawingConte { // This is glow text manually ::Microsoft::WRL::ComPtr d2dFactory; - clientDrawingContext->renderTarget->GetFactory(d2dFactory.GetAddressOf()); + clientDrawingContext->d2dContext->GetFactory(d2dFactory.GetAddressOf()); ::Microsoft::WRL::ComPtr pathGeometry; d2dFactory->CreatePathGeometry(pathGeometry.GetAddressOf()); @@ -521,12 +521,12 @@ HRESULT CustomTextRenderer::_DrawGlowGlyphRun(DrawingContext* clientDrawingConte ::Microsoft::WRL::ComPtr brush; ::Microsoft::WRL::ComPtr outlineBrush; - clientDrawingContext->renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White, 1.0f), brush.GetAddressOf()); - clientDrawingContext->renderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Red, 1.0f), outlineBrush.GetAddressOf()); + clientDrawingContext->d2dContext->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White, 1.0f), brush.GetAddressOf()); + clientDrawingContext->d2dContext->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Red, 1.0f), outlineBrush.GetAddressOf()); - clientDrawingContext->renderTarget->DrawGeometry(transformedGeometry.Get(), outlineBrush.Get(), 2.0f); + clientDrawingContext->d2dContext->DrawGeometry(transformedGeometry.Get(), outlineBrush.Get(), 2.0f); - clientDrawingContext->renderTarget->FillGeometry(alignedGeometry.Get(), brush.Get()); + clientDrawingContext->d2dContext->FillGeometry(alignedGeometry.Get(), brush.Get()); return S_OK; } diff --git a/src/renderer/dx/CustomTextRenderer.h b/src/renderer/dx/CustomTextRenderer.h index 186f7dcc9a2..ca847fd5828 100644 --- a/src/renderer/dx/CustomTextRenderer.h +++ b/src/renderer/dx/CustomTextRenderer.h @@ -3,13 +3,15 @@ #pragma once +#include + #include namespace Microsoft::Console::Render { struct DrawingContext { - DrawingContext(ID2D1RenderTarget* renderTarget, + DrawingContext(ID2D1DeviceContext4* d2dContext, ID2D1Brush* foregroundBrush, ID2D1Brush* backgroundBrush, IDWriteFactory* dwriteFactory, @@ -17,7 +19,7 @@ namespace Microsoft::Console::Render const D2D_SIZE_F cellSize, const D2D1_DRAW_TEXT_OPTIONS options = D2D1_DRAW_TEXT_OPTIONS_NONE) { - this->renderTarget = renderTarget; + this->d2dContext = d2dContext; this->foregroundBrush = foregroundBrush; this->backgroundBrush = backgroundBrush; this->dwriteFactory = dwriteFactory; @@ -26,7 +28,7 @@ namespace Microsoft::Console::Render this->options = options; } - ID2D1RenderTarget* renderTarget; + ID2D1DeviceContext4* d2dContext; ID2D1Brush* foregroundBrush; ID2D1Brush* backgroundBrush; IDWriteFactory* dwriteFactory; diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index 1c1df7af3a9..e58ab240b9e 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -33,9 +33,12 @@ DxEngine::DxEngine() : _presentOffset{ 0 }, _isEnabled{ false }, _isPainting{ false }, + _d2dPaintContext{ nullptr }, _displaySizePixels{ 0 }, _foregroundColor{ 0 }, _backgroundColor{ 0 }, + _shadowBlurInDip{ 3.0f }, + _shadowColor{ D2D1::ColorF(D2D1::ColorF::Black) }, _glyphCell{ 0 }, _haveDeviceResources{ false }, _hwndTarget{ static_cast(INVALID_HANDLE_VALUE) }, @@ -234,7 +237,7 @@ HRESULT DxEngine::_CreateDeviceResources(const bool createSwapChain) noexcept _haveDeviceResources = true; if (_isPainting) { // TODO: MSFT: 21169176 - remove this or restore the "try a few times to render" code... I think - _d2dRenderTarget->BeginDraw(); + _d2dPaintContext->BeginDraw(); } freeOnFail.release(); // don't need to release if we made it to the bottom and everything was good. @@ -253,7 +256,7 @@ HRESULT DxEngine::_PrepareRenderTarget() noexcept { RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&_dxgiSurface))); - D2D1_RENDER_TARGET_PROPERTIES props = + D2D1_RENDER_TARGET_PROPERTIES renderTargetProps = D2D1::RenderTargetProperties( D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED), @@ -261,10 +264,13 @@ HRESULT DxEngine::_PrepareRenderTarget() noexcept 0.0f); RETURN_IF_FAILED(_d2dFactory->CreateDxgiSurfaceRenderTarget(_dxgiSurface.Get(), - &props, + &renderTargetProps, &_d2dRenderTarget)); - _d2dRenderTarget->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); + RETURN_IF_FAILED(_d2dRenderTarget.As(&_d2dTargetRenderContext)); + + _d2dTargetRenderContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); + RETURN_IF_FAILED(_d2dRenderTarget->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::DarkRed), &_d2dBrushBackground)); @@ -287,6 +293,22 @@ HRESULT DxEngine::_PrepareRenderTarget() noexcept RETURN_IF_FAILED(sc2->SetMatrixTransform(&inverseScale)); } + // for effect render. Copies DPI, pixel format etc, from _d2dRenderTarget. + ::Microsoft::WRL::ComPtr bitmapRenderTarget; + RETURN_IF_FAILED(_d2dRenderTarget->CreateCompatibleRenderTarget(&bitmapRenderTarget)); + RETURN_IF_FAILED(bitmapRenderTarget.As(&_d2dEffectRenderContext)); + ::Microsoft::WRL::ComPtr bitmap; + RETURN_IF_FAILED(bitmapRenderTarget->GetBitmap(&bitmap)); + RETURN_IF_FAILED(_d2dTargetRenderContext->CreateEffect(CLSID_D2D1Shadow, &_d2dShadowEffect)); + _d2dShadowEffect->SetInput(0, bitmap.Get()); + RETURN_IF_FAILED(_d2dTargetRenderContext->CreateEffect(CLSID_D2D1Composite, &_d2dEffect)); + _d2dEffect->SetInputEffect(0, _d2dShadowEffect.Get()); + _d2dEffect->SetInput(1, bitmap.Get()); + RETURN_IF_FAILED(_d2dShadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, _shadowBlurInDip)); + RETURN_IF_FAILED(_d2dShadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, _shadowColor)); + + _d2dEffectRenderContext->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE); + return S_OK; } @@ -304,9 +326,15 @@ void DxEngine::_ReleaseDeviceResources() noexcept if (nullptr != _d2dRenderTarget.Get() && _isPainting) { - _d2dRenderTarget->EndDraw(); + _d2dPaintContext->EndDraw(); + _d2dPaintContext = nullptr; } + _d2dTargetRenderContext.Reset(); + _d2dEffectRenderContext.Reset(); + _d2dShadowEffect.Reset(); + _d2dEffect.Reset(); + _d2dRenderTarget.Reset(); _dxgiSurface.Reset(); @@ -696,12 +724,15 @@ HRESULT DxEngine::StartPaint() noexcept { _dxgiSurface.Reset(); _d2dRenderTarget.Reset(); + _d2dTargetRenderContext.Reset(); + _d2dEffectRenderContext.Reset(); _dxgiSwapChain->ResizeBuffers(2, clientSize.cx, clientSize.cy, DXGI_FORMAT_B8G8R8A8_UNORM, 0); RETURN_IF_FAILED(_PrepareRenderTarget()); _displaySizePixels = clientSize; } - _d2dRenderTarget->BeginDraw(); + _d2dPaintContext = _useEffect ? _d2dEffectRenderContext.Get() : _d2dTargetRenderContext.Get(); + _d2dPaintContext->BeginDraw(); _isPainting = true; } @@ -724,7 +755,17 @@ HRESULT DxEngine::EndPaint() noexcept if (_haveDeviceResources) { _isPainting = false; - hr = _d2dRenderTarget->EndDraw(); + LOG_IF_FAILED(hr = _d2dPaintContext->EndDraw()); + _d2dPaintContext = nullptr; + if (_useEffect && SUCCEEDED(hr)) + { + _d2dTargetRenderContext->BeginDraw(); + _d2dTargetRenderContext->DrawImage( + _d2dEffect.Get(), + D2D1_INTERPOLATION_MODE_NEAREST_NEIGHBOR, + D2D1_COMPOSITE_MODE_SOURCE_COPY); + LOG_IF_FAILED(hr = _d2dTargetRenderContext->EndDraw()); + } if (SUCCEEDED(hr)) { @@ -846,7 +887,7 @@ HRESULT DxEngine::PaintBackground() noexcept D2D1_COLOR_F nothing = { 0 }; - _d2dRenderTarget->Clear(nothing); + _d2dPaintContext->Clear(nothing); return S_OK; } @@ -884,7 +925,7 @@ HRESULT DxEngine::PaintBufferLine(std::basic_string_view const clusters RETURN_IF_FAILED(_dwriteTextFormat->GetLineSpacing(&spacing)); // Assemble the drawing context information - DrawingContext context(_d2dRenderTarget.Get(), + DrawingContext context(_d2dPaintContext, _d2dBrushForeground.Get(), _d2dBrushBackground.Get(), _dwriteFactory.Get(), @@ -938,7 +979,7 @@ HRESULT DxEngine::PaintBufferGridLines(GridLines const lines, end = start; end.x += font.X; - _d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get()); + _d2dPaintContext->DrawLine(start, end, _d2dBrushForeground.Get()); } if (lines & GridLines::Left) @@ -946,7 +987,7 @@ HRESULT DxEngine::PaintBufferGridLines(GridLines const lines, end = start; end.y += font.Y; - _d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get()); + _d2dPaintContext->DrawLine(start, end, _d2dBrushForeground.Get()); } // NOTE: Watch out for inclusive/exclusive rectangles here. @@ -964,7 +1005,7 @@ HRESULT DxEngine::PaintBufferGridLines(GridLines const lines, end = start; end.x += font.X; - _d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get()); + _d2dPaintContext->DrawLine(start, end, _d2dBrushForeground.Get()); } start = target; @@ -975,7 +1016,7 @@ HRESULT DxEngine::PaintBufferGridLines(GridLines const lines, end = start; end.y += font.Y; - _d2dRenderTarget->DrawLine(start, end, _d2dBrushForeground.Get()); + _d2dPaintContext->DrawLine(start, end, _d2dBrushForeground.Get()); } // Move to the next character in this run. @@ -1015,7 +1056,7 @@ HRESULT DxEngine::PaintSelection(const SMALL_RECT rect) noexcept draw.right = static_cast(pixels.right); draw.bottom = static_cast(pixels.bottom); - _d2dRenderTarget->FillRectangle(draw, _d2dBrushForeground.Get()); + _d2dPaintContext->FillRectangle(draw, _d2dBrushForeground.Get()); return S_OK; } @@ -1105,12 +1146,12 @@ HRESULT DxEngine::PaintCursor(const IRenderEngine::CursorOptions& options) noexc { case CursorPaintType::Fill: { - _d2dRenderTarget->FillRectangle(rect, brush.Get()); + _d2dPaintContext->FillRectangle(rect, brush.Get()); break; } case CursorPaintType::Outline: { - _d2dRenderTarget->DrawRectangle(rect, brush.Get()); + _d2dPaintContext->DrawRectangle(rect, brush.Get()); break; } default: @@ -1214,6 +1255,29 @@ HRESULT DxEngine::UpdateDpi(int const iDpi) noexcept return S_OK; } +// Routine Description: +// - Sets the shadow effect settings of this renderer. Updates text renderer +// render target to skip the bitmap render target if not enabled. +// Arguments: +// - enabled - Use the shadow effect. +// - blurInDip - The shadow blur standard deviation value, in DIPs. +// - color - The shadow color. +// Return Value: +// - S_OK +[[nodiscard]] +HRESULT DxEngine::UpdateShadow(bool const enabled, float const blurInDip, COLORREF const color) noexcept +{ + _useEffect = enabled; + _shadowBlurInDip = blurInDip; + _shadowColor = _ColorFFromColorRef(OPACITY_OPAQUE | color); + if (_d2dShadowEffect) + { + RETURN_IF_FAILED(_d2dShadowEffect->SetValue(D2D1_SHADOW_PROP_BLUR_STANDARD_DEVIATION, _shadowBlurInDip)); + RETURN_IF_FAILED(_d2dShadowEffect->SetValue(D2D1_SHADOW_PROP_COLOR, _shadowColor)); + } + return S_OK; +} + // Method Description: // - Get the current scale factor of this renderer. The actual DPI the renderer // is USER_DEFAULT_SCREEN_DPI * GetScaling() diff --git a/src/renderer/dx/DxRenderer.hpp b/src/renderer/dx/DxRenderer.hpp index 08e0da31df2..458b95a63b2 100644 --- a/src/renderer/dx/DxRenderer.hpp +++ b/src/renderer/dx/DxRenderer.hpp @@ -121,6 +121,9 @@ namespace Microsoft::Console::Render float GetScaling() const noexcept; + [[nodiscard]] + HRESULT UpdateShadow(bool const enabled, float const blurInDip, COLORREF const color) noexcept; + protected: [[nodiscard]] HRESULT _DoUpdateTitle(_In_ const std::wstring& newTitle) noexcept override; @@ -153,6 +156,10 @@ namespace Microsoft::Console::Render D2D1_COLOR_F _foregroundColor; D2D1_COLOR_F _backgroundColor; + bool _useEffect; + float _shadowBlurInDip; + D2D1_COLOR_F _shadowColor; + [[nodiscard]] RECT _GetDisplayRect() const noexcept; @@ -191,6 +198,11 @@ namespace Microsoft::Console::Render ::Microsoft::WRL::ComPtr _dxgiOutput; ::Microsoft::WRL::ComPtr _dxgiSurface; ::Microsoft::WRL::ComPtr _d2dRenderTarget; + ::Microsoft::WRL::ComPtr _d2dTargetRenderContext; + ::Microsoft::WRL::ComPtr _d2dEffectRenderContext; + ID2D1DeviceContext4* _d2dPaintContext; + ::Microsoft::WRL::ComPtr _d2dEffect; + ::Microsoft::WRL::ComPtr _d2dShadowEffect; ::Microsoft::WRL::ComPtr _d2dBrushForeground; ::Microsoft::WRL::ComPtr _d2dBrushBackground; ::Microsoft::WRL::ComPtr _dxgiSwapChain;