diff --git a/samples/PixelShaders/BackgroundImage.hlsl b/samples/PixelShaders/BackgroundImage.hlsl new file mode 100644 index 00000000000..1cca4ee28b8 --- /dev/null +++ b/samples/PixelShaders/BackgroundImage.hlsl @@ -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); +} diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 8d3e692b114..b873a7c4dd2 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -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()); @@ -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. diff --git a/src/cascadia/TerminalControl/IControlAppearance.idl b/src/cascadia/TerminalControl/IControlAppearance.idl index 124eb641ba1..c94e6373008 100644 --- a/src/cascadia/TerminalControl/IControlAppearance.idl +++ b/src/cascadia/TerminalControl/IControlAppearance.idl @@ -18,5 +18,6 @@ namespace Microsoft.Terminal.Control // Experimental settings Boolean RetroTerminalEffect { get; }; String PixelShaderPath { get; }; + String PixelShaderImagePath { get; }; }; } diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 3b086e3c143..6eabcdab072 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -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) { diff --git a/src/cascadia/TerminalSettingsModel/IAppearanceConfig.idl b/src/cascadia/TerminalSettingsModel/IAppearanceConfig.idl index 5161a6716bb..bf8d8c5ed95 100644 --- a/src/cascadia/TerminalSettingsModel/IAppearanceConfig.idl +++ b/src/cascadia/TerminalSettingsModel/IAppearanceConfig.idl @@ -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); diff --git a/src/cascadia/TerminalSettingsModel/MTSMSettings.h b/src/cascadia/TerminalSettingsModel/MTSMSettings.h index 52644d32195..9ff95eaf114 100644 --- a/src/cascadia/TerminalSettingsModel/MTSMSettings.h +++ b/src/cascadia/TerminalSettingsModel/MTSMSettings.h @@ -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) \ diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index cb5633eace4..dc3a420ec23 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -256,6 +256,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _RetroTerminalEffect = appearance.RetroTerminalEffect(); _PixelShaderPath = winrt::hstring{ wil::ExpandEnvironmentStringsW(appearance.PixelShaderPath().c_str()) }; + _PixelShaderImagePath = winrt::hstring{ wil::ExpandEnvironmentStringsW(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); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index 22fba938723..a6c19b267e3 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -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); diff --git a/src/cascadia/inc/ControlProperties.h b/src/cascadia/inc/ControlProperties.h index 8134701d0f6..fc62147a239 100644 --- a/src/cascadia/inc/ControlProperties.h +++ b/src/cascadia/inc/ControlProperties.h @@ -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. diff --git a/src/renderer/atlas/AtlasEngine.api.cpp b/src/renderer/atlas/AtlasEngine.api.cpp index 30cac1da931..e60baab59d4 100644 --- a/src/renderer/atlas/AtlasEngine.api.cpp +++ b/src/renderer/atlas/AtlasEngine.api.cpp @@ -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; @@ -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) diff --git a/src/renderer/atlas/AtlasEngine.cpp b/src/renderer/atlas/AtlasEngine.cpp index 01403ef4b38..f7ed91b169a 100644 --- a/src/renderer/atlas/AtlasEngine.cpp +++ b/src/renderer/atlas/AtlasEngine.cpp @@ -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(_p.s->viewportCellCount.x) + 7) & ~7; + _p.colorBitmapRowStride = alignForward(_p.s->viewportCellCount.x, 8); _p.colorBitmapDepthStride = _p.colorBitmapRowStride * _p.s->viewportCellCount.y; _p.colorBitmap = Buffer(_p.colorBitmapDepthStride * 2); _p.backgroundBitmap = { _p.colorBitmap.data(), _p.colorBitmapDepthStride }; diff --git a/src/renderer/atlas/AtlasEngine.h b/src/renderer/atlas/AtlasEngine.h index a32fa4dc2fc..1c238dad048 100644 --- a/src/renderer/atlas/AtlasEngine.h +++ b/src/renderer/atlas/AtlasEngine.h @@ -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; @@ -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; diff --git a/src/renderer/atlas/Backend.h b/src/renderer/atlas/Backend.h index ad22c1b20dd..cdeab63b12c 100644 --- a/src/renderer/atlas/Backend.h +++ b/src/renderer/atlas/Backend.h @@ -86,6 +86,13 @@ namespace Microsoft::Console::Render::Atlas return val < min ? min : (max < val ? max : val); } + template + 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); diff --git a/src/renderer/atlas/BackendD3D.cpp b/src/renderer/atlas/BackendD3D.cpp index 308518ec27e..fe9df2e13cd 100644 --- a/src/renderer/atlas/BackendD3D.cpp +++ b/src/renderer/atlas/BackendD3D.cpp @@ -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 @@ -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()) @@ -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) { @@ -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(newSize, 64 * 1024); newCapacity = newSize / sizeof(QuadInstance); _instanceBuffer.reset(); @@ -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 diff --git a/src/renderer/atlas/BackendD3D.h b/src/renderer/atlas/BackendD3D.h index 1ae6b139b4c..784c854c152 100644 --- a/src/renderer/atlas/BackendD3D.h +++ b/src/renderer/atlas/BackendD3D.h @@ -247,6 +247,8 @@ namespace Microsoft::Console::Render::Atlas wil::com_ptr _customPixelShader; wil::com_ptr _customShaderConstantBuffer; wil::com_ptr _customShaderSamplerState; + wil::com_ptr _customShaderTexture; + wil::com_ptr _customShaderTextureView; std::chrono::steady_clock::time_point _customShaderStartTime; wil::com_ptr _backgroundBitmap; diff --git a/src/renderer/atlas/atlas.vcxproj b/src/renderer/atlas/atlas.vcxproj index a2faefce901..694b850224d 100644 --- a/src/renderer/atlas/atlas.vcxproj +++ b/src/renderer/atlas/atlas.vcxproj @@ -11,6 +11,7 @@ + @@ -22,10 +23,11 @@ Create - + + @@ -35,7 +37,6 @@ - diff --git a/src/renderer/atlas/common.h b/src/renderer/atlas/common.h index 71010b31f77..9ab279fc78c 100644 --- a/src/renderer/atlas/common.h +++ b/src/renderer/atlas/common.h @@ -380,6 +380,7 @@ namespace Microsoft::Console::Render::Atlas u32 backgroundColor = 0; u32 selectionColor = 0x7fffffff; std::wstring customPixelShaderPath; + std::wstring customPixelShaderImagePath; bool useRetroTerminalEffect = false; }; diff --git a/src/renderer/atlas/pch.h b/src/renderer/atlas/pch.h index a611147b664..0192f1995e9 100644 --- a/src/renderer/atlas/pch.h +++ b/src/renderer/atlas/pch.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include diff --git a/src/renderer/atlas/wic.cpp b/src/renderer/atlas/wic.cpp new file mode 100644 index 00000000000..320c6c2e2d6 --- /dev/null +++ b/src/renderer/atlas/wic.cpp @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "wic.h" + +#include "Backend.h" + +using namespace Microsoft::Console::Render::Atlas; +using namespace Microsoft::Console::Render::Atlas::WIC; + +static wil::com_ptr getWicFactory() +{ + static const auto coUninitialize = wil::CoInitializeEx(); + static const auto wicFactory = wil::CoCreateInstance(CLSID_WICImagingFactory2); + return wicFactory; +} + +void WIC::SaveTextureToPNG(ID3D11DeviceContext* deviceContext, ID3D11Resource* source, double dpi, const wchar_t* fileName) +{ + __assume(deviceContext != nullptr); + __assume(source != nullptr); + + wil::com_ptr texture; + THROW_IF_FAILED(source->QueryInterface(IID_PPV_ARGS(texture.addressof()))); + + wil::com_ptr d3dDevice; + deviceContext->GetDevice(d3dDevice.addressof()); + + D3D11_TEXTURE2D_DESC desc{}; + texture->GetDesc(&desc); + desc.BindFlags = 0; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.Usage = D3D11_USAGE_STAGING; + + wil::com_ptr staging; + THROW_IF_FAILED(d3dDevice->CreateTexture2D(&desc, nullptr, staging.put())); + + deviceContext->CopyResource(staging.get(), source); + + const auto wicFactory = getWicFactory(); + + wil::com_ptr stream; + THROW_IF_FAILED(wicFactory->CreateStream(stream.addressof())); + THROW_IF_FAILED(stream->InitializeFromFilename(fileName, GENERIC_WRITE)); + + wil::com_ptr encoder; + THROW_IF_FAILED(wicFactory->CreateEncoder(GUID_ContainerFormatPng, nullptr, encoder.addressof())); + THROW_IF_FAILED(encoder->Initialize(stream.get(), WICBitmapEncoderNoCache)); + + wil::com_ptr frame; + wil::com_ptr props; + THROW_IF_FAILED(encoder->CreateNewFrame(frame.addressof(), props.addressof())); + THROW_IF_FAILED(frame->Initialize(props.get())); + THROW_IF_FAILED(frame->SetSize(desc.Width, desc.Height)); + THROW_IF_FAILED(frame->SetResolution(dpi, dpi)); + auto pixelFormat = GUID_WICPixelFormat32bppBGRA; + THROW_IF_FAILED(frame->SetPixelFormat(&pixelFormat)); + + D3D11_MAPPED_SUBRESOURCE mapped; + THROW_IF_FAILED(deviceContext->Map(staging.get(), 0, D3D11_MAP_READ, 0, &mapped)); + THROW_IF_FAILED(frame->WritePixels(desc.Height, mapped.RowPitch, mapped.RowPitch * desc.Height, static_cast(mapped.pData))); + deviceContext->Unmap(staging.get(), 0); + + THROW_IF_FAILED(frame->Commit()); + THROW_IF_FAILED(encoder->Commit()); +} + +void WIC::LoadTextureFromFile(ID3D11Device* device, const wchar_t* fileName, ID3D11Texture2D** out_texture, ID3D11ShaderResourceView** out_textureView) +{ + __assume(device != nullptr); + __assume(fileName != nullptr); + __assume(out_texture != nullptr); + __assume(out_textureView != nullptr); + + const auto wicFactory = getWicFactory(); + + wil::com_ptr decoder; + THROW_IF_FAILED(wicFactory->CreateDecoderFromFilename(fileName, nullptr, GENERIC_READ, WICDecodeMetadataCacheOnDemand, decoder.addressof())); + + wil::com_ptr frame; + THROW_IF_FAILED(decoder->GetFrame(0, frame.addressof())); + + WICPixelFormatGUID srcFormat; + THROW_IF_FAILED(frame->GetPixelFormat(&srcFormat)); + + UINT srcWidth, srcHeight; + THROW_IF_FAILED(frame->GetSize(&srcWidth, &srcHeight)); + + static constexpr u32 maxSizeU32 = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION; + static constexpr f32 maxSizeF32 = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION; + UINT tgtWidth = srcWidth; + UINT tgtHeight = srcHeight; + wil::com_ptr scaler; + IWICBitmapSource* currentSource = frame.get(); + if (srcWidth > maxSizeU32 || srcHeight > maxSizeU32) + { + const auto ar = static_cast(srcHeight) / static_cast(srcWidth); + if (srcWidth > srcHeight) + { + tgtWidth = maxSizeU32; + tgtHeight = std::max(1, lroundf(maxSizeF32 * ar)); + } + else + { + tgtHeight = maxSizeU32; + tgtWidth = std::max(1, lroundf(maxSizeF32 / ar)); + } + + THROW_IF_FAILED(wicFactory->CreateBitmapScaler(scaler.addressof())); + THROW_IF_FAILED(scaler->Initialize(currentSource, tgtWidth, tgtHeight, WICBitmapInterpolationModeFant)); + currentSource = scaler.get(); + } + + wil::com_ptr converter; + THROW_IF_FAILED(wicFactory->CreateFormatConverter(converter.addressof())); + BOOL canConvert = FALSE; + THROW_IF_FAILED(converter->CanConvert(srcFormat, GUID_WICPixelFormat32bppPBGRA, &canConvert)); + THROW_HR_IF(E_UNEXPECTED, !canConvert); + THROW_IF_FAILED(converter->Initialize(currentSource, GUID_WICPixelFormat32bppPBGRA, WICBitmapDitherTypeErrorDiffusion, nullptr, 0, WICBitmapPaletteTypeMedianCut)); + + // Aligning the width by 8 pixels, results in a 32 byte stride, which is better for memcpy on contemporary hardware. + const uint64_t stride = alignForward(tgtWidth, 8) * sizeof(u32); + const uint64_t bytes = stride * static_cast(tgtHeight); + THROW_HR_IF(ERROR_ARITHMETIC_OVERFLOW, bytes > UINT32_MAX); + + Buffer buffer{ gsl::narrow_cast(bytes) }; + THROW_IF_FAILED(converter->CopyPixels(nullptr, gsl::narrow_cast(stride), gsl::narrow_cast(bytes), buffer.data())); + + const D3D11_TEXTURE2D_DESC desc = { + .Width = tgtWidth, + .Height = tgtHeight, + .MipLevels = 1, + .ArraySize = 1, + .Format = DXGI_FORMAT_B8G8R8A8_UNORM, + .SampleDesc = { 1, 0 }, + .Usage = D3D11_USAGE_IMMUTABLE, + .BindFlags = D3D11_BIND_SHADER_RESOURCE, + }; + const D3D11_SUBRESOURCE_DATA initData = { + .pSysMem = buffer.data(), + .SysMemPitch = gsl::narrow_cast(stride), + .SysMemSlicePitch = gsl::narrow_cast(bytes), + }; + wil::com_ptr texture; + THROW_IF_FAILED(device->CreateTexture2D(&desc, &initData, texture.addressof())); + + wil::com_ptr textureView; + THROW_IF_FAILED(device->CreateShaderResourceView(texture.get(), nullptr, textureView.addressof())); + + *out_texture = texture.detach(); + *out_textureView = textureView.detach(); +} diff --git a/src/renderer/atlas/wic.h b/src/renderer/atlas/wic.h index f9c2c81035f..777b64cf825 100644 --- a/src/renderer/atlas/wic.h +++ b/src/renderer/atlas/wic.h @@ -3,55 +3,8 @@ #pragma once -#include - -inline void SaveTextureToPNG(ID3D11DeviceContext* deviceContext, ID3D11Resource* source, double dpi, const wchar_t* fileName) +namespace Microsoft::Console::Render::Atlas::WIC { - __assume(deviceContext != nullptr); - __assume(source != nullptr); - - wil::com_ptr texture; - THROW_IF_FAILED(source->QueryInterface(IID_PPV_ARGS(texture.addressof()))); - - wil::com_ptr d3dDevice; - deviceContext->GetDevice(d3dDevice.addressof()); - - D3D11_TEXTURE2D_DESC desc{}; - texture->GetDesc(&desc); - desc.BindFlags = 0; - desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; - desc.Usage = D3D11_USAGE_STAGING; - - wil::com_ptr staging; - THROW_IF_FAILED(d3dDevice->CreateTexture2D(&desc, nullptr, staging.put())); - - deviceContext->CopyResource(staging.get(), source); - - static const auto coUninitialize = wil::CoInitializeEx(); - static const auto wicFactory = wil::CoCreateInstance(CLSID_WICImagingFactory2); - - wil::com_ptr stream; - THROW_IF_FAILED(wicFactory->CreateStream(stream.addressof())); - THROW_IF_FAILED(stream->InitializeFromFilename(fileName, GENERIC_WRITE)); - - wil::com_ptr encoder; - THROW_IF_FAILED(wicFactory->CreateEncoder(GUID_ContainerFormatPng, nullptr, encoder.addressof())); - THROW_IF_FAILED(encoder->Initialize(stream.get(), WICBitmapEncoderNoCache)); - - wil::com_ptr frame; - wil::com_ptr props; - THROW_IF_FAILED(encoder->CreateNewFrame(frame.addressof(), props.addressof())); - THROW_IF_FAILED(frame->Initialize(props.get())); - THROW_IF_FAILED(frame->SetSize(desc.Width, desc.Height)); - THROW_IF_FAILED(frame->SetResolution(dpi, dpi)); - auto pixelFormat = GUID_WICPixelFormat32bppBGRA; - THROW_IF_FAILED(frame->SetPixelFormat(&pixelFormat)); - - D3D11_MAPPED_SUBRESOURCE mapped; - THROW_IF_FAILED(deviceContext->Map(staging.get(), 0, D3D11_MAP_READ, 0, &mapped)); - THROW_IF_FAILED(frame->WritePixels(desc.Height, mapped.RowPitch, mapped.RowPitch * desc.Height, static_cast(mapped.pData))); - deviceContext->Unmap(staging.get(), 0); - - THROW_IF_FAILED(frame->Commit()); - THROW_IF_FAILED(encoder->Commit()); + void SaveTextureToPNG(ID3D11DeviceContext* deviceContext, ID3D11Resource* source, double dpi, const wchar_t* fileName); + void LoadTextureFromFile(ID3D11Device* device, const wchar_t* fileName, ID3D11Texture2D** out_texture, ID3D11ShaderResourceView** out_textureView); } diff --git a/src/renderer/inc/IRenderEngine.hpp b/src/renderer/inc/IRenderEngine.hpp index 016f0f10baa..2d151364463 100644 --- a/src/renderer/inc/IRenderEngine.hpp +++ b/src/renderer/inc/IRenderEngine.hpp @@ -97,6 +97,7 @@ namespace Microsoft::Console::Render // DxRenderer - getter virtual HRESULT Enable() noexcept { return S_OK; } [[nodiscard]] virtual std::wstring_view GetPixelShaderPath() noexcept { return {}; } + [[nodiscard]] virtual std::wstring_view GetPixelShaderImagePath() noexcept { return {}; } [[nodiscard]] virtual bool GetRetroTerminalEffect() const noexcept { return false; } [[nodiscard]] virtual float GetScaling() const noexcept { return 1; } [[nodiscard]] virtual Types::Viewport GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept { return Types::Viewport::Empty(); } @@ -108,6 +109,7 @@ namespace Microsoft::Console::Render virtual void SetForceFullRepaintRendering(bool enable) noexcept {} [[nodiscard]] virtual HRESULT SetHwnd(const HWND hwnd) noexcept { return E_NOTIMPL; } virtual void SetPixelShaderPath(std::wstring_view value) noexcept {} + virtual void SetPixelShaderImagePath(std::wstring_view value) noexcept {} virtual void SetRetroTerminalEffect(bool enable) noexcept {} virtual void SetSelectionBackground(const COLORREF color, const float alpha = 0.5f) noexcept {} virtual void SetSoftwareRendering(bool enable) noexcept {}