Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/dawn/native/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,9 @@ source_set("sources") {
# WinPIX should be added as third party tools and linked statically

if (dawn_enable_d3d11 || dawn_enable_d3d12) {
# dcomp.lib provides DCompositionCreateDevice, used by SwapChainD3D to present
# per-pixel-transparent HWND surfaces through a DirectComposition visual.
libs += [ "dcomp.lib" ]
sources += [
"d3d/BackendD3D.cpp",
"d3d/BackendD3D.h",
Expand Down
4 changes: 3 additions & 1 deletion src/dawn/native/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,9 @@ if (DAWN_ENABLE_D3D11 OR DAWN_ENABLE_D3D12)
"d3d/SwapChainD3D.cpp"
"d3d/UtilsD3D.cpp"
)
list(APPEND conditional_private_platform_depends dxguid.lib)
# dcomp.lib provides DCompositionCreateDevice, used by SwapChainD3D to present
# per-pixel-transparent HWND surfaces through a DirectComposition visual.
list(APPEND conditional_private_platform_depends dxguid.lib dcomp.lib)
endif()

if (DAWN_ENABLE_D3D11)
Expand Down
108 changes: 99 additions & 9 deletions src/dawn/native/d3d/SwapChainD3D.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,18 @@ IWinUISwapChainPanelNative : public IUnknown {

SwapChain::~SwapChain() = default;

bool SwapChain::UsesComposition() const {
// DXGI only honours a non-opaque AlphaMode on composition swapchains, so a premultiplied
// request has to be routed through DirectComposition. Everything else keeps the plain
// CreateSwapChainForHwnd path.
//
// Note PhysicalDeviceD3D advertises Opaque and Premultiplied as the supported alpha modes,
// so Unpremultiplied never reaches here; DComp has no straight-alpha mode to map it to
// anyway.
return GetSurface()->GetType() == Surface::Type::WindowsHWND &&
GetAlphaMode() == wgpu::CompositeAlphaMode::Premultiplied;
}

// Initializes the swapchain on the surface. Note that `previousSwapChain` may or may not be
// nullptr. If it is not nullptr it means that it is the swapchain previously in use on the
// surface and that we have a chance to reuse it's underlying IDXGISwapChain and "buffers".
Expand All @@ -141,6 +153,14 @@ MaybeError SwapChain::Initialize(SwapChainBase* previousSwapChain) {
mConfig.swapChainFlags = PresentModeToSwapChainFlags(GetPresentMode());
mConfig.usage = ToDXGIUsage(GetDevice(), GetFormat(), GetUsage());

if (UsesComposition()) {
// A composition swapchain is never the fullscreen target of a window, and DXGI rejects
// creation outright if ALLOW_MODE_SWITCH is set. Mask it out of mConfig rather than at
// the creation site so the ResizeBuffers path below stays consistent with what the
// swapchain was actually created with.
mConfig.swapChainFlags &= ~DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
}

// There is no previous swapchain so we can create one directly and don't have anything else
// to do.
if (previousSwapChain == nullptr) {
Expand All @@ -164,10 +184,13 @@ MaybeError SwapChain::Initialize(SwapChainBase* previousSwapChain) {

// The previous swapchain is on the same device so we want to reuse it but it is still not
// always possible. Because DXGI requires that a new swapchain be created if the
// DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING flag is changed.
// DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING flag is changed, or if the alpha mode changed: a
// swapchain's AlphaMode is fixed at creation, and a composition swapchain and an HWND
// swapchain are not interchangeable.
bool canReuseSwapChain =
((mConfig.swapChainFlags ^ previousD3DSwapChain->mConfig.swapChainFlags) &
DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING) == 0;
DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING) == 0 &&
UsesComposition() == previousD3DSwapChain->UsesComposition();

// We can't reuse the previous swapchain, so we destroy it and wait for all of its reference
// to be forgotten (otherwise DXGI complains that there are outstanding references).
Expand All @@ -180,6 +203,13 @@ MaybeError SwapChain::Initialize(SwapChainBase* previousSwapChain) {
// the buffers.
mDXGISwapChain = std::move(previousD3DSwapChain->mDXGISwapChain);

// The visual tree belongs to the swapchain we just adopted, so it has to come with it.
// Leaving it on the previous object would destroy the target when that object dies, and
// the window would go blank while Present kept returning S_OK.
mDCompDevice = std::move(previousD3DSwapChain->mDCompDevice);
mDCompTarget = std::move(previousD3DSwapChain->mDCompTarget);
mDCompVisual = std::move(previousD3DSwapChain->mDCompVisual);

bool canReuseBuffers = GetWidth() == previousSwapChain->GetWidth() &&
GetHeight() == previousSwapChain->GetHeight() &&
GetFormat() == previousSwapChain->GetFormat() &&
Expand Down Expand Up @@ -222,7 +252,12 @@ MaybeError SwapChain::InitializeSwapChainFromScratch() {
swapChainDesc.BufferCount = mConfig.bufferCount;
swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
// Honour the WebGPU surface descriptor's alphaMode. Previously this hardcoded IGNORE, which
// silently dropped per-pixel transparency requests even though PhysicalDeviceD3D advertises
// Premultiplied as supported. DXGI only accepts a non-opaque mode on composition
// swapchains, so this pairs with the CreateSwapChainForComposition branch below.
swapChainDesc.AlphaMode =
UsesComposition() ? DXGI_ALPHA_MODE_PREMULTIPLIED : DXGI_ALPHA_MODE_IGNORE;
swapChainDesc.Flags = mConfig.swapChainFlags;

ComPtr<IDXGIFactory2> factory2 = nullptr;
Expand All @@ -232,16 +267,32 @@ MaybeError SwapChain::InitializeSwapChainFromScratch() {
ComPtr<IDXGISwapChain1> swapChain1;
switch (GetSurface()->GetType()) {
case Surface::Type::WindowsHWND: {
HWND hwnd = static_cast<HWND>(GetSurface()->GetHWND());

if (UsesComposition()) {
// Transparent path. The swapchain is created unparented and then bound to the
// window through a DirectComposition visual. For the window to actually show
// through, it must have been created WS_EX_NOREDIRECTIONBITMAP — otherwise the
// redirection bitmap sits behind the visual and stays opaque.
DAWN_TRY(CheckHRESULT(
factory2->CreateSwapChainForComposition(GetD3DDeviceForCreatingSwapChain(),
&swapChainDesc, nullptr, &swapChain1),
"Creating the composition IDXGISwapChain1"));

DAWN_TRY(InitializeDComp(hwnd, swapChain1.Get()));

// No MakeWindowAssociation here: a composition swapchain isn't associated with
// the window, and DXGI's alt+enter handling doesn't apply to it.
break;
}

DAWN_TRY(CheckHRESULT(
factory2->CreateSwapChainForHwnd(GetD3DDeviceForCreatingSwapChain(),
static_cast<HWND>(GetSurface()->GetHWND()),
factory2->CreateSwapChainForHwnd(GetD3DDeviceForCreatingSwapChain(), hwnd,
&swapChainDesc, nullptr, nullptr, &swapChain1),
"Creating the IDXGISwapChain1"));

DAWN_TRY(
CheckHRESULT(factory2->MakeWindowAssociation(
static_cast<HWND>(GetSurface()->GetHWND()), DXGI_MWA_NO_ALT_ENTER),
"Disabling DXGI's alt+enter fullscreen handling"));
DAWN_TRY(CheckHRESULT(factory2->MakeWindowAssociation(hwnd, DXGI_MWA_NO_ALT_ENTER),
"Disabling DXGI's alt+enter fullscreen handling"));
break;
}
case Surface::Type::WindowsCoreWindow: {
Expand Down Expand Up @@ -289,6 +340,29 @@ MaybeError SwapChain::InitializeSwapChainFromScratch() {
return CollectSwapChainBuffers();
}

MaybeError SwapChain::InitializeDComp(HWND hwnd, IDXGISwapChain1* swapChain) {
// Passing nullptr for the DXGI device lets DComp pick one. We can't hand it ours: the
// backend-supplied IUnknown here is an ID3D12CommandQueue on D3D12 and an ID3D11Device on
// D3D11, and only the latter can produce an IDXGIDevice.
DAWN_TRY(CheckHRESULT(DCompositionCreateDevice(nullptr, IID_PPV_ARGS(&mDCompDevice)),
"DCompositionCreateDevice"));

// topmost=TRUE so the visual composites above the (absent) redirection surface.
DAWN_TRY(CheckHRESULT(mDCompDevice->CreateTargetForHwnd(hwnd, TRUE, &mDCompTarget),
"IDCompositionDevice::CreateTargetForHwnd"));
DAWN_TRY(CheckHRESULT(mDCompDevice->CreateVisual(&mDCompVisual),
"IDCompositionDevice::CreateVisual"));
DAWN_TRY(CheckHRESULT(mDCompVisual->SetContent(swapChain), "IDCompositionVisual::SetContent"));
DAWN_TRY(
CheckHRESULT(mDCompTarget->SetRoot(mDCompVisual.Get()), "IDCompositionTarget::SetRoot"));

// Nothing in the visual tree takes effect until Commit. This is a one-time cost: later
// frames are published by IDXGISwapChain::Present alone.
DAWN_TRY(CheckHRESULT(mDCompDevice->Commit(), "IDCompositionDevice::Commit"));

return {};
}

MaybeError SwapChain::PresentDXGISwapChain() {
// Do the actual present. DXGI_STATUS_OCCLUDED is a valid return value that's just a
// message to the application that it could stop rendering.
Expand All @@ -307,6 +381,22 @@ MaybeError SwapChain::PresentDXGISwapChain() {
}

void SwapChain::ReleaseDXGISwapChain() {
// Unbind the visual tree before dropping our own reference. The visual holds a reference to
// the swapchain, so callers that need every reference gone — DetachAndWaitForDeallocation,
// and the DXGI operations it exists to satisfy — would otherwise still be blocked by it.
if (mDCompVisual != nullptr) {
mDCompVisual->SetContent(nullptr);
}
if (mDCompTarget != nullptr) {
mDCompTarget->SetRoot(nullptr);
}
if (mDCompDevice != nullptr) {
mDCompDevice->Commit();
}
mDCompVisual = nullptr;
mDCompTarget = nullptr;
mDCompDevice = nullptr;

mDXGISwapChain = nullptr;
}

Expand Down
21 changes: 21 additions & 0 deletions src/dawn/native/d3d/SwapChainD3D.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
#include "src/dawn/native/SwapChain.h"
#include "src/dawn/native/d3d/d3d_platform.h"

// DirectComposition, for per-pixel-transparent HWND surfaces: DXGI only allows a non-opaque
// AlphaMode on composition swapchains, never on the ones created by CreateSwapChainForHwnd.
// Must come after d3d_platform.h, which brings in windows.h.
#include <dcomp.h>

namespace dawn::native::d3d {

class Device;
Expand Down Expand Up @@ -73,12 +78,28 @@ class SwapChain : public SwapChainBase {
};
const Config& GetConfig() const;

// True when the surface asked for a per-pixel-transparent presentation and we therefore
// have to go through DirectComposition instead of CreateSwapChainForHwnd.
bool UsesComposition() const;

private:
// Does the swapchain initialization steps assuming there is nothing we can reuse.
MaybeError InitializeSwapChainFromScratch();

// Builds the DirectComposition device / target / visual tree for `hwnd` and points it at
// `swapChain`. Only called on the composition path.
MaybeError InitializeDComp(HWND hwnd, IDXGISwapChain1* swapChain);

Config mConfig;
ComPtr<IDXGISwapChain3> mDXGISwapChain;

// DirectComposition objects backing a transparent HWND swapchain; null on the opaque path.
// These must outlive mDXGISwapChain, and must be moved across when a swapchain is recycled
// onto a new SwapChain object — otherwise the visual tree is destroyed with the old object
// and the window goes blank while still presenting successfully.
ComPtr<IDCompositionDevice> mDCompDevice;
ComPtr<IDCompositionTarget> mDCompTarget;
ComPtr<IDCompositionVisual> mDCompVisual;
};

} // namespace dawn::native::d3d
Expand Down
40 changes: 20 additions & 20 deletions src/dawn/native/vulkan/SwapChainVk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
#include <utility>

#include "src/dawn/common/Compiler.h"
#include "src/dawn/common/Range.h"
#include "src/dawn/native/ChainUtils.h"
#include "src/dawn/native/Instance.h"
#include "src/dawn/native/Surface.h"
Expand Down Expand Up @@ -296,33 +295,34 @@ ResultOrError<SwapChain::Config> SwapChain::ChooseConfig(
"Vulkan SwapChain must support %s with sRGB colorspace.", config.wgpuFormat));
}

// Only the identity transform with opaque alpha is supported for now.
// Only the identity transform is supported for now.
DAWN_INVALID_IF(
(surfaceInfo.capabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) == 0,
"Vulkan SwapChain must support the identity transform.");

config.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;

config.alphaMode = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
#if !DAWN_PLATFORM_IS(ANDROID)
DAWN_INVALID_IF(
(surfaceInfo.capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) == 0,
"Vulkan SwapChain must support opaque alpha.");
#else
// TODO(dawn:286): investigate composite alpha for WebGPU native
std::array<VkCompositeAlphaFlagBitsKHR, 4u> compositeAlphaFlags = {
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,
};
for (uint32_t i : Range(4u)) {
if (surfaceInfo.capabilities.supportedCompositeAlpha & compositeAlphaFlags[i]) {
config.alphaMode = compositeAlphaFlags[i];
// Choose the Vulkan alpha mode by directly converting from the WebGPU enum. PhysicalDeviceVk
// only reports the alpha modes the surface supports, Surface.cpp resolves Auto and validates
// the rest, so the mode asked for here is always available.
switch (GetAlphaMode()) {
case wgpu::CompositeAlphaMode::Opaque:
config.alphaMode = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
break;
}
case wgpu::CompositeAlphaMode::Premultiplied:
config.alphaMode = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR;
break;
case wgpu::CompositeAlphaMode::Unpremultiplied:
config.alphaMode = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR;
break;
case wgpu::CompositeAlphaMode::Inherit:
config.alphaMode = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
break;
case wgpu::CompositeAlphaMode::Auto:
default:
DAWN_UNREACHABLE();
}
#endif // #if !DAWN_PLATFORM_IS(ANDROID)
DAWN_CHECK((surfaceInfo.capabilities.supportedCompositeAlpha & config.alphaMode) != 0);

// Choose the number of images for the swapchain= and clamp it to the min and max from the
// surface capabilities. maxImageCount = 0 means there is no limit.
Expand Down
27 changes: 23 additions & 4 deletions src/dawn/samples/ManualSurfaceTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
// This is an example to manually test surface code. Controls are the following, scoped to the
// currently focused window:
// - W: creates a new window.
// - T: creates a new window with a transparent framebuffer, to test the alpha modes.
// - L: Latches the current surface, to check what happens when the window changes but not the
// surface.
// - R: switches the rendering mode, between "The Red Triangle" and color-cycling clears that's
Expand Down Expand Up @@ -64,6 +65,14 @@
// - Config change tests:
// - Check that cycling between present modes.
// - Check that cycling between alpha modes (it sometimes produce a meaningful difference).
// - Check alpha modes on a transparent window (T) in the cycling color render mode: the clear
// is premultiplied and cycles its alpha, so Premultiplied and Unpremultiplied let the
// desktop show through and Opaque does not. On Windows this only tests the backends that
// present to the HWND: GLFW implements a transparent framebuffer with
// DwmEnableBlurBehindWindow, and the D3D backends present a premultiplied surface through a
// DirectComposition visual that composites above the window's redirection surface, which
// stays opaque. Testing those needs a window created with WS_EX_NOREDIRECTIONBITMAP, which
// GLFW does not do and which cannot be set after CreateWindowEx.
// - Check that cycling between formats works and gives the same color.
//
// - Frame throttling:
Expand All @@ -75,7 +84,6 @@
// - Check sRGB vs not sRGB gradients.
// - Check wide gamut / extended color range.
// - Check OpenGL rendering with extra usages / depth buffer / MRT.
// - Check with GLFW transparency on / off.

#include <webgpu/webgpu_cpp.h>

Expand Down Expand Up @@ -125,6 +133,7 @@ struct WindowData {
uint64_t serial = 0;

float clearCycle = 1.0f;
bool transparent = false;
bool latched = false;
bool renderTriangle = true;
uint32_t divisor = 1;
Expand Down Expand Up @@ -200,8 +209,9 @@ void SyncFromWindow(WindowData* data) {
data->targetConfig.height = std::max(1u, static_cast<uint32_t>(height) / data->divisor);
}

void AddWindow() {
void AddWindow(bool transparent = false) {
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, transparent ? GLFW_TRUE : GLFW_FALSE);
GLFWwindow* window = glfwCreateWindow(400, 400, "", nullptr, nullptr);
glfwSetKeyCallback(window, OnKeyPress);

Expand All @@ -221,6 +231,7 @@ void AddWindow() {
std::unique_ptr<WindowData> data = std::make_unique<WindowData>();
data->window = window;
data->serial = windowSerial++;
data->transparent = transparent;
data->surface = surface;
data->currentConfig = config;
data->targetConfig = config;
Expand Down Expand Up @@ -257,10 +268,14 @@ void DoRender(WindowData* data) {
data->clearCycle = 1.0f;
}

// On a transparent window cycle the alpha as well, so that the alpha modes have a
// visible effect. The color channels are premultiplied so that Premultiplied is valid.
const double alpha = data->transparent ? double{data->clearCycle} : 1.0;

dawn::utils::ComboRenderPassDescriptor desc({view});
desc.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
desc.cColorAttachments[0].clearValue = {double{data->clearCycle},
double{1.0f - data->clearCycle}, 0.0, 1.0};
desc.cColorAttachments[0].clearValue = {
alpha * double{data->clearCycle}, alpha * double{1.0f - data->clearCycle}, 0.0, alpha};

wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&desc);
pass.End();
Expand Down Expand Up @@ -316,6 +331,10 @@ void OnKeyPress(GLFWwindow* window, int key, int, int action, int) {
AddWindow();
break;

case GLFW_KEY_T:
AddWindow(/*transparent=*/true);
break;

case GLFW_KEY_L:
data->latched = !data->latched;
UpdateTitle(data);
Expand Down