internal/graphicsdriver/directx: refactoring: add graphicsInfra

This commit is contained in:
Hajime Hoshi 2023-03-26 17:39:49 +09:00
parent 0b46d2b799
commit c9469a64ee
3 changed files with 253 additions and 157 deletions

View File

@ -546,11 +546,12 @@ type _IDXGISwapChain4_Vtbl struct {
SetHDRMetaData uintptr SetHDRMetaData uintptr
} }
func (i *_IDXGISwapChain4) GetBuffer(buffer uint32) (*_ID3D12Resource, error) { func (i *_IDXGISwapChain4) GetBuffer(buffer uint32, riid *windows.GUID) (unsafe.Pointer, error) {
var resource *_ID3D12Resource var resource unsafe.Pointer
r, _, _ := syscall.Syscall6(i.vtbl.GetBuffer, 4, uintptr(unsafe.Pointer(i)), r, _, _ := syscall.Syscall6(i.vtbl.GetBuffer, 4, uintptr(unsafe.Pointer(i)),
uintptr(buffer), uintptr(unsafe.Pointer(&_IID_ID3D12Resource)), uintptr(unsafe.Pointer(&resource)), uintptr(buffer), uintptr(unsafe.Pointer(riid)), uintptr(unsafe.Pointer(&resource)),
0, 0) 0, 0)
runtime.KeepAlive(riid)
if uint32(r) != uint32(windows.S_OK) { if uint32(r) != uint32(windows.S_OK) {
return nil, fmt.Errorf("directx: IDXGISwapChain4::GetBuffer failed: %w", handleError(windows.Handle(uint32(r)))) return nil, fmt.Errorf("directx: IDXGISwapChain4::GetBuffer failed: %w", handleError(windows.Handle(uint32(r))))
} }

View File

@ -18,7 +18,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"time"
"unsafe" "unsafe"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
@ -30,8 +29,6 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/hlsl" "github.com/hajimehoshi/ebiten/v2/internal/shaderir/hlsl"
) )
const frameCount = 2
type resourceWithSize struct { type resourceWithSize struct {
value *_ID3D12Resource value *_ID3D12Resource
sizeInBytes uint32 sizeInBytes uint32
@ -79,10 +76,7 @@ type graphics12 struct {
vertices [frameCount][]*resourceWithSize vertices [frameCount][]*resourceWithSize
indices [frameCount][]*resourceWithSize indices [frameCount][]*resourceWithSize
allowTearing bool graphicsInfra *graphicsInfra
factory *_IDXGIFactory4
swapChain *_IDXGISwapChain4
window windows.HWND window windows.HWND
@ -104,12 +98,6 @@ type graphics12 struct {
vsyncEnabled bool vsyncEnabled bool
transparent bool transparent bool
// occluded reports whether the screen is invisible or not.
occluded bool
// lastTime is the last time for rendering.
lastTime time.Time
newScreenWidth int newScreenWidth int
newScreenHeight int newScreenHeight int
@ -162,55 +150,41 @@ func (g *graphics12) initializeDesktop(useWARP bool, useDebugLayer bool, feature
g.debug.EnableDebugLayer() g.debug.EnableDebugLayer()
} }
var flag uint32 gi, err := newGraphicsInfra(g.debug != nil)
if g.debug != nil {
flag = _DXGI_CREATE_FACTORY_DEBUG
}
f, err := _CreateDXGIFactory2(flag)
if err != nil { if err != nil {
return err return err
} }
g.factory = f g.graphicsInfra = gi
defer func() { defer func() {
if ferr != nil { if ferr != nil {
g.factory.Release() g.graphicsInfra.release()
g.factory = nil g.graphicsInfra = nil
}
}()
adapters, err := g.graphicsInfra.appendAdapters(nil, useWARP)
if err != nil {
return err
}
defer func() {
for _, a := range adapters {
a.Release()
} }
}() }()
var adapter *_IDXGIAdapter1 var adapter *_IDXGIAdapter1
if useWARP { if len(adapters) > 0 {
a, err := g.factory.EnumWarpAdapter() if useWARP {
if err != nil { adapter = adapters[0]
return err } else {
} for _, a := range adapters {
defer a.Release() // Test D3D12CreateDevice without creating an actual device.
adapter = a if _, err := _D3D12CreateDevice(unsafe.Pointer(a), featureLevel, &_IID_ID3D12Device, false); err != nil {
} else { continue
for i := 0; ; i++ { }
a, err := g.factory.EnumAdapters1(uint32(i)) adapter = a
if errors.Is(err, _DXGI_ERROR_NOT_FOUND) {
break break
} }
if err != nil {
return err
}
defer a.Release()
desc, err := a.GetDesc1()
if err != nil {
return err
}
if desc.Flags&_DXGI_ADAPTER_FLAG_SOFTWARE != 0 {
continue
}
// Test D3D12CreateDevice without creating an actual device.
if _, err := _D3D12CreateDevice(unsafe.Pointer(a), featureLevel, &_IID_ID3D12Device, false); err != nil {
continue
}
adapter = a
break
} }
} }
@ -224,15 +198,6 @@ func (g *graphics12) initializeDesktop(useWARP bool, useDebugLayer bool, feature
} }
g.device = (*_ID3D12Device)(d) g.device = (*_ID3D12Device)(d)
if f, err := g.factory.QueryInterface(&_IID_IDXGIFactory5); err == nil && f != nil {
factory := (*_IDXGIFactory5)(f)
defer factory.Release()
var allowTearing int32
if err := factory.CheckFeatureSupport(_DXGI_FEATURE_PRESENT_ALLOW_TEARING, unsafe.Pointer(&allowTearing), uint32(unsafe.Sizeof(allowTearing))); err == nil && allowTearing != 0 {
g.allowTearing = true
}
}
if err := g.initializeMembers(g.frameIndex); err != nil { if err := g.initializeMembers(g.frameIndex); err != nil {
return err return err
} }
@ -495,21 +460,18 @@ func (g *graphics12) updateSwapChain(width, height int) error {
return errors.New("directx: the window handle is not initialized yet") return errors.New("directx: the window handle is not initialized yet")
} }
if g.swapChain == nil { if microsoftgdk.IsXbox() {
if microsoftgdk.IsXbox() { if err := g.initSwapChainXbox(width, height); err != nil {
if err := g.initSwapChainXbox(width, height); err != nil { return err
return err
}
} else {
if err := g.initSwapChainDesktop(width, height); err != nil {
return err
}
} }
return nil return nil
} }
if microsoftgdk.IsXbox() { if !g.graphicsInfra.isSwapChainInited() {
return errors.New("directx: resizing should never happen on Xbox") if err := g.initSwapChainDesktop(width, height); err != nil {
return err
}
return nil
} }
g.newScreenWidth = width g.newScreenWidth = width
@ -518,45 +480,8 @@ func (g *graphics12) updateSwapChain(width, height int) error {
return nil return nil
} }
func (g *graphics12) initSwapChainDesktop(width, height int) (ferr error) { func (g *graphics12) initSwapChainDesktop(width, height int) error {
// Create a swap chain. g.graphicsInfra.initSwapChain(width, height, unsafe.Pointer(g.commandQueue), g.window)
//
// DXGI_ALPHA_MODE_PREMULTIPLIED doesn't work with a HWND well.
//
// IDXGIFactory::CreateSwapChain: Alpha blended swapchains must be created with CreateSwapChainForComposition,
// or CreateSwapChainForCoreWindow with the DXGI_SWAP_CHAIN_FLAG_FOREGROUND_LAYER flag
desc := &_DXGI_SWAP_CHAIN_DESC1{
Width: uint32(width),
Height: uint32(height),
Format: _DXGI_FORMAT_B8G8R8A8_UNORM,
BufferUsage: _DXGI_USAGE_RENDER_TARGET_OUTPUT,
BufferCount: frameCount,
SwapEffect: _DXGI_SWAP_EFFECT_FLIP_DISCARD,
SampleDesc: _DXGI_SAMPLE_DESC{
Count: 1,
Quality: 0,
},
}
if g.allowTearing {
desc.Flags |= uint32(_DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING)
}
s, err := g.factory.CreateSwapChainForHwnd(unsafe.Pointer(g.commandQueue), g.window, desc, nil, nil)
if err != nil {
return err
}
s.As(&g.swapChain)
defer func() {
if ferr != nil {
g.swapChain.Release()
g.swapChain = nil
}
}()
// MakeWindowAssociation should be called after swap chain creation.
// https://docs.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgifactory-makewindowassociation
if err := g.factory.MakeWindowAssociation(g.window, _DXGI_MWA_NO_WINDOW_CHANGES|_DXGI_MWA_NO_ALT_ENTER); err != nil {
return err
}
// TODO: Get the current buffer index? // TODO: Get the current buffer index?
@ -564,7 +489,7 @@ func (g *graphics12) initSwapChainDesktop(width, height int) (ferr error) {
return err return err
} }
g.frameIndex = int(g.swapChain.GetCurrentBackBufferIndex()) g.frameIndex = g.graphicsInfra.currentBackBufferIndex()
return nil return nil
} }
@ -636,11 +561,7 @@ func (g *graphics12) resizeSwapChainDesktop(width, height int) error {
r.Release() r.Release()
} }
var flag uint32 if err := g.graphicsInfra.resizeSwapChain(width, height); err != nil {
if g.allowTearing {
flag |= uint32(_DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING)
}
if err := g.swapChain.ResizeBuffers(frameCount, uint32(width), uint32(height), _DXGI_FORMAT_B8G8R8A8_UNORM, flag); err != nil {
return err return err
} }
@ -658,11 +579,11 @@ func (g *graphics12) createRenderTargetViewsDesktop() (ferr error) {
return err return err
} }
for i := 0; i < frameCount; i++ { for i := 0; i < frameCount; i++ {
r, err := g.swapChain.GetBuffer(uint32(i)) r, err := g.graphicsInfra.getBuffer(uint32(i), &_IID_ID3D12Resource)
if err != nil { if err != nil {
return err return err
} }
g.renderTargets[i] = r g.renderTargets[i] = (*_ID3D12Resource)(r)
defer func(i int) { defer func(i int) {
if ferr != nil { if ferr != nil {
g.renderTargets[i].Release() g.renderTargets[i].Release()
@ -670,7 +591,7 @@ func (g *graphics12) createRenderTargetViewsDesktop() (ferr error) {
} }
}(i) }(i)
g.device.CreateRenderTargetView(r, nil, h) g.device.CreateRenderTargetView((*_ID3D12Resource)(r), nil, h)
h.Offset(1, g.rtvDescriptorSize) h.Offset(1, g.rtvDescriptorSize)
} }
@ -795,40 +716,7 @@ func (g *graphics12) End(present bool) error {
} }
func (g *graphics12) presentDesktop() error { func (g *graphics12) presentDesktop() error {
if g.swapChain == nil { return g.graphicsInfra.present(g.vsyncEnabled)
return fmt.Errorf("directx: the swap chain is not initialized yet at End")
}
var syncInterval uint32
var flags _DXGI_PRESENT
if g.occluded {
// The screen is not visible. Test whether we can resume.
flags |= _DXGI_PRESENT_TEST
} else {
// Do actual rendering only when the screen is visible.
if g.vsyncEnabled {
syncInterval = 1
} else if g.allowTearing {
flags |= _DXGI_PRESENT_ALLOW_TEARING
}
}
occluded, err := g.swapChain.Present(syncInterval, uint32(flags))
if err != nil {
return err
}
g.occluded = occluded
// Reduce FPS when the screen is invisible.
now := time.Now()
if g.occluded {
if delta := 100*time.Millisecond - now.Sub(g.lastTime); delta > 0 {
time.Sleep(delta)
}
}
g.lastTime = now
return nil
} }
func (g *graphics12) presentXbox() error { func (g *graphics12) presentXbox() error {
@ -849,7 +737,7 @@ func (g *graphics12) moveToNextFrame() error {
if microsoftgdk.IsXbox() { if microsoftgdk.IsXbox() {
g.frameIndex = (g.frameIndex + 1) % frameCount g.frameIndex = (g.frameIndex + 1) % frameCount
} else { } else {
g.frameIndex = int(g.swapChain.GetCurrentBackBufferIndex()) g.frameIndex = g.graphicsInfra.currentBackBufferIndex()
} }
if g.fence.GetCompletedValue() < g.fenceValues[g.frameIndex] { if g.fence.GetCompletedValue() < g.fenceValues[g.frameIndex] {

View File

@ -15,12 +15,21 @@
package directx package directx
import ( import (
"errors"
"fmt"
"os" "os"
"runtime"
"strings" "strings"
"time"
"unsafe"
"golang.org/x/sys/windows"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
) )
const frameCount = 2
// NewGraphics creates an implementation of graphicsdriver.Graphics for DirectX. // NewGraphics creates an implementation of graphicsdriver.Graphics for DirectX.
// The returned graphics value is nil iff the error is not nil. // The returned graphics value is nil iff the error is not nil.
func NewGraphics() (graphicsdriver.Graphics, error) { func NewGraphics() (graphicsdriver.Graphics, error) {
@ -68,3 +77,201 @@ func NewGraphics() (graphicsdriver.Graphics, error) {
} }
return g, nil return g, nil
} }
type graphicsInfra struct {
factory *_IDXGIFactory4
swapChain *_IDXGISwapChain4
allowTearing bool
// occluded reports whether the screen is invisible or not.
occluded bool
// lastTime is the last time for rendering.
lastTime time.Time
}
func newGraphicsInfra(debug bool) (*graphicsInfra, error) {
var flag uint32
if debug {
flag = _DXGI_CREATE_FACTORY_DEBUG
}
f, err := _CreateDXGIFactory2(flag)
if err != nil {
return nil, err
}
g := &graphicsInfra{
factory: f,
}
runtime.SetFinalizer(g, (*graphicsInfra).release)
if f, err := g.factory.QueryInterface(&_IID_IDXGIFactory5); err == nil && f != nil {
factory := (*_IDXGIFactory5)(f)
defer factory.Release()
var allowTearing int32
if err := factory.CheckFeatureSupport(_DXGI_FEATURE_PRESENT_ALLOW_TEARING, unsafe.Pointer(&allowTearing), uint32(unsafe.Sizeof(allowTearing))); err == nil && allowTearing != 0 {
g.allowTearing = true
}
}
return g, nil
}
func (g *graphicsInfra) release() {
if g.factory != nil {
g.factory.Release()
g.factory = nil
}
if g.swapChain != nil {
g.swapChain.Release()
g.swapChain = nil
}
}
// appendAdapters appends found adapters to the given adapters.
// Releasing them is the caller's responsibility.
func (g *graphicsInfra) appendAdapters(adapters []*_IDXGIAdapter1, warp bool) ([]*_IDXGIAdapter1, error) {
if warp {
a, err := g.factory.EnumWarpAdapter()
if err != nil {
return nil, err
}
adapters = append(adapters, a)
return adapters, nil
}
for i := uint32(0); ; i++ {
a, err := g.factory.EnumAdapters1(i)
if errors.Is(err, _DXGI_ERROR_NOT_FOUND) {
break
}
if err != nil {
return nil, err
}
desc, err := a.GetDesc1()
if err != nil {
return nil, err
}
if desc.Flags&_DXGI_ADAPTER_FLAG_SOFTWARE != 0 {
continue
}
adapters = append(adapters, a)
}
return adapters, nil
}
func (g *graphicsInfra) isSwapChainInited() bool {
return g.swapChain != nil
}
func (g *graphicsInfra) initSwapChain(width, height int, device unsafe.Pointer, window windows.HWND) (ferr error) {
if g.swapChain != nil {
return fmt.Errorf("directx: swap chain must not be initialized at initSwapChain, but is already done")
}
// Create a swap chain.
//
// DXGI_ALPHA_MODE_PREMULTIPLIED doesn't work with a HWND well.
//
// IDXGIFactory::CreateSwapChain: Alpha blended swapchains must be created with CreateSwapChainForComposition,
// or CreateSwapChainForCoreWindow with the DXGI_SWAP_CHAIN_FLAG_FOREGROUND_LAYER flag
desc := &_DXGI_SWAP_CHAIN_DESC1{
Width: uint32(width),
Height: uint32(height),
Format: _DXGI_FORMAT_B8G8R8A8_UNORM,
BufferUsage: _DXGI_USAGE_RENDER_TARGET_OUTPUT,
BufferCount: frameCount,
SwapEffect: _DXGI_SWAP_EFFECT_FLIP_DISCARD,
SampleDesc: _DXGI_SAMPLE_DESC{
Count: 1,
Quality: 0,
},
}
if g.allowTearing {
desc.Flags |= uint32(_DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING)
}
s, err := g.factory.CreateSwapChainForHwnd(device, window, desc, nil, nil)
if err != nil {
return err
}
s.As(&g.swapChain)
defer func() {
if ferr != nil {
g.release()
}
}()
// MakeWindowAssociation should be called after swap chain creation.
// https://docs.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgifactory-makewindowassociation
if err := g.factory.MakeWindowAssociation(window, _DXGI_MWA_NO_WINDOW_CHANGES|_DXGI_MWA_NO_ALT_ENTER); err != nil {
return err
}
return nil
}
func (g *graphicsInfra) resizeSwapChain(width, height int) error {
if g.swapChain == nil {
return fmt.Errorf("directx: swap chain must be initialized at resizeSwapChain, but is not")
}
var flag uint32
if g.allowTearing {
flag |= uint32(_DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING)
}
if err := g.swapChain.ResizeBuffers(frameCount, uint32(width), uint32(height), _DXGI_FORMAT_B8G8R8A8_UNORM, flag); err != nil {
return err
}
return nil
}
func (g *graphicsInfra) currentBackBufferIndex() int {
return int(g.swapChain.GetCurrentBackBufferIndex())
}
func (g *graphicsInfra) present(vsyncEnabled bool) error {
if g.swapChain == nil {
return fmt.Errorf("directx: swap chain must be initialized at present, but is not")
}
var syncInterval uint32
var flags _DXGI_PRESENT
if g.occluded {
// The screen is not visible. Test whether we can resume.
flags |= _DXGI_PRESENT_TEST
} else {
// Do actual rendering only when the screen is visible.
if vsyncEnabled {
syncInterval = 1
} else if g.allowTearing {
flags |= _DXGI_PRESENT_ALLOW_TEARING
}
}
occluded, err := g.swapChain.Present(syncInterval, uint32(flags))
if err != nil {
return err
}
g.occluded = occluded
// Reduce FPS when the screen is invisible.
now := time.Now()
if g.occluded {
if delta := 100*time.Millisecond - now.Sub(g.lastTime); delta > 0 {
time.Sleep(delta)
}
}
g.lastTime = now
return nil
}
func (g *graphicsInfra) getBuffer(buffer uint32, riid *windows.GUID) (unsafe.Pointer, error) {
return g.swapChain.GetBuffer(buffer, riid)
}