ebiten/internal/graphicsdriver/directx/graphics12_windows.go
Hajime Hoshi 81bb5044ea internal/shaderir: revert the refactoring to generalize the memory layout logic
Unfortunately, the memory layout was not so universal. For example,
the memory layout for mat2 is different between Metal and DirectX.
2024-11-24 23:57:30 +09:00

1176 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) 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) {
vsh, psh, err := compileShader(program)
if err != nil {
return nil, err
}
s := &shader12{
graphics: g,
id: g.genNextShaderID(),
uniformTypes: program.Uniforms,
uniformOffsets: hlsl.UniformVariableOffsetsInDWords(program),
vertexShader: vsh,
pixelShader: psh,
}
g.addShader(s)
return s, nil
}
func (g *graphics12) DrawTriangles(dstID graphicsdriver.ImageID, srcs [graphics.ShaderSrcImageCount]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.FillRuleFillAll {
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.ShaderSrcImageCount]*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.FillRuleFillAll); 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
}