Compare commits

...

7 Commits

Author SHA1 Message Date
Bertrand Jung
142f503456
Merge 5b54a1d8b7 into 88dae9c7d4 2024-04-13 14:21:42 +00:00
Zyko
5b54a1d8b7 Attempt at fixing directx 12 tests 2024-04-13 16:21:34 +02:00
Zyko
483059184f Debug image entry index 2024-04-13 14:56:59 +02:00
Zyko
dc6074bec9 Fixed stencil tests for directx 2024-04-13 14:23:17 +02:00
Zyko
dbe06ec468 Add a test when a single image is used with DrawMRT 2024-04-13 14:04:39 +02:00
Zyko
d706d38ada Add test with empty locations + fix directx 11 && 12 nil first argument 2024-04-13 13:56:13 +02:00
Zyko
0b53525808 Add a basic MRT test 2024-04-13 11:40:31 +02:00
8 changed files with 215 additions and 135 deletions

View File

@ -711,10 +711,8 @@ func (i *Image) DrawTrianglesShader(vertices []Vertex, indices []uint16, shader
//
// When the image i is disposed, DrawTrianglesShader does nothing.
func DrawTrianglesShaderMRT(dsts [graphics.ShaderDstImageCount]*Image, vertices []Vertex, indices []uint16, shader *Shader, options *DrawTrianglesShaderOptions) {
if dsts[0] == nil || dsts[0].isDisposed() {
panic("ebiten: the first destination image given to DrawTrianglesShaderMRT must not be nil or disposed")
}
var dstImgs [graphics.ShaderDstImageCount]*ui.Image
var firstDst *Image
for i, dst := range dsts {
if dst == nil {
continue
@ -723,6 +721,9 @@ func DrawTrianglesShaderMRT(dsts [graphics.ShaderDstImageCount]*Image, vertices
if dst.isDisposed() {
panic("ebiten: the destination images given to DrawTrianglesShaderMRT must not be disposed")
}
if firstDst == nil {
firstDst = dst
}
dstImgs[i] = dst.image
}
@ -754,7 +755,7 @@ func DrawTrianglesShaderMRT(dsts [graphics.ShaderDstImageCount]*Image, vertices
blend = options.CompositeMode.blend().internalBlend()
}
dst := dsts[0]
dst := firstDst
vs := dst.ensureTmpVertices(len(vertices) * graphics.VertexFloatCount)
src := options.Images[0]
for i, v := range vertices {

View File

@ -576,12 +576,14 @@ func drawTrianglesMRT(dsts [graphics.ShaderDstImageCount]*Image, srcs [graphics.
src.backend.sourceInThisFrame = true
}
var firstDst *Image
var dstImgs [graphics.ShaderDstImageCount]*graphicscommand.Image
for i, dst := range dsts {
if dst == nil {
continue
}
dst.ensureIsolatedFromSource(backends)
firstDst = dst
dstImgs[i] = dst.backend.image
}
@ -595,7 +597,7 @@ func drawTrianglesMRT(dsts [graphics.ShaderDstImageCount]*Image, srcs [graphics.
}
}
r := dsts[0].regionWithPadding()
r := firstDst.regionWithPadding()
// TODO: Check if dstRegion does not to violate the region.
dstRegion = dstRegion.Add(r.Min)

View File

@ -330,7 +330,14 @@ func (q *commandQueue) prependPreservedUniforms(uniforms []uint32, shader *Shade
copy(uniforms[graphics.PreservedUniformUint32Count:], origUniforms)
// Set the destination texture size.
dw, dh := dsts[0].InternalSize()
var firstDst *Image
for _, dst := range dsts {
if dst != nil {
firstDst = dst
break
}
}
dw, dh := firstDst.InternalSize()
uniforms[0] = math.Float32bits(float32(dw))
uniforms[1] = math.Float32bits(float32(dh))
uniformIndex := 2

View File

@ -517,30 +517,31 @@ func (g *graphics11) removeShader(s *shader11) {
func (g *graphics11) setAsRenderTargets(dsts []*image11, useStencil bool) error {
var rtvs []*_ID3D11RenderTargetView
for _, i := range dsts {
var dsv *_ID3D11DepthStencilView
for _, dst := range dsts {
// Ignore a nil image in case of MRT
if i == nil {
if dst == nil {
rtvs = append(rtvs, nil)
continue
}
if i.renderTargetView == nil {
rtv, err := g.device.CreateRenderTargetView(unsafe.Pointer(i.texture), nil)
if dst.renderTargetView == nil {
rtv, err := g.device.CreateRenderTargetView(unsafe.Pointer(dst.texture), nil)
if err != nil {
return err
}
i.renderTargetView = rtv
dst.renderTargetView = rtv
}
rtvs = append(rtvs, i.renderTargetView)
rtvs = append(rtvs, dst.renderTargetView)
if !useStencil {
if !useStencil || dsv != nil {
continue
}
if i.screen {
if dst.screen {
return fmt.Errorf("directx: a stencil buffer is not available for a screen image")
}
if i.stencil == nil {
w, h := i.internalSize()
if dst.stencil == nil {
w, h := dst.internalSize()
s, err := g.device.CreateTexture2D(&_D3D11_TEXTURE2D_DESC{
Width: uint32(w),
Height: uint32(h),
@ -559,20 +560,21 @@ func (g *graphics11) setAsRenderTargets(dsts []*image11, useStencil bool) error
if err != nil {
return err
}
i.stencil = s
dst.stencil = s
}
if i.stencilView == nil {
sv, err := g.device.CreateDepthStencilView(unsafe.Pointer(i.stencil), nil)
if dst.stencilView == nil {
sv, err := g.device.CreateDepthStencilView(unsafe.Pointer(dst.stencil), nil)
if err != nil {
return err
}
i.stencilView = sv
dst.stencilView = sv
}
dsv = dst.stencilView
}
g.deviceContext.OMSetRenderTargets(rtvs, dsts[0].stencilView)
g.deviceContext.OMSetRenderTargets(rtvs, dsv)
if useStencil {
g.deviceContext.ClearDepthStencilView(dsts[0].stencilView, uint8(_D3D11_CLEAR_STENCIL), 0, 0)
g.deviceContext.ClearDepthStencilView(dsv, uint8(_D3D11_CLEAR_STENCIL), 0, 0)
}
return nil
@ -585,7 +587,7 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
g.deviceContext.PSSetShaderResources(0, srvs[:])
var dsts [graphics.ShaderDstImageCount]*image11
var viewports [graphics.ShaderDstImageCount]_D3D11_VIEWPORT
var vp _D3D11_VIEWPORT
var targetCount int
firstTarget := -1
for i, id := range dstIDs {
@ -598,7 +600,7 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
}
dsts[i] = img
w, h := img.internalSize()
viewports[i] = _D3D11_VIEWPORT{
vp = _D3D11_VIEWPORT{
TopLeftX: 0,
TopLeftY: 0,
Width: float32(w),
@ -621,13 +623,12 @@ func (g *graphics11) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
// If the number of targets is more than one, or if the only target is the first one, then
// it is safe to assume that MRT is used.
// Also, it only matters in order to specify empty targets/viewports when not all slots are
// being filled.
// being filled, even though it's not a MRT scenario.
if targetCount > 1 || firstTarget > 0 {
targetCount = graphics.ShaderDstImageCount
}
g.deviceContext.RSSetViewports(viewports[:targetCount])
g.deviceContext.RSSetViewports([]_D3D11_VIEWPORT{vp})
if err := g.setAsRenderTargets(dsts[:targetCount], fillRule != graphicsdriver.FillAll); err != nil {
return err
}

View File

@ -1084,7 +1084,7 @@ func (g *graphics12) NewShader(program *shaderir.Program) (graphicsdriver.Shader
func (g *graphics12) setAsRenderTargets(dsts []*image12, useStencil bool) error {
var rtvs []_D3D12_CPU_DESCRIPTOR_HANDLE
var dsvPtr *_D3D12_CPU_DESCRIPTOR_HANDLE
var dsv *_D3D12_CPU_DESCRIPTOR_HANDLE
for i, img := range dsts {
// Ignore a nil image in case of MRT
@ -1127,18 +1127,18 @@ func (g *graphics12) setAsRenderTargets(dsts []*image12, useStencil bool) error
rtv := rtvBase
rtvs = append(rtvs, rtv)
if !useStencil || dsvPtr != nil {
if !useStencil || dsv != nil {
continue
}
if err := img.ensureDepthStencilView(g.device); err != nil {
return err
}
dsv, err := img.dsvDescriptorHeap.GetCPUDescriptorHandleForHeapStart()
sv, err := img.dsvDescriptorHeap.GetCPUDescriptorHandleForHeapStart()
if err != nil {
return err
}
dsvPtr = &dsv
dsv = &sv
}
if !useStencil {
@ -1147,8 +1147,8 @@ func (g *graphics12) setAsRenderTargets(dsts []*image12, useStencil bool) error
}
g.drawCommandList.OMSetStencilRef(0)
g.drawCommandList.OMSetRenderTargets(rtvs, false, dsvPtr)
g.drawCommandList.ClearDepthStencilView(*dsvPtr, _D3D12_CLEAR_FLAG_STENCIL, 0, 0, nil)
g.drawCommandList.OMSetRenderTargets(rtvs, false, dsv)
g.drawCommandList.ClearDepthStencilView(*dsv, _D3D12_CLEAR_FLAG_STENCIL, 0, 0, nil)
return nil
}
@ -1177,7 +1177,7 @@ func (g *graphics12) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
var resourceBarriers []_D3D12_RESOURCE_BARRIER_Transition
var dsts [graphics.ShaderDstImageCount]*image12
var viewports [graphics.ShaderDstImageCount]_D3D12_VIEWPORT
var vp _D3D12_VIEWPORT
var targetCount int
firstTarget := -1
for i, id := range dstIDs {
@ -1190,7 +1190,7 @@ func (g *graphics12) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
}
dsts[i] = img
w, h := img.internalSize()
viewports[i] = _D3D12_VIEWPORT{
vp = _D3D12_VIEWPORT{
TopLeftX: 0,
TopLeftY: 0,
Width: float32(w),
@ -1225,14 +1225,12 @@ func (g *graphics12) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
// If the number of targets is more than one, or if the only target is the first one, then
// it is safe to assume that MRT is used.
// Also, it only matters in order to specify empty targets/viewports when not all slots are
// being filled.
// being filled, even though it's not a MRT scenario.
usesMRT := targetCount > 1 || firstTarget > 0
if usesMRT {
targetCount = graphics.ShaderDstImageCount
}
g.drawCommandList.RSSetViewports(viewports[:targetCount])
if err := g.setAsRenderTargets(dsts[:targetCount], fillRule != graphicsdriver.FillAll); err != nil {
return err
}
@ -1242,6 +1240,8 @@ func (g *graphics12) DrawTriangles(dstIDs [graphics.ShaderDstImageCount]graphics
g.needFlushDrawCommandList = true
g.drawCommandList.RSSetViewports([]_D3D12_VIEWPORT{vp})
g.drawCommandList.IASetPrimitiveTopology(_D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST)
g.drawCommandList.IASetVertexBuffers(0, []_D3D12_VERTEX_BUFFER_VIEW{
{

View File

@ -70,7 +70,7 @@ func (i *image12) disposeImpl() {
func (i *image12) ReadPixels(args []graphicsdriver.PixelsArgs) error {
if i.screen {
return errors.New("directx: Pixels cannot be called on the screen")
return errors.New("directx: ReadPixels cannot be called on the screen")
}
if err := i.graphics.flushCommandList(i.graphics.drawCommandList); err != nil {

View File

@ -484,6 +484,18 @@ func (p *pipelineStates) newPipelineState(device *_ID3D12Device, vsh, psh *_ID3D
}
// Create a pipeline state.
rtBlendDesc := _D3D12_RENDER_TARGET_BLEND_DESC{
BlendEnable: 1,
LogicOpEnable: 0,
SrcBlend: blendFactorToBlend12(blend.BlendFactorSourceRGB, false),
DestBlend: blendFactorToBlend12(blend.BlendFactorDestinationRGB, false),
BlendOp: blendOperationToBlendOp12(blend.BlendOperationRGB),
SrcBlendAlpha: blendFactorToBlend12(blend.BlendFactorSourceAlpha, true),
DestBlendAlpha: blendFactorToBlend12(blend.BlendFactorDestinationAlpha, true),
BlendOpAlpha: blendOperationToBlendOp12(blend.BlendOperationAlpha),
LogicOp: _D3D12_LOGIC_OP_NOOP,
RenderTargetWriteMask: writeMask,
}
psoDesc := _D3D12_GRAPHICS_PIPELINE_STATE_DESC{
pRootSignature: rootSignature,
VS: _D3D12_SHADER_BYTECODE{
@ -498,102 +510,7 @@ func (p *pipelineStates) newPipelineState(device *_ID3D12Device, vsh, psh *_ID3D
AlphaToCoverageEnable: 0,
IndependentBlendEnable: 0,
RenderTarget: [8]_D3D12_RENDER_TARGET_BLEND_DESC{
{
BlendEnable: 1,
LogicOpEnable: 0,
SrcBlend: blendFactorToBlend12(blend.BlendFactorSourceRGB, false),
DestBlend: blendFactorToBlend12(blend.BlendFactorDestinationRGB, false),
BlendOp: blendOperationToBlendOp12(blend.BlendOperationRGB),
SrcBlendAlpha: blendFactorToBlend12(blend.BlendFactorSourceAlpha, true),
DestBlendAlpha: blendFactorToBlend12(blend.BlendFactorDestinationAlpha, true),
BlendOpAlpha: blendOperationToBlendOp12(blend.BlendOperationAlpha),
LogicOp: _D3D12_LOGIC_OP_NOOP,
RenderTargetWriteMask: writeMask,
},
{
BlendEnable: 1,
LogicOpEnable: 0,
SrcBlend: blendFactorToBlend12(blend.BlendFactorSourceRGB, false),
DestBlend: blendFactorToBlend12(blend.BlendFactorDestinationRGB, false),
BlendOp: blendOperationToBlendOp12(blend.BlendOperationRGB),
SrcBlendAlpha: blendFactorToBlend12(blend.BlendFactorSourceAlpha, true),
DestBlendAlpha: blendFactorToBlend12(blend.BlendFactorDestinationAlpha, true),
BlendOpAlpha: blendOperationToBlendOp12(blend.BlendOperationAlpha),
LogicOp: _D3D12_LOGIC_OP_NOOP,
RenderTargetWriteMask: writeMask,
},
{
BlendEnable: 1,
LogicOpEnable: 0,
SrcBlend: blendFactorToBlend12(blend.BlendFactorSourceRGB, false),
DestBlend: blendFactorToBlend12(blend.BlendFactorDestinationRGB, false),
BlendOp: blendOperationToBlendOp12(blend.BlendOperationRGB),
SrcBlendAlpha: blendFactorToBlend12(blend.BlendFactorSourceAlpha, true),
DestBlendAlpha: blendFactorToBlend12(blend.BlendFactorDestinationAlpha, true),
BlendOpAlpha: blendOperationToBlendOp12(blend.BlendOperationAlpha),
LogicOp: _D3D12_LOGIC_OP_NOOP,
RenderTargetWriteMask: writeMask,
},
{
BlendEnable: 1,
LogicOpEnable: 0,
SrcBlend: blendFactorToBlend12(blend.BlendFactorSourceRGB, false),
DestBlend: blendFactorToBlend12(blend.BlendFactorDestinationRGB, false),
BlendOp: blendOperationToBlendOp12(blend.BlendOperationRGB),
SrcBlendAlpha: blendFactorToBlend12(blend.BlendFactorSourceAlpha, true),
DestBlendAlpha: blendFactorToBlend12(blend.BlendFactorDestinationAlpha, true),
BlendOpAlpha: blendOperationToBlendOp12(blend.BlendOperationAlpha),
LogicOp: _D3D12_LOGIC_OP_NOOP,
RenderTargetWriteMask: writeMask,
},
{
BlendEnable: 1,
LogicOpEnable: 0,
SrcBlend: blendFactorToBlend12(blend.BlendFactorSourceRGB, false),
DestBlend: blendFactorToBlend12(blend.BlendFactorDestinationRGB, false),
BlendOp: blendOperationToBlendOp12(blend.BlendOperationRGB),
SrcBlendAlpha: blendFactorToBlend12(blend.BlendFactorSourceAlpha, true),
DestBlendAlpha: blendFactorToBlend12(blend.BlendFactorDestinationAlpha, true),
BlendOpAlpha: blendOperationToBlendOp12(blend.BlendOperationAlpha),
LogicOp: _D3D12_LOGIC_OP_NOOP,
RenderTargetWriteMask: writeMask,
},
{
BlendEnable: 1,
LogicOpEnable: 0,
SrcBlend: blendFactorToBlend12(blend.BlendFactorSourceRGB, false),
DestBlend: blendFactorToBlend12(blend.BlendFactorDestinationRGB, false),
BlendOp: blendOperationToBlendOp12(blend.BlendOperationRGB),
SrcBlendAlpha: blendFactorToBlend12(blend.BlendFactorSourceAlpha, true),
DestBlendAlpha: blendFactorToBlend12(blend.BlendFactorDestinationAlpha, true),
BlendOpAlpha: blendOperationToBlendOp12(blend.BlendOperationAlpha),
LogicOp: _D3D12_LOGIC_OP_NOOP,
RenderTargetWriteMask: writeMask,
},
{
BlendEnable: 1,
LogicOpEnable: 0,
SrcBlend: blendFactorToBlend12(blend.BlendFactorSourceRGB, false),
DestBlend: blendFactorToBlend12(blend.BlendFactorDestinationRGB, false),
BlendOp: blendOperationToBlendOp12(blend.BlendOperationRGB),
SrcBlendAlpha: blendFactorToBlend12(blend.BlendFactorSourceAlpha, true),
DestBlendAlpha: blendFactorToBlend12(blend.BlendFactorDestinationAlpha, true),
BlendOpAlpha: blendOperationToBlendOp12(blend.BlendOperationAlpha),
LogicOp: _D3D12_LOGIC_OP_NOOP,
RenderTargetWriteMask: writeMask,
},
{
BlendEnable: 1,
LogicOpEnable: 0,
SrcBlend: blendFactorToBlend12(blend.BlendFactorSourceRGB, false),
DestBlend: blendFactorToBlend12(blend.BlendFactorDestinationRGB, false),
BlendOp: blendOperationToBlendOp12(blend.BlendOperationRGB),
SrcBlendAlpha: blendFactorToBlend12(blend.BlendFactorSourceAlpha, true),
DestBlendAlpha: blendFactorToBlend12(blend.BlendFactorDestinationAlpha, true),
BlendOpAlpha: blendOperationToBlendOp12(blend.BlendOperationAlpha),
LogicOp: _D3D12_LOGIC_OP_NOOP,
RenderTargetWriteMask: writeMask,
},
rtBlendDesc, // TODO: need to fill them all?
},
},
SampleMask: math.MaxUint32,

View File

@ -2595,3 +2595,155 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 {
}
}
}
// Issue #2930
func TestShaderMRT(t *testing.T) {
const w, h = 16, 16
s, err := ebiten.NewShader([]byte(`//kage:unit pixels
package main
func Fragment(dstPos vec4, srcPos vec2, color vec4) (vec4, vec4, vec4, vec4, vec4, vec4, vec4, vec4) {
return vec4(1, 0, 0, 1),
vec4(0, 1, 0, 1),
vec4(0, 0, 1, 1),
vec4(1, 0, 1, 1),
vec4(1, 1, 0, 1),
vec4(0, 1, 1, 1),
vec4(1, 1, 1, 1),
vec4(1, 1, 1, 0)
}
`))
if err != nil {
t.Fatal(err)
}
bounds := image.Rect(0, 0, w, h)
opts := &ebiten.NewImageOptions{
Unmanaged: true,
}
vertices := []ebiten.Vertex{
{
DstX: 0,
DstY: 0,
},
{
DstX: w,
DstY: 0,
},
{
DstX: 0,
DstY: h,
},
{
DstX: w,
DstY: h,
},
}
indices := []uint16{0, 1, 2, 1, 2, 3}
t.Run("8 locations", func(t *testing.T) {
imgs := [8]*ebiten.Image{
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
}
wantColors := [8]color.RGBA{
{R: 0xff, G: 0, B: 0, A: 0xff},
{R: 0, G: 0xff, B: 0, A: 0xff},
{R: 0, G: 0, B: 0xff, A: 0xff},
{R: 0xff, G: 0, B: 0xff, A: 0xff},
{R: 0xff, G: 0xff, B: 0, A: 0xff},
{R: 0, G: 0xff, B: 0xff, A: 0xff},
{R: 0xff, G: 0xff, B: 0xff, A: 0xff},
{R: 0xff, G: 0xff, B: 0xff, A: 0},
}
ebiten.DrawTrianglesShaderMRT(imgs, vertices, indices, s, nil)
for k, dst := range imgs {
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
got := dst.At(i, j).(color.RGBA)
want := wantColors[k]
if !sameColors(got, want, 1) {
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
}
})
/*t.Run("Empty locations", func(t *testing.T) {
imgs := [8]*ebiten.Image{
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
}
wantColors := [8]color.RGBA{
{},
{R: 0, G: 0xff, B: 0, A: 0xff},
{},
{R: 0xff, G: 0, B: 0xff, A: 0xff},
{},
{R: 0, G: 0xff, B: 0xff, A: 0xff},
{},
{R: 0xff, G: 0xff, B: 0xff, A: 0},
}
dsts := [8]*ebiten.Image{
nil, imgs[1], nil, imgs[3], nil, imgs[5], nil, imgs[7],
}
ebiten.DrawTrianglesShaderMRT(dsts, vertices, indices, s, nil)
for k, dst := range imgs {
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
got := dst.At(i, j).(color.RGBA)
want := wantColors[k]
if !sameColors(got, want, 1) {
t.Errorf("%d dst.At(%d, %d): got: %v, want: %v", k, i, j, got, want)
}
}
}
}
})*/
t.Run("1 location (first slot)", func(t *testing.T) {
imgs := [8]*ebiten.Image{
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
ebiten.NewImageWithOptions(bounds, opts),
}
wantColors := [8]color.RGBA{
{R: 0xff, G: 0, B: 0, A: 0xff},
{}, {}, {}, {}, {}, {}, {},
}
dsts := [8]*ebiten.Image{
imgs[0], nil, nil, nil, nil, nil, nil, nil,
}
ebiten.DrawTrianglesShaderMRT(dsts, vertices, indices, s, nil)
for k, dst := range imgs {
for j := 0; j < h; j++ {
for i := 0; i < w; i++ {
got := dst.At(i, j).(color.RGBA)
want := wantColors[k]
if !sameColors(got, want, 1) {
t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want)
}
}
}
}
})
}