mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
shaderir/glsl: Enable dFdx for WebGL
With WebGL1, an extension is required for dFdx. On the other hand, with WebGL2, GLSL ES 300 is required and the extension is forbidden. This change fixes shaderir/glsl to switch the output depends on the WebGL version. This change also adds a new build tag 'ebitenwebgl1' forcing WebGL 1. Updates #1404
This commit is contained in:
parent
2097312a8b
commit
b1d7a5f595
2
doc.go
2
doc.go
@ -72,6 +72,8 @@
|
|||||||
//
|
//
|
||||||
// `ebitengl` forces to use OpenGL in any environments.
|
// `ebitengl` forces to use OpenGL in any environments.
|
||||||
//
|
//
|
||||||
|
// `ebitenwebgl1` forces to use WebGL 1 on browsers.
|
||||||
|
//
|
||||||
// `ebitensinglethread` disables Ebiten's thread safety to unlock maximum performance. If you use this you will have
|
// `ebitensinglethread` disables Ebiten's thread safety to unlock maximum performance. If you use this you will have
|
||||||
// to manage threads yourself. Functions like IsKeyPressed will no longer be concurrent-safe with this build tag.
|
// to manage threads yourself. Functions like IsKeyPressed will no longer be concurrent-safe with this build tag.
|
||||||
// They must be called from the main thread or the same goroutine as the given game's callback functions like Update
|
// They must be called from the main thread or the same goroutine as the given game's callback functions like Update
|
||||||
|
@ -94,7 +94,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
isWebGL2Available = js.Global().Get("WebGL2RenderingContext").Truthy()
|
isWebGL2Available = !forceWebGL1 && js.Global().Get("WebGL2RenderingContext").Truthy()
|
||||||
)
|
)
|
||||||
|
|
||||||
type contextImpl struct {
|
type contextImpl struct {
|
||||||
@ -148,6 +148,10 @@ func (c *context) reset() error {
|
|||||||
c.blendFunc(driver.CompositeModeSourceOver)
|
c.blendFunc(driver.CompositeModeSourceOver)
|
||||||
f := gl.Call("getParameter", gles.FRAMEBUFFER_BINDING)
|
f := gl.Call("getParameter", gles.FRAMEBUFFER_BINDING)
|
||||||
c.screenFramebuffer = framebufferNative(f)
|
c.screenFramebuffer = framebufferNative(f)
|
||||||
|
|
||||||
|
if !isWebGL2Available {
|
||||||
|
gl.Call("getExtension", "OES_standard_derivatives")
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
19
internal/graphicsdriver/opengl/context_notwebgl1.go
Normal file
19
internal/graphicsdriver/opengl/context_notwebgl1.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2020 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.
|
||||||
|
|
||||||
|
// +build !ebitenwebgl1
|
||||||
|
|
||||||
|
package opengl
|
||||||
|
|
||||||
|
const forceWebGL1 = false
|
19
internal/graphicsdriver/opengl/context_webgl1.go
Normal file
19
internal/graphicsdriver/opengl/context_webgl1.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// Copyright 2020 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.
|
||||||
|
|
||||||
|
// +build ebitenwebgl1
|
||||||
|
|
||||||
|
package opengl
|
||||||
|
|
||||||
|
const forceWebGL1 = true
|
@ -52,7 +52,7 @@ func (s *Shader) Dispose() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Shader) compile() error {
|
func (s *Shader) compile() error {
|
||||||
vssrc, fssrc := glsl.Compile(s.ir)
|
vssrc, fssrc := glsl.Compile(s.ir, glslVersion())
|
||||||
|
|
||||||
vs, err := s.graphics.context.newShader(vertexShader, vssrc)
|
vs, err := s.graphics.context.newShader(vertexShader, vssrc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
26
internal/graphicsdriver/opengl/shader_js.go
Normal file
26
internal/graphicsdriver/opengl/shader_js.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2020 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 opengl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/glsl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func glslVersion() glsl.GLSLVersion {
|
||||||
|
if isWebGL2Available {
|
||||||
|
return glsl.GLSLVersionES300
|
||||||
|
}
|
||||||
|
return glsl.GLSLVersionWebGL1
|
||||||
|
}
|
25
internal/graphicsdriver/opengl/shader_notjs.go
Normal file
25
internal/graphicsdriver/opengl/shader_notjs.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2020 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.
|
||||||
|
|
||||||
|
// +build !js
|
||||||
|
|
||||||
|
package opengl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/internal/shaderir/glsl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func glslVersion() glsl.GLSLVersion {
|
||||||
|
return glsl.GLSLVersionDefault
|
||||||
|
}
|
@ -30,8 +30,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func glslNormalize(str string) string {
|
func glslNormalize(str string) string {
|
||||||
if strings.HasPrefix(str, glsl.FragmentPrelude) {
|
p := glsl.FragmentPrelude(glsl.GLSLVersionDefault)
|
||||||
str = str[len(glsl.FragmentPrelude):]
|
if strings.HasPrefix(str, p) {
|
||||||
|
str = str[len(p):]
|
||||||
}
|
}
|
||||||
return strings.TrimSpace(str)
|
return strings.TrimSpace(str)
|
||||||
}
|
}
|
||||||
@ -156,7 +157,7 @@ func TestCompile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GLSL
|
// GLSL
|
||||||
vs, fs := glsl.Compile(s)
|
vs, fs := glsl.Compile(s, glsl.GLSLVersionDefault)
|
||||||
if got, want := glslNormalize(vs), glslNormalize(string(tc.VS)); got != want {
|
if got, want := glslNormalize(vs), glslNormalize(string(tc.VS)); got != want {
|
||||||
compare(t, "GLSL Vertex", got, want)
|
compare(t, "GLSL Vertex", got, want)
|
||||||
}
|
}
|
||||||
|
@ -24,15 +24,40 @@ import (
|
|||||||
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
|
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
|
||||||
)
|
)
|
||||||
|
|
||||||
const FragmentPrelude = `#if defined(GL_ES)
|
type GLSLVersion int
|
||||||
|
|
||||||
|
const (
|
||||||
|
GLSLVersionDefault GLSLVersion = iota
|
||||||
|
GLSLVersionWebGL1
|
||||||
|
GLSLVersionES300
|
||||||
|
)
|
||||||
|
|
||||||
|
func VertexPrelude(version GLSLVersion) string {
|
||||||
|
if version == GLSLVersionES300 {
|
||||||
|
return `#version 300 es`
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func FragmentPrelude(version GLSLVersion) string {
|
||||||
|
var prefix string
|
||||||
|
switch version {
|
||||||
|
case GLSLVersionWebGL1:
|
||||||
|
prefix = `#extension GL_OES_standard_derivatives : enable` + "\n\n"
|
||||||
|
case GLSLVersionES300:
|
||||||
|
prefix = `#version 300 es` + "\n\n"
|
||||||
|
}
|
||||||
|
return prefix + `#if defined(GL_ES)
|
||||||
precision highp float;
|
precision highp float;
|
||||||
#else
|
#else
|
||||||
#define lowp
|
#define lowp
|
||||||
#define mediump
|
#define mediump
|
||||||
#define highp
|
#define highp
|
||||||
#endif`
|
#endif`
|
||||||
|
}
|
||||||
|
|
||||||
type compileContext struct {
|
type compileContext struct {
|
||||||
|
version GLSLVersion
|
||||||
structNames map[string]string
|
structNames map[string]string
|
||||||
structTypes []shaderir.Type
|
structTypes []shaderir.Type
|
||||||
}
|
}
|
||||||
@ -51,14 +76,16 @@ func (c *compileContext) structName(p *shaderir.Program, t *shaderir.Type) strin
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
func Compile(p *shaderir.Program) (vertexShader, fragmentShader string) {
|
func Compile(p *shaderir.Program, version GLSLVersion) (vertexShader, fragmentShader string) {
|
||||||
c := &compileContext{
|
c := &compileContext{
|
||||||
|
version: version,
|
||||||
structNames: map[string]string{},
|
structNames: map[string]string{},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vertex func
|
// Vertex func
|
||||||
var vslines []string
|
var vslines []string
|
||||||
{
|
{
|
||||||
|
vslines = append(vslines, strings.Split(VertexPrelude(version), "\n")...)
|
||||||
vslines = append(vslines, "{{.Structs}}")
|
vslines = append(vslines, "{{.Structs}}")
|
||||||
if len(p.Uniforms) > 0 || p.TextureNum > 0 || len(p.Attributes) > 0 || len(p.Varyings) > 0 {
|
if len(p.Uniforms) > 0 || p.TextureNum > 0 || len(p.Attributes) > 0 || len(p.Varyings) > 0 {
|
||||||
vslines = append(vslines, "")
|
vslines = append(vslines, "")
|
||||||
@ -69,10 +96,18 @@ func Compile(p *shaderir.Program) (vertexShader, fragmentShader string) {
|
|||||||
vslines = append(vslines, fmt.Sprintf("uniform sampler2D T%d;", i))
|
vslines = append(vslines, fmt.Sprintf("uniform sampler2D T%d;", i))
|
||||||
}
|
}
|
||||||
for i, t := range p.Attributes {
|
for i, t := range p.Attributes {
|
||||||
vslines = append(vslines, fmt.Sprintf("attribute %s;", c.glslVarDecl(p, &t, fmt.Sprintf("A%d", i))))
|
keyword := "attribute"
|
||||||
|
if version == GLSLVersionES300 {
|
||||||
|
keyword = "in"
|
||||||
|
}
|
||||||
|
vslines = append(vslines, fmt.Sprintf("%s %s;", keyword, c.glslVarDecl(p, &t, fmt.Sprintf("A%d", i))))
|
||||||
}
|
}
|
||||||
for i, t := range p.Varyings {
|
for i, t := range p.Varyings {
|
||||||
vslines = append(vslines, fmt.Sprintf("varying %s;", c.glslVarDecl(p, &t, fmt.Sprintf("V%d", i))))
|
keyword := "varying"
|
||||||
|
if version == GLSLVersionES300 {
|
||||||
|
keyword = "out"
|
||||||
|
}
|
||||||
|
vslines = append(vslines, fmt.Sprintf("%s %s;", keyword, c.glslVarDecl(p, &t, fmt.Sprintf("V%d", i))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(p.Funcs) > 0 {
|
if len(p.Funcs) > 0 {
|
||||||
@ -99,7 +134,7 @@ func Compile(p *shaderir.Program) (vertexShader, fragmentShader string) {
|
|||||||
// Fragment func
|
// Fragment func
|
||||||
var fslines []string
|
var fslines []string
|
||||||
{
|
{
|
||||||
fslines = append(fslines, strings.Split(FragmentPrelude, "\n")...)
|
fslines = append(fslines, strings.Split(FragmentPrelude(version), "\n")...)
|
||||||
fslines = append(fslines, "", "{{.Structs}}")
|
fslines = append(fslines, "", "{{.Structs}}")
|
||||||
if len(p.Uniforms) > 0 || p.TextureNum > 0 || len(p.Varyings) > 0 {
|
if len(p.Uniforms) > 0 || p.TextureNum > 0 || len(p.Varyings) > 0 {
|
||||||
fslines = append(fslines, "")
|
fslines = append(fslines, "")
|
||||||
@ -110,9 +145,17 @@ func Compile(p *shaderir.Program) (vertexShader, fragmentShader string) {
|
|||||||
fslines = append(fslines, fmt.Sprintf("uniform sampler2D T%d;", i))
|
fslines = append(fslines, fmt.Sprintf("uniform sampler2D T%d;", i))
|
||||||
}
|
}
|
||||||
for i, t := range p.Varyings {
|
for i, t := range p.Varyings {
|
||||||
fslines = append(fslines, fmt.Sprintf("varying %s;", c.glslVarDecl(p, &t, fmt.Sprintf("V%d", i))))
|
keyword := "varying"
|
||||||
|
if version == GLSLVersionES300 {
|
||||||
|
keyword = "in"
|
||||||
|
}
|
||||||
|
fslines = append(fslines, fmt.Sprintf("%s %s;", keyword, c.glslVarDecl(p, &t, fmt.Sprintf("V%d", i))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if version == GLSLVersionES300 {
|
||||||
|
fslines = append(fslines, "out vec4 fragColor;")
|
||||||
|
}
|
||||||
|
|
||||||
if len(p.Funcs) > 0 {
|
if len(p.Funcs) > 0 {
|
||||||
fslines = append(fslines, "")
|
fslines = append(fslines, "")
|
||||||
for _, f := range p.Funcs {
|
for _, f := range p.Funcs {
|
||||||
@ -273,7 +316,7 @@ func constantToNumberLiteral(t shaderir.ConstType, v constant.Value) string {
|
|||||||
return fmt.Sprintf("?(unexpected literal: %s)", v)
|
return fmt.Sprintf("?(unexpected literal: %s)", v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func localVariableName(p *shaderir.Program, topBlock, block *shaderir.Block, idx int) string {
|
func (c *compileContext) localVariableName(p *shaderir.Program, topBlock, block *shaderir.Block, idx int) string {
|
||||||
switch topBlock {
|
switch topBlock {
|
||||||
case p.VertexFunc.Block:
|
case p.VertexFunc.Block:
|
||||||
na := len(p.Attributes)
|
na := len(p.Attributes)
|
||||||
@ -296,6 +339,9 @@ func localVariableName(p *shaderir.Program, topBlock, block *shaderir.Block, idx
|
|||||||
case idx < nv+1:
|
case idx < nv+1:
|
||||||
return fmt.Sprintf("V%d", idx-1)
|
return fmt.Sprintf("V%d", idx-1)
|
||||||
case idx == nv+1:
|
case idx == nv+1:
|
||||||
|
if c.version == GLSLVersionES300 {
|
||||||
|
return "fragColor"
|
||||||
|
}
|
||||||
return "gl_FragColor"
|
return "gl_FragColor"
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("l%d", idx-(nv+2))
|
return fmt.Sprintf("l%d", idx-(nv+2))
|
||||||
@ -307,7 +353,7 @@ func localVariableName(p *shaderir.Program, topBlock, block *shaderir.Block, idx
|
|||||||
|
|
||||||
func (c *compileContext) initVariable(p *shaderir.Program, topBlock, block *shaderir.Block, index int, decl bool, level int) []string {
|
func (c *compileContext) initVariable(p *shaderir.Program, topBlock, block *shaderir.Block, index int, decl bool, level int) []string {
|
||||||
idt := strings.Repeat("\t", level+1)
|
idt := strings.Repeat("\t", level+1)
|
||||||
name := localVariableName(p, topBlock, block, index)
|
name := c.localVariableName(p, topBlock, block, index)
|
||||||
t := p.LocalVariableType(topBlock, block, index)
|
t := p.LocalVariableType(topBlock, block, index)
|
||||||
|
|
||||||
var lines []string
|
var lines []string
|
||||||
@ -352,11 +398,11 @@ func (c *compileContext) glslBlock(p *shaderir.Program, topBlock, block *shaderi
|
|||||||
case shaderir.TextureVariable:
|
case shaderir.TextureVariable:
|
||||||
return fmt.Sprintf("T%d", e.Index)
|
return fmt.Sprintf("T%d", e.Index)
|
||||||
case shaderir.LocalVariable:
|
case shaderir.LocalVariable:
|
||||||
return localVariableName(p, topBlock, block, e.Index)
|
return c.localVariableName(p, topBlock, block, e.Index)
|
||||||
case shaderir.StructMember:
|
case shaderir.StructMember:
|
||||||
return fmt.Sprintf("M%d", e.Index)
|
return fmt.Sprintf("M%d", e.Index)
|
||||||
case shaderir.BuiltinFuncExpr:
|
case shaderir.BuiltinFuncExpr:
|
||||||
return builtinFuncString(e.BuiltinFunc)
|
return c.builtinFuncString(e.BuiltinFunc)
|
||||||
case shaderir.SwizzlingExpr:
|
case shaderir.SwizzlingExpr:
|
||||||
if !shaderir.IsValidSwizzling(e.Swizzling) {
|
if !shaderir.IsValidSwizzling(e.Swizzling) {
|
||||||
return fmt.Sprintf("?(unexpected swizzling: %s)", e.Swizzling)
|
return fmt.Sprintf("?(unexpected swizzling: %s)", e.Swizzling)
|
||||||
@ -433,7 +479,7 @@ func (c *compileContext) glslBlock(p *shaderir.Program, topBlock, block *shaderi
|
|||||||
ct = shaderir.ConstTypeFloat
|
ct = shaderir.ConstTypeFloat
|
||||||
}
|
}
|
||||||
|
|
||||||
v := localVariableName(p, topBlock, block, s.ForVarIndex)
|
v := c.localVariableName(p, topBlock, block, s.ForVarIndex)
|
||||||
var delta string
|
var delta string
|
||||||
switch val, _ := constant.Float64Val(s.ForDelta); val {
|
switch val, _ := constant.Float64Val(s.ForDelta); val {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -63,7 +63,7 @@ func basicTypeString(t shaderir.BasicType) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func builtinFuncString(f shaderir.BuiltinFunc) string {
|
func (c *compileContext) builtinFuncString(f shaderir.BuiltinFunc) string {
|
||||||
switch f {
|
switch f {
|
||||||
case shaderir.Atan2:
|
case shaderir.Atan2:
|
||||||
return "atan"
|
return "atan"
|
||||||
@ -71,6 +71,11 @@ func builtinFuncString(f shaderir.BuiltinFunc) string {
|
|||||||
return "dFdx"
|
return "dFdx"
|
||||||
case shaderir.Dfdy:
|
case shaderir.Dfdy:
|
||||||
return "dFdy"
|
return "dFdy"
|
||||||
|
case shaderir.Texture2DF:
|
||||||
|
if c.version == GLSLVersionES300 {
|
||||||
|
return "texture"
|
||||||
|
}
|
||||||
|
return "texture2D"
|
||||||
default:
|
default:
|
||||||
return string(f)
|
return string(f)
|
||||||
}
|
}
|
||||||
|
@ -924,3 +924,56 @@ func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
|
|||||||
testPixels("DrawTrianglesShader", dst)
|
testPixels("DrawTrianglesShader", dst)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Issue #1404
|
||||||
|
func TestShaderDerivatives(t *testing.T) {
|
||||||
|
const w, h = 16, 16
|
||||||
|
|
||||||
|
s, err := NewShader([]byte(`package main
|
||||||
|
|
||||||
|
func Fragment(position vec4, texCoord vec2, color vec4) vec4 {
|
||||||
|
p := imageSrc0At(texCoord)
|
||||||
|
return vec4(abs(dfdx(p.r)), abs(dfdy(p.g)), 0, 1)
|
||||||
|
}
|
||||||
|
`))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := NewImage(w, h)
|
||||||
|
src := NewImage(w, h)
|
||||||
|
pix := make([]byte, 4*w*h)
|
||||||
|
for j := 0; j < h; j++ {
|
||||||
|
for i := 0; i < w; i++ {
|
||||||
|
if i < w/2 {
|
||||||
|
pix[4*(j*w+i)] = 0xff
|
||||||
|
}
|
||||||
|
if j < h/2 {
|
||||||
|
pix[4*(j*w+i)+1] = 0xff
|
||||||
|
}
|
||||||
|
pix[4*(j*w+i)+3] = 0xff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
src.ReplacePixels(pix)
|
||||||
|
|
||||||
|
op := &DrawRectShaderOptions{}
|
||||||
|
op.Images[0] = src
|
||||||
|
dst.DrawRectShader(w, h, s, op)
|
||||||
|
|
||||||
|
// The results of the edges might be unreliable. Skip the edges.
|
||||||
|
for j := 1; j < h-1; j++ {
|
||||||
|
for i := 1; i < w-1; i++ {
|
||||||
|
got := dst.At(i, j).(color.RGBA)
|
||||||
|
want := color.RGBA{0, 0, 0, 0xff}
|
||||||
|
if i == w/2-1 || i == w/2 {
|
||||||
|
want.R = 0xff
|
||||||
|
}
|
||||||
|
if j == h/2-1 || j == h/2 {
|
||||||
|
want.G = 0xff
|
||||||
|
}
|
||||||
|
if got != want {
|
||||||
|
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user