diff --git a/blend.go b/blend.go index 214c4d1f2..99fecd67c 100644 --- a/blend.go +++ b/blend.go @@ -77,13 +77,58 @@ const ( // BlendFactorDefault is the default factor value. // The actual value depends on which source or destination this value is used. BlendFactorDefault BlendFactor = iota + + // BlendFactorZero is a factor: + // + // 0 BlendFactorZero + + // BlendFactorOne is a factor: + // + // 1 BlendFactorOne + + // BlendFactorSourceColor is a factor: + // + // (source RGBA) + BlendFactorSourceColor + + // BlendFactorOneMinusSourceColor is a factor: + // + // 1 - (source color) + BlendFactorOneMinusSourceColor + + // BlendFactorSourceAlpha is a factor: + // + // (source alpha) BlendFactorSourceAlpha - BlendFactorDestinationAlpha + + // BlendFactorOneMinusSourceAlpha is a factor: + // + // 1 - (source alpha) BlendFactorOneMinusSourceAlpha - BlendFactorOneMinusDestinationAlpha + + // BlendFactorDestinationColor is a factor: + // + // (destination RGBA) BlendFactorDestinationColor + + // BlendFactorOneMinusDestinationColor is a factor: + // + // 1 - (destination RGBA) + BlendFactorOneMinusDestinationColor + + // BlendFactorDestinationAlpha is a factor: + // + // (destination alpha) + BlendFactorDestinationAlpha + + // BlendFactorOneMinusDestinationAlpha is a factor: + // + // 1 - (destination alpha) + BlendFactorOneMinusDestinationAlpha + + // TODO: Add BlendFactorSourceAlphaSaturated. This might not work well on some platforms like Steam SDK (#2382). ) func (b BlendFactor) internalBlendFactor(source bool) graphicsdriver.BlendFactor { @@ -98,16 +143,22 @@ func (b BlendFactor) internalBlendFactor(source bool) graphicsdriver.BlendFactor return graphicsdriver.BlendFactorZero case BlendFactorOne: return graphicsdriver.BlendFactorOne + case BlendFactorSourceColor: + return graphicsdriver.BlendFactorSourceColor + case BlendFactorOneMinusSourceColor: + return graphicsdriver.BlendFactorOneMinusSourceColor case BlendFactorSourceAlpha: return graphicsdriver.BlendFactorSourceAlpha - case BlendFactorDestinationAlpha: - return graphicsdriver.BlendFactorDestinationAlpha case BlendFactorOneMinusSourceAlpha: return graphicsdriver.BlendFactorOneMinusSourceAlpha - case BlendFactorOneMinusDestinationAlpha: - return graphicsdriver.BlendFactorOneMinusDestinationAlpha case BlendFactorDestinationColor: return graphicsdriver.BlendFactorDestinationColor + case BlendFactorOneMinusDestinationColor: + return graphicsdriver.BlendFactorOneMinusDestinationColor + case BlendFactorDestinationAlpha: + return graphicsdriver.BlendFactorDestinationAlpha + case BlendFactorOneMinusDestinationAlpha: + return graphicsdriver.BlendFactorOneMinusDestinationAlpha default: panic(fmt.Sprintf("ebiten: invalid blend factor: %d", b)) } diff --git a/image_test.go b/image_test.go index 1d5d3e68e..c32e93fb8 100644 --- a/image_test.go +++ b/image_test.go @@ -3647,3 +3647,211 @@ func TestImageBlendOperation(t *testing.T) { } } } + +func TestImageBlendFactor(t *testing.T) { + const w, h = 16, 1 + dst := ebiten.NewImage(w, h) + src := ebiten.NewImage(w, h) + + dstColor := func(i int) (byte, byte, byte, byte) { + return byte(4 * i * 17), byte(4*i*17 + 1), byte(4*i*17 + 2), byte(4*i*17 + 3) + } + srcColor := func(i int) (byte, byte, byte, byte) { + return byte(4 * i * 13), byte(4*i*13 + 1), byte(4*i*13 + 2), byte(4*i*13 + 3) + } + colorToFloats := func(r, g, b, a byte) (float64, float64, float64, float64) { + return float64(r) / 0xff, float64(g) / 0xff, float64(b) / 0xff, float64(a) / 0xff + } + clamp := func(x int) byte { + if x > 255 { + return 255 + } + if x < 0 { + return 0 + } + return byte(x) + } + + dstPix := make([]byte, 4*w*h) + for i := 0; i < w; i++ { + r, g, b, a := dstColor(i) + dstPix[4*i] = r + dstPix[4*i+1] = g + dstPix[4*i+2] = b + dstPix[4*i+3] = a + } + srcPix := make([]byte, 4*w*h) + for i := 0; i < w; i++ { + r, g, b, a := srcColor(i) + srcPix[4*i] = r + srcPix[4*i+1] = g + srcPix[4*i+2] = b + srcPix[4*i+3] = a + } + src.WritePixels(srcPix) + + factors := []ebiten.BlendFactor{ + ebiten.BlendFactorZero, + ebiten.BlendFactorOne, + ebiten.BlendFactorSourceColor, + ebiten.BlendFactorOneMinusSourceColor, + ebiten.BlendFactorSourceAlpha, + ebiten.BlendFactorOneMinusSourceAlpha, + ebiten.BlendFactorDestinationColor, + ebiten.BlendFactorOneMinusDestinationColor, + ebiten.BlendFactorDestinationAlpha, + ebiten.BlendFactorOneMinusDestinationAlpha, + } + for _, srcRGBFactor := range factors { + for _, srcAlphaFactor := range factors { + for _, dstRGBFactor := range factors { + for _, dstAlphaFactor := range factors { + // Reset the destination state. + dst.WritePixels(dstPix) + op := &ebiten.DrawImageOptions{} + op.Blend = ebiten.Blend{ + BlendFactorSourceRGB: srcRGBFactor, + BlendFactorSourceAlpha: srcAlphaFactor, + BlendFactorDestinationRGB: dstRGBFactor, + BlendFactorDestinationAlpha: dstAlphaFactor, + BlendOperationRGB: ebiten.BlendOperationAdd, + BlendOperationAlpha: ebiten.BlendOperationAdd, + } + dst.DrawImage(src, op) + for i := 0; i < w; i++ { + got := dst.At(i, 0).(color.RGBA) + + sr, sg, sb, sa := colorToFloats(srcColor(i)) + dr, dg, db, da := colorToFloats(dstColor(i)) + + var r, g, b, a float64 + + switch srcRGBFactor { + case ebiten.BlendFactorZero: + r += 0 * sr + g += 0 * sg + b += 0 * sb + case ebiten.BlendFactorOne: + r += 1 * sr + g += 1 * sg + b += 1 * sb + case ebiten.BlendFactorSourceColor: + r += sr * sr + g += sg * sg + b += sb * sb + case ebiten.BlendFactorOneMinusSourceColor: + r += (1 - sr) * sr + g += (1 - sg) * sg + b += (1 - sb) * sb + case ebiten.BlendFactorSourceAlpha: + r += sa * sr + g += sa * sg + b += sa * sb + case ebiten.BlendFactorOneMinusSourceAlpha: + r += (1 - sa) * sr + g += (1 - sa) * sg + b += (1 - sa) * sb + case ebiten.BlendFactorDestinationColor: + r += dr * sr + g += dg * sg + b += db * sb + case ebiten.BlendFactorOneMinusDestinationColor: + r += (1 - dr) * sr + g += (1 - dg) * sg + b += (1 - db) * sb + case ebiten.BlendFactorDestinationAlpha: + r += da * sr + g += da * sg + b += da * sb + case ebiten.BlendFactorOneMinusDestinationAlpha: + r += (1 - da) * sr + g += (1 - da) * sg + b += (1 - da) * sb + } + switch srcAlphaFactor { + case ebiten.BlendFactorZero: + a += 0 * sa + case ebiten.BlendFactorOne: + a += 1 * sa + case ebiten.BlendFactorSourceColor, ebiten.BlendFactorSourceAlpha: + a += sa * sa + case ebiten.BlendFactorOneMinusSourceColor, ebiten.BlendFactorOneMinusSourceAlpha: + a += (1 - sa) * sa + case ebiten.BlendFactorDestinationColor, ebiten.BlendFactorDestinationAlpha: + a += da * sa + case ebiten.BlendFactorOneMinusDestinationColor, ebiten.BlendFactorOneMinusDestinationAlpha: + a += (1 - da) * sa + } + + switch dstRGBFactor { + case ebiten.BlendFactorZero: + r += 0 * dr + g += 0 * dg + b += 0 * db + case ebiten.BlendFactorOne: + r += 1 * dr + g += 1 * dg + b += 1 * db + case ebiten.BlendFactorSourceColor: + r += sr * dr + g += sg * dg + b += sb * db + case ebiten.BlendFactorOneMinusSourceColor: + r += (1 - sr) * dr + g += (1 - sg) * dg + b += (1 - sb) * db + case ebiten.BlendFactorSourceAlpha: + r += sa * dr + g += sa * dg + b += sa * db + case ebiten.BlendFactorOneMinusSourceAlpha: + r += (1 - sa) * dr + g += (1 - sa) * dg + b += (1 - sa) * db + case ebiten.BlendFactorDestinationColor: + r += dr * dr + g += dg * dg + b += db * db + case ebiten.BlendFactorOneMinusDestinationColor: + r += (1 - dr) * dr + g += (1 - dg) * dg + b += (1 - db) * db + case ebiten.BlendFactorDestinationAlpha: + r += da * dr + g += da * dg + b += da * db + case ebiten.BlendFactorOneMinusDestinationAlpha: + r += (1 - da) * dr + g += (1 - da) * dg + b += (1 - da) * db + } + switch dstAlphaFactor { + case ebiten.BlendFactorZero: + a += 0 * da + case ebiten.BlendFactorOne: + a += 1 * da + case ebiten.BlendFactorSourceColor, ebiten.BlendFactorSourceAlpha: + a += sa * da + case ebiten.BlendFactorOneMinusSourceColor, ebiten.BlendFactorOneMinusSourceAlpha: + a += (1 - sa) * da + case ebiten.BlendFactorDestinationColor, ebiten.BlendFactorDestinationAlpha: + a += da * da + case ebiten.BlendFactorOneMinusDestinationColor, ebiten.BlendFactorOneMinusDestinationAlpha: + a += (1 - da) * da + } + + want := color.RGBA{ + R: clamp(int(r * 0xff)), + G: clamp(int(g * 0xff)), + B: clamp(int(b * 0xff)), + A: clamp(int(a * 0xff)), + } + if !sameColors(got, want, 1) { + t.Errorf("dst.At(%d, 0): factors: %d, %d, %d, %d: got: %v, want: %v", i, srcRGBFactor, srcAlphaFactor, dstRGBFactor, dstAlphaFactor, got, want) + } + } + } + } + } + } +} diff --git a/internal/graphicsdriver/blend.go b/internal/graphicsdriver/blend.go index 023cdfb84..39516e11b 100644 --- a/internal/graphicsdriver/blend.go +++ b/internal/graphicsdriver/blend.go @@ -28,11 +28,15 @@ type BlendFactor byte const ( BlendFactorZero BlendFactor = iota BlendFactorOne + BlendFactorSourceColor + BlendFactorOneMinusSourceColor BlendFactorSourceAlpha - BlendFactorDestinationAlpha BlendFactorOneMinusSourceAlpha - BlendFactorOneMinusDestinationAlpha BlendFactorDestinationColor + BlendFactorOneMinusDestinationColor + BlendFactorDestinationAlpha + BlendFactorOneMinusDestinationAlpha + BlendFactorSourceAlphaSaturated ) type BlendOperation byte diff --git a/internal/graphicsdriver/directx/pipeline_windows.go b/internal/graphicsdriver/directx/pipeline_windows.go index 998e063b6..d5fa1655c 100644 --- a/internal/graphicsdriver/directx/pipeline_windows.go +++ b/internal/graphicsdriver/directx/pipeline_windows.go @@ -26,26 +26,44 @@ import ( const numDescriptorsPerFrame = 32 func blendFactorToBlend(f graphicsdriver.BlendFactor, alpha bool) _D3D12_BLEND { + // D3D12_RENDER_TARGET_BLEND_DESC's *BlendAlpha members don't allow *_COLOR values. + // See https://learn.microsoft.com/en-us/windows/win32/api/d3d12/ns-d3d12-d3d12_render_target_blend_desc. + switch f { case graphicsdriver.BlendFactorZero: return _D3D12_BLEND_ZERO case graphicsdriver.BlendFactorOne: return _D3D12_BLEND_ONE + case graphicsdriver.BlendFactorSourceColor: + if alpha { + return _D3D12_BLEND_SRC_ALPHA + } + return _D3D12_BLEND_SRC_COLOR + case graphicsdriver.BlendFactorOneMinusSourceColor: + if alpha { + return _D3D12_BLEND_INV_SRC_ALPHA + } + return _D3D12_BLEND_INV_SRC_COLOR case graphicsdriver.BlendFactorSourceAlpha: return _D3D12_BLEND_SRC_ALPHA - case graphicsdriver.BlendFactorDestinationAlpha: - return _D3D12_BLEND_DEST_ALPHA case graphicsdriver.BlendFactorOneMinusSourceAlpha: return _D3D12_BLEND_INV_SRC_ALPHA - case graphicsdriver.BlendFactorOneMinusDestinationAlpha: - return _D3D12_BLEND_INV_DEST_ALPHA case graphicsdriver.BlendFactorDestinationColor: - // D3D12_RENDER_TARGET_BLEND_DESC's *BlendAlpha members don't allow *_COLOR values. - // See https://learn.microsoft.com/en-us/windows/win32/api/d3d12/ns-d3d12-d3d12_render_target_blend_desc. if alpha { return _D3D12_BLEND_DEST_ALPHA } return _D3D12_BLEND_DEST_COLOR + case graphicsdriver.BlendFactorOneMinusDestinationColor: + if alpha { + return _D3D12_BLEND_INV_DEST_ALPHA + } + return _D3D12_BLEND_INV_DEST_COLOR + case graphicsdriver.BlendFactorDestinationAlpha: + return _D3D12_BLEND_DEST_ALPHA + case graphicsdriver.BlendFactorOneMinusDestinationAlpha: + return _D3D12_BLEND_INV_DEST_ALPHA + case graphicsdriver.BlendFactorSourceAlphaSaturated: + return _D3D12_BLEND_SRC_ALPHA_SAT default: panic(fmt.Sprintf("directx: invalid blend factor: %d", f)) } diff --git a/internal/graphicsdriver/metal/graphics_darwin.go b/internal/graphicsdriver/metal/graphics_darwin.go index c73de0630..613c5e460 100644 --- a/internal/graphicsdriver/metal/graphics_darwin.go +++ b/internal/graphicsdriver/metal/graphics_darwin.go @@ -306,16 +306,24 @@ func blendFactorToMetalBlendFactor(c graphicsdriver.BlendFactor) mtl.BlendFactor return mtl.BlendFactorZero case graphicsdriver.BlendFactorOne: return mtl.BlendFactorOne + case graphicsdriver.BlendFactorSourceColor: + return mtl.BlendFactorSourceColor + case graphicsdriver.BlendFactorOneMinusSourceColor: + return mtl.BlendFactorOneMinusSourceColor case graphicsdriver.BlendFactorSourceAlpha: return mtl.BlendFactorSourceAlpha - case graphicsdriver.BlendFactorDestinationAlpha: - return mtl.BlendFactorDestinationAlpha case graphicsdriver.BlendFactorOneMinusSourceAlpha: return mtl.BlendFactorOneMinusSourceAlpha - case graphicsdriver.BlendFactorOneMinusDestinationAlpha: - return mtl.BlendFactorOneMinusDestinationAlpha case graphicsdriver.BlendFactorDestinationColor: return mtl.BlendFactorDestinationColor + case graphicsdriver.BlendFactorOneMinusDestinationColor: + return mtl.BlendFactorOneMinusDestinationColor + case graphicsdriver.BlendFactorDestinationAlpha: + return mtl.BlendFactorDestinationAlpha + case graphicsdriver.BlendFactorOneMinusDestinationAlpha: + return mtl.BlendFactorOneMinusDestinationAlpha + case graphicsdriver.BlendFactorSourceAlphaSaturated: + return mtl.BlendFactorSourceAlphaSaturated default: panic(fmt.Sprintf("metal: invalid blend factor: %d", c)) } diff --git a/internal/graphicsdriver/opengl/context.go b/internal/graphicsdriver/opengl/context.go index bd50e4da0..96b80cf3e 100644 --- a/internal/graphicsdriver/opengl/context.go +++ b/internal/graphicsdriver/opengl/context.go @@ -24,12 +24,16 @@ import ( type blendFactor int const ( - glDstAlpha blendFactor = 0x0304 - glDstColor blendFactor = 0x0306 + glDstAlpha blendFactor = 0x304 + glDstColor blendFactor = 0x306 glOne blendFactor = 1 - glOneMinusDstAlpha blendFactor = 0x0305 - glOneMinusSrcAlpha blendFactor = 0x0303 - glSrcAlpha blendFactor = 0x0302 + glOneMinusDstAlpha blendFactor = 0x305 + glOneMinusDstColor blendFactor = 0x307 + glOneMinusSrcAlpha blendFactor = 0x303 + glOneMinusSrcColor blendFactor = 0x301 + glSrcAlpha blendFactor = 0x302 + glSrcAlphaSaturate blendFactor = 0x308 + glSrcColor blendFactor = 0x300 glZero blendFactor = 0 ) @@ -47,16 +51,24 @@ func convertBlendFactor(f graphicsdriver.BlendFactor) blendFactor { return glZero case graphicsdriver.BlendFactorOne: return glOne + case graphicsdriver.BlendFactorSourceColor: + return glSrcColor + case graphicsdriver.BlendFactorOneMinusSourceColor: + return glOneMinusSrcColor case graphicsdriver.BlendFactorSourceAlpha: return glSrcAlpha - case graphicsdriver.BlendFactorDestinationAlpha: - return glDstAlpha case graphicsdriver.BlendFactorOneMinusSourceAlpha: return glOneMinusSrcAlpha - case graphicsdriver.BlendFactorOneMinusDestinationAlpha: - return glOneMinusDstAlpha case graphicsdriver.BlendFactorDestinationColor: return glDstColor + case graphicsdriver.BlendFactorOneMinusDestinationColor: + return glOneMinusDstColor + case graphicsdriver.BlendFactorDestinationAlpha: + return glDstAlpha + case graphicsdriver.BlendFactorOneMinusDestinationAlpha: + return glOneMinusDstAlpha + case graphicsdriver.BlendFactorSourceAlphaSaturated: + return glSrcAlphaSaturate default: panic(fmt.Sprintf("opengl: invalid blend factor %d", f)) }