mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-26 19:58:54 +01:00
1181 lines
31 KiB
Go
1181 lines
31 KiB
Go
// Copyright 2023 The Ebitengine Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package directx
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/windows"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
|
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
|
|
"github.com/hajimehoshi/ebiten/v2/internal/microsoftgdk"
|
|
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
|
|
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/hlsl"
|
|
)
|
|
|
|
type resourceWithSize struct {
|
|
value *_ID3D12Resource
|
|
sizeInBytes uint32
|
|
}
|
|
|
|
func (r *resourceWithSize) release() {
|
|
r.value.Release()
|
|
r.value = nil
|
|
r.sizeInBytes = 0
|
|
}
|
|
|
|
type graphics12 struct {
|
|
debug *_ID3D12Debug
|
|
device *_ID3D12Device
|
|
commandQueue *_ID3D12CommandQueue
|
|
rtvDescriptorHeap *_ID3D12DescriptorHeap
|
|
rtvDescriptorSize uint32
|
|
renderTargets [frameCount]*_ID3D12Resource
|
|
framePipelineToken _D3D12XBOX_FRAME_PIPELINE_TOKEN
|
|
|
|
fence *_ID3D12Fence
|
|
fenceValues [frameCount]uint64
|
|
fenceWaitEvent windows.Handle
|
|
|
|
// drawCommandAllocators are command allocators for a 3D engine (DrawIndexedInstanced).
|
|
// For the word 'engine', see https://docs.microsoft.com/en-us/windows/win32/direct3d12/user-mode-heap-synchronization.
|
|
// The term 'draw' is used instead of '3D' in this package.
|
|
drawCommandAllocators [frameCount]*_ID3D12CommandAllocator
|
|
|
|
// copyCommandAllocators are command allocators for a copy engine (CopyTextureRegion).
|
|
copyCommandAllocators [frameCount]*_ID3D12CommandAllocator
|
|
|
|
// drawCommandList is a command list for a 3D engine (DrawIndexedInstanced).
|
|
drawCommandList *_ID3D12GraphicsCommandList
|
|
|
|
needFlushDrawCommandList bool
|
|
|
|
// copyCommandList is a command list for a copy engine (CopyTextureRegion).
|
|
copyCommandList *_ID3D12GraphicsCommandList
|
|
|
|
needFlushCopyCommandList bool
|
|
|
|
// drawCommandList and copyCommandList are exclusive: if one is not empty, the other must be empty.
|
|
|
|
vertices [frameCount][]*resourceWithSize
|
|
indices [frameCount][]*resourceWithSize
|
|
|
|
graphicsInfra *graphicsInfra
|
|
|
|
window windows.HWND
|
|
|
|
frameIndex int
|
|
prevBeginFrameIndex int
|
|
|
|
// frameStarted is true since Begin until End with present
|
|
frameStarted bool
|
|
|
|
images map[graphicsdriver.ImageID]*image12
|
|
screenImage *image12
|
|
nextImageID graphicsdriver.ImageID
|
|
disposedImages [frameCount][]*image12
|
|
|
|
shaders map[graphicsdriver.ShaderID]*shader12
|
|
nextShaderID graphicsdriver.ShaderID
|
|
disposedShaders [frameCount][]*shader12
|
|
|
|
vsyncEnabled bool
|
|
|
|
newScreenWidth int
|
|
newScreenHeight int
|
|
|
|
suspendingCh chan struct{}
|
|
suspendedCh chan struct{}
|
|
resumeCh chan struct{}
|
|
|
|
pipelineStates
|
|
}
|
|
|
|
func newGraphics12(useWARP bool, useDebugLayer bool, featureLevel _D3D_FEATURE_LEVEL) (*graphics12, error) {
|
|
g := &graphics12{}
|
|
|
|
// Initialize not only a device but also other members like a fence.
|
|
// Even if initializing a device succeeds, initializing a fence might fail (#2142).
|
|
if microsoftgdk.IsXbox() {
|
|
if err := g.initializeXbox(useWARP, useDebugLayer); err != nil {
|
|
return nil, err
|
|
}
|
|
} else {
|
|
if err := g.initializeDesktop(useWARP, useDebugLayer, featureLevel); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return g, nil
|
|
}
|
|
|
|
func (g *graphics12) initializeDesktop(useWARP bool, useDebugLayer bool, featureLevel _D3D_FEATURE_LEVEL) (ferr error) {
|
|
if err := d3d12.Load(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// As g's lifetime is the same as the process's lifetime, debug and other objects are never released
|
|
// if this initialization succeeds.
|
|
|
|
// The debug interface is optional and might not exist.
|
|
if useDebugLayer {
|
|
d, err := _D3D12GetDebugInterface()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.debug = d
|
|
defer func() {
|
|
if ferr != nil {
|
|
g.debug.Release()
|
|
g.debug = nil
|
|
}
|
|
}()
|
|
g.debug.EnableDebugLayer()
|
|
}
|
|
|
|
f, err := _CreateDXGIFactory()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
gi, err := newGraphicsInfra(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.graphicsInfra = gi
|
|
defer func() {
|
|
if ferr != nil {
|
|
g.graphicsInfra.release()
|
|
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
|
|
if useWARP {
|
|
if len(adapters) > 0 {
|
|
adapter = adapters[0]
|
|
}
|
|
} else {
|
|
for _, a := range adapters {
|
|
desc, err := a.GetDesc1()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
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
|
|
}
|
|
}
|
|
|
|
if adapter == nil {
|
|
return errors.New("directx: DirectX 12 is not supported")
|
|
}
|
|
|
|
d, err := _D3D12CreateDevice(unsafe.Pointer(adapter), featureLevel, &_IID_ID3D12Device, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.device = (*_ID3D12Device)(d)
|
|
|
|
if err := g.initializeMembers(g.frameIndex); err != nil {
|
|
return err
|
|
}
|
|
|
|
// GetCopyableFootprints might return an invalid value with Wine (#2114).
|
|
// To check this early, call NewImage here.
|
|
i, err := g.NewImage(1, 1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
i.Dispose()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *graphics12) initializeXbox(useWARP bool, useDebugLayer bool) (ferr error) {
|
|
g = &graphics12{}
|
|
|
|
if err := d3d12x.Load(); err != nil {
|
|
return err
|
|
}
|
|
|
|
params := &_D3D12XBOX_CREATE_DEVICE_PARAMETERS{
|
|
Version: microsoftgdk.D3D12SDKVersion(),
|
|
GraphicsCommandQueueRingSizeBytes: _D3D12XBOX_DEFAULT_SIZE_BYTES,
|
|
GraphicsScratchMemorySizeBytes: _D3D12XBOX_DEFAULT_SIZE_BYTES,
|
|
ComputeScratchMemorySizeBytes: _D3D12XBOX_DEFAULT_SIZE_BYTES,
|
|
}
|
|
if useDebugLayer {
|
|
params.ProcessDebugFlags = _D3D12_PROCESS_DEBUG_FLAG_DEBUG_LAYER_ENABLED
|
|
}
|
|
d, err := _D3D12XboxCreateDevice(nil, params, &_IID_ID3D12Device)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.device = (*_ID3D12Device)(d)
|
|
|
|
if err := g.initializeMembers(g.frameIndex); err != nil {
|
|
return err
|
|
}
|
|
|
|
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 *graphics12) registerFrameEventForXbox() error {
|
|
d, err := g.device.QueryInterface(&_IID_IDXGIDevice)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dxgiDevice := (*_IDXGIDevice)(d)
|
|
defer dxgiDevice.Release()
|
|
|
|
dxgiAdapter, err := dxgiDevice.GetAdapter()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer dxgiAdapter.Release()
|
|
|
|
dxgiOutput, err := dxgiAdapter.EnumOutputs(0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer dxgiOutput.Release()
|
|
|
|
if err := g.device.SetFrameIntervalX(dxgiOutput, _D3D12XBOX_FRAME_INTERVAL_60_HZ, frameCount-1, _D3D12XBOX_FRAME_INTERVAL_FLAG_NONE); err != nil {
|
|
return err
|
|
}
|
|
if err := g.device.ScheduleFrameEventX(_D3D12XBOX_FRAME_EVENT_ORIGIN, 0, nil, _D3D12XBOX_SCHEDULE_FRAME_EVENT_FLAG_NONE); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *graphics12) initializeMembers(frameIndex int) (ferr error) {
|
|
// Create an event for a fence.
|
|
e, err := windows.CreateEventEx(nil, nil, 0, windows.EVENT_MODIFY_STATE|windows.SYNCHRONIZE)
|
|
if err != nil {
|
|
return fmt.Errorf("directx: CreateEvent failed: %w", err)
|
|
}
|
|
g.fenceWaitEvent = e
|
|
|
|
// Create a command queue.
|
|
desc := _D3D12_COMMAND_QUEUE_DESC{
|
|
Type: _D3D12_COMMAND_LIST_TYPE_DIRECT,
|
|
Flags: _D3D12_COMMAND_QUEUE_FLAG_NONE,
|
|
}
|
|
c, err := g.device.CreateCommandQueue(&desc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.commandQueue = c
|
|
defer func() {
|
|
if ferr != nil {
|
|
g.commandQueue.Release()
|
|
g.commandQueue = nil
|
|
}
|
|
}()
|
|
|
|
// Create command allocators.
|
|
for i := 0; i < frameCount; i++ {
|
|
dca, err := g.device.CreateCommandAllocator(_D3D12_COMMAND_LIST_TYPE_DIRECT)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.drawCommandAllocators[i] = dca
|
|
defer func(i int) {
|
|
if ferr != nil {
|
|
g.drawCommandAllocators[i].Release()
|
|
g.drawCommandAllocators[i] = nil
|
|
}
|
|
}(i)
|
|
|
|
cca, err := g.device.CreateCommandAllocator(_D3D12_COMMAND_LIST_TYPE_DIRECT)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.copyCommandAllocators[i] = cca
|
|
defer func(i int) {
|
|
if ferr != nil {
|
|
g.copyCommandAllocators[i].Release()
|
|
g.copyCommandAllocators[i] = nil
|
|
}
|
|
}(i)
|
|
}
|
|
|
|
// Create a frame fence.
|
|
f, err := g.device.CreateFence(0, _D3D12_FENCE_FLAG_NONE)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.fence = f
|
|
defer func() {
|
|
if ferr != nil {
|
|
g.fence.Release()
|
|
g.fence = nil
|
|
}
|
|
}()
|
|
g.fenceValues[frameIndex]++
|
|
|
|
// Create command lists.
|
|
dcl, err := g.device.CreateCommandList(0, _D3D12_COMMAND_LIST_TYPE_DIRECT, g.drawCommandAllocators[0], nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.drawCommandList = dcl
|
|
defer func() {
|
|
if ferr != nil {
|
|
g.drawCommandList.Release()
|
|
g.drawCommandList = nil
|
|
}
|
|
}()
|
|
|
|
ccl, err := g.device.CreateCommandList(0, _D3D12_COMMAND_LIST_TYPE_DIRECT, g.copyCommandAllocators[0], nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.copyCommandList = ccl
|
|
defer func() {
|
|
if ferr != nil {
|
|
g.copyCommandList.Release()
|
|
g.copyCommandList = nil
|
|
}
|
|
}()
|
|
|
|
// Close the command list once as this is immediately Reset at Begin.
|
|
if err := g.drawCommandList.Close(); err != nil {
|
|
return err
|
|
}
|
|
if err := g.copyCommandList.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Create a descriptor heap for RTV.
|
|
h, err := g.device.CreateDescriptorHeap(&_D3D12_DESCRIPTOR_HEAP_DESC{
|
|
Type: _D3D12_DESCRIPTOR_HEAP_TYPE_RTV,
|
|
NumDescriptors: frameCount,
|
|
Flags: _D3D12_DESCRIPTOR_HEAP_FLAG_NONE,
|
|
NodeMask: 0,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.rtvDescriptorHeap = h
|
|
defer func() {
|
|
if ferr != nil {
|
|
g.rtvDescriptorHeap.Release()
|
|
g.rtvDescriptorHeap = nil
|
|
}
|
|
}()
|
|
g.rtvDescriptorSize = g.device.GetDescriptorHandleIncrementSize(_D3D12_DESCRIPTOR_HEAP_TYPE_RTV)
|
|
|
|
if err := g.pipelineStates.initialize(g.device); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *graphics12) Initialize() (err error) {
|
|
// Initialization should already be done.
|
|
return nil
|
|
}
|
|
|
|
func createBuffer(device *_ID3D12Device, bufferSize uint64, heapType _D3D12_HEAP_TYPE) (*_ID3D12Resource, error) {
|
|
state := _D3D12_RESOURCE_STATE_GENERIC_READ()
|
|
if heapType == _D3D12_HEAP_TYPE_READBACK {
|
|
state = _D3D12_RESOURCE_STATE_COPY_DEST
|
|
}
|
|
|
|
r, err := device.CreateCommittedResource(&_D3D12_HEAP_PROPERTIES{
|
|
Type: heapType,
|
|
CPUPageProperty: _D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
|
|
MemoryPoolPreference: _D3D12_MEMORY_POOL_UNKNOWN,
|
|
CreationNodeMask: 1,
|
|
VisibleNodeMask: 1,
|
|
}, _D3D12_HEAP_FLAG_NONE, &_D3D12_RESOURCE_DESC{
|
|
Dimension: _D3D12_RESOURCE_DIMENSION_BUFFER,
|
|
Alignment: 0,
|
|
Width: bufferSize,
|
|
Height: 1,
|
|
DepthOrArraySize: 1,
|
|
MipLevels: 1,
|
|
Format: _DXGI_FORMAT_UNKNOWN,
|
|
SampleDesc: _DXGI_SAMPLE_DESC{
|
|
Count: 1,
|
|
Quality: 0,
|
|
},
|
|
Layout: _D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
|
|
Flags: _D3D12_RESOURCE_FLAG_NONE,
|
|
}, state, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
func (g *graphics12) updateSwapChain(width, height int) error {
|
|
if g.window == 0 {
|
|
return errors.New("directx: the window handle is not initialized yet")
|
|
}
|
|
|
|
if microsoftgdk.IsXbox() {
|
|
if err := g.initSwapChainXbox(width, height); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
if !g.graphicsInfra.isSwapChainInited() {
|
|
if err := g.initSwapChainDesktop(width, height); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
g.newScreenWidth = width
|
|
g.newScreenHeight = height
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *graphics12) initSwapChainDesktop(width, height int) error {
|
|
if err := g.graphicsInfra.initSwapChain(width, height, unsafe.Pointer(g.commandQueue), g.window); err != nil {
|
|
return err
|
|
}
|
|
|
|
// TODO: Get the current buffer index?
|
|
|
|
if err := g.createRenderTargetViewsDesktop(); err != nil {
|
|
return err
|
|
}
|
|
|
|
idx, err := g.graphicsInfra.currentBackBufferIndex()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.frameIndex = idx
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *graphics12) initSwapChainXbox(width, height int) (ferr error) {
|
|
h, err := g.rtvDescriptorHeap.GetCPUDescriptorHandleForHeapStart()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for i := 0; i < frameCount; i++ {
|
|
r, err := g.device.CreateCommittedResource(&_D3D12_HEAP_PROPERTIES{
|
|
Type: _D3D12_HEAP_TYPE_DEFAULT,
|
|
CPUPageProperty: _D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
|
|
MemoryPoolPreference: _D3D12_MEMORY_POOL_UNKNOWN,
|
|
CreationNodeMask: 1,
|
|
VisibleNodeMask: 1,
|
|
}, _D3D12_HEAP_FLAG_ALLOW_DISPLAY, &_D3D12_RESOURCE_DESC{
|
|
Dimension: _D3D12_RESOURCE_DIMENSION_TEXTURE2D,
|
|
Alignment: 0,
|
|
Width: uint64(width),
|
|
Height: uint32(height),
|
|
DepthOrArraySize: 1,
|
|
MipLevels: 1, // Use a single mipmap level
|
|
Format: _DXGI_FORMAT_B8G8R8A8_UNORM,
|
|
SampleDesc: _DXGI_SAMPLE_DESC{
|
|
Count: 1,
|
|
Quality: 0,
|
|
},
|
|
Layout: _D3D12_TEXTURE_LAYOUT_UNKNOWN,
|
|
Flags: _D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET,
|
|
}, _D3D12_RESOURCE_STATE_PRESENT, &_D3D12_CLEAR_VALUE{
|
|
Format: _DXGI_FORMAT_B8G8R8A8_UNORM,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
g.renderTargets[i] = r
|
|
defer func(i int) {
|
|
if ferr != nil {
|
|
g.renderTargets[i].Release()
|
|
g.renderTargets[i] = nil
|
|
}
|
|
}(i)
|
|
|
|
g.device.CreateRenderTargetView(r, &_D3D12_RENDER_TARGET_VIEW_DESC{
|
|
Format: _DXGI_FORMAT_B8G8R8A8_UNORM,
|
|
ViewDimension: _D3D12_RTV_DIMENSION_TEXTURE2D,
|
|
}, h)
|
|
h.Offset(1, g.rtvDescriptorSize)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *graphics12) resizeSwapChainDesktop(width, height int) error {
|
|
// All resources must be released before ResizeBuffers.
|
|
if err := g.waitForCommandQueue(); err != nil {
|
|
return err
|
|
}
|
|
g.releaseResources(g.frameIndex)
|
|
|
|
for i := 0; i < frameCount; i++ {
|
|
g.fenceValues[i] = g.fenceValues[g.frameIndex]
|
|
}
|
|
|
|
for _, r := range g.renderTargets {
|
|
r.Release()
|
|
}
|
|
|
|
if err := g.graphicsInfra.resizeSwapChain(width, height); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := g.createRenderTargetViewsDesktop(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *graphics12) createRenderTargetViewsDesktop() (ferr error) {
|
|
// Create frame resources.
|
|
h, err := g.rtvDescriptorHeap.GetCPUDescriptorHandleForHeapStart()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for i := 0; i < frameCount; i++ {
|
|
r, err := g.graphicsInfra.getBuffer(uint32(i), &_IID_ID3D12Resource)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.renderTargets[i] = (*_ID3D12Resource)(r)
|
|
defer func(i int) {
|
|
if ferr != nil {
|
|
g.renderTargets[i].Release()
|
|
g.renderTargets[i] = nil
|
|
}
|
|
}(i)
|
|
|
|
g.device.CreateRenderTargetView((*_ID3D12Resource)(r), nil, h)
|
|
h.Offset(1, g.rtvDescriptorSize)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *graphics12) SetWindow(window uintptr) {
|
|
g.window = windows.HWND(window)
|
|
// TODO: need to update the swap chain?
|
|
}
|
|
|
|
func (g *graphics12) 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
|
|
}
|
|
}
|
|
g.frameStarted = true
|
|
|
|
if g.prevBeginFrameIndex != g.frameIndex {
|
|
if err := g.drawCommandAllocators[g.frameIndex].Reset(); err != nil {
|
|
return err
|
|
}
|
|
if err := g.copyCommandAllocators[g.frameIndex].Reset(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
g.prevBeginFrameIndex = g.frameIndex
|
|
|
|
if err := g.drawCommandList.Reset(g.drawCommandAllocators[g.frameIndex], nil); err != nil {
|
|
return err
|
|
}
|
|
if err := g.copyCommandList.Reset(g.copyCommandAllocators[g.frameIndex], nil); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (g *graphics12) End(present bool) error {
|
|
// The swap chain might still be nil when Begin-End is invoked not by a frame (e.g., Image.At).
|
|
|
|
// As copyCommandList and drawCommandList are exclusive, the order should not matter here.
|
|
if err := g.flushCommandList(g.copyCommandList); err != nil {
|
|
return err
|
|
}
|
|
if err := g.copyCommandList.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// screenImage can be nil in tests.
|
|
if present && g.screenImage != nil {
|
|
if rb, ok := g.screenImage.transiteState(_D3D12_RESOURCE_STATE_PRESENT); ok {
|
|
g.drawCommandList.ResourceBarrier([]_D3D12_RESOURCE_BARRIER_Transition{rb})
|
|
}
|
|
}
|
|
|
|
if err := g.drawCommandList.Close(); err != nil {
|
|
return err
|
|
}
|
|
g.commandQueue.ExecuteCommandLists([]*_ID3D12GraphicsCommandList{g.drawCommandList})
|
|
|
|
// Release vertices and indices buffers when too many ones were created.
|
|
// The threshold is an arbitrary number.
|
|
// This is needed especially for testings, where present is always false.
|
|
if len(g.vertices[g.frameIndex]) >= 16 {
|
|
if err := g.waitForCommandQueue(); err != nil {
|
|
return err
|
|
}
|
|
g.releaseResources(g.frameIndex)
|
|
g.resetVerticesAndIndices(g.frameIndex, true)
|
|
}
|
|
|
|
g.pipelineStates.resetConstantBuffers(g.frameIndex)
|
|
|
|
if present {
|
|
if microsoftgdk.IsXbox() {
|
|
if err := g.presentXbox(); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
if err := g.presentDesktop(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if g.newScreenWidth != 0 && g.newScreenHeight != 0 {
|
|
if err := g.resizeSwapChainDesktop(g.newScreenWidth, g.newScreenHeight); err != nil {
|
|
return err
|
|
}
|
|
g.screenImage.width = g.newScreenWidth
|
|
g.screenImage.height = g.newScreenHeight
|
|
g.newScreenWidth = 0
|
|
g.newScreenHeight = 0
|
|
}
|
|
|
|
if err := g.moveToNextFrame(); err != nil {
|
|
return err
|
|
}
|
|
|
|
g.releaseResources(g.frameIndex)
|
|
g.resetVerticesAndIndices(g.frameIndex, false)
|
|
|
|
g.frameStarted = false
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *graphics12) presentDesktop() error {
|
|
return g.graphicsInfra.present(g.vsyncEnabled)
|
|
}
|
|
|
|
func (g *graphics12) presentXbox() error {
|
|
return g.commandQueue.PresentX(1, &_D3D12XBOX_PRESENT_PLANE_PARAMETERS{
|
|
Token: g.framePipelineToken,
|
|
ResourceCount: 1,
|
|
ppResources: &g.renderTargets[g.frameIndex],
|
|
}, nil)
|
|
}
|
|
|
|
func (g *graphics12) moveToNextFrame() error {
|
|
fv := g.fenceValues[g.frameIndex]
|
|
if err := g.commandQueue.Signal(g.fence, fv); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update the frame index.
|
|
if microsoftgdk.IsXbox() {
|
|
g.frameIndex = (g.frameIndex + 1) % frameCount
|
|
} else {
|
|
idx, err := g.graphicsInfra.currentBackBufferIndex()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.frameIndex = idx
|
|
}
|
|
|
|
if g.fence.GetCompletedValue() < g.fenceValues[g.frameIndex] {
|
|
if err := g.fence.SetEventOnCompletion(g.fenceValues[g.frameIndex], g.fenceWaitEvent); err != nil {
|
|
return err
|
|
}
|
|
if _, err := windows.WaitForSingleObject(g.fenceWaitEvent, windows.INFINITE); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
g.fenceValues[g.frameIndex] = fv + 1
|
|
return nil
|
|
}
|
|
|
|
func (g *graphics12) releaseResources(frameIndex int) {
|
|
for i, img := range g.disposedImages[frameIndex] {
|
|
img.disposeImpl()
|
|
g.disposedImages[frameIndex][i] = nil
|
|
}
|
|
g.disposedImages[frameIndex] = g.disposedImages[frameIndex][:0]
|
|
|
|
for i, s := range g.disposedShaders[frameIndex] {
|
|
s.disposeImpl()
|
|
g.disposedShaders[frameIndex][i] = nil
|
|
}
|
|
g.disposedShaders[frameIndex] = g.disposedShaders[frameIndex][:0]
|
|
}
|
|
|
|
func (g *graphics12) resetVerticesAndIndices(frameIndex int, release bool) {
|
|
if release {
|
|
for i := range g.vertices[frameIndex] {
|
|
g.vertices[frameIndex][i].release()
|
|
g.vertices[frameIndex][i] = nil
|
|
}
|
|
}
|
|
g.vertices[frameIndex] = g.vertices[frameIndex][:0]
|
|
|
|
if release {
|
|
for i := range g.indices[frameIndex] {
|
|
g.indices[frameIndex][i].release()
|
|
g.indices[frameIndex][i] = nil
|
|
}
|
|
}
|
|
g.indices[frameIndex] = g.indices[frameIndex][:0]
|
|
}
|
|
|
|
// flushCommandList executes commands in the command list and waits for its completion.
|
|
//
|
|
// TODO: This is not efficient. Is it possible to make two command lists work in parallel?
|
|
func (g *graphics12) flushCommandList(commandList *_ID3D12GraphicsCommandList) error {
|
|
switch commandList {
|
|
case g.drawCommandList:
|
|
if !g.needFlushDrawCommandList {
|
|
return nil
|
|
}
|
|
g.needFlushDrawCommandList = false
|
|
case g.copyCommandList:
|
|
if !g.needFlushCopyCommandList {
|
|
return nil
|
|
}
|
|
g.needFlushCopyCommandList = false
|
|
}
|
|
|
|
if err := commandList.Close(); err != nil {
|
|
return err
|
|
}
|
|
|
|
g.commandQueue.ExecuteCommandLists([]*_ID3D12GraphicsCommandList{commandList})
|
|
|
|
if err := g.waitForCommandQueue(); err != nil {
|
|
return err
|
|
}
|
|
|
|
switch commandList {
|
|
case g.drawCommandList:
|
|
if err := g.drawCommandAllocators[g.frameIndex].Reset(); err != nil {
|
|
return err
|
|
}
|
|
if err := commandList.Reset(g.drawCommandAllocators[g.frameIndex], nil); err != nil {
|
|
return err
|
|
}
|
|
case g.copyCommandList:
|
|
if err := g.copyCommandAllocators[g.frameIndex].Reset(); err != nil {
|
|
return err
|
|
}
|
|
if err := commandList.Reset(g.copyCommandAllocators[g.frameIndex], nil); err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, img := range g.images {
|
|
img.releaseUploadingStagingBuffers()
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *graphics12) waitForCommandQueue() error {
|
|
fv := g.fenceValues[g.frameIndex]
|
|
if err := g.commandQueue.Signal(g.fence, fv); err != nil {
|
|
return err
|
|
}
|
|
if err := g.fence.SetEventOnCompletion(fv, g.fenceWaitEvent); err != nil {
|
|
return err
|
|
}
|
|
if _, err := windows.WaitForSingleObject(g.fenceWaitEvent, windows.INFINITE); err != nil {
|
|
return err
|
|
}
|
|
g.fenceValues[g.frameIndex]++
|
|
return nil
|
|
}
|
|
|
|
func (g *graphics12) SetTransparent(transparent bool) {
|
|
// TODO: Implement this?
|
|
}
|
|
|
|
func (g *graphics12) SetVertices(vertices []float32, indices []uint32) (ferr error) {
|
|
// Create buffers if necessary.
|
|
vidx := len(g.vertices[g.frameIndex])
|
|
if cap(g.vertices[g.frameIndex]) > vidx {
|
|
g.vertices[g.frameIndex] = g.vertices[g.frameIndex][:vidx+1]
|
|
} else {
|
|
g.vertices[g.frameIndex] = append(g.vertices[g.frameIndex], nil)
|
|
}
|
|
vsize := pow2(uint32(len(vertices)) * uint32(unsafe.Sizeof(vertices[0])))
|
|
if g.vertices[g.frameIndex][vidx] != nil && g.vertices[g.frameIndex][vidx].sizeInBytes < vsize {
|
|
g.vertices[g.frameIndex][vidx].release()
|
|
g.vertices[g.frameIndex][vidx] = nil
|
|
}
|
|
if g.vertices[g.frameIndex][vidx] == nil {
|
|
// TODO: Use the default heap for efficiently. See the official example HelloTriangle.
|
|
vs, err := createBuffer(g.device, uint64(vsize), _D3D12_HEAP_TYPE_UPLOAD)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.vertices[g.frameIndex][vidx] = &resourceWithSize{
|
|
value: vs,
|
|
sizeInBytes: vsize,
|
|
}
|
|
defer func() {
|
|
if ferr != nil {
|
|
g.vertices[g.frameIndex][vidx].release()
|
|
g.vertices[g.frameIndex][vidx] = nil
|
|
}
|
|
}()
|
|
}
|
|
|
|
iidx := len(g.indices[g.frameIndex])
|
|
if cap(g.indices[g.frameIndex]) > iidx {
|
|
g.indices[g.frameIndex] = g.indices[g.frameIndex][:iidx+1]
|
|
} else {
|
|
g.indices[g.frameIndex] = append(g.indices[g.frameIndex], nil)
|
|
}
|
|
isize := pow2(uint32(len(indices)) * uint32(unsafe.Sizeof(indices[0])))
|
|
if g.indices[g.frameIndex][iidx] != nil && g.indices[g.frameIndex][iidx].sizeInBytes < isize {
|
|
g.indices[g.frameIndex][iidx].release()
|
|
g.indices[g.frameIndex][iidx] = nil
|
|
}
|
|
if g.indices[g.frameIndex][iidx] == nil {
|
|
is, err := createBuffer(g.device, uint64(isize), _D3D12_HEAP_TYPE_UPLOAD)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
g.indices[g.frameIndex][iidx] = &resourceWithSize{
|
|
value: is,
|
|
sizeInBytes: isize,
|
|
}
|
|
defer func() {
|
|
if ferr != nil {
|
|
g.indices[g.frameIndex][iidx].release()
|
|
g.indices[g.frameIndex][iidx] = nil
|
|
}
|
|
}()
|
|
}
|
|
|
|
m, err := g.vertices[g.frameIndex][vidx].value.Map(0, &_D3D12_RANGE{0, 0})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
copy(unsafe.Slice((*float32)(unsafe.Pointer(m)), len(vertices)), vertices)
|
|
g.vertices[g.frameIndex][vidx].value.Unmap(0, nil)
|
|
|
|
m, err = g.indices[g.frameIndex][iidx].value.Map(0, &_D3D12_RANGE{0, 0})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
copy(unsafe.Slice((*uint32)(unsafe.Pointer(m)), len(indices)), indices)
|
|
g.indices[g.frameIndex][iidx].value.Unmap(0, nil)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *graphics12) NewImage(width, height int) (graphicsdriver.Image, error) {
|
|
desc := _D3D12_RESOURCE_DESC{
|
|
Dimension: _D3D12_RESOURCE_DIMENSION_TEXTURE2D,
|
|
Alignment: 0,
|
|
Width: uint64(graphics.InternalImageSize(width)),
|
|
Height: uint32(graphics.InternalImageSize(height)),
|
|
DepthOrArraySize: 1,
|
|
MipLevels: 0,
|
|
Format: _DXGI_FORMAT_R8G8B8A8_UNORM,
|
|
SampleDesc: _DXGI_SAMPLE_DESC{
|
|
Count: 1,
|
|
Quality: 0,
|
|
},
|
|
Layout: _D3D12_TEXTURE_LAYOUT_UNKNOWN,
|
|
Flags: _D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET,
|
|
}
|
|
|
|
state := _D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE
|
|
t, err := g.device.CreateCommittedResource(&_D3D12_HEAP_PROPERTIES{
|
|
Type: _D3D12_HEAP_TYPE_DEFAULT, // Upload?
|
|
CPUPageProperty: _D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
|
|
MemoryPoolPreference: _D3D12_MEMORY_POOL_UNKNOWN,
|
|
CreationNodeMask: 1,
|
|
VisibleNodeMask: 1,
|
|
}, _D3D12_HEAP_FLAG_NONE, &desc, state, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
i := &image12{
|
|
graphics: g,
|
|
id: g.genNextImageID(),
|
|
width: width,
|
|
height: height,
|
|
texture: t,
|
|
states: [frameCount]_D3D12_RESOURCE_STATES{state},
|
|
}
|
|
g.addImage(i)
|
|
return i, nil
|
|
}
|
|
|
|
func (g *graphics12) NewScreenFramebufferImage(width, height int) (graphicsdriver.Image, error) {
|
|
imageWidth := width
|
|
imageHeight := height
|
|
if g.screenImage != nil {
|
|
imageWidth = g.screenImage.width
|
|
imageHeight = g.screenImage.height
|
|
g.screenImage.Dispose()
|
|
g.screenImage = nil
|
|
}
|
|
|
|
if err := g.updateSwapChain(width, height); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
i := &image12{
|
|
graphics: g,
|
|
id: g.genNextImageID(),
|
|
width: imageWidth,
|
|
height: imageHeight,
|
|
screen: true,
|
|
states: [frameCount]_D3D12_RESOURCE_STATES{0, 0},
|
|
}
|
|
g.addImage(i)
|
|
g.screenImage = i
|
|
return i, nil
|
|
}
|
|
|
|
func (g *graphics12) addImage(img *image12) {
|
|
if g.images == nil {
|
|
g.images = map[graphicsdriver.ImageID]*image12{}
|
|
}
|
|
if _, ok := g.images[img.id]; ok {
|
|
panic(fmt.Sprintf("directx: image ID %d was already registered", img.id))
|
|
}
|
|
g.images[img.id] = img
|
|
}
|
|
|
|
func (g *graphics12) removeImage(img *image12) {
|
|
delete(g.images, img.id)
|
|
g.disposedImages[g.frameIndex] = append(g.disposedImages[g.frameIndex], img)
|
|
}
|
|
|
|
func (g *graphics12) addShader(s *shader12) {
|
|
if g.shaders == nil {
|
|
g.shaders = map[graphicsdriver.ShaderID]*shader12{}
|
|
}
|
|
if _, ok := g.shaders[s.id]; ok {
|
|
panic(fmt.Sprintf("directx: shader ID %d was already registered", s.id))
|
|
}
|
|
g.shaders[s.id] = s
|
|
}
|
|
|
|
func (g *graphics12) removeShader(s *shader12) {
|
|
delete(g.shaders, s.id)
|
|
g.disposedShaders[g.frameIndex] = append(g.disposedShaders[g.frameIndex], s)
|
|
}
|
|
|
|
func (g *graphics12) SetVsyncEnabled(enabled bool) {
|
|
g.vsyncEnabled = enabled
|
|
}
|
|
|
|
func (g *graphics12) NeedsRestoring() bool {
|
|
return false
|
|
}
|
|
|
|
func (g *graphics12) NeedsClearingScreen() bool {
|
|
// TODO: Confirm this is really true.
|
|
return true
|
|
}
|
|
|
|
func (g *graphics12) MaxImageSize() int {
|
|
return _D3D12_REQ_TEXTURE2D_U_OR_V_DIMENSION
|
|
}
|
|
|
|
func (g *graphics12) NewShader(program *shaderir.Program) (graphicsdriver.Shader, error) {
|
|
vs, ps, offsets := hlsl.Compile(program)
|
|
vsh, psh, err := compileShader(vs, ps)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s := &shader12{
|
|
graphics: g,
|
|
id: g.genNextShaderID(),
|
|
uniformTypes: program.Uniforms,
|
|
uniformOffsets: offsets,
|
|
vertexShader: vsh,
|
|
pixelShader: psh,
|
|
}
|
|
g.addShader(s)
|
|
return s, nil
|
|
}
|
|
|
|
func (g *graphics12) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.ShaderImageCount]graphicsdriver.ImageID, shaderID graphicsdriver.ShaderID, dstRegions []graphicsdriver.DstRegion, indexOffset int, blend graphicsdriver.Blend, uniforms []uint32, fillRule graphicsdriver.FillRule) error {
|
|
if shaderID == graphicsdriver.InvalidShaderID {
|
|
return fmt.Errorf("directx: shader ID is invalid")
|
|
}
|
|
|
|
if err := g.flushCommandList(g.copyCommandList); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Release constant buffers when too many ones will be created.
|
|
numPipelines := 1
|
|
if fillRule != graphicsdriver.FillAll {
|
|
numPipelines = 2
|
|
}
|
|
if len(g.pipelineStates.constantBuffers[g.frameIndex])+numPipelines > numDescriptorsPerFrame {
|
|
if err := g.flushCommandList(g.drawCommandList); err != nil {
|
|
return err
|
|
}
|
|
g.pipelineStates.releaseConstantBuffers(g.frameIndex)
|
|
}
|
|
|
|
dst := g.images[dstID]
|
|
var resourceBarriers []_D3D12_RESOURCE_BARRIER_Transition
|
|
if rb, ok := dst.transiteState(_D3D12_RESOURCE_STATE_RENDER_TARGET); ok {
|
|
resourceBarriers = append(resourceBarriers, rb)
|
|
}
|
|
|
|
var srcImages [graphics.ShaderImageCount]*image12
|
|
for i, srcID := range srcs {
|
|
src := g.images[srcID]
|
|
if src == nil {
|
|
continue
|
|
}
|
|
srcImages[i] = src
|
|
if rb, ok := src.transiteState(_D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE); ok {
|
|
resourceBarriers = append(resourceBarriers, rb)
|
|
}
|
|
}
|
|
|
|
if len(resourceBarriers) > 0 {
|
|
g.drawCommandList.ResourceBarrier(resourceBarriers)
|
|
}
|
|
|
|
if err := dst.setAsRenderTarget(g.drawCommandList, g.device, fillRule != graphicsdriver.FillAll); err != nil {
|
|
return err
|
|
}
|
|
|
|
shader := g.shaders[shaderID]
|
|
adjustedUniforms := adjustUniforms(shader.uniformTypes, shader.uniformOffsets, uniforms)
|
|
|
|
w, h := dst.internalSize()
|
|
g.needFlushDrawCommandList = true
|
|
g.drawCommandList.RSSetViewports([]_D3D12_VIEWPORT{
|
|
{
|
|
TopLeftX: 0,
|
|
TopLeftY: 0,
|
|
Width: float32(w),
|
|
Height: float32(h),
|
|
MinDepth: _D3D12_MIN_DEPTH,
|
|
MaxDepth: _D3D12_MAX_DEPTH,
|
|
},
|
|
})
|
|
g.drawCommandList.IASetPrimitiveTopology(_D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST)
|
|
g.drawCommandList.IASetVertexBuffers(0, []_D3D12_VERTEX_BUFFER_VIEW{
|
|
{
|
|
BufferLocation: g.vertices[g.frameIndex][len(g.vertices[g.frameIndex])-1].value.GetGPUVirtualAddress(),
|
|
SizeInBytes: g.vertices[g.frameIndex][len(g.vertices[g.frameIndex])-1].sizeInBytes,
|
|
StrideInBytes: graphics.VertexFloatCount * uint32(unsafe.Sizeof(float32(0))),
|
|
},
|
|
})
|
|
g.drawCommandList.IASetIndexBuffer(&_D3D12_INDEX_BUFFER_VIEW{
|
|
BufferLocation: g.indices[g.frameIndex][len(g.indices[g.frameIndex])-1].value.GetGPUVirtualAddress(),
|
|
SizeInBytes: g.indices[g.frameIndex][len(g.indices[g.frameIndex])-1].sizeInBytes,
|
|
Format: _DXGI_FORMAT_R32_UINT,
|
|
})
|
|
|
|
if err := g.pipelineStates.drawTriangles(g.device, g.drawCommandList, g.frameIndex, dst.screen, srcImages, shader, dstRegions, adjustedUniforms, blend, indexOffset, fillRule); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (g *graphics12) genNextImageID() graphicsdriver.ImageID {
|
|
g.nextImageID++
|
|
return g.nextImageID
|
|
}
|
|
|
|
func (g *graphics12) genNextShaderID() graphicsdriver.ShaderID {
|
|
g.nextShaderID++
|
|
return g.nextShaderID
|
|
}
|