internal/graphicsdriver/directx: implement suspend/resume for Xbox

This commit is contained in:
Hajime Hoshi 2022-12-22 02:56:36 +09:00
parent 8dc582aca4
commit de8184ac10
2 changed files with 81 additions and 7 deletions

View File

@ -1011,12 +1011,18 @@ type _D3D12XBOX_WAIT_FRAME_OBJECT_LIST struct {
pSignaledObjectIndex *uint32 pSignaledObjectIndex *uint32
} }
type _PAPPSTATE_CHANGE_ROUTINE func(quiesced bool, context unsafe.Pointer) uintptr
var ( var (
// https://github.com/MicrosoftDocs/sdk-api/blob/docs/sdk-api-src/content/appnotify/nf-appnotify-registerappstatechangenotification.md
appnotify = windows.NewLazySystemDLL("API-MS-Win-Core-psm-appnotify-l1-1-0.dll")
d3d12 = windows.NewLazySystemDLL("d3d12.dll") d3d12 = windows.NewLazySystemDLL("d3d12.dll")
d3d12x = windows.NewLazySystemDLL(microsoftgdk.D3D12DLLName()) d3d12x = windows.NewLazySystemDLL(microsoftgdk.D3D12DLLName())
d3dcompiler = windows.NewLazySystemDLL("d3dcompiler_47.dll") d3dcompiler = windows.NewLazySystemDLL("d3dcompiler_47.dll")
dxgi = windows.NewLazySystemDLL("dxgi.dll") dxgi = windows.NewLazySystemDLL("dxgi.dll")
procRegisterAppStateChangeNotification = appnotify.NewProc("RegisterAppStateChangeNotification")
procD3D12CreateDevice = d3d12.NewProc("D3D12CreateDevice") procD3D12CreateDevice = d3d12.NewProc("D3D12CreateDevice")
procD3D12GetDebugInterface = d3d12.NewProc("D3D12GetDebugInterface") procD3D12GetDebugInterface = d3d12.NewProc("D3D12GetDebugInterface")
procD3D12SerializeRootSignature = d3d12.NewProc("D3D12SerializeRootSignature") procD3D12SerializeRootSignature = d3d12.NewProc("D3D12SerializeRootSignature")
@ -1115,6 +1121,16 @@ func _D3DCompile(srcData []byte, sourceName string, pDefines []_D3D_SHADER_MACRO
return code, nil return code, nil
} }
func _RegisterAppStateChangeNotification(routine _PAPPSTATE_CHANGE_ROUTINE, context unsafe.Pointer) (unsafe.Pointer, error) {
cb := windows.NewCallback(routine)
var registration unsafe.Pointer
r, _, _ := procRegisterAppStateChangeNotification.Call(cb, uintptr(context), uintptr(unsafe.Pointer(&registration)))
if windows.Errno(r) != windows.ERROR_SUCCESS {
return nil, fmt.Errorf("directx: RegisterAppStateChangeNotification failed: %w", windows.Errno(r))
}
return registration, nil
}
func _CreateDXGIFactory2(flags uint32) (*_IDXGIFactory4, error) { func _CreateDXGIFactory2(flags uint32) (*_IDXGIFactory4, error) {
var factory *_IDXGIFactory4 var factory *_IDXGIFactory4
r, _, _ := procCreateDXGIFactory2.Call(uintptr(flags), uintptr(unsafe.Pointer(&_IID_IDXGIFactory4)), uintptr(unsafe.Pointer(&factory))) r, _, _ := procCreateDXGIFactory2.Call(uintptr(flags), uintptr(unsafe.Pointer(&_IID_IDXGIFactory4)), uintptr(unsafe.Pointer(&factory)))
@ -1296,8 +1312,8 @@ type _ID3D12CommandQueue_Vtbl struct {
// These members are for Xbox. // These members are for Xbox.
_ uintptr _ uintptr
_ uintptr _ uintptr
_ uintptr SuspendX uintptr
_ uintptr ResumeX uintptr
_ uintptr _ uintptr
_ uintptr _ uintptr
_ uintptr _ uintptr
@ -1326,6 +1342,18 @@ func (i *_ID3D12CommandQueue) PresentX(planeCount uint32, pPlaneParameters *_D3D
return nil return nil
} }
func (i *_ID3D12CommandQueue) Release() uint32 {
r, _, _ := syscall.Syscall(i.vtbl.Release, 1, uintptr(unsafe.Pointer(i)), 0, 0)
return uint32(r)
}
func (i *_ID3D12CommandQueue) ResumeX() error {
if r, _, _ := syscall.Syscall(i.vtbl.ResumeX, 1, uintptr(unsafe.Pointer(i)), 0, 0); uint32(r) != uint32(windows.S_OK) {
return fmt.Errorf("directx: ID3D12CommandQueue::ResumeX failed: %w", handleError(windows.Handle(uint32(r))))
}
return nil
}
func (i *_ID3D12CommandQueue) Signal(signal *_ID3D12Fence, value uint64) error { func (i *_ID3D12CommandQueue) Signal(signal *_ID3D12Fence, value uint64) error {
var r uintptr var r uintptr
if is64bit { if is64bit {
@ -1342,9 +1370,11 @@ func (i *_ID3D12CommandQueue) Signal(signal *_ID3D12Fence, value uint64) error {
return nil return nil
} }
func (i *_ID3D12CommandQueue) Release() uint32 { func (i *_ID3D12CommandQueue) SuspendX(flags uint32) error {
r, _, _ := syscall.Syscall(i.vtbl.Release, 1, uintptr(unsafe.Pointer(i)), 0, 0) if r, _, _ := syscall.Syscall(i.vtbl.SuspendX, 2, uintptr(unsafe.Pointer(i)), uintptr(flags), 0); uint32(r) != uint32(windows.S_OK) {
return uint32(r) return fmt.Errorf("directx: ID3D12CommandQueue::SuspendX failed: %w", handleError(windows.Handle(uint32(r))))
}
return nil
} }
type _ID3D12Debug struct { type _ID3D12Debug struct {

View File

@ -150,6 +150,10 @@ type Graphics struct {
newScreenWidth int newScreenWidth int
newScreenHeight int newScreenHeight int
suspendingCh chan struct{}
suspendedCh chan struct{}
resumeCh chan struct{}
pipelineStates pipelineStates
} }
@ -341,11 +345,35 @@ func (g *Graphics) initializeXbox(useWARP bool, useDebugLayer bool) (ferr error)
return err return err
} }
dd, err := g.device.QueryInterface(&_IID_IDXGIDevice) if err := g.registerFrameEventForXbox(); err != nil {
return err
}
g.suspendingCh = make(chan struct{})
g.suspendedCh = make(chan struct{})
g.resumeCh = make(chan struct{})
if _, err := _RegisterAppStateChangeNotification(func(quiesced bool, context unsafe.Pointer) uintptr {
if quiesced {
g.suspendingCh <- struct{}{}
// Confirm the suspension completed before the callback ends.
<-g.suspendedCh
} else {
g.resumeCh <- struct{}{}
}
return 0
}, nil); err != nil {
return err
}
return nil
}
func (g *Graphics) registerFrameEventForXbox() error {
d, err := g.device.QueryInterface(&_IID_IDXGIDevice)
if err != nil { if err != nil {
return err return err
} }
dxgiDevice := (*_IDXGIDevice)(dd) dxgiDevice := (*_IDXGIDevice)(d)
defer dxgiDevice.Release() defer dxgiDevice.Release()
dxgiAdapter, err := dxgiDevice.GetAdapter() dxgiAdapter, err := dxgiDevice.GetAdapter()
@ -727,6 +755,22 @@ func (g *Graphics) SetWindow(window uintptr) {
func (g *Graphics) Begin() error { func (g *Graphics) Begin() error {
if microsoftgdk.IsXbox() && !g.frameStarted { if microsoftgdk.IsXbox() && !g.frameStarted {
select {
case <-g.suspendingCh:
if err := g.commandQueue.SuspendX(0); err != nil {
return err
}
g.suspendedCh <- struct{}{}
<-g.resumeCh
if err := g.commandQueue.ResumeX(); err != nil {
return err
}
if err := g.registerFrameEventForXbox(); err != nil {
return err
}
default:
}
g.framePipelineToken = _D3D12XBOX_FRAME_PIPELINE_TOKEN_NULL g.framePipelineToken = _D3D12XBOX_FRAME_PIPELINE_TOKEN_NULL
if err := g.device.WaitFrameEventX(_D3D12XBOX_FRAME_EVENT_ORIGIN, windows.INFINITE, nil, _D3D12XBOX_WAIT_FRAME_EVENT_FLAG_NONE, &g.framePipelineToken); err != nil { if err := g.device.WaitFrameEventX(_D3D12XBOX_FRAME_EVENT_ORIGIN, windows.INFINITE, nil, _D3D12XBOX_WAIT_FRAME_EVENT_FLAG_NONE, &g.framePipelineToken); err != nil {
return err return err