diff --git a/image.go b/image.go index 878679618..cdbf69fd9 100644 --- a/image.go +++ b/image.go @@ -438,6 +438,9 @@ func (i *Image) DrawTriangles(vertices []Vertex, indices []uint16, img *Image, o if int(idx) >= len(vertices) { panic(fmt.Sprintf("ebiten: indices[%d] must be less than len(vertices) (%d) but was %d", i, len(vertices), idx)) } + if idx >= MaxVerticesCount { + panic(fmt.Sprintf("ebiten: indices[%d] must be less than MaxVerticesCount %d but was %d", i, MaxVerticesCount, idx)) + } } if options == nil { @@ -600,6 +603,9 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader if int(idx) >= len(vertices) { panic(fmt.Sprintf("ebiten: indices[%d] must be less than len(vertices) (%d) but was %d", i, len(vertices), idx)) } + if idx >= MaxVerticesCount { + panic(fmt.Sprintf("ebiten: indices[%d] must be less than MaxVerticesCount %d but was %d", i, MaxVerticesCount, idx)) + } } if options == nil { diff --git a/image_test.go b/image_test.go index c8e2493fc..155804f73 100644 --- a/image_test.go +++ b/image_test.go @@ -2805,7 +2805,7 @@ func BenchmarkColorMScale(b *testing.B) { } } -func TestImageMoreIndicesThanMaxUint16(t *testing.T) { +func TestIndicesOverflow(t *testing.T) { const ( w = 16 h = 16 @@ -2817,10 +2817,10 @@ func TestImageMoreIndicesThanMaxUint16(t *testing.T) { op := &ebiten.DrawTrianglesOptions{} vs := make([]ebiten.Vertex, 3) - is := make([]uint16, 65538) + is := make([]uint16, graphics.MaxVerticesCount/3*3) dst.DrawTriangles(vs, is, src, op) - // The next draw call should work well (and this is likely batched). + // Cause an overflow for indices. vs = []ebiten.Vertex{ { DstX: 0, @@ -2877,7 +2877,7 @@ func TestImageMoreIndicesThanMaxUint16(t *testing.T) { } } -func TestImageMoreVerticesThanMaxUint16(t *testing.T) { +func TestVerticesOverflow(t *testing.T) { const ( w = 16 h = 16 @@ -2888,11 +2888,11 @@ func TestImageMoreVerticesThanMaxUint16(t *testing.T) { src.Fill(color.White) op := &ebiten.DrawTrianglesOptions{} - vs := make([]ebiten.Vertex, math.MaxUint16+1) + vs := make([]ebiten.Vertex, graphics.MaxVerticesCount-1) is := make([]uint16, 3) dst.DrawTriangles(vs, is, src, op) - // The next draw call should work well (and this is likely batched). + // Cause an overflow for vertices. vs = []ebiten.Vertex{ { DstX: 0, @@ -2949,6 +2949,26 @@ func TestImageMoreVerticesThanMaxUint16(t *testing.T) { } } +func TestTooManyVertices(t *testing.T) { + const ( + w = 16 + h = 16 + ) + + dst := ebiten.NewImage(w, h) + src := ebiten.NewImage(w, h) + src.Fill(color.White) + + op := &ebiten.DrawTrianglesOptions{} + vs := make([]ebiten.Vertex, graphics.MaxVerticesCount+1) + is := make([]uint16, 3) + dst.DrawTriangles(vs, is, src, op) + + // Force to cause flushing the graphics commands. + // Confirm this doesn't freeze. + dst.At(0, 0) +} + func TestImageNewImageFromEbitenImage(t *testing.T) { const ( w = 16 @@ -4298,6 +4318,48 @@ func TestImageDrawTrianglesShaderWithGreaterIndexThanVerticesCount(t *testing.T) dst.DrawTrianglesShader(vs, is, shader, nil) } +// Issue #2611 +func TestImageDrawTrianglesWithTooBigIndex(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("DrawTriangles must panic but not") + } + }() + + const w, h = 16, 16 + dst := ebiten.NewImage(w, h) + src := ebiten.NewImage(w, h) + + vs := make([]ebiten.Vertex, ebiten.MaxVerticesCount+1) + is := []uint16{0, 1, 2, 1, 2, ebiten.MaxVerticesCount} + dst.DrawTriangles(vs, is, src, nil) +} + +// Issue #2611 +func TestImageDrawTrianglesShaderWithTooBigIndex(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Errorf("DrawTrianglesShader must panic but not") + } + }() + + const w, h = 16, 16 + dst := ebiten.NewImage(w, h) + + vs := make([]ebiten.Vertex, ebiten.MaxVerticesCount+1) + is := []uint16{0, 1, 2, 1, 2, ebiten.MaxVerticesCount} + shader, err := ebiten.NewShader([]byte(` + package main + func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 { + return color + } + `)) + if err != nil { + t.Fatalf("could not compile shader: %v", err) + } + dst.DrawTrianglesShader(vs, is, shader, nil) +} + // Issue #2733 func TestImageGeoMAfterDraw(t *testing.T) { src := ebiten.NewImage(1, 1) diff --git a/internal/graphics/vertex.go b/internal/graphics/vertex.go index 545c07624..074a36292 100644 --- a/internal/graphics/vertex.go +++ b/internal/graphics/vertex.go @@ -46,10 +46,12 @@ const ( VertexFloatCount = 8 // MaxVerticesCount is the maximum number of vertices for one draw call. - // This value is 2^32 - 1, as the index type is uint32. - // This value cannot be exactly 2^32 especially with WebGL 2, as 2^32th vertex is not rendered correctly. + // This value is 2^16 - 1 = 65535, as the index type is uint16. + // This value cannot be exactly 2^16 == 65536 especially with WebGL 2, as 65536th vertex is not rendered correctly. // See https://registry.khronos.org/webgl/specs/latest/2.0/#5.18 . - MaxVerticesCount = math.MaxUint32 + // + // TODO: Use MaxUint32 and move these values to internal/graphicscommand (#2612) + MaxVerticesCount = math.MaxUint16 MaxVertexFloatsCount = MaxVerticesCount * VertexFloatCount ) diff --git a/internal/ui/image.go b/internal/ui/image.go index 77f7efa76..e73675f12 100644 --- a/internal/ui/image.go +++ b/internal/ui/image.go @@ -139,7 +139,8 @@ func (i *Image) WritePixels(pix []byte, region image.Rectangle) { copy(clr[:], pix) i.dotsBuffer[region.Min] = clr - if len(i.dotsBuffer) >= 10000 { + // One square requires 6 indices (= 2 triangles). + if len(i.dotsBuffer) >= graphics.MaxVerticesCount/6 { i.flushDotsBufferIfNeeded() } return