diff --git a/example/shapes/main.go b/example/shapes/main.go new file mode 100644 index 000000000..bbf5c08b3 --- /dev/null +++ b/example/shapes/main.go @@ -0,0 +1,74 @@ +// Copyright 2015 Hajime Hoshi +// +// 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 main + +import ( + "github.com/hajimehoshi/ebiten" + "image" + "image/color" + "log" + "math/rand" +) + +const ( + screenWidth = 320 + screenHeight = 240 +) + +var rectsToDraw = make([]image.Rectangle, 100) + +func min(a, b int) int { + if a < b { + return a + } + return b +} + +func max(a, b int) int { + if a < b { + return b + } + return a +} + +func init() { + for i, _ := range rectsToDraw { + x0, x1 := rand.Intn(screenWidth), rand.Intn(screenWidth) + y0, y1 := rand.Intn(screenHeight), rand.Intn(screenHeight) + rectsToDraw[i] = image.Rect(min(x0, x1), min(y0, y1), max(x0, x1), max(y0, y1)) + } +} + +type rects []image.Rectangle + +func (r rects) Len() int { + return len(r) +} + +func (r rects) Points(i int) (x0, y0, x1, y1 int) { + rect := &r[i] + return rect.Min.X, rect.Min.Y, rect.Max.X, rect.Max.Y +} + +func update(screen *ebiten.Image) error { + screen.DrawRects(color.NRGBA{0x80, 0x80, 0xff, 0x80}, rects(rectsToDraw)) + return nil +} + +func main() { + if err := ebiten.Run(update, screenWidth, screenHeight, 2, "Shapes (Ebiten Demo)"); err != nil { + log.Fatal(err) + } +} diff --git a/image.go b/image.go index 25d02cd11..925d0c746 100644 --- a/image.go +++ b/image.go @@ -94,6 +94,36 @@ func (i *Image) DrawImage(image *Image, options *DrawImageOptions) (err error) { return } +// A Rects represents the set of rectangles. +type Rects interface { + Len() int + Points(i int) (x0, y0, x1, y1 int) +} + +type rectVertexQuads struct { + Rects +} + +func (l rectVertexQuads) Len() int { + return l.Rects.Len() +} + +func (l rectVertexQuads) Vertex(i int) (x0, y0, x1, y1 float32) { + ix0, iy0, ix1, iy1 := l.Rects.Points(i) + return float32(ix0), float32(iy0), float32(ix1), float32(iy1) +} + +// DrawRects draws rectangles on the image. +// +// NOTE: This method is experimental. +func (i *Image) DrawRects(clr color.Color, rects Rects) (err error) { + r, g, b, a := internal.RGBA(clr) + ui.Use(func(c *opengl.Context) { + err = i.framebuffer.DrawRects(c, r, g, b, a, &rectVertexQuads{rects}) + }) + return +} + // Bounds returns the bounds of the image. func (i *Image) Bounds() image.Rectangle { w, h := i.Size() diff --git a/internal/graphics/framebuffer.go b/internal/graphics/framebuffer.go index d4e3915d4..cb491b18a 100644 --- a/internal/graphics/framebuffer.go +++ b/internal/graphics/framebuffer.go @@ -109,6 +109,19 @@ func (f *Framebuffer) DrawTexture(c *opengl.Context, t *Texture, quads TextureQu if err := f.setAsViewport(c); err != nil { return err } - projectionMatrix := f.projectionMatrix() - return shader.DrawTexture(c, t.native, projectionMatrix, quads, geo, clr) + p := f.projectionMatrix() + return shader.DrawTexture(c, t.native, p, quads, geo, clr) +} + +type VertexQuads interface { + Len() int + Vertex(i int) (x0, y0, x1, y1 float32) +} + +func (f *Framebuffer) DrawRects(c *opengl.Context, r, g, b, a float64, quads VertexQuads) error { + if err := f.setAsViewport(c); err != nil { + return err + } + p := f.projectionMatrix() + return shader.DrawRects(c, p, r, g, b, a, quads) } diff --git a/internal/graphics/internal/shader/drawtexture.go b/internal/graphics/internal/shader/draw.go similarity index 70% rename from internal/graphics/internal/shader/drawtexture.go rename to internal/graphics/internal/shader/draw.go index fc6968d60..a50d47872 100644 --- a/internal/graphics/internal/shader/drawtexture.go +++ b/internal/graphics/internal/shader/draw.go @@ -43,6 +43,7 @@ func DrawTexture(c *opengl.Context, texture opengl.Texture, projectionMatrix *[4 // TODO: WebGL doesn't seem to have Check gl.MAX_ELEMENTS_VERTICES or gl.MAX_ELEMENTS_INDICES so far. // Let's use them to compare to len(quads) in the future. + // TODO: Unify stride or other consts const stride = 4 * 4 if !initialized { @@ -80,3 +81,54 @@ func DrawTexture(c *opengl.Context, texture opengl.Texture, projectionMatrix *[4 c.DrawElements(6 * len(vertices) / 16) return nil } + +type VertexQuads interface { + Len() int + Vertex(i int) (x0, y0, x1, y1 float32) +} + +func max(a, b float32) float32 { + if a < b { + return b + } + return a +} + +func DrawRects(c *opengl.Context, projectionMatrix *[4][4]float64, r, g, b, a float64, quads VertexQuads) error { + const stride = 4 * 4 + + if !initialized { + if err := initialize(c); err != nil { + return err + } + initialized = true + } + + if quads.Len() == 0 { + return nil + } + + f := useProgramRect(c, glMatrix(projectionMatrix), r, g, b, a) + defer f.FinishProgram() + + vertices := make([]float32, 0, stride*quads.Len()) + for i := 0; i < quads.Len(); i++ { + x0, y0, x1, y1 := quads.Vertex(i) + if x0 == x1 || y0 == y1 { + continue + } + vertices = append(vertices, + x0, y0, 0, 0, + x1, y0, 1, 0, + x0, y1, 0, 1, + x1, y1, 1, 1, + ) + } + if len(vertices) == 0 { + return nil + } + c.BufferSubData(c.ArrayBuffer, vertices) + c.DrawElements(6 * len(vertices) / 16) + + return nil +} diff --git a/internal/graphics/internal/shader/program.go b/internal/graphics/internal/shader/program.go index 7dc6be8e0..268753b98 100644 --- a/internal/graphics/internal/shader/program.go +++ b/internal/graphics/internal/shader/program.go @@ -18,7 +18,10 @@ import ( "github.com/hajimehoshi/ebiten/internal/opengl" ) -var programTexture opengl.Program +var ( + programTexture opengl.Program + programRect opengl.Program +) func initialize(c *opengl.Context) error { const size = 10000 @@ -36,11 +39,24 @@ func initialize(c *opengl.Context) error { } defer c.DeleteShader(shaderFragmentTextureNative) - shaders := []opengl.Shader{ + shaderFragmentRectNative, err := c.NewShader(c.FragmentShader, shader(c, shaderFragmentRect)) + if err != nil { + return err + } + defer c.DeleteShader(shaderFragmentRectNative) + + programTexture, err = c.NewProgram([]opengl.Shader{ shaderVertexNative, shaderFragmentTextureNative, + }) + if err != nil { + return err } - programTexture, err = c.NewProgram(shaders) + + programRect, err = c.NewProgram([]opengl.Shader{ + shaderVertexNative, + shaderFragmentRectNative, + }) if err != nil { return err } @@ -132,3 +148,38 @@ func useProgramTexture(c *opengl.Context, projectionMatrix []float32, texture op c.DisableVertexAttribArray(program, "vertex") } } + +func useProgramRect(c *opengl.Context, projectionMatrix []float32, r, g, b, a float64) programFinisher { + const float32Size = 4 + const stride = 4 * 4 + + if lastProgram != programRect { + c.UseProgram(programRect) + lastProgram = programRect + } + program := programRect + + c.UniformFloats(program, "projection_matrix", projectionMatrix) + + glModelviewMatrix := []float32{ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + } + c.UniformFloats(program, "modelview_matrix", glModelviewMatrix) + + clr := []float32{float32(r), float32(g), float32(b), float32(a)} + c.UniformFloats(program, "color", clr) + + c.EnableVertexAttribArray(program, "vertex") + c.EnableVertexAttribArray(program, "tex_coord") + + c.VertexAttribPointer(program, "vertex", stride, uintptr(float32Size*0)) + c.VertexAttribPointer(program, "tex_coord", stride, uintptr(float32Size*2)) + + return func() { + c.DisableVertexAttribArray(program, "tex_coord") + c.DisableVertexAttribArray(program, "vertex") + } +} diff --git a/internal/graphics/internal/shader/shader.go b/internal/graphics/internal/shader/shader.go index a863f16b5..3457a7f2c 100644 --- a/internal/graphics/internal/shader/shader.go +++ b/internal/graphics/internal/shader/shader.go @@ -24,6 +24,7 @@ type shaderId int const ( shaderVertex shaderId = iota shaderFragmentTexture + shaderFragmentRect ) func shader(c *opengl.Context, id shaderId) string { @@ -69,5 +70,13 @@ void main(void) { gl_FragColor = color; } +`, + shaderFragmentRect: ` +uniform lowp vec4 color; +varying highp vec2 vertex_out_tex_coord; + +void main(void) { + gl_FragColor = color; +} `, }