From c371659f635b5deb2407b393ab2ab48e5ca9110a Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Thu, 22 Dec 2022 02:56:36 +0900 Subject: [PATCH] internal/graphicsdriver/directx: implement suspend/resume for Xbox --- .../graphicsdriver/directx/api_windows.go | 40 ++++++++++++++-- .../directx/graphics_windows.go | 48 ++++++++++++++++++- 2 files changed, 81 insertions(+), 7 deletions(-) diff --git a/internal/graphicsdriver/directx/api_windows.go b/internal/graphicsdriver/directx/api_windows.go index ea67a45c9..4f06eada9 100644 --- a/internal/graphicsdriver/directx/api_windows.go +++ b/internal/graphicsdriver/directx/api_windows.go @@ -1011,12 +1011,18 @@ type _D3D12XBOX_WAIT_FRAME_OBJECT_LIST struct { pSignaledObjectIndex *uint32 } +type _PAPPSTATE_CHANGE_ROUTINE func(quiesced bool, context unsafe.Pointer) uintptr + 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") d3d12x = windows.NewLazySystemDLL(microsoftgdk.D3D12DLLName()) d3dcompiler = windows.NewLazySystemDLL("d3dcompiler_47.dll") dxgi = windows.NewLazySystemDLL("dxgi.dll") + procRegisterAppStateChangeNotification = appnotify.NewProc("RegisterAppStateChangeNotification") + procD3D12CreateDevice = d3d12.NewProc("D3D12CreateDevice") procD3D12GetDebugInterface = d3d12.NewProc("D3D12GetDebugInterface") procD3D12SerializeRootSignature = d3d12.NewProc("D3D12SerializeRootSignature") @@ -1115,6 +1121,16 @@ func _D3DCompile(srcData []byte, sourceName string, pDefines []_D3D_SHADER_MACRO 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(®istration))) + 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) { var factory *_IDXGIFactory4 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. _ uintptr _ uintptr - _ uintptr - _ uintptr + SuspendX uintptr + ResumeX uintptr _ uintptr _ uintptr _ uintptr @@ -1326,6 +1342,18 @@ func (i *_ID3D12CommandQueue) PresentX(planeCount uint32, pPlaneParameters *_D3D 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 { var r uintptr if is64bit { @@ -1342,9 +1370,11 @@ func (i *_ID3D12CommandQueue) Signal(signal *_ID3D12Fence, value uint64) error { 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) SuspendX(flags uint32) error { + if r, _, _ := syscall.Syscall(i.vtbl.SuspendX, 2, uintptr(unsafe.Pointer(i)), uintptr(flags), 0); uint32(r) != uint32(windows.S_OK) { + return fmt.Errorf("directx: ID3D12CommandQueue::SuspendX failed: %w", handleError(windows.Handle(uint32(r)))) + } + return nil } type _ID3D12Debug struct { diff --git a/internal/graphicsdriver/directx/graphics_windows.go b/internal/graphicsdriver/directx/graphics_windows.go index ba90579fd..b68d71ed7 100644 --- a/internal/graphicsdriver/directx/graphics_windows.go +++ b/internal/graphicsdriver/directx/graphics_windows.go @@ -150,6 +150,10 @@ type Graphics struct { newScreenWidth int newScreenHeight int + suspendingCh chan struct{} + suspendedCh chan struct{} + resumeCh chan struct{} + pipelineStates } @@ -341,11 +345,35 @@ func (g *Graphics) initializeXbox(useWARP bool, useDebugLayer bool) (ferr error) 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 { return err } - dxgiDevice := (*_IDXGIDevice)(dd) + dxgiDevice := (*_IDXGIDevice)(d) defer dxgiDevice.Release() dxgiAdapter, err := dxgiDevice.GetAdapter() @@ -727,6 +755,22 @@ func (g *Graphics) SetWindow(window uintptr) { func (g *Graphics) Begin() error { 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 if err := g.device.WaitFrameEventX(_D3D12XBOX_FRAME_EVENT_ORIGIN, windows.INFINITE, nil, _D3D12XBOX_WAIT_FRAME_EVENT_FLAG_NONE, &g.framePipelineToken); err != nil { return err