mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2026-04-22 15:57:15 +08:00
1de8288361
Closes #3366
238 lines
5.9 KiB
Go
238 lines
5.9 KiB
Go
// Copyright 2025 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 vector
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
)
|
|
|
|
// The implementation is based on the following article:
|
|
// https://medium.com/@evanwallace/easy-scalable-text-rendering-on-the-gpu-c3f4d782c5ac
|
|
|
|
// These values are protected by cacheM.
|
|
|
|
var (
|
|
stencilBufferFillShader *ebiten.Shader
|
|
stencilBufferBezierShader *ebiten.Shader
|
|
stencilBufferNonZeroShader *ebiten.Shader
|
|
stencilBufferNonZeroAAShader *ebiten.Shader
|
|
stencilBufferEvenOddShader *ebiten.Shader
|
|
stencilBufferEvenOddAAShader *ebiten.Shader
|
|
|
|
stencilBufferM sync.Mutex
|
|
)
|
|
|
|
func ensureStencilBufferShaders() (*ebiten.Shader, error) {
|
|
stencilBufferM.Lock()
|
|
defer stencilBufferM.Unlock()
|
|
|
|
if stencilBufferFillShader != nil {
|
|
return stencilBufferFillShader, nil
|
|
}
|
|
s, err := ebiten.NewShader([]byte(stencilBufferFillShaderSrc))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stencilBufferFillShader = s
|
|
return stencilBufferFillShader, err
|
|
}
|
|
|
|
func ensureStencilBufferBezierShader() (*ebiten.Shader, error) {
|
|
stencilBufferM.Lock()
|
|
defer stencilBufferM.Unlock()
|
|
|
|
if stencilBufferBezierShader != nil {
|
|
return stencilBufferBezierShader, nil
|
|
}
|
|
s, err := ebiten.NewShader([]byte(stencilBufferBezierShaderSrc))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stencilBufferBezierShader = s
|
|
return stencilBufferBezierShader, nil
|
|
}
|
|
|
|
func ensureStencilBufferNonZeroShader(antialias bool) (*ebiten.Shader, error) {
|
|
stencilBufferM.Lock()
|
|
defer stencilBufferM.Unlock()
|
|
|
|
if antialias {
|
|
if stencilBufferNonZeroAAShader != nil {
|
|
return stencilBufferNonZeroAAShader, nil
|
|
}
|
|
s, err := ebiten.NewShader([]byte(stencilBufferNonZeroAAShaderSrc))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stencilBufferNonZeroAAShader = s
|
|
return stencilBufferNonZeroAAShader, nil
|
|
}
|
|
|
|
if stencilBufferNonZeroShader != nil {
|
|
return stencilBufferNonZeroShader, nil
|
|
}
|
|
s, err := ebiten.NewShader([]byte(stencilBufferNonZeroShaderSrc))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stencilBufferNonZeroShader = s
|
|
return stencilBufferNonZeroShader, nil
|
|
}
|
|
|
|
func ensureStencilBufferEvenOddShader(antialias bool) (*ebiten.Shader, error) {
|
|
stencilBufferM.Lock()
|
|
defer stencilBufferM.Unlock()
|
|
|
|
if antialias {
|
|
if stencilBufferEvenOddAAShader != nil {
|
|
return stencilBufferEvenOddAAShader, nil
|
|
}
|
|
s, err := ebiten.NewShader([]byte(stencilBufferEvenOddAAShaderSrc))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stencilBufferEvenOddAAShader = s
|
|
return stencilBufferEvenOddAAShader, nil
|
|
}
|
|
|
|
if stencilBufferEvenOddShader != nil {
|
|
return stencilBufferEvenOddShader, nil
|
|
}
|
|
s, err := ebiten.NewShader([]byte(stencilBufferEvenOddShaderSrc))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stencilBufferEvenOddShader = s
|
|
return stencilBufferEvenOddShader, nil
|
|
}
|
|
|
|
//ebitengine:shadersource
|
|
const stencilBufferFillShaderSrc = `//kage:unit pixels
|
|
|
|
package main
|
|
|
|
func Fragment(dstPos vec4, srcPos vec2, color vec4, custom vec4) vec4 {
|
|
v := 1.0 / 255.0
|
|
if frontfacing() {
|
|
v *= 16
|
|
}
|
|
return v * color
|
|
}
|
|
`
|
|
|
|
//ebitengine:shadersource
|
|
const stencilBufferBezierShaderSrc = `//kage:unit pixels
|
|
|
|
package main
|
|
|
|
func Fragment(dstPos vec4, srcPos vec2, color vec4, custom vec4) vec4 {
|
|
// Loop-Blinn algorithm.
|
|
// https://developer.nvidia.com/gpugems/gpugems3/part-iv-image-effects/chapter-25-rendering-vector-art-gpu
|
|
uv := custom.xy
|
|
v := clamp(-sign(uv.x * uv.x - uv.y), 0, 1) * 1.0/255.0
|
|
// This is opposite to the fill shader, especially for the non-zero fill rule.
|
|
if !frontfacing() {
|
|
v *= 16
|
|
}
|
|
return v * color
|
|
}
|
|
`
|
|
|
|
//ebitengine:shadersource
|
|
const stencilBufferNonZeroShaderSrc = `//kage:unit pixels
|
|
|
|
package main
|
|
|
|
func round(x float) float {
|
|
return floor(x + 0.5)
|
|
}
|
|
|
|
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
|
|
c := imageSrc0UnsafeAt(srcPos)
|
|
r := int(round(c.r*255))
|
|
w := abs((r >> 4) - (r & 0x0F))
|
|
v := min(float(w), 1)
|
|
return v * color
|
|
}
|
|
`
|
|
|
|
//ebitengine:shadersource
|
|
const stencilBufferNonZeroAAShaderSrc = `//kage:unit pixels
|
|
|
|
package main
|
|
|
|
func round(x vec4) vec4 {
|
|
return floor(x + 0.5)
|
|
}
|
|
|
|
func Fragment(dstPos vec4, srcPos vec2, color vec4, custom vec4) vec4 {
|
|
c0 := imageSrc0UnsafeAt(srcPos)
|
|
// imageSrc1UnsafeAt uses the offset info, which would prevent batching.
|
|
// Use a custom offset instead.
|
|
c1 := imageSrc0UnsafeAt(srcPos + custom.xy)
|
|
ci0 := ivec4(round(c0*255))
|
|
ci1 := ivec4(round(c1*255))
|
|
w0 := abs((ci0 >> 4) - (ci0 & 0x0F))
|
|
w1 := abs((ci1 >> 4) - (ci1 & 0x0F))
|
|
v0 := min(vec4(w0), 1)
|
|
v1 := min(vec4(w1), 1)
|
|
return (dot(v0, vec4(1.0/8.0)) + dot(v1, vec4(1.0/8.0))) * color
|
|
}
|
|
`
|
|
|
|
//ebitengine:shadersource
|
|
const stencilBufferEvenOddShaderSrc = `//kage:unit pixels
|
|
|
|
package main
|
|
|
|
func round(x float) float {
|
|
return floor(x + 0.5)
|
|
}
|
|
|
|
func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
|
|
c := imageSrc0UnsafeAt(srcPos)
|
|
r := int(round(c.r*255))
|
|
w := abs((r >> 4) - (r & 0x0F))
|
|
v := float(w % 2)
|
|
return v * color
|
|
}
|
|
`
|
|
|
|
//ebitengine:shadersource
|
|
const stencilBufferEvenOddAAShaderSrc = `//kage:unit pixels
|
|
|
|
package main
|
|
|
|
func round(x vec4) vec4 {
|
|
return floor(x + 0.5)
|
|
}
|
|
|
|
func Fragment(dstPos vec4, srcPos vec2, color vec4, custom vec4) vec4 {
|
|
c0 := imageSrc0UnsafeAt(srcPos)
|
|
// imageSrc1UnsafeAt uses the offset info, which would prevent batching.
|
|
// Use a custom offset instead.
|
|
c1 := imageSrc0UnsafeAt(srcPos + custom.xy)
|
|
ci0 := ivec4(round(c0*255))
|
|
ci1 := ivec4(round(c1*255))
|
|
w0 := abs((ci0 >> 4) - (ci0 & 0x0F))
|
|
w1 := abs((ci1 >> 4) - (ci1 & 0x0F))
|
|
v0 := vec4(w0 % 2)
|
|
v1 := vec4(w1 % 2)
|
|
return (dot(v0, vec4(1.0/8.0)) + dot(v1, vec4(1.0/8.0))) * color
|
|
}
|
|
`
|