internal/graphicsdriver: introduce the DirectX driver

Closes #1007
This commit is contained in:
Hajime Hoshi 2022-02-11 20:38:45 +09:00
parent a936ffc032
commit 79e93d3b12
14 changed files with 4625 additions and 31 deletions

View File

@ -92,15 +92,10 @@ jobs:
- name: go test - name: go test
# TODO: Add more test environments (#1305) # TODO: Add more test environments (#1305)
if: ${{ startsWith(matrix.os, 'ubuntu-') }} if: ${{ startsWith(matrix.os, 'ubuntu-') || startsWith(matrix.os, 'windows-') }}
run: | run: |
go test -tags=example ${{ !startsWith(matrix.go, '1.15.') && !startsWith(matrix.go, '1.16.') && '-shuffle=on' || '' }} -v ./... go test -tags=example ${{ !startsWith(matrix.go, '1.15.') && !startsWith(matrix.go, '1.16.') && '-shuffle=on' || '' }} -v ./...
- name: go test (Windows)
if: ${{ startsWith(matrix.os, 'windows-') }}
run: |
go test -tags=example ${{ !startsWith(matrix.go, '1.15.') && !startsWith(matrix.go, '1.16.') && '-shuffle=on' || '' }} -v ./internal/shader
- name: go test (Wasm) - name: go test (Wasm)
# TODO: Investigate times out on Windows. (#1313) # TODO: Investigate times out on Windows. (#1313)
if: ${{ !startsWith(matrix.os, 'windows-') && !startsWith(matrix.go, '1.15.') && !startsWith(matrix.go, '1.16.') }} if: ${{ !startsWith(matrix.os, 'windows-') && !startsWith(matrix.go, '1.15.') && !startsWith(matrix.go, '1.16.') }}

7
doc.go
View File

@ -71,8 +71,15 @@
// //
// "auto": Ebiten chooses the graphics library automatically. This is the default value. // "auto": Ebiten chooses the graphics library automatically. This is the default value.
// "opengl": OpenGL, OpenGL ES, or WebGL. // "opengl": OpenGL, OpenGL ES, or WebGL.
// "directx": DirectX. This works only on Windows.
// "metal": Metal. This works only on macOS or iOS. // "metal": Metal. This works only on macOS or iOS.
// //
// `EBITEN_DIRECTX` environment variable specifies various parameters for DirectX.
// You can specify multiple values separated by a comma. The default value is empty (i.e. no parameters).
//
// "warp": Use WARP (i.e. software rendering).
// "debug": Use a debug layer.
//
// Build tags // Build tags
// //
// `ebitendebug` outputs a log of graphics commands. This is useful to know what happens in Ebiten. In general, the // `ebitendebug` outputs a log of graphics commands. This is useful to know what happens in Ebiten. In general, the

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,686 @@
// Copyright 2022 The Ebiten 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 (
"fmt"
"math"
"unsafe"
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
)
const numDescriptorsPerFrame = 256
func operationToBlend(c graphicsdriver.Operation, alpha bool) _D3D12_BLEND {
switch c {
case graphicsdriver.Zero:
return _D3D12_BLEND_ZERO
case graphicsdriver.One:
return _D3D12_BLEND_ONE
case graphicsdriver.SrcAlpha:
return _D3D12_BLEND_SRC_ALPHA
case graphicsdriver.DstAlpha:
return _D3D12_BLEND_DEST_ALPHA
case graphicsdriver.OneMinusSrcAlpha:
return _D3D12_BLEND_INV_SRC_ALPHA
case graphicsdriver.OneMinusDstAlpha:
return _D3D12_BLEND_INV_DEST_ALPHA
case graphicsdriver.DstColor:
if alpha {
return _D3D12_BLEND_DEST_ALPHA
}
return _D3D12_BLEND_DEST_COLOR
default:
panic(fmt.Sprintf("directx: invalid operation: %d", c))
}
}
type builtinPipelineStatesKey struct {
useColorM bool
compositeMode graphicsdriver.CompositeMode
filter graphicsdriver.Filter
address graphicsdriver.Address
stencilMode stencilMode
screen bool
}
func (k *builtinPipelineStatesKey) defs() ([]_D3D_SHADER_MACRO, error) {
var defs []_D3D_SHADER_MACRO
defval := []byte("1\x00")
if k.useColorM {
name := []byte("USE_COLOR_MATRIX\x00")
defs = append(defs, _D3D_SHADER_MACRO{&name[0], &defval[0]})
}
switch k.filter {
case graphicsdriver.FilterNearest:
name := []byte("FILTER_NEAREST\x00")
defs = append(defs, _D3D_SHADER_MACRO{&name[0], &defval[0]})
case graphicsdriver.FilterLinear:
name := []byte("FILTER_LINEAR\x00")
defs = append(defs, _D3D_SHADER_MACRO{&name[0], &defval[0]})
case graphicsdriver.FilterScreen:
name := []byte("FILTER_SCREEN\x00")
defs = append(defs, _D3D_SHADER_MACRO{&name[0], &defval[0]})
default:
return nil, fmt.Errorf("directx: invalid filter: %d", k.filter)
}
switch k.address {
case graphicsdriver.AddressUnsafe:
name := []byte("ADDRESS_UNSAFE\x00")
defs = append(defs, _D3D_SHADER_MACRO{&name[0], &defval[0]})
case graphicsdriver.AddressClampToZero:
name := []byte("ADDRESS_CLAMP_TO_ZERO\x00")
defs = append(defs, _D3D_SHADER_MACRO{&name[0], &defval[0]})
case graphicsdriver.AddressRepeat:
name := []byte("ADDRESS_REPEAT\x00")
defs = append(defs, _D3D_SHADER_MACRO{&name[0], &defval[0]})
default:
return nil, fmt.Errorf("directx: invalid address: %d", k.address)
}
// Termination
defs = append(defs, _D3D_SHADER_MACRO{})
return defs, nil
}
func (k *builtinPipelineStatesKey) source() []byte {
return []byte(`struct PSInput {
float4 position : SV_POSITION;
float2 texcoord : TEXCOORD0;
float4 color : COLOR;
};
cbuffer ShaderParameter : register(b0) {
float2 viewport_size;
float2 source_size;
float4x4 color_matrix_body;
float4 color_matrix_translation;
float4 source_region;
// This member should be the last not to create a new sector.
// https://docs.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-packing-rules
float scale;
}
PSInput VSMain(float2 position : POSITION, float2 tex : TEXCOORD, float4 color : COLOR) {
// In DirectX, the NDC's Y direction (upward) and the framebuffer's Y direction (downward) don't
// match. Then, the Y direction must be inverted.
float4x4 projectionMatrix = {
2.0 / viewport_size.x, 0, 0, -1,
0, -2.0 / viewport_size.y, 0, 1,
0, 0, 1, 0,
0, 0, 0, 1,
};
PSInput result;
result.position = mul(projectionMatrix, float4(position, 0, 1));
result.texcoord = tex;
result.color = float4(color.rgb, 1) * color.a;
return result;
}
Texture2D tex : register(t0);
SamplerState samp : register(s0);
float euclideanMod(float x, float y) {
// Assume that y is always positive.
return x - y * floor(x/y);
}
float2 adjustTexelByAddress(float2 p, float4 source_region) {
#if defined(ADDRESS_CLAMP_TO_ZERO)
return p;
#endif
#if defined(ADDRESS_REPEAT)
float2 o = float2(source_region[0], source_region[1]);
float2 size = float2(source_region[2] - source_region[0], source_region[3] - source_region[1]);
return float2(euclideanMod((p.x - o.x), size.x) + o.x, euclideanMod((p.y - o.y), size.y) + o.y);
#endif
#if defined(ADDRESS_UNSAFE)
return p;
#endif
}
float4 PSMain(PSInput input) : SV_TARGET {
#if defined(FILTER_NEAREST)
# if defined(ADDRESS_UNSAFE)
float4 color = tex.Sample(samp, input.texcoord);
# else
float4 color;
float2 pos = adjustTexelByAddress(input.texcoord, source_region);
if (source_region[0] <= pos.x &&
source_region[1] <= pos.y &&
pos.x < source_region[2] &&
pos.y < source_region[3]) {
color = tex.Sample(samp, pos);
} else {
color = float4(0, 0, 0, 0);
}
# endif // defined(ADDRESS_UNSAFE)
#endif // defined(FILTER_NEAREST)
#if defined(FILTER_LINEAR)
float2 pos = input.texcoord;
float2 texel_size = 1.0 / source_size;
// Shift 1/512 [texel] to avoid the tie-breaking issue.
// As all the vertex positions are aligned to 1/16 [pixel], this shiting should work in most cases.
float2 p0 = pos - (texel_size) / 2.0 + (texel_size / 512.0);
float2 p1 = pos + (texel_size) / 2.0 + (texel_size / 512.0);
# if !defined(ADDRESS_UNSAFE)
p0 = adjustTexelByAddress(p0, source_region);
p1 = adjustTexelByAddress(p1, source_region);
# endif // !defined(ADDRESS_UNSAFE)
float4 c0 = tex.Sample(samp, p0);
float4 c1 = tex.Sample(samp, float2(p1.x, p0.y));
float4 c2 = tex.Sample(samp, float2(p0.x, p1.y));
float4 c3 = tex.Sample(samp, p1);
# if !defined(ADDRESS_UNSAFE)
if (p0.x < source_region[0]) {
c0 = float4(0, 0, 0, 0);
c2 = float4(0, 0, 0, 0);
}
if (p0.y < source_region[1]) {
c0 = float4(0, 0, 0, 0);
c1 = float4(0, 0, 0, 0);
}
if (source_region[2] <= p1.x) {
c1 = float4(0, 0, 0, 0);
c3 = float4(0, 0, 0, 0);
}
if (source_region[3] <= p1.y) {
c2 = float4(0, 0, 0, 0);
c3 = float4(0, 0, 0, 0);
}
# endif // !defined(ADDRESS_UNSAFE)
float2 rate = frac(p0 * source_size);
float4 color = lerp(lerp(c0, c1, rate.x), lerp(c2, c3, rate.x), rate.y);
#endif // defined(FILTER_LINEAR)
#if defined(FILTER_SCREEN)
float2 pos = input.texcoord;
float2 texel_size = 1.0 / source_size;
float2 half_scaled_texel_size = texel_size / 2.0 / scale;
float2 p0 = pos - half_scaled_texel_size + (texel_size / 512.0);
float2 p1 = pos + half_scaled_texel_size + (texel_size / 512.0);
float4 c0 = tex.Sample(samp, p0);
float4 c1 = tex.Sample(samp, float2(p1.x, p0.y));
float4 c2 = tex.Sample(samp, float2(p0.x, p1.y));
float4 c3 = tex.Sample(samp, p1);
// Texels must be in the source rect, so it is not necessary to check that like linear filter.
float2 rate_center = float2(1.0, 1.0) - half_scaled_texel_size;
float2 rate = clamp(((frac(p0 * source_size) - rate_center) * scale) + rate_center, 0.0, 1.0);
float4 color = lerp(lerp(c0, c1, rate.x), lerp(c2, c3, rate.x), rate.y);
#endif // defined(FILTER_SCREEN)
#if defined(USE_COLOR_MATRIX)
// Un-premultiply alpha.
// When the alpha is 0, 1.0 - sign(alpha) is 1.0, which means division does nothing.
color.rgb /= color.a + (1.0 - sign(color.a));
// Apply the color matrix or scale.
color = mul(color_matrix_body, color) + color_matrix_translation;
// Premultiply alpha
color.rgb *= color.a;
// Apply color scale.
color *= input.color;
// Clamp the output.
color.rgb = min(color.rgb, color.a);
return color;
#elif defined(FILTER_SCREEN)
return color;
#else
return input.color * color;
#endif // defined(USE_COLOR_MATRIX)
}`)
}
type pipelineStates struct {
rootSignature *iD3D12RootSignature
cache map[builtinPipelineStatesKey]*iD3D12PipelineState
// builtinShaders is a set of the built-in vertex/pixel shaders that are never released.
builtinShaders []*iD3DBlob
shaderDescriptorHeap *iD3D12DescriptorHeap
shaderDescriptorSize uint32
samplerDescriptorHeap *iD3D12DescriptorHeap
constantBuffers [frameCount][]*iD3D12Resource1
}
const numConstantBufferAndSourceTextures = 1 + graphics.ShaderImageNum
func (p *pipelineStates) initialize(device *iD3D12Device) (ferr error) {
// Create a CBV/SRV/UAV descriptor heap.
// 5n+0: constants
// 5n+m (1<=4): textures
shaderH, err := device.CreateDescriptorHeap(&_D3D12_DESCRIPTOR_HEAP_DESC{
Type: _D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV,
NumDescriptors: frameCount * numDescriptorsPerFrame * numConstantBufferAndSourceTextures,
Flags: _D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE,
NodeMask: 0,
})
if err != nil {
return err
}
p.shaderDescriptorHeap = shaderH
defer func() {
if ferr != nil {
p.shaderDescriptorHeap.Release()
p.shaderDescriptorHeap = nil
}
}()
p.shaderDescriptorSize = device.GetDescriptorHandleIncrementSize(_D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV)
samplerH, err := device.CreateDescriptorHeap(&_D3D12_DESCRIPTOR_HEAP_DESC{
Type: _D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER,
NumDescriptors: 1,
Flags: _D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE,
NodeMask: 0,
})
if err != nil {
return err
}
p.samplerDescriptorHeap = samplerH
device.CreateSampler(&_D3D12_SAMPLER_DESC{
Filter: _D3D12_FILTER_MIN_MAG_MIP_POINT,
AddressU: _D3D12_TEXTURE_ADDRESS_MODE_WRAP,
AddressV: _D3D12_TEXTURE_ADDRESS_MODE_WRAP,
AddressW: _D3D12_TEXTURE_ADDRESS_MODE_WRAP,
ComparisonFunc: _D3D12_COMPARISON_FUNC_NEVER,
MinLOD: -math.MaxFloat32,
MaxLOD: math.MaxFloat32,
}, p.samplerDescriptorHeap.GetCPUDescriptorHandleForHeapStart())
return nil
}
func (p *pipelineStates) builtinGraphicsPipelineState(device *iD3D12Device, key builtinPipelineStatesKey) (*iD3D12PipelineState, error) {
state, ok := p.cache[key]
if ok {
return state, nil
}
defs, err := key.defs()
if err != nil {
return nil, err
}
vsh, psh, err := newShader(key.source(), defs)
if err != nil {
return nil, err
}
// Keep the shaders. These are never released.
p.builtinShaders = append(p.builtinShaders, vsh, psh)
s, err := p.newPipelineState(device, vsh, psh, key.compositeMode, key.stencilMode, key.screen)
if err != nil {
return nil, err
}
if p.cache == nil {
p.cache = map[builtinPipelineStatesKey]*iD3D12PipelineState{}
}
p.cache[key] = s
return s, nil
}
func (p *pipelineStates) useGraphicsPipelineState(device *iD3D12Device, commandList *iD3D12GraphicsCommandList, frameIndex int, pipelineState *iD3D12PipelineState, srcs [graphics.ShaderImageNum]*Image, uniforms []float32) error {
idx := len(p.constantBuffers[frameIndex])
if idx >= numDescriptorsPerFrame*2 {
return fmt.Errorf("directx: too many constant buffers")
}
if cap(p.constantBuffers[frameIndex]) > idx {
p.constantBuffers[frameIndex] = p.constantBuffers[frameIndex][:idx+1]
} else {
p.constantBuffers[frameIndex] = append(p.constantBuffers[frameIndex], nil)
}
const bufferSizeAlignement = 256
bufferSize := uint32(unsafe.Sizeof(float32(0))) * uint32(len(uniforms))
if bufferSize > 0 {
bufferSize = ((bufferSize-1)/bufferSizeAlignement + 1) * bufferSizeAlignement
}
cb := p.constantBuffers[frameIndex][idx]
if cb != nil {
if uint32(cb.GetDesc().Width) < bufferSize {
p.constantBuffers[frameIndex][idx].Release()
p.constantBuffers[frameIndex][idx] = nil
cb = nil
}
}
if cb == nil {
var err error
cb, err = createBuffer(device, uint64(bufferSize), _D3D12_HEAP_TYPE_UPLOAD)
if err != nil {
return err
}
p.constantBuffers[frameIndex][idx] = cb
h := p.shaderDescriptorHeap.GetCPUDescriptorHandleForHeapStart()
h.Offset(int32(frameIndex*numDescriptorsPerFrame+numConstantBufferAndSourceTextures*idx), p.shaderDescriptorSize)
device.CreateConstantBufferView(&_D3D12_CONSTANT_BUFFER_VIEW_DESC{
BufferLocation: cb.GetGPUVirtualAddress(),
SizeInBytes: bufferSize,
}, h)
}
h := p.shaderDescriptorHeap.GetCPUDescriptorHandleForHeapStart()
h.Offset(int32(frameIndex*numDescriptorsPerFrame+numConstantBufferAndSourceTextures*idx), p.shaderDescriptorSize)
for _, src := range srcs {
h.Offset(1, p.shaderDescriptorSize)
if src == nil {
continue
}
device.CreateShaderResourceView(src.resource(), &_D3D12_SHADER_RESOURCE_VIEW_DESC{
Format: _DXGI_FORMAT_R8G8B8A8_UNORM,
ViewDimension: _D3D12_SRV_DIMENSION_TEXTURE2D,
Shader4ComponentMapping: _D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
Texture2D: _D3D12_TEX2D_SRV{
MipLevels: 1,
},
}, h)
}
// Update the constant buffer.
m, err := cb.Map(0, &_D3D12_RANGE{0, 0})
if err != nil {
return err
}
copyFloat32s(m, uniforms)
if err := cb.Unmap(0, nil); err != nil {
return err
}
commandList.SetPipelineState(pipelineState)
rs, err := p.ensureRootSignature(device)
if err != nil {
return err
}
commandList.SetGraphicsRootSignature(rs)
commandList.SetDescriptorHeaps([]*iD3D12DescriptorHeap{
p.shaderDescriptorHeap,
p.samplerDescriptorHeap,
})
// Match the indices with rootParams in graphicsPipelineState.
gh := p.shaderDescriptorHeap.GetGPUDescriptorHandleForHeapStart()
gh.Offset(int32(frameIndex*numDescriptorsPerFrame+numConstantBufferAndSourceTextures*idx), p.shaderDescriptorSize)
commandList.SetGraphicsRootDescriptorTable(0, gh)
gh.Offset(1, p.shaderDescriptorSize)
commandList.SetGraphicsRootDescriptorTable(1, gh)
commandList.SetGraphicsRootDescriptorTable(2, p.samplerDescriptorHeap.GetGPUDescriptorHandleForHeapStart())
return nil
}
func (p *pipelineStates) ensureRootSignature(device *iD3D12Device) (rootSignature *iD3D12RootSignature, ferr error) {
if p.rootSignature != nil {
return p.rootSignature, nil
}
cbv := _D3D12_DESCRIPTOR_RANGE{
RangeType: _D3D12_DESCRIPTOR_RANGE_TYPE_CBV, // b0
NumDescriptors: 1,
BaseShaderRegister: 0,
RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: _D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
}
srv := _D3D12_DESCRIPTOR_RANGE{
RangeType: _D3D12_DESCRIPTOR_RANGE_TYPE_SRV, // t0
NumDescriptors: graphics.ShaderImageNum,
BaseShaderRegister: 0,
RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: _D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
}
sampler := _D3D12_DESCRIPTOR_RANGE{
RangeType: _D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, // s0
NumDescriptors: 1,
BaseShaderRegister: 0,
RegisterSpace: 0,
OffsetInDescriptorsFromTableStart: _D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,
}
rootParams := [...]_D3D12_ROOT_PARAMETER{
{
ParameterType: _D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
DescriptorTable: _D3D12_ROOT_DESCRIPTOR_TABLE{
NumDescriptorRanges: 1,
pDescriptorRanges: &cbv,
},
ShaderVisibility: _D3D12_SHADER_VISIBILITY_ALL,
},
{
ParameterType: _D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
DescriptorTable: _D3D12_ROOT_DESCRIPTOR_TABLE{
NumDescriptorRanges: 1,
pDescriptorRanges: &srv,
},
ShaderVisibility: _D3D12_SHADER_VISIBILITY_PIXEL,
},
{
ParameterType: _D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE,
DescriptorTable: _D3D12_ROOT_DESCRIPTOR_TABLE{
NumDescriptorRanges: 1,
pDescriptorRanges: &sampler,
},
ShaderVisibility: _D3D12_SHADER_VISIBILITY_PIXEL,
},
}
// Create a root signature.
sig, err := d3D12SerializeRootSignature(&_D3D12_ROOT_SIGNATURE_DESC{
NumParameters: uint32(len(rootParams)),
pParameters: &rootParams[0],
NumStaticSamplers: 0,
pStaticSamplers: nil,
Flags: _D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT,
}, _D3D_ROOT_SIGNATURE_VERSION_1_0)
if err != nil {
return nil, err
}
defer sig.Release()
rs, err := device.CreateRootSignature(0, sig.GetBufferPointer(), sig.GetBufferSize())
if err != nil {
return nil, err
}
defer func() {
if ferr != nil {
rootSignature.Release()
}
}()
p.rootSignature = rs
return p.rootSignature, nil
}
func newShader(source []byte, defs []_D3D_SHADER_MACRO) (vsh, psh *iD3DBlob, ferr error) {
// Create a shader
v, err := d3DCompile(source, "shader", defs, nil, "VSMain", "vs_5_0", 0, 0)
if err != nil {
return nil, nil, err
}
defer func() {
if ferr != nil {
v.Release()
}
}()
p, err := d3DCompile(source, "shader", defs, nil, "PSMain", "ps_5_0", 0, 0)
if err != nil {
return nil, nil, err
}
defer func() {
if ferr != nil {
p.Release()
}
}()
return v, p, nil
}
func (p *pipelineStates) newPipelineState(device *iD3D12Device, vsh, psh *iD3DBlob, compositeMode graphicsdriver.CompositeMode, stencilMode stencilMode, screen bool) (state *iD3D12PipelineState, ferr error) {
rootSignature, err := p.ensureRootSignature(device)
if err != nil {
return nil, err
}
defer func() {
if ferr != nil {
rootSignature.Release()
}
}()
depthStencilDesc := _D3D12_DEPTH_STENCIL_DESC{
DepthEnable: 0,
DepthWriteMask: _D3D12_DEPTH_WRITE_MASK_ALL,
DepthFunc: _D3D12_COMPARISON_FUNC_LESS,
StencilEnable: 0,
StencilReadMask: _D3D12_DEFAULT_STENCIL_READ_MASK,
StencilWriteMask: _D3D12_DEFAULT_STENCIL_WRITE_MASK,
FrontFace: _D3D12_DEPTH_STENCILOP_DESC{
StencilFailOp: _D3D12_STENCIL_OP_KEEP,
StencilDepthFailOp: _D3D12_STENCIL_OP_KEEP,
StencilPassOp: _D3D12_STENCIL_OP_KEEP,
StencilFunc: _D3D12_COMPARISON_FUNC_ALWAYS,
},
BackFace: _D3D12_DEPTH_STENCILOP_DESC{
StencilFailOp: _D3D12_STENCIL_OP_KEEP,
StencilDepthFailOp: _D3D12_STENCIL_OP_KEEP,
StencilPassOp: _D3D12_STENCIL_OP_KEEP,
StencilFunc: _D3D12_COMPARISON_FUNC_ALWAYS,
},
}
writeMask := uint8(_D3D12_COLOR_WRITE_ENABLE_ALL)
switch stencilMode {
case prepareStencil:
depthStencilDesc.StencilEnable = 1
depthStencilDesc.FrontFace.StencilPassOp = _D3D12_STENCIL_OP_INVERT
depthStencilDesc.BackFace.StencilPassOp = _D3D12_STENCIL_OP_INVERT
writeMask = 0
case drawWithStencil:
depthStencilDesc.StencilEnable = 1
depthStencilDesc.FrontFace.StencilFunc = _D3D12_COMPARISON_FUNC_NOT_EQUAL
depthStencilDesc.BackFace.StencilFunc = _D3D12_COMPARISON_FUNC_NOT_EQUAL
}
rtvFormat := _DXGI_FORMAT_R8G8B8A8_UNORM
if screen {
rtvFormat = _DXGI_FORMAT_B8G8R8A8_UNORM
}
// Create a pipeline state.
srcOp, dstOp := compositeMode.Operations()
psoDesc := _D3D12_GRAPHICS_PIPELINE_STATE_DESC{
pRootSignature: rootSignature,
VS: _D3D12_SHADER_BYTECODE{
pShaderBytecode: vsh.GetBufferPointer(),
BytecodeLength: vsh.GetBufferSize(),
},
PS: _D3D12_SHADER_BYTECODE{
pShaderBytecode: psh.GetBufferPointer(),
BytecodeLength: psh.GetBufferSize(),
},
BlendState: _D3D12_BLEND_DESC{
AlphaToCoverageEnable: 0,
IndependentBlendEnable: 0,
RenderTarget: [8]_D3D12_RENDER_TARGET_BLEND_DESC{
{
BlendEnable: 1,
LogicOpEnable: 0,
SrcBlend: operationToBlend(srcOp, false),
DestBlend: operationToBlend(dstOp, false),
BlendOp: _D3D12_BLEND_OP_ADD,
SrcBlendAlpha: operationToBlend(srcOp, true),
DestBlendAlpha: operationToBlend(dstOp, true),
BlendOpAlpha: _D3D12_BLEND_OP_ADD,
LogicOp: _D3D12_LOGIC_OP_NOOP,
RenderTargetWriteMask: writeMask,
},
},
},
SampleMask: math.MaxUint32,
RasterizerState: _D3D12_RASTERIZER_DESC{
FillMode: _D3D12_FILL_MODE_SOLID,
CullMode: _D3D12_CULL_MODE_NONE,
FrontCounterClockwise: 0,
DepthBias: _D3D12_DEFAULT_DEPTH_BIAS,
DepthBiasClamp: _D3D12_DEFAULT_DEPTH_BIAS_CLAMP,
SlopeScaledDepthBias: _D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS,
DepthClipEnable: 0,
MultisampleEnable: 0,
AntialiasedLineEnable: 0,
ForcedSampleCount: 0,
ConservativeRaster: _D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF,
},
DepthStencilState: depthStencilDesc,
InputLayout: _D3D12_INPUT_LAYOUT_DESC{
pInputElementDescs: &inputElementDescs[0],
NumElements: uint32(len(inputElementDescs)),
},
PrimitiveTopologyType: _D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE,
NumRenderTargets: 1,
RTVFormats: [8]_DXGI_FORMAT{
rtvFormat,
},
DSVFormat: _DXGI_FORMAT_D24_UNORM_S8_UINT,
SampleDesc: _DXGI_SAMPLE_DESC{
Count: 1,
Quality: 0,
},
}
s, err := device.CreateGraphicsPipelineState(&psoDesc)
if err != nil {
return nil, err
}
return s, nil
}
func (p *pipelineStates) releaseConstantBuffers(frameIndex int) {
for i := range p.constantBuffers[frameIndex] {
p.constantBuffers[frameIndex][i].Release()
p.constantBuffers[frameIndex][i] = nil
}
p.constantBuffers[frameIndex] = p.constantBuffers[frameIndex][:0]
}
func (p *pipelineStates) resetConstantBuffers(frameIndex int) {
p.constantBuffers[frameIndex] = p.constantBuffers[frameIndex][:0]
}

View File

@ -54,6 +54,7 @@ type Graphics interface {
NeedsRestoring() bool NeedsRestoring() bool
NeedsClearingScreen() bool NeedsClearingScreen() bool
IsGL() bool IsGL() bool
IsDirectX() bool
HasHighPrecisionFloat() bool HasHighPrecisionFloat() bool
MaxImageSize() int MaxImageSize() int

View File

@ -1046,6 +1046,10 @@ func (g *Graphics) IsGL() bool {
return false return false
} }
func (g *Graphics) IsDirectX() bool {
return false
}
func (g *Graphics) HasHighPrecisionFloat() bool { func (g *Graphics) HasHighPrecisionFloat() bool {
return true return true
} }

View File

@ -392,6 +392,10 @@ func (g *Graphics) IsGL() bool {
return true return true
} }
func (g *Graphics) IsDirectX() bool {
return false
}
func (g *Graphics) HasHighPrecisionFloat() bool { func (g *Graphics) HasHighPrecisionFloat() bool {
return g.context.hasHighPrecisionFloat() return g.context.hasHighPrecisionFloat()
} }

View File

@ -61,7 +61,18 @@ func (c *contextImpl) updateFrame(graphicsDriver graphicsdriver.Graphics, outsid
} }
func (c *contextImpl) forceUpdateFrame(graphicsDriver graphicsdriver.Graphics, outsideWidth, outsideHeight float64, deviceScaleFactor float64) error { func (c *contextImpl) forceUpdateFrame(graphicsDriver graphicsdriver.Graphics, outsideWidth, outsideHeight float64, deviceScaleFactor float64) error {
return c.updateFrameImpl(graphicsDriver, 1, outsideWidth, outsideHeight, deviceScaleFactor) n := 1
if graphicsDriver.IsDirectX() {
// On DirectX, both framebuffers in the swap chain should be updated.
// Or, the rendering result becomes unexpected when the window is resized.
n = 2
}
for i := 0; i < n; i++ {
if err := c.updateFrameImpl(graphicsDriver, 1, outsideWidth, outsideHeight, deviceScaleFactor); err != nil {
return err
}
}
return nil
} }
func (c *contextImpl) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, updateCount int, outsideWidth, outsideHeight float64, deviceScaleFactor float64) error { func (c *contextImpl) updateFrameImpl(graphicsDriver graphicsdriver.Graphics, updateCount int, outsideWidth, outsideHeight float64, deviceScaleFactor float64) error {

View File

@ -811,19 +811,6 @@ event:
} }
func (u *userInterfaceImpl) init() error { func (u *userInterfaceImpl) init() error {
g, err := chooseGraphicsDriver(&graphicsDriverGetterImpl{})
if err != nil {
return err
}
u.graphicsDriver = g
if u.graphicsDriver.IsGL() {
glfw.WindowHint(glfw.ClientAPI, glfw.OpenGLAPI)
glfw.WindowHint(glfw.ContextVersionMajor, 2)
glfw.WindowHint(glfw.ContextVersionMinor, 1)
} else {
glfw.WindowHint(glfw.ClientAPI, glfw.NoAPI)
}
glfw.WindowHint(glfw.AutoIconify, glfw.False) glfw.WindowHint(glfw.AutoIconify, glfw.False)
decorated := glfw.False decorated := glfw.False
@ -832,13 +819,30 @@ func (u *userInterfaceImpl) init() error {
} }
glfw.WindowHint(glfw.Decorated, decorated) glfw.WindowHint(glfw.Decorated, decorated)
transparent := glfw.False transparent := u.isInitScreenTransparent()
if u.isInitScreenTransparent() { glfwTransparent := glfw.False
transparent = glfw.True if transparent {
glfwTransparent = glfw.True
} }
glfw.WindowHint(glfw.TransparentFramebuffer, transparent) glfw.WindowHint(glfw.TransparentFramebuffer, glfwTransparent)
g, err := chooseGraphicsDriver(&graphicsDriverGetterImpl{
transparent: transparent,
})
if err != nil {
return err
}
u.graphicsDriver = g
u.graphicsDriver.SetTransparent(u.isInitScreenTransparent()) u.graphicsDriver.SetTransparent(u.isInitScreenTransparent())
if u.graphicsDriver.IsGL() {
glfw.WindowHint(glfw.ClientAPI, glfw.OpenGLAPI)
glfw.WindowHint(glfw.ContextVersionMajor, 2)
glfw.WindowHint(glfw.ContextVersionMinor, 1)
} else {
glfw.WindowHint(glfw.ClientAPI, glfw.NoAPI)
}
// Before creating a window, set it unresizable no matter what u.isInitWindowResizable() is (#1987). // Before creating a window, set it unresizable no matter what u.isInitWindowResizable() is (#1987).
// Making the window resizable here doesn't work correctly when switching to enable resizing. // Making the window resizable here doesn't work correctly when switching to enable resizing.
resizable := glfw.False resizable := glfw.False

View File

@ -232,7 +232,9 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
) )
type graphicsDriverGetterImpl struct{} type graphicsDriverGetterImpl struct {
transparent bool
}
func (g *graphicsDriverGetterImpl) getAuto() graphicsdriver.Graphics { func (g *graphicsDriverGetterImpl) getAuto() graphicsdriver.Graphics {
if m := g.getMetal(); m != nil { if m := g.getMetal(); m != nil {

View File

@ -31,7 +31,9 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
) )
type graphicsDriverGetterImpl struct{} type graphicsDriverGetterImpl struct {
transparent bool
}
func (g *graphicsDriverGetterImpl) getAuto() graphicsdriver.Graphics { func (g *graphicsDriverGetterImpl) getAuto() graphicsdriver.Graphics {
return g.getOpenGL() return g.getOpenGL()

View File

@ -26,12 +26,18 @@ import (
"github.com/hajimehoshi/ebiten/v2/internal/glfw" "github.com/hajimehoshi/ebiten/v2/internal/glfw"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/directx"
"github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl" "github.com/hajimehoshi/ebiten/v2/internal/graphicsdriver/opengl"
) )
type graphicsDriverGetterImpl struct{} type graphicsDriverGetterImpl struct {
transparent bool
}
func (g *graphicsDriverGetterImpl) getAuto() graphicsdriver.Graphics { func (g *graphicsDriverGetterImpl) getAuto() graphicsdriver.Graphics {
if d := g.getDirectX(); d != nil {
return d
}
return g.getOpenGL() return g.getOpenGL()
} }
@ -42,7 +48,13 @@ func (*graphicsDriverGetterImpl) getOpenGL() graphicsdriver.Graphics {
return nil return nil
} }
func (*graphicsDriverGetterImpl) getDirectX() graphicsdriver.Graphics { func (g *graphicsDriverGetterImpl) getDirectX() graphicsdriver.Graphics {
if g.transparent {
return nil
}
if d := directx.Get(); d != nil {
return d
}
return nil return nil
} }

View File

@ -17,6 +17,7 @@ package ebiten_test
import ( import (
"image" "image"
"image/color" "image/color"
"math"
"testing" "testing"
"github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2"
@ -654,3 +655,74 @@ func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
} }
} }
} }
func TestShaderTextureAt(t *testing.T) {
const w, h = 16, 16
src := ebiten.NewImage(w, h)
src.Fill(color.RGBA{0x10, 0x20, 0x30, 0xff})
dst := ebiten.NewImage(w, h)
s, err := ebiten.NewShader([]byte(`package main
func textureAt(uv vec2) vec4 {
return imageSrc0UnsafeAt(uv)
}
func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
return textureAt(texCoord)
}
`))
if err != nil {
t.Fatal(err)
}
op := &ebiten.DrawRectShaderOptions{}
op.Images[0] = src
dst.DrawRectShader(w, h, s, op)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
got := dst.At(i, j).(color.RGBA)
want := color.RGBA{0x10, 0x20, 0x30, 0xff}
if !sameColors(got, want, 2) {
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
}
func TestShaderAtan2(t *testing.T) {
const w, h = 16, 16
src := ebiten.NewImage(w, h)
src.Fill(color.RGBA{0x10, 0x20, 0x30, 0xff})
dst := ebiten.NewImage(w, h)
s, err := ebiten.NewShader([]byte(`package main
func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
y := vec4(1, 1, 1, 1)
x := vec4(1, 1, 1, 1)
return atan2(y, x)
}
`))
if err != nil {
t.Fatal(err)
}
op := &ebiten.DrawRectShaderOptions{}
op.Images[0] = src
dst.DrawRectShader(w, h, s, op)
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
got := dst.At(i, j).(color.RGBA)
v := byte(math.Floor(0xff * math.Pi / 4))
want := color.RGBA{v, v, v, v}
if !sameColors(got, want, 2) {
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
}