diff --git a/.gitignore b/.gitignore index eedaa3c..2aca880 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ test -.vscode \ No newline at end of file +.vscode +*.exe \ No newline at end of file diff --git a/examples/shaders/simple/main.go b/examples/shaders/simple/main.go new file mode 100644 index 0000000..b10522d --- /dev/null +++ b/examples/shaders/simple/main.go @@ -0,0 +1,98 @@ +package main + +import ( + "image" + "os" + "time" + + _ "image/png" + + "github.com/faiface/pixel" + "github.com/faiface/pixel/pixelgl" + "golang.org/x/image/colornames" +) + +func loadPicture(path string) (pixel.Picture, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + img, _, err := image.Decode(file) + if err != nil { + return nil, err + } + return pixel.PictureDataFromImage(img), nil +} + +func run() { + cfg := pixelgl.WindowConfig{ + Title: "Pixel Rocks!", + Bounds: pixel.R(0, 0, 1024, 768), + VSync: true, + } + win, err := pixelgl.NewWindow(cfg) + if err != nil { + panic(err) + } + + pic, err := loadPicture("thegopherproject.png") + if err != nil { + panic(err) + } + + start := time.Now() + var utime float32 + + sprite := pixel.NewSprite(pic, pic.Bounds()) + sc := pixelgl.NewCanvas(win.Bounds()) + sc.SetFragmentShader(customFragShader) + sc.BindUniform("u_time", &utime) + sc.RecompileShader() + + sprite.Draw(sc, pixel.IM.Moved(win.Bounds().Center())) + win.Clear(colornames.Greenyellow) + + for !win.Closed() { + utime = float32(time.Since(start).Seconds()) + sprite.Draw(sc, pixel.IM.Moved(win.Bounds().Center())) + sc.Draw(win, pixel.IM.Moved(win.Bounds().Center())) + win.Update() + } +} + +func main() { + pixelgl.Run(run) +} + +var customFragShader = ` +#version 330 core + +in vec4 Color; +in vec2 TexCoords; +in float Intensity; + +out vec4 color; + +uniform vec4 colorMask; +uniform vec4 texBounds; +uniform sampler2D tex; +uniform float u_time; + +void main() { + if (Intensity == 0) { + color = colorMask * Color; + } else { + color = vec4(0, 0, 0, 0); + color += (1 - Intensity) * Color; + vec2 t = (TexCoords - texBounds.xy) / texBounds.zw; + color += Intensity * Color * texture(tex, t); + color *= colorMask; + } + color.rgb *= cos(u_time*5); +} +` + +// var umouse = mgl32.Vec2{} +// umouse[0] = float32(win.MousePosition().X) / 1024 +// umouse[1] = float32(win.MousePosition().Y) / 768 diff --git a/examples/shaders/simple/thegopherproject.png b/examples/shaders/simple/thegopherproject.png new file mode 100644 index 0000000..8cfbd11 Binary files /dev/null and b/examples/shaders/simple/thegopherproject.png differ diff --git a/examples/shaders/waterreflection/main.go b/examples/shaders/waterreflection/main.go new file mode 100644 index 0000000..b1762d2 --- /dev/null +++ b/examples/shaders/waterreflection/main.go @@ -0,0 +1,153 @@ +package main + +import ( + "image" + "math" + "os" + "time" + + "golang.org/x/image/colornames" + + _ "image/png" + + "github.com/faiface/pixel" + "github.com/faiface/pixel/imdraw" + "github.com/faiface/pixel/pixelgl" +) + +func loadPicture(path string) (pixel.Picture, error) { + file, err := os.Open(path) + if err != nil { + return nil, err + } + defer file.Close() + img, _, err := image.Decode(file) + if err != nil { + return nil, err + } + return pixel.PictureDataFromImage(img), nil +} + +func run() { + + cfg := pixelgl.WindowConfig{ + Title: "Pixel Rocks!", + Bounds: pixel.R(0, 0, 1024, 768), + VSync: true, + } + pic, err := loadPicture("thegopherproject.png") + if err != nil { + panic(err) + } + win, err := pixelgl.NewWindow(cfg) + if err != nil { + panic(err) + } + + start := time.Now() + var utime float32 + + sprite := pixel.NewSprite(pic, pic.Bounds()) + + bounds := pic.Bounds() + + sc := pixelgl.NewCanvas(pic.Bounds()) + + sc2 := pixelgl.NewCanvas(pic.Bounds()) + sc2.SetFragmentShader(reflectionShader) + sc2.BindUniform("u_time", &utime) + sc2.RecompileShader() + imd := imdraw.New(nil) + imd.Color = colornames.Blueviolet + imd.EndShape = imdraw.RoundEndShape + + curpos := pixel.V(sc.Bounds().Center().X, -25) + tgtpos := pixel.V(sc.Bounds().Center().X, 316) + last := start + + for !win.Closed() { + + dt := time.Since(last).Seconds() + last = time.Now() + curpos = pixel.Lerp(curpos, tgtpos, 1-math.Pow(1.0/16, dt*0.5)) + sc.Clear(colornames.Black) + sc2.Clear(colornames.Black) + win.Clear(colornames.Black) + imd.Clear() + imd.Push(pixel.V(0, 0), pixel.V(bounds.Max.X, 0)) + imd.Push(pixel.V(bounds.Max.X, 0), pixel.V(bounds.Max.X, bounds.Max.Y)) + imd.Push(pixel.V(bounds.Max.X, bounds.Max.Y), pixel.V(0, bounds.Max.Y)) + imd.Push(pixel.V(0, bounds.Max.Y), pixel.V(0, 0)) + imd.Line(3) + + utime = float32(time.Since(start).Seconds()) + sprite.Draw(sc, pixel.IM.Moved(curpos)) + sc.Draw(sc2, pixel.IM.Moved(sc2.Bounds().Center())) + + imd.Draw(sc2) + sc2.Draw(win, pixel.IM.Moved(win.Bounds().Center())) + + win.Update() + } +} + +func main() { + pixelgl.Run(run) +} + +var reflectionShader = ` +#version 330 core + +in vec4 Color; +in vec2 TexCoords; +in float Intensity; + +out vec4 color; + +uniform vec4 colorMask; +uniform vec4 texBounds; +uniform sampler2D tex; +uniform float u_time; + +void main() { + if (Intensity == 0) { + color = colorMask * Color; + } else { + color = vec4(0, 0, 0, 0); + color += (1 - Intensity) * Color; + vec2 t = (TexCoords - texBounds.xy) / texBounds.zw; + color += Intensity * Color * texture(tex, t); + color *= colorMask; + vec2 uv = t; + vec3 overlayColor = vec3(0.1,0.1,1); + float sepoffset = 0.005*cos(u_time*3.0); + + if (t.y < 0.3 + sepoffset) + { + float xoffset = 0.005*cos(u_time*3.0+200.0*t.y); + float yoffset = ((0.3 - t.y)/0.3) * 0.05*(1.0+cos(u_time*3.0+50.0*t.y)); + color = texture(tex, vec2(t.x+xoffset,t.y+yoffset)); + } + } +} +` + +// void main() { +// fragColor = vec4(0, 0, 0, 0); +// fragColor += (1 - Intensity) * incolor; +// vec2 t = (TexCoords) / texBounds.zw; +// fragColor += Intensity * incolor * texture(tex, t); +// fragColor *= colorMask; + +// vec2 uv = t; +// vec3 overlayColor = vec3(0.1,0.1,1); +// float sepoffset = 0.005*cos(u_time*3.0); + +// if (t.y < 0.3 + sepoffset) +// { +// float xoffset = 0.005*cos(u_time*3.0+200.0*t.y); +// float yoffset = ((0.3 - t.y)/0.3) * 0.05*(1.0+cos(u_time*3.0+50.0*t.y)); +// fragColor = texture(tex, vec2(t.x+xoffset,t.y+yoffset)); +// } +// } +// ` diff --git a/examples/shaders/waterreflection/thegopherproject.png b/examples/shaders/waterreflection/thegopherproject.png new file mode 100644 index 0000000..8cfbd11 Binary files /dev/null and b/examples/shaders/waterreflection/thegopherproject.png differ diff --git a/pixelgl/canvas.go b/pixelgl/canvas.go index b8e4057..8587379 100644 --- a/pixelgl/canvas.go +++ b/pixelgl/canvas.go @@ -17,7 +17,7 @@ import ( // It supports TrianglesPosition, TrianglesColor, TrianglesPicture and PictureColor. type Canvas struct { gf *GLFrame - shader *glhf.Shader + shader *GLShader cmp pixel.ComposeMethod mat mgl32.Mat3 @@ -37,23 +37,9 @@ func NewCanvas(bounds pixel.Rect) *Canvas { col: mgl32.Vec4{1, 1, 1, 1}, } + baseShader(c) c.SetBounds(bounds) - - var shader *glhf.Shader - mainthread.Call(func() { - var err error - shader, err = glhf.NewShader( - canvasVertexFormat, - canvasUniformFormat, - canvasVertexShader, - canvasFragmentShader, - ) - if err != nil { - panic(errors.Wrap(err, "failed to create Canvas, there's a bug in the shader")) - } - }) - c.shader = shader - + c.shader.compile() return c } @@ -62,7 +48,7 @@ func NewCanvas(bounds pixel.Rect) *Canvas { // TrianglesPosition, TrianglesColor and TrianglesPicture are supported. func (c *Canvas) MakeTriangles(t pixel.Triangles) pixel.TargetTriangles { return &canvasTriangles{ - GLTriangles: NewGLTriangles(c.shader, t), + GLTriangles: NewGLTriangles(c.shader.s, t), dst: c, } } @@ -279,29 +265,33 @@ func (ct *canvasTriangles) draw(tex *glhf.Texture, bounds pixel.Rect) { // save the current state vars to avoid race condition cmp := ct.dst.cmp + smt := ct.dst.smooth mat := ct.dst.mat col := ct.dst.col - smt := ct.dst.smooth mainthread.CallNonBlock(func() { ct.dst.setGlhfBounds() setBlendFunc(cmp) frame := ct.dst.gf.Frame() - shader := ct.dst.shader + shader := ct.dst.shader.s frame.Begin() shader.Begin() + ct.dst.shader.uniformDefaults.transform = mat + ct.dst.shader.uniformDefaults.colormask = col dstBounds := ct.dst.Bounds() - shader.SetUniformAttr(canvasBounds, mgl32.Vec4{ + ct.dst.shader.uniformDefaults.bounds = mgl32.Vec4{ float32(dstBounds.Min.X), float32(dstBounds.Min.Y), float32(dstBounds.W()), float32(dstBounds.H()), - }) - shader.SetUniformAttr(canvasTransform, mat) - shader.SetUniformAttr(canvasColorMask, col) + } + + for loc, u := range ct.dst.shader.uniforms { + ct.dst.shader.s.SetUniformAttr(loc, u.Value) + } if tex == nil { ct.vs.Begin() @@ -311,7 +301,7 @@ func (ct *canvasTriangles) draw(tex *glhf.Texture, bounds pixel.Rect) { tex.Begin() bx, by, bw, bh := intBounds(bounds) - shader.SetUniformAttr(canvasTexBounds, mgl32.Vec4{ + shader.SetUniformAttr(canvasTexBounds, &mgl32.Vec4{ float32(bx), float32(by), float32(bw), @@ -358,7 +348,7 @@ const ( canvasIntensity ) -var canvasVertexFormat = glhf.AttrFormat{ +var defaultCanvasVertexFormat = glhf.AttrFormat{ canvasPosition: {Name: "position", Type: glhf.Vec2}, canvasColor: {Name: "color", Type: glhf.Vec4}, canvasTexCoords: {Name: "texCoords", Type: glhf.Vec2}, @@ -379,53 +369,14 @@ var canvasUniformFormat = glhf.AttrFormat{ canvasTexBounds: {Name: "texBounds", Type: glhf.Vec4}, } -var canvasVertexShader = ` -#version 330 core - -in vec2 position; -in vec4 color; -in vec2 texCoords; -in float intensity; - -out vec4 Color; -out vec2 TexCoords; -out float Intensity; - -uniform mat3 transform; -uniform vec4 bounds; - -void main() { - vec2 transPos = (transform * vec3(position, 1.0)).xy; - vec2 normPos = (transPos - bounds.xy) / bounds.zw * 2 - vec2(1, 1); - gl_Position = vec4(normPos, 0.0, 1.0); - Color = color; - TexCoords = texCoords; - Intensity = intensity; +func (c *Canvas) BindUniform(Name string, Value interface{}) { + c.shader.AddUniform(Name, Value) } -` -var canvasFragmentShader = ` -#version 330 core - -in vec4 Color; -in vec2 TexCoords; -in float Intensity; - -out vec4 color; - -uniform vec4 colorMask; -uniform vec4 texBounds; -uniform sampler2D tex; - -void main() { - if (Intensity == 0) { - color = colorMask * Color; - } else { - color = vec4(0, 0, 0, 0); - color += (1 - Intensity) * Color; - vec2 t = (TexCoords - texBounds.xy) / texBounds.zw; - color += Intensity * Color * texture(tex, t); - color *= colorMask; - } +func (c *Canvas) RecompileShader() { + c.shader.compile() +} + +func (c *Canvas) SetFragmentShader(fs string) { + c.shader.fs = fs } -` diff --git a/pixelgl/shader.go b/pixelgl/shader.go new file mode 100644 index 0000000..685f525 --- /dev/null +++ b/pixelgl/shader.go @@ -0,0 +1,262 @@ +package pixelgl + +import ( + "fmt" + + "github.com/faiface/glhf" + "github.com/faiface/mainthread" + "github.com/faiface/pixel" + "github.com/go-gl/mathgl/mgl32" + "github.com/pkg/errors" +) + +type ( + GLShader struct { + s *glhf.Shader + vf, uf glhf.AttrFormat + vs, fs string + + uniforms []gsUniformAttr + + uniformDefaults struct { + transform mgl32.Mat3 + colormask mgl32.Vec4 + bounds mgl32.Vec4 + texbounds mgl32.Vec4 + } + } + + gsUniformAttr struct { + Name string + Type AttrType + Value interface{} + } +) + +func (gs *GLShader) compile() { + gs.uf = nil + for _, u := range gs.uniforms { + gs.uf = append(gs.uf, glhf.Attr{ + Name: u.Name, + Type: glhf.AttrType(u.Type), + }) + } + var shader *glhf.Shader + mainthread.Call(func() { + var err error + shader, err = glhf.NewShader( + gs.vf, + gs.uf, + gs.vs, + gs.fs, + ) + if err != nil { + panic(errors.Wrap(err, "failed to create Canvas, there's a bug in the shader")) + } + }) + + gs.s = shader +} +func (gs *GLShader) GetUniform(Name string) int { + for i, u := range gs.uniforms { + if u.Name == Name { + return i + } + } + return -1 +} +func (gs *GLShader) AddUniform(Name string, Value interface{}) { + Type := getUniformType(Value) + fmt.Println(Type) + if loc := gs.GetUniform(Name); loc > -1 { + gs.uniforms[loc].Name = Name + gs.uniforms[loc].Type = Type + gs.uniforms[loc].Value = Value + return + } + gs.uniforms = append(gs.uniforms, gsUniformAttr{ + Name: Name, + Type: Type, + Value: Value, + }) +} + +func (c *Canvas) setUniforms(texbounds pixel.Rect) { + mat := c.mat + col := c.col + c.shader.uniformDefaults.transform = mat + c.shader.uniformDefaults.colormask = col + dstBounds := c.Bounds() + c.shader.uniformDefaults.bounds = mgl32.Vec4{ + float32(dstBounds.Min.X), + float32(dstBounds.Min.Y), + float32(dstBounds.W()), + float32(dstBounds.H()), + } + + for loc, u := range c.shader.uniforms { + c.shader.s.SetUniformAttr(loc, u.Value) + } +} + +func baseShader(c *Canvas) { + gs := &GLShader{ + vf: defaultCanvasVertexFormat, + vs: defaultCanvasVertexShader, + fs: baseCanvasFragmentShader, + } + + gs.AddUniform("transform", &gs.uniformDefaults.transform) + gs.AddUniform("colorMask", &gs.uniformDefaults.colormask) + gs.AddUniform("bounds", &gs.uniformDefaults.bounds) + gs.AddUniform("texBounds", &gs.uniformDefaults.texbounds) + + c.shader = gs +} +func getUniformType(v interface{}) AttrType { + switch v.(type) { + case int32: + return Int + case float32: + return Float + case mgl32.Vec2: + return Vec2 + case mgl32.Vec3: + return Vec3 + case mgl32.Vec4: + return Vec4 + case mgl32.Mat2: + return Mat2 + case mgl32.Mat2x3: + return Mat23 + case mgl32.Mat2x4: + return Mat24 + case mgl32.Mat3: + return Mat3 + case mgl32.Mat3x2: + return Mat32 + case mgl32.Mat3x4: + return Mat34 + case mgl32.Mat4: + return Mat4 + case mgl32.Mat4x2: + return Mat42 + case mgl32.Mat4x3: + return Mat43 + case *mgl32.Vec2: + return Vec2p + case *mgl32.Vec3: + return Vec3p + case *mgl32.Vec4: + return Vec4p + case *mgl32.Mat2: + return Mat2p + case *mgl32.Mat2x3: + return Mat23p + case *mgl32.Mat2x4: + return Mat24p + case *mgl32.Mat3: + return Mat3p + case *mgl32.Mat3x2: + return Mat32p + case *mgl32.Mat3x4: + return Mat34p + case *mgl32.Mat4: + return Mat4p + case *mgl32.Mat4x2: + return Mat42p + case *mgl32.Mat4x3: + return Mat43p + case *int32: + return Intp + case *float32: + return Floatp + default: + panic("invalid AttrType") + } +} + +type AttrType int + +// List of all possible attribute types. +const ( + Int AttrType = iota + Float + Vec2 + Vec3 + Vec4 + Mat2 + Mat23 + Mat24 + Mat3 + Mat32 + Mat34 + Mat4 + Mat42 + Mat43 + Intp + Floatp + Vec2p + Vec3p + Vec4p + Mat2p + Mat23p + Mat24p + Mat3p + Mat32p + Mat34p + Mat4p + Mat42p + Mat43p +) + +var defaultCanvasVertexShader = ` +#version 330 core + +in vec2 position; +in vec4 color; +in vec2 texCoords; +in float intensity; + +out vec4 Color; +out vec2 TexCoords; +out float Intensity; + +uniform mat3 transform; +uniform vec4 bounds; + +void main() { + vec2 transPos = (transform * vec3(position, 1.0)).xy; + vec2 normPos = (transPos - bounds.xy) / bounds.zw * 2 - vec2(1, 1); + gl_Position = vec4(normPos, 0.0, 1.0); + Color = color; + TexCoords = texCoords; + Intensity = intensity; +} +` + +var baseCanvasFragmentShader = ` +#version 330 core + +in vec4 Color; +in vec2 TexCoords; +in float Intensity; + +out vec4 color; + +uniform vec4 colorMask; +uniform vec4 texBounds; +uniform sampler2D tex; + +void main() { + if (Intensity == 0) { + color = colorMask * Color; + } else { + color = vec4(0, 0, 0, 0); + color += (1 - Intensity) * Color; + vec2 t = (TexCoords - texBounds.xy) / texBounds.zw; + color += Intensity * Color * texture(tex, t); + color *= colorMask; + } +} +`