mirror of
https://github.com/aphrody-labs/terminal.git
synced 2026-10-13 22:05:20 +00:00
feat(atlas): BackendD3D12 natif (atlas DirectWrite/D2D via D3D11On12, swap chain flip à file D3D12, 3 trames en vol, anneau d'upload, root signatures et PSO précompilés, shaders personnalisés multi-passes t0-t3 et entrées temps/curseur, repli D3D11) et paramètre direct3d12 (schéma, UI en-US/fr-FR, test de sérialisation)
This commit is contained in:
1 parent
9566761aa6
commit
2ab6d30aba
18 files changed
+1762
-19
No files matched your search
@@ -2507,12 +2507,13 @@
|
||||
"type": "array"
|
||||
},
|
||||
"rendering.graphicsAPI": {
|
||||
"description": "Direct3D 11 provides a more performant and feature-rich experience, whereas Direct2D is more stable. The default option \"Automatic\" will pick the API that best fits your graphics hardware. If you experience significant issues, consider using Direct2D.",
|
||||
"description": "Direct3D 11 provides a more performant and feature-rich experience, whereas Direct2D is more stable. Direct3D 12 renders like Direct3D 11 and additionally supports multi-pass custom shaders. The default option \"Automatic\" will pick the API that best fits your graphics hardware. If you experience significant issues, consider using Direct2D.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"automatic",
|
||||
"direct2d",
|
||||
"direct3d11"
|
||||
"direct3d11",
|
||||
"direct3d12"
|
||||
]
|
||||
},
|
||||
"rendering.disablePartialInvalidation": {
|
||||
|
||||
@@ -305,7 +305,7 @@
|
||||
<comment>This text is shown next to a list of choices.</comment>
|
||||
</data>
|
||||
<data name="Globals_GraphicsAPI.Description" xml:space="preserve">
|
||||
<value>Direct3D 11 provides a more performant and feature-rich experience, whereas Direct2D is more stable. The default option "Automatic" will pick the API that best fits your graphics hardware. If you experience significant issues, consider using Direct2D.</value>
|
||||
<value>Direct3D 11 provides a more performant and feature-rich experience, whereas Direct2D is more stable. Direct3D 12 renders like Direct3D 11 and additionally supports multi-pass custom shaders. The default option "Automatic" will pick the API that best fits your graphics hardware. If you experience significant issues, consider using Direct2D.</value>
|
||||
</data>
|
||||
<data name="Globals_GraphicsAPI_Automatic.Text" xml:space="preserve">
|
||||
<value>Automatic</value>
|
||||
@@ -317,6 +317,9 @@
|
||||
<data name="Globals_GraphicsAPI_Direct3d11.Text" xml:space="preserve">
|
||||
<value>Direct3D 11</value>
|
||||
</data>
|
||||
<data name="Globals_GraphicsAPI_Direct3d12.Text" xml:space="preserve">
|
||||
<value>Direct3D 12</value>
|
||||
</data>
|
||||
<data name="Globals_DisablePartialInvalidation.Header" xml:space="preserve">
|
||||
<value>Disable partial Swap Chain invalidation</value>
|
||||
<comment>"Swap Chain" is an official technical term by Microsoft. This text is shown next to a toggle.</comment>
|
||||
|
||||
@@ -305,7 +305,7 @@
|
||||
<comment>This text is shown next to a list of choices.</comment>
|
||||
</data>
|
||||
<data name="Globals_GraphicsAPI.Description" xml:space="preserve">
|
||||
<value>Direct3D 11 offre une expérience plus performante et riche en fonctionnalités, tandis que Direct2D est plus stable. L’option par défaut « Automatique » sélectionne l’API qui correspond le mieux à votre matériel graphique. Si vous rencontrez des problèmes importants, envisagez d’utiliser Direct2D.</value>
|
||||
<value>Direct3D 11 offre une expérience plus performante et riche en fonctionnalités, tandis que Direct2D est plus stable. Direct3D 12 offre le même rendu que Direct3D 11 et prend en charge en plus les nuanceurs personnalisés à plusieurs passes. L’option par défaut « Automatique » sélectionne l’API qui correspond le mieux à votre matériel graphique. Si vous rencontrez des problèmes importants, envisagez d’utiliser Direct2D.</value>
|
||||
</data>
|
||||
<data name="Globals_GraphicsAPI_Automatic.Text" xml:space="preserve">
|
||||
<value>Automatique</value>
|
||||
@@ -317,6 +317,9 @@
|
||||
<data name="Globals_GraphicsAPI_Direct3d11.Text" xml:space="preserve">
|
||||
<value>Direct3D 11</value>
|
||||
</data>
|
||||
<data name="Globals_GraphicsAPI_Direct3d12.Text" xml:space="preserve">
|
||||
<value>Direct3D 12</value>
|
||||
</data>
|
||||
<data name="Globals_DisablePartialInvalidation.Header" xml:space="preserve">
|
||||
<value>Désactiver l’invalidation partielle de la chaîne d’échange</value>
|
||||
<comment>"Swap Chain" is an official technical term by Microsoft. This text is shown next to a toggle.</comment>
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace SettingsModelUnitTests
|
||||
TEST_CLASS(SerializationTests);
|
||||
|
||||
TEST_METHOD(GlobalSettings);
|
||||
TEST_METHOD(GraphicsAPI);
|
||||
TEST_METHOD(Profile);
|
||||
TEST_METHOD(ColorScheme);
|
||||
TEST_METHOD(Actions);
|
||||
@@ -150,6 +151,31 @@ namespace SettingsModelUnitTests
|
||||
RoundtripTest<implementation::GlobalAppSettings>(smallGlobalsString);
|
||||
}
|
||||
|
||||
void SerializationTests::GraphicsAPI()
|
||||
{
|
||||
static constexpr std::pair<std::string_view, winrt::Microsoft::Terminal::Control::GraphicsAPI> cases[]{
|
||||
{ R"({ "rendering.graphicsAPI": "automatic", "actions": [], "keybindings": [] })", winrt::Microsoft::Terminal::Control::GraphicsAPI::Automatic },
|
||||
{ R"({ "rendering.graphicsAPI": "direct2d", "actions": [], "keybindings": [] })", winrt::Microsoft::Terminal::Control::GraphicsAPI::Direct2D },
|
||||
{ R"({ "rendering.graphicsAPI": "direct3d11", "actions": [], "keybindings": [] })", winrt::Microsoft::Terminal::Control::GraphicsAPI::Direct3D11 },
|
||||
{ R"({ "rendering.graphicsAPI": "direct3d12", "actions": [], "keybindings": [] })", winrt::Microsoft::Terminal::Control::GraphicsAPI::Direct3D12 },
|
||||
};
|
||||
|
||||
for (const auto& [jsonString, expected] : cases)
|
||||
{
|
||||
RoundtripTest<implementation::GlobalAppSettings>(jsonString);
|
||||
|
||||
const auto settings = implementation::GlobalAppSettings::FromJson(VerifyParseSucceeded(jsonString));
|
||||
VERIFY_IS_TRUE(settings->HasGraphicsAPI());
|
||||
VERIFY_ARE_EQUAL(expected, settings->GraphicsAPI());
|
||||
}
|
||||
|
||||
// Setting the value from code (Settings UI) writes the JSON name back out.
|
||||
const auto settings = implementation::GlobalAppSettings::FromJson(VerifyParseSucceeded(R"({ "actions": [], "keybindings": [] })"));
|
||||
settings->GraphicsAPI(winrt::Microsoft::Terminal::Control::GraphicsAPI::Direct3D12);
|
||||
const auto result = settings->ToJson();
|
||||
VERIFY_ARE_EQUAL("direct3d12", result["rendering.graphicsAPI"].asString());
|
||||
}
|
||||
|
||||
void SerializationTests::Profile()
|
||||
{
|
||||
static constexpr std::string_view profileString{ R"(
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "BackendD2D.h"
|
||||
#include "BackendD3D.h"
|
||||
#include "BackendD3D12.h"
|
||||
|
||||
// #### NOTE ####
|
||||
// If you see any code in here that contains "_api." you might be seeing a race condition.
|
||||
@@ -173,9 +174,31 @@ void AtlasEngine::_recreateBackend()
|
||||
// D3D11 defers the destruction of objects and only one swap chain can be associated with a
|
||||
// HWND, IWindow, or composition surface at a time. --> Destroy it while we still have the old device.
|
||||
_destroySwapChain();
|
||||
_p.d3d12 = {};
|
||||
|
||||
auto graphicsAPI = _p.s->target->graphicsAPI;
|
||||
|
||||
if (graphicsAPI == GraphicsAPI::Direct3D12)
|
||||
{
|
||||
try
|
||||
{
|
||||
BackendD3D12::CreateDevice(_p);
|
||||
_p.device = nullptr;
|
||||
_p.deviceContext = nullptr;
|
||||
_b = std::make_unique<BackendD3D12>(_p);
|
||||
_p.MarkAllAsDirty();
|
||||
return;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Direct3D 12 is unavailable (or broken) on this adapter. Fall back to Direct3D 11.
|
||||
LOG_CAUGHT_EXCEPTION();
|
||||
_b.reset();
|
||||
_p.d3d12 = {};
|
||||
graphicsAPI = GraphicsAPI::Direct3D11;
|
||||
}
|
||||
}
|
||||
|
||||
auto deviceFlags =
|
||||
D3D11_CREATE_DEVICE_SINGLETHREADED
|
||||
#ifndef NDEBUG
|
||||
@@ -358,11 +381,13 @@ bool AtlasEngine::_createSwapChain(HANDLE shutdownEvent)
|
||||
|
||||
wil::com_ptr<IDXGISwapChain1> swapChain1;
|
||||
wil::unique_handle handle;
|
||||
// Direct3D 12 swap chains are created on the command queue instead of the device.
|
||||
IUnknown* const swapChainDevice = _p.d3d12.queue ? static_cast<IUnknown*>(_p.d3d12.queue.get()) : static_cast<IUnknown*>(_p.device.get());
|
||||
|
||||
if (_p.s->target->hwnd)
|
||||
{
|
||||
desc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
|
||||
THROW_IF_FAILED(_p.dxgi.factory->CreateSwapChainForHwnd(_p.device.get(), _p.s->target->hwnd, &desc, nullptr, nullptr, swapChain1.addressof()));
|
||||
THROW_IF_FAILED(_p.dxgi.factory->CreateSwapChainForHwnd(swapChainDevice, _p.s->target->hwnd, &desc, nullptr, nullptr, swapChain1.addressof()));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -373,7 +398,7 @@ bool AtlasEngine::_createSwapChain(HANDLE shutdownEvent)
|
||||
// As per: https://docs.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-dcompositioncreatesurfacehandle
|
||||
static constexpr DWORD COMPOSITIONSURFACE_ALL_ACCESS = 0x0003L;
|
||||
THROW_IF_FAILED(DCompositionCreateSurfaceHandle(COMPOSITIONSURFACE_ALL_ACCESS, nullptr, handle.addressof()));
|
||||
THROW_IF_FAILED(_p.dxgi.factory.query<IDXGIFactoryMedia>()->CreateSwapChainForCompositionSurfaceHandle(_p.device.get(), handle.get(), &desc, nullptr, swapChain1.addressof()));
|
||||
THROW_IF_FAILED(_p.dxgi.factory.query<IDXGIFactoryMedia>()->CreateSwapChainForCompositionSurfaceHandle(swapChainDevice, handle.get(), &desc, nullptr, swapChain1.addressof()));
|
||||
}
|
||||
|
||||
_p.swapChain.swapChain = swapChain1.query<IDXGISwapChain2>();
|
||||
@@ -424,7 +449,10 @@ void AtlasEngine::_destroySwapChain()
|
||||
void AtlasEngine::_resizeBuffers()
|
||||
{
|
||||
_b->ReleaseResources();
|
||||
_p.deviceContext->ClearState();
|
||||
if (_p.deviceContext)
|
||||
{
|
||||
_p.deviceContext->ClearState();
|
||||
}
|
||||
|
||||
THROW_IF_FAILED(_p.swapChain.swapChain->ResizeBuffers(0, _p.s->targetSize.x, _p.s->targetSize.y, DXGI_FORMAT_UNKNOWN, swapChainFlags));
|
||||
_p.swapChain.targetSize = _p.s->targetSize;
|
||||
@@ -496,7 +524,11 @@ void AtlasEngine::_present()
|
||||
{
|
||||
// Render() has already issued GPU commands. If we don't Present() now, the GPU's
|
||||
// command queue would just fill up and effectively leak memory. Flush() it.
|
||||
_p.deviceContext->Flush();
|
||||
// BackendD3D12 submits its command lists itself.
|
||||
if (_p.deviceContext)
|
||||
{
|
||||
_p.deviceContext->Flush();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,176 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "BackendD3D.h"
|
||||
|
||||
namespace Microsoft::Console::Render::Atlas
|
||||
{
|
||||
// A Direct3D 12 port of BackendD3D. The CPU side (text shaping results -> quads, glyph atlas packing,
|
||||
// cursor, selection, gridlines, bitmaps) is inherited unchanged. Glyphs are still rasterized by Direct2D,
|
||||
// which writes into the D3D12 glyph atlas through a D3D11On12 device sharing our command queue.
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4324) // 'BackendD3D12': structure was padded due to alignment specifier
|
||||
struct BackendD3D12 final : BackendD3D
|
||||
{
|
||||
// Creates the ID3D12Device and its direct command queue in p.d3d12. Throws if Direct3D 12 is unavailable.
|
||||
static void CreateDevice(RenderingPayload& p);
|
||||
|
||||
BackendD3D12(const RenderingPayload& p);
|
||||
~BackendD3D12() override;
|
||||
|
||||
void ReleaseResources() noexcept override;
|
||||
void Render(RenderingPayload& payload) override;
|
||||
|
||||
// The first 32 bytes are identical to BackendD3D::CustomConstBuffer, so shaders written for
|
||||
// Direct3D 11 work unchanged. See README.md ("Custom shaders with Direct3D 12") for the contract.
|
||||
// WARNING: The same packing rules as for VSConstBuffer apply.
|
||||
struct alignas(16) CustomConstBuffer12
|
||||
{
|
||||
alignas(sizeof(f32)) f32 time = 0;
|
||||
alignas(sizeof(f32)) f32 scale = 0;
|
||||
alignas(sizeof(f32x2)) f32x2 resolution;
|
||||
alignas(sizeof(f32x4)) f32x4 background;
|
||||
alignas(sizeof(f32x4)) f32x4 cursorRect;
|
||||
alignas(sizeof(f32x4)) f32x4 cursorColor;
|
||||
alignas(sizeof(f32x2)) f32x2 targetSize;
|
||||
alignas(sizeof(f32)) f32 deltaTime = 0;
|
||||
alignas(sizeof(u32)) u32 frameCount = 0;
|
||||
alignas(sizeof(f32)) f32 cursorTime = 0;
|
||||
alignas(sizeof(u32)) u32 passIndex = 0;
|
||||
alignas(sizeof(u32)) u32 passCount = 0;
|
||||
#pragma warning(suppress : 4324) // 'CustomConstBuffer12': structure was padded due to alignment specifier
|
||||
};
|
||||
|
||||
private:
|
||||
static constexpr u32 FrameCount = 3;
|
||||
static constexpr u32 MaxBackBuffers = 8;
|
||||
static constexpr u32 MaxCustomPasses = 8;
|
||||
|
||||
// Shader visible CBV/SRV/UAV heap layout.
|
||||
static constexpr u32 SrvBackground = 0;
|
||||
static constexpr u32 SrvGlyphAtlas = 1;
|
||||
static constexpr u32 SrvCustomFirst = 2; // + 4 descriptors (t0-t3) per custom shader pass
|
||||
static constexpr u32 SrvCount = SrvCustomFirst + 4 * MaxCustomPasses;
|
||||
|
||||
// RTV heap layout.
|
||||
static constexpr u32 RtvBackBufferFirst = 0;
|
||||
static constexpr u32 RtvCustomOffscreen = MaxBackBuffers;
|
||||
static constexpr u32 RtvCustomPingPong = MaxBackBuffers + 1; // + 2
|
||||
static constexpr u32 RtvCount = MaxBackBuffers + 3;
|
||||
|
||||
struct FrameContext
|
||||
{
|
||||
wil::com_ptr<ID3D12CommandAllocator> allocator;
|
||||
u64 fenceValue = 0;
|
||||
u64 ringEnd = 0;
|
||||
};
|
||||
|
||||
struct RingAllocation
|
||||
{
|
||||
u8* cpu = nullptr;
|
||||
D3D12_GPU_VIRTUAL_ADDRESS gpu = 0;
|
||||
u64 offset = 0; // relative to the start of _ring
|
||||
};
|
||||
|
||||
void _d2dBeginDrawing() override;
|
||||
void _d2dEndDrawing() override;
|
||||
void _resizeGlyphAtlas(const RenderingPayload& p, u16 u, u16 v) override;
|
||||
void _flushQuads(const RenderingPayload& p) override;
|
||||
void _uploadBackgroundBitmap(const RenderingPayload& p) override;
|
||||
|
||||
ATLAS_ATTR_COLD void _handleSettingsUpdate12(const RenderingPayload& p);
|
||||
ATLAS_ATTR_COLD void _createBackBuffers(const RenderingPayload& p);
|
||||
ATLAS_ATTR_COLD void _recreateBackgroundBitmap12(const RenderingPayload& p);
|
||||
ATLAS_ATTR_COLD void _recreateConstants(const RenderingPayload& p);
|
||||
ATLAS_ATTR_COLD void _recreateCustomShader12(const RenderingPayload& p);
|
||||
ATLAS_ATTR_COLD void _recreateCustomTargets(const RenderingPayload& p);
|
||||
ATLAS_ATTR_COLD void _releaseCustomTargets() noexcept;
|
||||
ATLAS_ATTR_COLD wil::com_ptr<ID3D12Resource> _uploadImage(const RenderingPayload& p, const wchar_t* path);
|
||||
void _executeCustomShader12(RenderingPayload& p);
|
||||
|
||||
void _beginFrame();
|
||||
void _endFrame();
|
||||
void _setCommandListState();
|
||||
void _submit(bool reopen);
|
||||
void _waitForFence(u64 value);
|
||||
void _waitForGpuIdle();
|
||||
void _waitForGpuIdleNoThrow() noexcept;
|
||||
RingAllocation _ringAlloc(u64 size, u64 alignment);
|
||||
ATLAS_ATTR_COLD void _growRing(u64 minSize);
|
||||
void _transition(ID3D12Resource* resource, D3D12_RESOURCE_STATES before, D3D12_RESOURCE_STATES after) const noexcept;
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE _srvCpu(u32 index) const noexcept;
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE _srvGpu(u32 index) const noexcept;
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE _rtvCpu(u32 index) const noexcept;
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE _mainTargetRtv() const noexcept;
|
||||
ATLAS_ATTR_COLD wil::com_ptr<ID3D12Resource> _createTexture(u32 width, u32 height, DXGI_FORMAT format, D3D12_RESOURCE_FLAGS flags, D3D12_RESOURCE_STATES state) const;
|
||||
ATLAS_ATTR_COLD void _writeSrv(ID3D12Resource* resource, DXGI_FORMAT format, u32 index) const noexcept;
|
||||
|
||||
// Device
|
||||
wil::com_ptr<ID3D12Device> _device;
|
||||
wil::com_ptr<ID3D12CommandQueue> _queue;
|
||||
wil::com_ptr<ID3D12GraphicsCommandList> _commandList;
|
||||
wil::com_ptr<ID3D12CommandAllocator> _immediateAllocator;
|
||||
wil::com_ptr<ID3D12Fence> _fence;
|
||||
wil::unique_event _fenceEvent;
|
||||
u64 _fenceValue = 0;
|
||||
FrameContext _frames[FrameCount];
|
||||
u32 _frameIndex = 0;
|
||||
bool _commandListOpen = false;
|
||||
bool _atlasReadsPending = false;
|
||||
|
||||
// Direct2D interop
|
||||
wil::com_ptr<ID3D11Device> _d3d11Device;
|
||||
wil::com_ptr<ID3D11DeviceContext> _d3d11Context;
|
||||
wil::com_ptr<ID3D11On12Device> _d3d11On12;
|
||||
wil::com_ptr<ID3D11Resource> _glyphAtlasWrapped;
|
||||
|
||||
// Descriptors
|
||||
wil::com_ptr<ID3D12DescriptorHeap> _srvHeap;
|
||||
wil::com_ptr<ID3D12DescriptorHeap> _rtvHeap;
|
||||
u32 _srvDescriptorSize = 0;
|
||||
u32 _rtvDescriptorSize = 0;
|
||||
|
||||
// Main pipeline
|
||||
wil::com_ptr<ID3D12RootSignature> _rootSignature;
|
||||
wil::com_ptr<ID3D12PipelineState> _pipelineState;
|
||||
wil::com_ptr<ID3D12Resource> _staticBuffer; // vertices + indices
|
||||
D3D12_VERTEX_BUFFER_VIEW _vertexBufferView{};
|
||||
D3D12_INDEX_BUFFER_VIEW _indexBufferView{};
|
||||
VSConstBuffer _vsConstants{};
|
||||
PSConstBuffer _psConstants{};
|
||||
|
||||
// Upload ring buffer (persistently mapped)
|
||||
wil::com_ptr<ID3D12Resource> _ring;
|
||||
u8* _ringData = nullptr;
|
||||
D3D12_GPU_VIRTUAL_ADDRESS _ringGpu = 0;
|
||||
u64 _ringSize = 0;
|
||||
u64 _ringHead = 0; // monotonic
|
||||
u64 _ringTail = 0; // monotonic
|
||||
|
||||
// Swap chain
|
||||
wil::com_ptr<IDXGISwapChain3> _swapChain3;
|
||||
wil::com_ptr<ID3D12Resource> _backBuffers[MaxBackBuffers];
|
||||
u32 _backBufferCount = 0;
|
||||
u32 _backBufferIndex = 0;
|
||||
|
||||
// Textures
|
||||
wil::com_ptr<ID3D12Resource> _glyphAtlas12;
|
||||
wil::com_ptr<ID3D12Resource> _backgroundBitmap12;
|
||||
|
||||
// Custom shaders
|
||||
wil::com_ptr<ID3D12RootSignature> _customRootSignature;
|
||||
std::vector<wil::com_ptr<ID3D12PipelineState>> _customPasses;
|
||||
wil::com_ptr<ID3D12Resource> _customOffscreen;
|
||||
wil::com_ptr<ID3D12Resource> _customPingPong[2];
|
||||
wil::com_ptr<ID3D12Resource> _customHistory;
|
||||
wil::com_ptr<ID3D12Resource> _customImage;
|
||||
bool _customUsesHistory = false;
|
||||
u64 _customLastFrameTime = 0;
|
||||
u64 _customCursorChangeTime = 0;
|
||||
til::rect _customLastCursorRect;
|
||||
u32 _customFrameCount = 0;
|
||||
};
|
||||
#pragma warning(pop)
|
||||
}
|
||||
@@ -113,3 +113,73 @@ graph TD
|
||||
graph TD
|
||||
Render --> _executeCustomShader
|
||||
```
|
||||
|
||||
## BackendD3D12
|
||||
|
||||
`"rendering.graphicsAPI": "direct3d12"` selects `BackendD3D12`. `automatic` never selects it: it isn't faster than `BackendD3D` on the hardware it was measured on (see `tools/aphrody/bench-render.md`).
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
AtlasEngine.r.cpp -->|CreateDevice, falls back to D3D11 on failure| BackendD3D12.cpp
|
||||
BackendD3D12.cpp -.->|inherits the CPU side:<br>quads, glyph atlas packing, cursor,<br>selection, gridlines, bitmaps| BackendD3D.cpp
|
||||
BackendD3D12.cpp -->|D3D11On12, glyph atlas only| Direct2D
|
||||
```
|
||||
|
||||
* `BackendD3D12` derives from `BackendD3D` and overrides the parts that touch the GPU: `_flushQuads`, `_uploadBackgroundBitmap`, `_resizeGlyphAtlas` and `_d2dBeginDrawing`/`_d2dEndDrawing`. Everything that turns rows into quads is shared, so both backends render identically.
|
||||
* `d3d12.dll` is loaded at runtime. If device creation fails, `AtlasEngine` logs the error and uses Direct3D 11.
|
||||
* The swap chain is created on the direct command queue. It keeps the flip model, the frame latency waitable object and the `Present1` dirty rects of `AtlasEngine::_present`.
|
||||
* 3 frames in flight, a single fence. Instances, the background bitmap and the custom shader constants are written into a persistently mapped upload ring buffer, which grows when a single frame doesn't fit.
|
||||
* Root signatures are written in HLSL (`shader_d3d12_rootsig.hlsl`) and embedded in the `*_d3d12.hlsl` vertex shaders, which wrap the regular shaders and are compiled for shader model 5.1. The pipeline states are created once.
|
||||
* Direct2D can't draw into D3D12 resources, so the glyph atlas is a D3D12 texture wrapped by a D3D11On12 device that submits to the same queue. Draw calls recorded before Direct2D writes to the atlas again are submitted first, because they read its previous contents.
|
||||
|
||||
### Custom shaders with Direct3D 12
|
||||
|
||||
Shaders written for Direct3D 11 work unchanged. They're compiled for `ps_5_1`, and the following is available on top:
|
||||
|
||||
* **Multiple passes.** Optional entry points `pass1` to `pass7` run in that order before `main`. Intermediate passes render into `R16G16B16A16_FLOAT` targets (the size of the swap chain), `main` renders into the swap chain.
|
||||
* **Textures.**
|
||||
* `t0`: output of the previous pass. For the first pass that's the terminal contents (same as with Direct3D 11).
|
||||
* `t1`: `experimental.pixelShaderImagePath` (same as with Direct3D 11).
|
||||
* `t2`: the terminal contents, in every pass.
|
||||
* `t3`: the final output of the previous frame, for trails or decay effects. Using it turns on continuous redraw.
|
||||
* **Samplers.**
|
||||
* `s0`: linear, border, transparent black (same as with Direct3D 11).
|
||||
* `s1`: point, clamp.
|
||||
* `s2`: linear, clamp.
|
||||
* `s3`: linear, wrap.
|
||||
* **Constant buffer `b0`.** The first 32 bytes are the Direct3D 11 layout.
|
||||
|
||||
| Offset | Type | Name | Meaning |
|
||||
|-------:|----------|---------------|--------------------------------------------------------------|
|
||||
| 0 | `float` | `time` | seconds, wraps around after 1000 s |
|
||||
| 4 | `float` | `scale` | DPI / 96 |
|
||||
| 8 | `float2` | `resolution` | size of the text area in pixels |
|
||||
| 16 | `float4` | `background` | premultiplied background color |
|
||||
| 32 | `float4` | `cursorRect` | cursor left, top, right, bottom in pixels (0 when hidden) |
|
||||
| 48 | `float4` | `cursorColor` | cursor color |
|
||||
| 64 | `float2` | `targetSize` | size of the swap chain in pixels |
|
||||
| 72 | `float` | `deltaTime` | seconds since the previous frame (at most 1) |
|
||||
| 76 | `uint` | `frameCount` | frames drawn since the shader was loaded |
|
||||
| 80 | `float` | `cursorTime` | seconds since the cursor last moved (at most 1000) |
|
||||
| 84 | `uint` | `passIndex` | index of the running pass (`main` is `passCount - 1`) |
|
||||
| 88 | `uint` | `passCount` | number of passes |
|
||||
|
||||
```hlsl
|
||||
cbuffer PixelShaderSettings : register(b0)
|
||||
{
|
||||
float time;
|
||||
float scale;
|
||||
float2 resolution;
|
||||
float4 background;
|
||||
float4 cursorRect;
|
||||
float4 cursorColor;
|
||||
float2 targetSize;
|
||||
float deltaTime;
|
||||
uint frameCount;
|
||||
float cursorTime;
|
||||
uint passIndex;
|
||||
uint passCount;
|
||||
};
|
||||
```
|
||||
* **Continuous redraw** is enabled when any pass uses `time`, `deltaTime`, `frameCount`, `cursorTime` or `t3`, or when the shader can't be reflected. Otherwise the terminal is only redrawn when its contents change.
|
||||
* Binding anything outside of `b0`, `t0`-`t3` and `s0`-`s3` makes pipeline creation fail. The error is reported like a compilation error.
|
||||
@@ -17,6 +17,7 @@
|
||||
<ClCompile Include="Backend.cpp" />
|
||||
<ClCompile Include="BackendD2D.cpp" />
|
||||
<ClCompile Include="BackendD3D.cpp" />
|
||||
<ClCompile Include="BackendD3D12.cpp" />
|
||||
<ClCompile Include="BuiltinGlyphs.cpp" />
|
||||
<ClCompile Include="dwrite_helpers.cpp" />
|
||||
<ClCompile Include="DWriteTextAnalysis.cpp" />
|
||||
@@ -31,6 +32,7 @@
|
||||
<ClInclude Include="Backend.h" />
|
||||
<ClInclude Include="BackendD2D.h" />
|
||||
<ClInclude Include="BackendD3D.h" />
|
||||
<ClInclude Include="BackendD3D12.h" />
|
||||
<ClInclude Include="BuiltinGlyphs.h" />
|
||||
<ClInclude Include="common.h" />
|
||||
<ClInclude Include="dwrite_helpers.h" />
|
||||
@@ -45,18 +47,37 @@
|
||||
<FxCompile Include="custom_shader_vs.hlsl">
|
||||
<ShaderType>Vertex</ShaderType>
|
||||
</FxCompile>
|
||||
<FxCompile Include="custom_shader_ps_d3d12.hlsl">
|
||||
<ShaderType>Pixel</ShaderType>
|
||||
<ShaderModel>5.1</ShaderModel>
|
||||
</FxCompile>
|
||||
<FxCompile Include="custom_shader_vs_d3d12.hlsl">
|
||||
<ShaderType>Vertex</ShaderType>
|
||||
<ShaderModel>5.1</ShaderModel>
|
||||
</FxCompile>
|
||||
<FxCompile Include="dwrite_helpers.hlsl">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</FxCompile>
|
||||
<FxCompile Include="shader_common.hlsl">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</FxCompile>
|
||||
<FxCompile Include="shader_d3d12_rootsig.hlsl">
|
||||
<ExcludedFromBuild>true</ExcludedFromBuild>
|
||||
</FxCompile>
|
||||
<FxCompile Include="shader_ps.hlsl">
|
||||
<ShaderType>Pixel</ShaderType>
|
||||
</FxCompile>
|
||||
<FxCompile Include="shader_ps_d3d12.hlsl">
|
||||
<ShaderType>Pixel</ShaderType>
|
||||
<ShaderModel>5.1</ShaderModel>
|
||||
</FxCompile>
|
||||
<FxCompile Include="shader_vs.hlsl">
|
||||
<ShaderType>Vertex</ShaderType>
|
||||
</FxCompile>
|
||||
<FxCompile Include="shader_vs_d3d12.hlsl">
|
||||
<ShaderType>Vertex</ShaderType>
|
||||
<ShaderModel>5.1</ShaderModel>
|
||||
</FxCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="README.md" />
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// Same retro pixel shader as BackendD3D, compiled for shader model 5.1.
|
||||
#include "custom_shader_ps.hlsl"
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "shader_d3d12_rootsig.hlsl"
|
||||
|
||||
#define main atlas_custom_vs_main
|
||||
#include "custom_shader_vs.hlsl"
|
||||
#undef main
|
||||
|
||||
// clang-format off
|
||||
[RootSignature(ATLAS_CUSTOM_ROOT_SIGNATURE)]
|
||||
VS_OUTPUT main(uint id : SV_VERTEXID)
|
||||
// clang-format on
|
||||
{
|
||||
return atlas_custom_vs_main(id);
|
||||
}
|
||||
@@ -19,3 +19,21 @@ $(OBJ_PATH)\$(O)\custom_shader_ps.h: custom_shader_ps.hlsl
|
||||
$(OBJ_PATH)\$(O)\shader_ps.h: shader_ps.hlsl
|
||||
@$(ECHO) Compiling shader $(*F)...
|
||||
$(FXC) $(FXC_FLAGS) /Tps_$(FXC_PROFILE_VERSION) /Fh"$@" /Vn$(*B) $?
|
||||
|
||||
# Direct3D 12 variants (BackendD3D12): shader model 5.1 with embedded root signatures
|
||||
|
||||
$(OBJ_PATH)\$(O)\custom_shader_vs_d3d12.h: custom_shader_vs_d3d12.hlsl custom_shader_vs.hlsl shader_d3d12_rootsig.hlsl
|
||||
@$(ECHO) Compiling shader $(*F)...
|
||||
$(FXC) $(FXC_FLAGS) /Tvs_5_1 /Fh"$@" /Vn$(*B) custom_shader_vs_d3d12.hlsl
|
||||
|
||||
$(OBJ_PATH)\$(O)\shader_vs_d3d12.h: shader_vs_d3d12.hlsl shader_vs.hlsl shader_d3d12_rootsig.hlsl
|
||||
@$(ECHO) Compiling shader $(*F)...
|
||||
$(FXC) $(FXC_FLAGS) /Tvs_5_1 /Fh"$@" /Vn$(*B) shader_vs_d3d12.hlsl
|
||||
|
||||
$(OBJ_PATH)\$(O)\custom_shader_ps_d3d12.h: custom_shader_ps_d3d12.hlsl custom_shader_ps.hlsl
|
||||
@$(ECHO) Compiling shader $(*F)...
|
||||
$(FXC) $(FXC_FLAGS) /Tps_5_1 /Fh"$@" /Vn$(*B) custom_shader_ps_d3d12.hlsl
|
||||
|
||||
$(OBJ_PATH)\$(O)\shader_ps_d3d12.h: shader_ps_d3d12.hlsl shader_ps.hlsl
|
||||
@$(ECHO) Compiling shader $(*F)...
|
||||
$(FXC) $(FXC_FLAGS) /Tps_5_1 /Fh"$@" /Vn$(*B) shader_ps_d3d12.hlsl
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// Root signatures of BackendD3D12. They're embedded into the vertex shaders (shader_vs_d3d12.hlsl and
|
||||
// custom_shader_vs_d3d12.hlsl) and the C++ side creates them straight from that bytecode.
|
||||
|
||||
// b0 (VS) = VSConstBuffer, b0 (PS) = PSConstBuffer, t0 = background bitmap, t1 = glyph atlas.
|
||||
#define ATLAS_MAIN_ROOT_SIGNATURE \
|
||||
"RootFlags(ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT | DENY_HULL_SHADER_ROOT_ACCESS | DENY_DOMAIN_SHADER_ROOT_ACCESS | DENY_GEOMETRY_SHADER_ROOT_ACCESS)," \
|
||||
"RootConstants(num32BitConstants=4, b0, visibility=SHADER_VISIBILITY_VERTEX)," \
|
||||
"RootConstants(num32BitConstants=20, b0, visibility=SHADER_VISIBILITY_PIXEL)," \
|
||||
"DescriptorTable(SRV(t0, numDescriptors=2, flags=DESCRIPTORS_VOLATILE | DATA_VOLATILE), visibility=SHADER_VISIBILITY_PIXEL)"
|
||||
|
||||
// b0 = CustomConstBuffer12, t0 = previous pass (or the terminal), t1 = experimental.pixelShaderImagePath,
|
||||
// t2 = the terminal, t3 = the previous frame. s0 matches the sampler of BackendD3D, s1-s3 are extras.
|
||||
#define ATLAS_CUSTOM_ROOT_SIGNATURE \
|
||||
"RootFlags(DENY_HULL_SHADER_ROOT_ACCESS | DENY_DOMAIN_SHADER_ROOT_ACCESS | DENY_GEOMETRY_SHADER_ROOT_ACCESS)," \
|
||||
"CBV(b0, visibility=SHADER_VISIBILITY_PIXEL)," \
|
||||
"DescriptorTable(SRV(t0, numDescriptors=4, flags=DESCRIPTORS_VOLATILE | DATA_VOLATILE), visibility=SHADER_VISIBILITY_PIXEL)," \
|
||||
"StaticSampler(s0, filter=FILTER_MIN_MAG_MIP_LINEAR, addressU=TEXTURE_ADDRESS_BORDER, addressV=TEXTURE_ADDRESS_BORDER, addressW=TEXTURE_ADDRESS_BORDER, maxAnisotropy=1, comparisonFunc=COMPARISON_ALWAYS, borderColor=STATIC_BORDER_COLOR_TRANSPARENT_BLACK, visibility=SHADER_VISIBILITY_PIXEL)," \
|
||||
"StaticSampler(s1, filter=FILTER_MIN_MAG_MIP_POINT, addressU=TEXTURE_ADDRESS_CLAMP, addressV=TEXTURE_ADDRESS_CLAMP, addressW=TEXTURE_ADDRESS_CLAMP, maxAnisotropy=1, comparisonFunc=COMPARISON_ALWAYS, visibility=SHADER_VISIBILITY_PIXEL)," \
|
||||
"StaticSampler(s2, filter=FILTER_MIN_MAG_MIP_LINEAR, addressU=TEXTURE_ADDRESS_CLAMP, addressV=TEXTURE_ADDRESS_CLAMP, addressW=TEXTURE_ADDRESS_CLAMP, maxAnisotropy=1, comparisonFunc=COMPARISON_ALWAYS, visibility=SHADER_VISIBILITY_PIXEL)," \
|
||||
"StaticSampler(s3, filter=FILTER_MIN_MAG_MIP_LINEAR, addressU=TEXTURE_ADDRESS_WRAP, addressV=TEXTURE_ADDRESS_WRAP, addressW=TEXTURE_ADDRESS_WRAP, maxAnisotropy=1, comparisonFunc=COMPARISON_ALWAYS, visibility=SHADER_VISIBILITY_PIXEL)"
|
||||
@@ -0,0 +1,5 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
// Same pixel shader as BackendD3D, compiled for shader model 5.1.
|
||||
#include "shader_ps.hlsl"
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "shader_d3d12_rootsig.hlsl"
|
||||
|
||||
#define main atlas_vs_main
|
||||
#include "shader_vs.hlsl"
|
||||
#undef main
|
||||
|
||||
// clang-format off
|
||||
[RootSignature(ATLAS_MAIN_ROOT_SIGNATURE)]
|
||||
PSData main(VSData data)
|
||||
// clang-format on
|
||||
{
|
||||
return atlas_vs_main(data);
|
||||
}
|
||||
@@ -32,6 +32,7 @@ SOURCES = \
|
||||
Backend.cpp \
|
||||
BackendD2D.cpp \
|
||||
BackendD3D.cpp \
|
||||
BackendD3D12.cpp \
|
||||
BuiltinGlyphs.cpp \
|
||||
dwrite_helpers.cpp \
|
||||
DWriteTextAnalysis.cpp \
|
||||
@@ -44,3 +45,7 @@ NTTARGETFILE0 = \
|
||||
$(OBJ_PATH)\$(O)\custom_shader_ps.h \
|
||||
$(OBJ_PATH)\$(O)\shader_vs.h \
|
||||
$(OBJ_PATH)\$(O)\shader_ps.h \
|
||||
$(OBJ_PATH)\$(O)\custom_shader_vs_d3d12.h \
|
||||
$(OBJ_PATH)\$(O)\custom_shader_ps_d3d12.h \
|
||||
$(OBJ_PATH)\$(O)\shader_vs_d3d12.h \
|
||||
$(OBJ_PATH)\$(O)\shader_ps_d3d12.h \
|
||||
+23
-11
@@ -66,12 +66,9 @@ void WIC::SaveTextureToPNG(ID3D11DeviceContext* deviceContext, ID3D11Resource* s
|
||||
THROW_IF_FAILED(encoder->Commit());
|
||||
}
|
||||
|
||||
void WIC::LoadTextureFromFile(ID3D11Device* device, const wchar_t* fileName, ID3D11Texture2D** out_texture, ID3D11ShaderResourceView** out_textureView)
|
||||
Image WIC::LoadImageFromFile(const wchar_t* fileName)
|
||||
{
|
||||
__assume(device != nullptr);
|
||||
__assume(fileName != nullptr);
|
||||
__assume(out_texture != nullptr);
|
||||
__assume(out_textureView != nullptr);
|
||||
|
||||
const auto wicFactory = getWicFactory();
|
||||
|
||||
@@ -124,12 +121,27 @@ void WIC::LoadTextureFromFile(ID3D11Device* device, const wchar_t* fileName, ID3
|
||||
const uint64_t bytes = stride * static_cast<uint64_t>(tgtHeight);
|
||||
THROW_HR_IF(ERROR_ARITHMETIC_OVERFLOW, bytes > UINT32_MAX);
|
||||
|
||||
Buffer<BYTE, 32> buffer{ gsl::narrow_cast<size_t>(bytes) };
|
||||
THROW_IF_FAILED(converter->CopyPixels(nullptr, gsl::narrow_cast<UINT>(stride), gsl::narrow_cast<UINT>(bytes), buffer.data()));
|
||||
Image image{
|
||||
.pixels = Buffer<BYTE, 32>{ gsl::narrow_cast<size_t>(bytes) },
|
||||
.width = tgtWidth,
|
||||
.height = tgtHeight,
|
||||
.stride = gsl::narrow_cast<u32>(stride),
|
||||
};
|
||||
THROW_IF_FAILED(converter->CopyPixels(nullptr, image.stride, gsl::narrow_cast<UINT>(bytes), image.pixels.data()));
|
||||
return image;
|
||||
}
|
||||
|
||||
void WIC::LoadTextureFromFile(ID3D11Device* device, const wchar_t* fileName, ID3D11Texture2D** out_texture, ID3D11ShaderResourceView** out_textureView)
|
||||
{
|
||||
__assume(device != nullptr);
|
||||
__assume(out_texture != nullptr);
|
||||
__assume(out_textureView != nullptr);
|
||||
|
||||
const auto image = LoadImageFromFile(fileName);
|
||||
|
||||
const D3D11_TEXTURE2D_DESC desc = {
|
||||
.Width = tgtWidth,
|
||||
.Height = tgtHeight,
|
||||
.Width = image.width,
|
||||
.Height = image.height,
|
||||
.MipLevels = 1,
|
||||
.ArraySize = 1,
|
||||
.Format = DXGI_FORMAT_B8G8R8A8_UNORM,
|
||||
@@ -138,9 +150,9 @@ void WIC::LoadTextureFromFile(ID3D11Device* device, const wchar_t* fileName, ID3
|
||||
.BindFlags = D3D11_BIND_SHADER_RESOURCE,
|
||||
};
|
||||
const D3D11_SUBRESOURCE_DATA initData = {
|
||||
.pSysMem = buffer.data(),
|
||||
.SysMemPitch = gsl::narrow_cast<UINT>(stride),
|
||||
.SysMemSlicePitch = gsl::narrow_cast<UINT>(bytes),
|
||||
.pSysMem = image.pixels.data(),
|
||||
.SysMemPitch = image.stride,
|
||||
.SysMemSlicePitch = gsl::narrow_cast<UINT>(image.pixels.size()),
|
||||
};
|
||||
wil::com_ptr<ID3D11Texture2D> texture;
|
||||
THROW_IF_FAILED(device->CreateTexture2D(&desc, &initData, texture.addressof()));
|
||||
|
||||
@@ -3,8 +3,20 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common.h"
|
||||
|
||||
namespace Microsoft::Console::Render::Atlas::WIC
|
||||
{
|
||||
// 32bpp premultiplied BGRA pixels, clamped to D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION.
|
||||
struct Image
|
||||
{
|
||||
Buffer<BYTE, 32> pixels;
|
||||
u32 width = 0;
|
||||
u32 height = 0;
|
||||
u32 stride = 0;
|
||||
};
|
||||
|
||||
void SaveTextureToPNG(ID3D11DeviceContext* deviceContext, ID3D11Resource* source, double dpi, const wchar_t* fileName);
|
||||
Image LoadImageFromFile(const wchar_t* fileName);
|
||||
void LoadTextureFromFile(ID3D11Device* device, const wchar_t* fileName, ID3D11Texture2D** out_texture, ID3D11ShaderResourceView** out_textureView);
|
||||
}
|
||||
Reference in new issue
Block a user