terminal/src/renderer/atlas/AtlasEngine.h
Leonard Hecker 70eeea68e4 wip
2021-10-11 02:15:21 +02:00

345 lines
15 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include <d2d1.h>
#include <d3d11_1.h>
#include <dwrite.h>
#include <dxgi.h>
#include <robin_hood.h>
#include <til/pair.h>
#include "../../renderer/inc/IRenderEngine.hpp"
namespace Microsoft::Console::Render
{
class AtlasEngine final : public IRenderEngine
{
public:
explicit AtlasEngine();
AtlasEngine(const AtlasEngine&) = delete;
AtlasEngine& operator=(const AtlasEngine&) = delete;
// IRenderEngine
[[nodiscard]] HRESULT StartPaint() noexcept override;
[[nodiscard]] HRESULT EndPaint() noexcept override;
[[nodiscard]] bool RequiresContinuousRedraw() noexcept override;
void WaitUntilCanRender() noexcept override;
[[nodiscard]] HRESULT Present() noexcept override;
[[nodiscard]] HRESULT PrepareForTeardown(_Out_ bool* const pForcePaint) noexcept override;
[[nodiscard]] HRESULT ScrollFrame() noexcept override;
[[nodiscard]] HRESULT Invalidate(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept override;
[[nodiscard]] HRESULT InvalidateSystem(const RECT* const prcDirtyClient) noexcept override;
[[nodiscard]] HRESULT InvalidateSelection(const std::vector<SMALL_RECT>& rectangles) noexcept override;
[[nodiscard]] HRESULT InvalidateScroll(const COORD* const pcoordDelta) noexcept override;
[[nodiscard]] HRESULT InvalidateAll() noexcept override;
[[nodiscard]] HRESULT InvalidateCircling(_Out_ bool* const pForcePaint) noexcept override;
[[nodiscard]] HRESULT InvalidateTitle(const std::wstring_view proposedTitle) noexcept override;
[[nodiscard]] HRESULT PrepareRenderInfo(const RenderFrameInfo& info) noexcept override;
[[nodiscard]] HRESULT ResetLineTransform() noexcept override;
[[nodiscard]] HRESULT PrepareLineTransform(const LineRendition lineRendition, const size_t targetRow, const size_t viewportLeft) noexcept override;
[[nodiscard]] HRESULT PaintBackground() noexcept override;
[[nodiscard]] HRESULT PaintBufferLine(gsl::span<const Cluster> const clusters, const COORD coord, const bool fTrimLeft, const bool lineWrapped) noexcept override;
[[nodiscard]] HRESULT PaintBufferGridLines(const GridLineSet lines, const COLORREF color, const size_t cchLine, const COORD coordTarget) noexcept override;
[[nodiscard]] HRESULT PaintSelection(const SMALL_RECT rect) noexcept override;
[[nodiscard]] HRESULT PaintCursor(const CursorOptions& options) noexcept override;
[[nodiscard]] HRESULT UpdateDrawingBrushes(const TextAttribute& textAttributes, const gsl::not_null<IRenderData*> pData, const bool usingSoftFont, const bool isSettingDefaultBrushes) noexcept override;
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo) noexcept override;
[[nodiscard]] HRESULT UpdateSoftFont(const gsl::span<const uint16_t> bitPattern, const SIZE cellSize, const size_t centeringHint) noexcept override;
[[nodiscard]] HRESULT UpdateDpi(const int iDpi) noexcept override;
[[nodiscard]] HRESULT UpdateViewport(const SMALL_RECT srNewViewport) noexcept override;
[[nodiscard]] HRESULT GetProposedFont(const FontInfoDesired& FontInfoDesired, _Out_ FontInfo& FontInfo, const int iDpi) noexcept override;
[[nodiscard]] HRESULT GetDirtyArea(gsl::span<const til::rectangle>& area) noexcept override;
[[nodiscard]] HRESULT GetFontSize(_Out_ COORD* const pFontSize) noexcept override;
[[nodiscard]] HRESULT IsGlyphWideByFont(const std::wstring_view glyph, _Out_ bool* const pResult) noexcept override;
[[nodiscard]] HRESULT UpdateTitle(const std::wstring_view newTitle) noexcept override;
// Just for compatibility with DxEngine, but can be removed at some point.
HRESULT Enable()
{
return S_OK;
}
// DxRenderer - getter
[[nodiscard]] bool GetRetroTerminalEffect() const noexcept;
[[nodiscard]] float GetScaling() const noexcept;
[[nodiscard]] HANDLE GetSwapChainHandle();
[[nodiscard]] Types::Viewport GetViewportInCharacters(const Types::Viewport& viewInPixels) const noexcept;
[[nodiscard]] Types::Viewport GetViewportInPixels(const Types::Viewport& viewInCharacters) const noexcept;
// DxRenderer - setter
void SetAntialiasingMode(const D2D1_TEXT_ANTIALIAS_MODE antialiasingMode) noexcept;
void SetCallback(std::function<void()> pfn);
void SetDefaultTextBackgroundOpacity(const float opacity) noexcept;
void SetForceFullRepaintRendering(bool enable) noexcept;
[[nodiscard]] HRESULT SetHwnd(const HWND hwnd) noexcept;
void SetPixelShaderPath(std::wstring_view value) noexcept;
void SetRetroTerminalEffect(bool enable) noexcept;
void SetSelectionBackground(const COLORREF color, const float alpha = 0.5f) noexcept;
void SetSoftwareRendering(bool enable) noexcept;
void SetWarningCallback(std::function<void(const HRESULT)> pfn);
[[nodiscard]] HRESULT SetWindowSize(const SIZE pixels) noexcept;
void ToggleShaderEffects();
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept;
void UpdateHyperlinkHoveredId(const uint16_t hoveredId) noexcept;
// Some helper classes for the implementation.
// public because I don't want to sprinkle the code with friends.
public:
template<typename T>
struct aligned_buffer
{
constexpr aligned_buffer() noexcept = default;
explicit aligned_buffer(size_t size, size_t alignment) :
_data{ THROW_IF_NULL_ALLOC(static_cast<T*>(_aligned_malloc(size * sizeof(T), alignment))) },
_size{ size }
{
}
~aligned_buffer()
{
_aligned_free(_data);
}
aligned_buffer(aligned_buffer&& other) noexcept :
_data{ std::exchange(other._data, nullptr) },
_size{ std::exchange(other._size, 0) }
{
}
aligned_buffer& operator=(aligned_buffer&& other) noexcept
{
_aligned_free(_data);
_data = std::exchange(other._data, nullptr);
_size = std::exchange(other._size, 0);
return *this;
}
T* data()
{
return _data;
}
size_t size()
{
return _size;
}
private:
T* _data = nullptr;
size_t _size = 0;
};
template<typename T>
struct vec2
{
T x{};
T y{};
bool operator==(const vec2& other) const noexcept
{
return memcmp(this, &other, sizeof(vec2)) == 0;
}
bool operator!=(const vec2& other) const noexcept
{
return memcmp(this, &other, sizeof(vec2)) != 0;
}
vec2 operator*(const vec2& other) const noexcept
{
return { static_cast<T>(x * other.x), static_cast<T>(y * other.y) };
}
vec2 operator/(const vec2& other) const noexcept
{
return { static_cast<T>(x / other.x), static_cast<T>(y / other.y) };
}
template<typename U = T>
U area() const noexcept
{
return static_cast<U>(x) * static_cast<U>(y);
}
};
template<typename T>
struct vec4
{
T x{};
T y{};
T z{};
T w{};
};
using u8 = uint8_t;
using u16 = uint16_t;
using u16x2 = vec2<u16>;
using u32 = uint32_t;
using u32x2 = vec2<u32>;
using f32 = float;
using f32x2 = vec2<f32>;
using f32x4 = vec4<f32>;
union glyph_entry
{
uint32_t value;
struct
{
uint32_t codepoint : 20;
uint32_t wide : 1;
uint32_t bold : 1;
uint32_t italic : 1;
};
constexpr bool operator==(const glyph_entry& other) const noexcept
{
return value == other.value;
}
};
struct glyph_entry_hasher
{
constexpr size_t operator()(glyph_entry entry) const noexcept
{
uint64_t x = entry.value;
x ^= x >> 33;
x *= UINT64_C(0xff51afd7ed558ccd);
x ^= x >> 33;
return static_cast<size_t>(x);
}
};
private:
// D3D constant buffers sizes must be a multiple of 16 bytes.
struct alignas(16) const_buffer
{
f32x4 viewport;
u32x2 cellSize;
u32 cellCountX;
u32 backgroundColor;
u32 selectionColor;
#pragma warning(suppress : 4324) // structure was padded due to alignment specifier
};
struct cell
{
union
{
u32 glyphIndex;
u16x2 glyphIndex16;
};
u32 flags;
u32x2 color;
};
enum class invalidation_flags : u8
{
none = 0,
device = 1 << 0,
size = 1 << 1,
font = 1 << 2,
cbuffer = 1 << 3,
title = 1 << 4,
};
friend constexpr invalidation_flags operator~(invalidation_flags v) noexcept { return static_cast<invalidation_flags>(~static_cast<u8>(v)); }
friend constexpr invalidation_flags operator|(invalidation_flags lhs, invalidation_flags rhs) noexcept { return static_cast<invalidation_flags>(static_cast<u8>(lhs) | static_cast<u8>(rhs)); }
friend constexpr invalidation_flags operator&(invalidation_flags lhs, invalidation_flags rhs) noexcept { return static_cast<invalidation_flags>(static_cast<u8>(lhs) & static_cast<u8>(rhs)); }
friend constexpr void operator|=(invalidation_flags& lhs, invalidation_flags rhs) noexcept { lhs = lhs | rhs; }
friend constexpr void operator&=(invalidation_flags& lhs, invalidation_flags rhs) noexcept { lhs = lhs & rhs; }
// resource handling
[[nodiscard]] HRESULT _handleException(const wil::ResultException& exception) noexcept;
__declspec(noinline) void _createResources();
__declspec(noinline) void _recreateSizeDependentResources();
__declspec(noinline) void _recreateFontDependentResources();
void _setShaderResources() const;
void _updateConstantBuffer() const;
// text handling
IDWriteTextFormat* _getTextFormat(bool bold, bool italic) const noexcept { return _r.textFormats[italic][bold].get(); }
wil::com_ptr<IDWriteTextFormat> _createTextFormat(const wchar_t* fontFamilyName, DWRITE_FONT_WEIGHT fontWeight, DWRITE_FONT_STYLE fontStyle, float fontSize, const wchar_t* localeName) const;
u16x2 _allocateAtlasCell() noexcept;
void _drawGlyph(const til::pair<glyph_entry, std::array<u16x2, 2>>& pair) const;
void _drawCursor() const;
void _copyScratchpadCell(uint32_t scratchpadIndex, u16x2 target, uint32_t copyFlags = 0) const;
template<typename T1, typename T2>
cell* _getCell(T1 x, T2 y) noexcept
{
return _r.cells.data() + static_cast<size_t>(_api.cellCount.x) * y + x;
}
struct static_resources
{
wil::com_ptr<ID2D1Factory> d2dFactory;
wil::com_ptr<IDWriteFactory> dwriteFactory;
bool isWindows10OrGreater = true;
} _sr;
struct resources
{
// D3D resources
wil::com_ptr<ID3D11Device> device;
wil::com_ptr<ID3D11DeviceContext1> deviceContext;
wil::com_ptr<IDXGISwapChain1> swapChain;
wil::unique_handle swapChainHandle;
wil::unique_handle frameLatencyWaitableObject;
wil::com_ptr<ID3D11RenderTargetView> renderTargetView;
wil::com_ptr<ID3D11VertexShader> vertexShader;
wil::com_ptr<ID3D11PixelShader> pixelShader;
wil::com_ptr<ID3D11Buffer> constantBuffer;
wil::com_ptr<ID3D11Buffer> cellBuffer;
wil::com_ptr<ID3D11ShaderResourceView> cellView;
// D2D resources
wil::com_ptr<ID3D11Texture2D> glyphBuffer;
wil::com_ptr<ID3D11ShaderResourceView> glyphView;
wil::com_ptr<ID3D11Texture2D> glyphScratchpad;
wil::com_ptr<ID2D1RenderTarget> d2dRenderTarget;
wil::com_ptr<ID2D1Brush> brush;
wil::com_ptr<IDWriteTextFormat> textFormats[2][2];
// Resources dependent on _api.sizeInPixel
aligned_buffer<cell> cells;
// Resources dependent on _api.cellSize
robin_hood::unordered_flat_map<glyph_entry, std::array<u16x2, 2>, glyph_entry_hasher> glyphs;
std::vector<til::pair<glyph_entry, std::array<u16x2, 2>>> glyphQueue;
u16x2 atlasSizeInPixel;
u16x2 atlasPosition;
} _r;
struct api_state
{
f32x2 cellSizeDIP; // invalidation_flags::font
u16x2 cellSize; // invalidation_flags::size
u16x2 cellCount; // caches `sizeInPixel / cellSize`
u16x2 sizeInPixel; // invalidation_flags::size
std::wstring fontName; // invalidation_flags::font|size
u16 fontSize = 0; // invalidation_flags::font|size
u16 fontWeight = DWRITE_FONT_WEIGHT_NORMAL; // invalidation_flags::font
u16 dpi = USER_DEFAULT_SCREEN_DPI; // invalidation_flags::font|size
u16 antialiasingMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE; // invalidation_flags::font
std::function<void()> swapChainChangedCallback;
HWND hwnd = nullptr;
} _api;
struct render_api_state
{
til::rectangle dirtyArea;
u32x2 currentColor{};
glyph_entry attributes{};
u32 backgroundColor = ~u32(0);
u32 selectionColor = 0x7fffffff;
} _rapi;
invalidation_flags _invalidations = invalidation_flags::device;
};
}