From 25c4eb5efbadee36106a1cab6910b3c740bea8d2 Mon Sep 17 00:00:00 2001 From: wuggy Date: Sat, 12 Sep 2026 08:57:32 -0700 Subject: [PATCH] [WIP] D3D11 gpu accel --- gfx/layers/d3d11/CompositorD3D11.cpp | 61 ++++++++++ gfx/layers/d3d11/CompositorD3D11.h | 3 + gfx/layers/d3d11/GpuRasterD3D11.c | 84 ++++++++++++++ gfx/layers/d3d11/GpuRasterD3D11.h | 36 ++++++ gfx/layers/moz.build | 1 + gfx/tests/gtest/TestGpuRasterD3D11.cpp | 148 +++++++++++++++++++++++++ gfx/tests/gtest/moz.build | 3 + gfx/thebes/gfxPrefs.h | 1 + modules/libpref/init/all.js | 2 + 9 files changed, 339 insertions(+) create mode 100644 gfx/layers/d3d11/GpuRasterD3D11.c create mode 100644 gfx/layers/d3d11/GpuRasterD3D11.h create mode 100644 gfx/tests/gtest/TestGpuRasterD3D11.cpp diff --git a/gfx/layers/d3d11/CompositorD3D11.cpp b/gfx/layers/d3d11/CompositorD3D11.cpp index 121794840f..58befb3fca 100644 --- a/gfx/layers/d3d11/CompositorD3D11.cpp +++ b/gfx/layers/d3d11/CompositorD3D11.cpp @@ -7,6 +7,7 @@ #include "TextureD3D11.h" #include "CompositorD3D11Shaders.h" +#include "GpuRasterD3D11.h" #include "gfxWindowsPlatform.h" #include "nsIWidget.h" @@ -143,6 +144,7 @@ private: CompositorD3D11::CompositorD3D11(CompositorBridgeParent* aParent, widget::CompositorWidget* aWidget) : Compositor(aWidget, aParent) + , mGpuRasterViewport(false) , mAttachments(nullptr) , mHwnd(nullptr) , mDisableSequenceForNextFrame(false) @@ -199,6 +201,16 @@ CompositorD3D11::Initialize(nsCString* const out_failureReason) mFeatureLevel = mDevice->GetFeatureLevel(); + D3D11_FEATURE_DATA_D3D11_OPTIONS options = {}; + if (gfxPrefs::D3D11GpuRectangleFills() && + mFeatureLevel >= D3D_FEATURE_LEVEL_10_0 && + SUCCEEDED(mDevice->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS, + &options, sizeof(options))) && + options.ClearView) { + mContext->QueryInterface(__uuidof(ID3D11DeviceContext1), + getter_AddRefs(mGpuRasterContext)); + } + mHwnd = mWidget->AsWindows()->GetHwnd(); memset(&mVSConstants, 0, sizeof(VertexShaderConstants)); @@ -656,6 +668,20 @@ CompositorD3D11::GetPSForEffect(Effect* aEffect, MaskType aMaskType) void CompositorD3D11::ClearRect(const gfx::Rect& aRect) { + if (mGpuRasterContext && mGpuRasterViewport && mCurrentRT && + !mCurrentRT->HasComplexProjection()) { + GpuRasterRect rect = { aRect.x, aRect.y, aRect.XMost(), aRect.YMost() }; + IntSize size = mCurrentRT->GetSize(); + D3D11_RECT clip = { 0, 0, size.width, size.height }; + const float clear[4] = { 0, 0, 0, 0 }; + if (GpuRasterD3D11FillRect(mGpuRasterContext, mCurrentRT->mRTView, + size.width, size.height, &rect, &clip, clear)) { + // BeginFrame relies on ClearRect to establish premultiplied blending. + mContext->OMSetBlendState(mAttachments->mPremulBlendState, sBlendFactor, 0xFFFFFFFF); + return; + } + } + mContext->OMSetBlendState(mAttachments->mDisabledBlendState, sBlendFactor, 0xFFFFFFFF); Matrix4x4 identity; @@ -736,6 +762,39 @@ CompositorD3D11::DrawQuad(const gfx::Rect& aRect, MOZ_ASSERT(mCurrentRT, "No render target"); + // Opaque, integer-aligned translated rectangles can be filled directly by + // the GPU without shader binding or per-quad constant-buffer uploads. + if (mGpuRasterContext && mGpuRasterViewport && !mCurrentRT->HasComplexProjection() && + aEffectChain.mPrimaryEffect->mType == EffectTypes::SOLID_COLOR && + !aEffectChain.mSecondaryEffects[EffectTypes::MASK] && + !aEffectChain.mSecondaryEffects[EffectTypes::BLEND_MODE] && + aOpacity == 1.0f && aTransform.Is2D() && aTransform.As2D().IsTranslation()) { + const Color& color = static_cast(aEffectChain.mPrimaryEffect.get())->mColor; + if (color.a == 1.0f) { + IntPoint origin = mCurrentRT->GetOrigin(); + // Transform both edges in the shader's order before subtracting the + // target origin; moving x/y and then adding width/height can round + // differently for large coordinates. + float left = aRect.x + aTransform._41; + float right = aRect.XMost() + aTransform._41; + float top = aRect.y + aTransform._42; + float bottom = aRect.YMost() + aTransform._42; + GpuRasterRect rect = { left - origin.x, top - origin.y, + right - origin.x, bottom - origin.y }; + IntRect clipRect = aClipRect; + if (mCurrentRT == mDefaultRT) { + clipRect = clipRect.Intersect(mCurrentClip); + } + D3D11_RECT clip = { clipRect.x, clipRect.y, clipRect.XMost(), clipRect.YMost() }; + IntSize size = mCurrentRT->GetSize(); + const float fill[4] = { color.r, color.g, color.b, color.a }; + if (GpuRasterD3D11FillRect(mGpuRasterContext, mCurrentRT->mRTView, + size.width, size.height, &rect, &clip, fill)) { + return; + } + } + } + memcpy(&mVSConstants.layerTransform, &aTransform._11, 64); IntPoint origin = mCurrentRT->GetOrigin(); mVSConstants.renderTargetOffset[0] = origin.x; @@ -1193,6 +1252,7 @@ CompositorD3D11::PrepareViewport(const gfx::IntSize& aSize) projection._33 = 0.0f; PrepareViewport(aSize, projection, 0.0f, 1.0f); + mGpuRasterViewport = true; } void @@ -1213,6 +1273,7 @@ CompositorD3D11::PrepareViewport(const gfx::IntSize& aSize, const gfx::Matrix4x4& aProjection, float aZNear, float aZFar) { + mGpuRasterViewport = false; D3D11_VIEWPORT viewport; viewport.MaxDepth = aZFar; viewport.MinDepth = aZNear; diff --git a/gfx/layers/d3d11/CompositorD3D11.h b/gfx/layers/d3d11/CompositorD3D11.h index ca775cd60b..d26a3d6916 100644 --- a/gfx/layers/d3d11/CompositorD3D11.h +++ b/gfx/layers/d3d11/CompositorD3D11.h @@ -11,6 +11,7 @@ #include "mozilla/layers/Compositor.h" #include "TextureD3D11.h" #include +#include class nsWidget; @@ -174,6 +175,8 @@ private: RefPtr* aOutView); RefPtr mContext; + RefPtr mGpuRasterContext; + bool mGpuRasterViewport; RefPtr mDevice; RefPtr mSwapChain; RefPtr mDefaultRT; diff --git a/gfx/layers/d3d11/GpuRasterD3D11.c b/gfx/layers/d3d11/GpuRasterD3D11.c new file mode 100644 index 0000000000..6e85300502 --- /dev/null +++ b/gfx/layers/d3d11/GpuRasterD3D11.c @@ -0,0 +1,84 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#define COBJMACROS +#include "GpuRasterD3D11.h" + +#include + +static BOOL +IntegerEdge(double value) +{ + /* Ordered comparisons reject NaNs and infinities before integer conversion. */ + return value >= LONG_MIN && value <= LONG_MAX && value == (LONG)value; +} + +BOOL +GpuRasterD3D11ClipRect(const GpuRasterRect* rect, const D3D11_RECT* clip, + LONG width, LONG height, D3D11_RECT* result) +{ + LONG left, top, right, bottom; + if (!rect || !clip || !result || width <= 0 || height <= 0 || + !IntegerEdge(rect->left) || !IntegerEdge(rect->top) || + !IntegerEdge(rect->right) || !IntegerEdge(rect->bottom) || + rect->right < rect->left || rect->bottom < rect->top) { + return FALSE; + } + + left = (LONG)rect->left; + top = (LONG)rect->top; + right = (LONG)rect->right; + bottom = (LONG)rect->bottom; + if (left < clip->left) left = clip->left; + if (top < clip->top) top = clip->top; + if (right > clip->right) right = clip->right; + if (bottom > clip->bottom) bottom = clip->bottom; + if (left < 0) left = 0; + if (top < 0) top = 0; + if (right > width) right = width; + if (bottom > height) bottom = height; + + if (right <= left || bottom <= top) { + result->left = result->top = result->right = result->bottom = 0; + } else { + result->left = left; + result->top = top; + result->right = right; + result->bottom = bottom; + } + return TRUE; +} + +BOOL +GpuRasterD3D11FillRect(ID3D11DeviceContext1* context, + ID3D11RenderTargetView* target, + LONG width, LONG height, + const GpuRasterRect* rect, const D3D11_RECT* clip, + const float color[4]) +{ + D3D11_RECT clipped; + D3D11_RENDER_TARGET_VIEW_DESC desc; + unsigned i; + if (!context || !target || !color || + !GpuRasterD3D11ClipRect(rect, clip, width, height, &clipped)) { + return FALSE; + } + for (i = 0; i < 4; ++i) { + if (!(color[i] >= 0.0f && color[i] <= 1.0f)) { + return FALSE; + } + } + + ID3D11RenderTargetView_GetDesc(target, &desc); + if ((desc.Format != DXGI_FORMAT_B8G8R8A8_UNORM && + desc.Format != DXGI_FORMAT_R8G8B8A8_UNORM) || + desc.ViewDimension != D3D11_RTV_DIMENSION_TEXTURE2D || + desc.Texture2D.MipSlice != 0) { + return FALSE; + } + if (clipped.right != clipped.left && clipped.bottom != clipped.top) { + ID3D11DeviceContext1_ClearView(context, (ID3D11View*)target, color, &clipped, 1); + } + return TRUE; +} diff --git a/gfx/layers/d3d11/GpuRasterD3D11.h b/gfx/layers/d3d11/GpuRasterD3D11.h new file mode 100644 index 0000000000..ae7c219e69 --- /dev/null +++ b/gfx/layers/d3d11/GpuRasterD3D11.h @@ -0,0 +1,36 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef GFX_GPU_RASTER_D3D11_H +#define GFX_GPU_RASTER_D3D11_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Device-space edges. Fractional or non-finite edges require normal drawing. */ +typedef struct GpuRasterRect { + double left, top, right, bottom; +} GpuRasterRect; + +/* FALSE means unsupported input; TRUE may produce an empty clipped rectangle. */ +BOOL GpuRasterD3D11ClipRect(const GpuRasterRect* rect, const D3D11_RECT* clip, + LONG width, LONG height, D3D11_RECT* result); + +/* Source replacement, not alpha blending. The caller must check ClearView + * device support and supply the dimensions of the render target. No pipeline + * bindings are changed. FALSE leaves the target untouched for shader fallback. */ +BOOL GpuRasterD3D11FillRect(ID3D11DeviceContext1* context, + ID3D11RenderTargetView* target, + LONG width, LONG height, + const GpuRasterRect* rect, const D3D11_RECT* clip, + const float color[4]); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/gfx/layers/moz.build b/gfx/layers/moz.build index dfec86adbf..1b081a780b 100644 --- a/gfx/layers/moz.build +++ b/gfx/layers/moz.build @@ -80,6 +80,7 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': ] SOURCES += [ 'd3d11/CompositorD3D11.cpp', + 'd3d11/GpuRasterD3D11.c', 'd3d11/ReadbackManagerD3D11.cpp', ] UNIFIED_SOURCES += [ diff --git a/gfx/tests/gtest/TestGpuRasterD3D11.cpp b/gfx/tests/gtest/TestGpuRasterD3D11.cpp new file mode 100644 index 0000000000..27670ecc65 --- /dev/null +++ b/gfx/tests/gtest/TestGpuRasterD3D11.cpp @@ -0,0 +1,148 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef NOMINMAX +# define NOMINMAX +#endif +#include "d3d11/GpuRasterD3D11.h" + +#include +#include +#include + +#ifndef GPU_RASTER_STANDALONE +# include "gtest/gtest.h" +#endif + +#define CHECK_GPU(condition) do { \ + if (!(condition)) { fprintf(stderr, "GPU rectangle test failed at line %d\n", __LINE__); \ + return false; } \ +} while (0) + +static bool TestGpuRectangleClipping() +{ + D3D11_RECT clip = { 2, 3, 12, 13 }, output; + GpuRasterRect rect = { -4, -5, 20, 21 }; + CHECK_GPU(GpuRasterD3D11ClipRect(&rect, &clip, 10, 10, &output)); + CHECK_GPU(output.left == 2 && output.top == 3 && output.right == 10 && output.bottom == 10); + rect = { 20, 20, 25, 25 }; + CHECK_GPU(GpuRasterD3D11ClipRect(&rect, &clip, 10, 10, &output)); + CHECK_GPU(output.left == output.right && output.top == output.bottom); + rect = { 1.5, 2, 4, 5 }; + CHECK_GPU(!GpuRasterD3D11ClipRect(&rect, &clip, 10, 10, &output)); + rect.left = std::numeric_limits::quiet_NaN(); + CHECK_GPU(!GpuRasterD3D11ClipRect(&rect, &clip, 10, 10, &output)); + rect.left = std::numeric_limits::infinity(); + CHECK_GPU(!GpuRasterD3D11ClipRect(&rect, &clip, 10, 10, &output)); + rect.left = 2147483648.0; + CHECK_GPU(!GpuRasterD3D11ClipRect(&rect, &clip, 10, 10, &output)); + rect = { 6, 2, 4, 5 }; + CHECK_GPU(!GpuRasterD3D11ClipRect(&rect, &clip, 10, 10, &output)); + CHECK_GPU(!GpuRasterD3D11ClipRect(nullptr, &clip, 10, 10, &output)); + CHECK_GPU(!GpuRasterD3D11ClipRect(&rect, &clip, 0, 10, &output)); + return true; +} + +#ifdef GPU_RASTER_STANDALONE +struct GpuRasterTestDevice { + ID3D11Device* device = nullptr; + ID3D11DeviceContext* context = nullptr; + ID3D11DeviceContext1* context1 = nullptr; + ID3D11Texture2D* texture = nullptr; + ID3D11Texture2D* readback = nullptr; + ID3D11RenderTargetView* view = nullptr; + + ~GpuRasterTestDevice() { + if (view) view->Release(); + if (readback) readback->Release(); + if (texture) texture->Release(); + if (context1) context1->Release(); + if (context) context->Release(); + if (device) device->Release(); + } +}; + +static bool TestGpuRectanglePixels(DXGI_FORMAT format) +{ + GpuRasterTestDevice gpu; + CHECK_GPU(SUCCEEDED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, + 0, nullptr, 0, D3D11_SDK_VERSION, + &gpu.device, nullptr, &gpu.context))); + CHECK_GPU(SUCCEEDED(gpu.context->QueryInterface(__uuidof(ID3D11DeviceContext1), + (void**)&gpu.context1))); + D3D11_FEATURE_DATA_D3D11_OPTIONS options = {}; + CHECK_GPU(SUCCEEDED(gpu.device->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS, + &options, sizeof(options)))); + CHECK_GPU(options.ClearView); + D3D11_TEXTURE2D_DESC desc = {}; + desc.Width = desc.Height = 16; + desc.MipLevels = desc.ArraySize = desc.SampleDesc.Count = 1; + desc.Format = format; + desc.BindFlags = D3D11_BIND_RENDER_TARGET; + CHECK_GPU(SUCCEEDED(gpu.device->CreateTexture2D(&desc, nullptr, &gpu.texture))); + CHECK_GPU(SUCCEEDED(gpu.device->CreateRenderTargetView(gpu.texture, nullptr, &gpu.view))); + desc.Usage = D3D11_USAGE_STAGING; + desc.BindFlags = 0; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + CHECK_GPU(SUCCEEDED(gpu.device->CreateTexture2D(&desc, nullptr, &gpu.readback))); + + const float blue[4] = { 0, 0, 1, 1 }, red[4] = { 1, 0, 0, 1 }, clear[4] = { 0, 0, 0, 0 }; + gpu.context->ClearRenderTargetView(gpu.view, blue); + GpuRasterRect rect = { -3, 2, 12, 20 }; + D3D11_RECT clip = { 3, 4, 10, 11 }; + // ClearView must use the explicit clip, not the context's scissor state. + D3D11_RECT scissor = { 0, 0, 1, 1 }; + gpu.context->RSSetScissorRects(1, &scissor); + gpu.context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST); + CHECK_GPU(GpuRasterD3D11FillRect(gpu.context1, gpu.view, 16, 16, &rect, &clip, red)); + rect = { 5, 5, 7, 7 }; + clip = { 0, 0, 16, 16 }; + CHECK_GPU(GpuRasterD3D11FillRect(gpu.context1, gpu.view, 16, 16, &rect, &clip, clear)); + rect = { 50, 50, 60, 60 }; + CHECK_GPU(GpuRasterD3D11FillRect(gpu.context1, gpu.view, 16, 16, &rect, &clip, clear)); + rect = { 0.5, 0, 16, 16 }; + CHECK_GPU(!GpuRasterD3D11FillRect(gpu.context1, gpu.view, 16, 16, &rect, &clip, clear)); + CHECK_GPU(!GpuRasterD3D11FillRect(nullptr, gpu.view, 16, 16, &rect, &clip, clear)); + + D3D11_PRIMITIVE_TOPOLOGY topology; + gpu.context->IAGetPrimitiveTopology(&topology); + CHECK_GPU(topology == D3D11_PRIMITIVE_TOPOLOGY_POINTLIST); + UINT count = 1; + gpu.context->RSGetScissorRects(&count, &scissor); + CHECK_GPU(count == 1 && scissor.left == 0 && scissor.top == 0 && + scissor.right == 1 && scissor.bottom == 1); + + gpu.context->CopyResource(gpu.readback, gpu.texture); + D3D11_MAPPED_SUBRESOURCE map; + CHECK_GPU(SUCCEEDED(gpu.context->Map(gpu.readback, 0, D3D11_MAP_READ, 0, &map))); + bool equal = true; + for (unsigned y = 0; y < 16; ++y) { + const uint32_t* row = reinterpret_cast( + static_cast(map.pData) + y * map.RowPitch); + for (unsigned x = 0; x < 16; ++x) { + bool isRed = x >= 3 && x < 10 && y >= 4 && y < 11; + uint32_t expected = (isRed == (format == DXGI_FORMAT_R8G8B8A8_UNORM)) + ? 0xff0000ff : 0xffff0000; + if (x >= 5 && x < 7 && y >= 5 && y < 7) expected = 0; + equal = equal && row[x] == expected; + } + } + gpu.context->Unmap(gpu.readback, 0); + CHECK_GPU(equal); + return true; +} + +int main() +{ + return TestGpuRectangleClipping() && + TestGpuRectanglePixels(DXGI_FORMAT_R8G8B8A8_UNORM) && + TestGpuRectanglePixels(DXGI_FORMAT_B8G8R8A8_UNORM) ? 0 : 1; +} +#else +TEST(GpuRasterD3D11, ClipRect) { EXPECT_TRUE(TestGpuRectangleClipping()); } +// WARP/Context1 is not available on every supported Windows installation. +// Run the standalone pixel tests on Windows 8+ with a D3D11.1 runtime. +#endif + +#undef CHECK_GPU diff --git a/gfx/tests/gtest/moz.build b/gfx/tests/gtest/moz.build index 66851abc70..ea1102f71d 100644 --- a/gfx/tests/gtest/moz.build +++ b/gfx/tests/gtest/moz.build @@ -53,6 +53,9 @@ LOCAL_INCLUDES += [ FINAL_LIBRARY = 'xul-gtest' +if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows' and CONFIG['MOZ_ENABLE_D3D10_LAYER']: + SOURCES += ['TestGpuRasterD3D11.cpp'] + CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS'] if CONFIG['GNU_CXX']: diff --git a/gfx/thebes/gfxPrefs.h b/gfx/thebes/gfxPrefs.h index eb447c5667..adab38e25b 100644 --- a/gfx/thebes/gfxPrefs.h +++ b/gfx/thebes/gfxPrefs.h @@ -468,6 +468,7 @@ private: DECL_GFX_PREF(Once, "layers.componentalpha.enabled", ComponentAlphaEnabled, bool, true); DECL_GFX_PREF(Live, "layers.composer2d.enabled", Composer2DCompositionEnabled, bool, false); DECL_GFX_PREF(Once, "layers.d3d11.force-warp", LayersD3D11ForceWARP, bool, false); + DECL_GFX_PREF(Once, "layers.d3d11.gpu-rectangle-fills.enabled", D3D11GpuRectangleFills, bool, false); DECL_GFX_PREF(Live, "layers.deaa.enabled", LayersDEAAEnabled, bool, false); DECL_GFX_PREF(Live, "layers.draw-bigimage-borders", DrawBigImageBorders, bool, false); DECL_GFX_PREF(Live, "layers.draw-borders", DrawLayerBorders, bool, false); diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 5e124ff937..1df142b5e5 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -4570,6 +4570,8 @@ pref("gfx.direct2d.force-enabled", false); pref("layers.prefer-opengl", false); pref("layers.prefer-d3d9", false); +// Experimental C GPU rectangle path. Requires D3D11.1 ClearView support and a restart. +pref("layers.d3d11.gpu-rectangle-fills.enabled", false); // Enable fallback if d3d11 can't be used. See bug #1262187 pref("layers.allow-d3d9-fallback", true); #endif