1

Context

I have 2 big projects I'm working on: a game engine, and a file explorer replacement. My focus is Windows only. I know they're 2 huge things I'll likely never complete, I just enjoy coding during free time.

Across my many projects I started building up a collection of utilities and essentially made my own library. I'm actually enjoying writing generalized stuff.

That's the tale of how I started trying to make a generic-ish windowing framework for Windows.

A big requirement that libs like glfw and SFML are missing is modularity; my windows are made up of multiple modules, which avoids having a ginormous procedure function that handles everything, and lets me extend the window with future specific modules (I was working on a vulkan module, now i'm working on a direct2d module). Hence all the code you'll be seeing in my snippets related to HWNDs and window procecdures only handles drawing-related messages, which I hope will make navigation easier. Don't worry about the absence of all the WM_CREATE and other kind of messages unrelated to drawing in these snippets.

For the game engine all I need is "updated" direct2d drawing, so with the newer DeviceContext that goes through DXGISwapChain etcc instead of the older RenderTarget. For the file explorer I "need" (well, ok, want) window transparency, which with the older RenderTarget "just works" (you pass it the ALPHA_PREMULTIPLIED flag and everything is nice). I can also get a DeviceContext from the older RenderTarget as answered by Paint.NET's creator here; that gives me access to newer functions even though the context was cast from the older RenderTarget. Important: this approach gives me no control over which device that context is associated with.

For the file explorer I also wish to share resources across multiple windows, and the older RenderTarget doesn't seem to allow for that. Resources will be bound to the individual RenderTarget, while with DeviceContexts the resources should be bound to a Device and can be accessed by all the contexts associated with the same Device (at least for my understanding so far).

Q: correct me if I'm wrong about last sentence^

Where the pain begun

Here comes the dreaded issue: creating a swapchain with CreateSwapChainForHWND does not support any ALPHA_* option besides ALPHA_NONE. So if I try to draw on a window the DeviceContext and retain control of which Device creates them I need to find another way.

In short:

  1. One window + DeviceContext newer functions + transparency -> use RenderTarget and cast to DeviceContext
  2. Resources shared across multiple windows + DeviceContext newer functions -> use DXGISwapChain + DeviceContext
  3. Resources shared across multiple windows + DeviceContext newer functions + transparency -> ¯\_(ツ)_/¯ + ???

The debug layer told me CreateSwapChainForComposition does support ALPHA_PREMULTIPLIED, but at that point a whole separate topic of Windows APIs seems to open up, and I've already a huge chunk of DirectX to learn, so I'd rather avoid further months-long-derailings. But it seems there's no avoiding this issue.

Anyway, I created my CreateSwapChainForComposition swapchain and... That function takes no HWND parameter! So I assume I did create a swapchain somewhere, and direct2d calls are drawing on that swapchain, but the content of that swapchain never reaches the actual window, unless I do some magic with the compoisition APIs perhaps? Then I started giving a look at those composition APIs and... pretty much everything assumes you're working on an higher level framework, mostly XAML, which I'm not.

The closest thing I found in the docs is this

However that example retreives a direct2d device context from the composition APIs through BeginDraw... there's no swapchain in sight. And even worse this brings me back to the same issue I had with RenderTarget: If I've no guarantee the DeviceContexts are created from the same Device, so I cannot share resources across multiple windows. Plus having to learn a new whole topic just to have 2 things working together that I already managed to make work individually really discouraged me.

My code, notes

Notes


d2d::*, dxgi::*, d3d::* classes are just wrappers around MS's ComPtr where I gave various classes a throwing constructor that wraps the "return HRESULT, reference actual return object" functions from MS.

the procedure_result return value of my window modules procedures exist because multiple modules can work on the same message, the behaviour is as follows:

  • procedure_result.next() this module didn't return any value, let next module evaluate the message.
  • procedure_result.stop(value) this module returned value, no other module will evaluate the message.
  • procedure_result.next(value) this module returned value, let next module evaluate the message.

After all modules have processed the message, or after one module called stop(), return the value returned by the last module that evaluated the current message, or pass to the default window procedure if no module returned any value.

I use whitesmiths indentation. I know it's not really widespread but please don't kill me for it :)

If you want to try my code, the project expects you have my CPP_Utils repository in the same directory as follows:

root
    > CPP_Utilities_MS //the project we're discussing
        > .git
        > ...
    > CPP_Utilities
        > .git
        > ...

The CPP_Utilities repository is entirely header-only because screw compile times, so no need to link .libs or add .dlls

For record-keeping reasons I'll leave links to the last commit to the date this question was asked:

...now to the actual directly meaningful code:

How I make my window transparent


inline bool make_glass_CompositionAttribute(HWND hwnd)
    {
    if (HMODULE hUser = GetModuleHandleA("user32.dll"))
        {
        //Windows >= 10
        pfnSetWindowCompositionAttribute SetWindowCompositionAttribute = (pfnSetWindowCompositionAttribute)GetProcAddress(hUser, "SetWindowCompositionAttribute");
        if (SetWindowCompositionAttribute)
            {
            ACCENT_POLICY accent = {ACCENT_ENABLE_BLURBEHIND, 0, 0, 0};

            WINDOWCOMPOSITIONATTRIBDATA data;
            data.Attrib = WCA_ACCENT_POLICY;
            data.pvData = &accent;
            data.cbData = sizeof(accent);
            SetWindowCompositionAttribute(hwnd, &data);
            return true;
            }
        }
    return false;
    }

How I create my RenderTarget and get a DeviceContext out of it

used for (1): One window + DeviceContext newer functions + transparency

Create d2d factory


namespace d2d
    {
    struct factory : details::com_ptr<ID2D1Factory6>
        {
        using com_ptr::com_ptr;
        factory() : com_ptr{[]
            {
            D2D1_FACTORY_OPTIONS options
                {
                .debugLevel{details::enable_debug_layer ? D2D1_DEBUG_LEVEL_INFORMATION : D2D1_DEBUG_LEVEL_NONE}
                };

            self_t ret{nullptr};

            details::throw_if_failed(D2D1CreateFactory
                (
                D2D1_FACTORY_TYPE_SINGLE_THREADED,
                options,
                ret.address_of()
                ));
            return ret;
            }()} {}
        };
    }

Create RenderTarget from d2d factory and HWND


namespace d2d
    {
    struct hwnd_render_target : details::com_ptr<ID2D1HwndRenderTarget>
        {
        using com_ptr::com_ptr;
        hwnd_render_target(const factory& factory, const HWND& hwnd) : com_ptr{[&factory, &hwnd]
            {
            self_t ret{nullptr};
            D2D1_RENDER_TARGET_PROPERTIES properties
                {
                .type{D2D1_RENDER_TARGET_TYPE_DEFAULT},
                .pixelFormat
                    {
                    .format{DXGI_FORMAT_UNKNOWN},
                    .alphaMode{D2D1_ALPHA_MODE_PREMULTIPLIED}
                    }
                };

            details::throw_if_failed(factory->CreateHwndRenderTarget(properties, D2D1::HwndRenderTargetProperties(hwnd), ret.address_of()));
            return ret;
            }()} {}
        };
    }

Create DeviceContext from RenderTarget


namespace d2d
    {
    class device_context : public details::com_ptr<ID2D1DeviceContext5>
        {
        public:
            using com_ptr::com_ptr;
            device_context(const d2d::hwnd_render_target& hwnd_rt) : com_ptr{create(hwnd_rt   )} {}

        private:
            inline static self_t create(const d2d::hwnd_render_target& hwnd_rt)
                {
                return hwnd_rt.as<interface_type>();
                }
        };
    }

Window module that makes use of all that


//window modules file/namespace
class render_target : public utils::MS::window::module
    {
    public:
        using on_draw_signature = void(utils::MS::window::base&, const d2d::device_context&);

        struct create_info
            {
            using module_type = render_target;
            
            const d2d::factory& d2d_factory;
            std::function<on_draw_signature> on_render;
            
            //adds the WS_EX_NOREDIRECTIONBITMAP flag to the flags used to create the base window
            inline void adjust_base_create_info(utils::MS::window::base::create_info& base_create_info) const noexcept
                {
                base_create_info.style_ex |= WS_EX_NOREDIRECTIONBITMAP;
                }
            };

        render_target(utils::MS::window::base& base, create_info create_info) :
            module{base},
            on_render{create_info.on_render},
            d2d_hwnd_rt{create_info.d2d_factory, get_base().get_handle()},
            d2d_device_context{d2d_hwnd_rt}
            {
            }

        std::function<on_draw_signature> on_render;

        void present() noexcept
            {
            }

    private:
        d2d::hwnd_render_target d2d_hwnd_rt;
        d2d::device_context d2d_device_context;

        virtual utils::MS::window::procedure_result procedure(UINT msg, WPARAM wparam, LPARAM lparam) override
            {
            switch (msg)
                {
                case WM_SIZE:
                    on_resize({LOWORD(lparam), HIWORD(lparam)});
                    return utils::MS::window::procedure_result::next(0);

                case WM_DISPLAYCHANGE:
                    //InvalidateRect(get_handle(), NULL, FALSE);
                    break;
                
                case WM_ERASEBKGND: return utils::MS::window::procedure_result::stop(1);

                case WM_PAINT:
                    if (on_render)
                        {
                        on_render(get_base(), d2d_device_context);
                        ValidateRect(get_base().get_handle(), NULL);
                        return utils::MS::window::procedure_result::next(0);
                        }
                    break;

                }

            return utils::MS::window::procedure_result::next();
            }

        void on_resize(utils::math::vec2u size)
            {
            d2d_hwnd_rt->Resize({size.x, size.y});
            }
    };

How I create my DeviceContext from a DXGISwapChain

used for (2): Resources shared across multiple windows + DeviceContext newer functions, does NOT support transparency

Create d3d device


namespace d3d
    {
    class device : public details::com_ptr<ID3D11Device2>
        {
        public:
            using com_ptr::com_ptr;
            device() : com_ptr{create()} {}

        private:
            inline static self_t create()
                {
                details::com_ptr<ID3D11Device> base_device;

                UINT creation_flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
                if constexpr (details::enable_debug_layer)
                    {
                    // If the project is in a debug build, enable debugging via SDK Layers with this flag.
                    creation_flags |= D3D11_CREATE_DEVICE_DEBUG;
                    }

                D3D_FEATURE_LEVEL feature_level_created;

                std::array<D3D_DRIVER_TYPE, 3> attempts{D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP, D3D_DRIVER_TYPE_SOFTWARE};
                HRESULT last{S_FALSE};
                for (const auto& attempt : attempts)
                    {
                    last = D3D11CreateDevice
                    (
                        nullptr,                  // specify null to use the default adapter
                        attempt,
                        0,
                        creation_flags,           // optionally set debug and Direct2D compatibility flags
                        nullptr,                  // use the lastest feature level
                        0,                        // use the lastest feature level
                        D3D11_SDK_VERSION,
                        base_device.address_of(), // returns the Direct3D device created
                        &feature_level_created,   // returns feature level of device created
                        nullptr
                    );
                    if (details::succeeded(last)) { break; }
                    }
                details::throw_if_failed(last);
                return base_device.as<self_t>();
                }
        };
    }

Get dxgi device from d3d device


namespace dxgi
    {
    struct device : details::com_ptr<IDXGIDevice3>
        {
        using com_ptr::com_ptr;
        device(const d3d::device& d3d_device) : com_ptr{[&d3d_device]
            {
            return d3d_device.as<interface_type>();
            }()} {}
        };
    }

Create dxgi swapchain from dxgi device for HWND

namespace dxgi
    {
    class swap_chain : public details::com_ptr<IDXGISwapChain1>
        {
        public:
            swap_chain(const dxgi::device& dxgi_device, HWND hwnd) : com_ptr{create(dxgi_device, hwnd)} {}

            void resize(utils::math::vec2u size)
                {
                HRESULT hresult{get()->ResizeBuffers(2, size.x, size.y, DXGI_FORMAT_B8G8R8A8_UNORM, 0)};
                if (hresult == DXGI_ERROR_DEVICE_REMOVED || hresult == DXGI_ERROR_DEVICE_RESET)
                    {
                    throw std::runtime_error("Device removed or reset");
                    }
                else { details::throw_if_failed(hresult); }
                }

            void present()
                {
                HRESULT hresult{get()->Present(1, 0)};
                if (hresult == DXGI_ERROR_DEVICE_REMOVED || hresult == DXGI_ERROR_DEVICE_RESET)
                    {
                    throw std::runtime_error("Device removed or reset");
                    }
                else { details::throw_if_failed(hresult); }
                }

        private:
            inline static self_t create(const dxgi::device& dxgi_device, HWND hwnd)
                {
                RECT client_rect{0, 0, 0, 0};
                GetClientRect(hwnd, &client_rect);
                utils::math::rect<long> rectl{.ll{client_rect.left}, .up{client_rect.top}, .rr{client_rect.right}, .dw{client_rect.bottom}};

                details::com_ptr<IDXGIAdapter> dxgi_adapter;
                details::throw_if_failed(dxgi_device->GetAdapter(dxgi_adapter.address_of()));

                details::com_ptr<IDXGIFactory2> dxgi_factory;
                details::throw_if_failed(dxgi_adapter->GetParent(IID_PPV_ARGS(dxgi_factory.address_of())));

                DXGI_SWAP_CHAIN_DESC1 desc
                    {
                    .Width      {static_cast<UINT>(rectl.w())},
                    .Height     {static_cast<UINT>(rectl.h())},
                    .Format     {DXGI_FORMAT_B8G8R8A8_UNORM},
                    .Stereo     {false},
                    .SampleDesc
                        {
                        .Count  {1},
                        .Quality{0}
                        },
                    .BufferUsage {DXGI_USAGE_RENDER_TARGET_OUTPUT},
                    .BufferCount {2},
                    .Scaling     {DXGI_SCALING_NONE},
                    .SwapEffect  {DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL},
                    .AlphaMode   {DXGI_ALPHA_MODE_IGNORE}, 
                    //.AlphaMode {DXGI_ALPHA_MODE_PREMULTIPLIED}, // I wish it was this easy!
                    .Flags       {0},
                    };
                DXGI_SWAP_CHAIN_FULLSCREEN_DESC desc_fullscreen
                    {
                    .RefreshRate{.Numerator{1}, .Denominator{0}},
                    .Scaling     {DXGI_MODE_SCALING_CENTERED},
                    };
            
                self_t ret{nullptr};
                details::throw_if_failed(dxgi_factory->CreateSwapChainForHwnd(dxgi_device.get(), hwnd, &desc, &desc_fullscreen, nullptr, ret.address_of()));

                dxgi_device->SetMaximumFrameLatency(1);

                return ret;
                }
        }
    }

Create d2d device from dxgi device, and create d2d device context from d2d device


namespace d2d
    {
    class device : public details::com_ptr<ID2D1Device5>
        {
        public:
            using com_ptr::com_ptr;
            device(const d2d::factory& d2d_factory, const dxgi::device& dxgi_device) : com_ptr{create(d2d_factory, dxgi_device)} {}

            dxgi::device get_dxgi_device() const noexcept 
                {
                details::com_ptr<IDXGIDevice> ret{nullptr};
                details::throw_if_failed(get()->GetDxgiDevice(ret.address_of()));
                return ret.as<dxgi::device>();
                }

        private:
            inline static self_t create(const d2d::factory& d2d_factory, const dxgi::device& dxgi_device)
                {
                self_t ret{nullptr};
                details::throw_if_failed(d2d_factory->CreateDevice(dxgi_device.get(), ret.address_of()));
                return ret;
                }
        };
        
    class device_context : public details::com_ptr<ID2D1DeviceContext5>
        {
        public:
            using com_ptr::com_ptr;
            device_context(const d2d::device& d2d_device) : com_ptr{create(d2d_device)} {}

        private:
            inline static self_t create(const d2d::device& d2d_device)
                {
                self_t ret{nullptr};
                details::throw_if_failed(d2d_device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, ret.address_of()));
                return ret;
                }
        };
    }

Create d2d bitmap from d2d device context and dxgi swapchain


namespace d2d
    {
    class bitmap : public details::com_ptr<ID2D1Bitmap1>
        {
        public:
            using com_ptr::com_ptr;
            bitmap(const d2d::device_context& d2d_device_context, const dxgi::swap_chain& dxgi_swapchain) : com_ptr{create(d2d_device_context, dxgi_swapchain)} {}

        private:
            inline static self_t create(const d2d::device_context& d2d_device_context, const dxgi::swap_chain& dxgi_swapchain)
                {
                //details::com_ptr<ID3D11Texture2D> d3d_texture_back_buffer;
                //details::throw_if_failed(dxgi_swapchain->GetBuffer(0, IID_PPV_ARGS(d3d_texture_back_buffer.address_of())));

                details::com_ptr<IDXGISurface2> dxgi_back_buffer;
                details::throw_if_failed(dxgi_swapchain->GetBuffer(0, IID_PPV_ARGS(dxgi_back_buffer.address_of())));

                D2D1_BITMAP_PROPERTIES1 properties
                    {
                    .pixelFormat{DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED},
                    .dpiX{1},//TODO dpi stuff
                    .dpiY{1},
                    .bitmapOptions{D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW},
                    };

                self_t ret{nullptr};
                details::throw_if_failed(d2d_device_context->CreateBitmapFromDxgiSurface(dxgi_back_buffer.get(), &properties, ret.address_of()));
                return ret;
                }
        };
    }

Window module that makes use of all that


//window modules file/namespace

class swap_chain: public utils::MS::window::module
    {
    public:
        using on_draw_signature = void(utils::MS::window::base&, const d2d::device_context&);

        struct create_info
            {
            using module_type = swap_chain;

            const d2d ::device& d2d_device;
            std ::function<on_draw_signature> on_render;

            inline void adjust_base_create_info(utils::MS::window::base::create_info& base_create_info) const noexcept
                {
                base_create_info.style_ex |= WS_EX_NOREDIRECTIONBITMAP;
                }
            };

        swap_chain(utils::MS::window::base& base, create_info create_info) :
            module{base},
            on_render{create_info.on_render},
            d2d_device_context{create_info.d2d_device},
            dxgi_swapchain{create_info.d2d_device.get_dxgi_device(), get_base().get_handle()},
            d2d_bitmap_target{d2d_device_context, dxgi_swapchain}
            {
            d2d_device_context->SetTarget(d2d_bitmap_target.get());
            }

        std::function<on_draw_signature> on_render;

    private:
        d2d::device_context d2d_device_context;
        dxgi::swap_chain dxgi_swapchain;
        d2d::bitmap d2d_bitmap_target;


        virtual utils::MS::window::procedure_result procedure(UINT msg, WPARAM wparam, LPARAM lparam) override
            {
            switch (msg)
                {
                case WM_SIZE:
                    on_resize({LOWORD(lparam), HIWORD(lparam)});
                    return utils::MS::window::procedure_result::next(0);

                case WM_DISPLAYCHANGE:
                    //InvalidateRect(get_handle(), NULL, FALSE);
                    break;

                case WM_ERASEBKGND: return utils::MS::window::procedure_result::stop(1);

                case WM_PAINT:
                    if (on_render)
                        {
                        on_render(get_base(), d2d_device_context);
                        dxgi_swapchain.present();

                        ValidateRect(get_base().get_handle(), NULL);
                        return utils::MS::window::procedure_result::next(0);
                        }
                    break;

                }

            return utils::MS::window::procedure_result::next();
            }

        void on_resize(utils::math::vec2u size)
            {
            //Release things that reference the swapchain before resizing
            d2d_device_context->SetTarget(nullptr);
            d2d_bitmap_target.reset();

            dxgi_swapchain.resize(size);

            //re-get back buffer
            d2d_bitmap_target = d2d::bitmap{d2d_device_context, dxgi_swapchain};
            d2d_device_context->SetTarget(d2d_bitmap_target.get());
            }
    };

Attempt at creating a DeviceContext from a DXGISwapChain WITH transparency and somhow composition (questions here)

would be used for (3): Resources shared across multiple windows + DeviceContext newer functions + transparency

This just doesn't work. I followed the guide linked before, but as mentioned it bypasses/does not consider the manual creation of a swapchain. I didn't even get it working since what the example is doing doesn't seem to lead me in the right direction. The following code is relevant more for my commented questions than for actual code

Create dxgi swapchain from dxgi device using the CreateSwapChainForComposition method


namespace dxgi
    {
    class swap_chain : public details::com_ptr<IDXGISwapChain1>
        {
        public:
            //that nullptr_t is just a temporary flag for testing purposes, to distinguish from the other constructor used in the previous snippets
            swap_chain(const dxgi::device& dxgi_device, HWND hwnd, nullptr_t) : com_ptr{create_composition(dxgi_device, hwnd)} {}

            void resize(utils::math::vec2u size)
                {
                HRESULT hresult{get()->ResizeBuffers(2, size.x, size.y, DXGI_FORMAT_B8G8R8A8_UNORM, 0)};
                if (hresult == DXGI_ERROR_DEVICE_REMOVED || hresult == DXGI_ERROR_DEVICE_RESET)
                    {
                    throw std::runtime_error("Device removed or reset");
                    }
                else { details::throw_if_failed(hresult); }
                }

            void present()
                {
                HRESULT hresult{get()->Present(1, 0)};
                if (hresult == DXGI_ERROR_DEVICE_REMOVED || hresult == DXGI_ERROR_DEVICE_RESET)
                    {
                    throw std::runtime_error("Device removed or reset");
                    }
                else { details::throw_if_failed(hresult); }
                }

        private:
            inline static self_t create_composition(const dxgi::device& dxgi_device, HWND hwnd)
                {
                RECT client_rect{0, 0, 0, 0};
                GetClientRect(hwnd, &client_rect);
                utils::math::rect<long> rectl{.ll{client_rect.left}, .up{client_rect.top}, .rr{client_rect.right}, .dw{client_rect.bottom}};

                details::com_ptr<IDXGIAdapter> dxgi_adapter;
                details::throw_if_failed(dxgi_device->GetAdapter(dxgi_adapter.address_of()));

                details::com_ptr<IDXGIFactory2> dxgi_factory;
                details::throw_if_failed(dxgi_adapter->GetParent(IID_PPV_ARGS(dxgi_factory.address_of())));

                DXGI_SWAP_CHAIN_DESC1 desc
                    {
                    .Width      {static_cast<UINT>(rectl.w())},
                    .Height     {static_cast<UINT>(rectl.h())},
                    .Format     {DXGI_FORMAT_B8G8R8A8_UNORM},
                    .Stereo     {false},
                    .SampleDesc
                        {
                        .Count  {1},
                        .Quality{0}
                        },
                    .BufferUsage {DXGI_USAGE_RENDER_TARGET_OUTPUT},
                    .BufferCount {2},
                    .Scaling     {DXGI_SCALING_STRETCH},
                    .SwapEffect  {DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL},
                    .AlphaMode   {DXGI_ALPHA_MODE_PREMULTIPLIED},
                    .Flags       {DXGI_SWAP_CHAIN_FLAG_FOREGROUND_LAYER},
                    };
            
                self_t ret{nullptr};
                details::throw_if_failed(dxgi_factory->CreateSwapChainForComposition(dxgi_device.get(), &desc, nullptr, ret.address_of()));

                dxgi_device->SetMaximumFrameLatency(1);

                return ret;
                }
        };
    }

Composition wrappers, pretty straightforward


namespace composition
    {
    struct device : details::com_ptr<IDCompositionDevice>
        {
        using com_ptr::com_ptr;
        device(const dxgi::device& dxgi_device) : com_ptr{[&dxgi_device]
            {
            self_t ret{nullptr};
            details::throw_if_failed(DCompositionCreateDevice(dxgi_device.get(), __uuidof(interface_type), reinterpret_cast<void**>(ret.address_of())));
            return ret;
            }()} {}
        };
    struct target : details::com_ptr<IDCompositionTarget>
        {
        using com_ptr::com_ptr;
        target(const composition::device& composition_device, HWND hwnd) : com_ptr{[&composition_device, &hwnd]
            {
            self_t ret{nullptr};
            details::throw_if_failed(composition_device->CreateTargetForHwnd(hwnd, TRUE, ret.address_of()));
            return ret;
            }()} {}
        };
    struct visual : details::com_ptr<IDCompositionVisual>
        {
        using com_ptr::com_ptr;
        visual(const composition::device& composition_device) : com_ptr{[&composition_device]
            {
            self_t ret{nullptr};
            details::throw_if_failed(composition_device->CreateVisual(ret.address_of()));
            return ret;
            }()} {} 
        };

    struct surface : details::com_ptr<IDCompositionSurface>
        {
        using com_ptr::com_ptr;
        surface(const composition::device& composition_device, HWND hwnd) : com_ptr{[&composition_device, &hwnd]
            {
            self_t ret{nullptr};
            details::throw_if_failed(composition_device->CreateSurface(128, 128, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_ALPHA_MODE_PREMULTIPLIED, ret.address_of()));
            return ret;
            }()} {}
        };
    }

Final window for the composition swapchain, questions and doubts are in the comments of this snippet


//window modules file/namespace
class composition_swap_chain : public utils::MS::window::module
    {
    public:
        using on_draw_signature = void(utils::MS::window::base&, const d2d::device_context&);

        struct create_info
            {
            using module_type = composition_swap_chain;

            const d2d ::device& d2d_device;
            std ::function<on_draw_signature> on_render;

            inline void adjust_base_create_info(utils::MS::window::base::create_info& base_create_info) const noexcept
                {
                base_create_info.style_ex |= WS_EX_NOREDIRECTIONBITMAP;
                }
            };

        composition_swap_chain(utils::MS::window::base& base, create_info create_info) :
            module{base},
            on_render{create_info.on_render},
            
            // from here...
            d2d_device_context{create_info.d2d_device},
            dxgi_swapchain{create_info.d2d_device.get_dxgi_device(), get_base().get_handle(), nullptr/*flag to create swapchain with the "ForComposition" function*/},
            d2d_bitmap_target{d2d_device_context, dxgi_swapchain}
            // ...to here, I get the swapchain I wish to use
            
            // from here...
            composition_device{create_info.d2d_device.get_dxgi_device()},
            composition_surface{composition_device, get_base().get_handle()},
            // ...to here I started initializing some composition stuff
            // that I can't seem to be able to "connect" to the previously
            // created swapchain in any way
            {
            // I wish to target the bitmap from the swapchain backbuffer,
            // but again this isn't connected in any way to the composition stuff
            d2d_device_context->SetTarget(d2d_bitmap_target.get());
            }

        std::function<on_draw_signature> on_render;

    private:
        d2d::device_context d2d_device_context;

        composition::device composition_device;
        composition::surface composition_surface;

        dxgi::swap_chain dxgi_swapchain;
        d2d::bitmap d2d_bitmap_target;

        virtual utils::MS::window::procedure_result procedure(UINT msg, WPARAM wparam, LPARAM lparam) override
            {
            switch (msg)
                {
                case WM_SIZE:
                    on_resize({LOWORD(lparam), HIWORD(lparam)});
                    return utils::MS::window::procedure_result::next(0);

                case WM_DISPLAYCHANGE:
                    //InvalidateRect(get_handle(), NULL, FALSE);
                    break;

                case WM_ERASEBKGND: return utils::MS::window::procedure_result::stop(1);

                case WM_PAINT:
                    if (on_render)
                        {
                        // If BeginDraw gives me a d2dDevice context...
                        POINT offset{}; //?

                        auto comp_surf_interop{composition_surface.as<ABI::Windows::UI::Composition::ICompositionDrawingSurfaceInterop>()};

                        auto HR{comp_surf_interop->BeginDraw(nullptr, __uuidof(ID2D1DeviceContext5), reinterpret_cast<void**>(d2d_device_context.address_of()), &offset)};
                        if (SUCCEEDED(HR))
                            {
                            on_render(get_base(), d2d_device_context);
                            // ...what am I supposed to do with the swapchain?
                            //dxgi_swapchain.present();

                            details::throw_if_failed(composition_surface->EndDraw());

                            ValidateRect(get_base().get_handle(), NULL);
                            }
                        else
                            {
                            std::cout << details::hr_to_string(HR) << std::endl;
                            }
                        return utils::MS::window::procedure_result::next(0);
                        }
                    break;

                }

            return utils::MS::window::procedure_result::next();
            }

        void on_resize(utils::math::vec2u size)
            {
            // So do I resize a swapchain or not?
            }
    };
Barnack
  • 921
  • 1
  • 8
  • 20
  • You don't need to create a swap chain manually. For Windows 10 and higher, you want to use Direct Composition, its latest version being named "The Visual Layer" (https://learn.microsoft.com/en-us/windows/uwp/composition/visual-layer) provided by the WinRT API in the Windows.UI.Composition namespace. Contrary to what the doc implies, it's not bound to UWP, it's implemented by Windows Desktop Window Manager (DWM). The rosetta stone for me to this was this seminal simple "pure" Win32 desktop sample from Kenny Kerr https://gist.github.com/kennykerr/62923cdacaba28fedc4f3dab6e0c12ec – Simon Mourier Jan 16 '23 at 08:06
  • 1
    If you want to see a bigger usage I have written this project https://github.com/aelyo-softworks/Wice (it's in C# not in C++ but principles are the same) and the most relevant portion is the Window implementation: https://github.com/aelyo-softworks/Wice/blob/main/Wice/Window.cs#L1029 – Simon Mourier Jan 16 '23 at 08:07
  • @SimonMourier the big difference is the same as with the older RenderTarget: when I create the swapchain I do so passing a specific "Device" instance, which guarantees me that all the windows I create a swapchain for can share resources if I use the same device. I didn't find any way to get that guarantee with composition, everything seems to assume your programs use only 1 window and they don't even consider this requirement to begin with – Barnack Jan 16 '23 at 15:03
  • You don't need to create a swapchain, the Visual Layer does that for you. Then you can do many things w/o a Direct2 RenderTarget (with recent version of Direct2D DeviceContext is a RenderTarget and the reverse, I'm not sure what you mean by the "older" RenderTarget). When you do need a D2D DeviceContext (=RenderTarget), you can QI for ICompositionDrawingSurfaceInterop on a SpriteVisual and call https://learn.microsoft.com/en-us/windows/win32/api/windows.ui.composition.interop/nf-windows-ui-composition-interop-icompositiondrawingsurfaceinterop-begindraw – Simon Mourier Jan 16 '23 at 16:10
  • This is demonstrated here in the WICE project https://github.com/aelyo-softworks/Wice/blob/main/Wice/RenderVisual.cs#L77 . Also you can create as many windows as you want, I don't understand what's the problem here either? – Simon Mourier Jan 16 '23 at 16:11
  • @SimonMourier the problem is sharing resources across multiple windows, see the last paragraph in my context section. DeviceContexts can share brushes if they're created from the same device. With the swapchain method (CreateDevice > CreateDeviceContext > SetTarget(swapchain backbuffer) I can decide which device they are created from so I can explicitely use the same device, with the older way of creating RenderTargets (CreateHwndRenderTarget) that wasn't a possibility, and with the newer Composition stuff I don't see a way to guarantee that either – Barnack Jan 16 '23 at 17:40
  • 1
    With DComp if you want to use Direct2D on a visual, you still need a D3D11 device and a D2D1 device (or a swapchain) to be able to create a CompositionGraphicsDevice (https://learn.microsoft.com/en-us/uwp/api/windows.ui.composition.compositiongraphicsdevice) using ICompositorInterop (QI from the Compositor class) (https://learn.microsoft.com/en-us/windows/win32/api/windows.ui.composition.interop/nn-windows-ui-composition-interop-icompositorinterop). – Simon Mourier Jan 16 '23 at 18:30
  • @SimonMourier oh that's the part i was missing, thanks a lot! – Barnack Jan 16 '23 at 19:54

0 Answers0