Compare commits

...

27 commits

Author SHA1 Message Date
Mike Griese 207522a93a GAH there are turds at the bottom and I can't fix them 2021-07-28 16:40:32 -05:00
Mike Griese a67478c382 cleanup from the previous commit 2021-07-28 15:33:56 -05:00
Mike Griese 58f31f1b6b Don't InvalidateCursor unless it's actually in the viewport 2021-07-28 15:33:06 -05:00
Mike Griese db3bdcb8aa code cleanup, comments 2021-07-28 11:07:43 -05:00
Mike Griese f516840730 hax: we don't need to do the copy step unless the buffer actually scrolled lol 2021-07-28 10:59:41 -05:00
Mike Griese 3504fbea5d oh yiss, fix the math, it works like a charm 2021-07-28 10:25:22 -05:00
Mike Griese a4fd18d443 hoo baby this works for scrolling up. the math just needs to be shuffled for scrolling down 2021-07-28 09:50:20 -05:00
Mike Griese 63f87e9d33 huh. This is definitely rendering, and not flickering. So that much mean the copy is working (so long as you don't scroll apparently) 2021-07-28 09:45:49 -05:00
Mike Griese 77ef9debb3 learning 2021-07-28 09:20:17 -05:00
Mike Griese cb8ada7eeb Trying to do the swapping thing again, one piece at a time 2021-07-28 08:53:21 -05:00
Mike Griese fb82b1f0fb Revert "I'm committing this, even thoguh it doesn't work."
This reverts commit 8fad38d291.
2021-07-28 08:33:27 -05:00
Mike Griese 32c536c76f Revert "Clearly I'm doing something terribly wrong"
This reverts commit de28bc435d.
2021-07-28 08:33:18 -05:00
Mike Griese de28bc435d Clearly I'm doing something terribly wrong 2021-07-28 08:33:01 -05:00
Mike Griese 8fad38d291 I'm committing this, even thoguh it doesn't work.
I wanted to have two buffers that we render to - we always draw the text to the back one, then copy it to the front one and swap them. This, copying, done with `ID3D10Device::CopySubresourceRegion`, should let us scroll the contents of the buffer easily.

  I'm having a hard time with this. I had it working a little bit earlier, then I moved the swap out of Present() into StartPaint, and that's obviously wrong. I keep getting

  > 88990001 The object was not in the correct state to process the method.

  I'm gonna ask @mrange if they have ideas.
2021-07-27 16:59:48 -05:00
Mike Griese 6eacddb4c3 cleanup shader code too 2021-07-27 15:26:25 -05:00
Mike Griese fd15381384 add comments 2021-07-27 13:23:56 -05:00
Mike Griese c3de05cbd2 fall back to the original Present1 code when we're not using a shader 2021-07-27 11:00:05 -05:00
Mike Griese cd99201c70 I'm pretty sure this just... works 2021-07-27 10:12:00 -05:00
Mike Griese 468d58706e Trying out the proposal from mrange/shaderificIII
As mentioned in #7147, there's a better way of doing shader presenting.

  This seems about right for when the shader effect is enabled. It's hard to tell if this is actually working well or not though. Maybe need to try another shader.

  When the shader is disabled, this absolutely doesn't work.
2021-07-27 09:44:45 -05:00
Mike Griese d5b59a09e2 Merge remote-tracking branch 'origin/main' into dev/migrie/fhl-2021/more-shader-variables 2021-07-27 08:36:55 -05:00
Mike Griese 0c4173b238 This is a much better rainbow function 2021-07-27 08:29:01 -05:00
Mike Griese d5d8576d1b pass the glyph size to the shader too 2021-07-26 15:58:08 -05:00
Mike Griese 54db2c2c3b allow shaders to be reloaded with a pair of toggleShaderEffects actions 2021-07-26 15:38:45 -05:00
Mike Griese 3a8c8830e8 notes 2021-07-26 15:33:17 -05:00
Mike Griese eea3a9bebe Revert "This was a dead end"
This reverts commit 4695dd486c.
2021-07-26 15:17:15 -05:00
Mike Griese 4695dd486c This was a dead end 2021-07-26 15:16:09 -05:00
Mike Griese 1f906aba84 Add a few more members to the shaders. Only update the main shader settings once per frame 2021-07-26 14:31:48 -05:00
6 changed files with 323 additions and 151 deletions

View file

@ -30,4 +30,4 @@ float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
// Return the final color
return color;
}
}

View file

@ -0,0 +1,69 @@
// A minimal pixel shader that inverts the colors
// The terminal graphics as a texture
Texture2D shaderTexture;
SamplerState samplerState;
// Terminal settings such as the resolution of the texture
cbuffer PixelShaderSettings {
float Time; // The number of seconds since the pixel shader was enabled
float Scale; // UI Scale
float2 Resolution; // Resolution of the shaderTexture
float4 Background; // Background color as rgba
float2 GlyphSize;
float2 CursorPosition;
float2 BufferSize;
};
// Helper for converting a hue [0, 1) to an RGB value.
// Credit to https://www.chilliant.com/rgb2hsv.html
float3 HUEtoRGB(float H)
{
float R = abs(H * 6 - 3) - 1;
float G = 2 - abs(H * 6 - 2);
float B = 2 - abs(H * 6 - 4);
return saturate(float3(R,G,B));
};
// A pixel shader is a program that given a texture coordinate (tex) produces a color.
// tex is an x,y tuple that ranges from 0,0 (top left) to 1,1 (bottom right).
// Just ignore the pos parameter.
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
// Read the color value at the current texture coordinate (tex)
// float4 is tuple of 4 floats, rgba
float4 color = shaderTexture.Sample(samplerState, tex);
// Find the location of the cursor within the viewport, in pixels.
float2 pxCursorTopLeft = CursorPosition * GlyphSize;
float2 pxCursorBottomRight = (CursorPosition + float2(1, 1)) * GlyphSize;
// Convert pixel position [{0}, {Resolution}) to texel space [{0}, {1})
float2 texelRelativeTopLeft = pxCursorTopLeft / Resolution;
float2 texelRelativeBottomRight = pxCursorBottomRight / Resolution;
// If we're rendering the cells within the bounds of the cursor cell...
if ((tex.x >= texelRelativeTopLeft.x && tex.x <= texelRelativeBottomRight.x) &&
(tex.y >= texelRelativeTopLeft.y && tex.y <= texelRelativeBottomRight.y)) {
float2 withinCursor = (tex - texelRelativeTopLeft) * (GlyphSize);
// Duration of the animation, in seconds
float duration = 2.0;
// fmod(x, y) will cycle linearly between 0 and y. fmod(x, 1) will just
// get the fractional component of x. Since HUEtoRGB(0) == HUEtoRBG(1),
// the colors naturally cycle.
// This lets us scroll through the entire color spectrum smoothly.
// multiply by 1.25 to make the animation a little faster - this gets a
// bit more hue variation in the cursor at a single time.
float hue = lerp(0.00, 1, fmod(1.25*(Time - withinCursor.y) / duration, 1));
color.xyz = HUEtoRGB(hue);
color.a = 1.0;
}
// Return the final color
return color;
}

View file

@ -278,13 +278,12 @@ void Renderer::TriggerRedrawCursor(const COORD* const pcoord)
const SMALL_RECT cursorRect = { pcoord->X, pcoord->Y, pcoord->X + cursorWidth - 1, pcoord->Y };
Viewport cursorView = Viewport::FromInclusive(BufferToScreenLine(cursorRect, lineRendition));
// The region is clamped within the viewport boundaries and we only
// trigger a redraw if the region is not empty.
Viewport view = _pData->GetViewport();
cursorView = view.Clamp(cursorView);
if (cursorView.IsValid())
if (view.IsInBounds(cursorView))
{
// The region is clamped within the viewport boundaries and we only
// trigger a redraw if the region is not empty.
cursorView = view.Clamp(cursorView);
const SMALL_RECT updateRect = view.ConvertToOrigin(cursorView).ToExclusive();
for (IRenderEngine* pEngine : _rgpEngines)
{

View file

@ -49,6 +49,8 @@ D3D11_INPUT_ELEMENT_DESC _shaderInputLayout[] = {
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }
};
static constexpr std::string_view NOP_PIXEL_SHADER{ nopPixelShaderString };
#pragma hdrstop
using namespace Microsoft::Console::Render;
@ -246,11 +248,20 @@ bool DxEngine::_HasTerminalEffects() const noexcept
void DxEngine::ToggleShaderEffects()
{
_terminalEffectsEnabled = !_terminalEffectsEnabled;
// If we're enabling the shader, try reloading it.
// It's not as good as hot reloading the hlsl file itself, but it's good enough.
if (_terminalEffectsEnabled)
{
_SetupTerminalEffects();
}
LOG_IF_FAILED(InvalidateAll());
}
// Routine Description:
// - Loads pixel shader source depending on _retroTerminalEffect and _pixelShaderPath
// - Loads pixel shader source depending on _retroTerminalEffect and _pixelShaderPath.
// - If for any reason, we failed to load the shader file, then always fall
// back to the no-op shader. It does nothing - it simply passes
// the color through unmodified.
// Arguments:
// Return Value:
// - Pixel shader source code
@ -304,7 +315,11 @@ std::string DxEngine::_LoadPixelShaderFile() const
return std::string{ retroPixelShaderString };
}
return std::string{};
// If for any reason, we failed to load the shader file, then always fall
// back to the no-op shader. This one's embedded as a string inside this
// binary. It'll definitely compile, and it does nothing - it simply passes
// the color through unmodified.
return std::string{ NOP_PIXEL_SHADER };
}
// Routine Description:
@ -472,6 +487,11 @@ void DxEngine::_ComputePixelShaderSettings() noexcept
background.w = _backgroundColor.a;
_pixelShaderSettings.Background = background;
til::size glyphSize{ _fontRenderData->GlyphCell() };
_pixelShaderSettings.GlyphSize = XMFLOAT2{ ::base::saturated_cast<float>(glyphSize.width()), ::base::saturated_cast<float>(glyphSize.height()) };
_pixelShaderSettings.CursorPosition = XMFLOAT2{ ::base::saturated_cast<float>(_lastCursor.x()), ::base::saturated_cast<float>(_lastCursor.y()) };
_pixelShaderSettings.BufferSize = XMFLOAT2{ ::base::saturated_cast<float>(_lastBufferSize.width()), ::base::saturated_cast<float>(_lastBufferSize.height()) };
_d3dDeviceContext->UpdateSubresource(_pixelShaderSettingsBuffer.Get(), 0, nullptr, &_pixelShaderSettings, 0, 0);
}
CATCH_LOG();
@ -685,15 +705,25 @@ try
}
}
if (_HasTerminalEffects())
::Microsoft::WRL::ComPtr<ID3D11Texture2D> swapBuffer;
RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&swapBuffer));
// Setup _framebuffer, to where we'll write all console graphics
D3D11_TEXTURE2D_DESC framebufferDesc{};
swapBuffer->GetDesc(&framebufferDesc);
WI_SetFlag(framebufferDesc.BindFlags, D3D11_BIND_SHADER_RESOURCE);
RETURN_IF_FAILED(_d3dDevice->CreateTexture2D(&framebufferDesc, nullptr, &_framebuffer));
RETURN_IF_FAILED(_d3dDevice->CreateTexture2D(&framebufferDesc, nullptr, &_otherbuffer));
// if (_HasTerminalEffects())
// {
const HRESULT hr = _SetupTerminalEffects();
if (FAILED(hr))
{
const HRESULT hr = _SetupTerminalEffects();
if (FAILED(hr))
{
LOG_HR_MSG(hr, "Failed to setup terminal effects. Disabling.");
_terminalEffectsEnabled = false;
}
LOG_HR_MSG(hr, "Failed to setup terminal effects. Disabling.");
_terminalEffectsEnabled = false;
}
// }
// With a new swap chain, mark the entire thing as invalid.
RETURN_IF_FAILED(InvalidateAll());
@ -752,14 +782,19 @@ static constexpr D2D1_ALPHA_MODE _dxgiAlphaToD2d1Alpha(DXGI_ALPHA_MODE mode) noe
{
try
{
// Pull surface out of swap chain.
RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&_dxgiSurface)));
// Make a bitmap and bind it to the swap chain surface
const auto bitmapProperties = D2D1::BitmapProperties1(
D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
D2D1::PixelFormat(_swapChainDesc.Format, _dxgiAlphaToD2d1Alpha(_swapChainDesc.AlphaMode)));
// Create the bitmaps for each frame buffer. We'll draw to these
// surfaces.
// Get each framebuffer as a IDXGISurface, and use
// CreateBitmapFromDxgiSurface to instantiate the ID2D1Bitmap
RETURN_IF_FAILED(_otherbuffer->QueryInterface(IID_PPV_ARGS(&_dxgiSurface)));
RETURN_IF_FAILED(_d2dDeviceContext->CreateBitmapFromDxgiSurface(_dxgiSurface.Get(), bitmapProperties, &_d2dOtherBitmap));
RETURN_IF_FAILED(_framebuffer->QueryInterface(IID_PPV_ARGS(&_dxgiSurface)));
RETURN_IF_FAILED(_d2dDeviceContext->CreateBitmapFromDxgiSurface(_dxgiSurface.Get(), bitmapProperties, &_d2dBitmap));
// Assign that bitmap as the target of the D2D device context. Draw commands hit the context
@ -768,7 +803,6 @@ static constexpr D2D1_ALPHA_MODE _dxgiAlphaToD2d1Alpha(DXGI_ALPHA_MODE mode) noe
// The leg bone connected to the knee bone,
// The knee bone connected to the thigh bone
// ... and so on)
_d2dDeviceContext->SetTarget(_d2dBitmap.Get());
// We need the AntialiasMode for non-text object to be Aliased to ensure
@ -861,6 +895,8 @@ void DxEngine::_ReleaseDeviceResources() noexcept
_d2dDeviceContext->EndDraw();
}
_framebuffer.Reset();
_d2dDeviceContext.Reset();
_dxgiSurface.Reset();
@ -1086,6 +1122,7 @@ CATCH_RETURN()
// - S_OK
[[nodiscard]] HRESULT DxEngine::InvalidateCursor(const SMALL_RECT* const psrRegion) noexcept
{
_lastCursor = til::point{ psrRegion->Left, psrRegion->Top };
return Invalidate(psrRegion);
}
@ -1152,6 +1189,7 @@ try
_invalidMap.translate(deltaCells, true);
_invalidScroll += deltaCells;
_allInvalid = _IsAllInvalid();
_lastCursor += deltaCells;
}
}
@ -1322,6 +1360,68 @@ try
RETURN_IF_FAILED(InvalidateAll());
}
// GH#7147
// Here's a bit of trickiness. Each frame, we're rendering what changed
// in the console into a framebuffer. But when the frame scrolls, we
// don't want to redraw everything we already have, we just want to
// shift it up/down.
// To facilitate this, we have two buffers that we render to, like a
// swapchain. in between frames, we'll copy the contents from the back
// buffer into the front buffer, using CopyFromRenderTarget. This allows
// us to move the contents from one buffer into the other, at a given
// offset,
// To optimize this, we only need to do this copying and swapping when
// there's actually a scroll delta. Otherwise, the viewport is still
// just in the same location, and we can just leave the contents of the
// current framebuffer alone.
if (_d2dBitmap && _d2dOtherBitmap && _invalidScroll.y() != 0)
{
// Swap the framebuffers
std::swap(_framebuffer, _otherbuffer);
std::swap(_d2dBitmap, _d2dOtherBitmap);
// Figure out how much of the screen to scroll, and where to scroll it to.
til::size glyphSize{ _fontRenderData->GlyphCell() };
til::point scrollInPixels = _invalidScroll * glyphSize;
til::point sourceOrigin{ 0, 0 };
// D2D_POINT_2U is an unsigned point, it won't accept negative
// numbers. If we want to scroll the frame contents up (s.t. delta.y
// < 0), we need to copy just the bottom portion of the last buffer
// to 0,0 in the new buffer.
if (scrollInPixels.y() < 0)
{
sourceOrigin = -scrollInPixels;
scrollInPixels = til::point{ 0, 0 };
}
D2D_POINT_2U tgtPos{ scrollInPixels.x<uint32_t>(),
scrollInPixels.y<uint32_t>() };
// auto heightOffset = std::max(sourceOrigin.y<uint32_t>(), scrollInPixels.y<uint32_t>());
auto heightOffset = sourceOrigin.y<uint32_t>();
til::size realBufferSize{ _lastBufferSize * glyphSize };
til::size srcDimensions{ realBufferSize.width() - sourceOrigin.x(),
realBufferSize.height() - heightOffset };
til::rectangle source{ sourceOrigin, srcDimensions };
D2D1_RECT_U srcRect{
source.left<uint32_t>(),
source.top<uint32_t>(),
source.right<uint32_t>(),
source.bottom<uint32_t>(),
};
// Get our _d2dDeviceContext as a ID2D1RenderTarget
Microsoft::WRL::ComPtr<ID2D1RenderTarget> otherRenderTarget;
RETURN_IF_FAILED(_d2dDeviceContext->QueryInterface(IID_PPV_ARGS(&otherRenderTarget)));
// Copy the contents from the old buffer (now in otherBuffer) to the
// current bitmap.
_d2dBitmap->CopyFromRenderTarget(&tgtPos, // destPoint
otherRenderTarget.Get(), // renderTarget
&srcRect); // srcRect
// make sure to tell our device that we're drawing to a different bitmap now
_d2dDeviceContext->SetTarget(_d2dBitmap.Get());
}
_d2dDeviceContext->BeginDraw();
_isPainting = true;
@ -1368,7 +1468,9 @@ try
// If there's still a clip hanging around, remove it. We're all done.
LOG_IF_FAILED(_customRenderer->EndClip(_drawingContext.get()));
hr = _d2dDeviceContext->EndDraw();
D2D1_TAG tag1;
D2D1_TAG tag2;
hr = _d2dDeviceContext->EndDraw(&tag1, &tag2);
if (SUCCEEDED(hr))
{
@ -1431,31 +1533,6 @@ try
}
CATCH_RETURN()
// Routine Description:
// - Copies the front surface of the swap chain (the one being displayed)
// to the back surface of the swap chain (the one we draw on next)
// so we can draw on top of what's already there.
// Arguments:
// - <none>
// Return Value:
// - Any DirectX error, a memory error, etc.
[[nodiscard]] HRESULT DxEngine::_CopyFrontToBack() noexcept
{
try
{
Microsoft::WRL::ComPtr<ID3D11Resource> backBuffer;
Microsoft::WRL::ComPtr<ID3D11Resource> frontBuffer;
RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer)));
RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(1, IID_PPV_ARGS(&frontBuffer)));
_d3dDeviceContext->CopyResource(backBuffer.Get(), frontBuffer.Get());
}
CATCH_RETURN();
return S_OK;
}
// Method Description:
// - When the shaders are on, say that we need to keep redrawing every
// possible frame in case they have some smooth action on every frame tick.
@ -1498,6 +1575,51 @@ void DxEngine::WaitUntilCanRender() noexcept
}
}
// Method Description:
// - Draws the contents of our _framebuffer to the swapchain. We always do this,
// whether the user has specified a custom pixel shader or not.
// - This draws a single quad to the entire swapchain, as rendered by the
// configured pixel shader.
// - If no pixel shader is configured, we'll have already decided to use the
// no-op shader (see `nopPixelShaderString` in `ScreenPixelShader.h`), which
// will render the _framebuffer unmodified.
[[nodiscard]] HRESULT DxEngine::_RenderToSwapChain() noexcept
try
{
// Should have been initialized.
RETURN_HR_IF(E_NOT_VALID_STATE, !_framebuffer);
D3D11_TEXTURE2D_DESC desc;
_framebuffer->GetDesc(&desc);
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = desc.MipLevels;
srvDesc.Format = desc.Format;
::Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> shaderResource;
RETURN_IF_FAILED(_d3dDevice->CreateShaderResourceView(_framebuffer.Get(), &srvDesc, &shaderResource));
// Render the screen quad with shader effects.
const UINT stride = sizeof(ShaderInput);
const UINT offset = 0;
_d3dDeviceContext->OMSetRenderTargets(1, _renderTargetView.GetAddressOf(), nullptr);
_d3dDeviceContext->IASetVertexBuffers(0, 1, _screenQuadVertexBuffer.GetAddressOf(), &stride, &offset);
_d3dDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
_d3dDeviceContext->IASetInputLayout(_vertexLayout.Get());
_d3dDeviceContext->VSSetShader(_vertexShader.Get(), nullptr, 0);
_d3dDeviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0);
_d3dDeviceContext->PSSetShaderResources(0, 1, shaderResource.GetAddressOf());
_d3dDeviceContext->PSSetSamplers(0, 1, _samplerState.GetAddressOf());
_d3dDeviceContext->PSSetConstantBuffers(0, 1, _pixelShaderSettingsBuffer.GetAddressOf());
_d3dDeviceContext->Draw(ARRAYSIZE(_screenQuadVertices), 0);
return S_OK;
}
CATCH_RETURN()
// Routine Description:
// - Takes queued drawing information and presents it to the screen.
// - This is separated out so it can be done outside the lock as it's expensive.
@ -1509,53 +1631,62 @@ void DxEngine::WaitUntilCanRender() noexcept
{
if (_presentReady)
{
if (_HasTerminalEffects() && _pixelShaderLoaded)
{
const HRESULT hr2 = _PaintTerminalEffects();
if (FAILED(hr2))
{
_pixelShaderLoaded = false;
LOG_HR_MSG(hr2, "Failed to paint terminal effects. Disabling.");
}
}
try
{
HRESULT hr = S_OK;
// Updates the pixel shader resource (including time)
_ComputePixelShaderSettings();
// Renders framebuffer texture + potential pixel shader effects
LOG_IF_FAILED(_RenderToSwapChain());
bool recreate = false;
HRESULT hr = S_OK;
// On anything but the first frame, try partial presentation.
// We'll do it first because if it fails, we'll try again with full presentation.
if (!_firstFrame)
{
hr = _dxgiSwapChain->Present1(1, 0, &_presentParams);
// These two error codes are indicated for destroy-and-recreate
// If we were told to destroy-and-recreate, we're going to skip straight into doing that
// and not try again with full presentation.
recreate = hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET;
// Log this as we actually don't expect it to happen, we just will try again
// below for robustness of our drawing.
if (FAILED(hr) && !recreate)
{
LOG_HR(hr);
}
}
// If it's the first frame through, we cannot do partial presentation.
// Also if partial presentation failed above and we weren't told to skip straight to
// device recreation.
// In both of these circumstances, do a full presentation.
if (_firstFrame || (FAILED(hr) && !recreate))
// If we're actually rendering a real shader effect, then just call
// the normal Present() to present the whole frame. We're assuming
// that the user who's configured a pixel shader isn't concerned
// with the optimization that Present1 provides (esp. for remote
// desktop scenarios)
if (_HasTerminalEffects())
{
hr = _dxgiSwapChain->Present(1, 0);
_firstFrame = false;
// These two error codes are indicated for destroy-and-recreate
recreate = hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET;
}
else
{
// On anything but the first frame, try partial presentation.
// We'll do it first because if it fails, we'll try again with full presentation.
if (!_firstFrame)
{
hr = _dxgiSwapChain->Present1(1, 0, &_presentParams);
// These two error codes are indicated for destroy-and-recreate
// If we were told to destroy-and-recreate, we're going to skip straight into doing that
// and not try again with full presentation.
recreate = hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET;
// Log this as we actually don't expect it to happen, we just will try again
// below for robustness of our drawing.
if (FAILED(hr) && !recreate)
{
LOG_HR(hr);
}
}
// If it's the first frame through, we cannot do partial presentation.
// Also if partial presentation failed above and we weren't told to skip straight to
// device recreation.
// In both of these circumstances, do a full presentation.
if (_firstFrame || (FAILED(hr) && !recreate))
{
hr = _dxgiSwapChain->Present(1, 0);
_firstFrame = false;
// These two error codes are indicated for destroy-and-recreate
recreate = hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET;
}
}
// Now check for failure cases from either presentation mode.
if (FAILED(hr))
@ -1575,15 +1706,6 @@ void DxEngine::WaitUntilCanRender() noexcept
}
}
// If we are doing full repaints we don't need to copy front buffer to back buffer
if (!_FullRepaintNeeded())
{
// Finally copy the front image (being presented now) onto the backing buffer
// (where we are about to draw the next frame) so we can draw only the differences
// next frame.
RETURN_IF_FAILED(_CopyFrontToBack());
}
_presentReady = false;
_presentDirty.clear();
@ -1841,64 +1963,14 @@ CATCH_RETURN()
return S_OK;
}
// Routine Description:
// - Paint terminal effects.
// Arguments:
// Return Value:
// - S_OK or relevant DirectX error.
[[nodiscard]] HRESULT DxEngine::_PaintTerminalEffects() noexcept
try
{
// Should have been initialized.
RETURN_HR_IF(E_NOT_VALID_STATE, !_framebufferCapture);
// Capture current frame in swap chain to a texture.
::Microsoft::WRL::ComPtr<ID3D11Texture2D> swapBuffer;
RETURN_IF_FAILED(_dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&swapBuffer)));
_d3dDeviceContext->CopyResource(_framebufferCapture.Get(), swapBuffer.Get());
// Prepare captured texture as input resource to shader program.
D3D11_TEXTURE2D_DESC desc;
_framebufferCapture->GetDesc(&desc);
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = desc.MipLevels;
srvDesc.Format = desc.Format;
::Microsoft::WRL::ComPtr<ID3D11ShaderResourceView> shaderResource;
RETURN_IF_FAILED(_d3dDevice->CreateShaderResourceView(_framebufferCapture.Get(), &srvDesc, &shaderResource));
// Render the screen quad with shader effects.
const UINT stride = sizeof(ShaderInput);
const UINT offset = 0;
_d3dDeviceContext->OMSetRenderTargets(1, _renderTargetView.GetAddressOf(), nullptr);
_d3dDeviceContext->IASetVertexBuffers(0, 1, _screenQuadVertexBuffer.GetAddressOf(), &stride, &offset);
_d3dDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
_d3dDeviceContext->IASetInputLayout(_vertexLayout.Get());
_d3dDeviceContext->VSSetShader(_vertexShader.Get(), nullptr, 0);
_d3dDeviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0);
_d3dDeviceContext->PSSetShaderResources(0, 1, shaderResource.GetAddressOf());
_d3dDeviceContext->PSSetSamplers(0, 1, _samplerState.GetAddressOf());
_d3dDeviceContext->PSSetConstantBuffers(0, 1, _pixelShaderSettingsBuffer.GetAddressOf());
_d3dDeviceContext->Draw(ARRAYSIZE(_screenQuadVertices), 0);
return S_OK;
}
CATCH_RETURN()
[[nodiscard]] bool DxEngine::_FullRepaintNeeded() const noexcept
{
// If someone explicitly requested differential rendering off, then we need to invalidate everything
// so the entire frame is repainted.
// If someone explicitly requested differential rendering off, then we need
// to invalidate everything so the entire frame is repainted.
//
// If terminal effects are on, we must invalidate everything for them to draw correctly.
// Yes, this will further impact the performance of terminal effects.
// But we're talking about running the entire display pipeline through a shader for
// cosmetic effect, so performance isn't likely the top concern with this feature.
return _forceFullRepaintRendering || _HasTerminalEffects();
// As of GH#7147, we no longer need to repaint everything when shader
// effects are enabled.
return _forceFullRepaintRendering;
}
// Routine Description:
@ -1967,7 +2039,7 @@ CATCH_RETURN()
}
// Update pixel shader settings as background color might have changed
_ComputePixelShaderSettings();
// _ComputePixelShaderSettings();
return S_OK;
}
@ -2038,7 +2110,7 @@ CATCH_RETURN();
RETURN_IF_FAILED(InvalidateAll());
// Update pixel shader settings as scale might have changed
_ComputePixelShaderSettings();
// _ComputePixelShaderSettings();
return S_OK;
}
@ -2062,8 +2134,9 @@ float DxEngine::GetScaling() const noexcept
// - srNewViewport - The bounds of the new viewport.
// Return Value:
// - HRESULT S_OK
[[nodiscard]] HRESULT DxEngine::UpdateViewport(const SMALL_RECT /*srNewViewport*/) noexcept
[[nodiscard]] HRESULT DxEngine::UpdateViewport(const SMALL_RECT srNewViewport) noexcept
{
_lastBufferSize = til::size{ srNewViewport.Right - srNewViewport.Left, srNewViewport.Bottom - srNewViewport.Top };
return S_OK;
}

View file

@ -208,6 +208,7 @@ namespace Microsoft::Console::Render
::Microsoft::WRL::ComPtr<ID2D1Device> _d2dDevice;
::Microsoft::WRL::ComPtr<ID2D1DeviceContext> _d2dDeviceContext;
::Microsoft::WRL::ComPtr<ID2D1Bitmap1> _d2dBitmap;
::Microsoft::WRL::ComPtr<ID2D1Bitmap1> _d2dOtherBitmap;
::Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> _d2dBrushForeground;
::Microsoft::WRL::ComPtr<ID2D1SolidColorBrush> _d2dBrushBackground;
@ -221,10 +222,14 @@ namespace Microsoft::Console::Render
wil::unique_handle _swapChainFrameLatencyWaitableObject;
std::unique_ptr<DrawingContext> _drawingContext;
::Microsoft::WRL::ComPtr<ID3D11Texture2D> _framebuffer{ nullptr };
::Microsoft::WRL::ComPtr<ID3D11Texture2D> _otherbuffer{ nullptr };
// Terminal effects resources.
// Controls if configured terminal effects are enabled
bool _terminalEffectsEnabled;
til::point _lastCursor{ 0, 0 };
til::size _lastBufferSize{ 0, 0 };
// Experimental and deprecated retro terminal effect
// Preserved for backwards compatibility
@ -262,16 +267,23 @@ namespace Microsoft::Console::Render
{
// Note: This can be seen as API endpoint towards user provided pixel shaders.
// Changes here can break existing pixel shaders so be careful with changing datatypes
// and order of parameters
// and order of parameters.
float Time;
float Scale;
DirectX::XMFLOAT2 Resolution;
DirectX::XMFLOAT4 Background;
DirectX::XMFLOAT2 GlyphSize;
DirectX::XMFLOAT2 CursorPosition;
DirectX::XMFLOAT2 BufferSize;
// You can always add more values to the end, but they must always be in this order.
// Adding values is not a breaking change. Shaders that don't have those values declared will simply ignore them.
// However, if you want to use `CursorPostion`, then you'll need to declare all the members that preceed it.
#pragma warning(suppress : 4324) // structure was padded due to __declspec(align())
} _pixelShaderSettings;
[[nodiscard]] HRESULT _CreateDeviceResources(const bool createSwapChain) noexcept;
[[nodiscard]] HRESULT _CreateSurfaceHandle() noexcept;
[[nodiscard]] HRESULT _RenderToSwapChain() noexcept;
bool _HasTerminalEffects() const noexcept;
std::string _LoadPixelShaderFile() const;
@ -279,6 +291,8 @@ namespace Microsoft::Console::Render
void _ComputePixelShaderSettings() noexcept;
[[nodiscard]] HRESULT _PrepareRenderTarget() noexcept;
// [[nodiscard]] HRESULT _PrepareRenderTarget(::Microsoft::WRL::ComPtr<ID3D11Texture2D>& buffer,
// ::Microsoft::WRL::ComPtr<ID2D1Bitmap1>& bitmap) noexcept;
void _ReleaseDeviceResources() noexcept;
@ -289,7 +303,7 @@ namespace Microsoft::Console::Render
_In_ size_t StringLength,
_Out_ IDWriteTextLayout** ppTextLayout) noexcept;
[[nodiscard]] HRESULT _CopyFrontToBack() noexcept;
// [[nodiscard]] HRESULT _CopyFrontToBack() noexcept;
[[nodiscard]] HRESULT _EnableDisplayAccess(const bool outputEnabled) noexcept;

View file

@ -89,3 +89,20 @@ float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
}
)" };
#endif
constexpr std::string_view nopPixelShaderString{ R"(
// No-op shader used for normal operations
Texture2D shaderTexture;
SamplerState samplerState;
cbuffer PixelShaderSettings {
float Time;
float Scale;
float2 Resolution;
float4 Background;
};
float4 main(float4 pos : SV_POSITION, float2 tex : TEXCOORD) : SV_TARGET
{
float4 color = shaderTexture.Sample(samplerState, tex);
return color;
}
)" };