2
\$\begingroup\$

After figuring out DXGI, I wrote a class that combines the act of creating a window and its associated swap chain (as well as an OOP approach to messages). The class supports different window modes that are useful for games and editors, and handles XInput behind the scenes, combining gamepad, mouse, and keyboard controls into one domain.

I am primarily concerned with the design at this point - I have yet to take any classes at my school related to design, and a lot of my code winds up being a bit hacky. If this is sensible (or at least passable for use in a game of my own), though, I would like to know.

#pragma once

#ifdef _MSC_VER
    #pragma comment(lib, "d3d11.lib")
    #pragma comment(lib, "dxgi.lib")
    #pragma comment(lib, "XINPUT9_1_0.lib")
    #pragma comment(lib, "DXWindow.lib")
#endif

#include <Windows.h>
#include <d3d11.h>
#include <dxgi.h>
#include <Xinput.h>
#include <comdef.h>
#include <atlbase.h>
#include <vector>
#include <string>

class DXWindowFactory;
class DXWindow;

class DXWindow final {
public:
    struct RESOLUTION {
        WORD Width;
        WORD Height;
    };

    enum WINDOW_MODE {
        WINDOW_MODE_WINDOWED = 0, //Non-resizable window with border
        WINDOW_MODE_BORDERLESS = 1, //Non-resizable window without border
        WINDOW_MODE_MAXIMIZED = 2 //Non-resizable maximized window without border
    };

    enum FULLSCREEN_MODE {
        FULLSCREEN_MODE_FULLSCREEN = 0, //Exclusive fullscreen (Native resolution)
        FULLSCREEN_MODE_FULLSCREEN_WINDOW = 1 //Non-exclusive fullscreen (Desktop resolution)
    };

    struct DESC {
        RESOLUTION Resolution; //Resolution of the window in windowed or borderless mode
        LPCWSTR Name;
        WINDOW_MODE WindowMode;
        FULLSCREEN_MODE FullscreenMode;
        BOOL InitFullscreen;
        BOOL AllowToggle;
    };

    struct MessageCallback {
        //Gamepad methods
        virtual void OnButtonDown(DWORD dwUserIndex, WORD wButton) { }

        virtual void OnButtonUp(DWORD dwUserIndex, WORD wButton) { }

        virtual void OnLeftAnalogMove(DWORD dwUserIndex, SHORT xPos, SHORT yPos) { }

        virtual void OnRightAnalogMove(DWORD dwUserIndex, SHORT xPos, SHORT yPos) { }

        virtual void OnLeftTriggerMove(DWORD dwUserIndex, BYTE Val) { }

        virtual void OnRightTriggerMove(DWORD dwUserIndex, BYTE Val) { }

        //Keyboard methods
        virtual void OnKeyDown(WPARAM Key, LPARAM lParam) { }

        virtual void OnKeyUp(WPARAM Key, LPARAM lParam) { }

        //Mouse methods
        virtual void OnLButtonDown(WORD Flags, WORD xPos, WORD yPos) { }

        virtual void OnLButtonUp(WORD Flags, WORD xPos, WORD yPos) { }

        virtual void OnRButtonDown(WORD Flags, WORD xPos, WORD yPos) { }

        virtual void OnRButtonUp(WORD Flags, WORD xPos, WORD yPos) { }

        virtual void OnMButtonDown(WORD Flags, WORD xPos, WORD yPos) { }

        virtual void OnMButtonUp(WORD Flags, WORD xPos, WORD yPos) { }

        virtual void OnMove(WORD Flags, WORD xPos, WORD yPos) { }

        virtual void OnWheel(WORD Flags, SHORT Delta, WORD xPos, WORD yPos) { }

        //Window methods
        virtual void OnClose() { }

        virtual void OnMinimize() { }

        virtual void OnMaximize() { }

        //Buffer methods
        virtual void OnBufferCreate() { }

        virtual void OnBufferRelease() { }
    };

    ~DXWindow();

    HRESULT PumpMessages();

    HRESULT Present(UINT SyncInterval, UINT Flags);

    void RegisterCallback(MessageCallback& Callback);

    void UnregisterCallback(MessageCallback& Callback);

    DXGI_FORMAT GetFormat();

    HRESULT GetBackBuffer(ID3D11Texture2D** ppBackBuffer);

    HRESULT GetBackBuffer(CComPtr<ID3D11Texture2D>& BackBuffer);

    HRESULT SetWindowMode(WINDOW_MODE Mode);

    WINDOW_MODE GetWindowMode();

    HRESULT JumpWindowMode(WINDOW_MODE Mode);

    HRESULT SetFullscreenMode(FULLSCREEN_MODE Mode);

    FULLSCREEN_MODE GetFullscreenMode();

    HRESULT JumpFullscreenMode(FULLSCREEN_MODE Mode);

    HRESULT SetFullscreen(BOOL Fullscreen);

    BOOL GetFullscreen();

    HRESULT SetWindowResolution(RESOLUTION Resolution);

    RESOLUTION GetWindowResolution();

    void SetAllowToggle(BOOL AllowToggle);

    BOOL GetAllowToggle();

private:
    DXWindowFactory& m_WindowFactory;

    CComPtr<IDXGISwapChain> m_SwapChain;
    CComPtr<ID3D11Device> m_Device;
    CComPtr<ID3D11DeviceContext> m_DeviceContext;

    HWND m_Handle;
    RESOLUTION m_Resolution;
    WINDOW_MODE m_WindowMode;
    FULLSCREEN_MODE m_FullscreenMode;
    BOOL m_InFocus;
    BOOL m_Fullscreen;
    BOOL m_AllowToggle;
    std::vector<MessageCallback*> m_Callbacks;
    XINPUT_STATE m_PrevGamepadState[4];
    DWORD m_WindowStyle;
    DWORD m_WindowExStyle;

    explicit DXWindow(DXWindowFactory& Factory);

    DXWindow() = delete;

    DXWindow(const DXWindow& Copy) = delete;

    HRESULT Initialize(const DESC& Desc);

    static LRESULT CALLBACK StaticWndProc(HWND handle, UINT message, WPARAM wParam, LPARAM lParam);

    LRESULT WndProc(UINT message, WPARAM wParam, LPARAM lParam);

    HRESULT RegisterWindowClass(DESC& desc);

    HRESULT MakeWindow(DESC& desc);

    HRESULT CreateSwapChain(CComPtr<IDXGIAdapter> adapter);

    HRESULT ResizeBuffers();

    HRESULT KillFocus();

    HRESULT SetFocus();

    HRESULT CenterWindow();

    HRESULT DefaultWindowPos();

    HRESULT CenterCursor();

    HRESULT CheckGamepads();

    HRESULT ToggleFullscreen();

    void WindowModeSetStyle();

    friend class DXWindowFactory;
};

Above is the window class. Most of the methods are rather straightforward, and the only thing I feel the need to point out is how the window modes work. There are 5 modes: Windowed, Borderless, Maximized, Fullscreen, and Fullscreen Window. They are separated into two categories: Windowed, and Fullscreen modes. This is because the window supports toggling between fullscreen and non-fullscreen with F11 (much in the way a browser does). SetWindowMode and SetFullscreenMode both change the appearance of their respective modes and only immediately modify the window if their modes are in effect (IE, SetWindowMode will only instantly change the window if it's windowed, borderless, or maximized). If they are not, the changes will be made on toggle. JumpWindowMode and JumpFullscreenMode, however, immediately set the window to the given mode.

Given that mouthful, I'm debating whether or not the distinction is actually useful.

class DXWindowFactory final {
public:
    DXWindowFactory();

    ~DXWindowFactory();

    HRESULT Initialize(LPCWSTR LargeIcon = NULL, LPCWSTR SmallIcon = NULL, LPCWSTR Cursor = NULL);

    HRESULT CreateDXWindow(const DXWindow::DESC& Desc, DXWindow*& pWindow);

    void GetDevice(ID3D11Device** ppDevice);

    void GetDevice(CComPtr<ID3D11Device>& Device);

    void GetDeviceContext(ID3D11DeviceContext** ppDeviceContext);

    void GetDeviceContext(CComPtr<ID3D11DeviceContext>& DeviceContext);

private:
    CComPtr<ID3D11Device> m_Device;
    CComPtr<ID3D11DeviceContext> m_DeviceContext;
    CComPtr<IDXGIAdapter> m_Adapter;

    HRESULT CreateDevice();

    HRESULT RegisterWindowClass(LPCWSTR LargeIcon, LPCWSTR SmallIcon, LPCWSTR Cursor);

    void GetAdapter(CComPtr<IDXGIAdapter>& Adapter);

    friend class DXWindow;
};

This is the factory class that creates the window. It is necessary in situations where multiple windows might be created, since it makes little sense to create a new Direct3D Device for every window, or query adapter outputs or what have you. You probably notice the friend keyword. That's something that I think is hacky, but ultimately I think it helps to boil down usage of the two classes to just a few lines.

I'd also love to know how well this works on other devices. I've created a repository here with a MSVC 2013 project that should hopefully build without issues. The test project basically just allows testing of window mode switching with the F1 - F7 keys.

EDIT: I also made a branch (post-cr) if you're looking for the current code.

\$\endgroup\$
1
\$\begingroup\$

Your code seems fine, though I'm only looking at the interface, not the implementation. Since you haven't got any architectural or DX related answers yet, I'll mention a few things I believe could be improve in this code.

  • A header file is no place for this:

    #ifdef _MSC_VER
        #pragma comment(lib, "d3d11.lib")
        #pragma comment(lib, "dxgi.lib")
        #pragma comment(lib, "XINPUT9_1_0.lib")
        #pragma comment(lib, "DXWindow.lib")
    #endif
    

    That would be much better suited in a source (.cpp) file, since it is an implementation detail. The set of linked libraries is not relevant to the code including your header file, not to mention that this is a compiler specific feature that people not familiar with VS might not even know what it means, thus only causing distraction.

  • Your enums are already sequential and starting from zero. No need to explicitly supply the values. That's only maintenance overhead when other values must be added.

  • A very important thing: MessageCallback is an abstract base class, however, it is missing a virtual destructor! If anyone were to delete an instance of a derived class thru a MessageCallback pointer, it would invoke undefined behavior, due the lack of a virtual destructor. See When to use virtual destructors?

  • In DXWindowFactory, you have provided methods that operate on raw pointers and overloads that operate on smart pointers (CComPtr). My advice would be to disallow operations on raw pointers. Keep the smart pointer versions only, to avoid unsafe uses of your class.

  • I see a few instances of NULL here and there, but your code is clearly C++11, so why not use the much superior nullptr?

\$\endgroup\$
  • \$\begingroup\$ On the last point - Windows APIs tend to use NULL. Any time I invoke a method or function from a Windows API I tend to use (what seems to be) their common practice, whereas if I was using a STL container I would use nullptr. No real rhyme or reason, I just think it's more consistent to do it this way. \$\endgroup\$ – NmdMystery Jun 4 '15 at 19:42
  • \$\begingroup\$ @NmdMystery, fair enough. \$\endgroup\$ – glampert Jun 4 '15 at 20:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.