From d0aa18ddb9977ca424bb9e28d87b032e02b93a2c Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Sat, 30 May 2020 04:36:24 +0900 Subject: [PATCH] Add shader public APIs experimentally Updates #1168 --- examples/shader/main.go | 94 +++++++++++++++++++++++++++++++++++++++++ image.go | 82 ++++++++++++++++++++++++++++++++++- shader.go | 40 ++++++++++++++++++ 3 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 examples/shader/main.go create mode 100644 shader.go diff --git a/examples/shader/main.go b/examples/shader/main.go new file mode 100644 index 000000000..00ad3625d --- /dev/null +++ b/examples/shader/main.go @@ -0,0 +1,94 @@ +// 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 example jsgo + +package main + +import ( + "log" + + "github.com/hajimehoshi/ebiten" +) + +const ( + screenWidth = 640 + screenHeight = 480 +) + +const shaderSrc = `package main + +func Vertex(position vec2, texCoord vec2, color vec4) vec4 { + return mat4( + 2.0/640, 0, 0, 0, + 0, 2.0/480, 0, 0, + 0, 0, 1, 0, + -1, -1, 0, 1, + ) * vec4(position, 0, 1) +} + +func Fragment(position vec4) vec4 { + return vec4(position.x/640, position.y/480, 0, 1) +}` + +type Game struct { + shader *ebiten.Shader +} + +func (g *Game) Update(screen *ebiten.Image) error { + if g.shader == nil { + var err error + g.shader, err = ebiten.NewShader([]byte(shaderSrc)) + if err != nil { + return err + } + } + return nil +} + +func (g *Game) Draw(screen *ebiten.Image) { + w, h := screen.Size() + vs := []ebiten.Vertex{ + { + DstX: 0, + DstY: 0, + }, + { + DstX: float32(w), + DstY: 0, + }, + { + DstX: 0, + DstY: float32(h), + }, + { + DstX: float32(w), + DstY: float32(h), + }, + } + is := []uint16{0, 1, 2, 1, 2, 3} + screen.DrawTrianglesWithShader(vs, is, g.shader, nil) +} + +func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) { + return screenWidth, screenHeight +} + +func main() { + ebiten.SetWindowSize(screenWidth, screenHeight) + ebiten.SetWindowTitle("Shader (Ebiten Demo)") + if err := ebiten.RunGame(&Game{}); err != nil { + log.Fatal(err) + } +} diff --git a/image.go b/image.go index c7336c729..4a359a10f 100644 --- a/image.go +++ b/image.go @@ -312,7 +312,6 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o bx1 := float32(b.Max.X) by1 := float32(b.Max.Y) - // TODO: Should we use mipmap.verticesBackend? vs := make([]float32, len(vertices)*graphics.VertexFloatNum) for i, v := range vertices { vs[i*graphics.VertexFloatNum] = v.DstX @@ -334,6 +333,87 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o i.buffered.DrawTriangles(img.buffered, vs, is, options.ColorM.impl, mode, filter, driver.Address(options.Address), nil, nil) } +type DrawTrianglesWithShaderOptions struct { + Uniforms []interface{} + CompositeMode CompositeMode +} + +func (i *Image) DrawTrianglesWithShader(vertices []Vertex, indices []uint16, shader *Shader, options *DrawTrianglesWithShaderOptions) { + i.copyCheck() + + if i.isDisposed() { + return + } + + if i.isSubImage() { + panic("ebiten: render to a subimage is not implemented (DrawTriangles)") + } + + if len(indices)%3 != 0 { + panic("ebiten: len(indices) % 3 must be 0") + } + if len(indices) > MaxIndicesNum { + panic("ebiten: len(indices) must be <= MaxIndicesNum") + } + + if options == nil { + options = &DrawTrianglesWithShaderOptions{} + } + + mode := driver.CompositeMode(options.CompositeMode) + + us := []interface{}{} + var firstImage *Image + for _, v := range options.Uniforms { + switch v := v.(type) { + case *Image: + us = append(us, v.buffered) + if firstImage == nil { + firstImage = v + } else { + b := v.Bounds() + us = append(us, []float32{ + float32(b.Min.X), + float32(b.Min.Y), + float32(b.Max.X), + float32(b.Max.Y), + }) + } + default: + us = append(us, v) + } + } + + var bx0, by0, bx1, by1 float32 + if firstImage != nil { + b := firstImage.Bounds() + bx0 = float32(b.Min.X) + by0 = float32(b.Min.Y) + bx1 = float32(b.Max.X) + by1 = float32(b.Max.Y) + } + + vs := make([]float32, len(vertices)*graphics.VertexFloatNum) + for i, v := range vertices { + vs[i*graphics.VertexFloatNum] = v.DstX + vs[i*graphics.VertexFloatNum+1] = v.DstY + vs[i*graphics.VertexFloatNum+2] = v.SrcX + vs[i*graphics.VertexFloatNum+3] = v.SrcY + vs[i*graphics.VertexFloatNum+4] = bx0 + vs[i*graphics.VertexFloatNum+5] = by0 + vs[i*graphics.VertexFloatNum+6] = bx1 + vs[i*graphics.VertexFloatNum+7] = by1 + vs[i*graphics.VertexFloatNum+8] = v.ColorR + vs[i*graphics.VertexFloatNum+9] = v.ColorG + vs[i*graphics.VertexFloatNum+10] = v.ColorB + vs[i*graphics.VertexFloatNum+11] = v.ColorA + } + is := make([]uint16, len(indices)) + copy(is, indices) + + i.buffered.DrawTriangles(nil, vs, is, nil, mode, driver.FilterNearest, driver.AddressClampToZero, shader.shader, us) +} + // SubImage returns an image representing the portion of the image p visible through r. The returned value shares pixels with the original image. // // The returned value is always *ebiten.Image. diff --git a/shader.go b/shader.go new file mode 100644 index 000000000..4b3bc898c --- /dev/null +++ b/shader.go @@ -0,0 +1,40 @@ +// 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 ebiten + +import ( + "github.com/hajimehoshi/ebiten/internal/buffered" + "github.com/hajimehoshi/ebiten/internal/shader" +) + +type Shader struct { + shader *buffered.Shader +} + +func NewShader(src []byte) (*Shader, error) { + s, err := shader.Compile(src) + if err != nil { + return nil, err + } + + return &Shader{ + shader: buffered.NewShader(s), + }, nil +} + +func (s *Shader) Dispose() { + s.shader.MarkDisposed() + s.shader = nil +}