diff --git a/internal/graphicsdriver/directx/shader_windows.go b/internal/graphicsdriver/directx/shader_windows.go index cec20d1a7..4af2cff52 100644 --- a/internal/graphicsdriver/directx/shader_windows.go +++ b/internal/graphicsdriver/directx/shader_windows.go @@ -199,6 +199,9 @@ func constantBufferSize(uniformTypes []shaderir.Type, uniformOffsets []int) int } func adjustUniforms(uniformTypes []shaderir.Type, uniformOffsets []int, uniforms []uint32) []uint32 { + // Note that HLSL's matrices are row-major, while GLSL and MSL are column-major. + // Transpose matrices so that users can access matrix indices in the same way as GLSL and MSL. + var fs []uint32 var idx int for i, typ := range uniformTypes { diff --git a/internal/graphicsdriver/metal/graphics_darwin.go b/internal/graphicsdriver/metal/graphics_darwin.go index 69c99c366..0b0e57c5c 100644 --- a/internal/graphicsdriver/metal/graphics_darwin.go +++ b/internal/graphicsdriver/metal/graphics_darwin.go @@ -909,11 +909,11 @@ func adjustUniformVariablesLayout(uniformTypes []shaderir.Type, uniforms []uint3 values = append(values, 0) case shaderir.Mat4: values = fillZerosToFitAlignment(values, 4) - u := uniforms[idx : idx+16] if i == graphics.ProjectionMatrixUniformVariableIndex { // In Metal, the NDC's Y direction (upward) and the framebuffer's Y direction (downward) don't // match. Then, the Y direction must be inverted. // Invert the sign bits as float32 values. + u := uniforms[idx : idx+16] values = append(values, u[0], u[1]^uint32(1<<31), u[2], u[3], u[4], u[5]^uint32(1<<31), u[6], u[7], diff --git a/shader_test.go b/shader_test.go index 954838ee5..b1b8a5dbc 100644 --- a/shader_test.go +++ b/shader_test.go @@ -1097,6 +1097,46 @@ func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 { } } +func TestShaderUniformMatrixIndexer(t *testing.T) { + const w, h = 16, 16 + + dst := ebiten.NewImage(w, h) + s, err := ebiten.NewShader([]byte(`//kage:unit pixels + +package main + +var Mat4 mat4 + +func Fragment(dstPos vec4, srcPos vec2, color vec4) vec4 { + return Mat4[1][2] * vec4(1) +} +`)) + if err != nil { + t.Fatal(err) + } + + op := &ebiten.DrawRectShaderOptions{} + op.Uniforms = map[string]any{ + "Mat4": []float32{ + 1.0 / 256.0, 2.0 / 256.0, 3.0 / 256.0, 4.0 / 256.0, + 5.0 / 256.0, 6.0 / 256.0, 7.0 / 256.0, 8.0 / 256.0, + 9.0 / 256.0, 10.0 / 256.0, 11.0 / 256.0, 12.0 / 256.0, + 13.0 / 256.0, 14.0 / 256.0, 15.0 / 256.0, 16.0 / 256.0, + }, + } + dst.DrawRectShader(w, h, s, op) + + for j := 0; j < h; j++ { + for i := 0; i < w; i++ { + got := dst.At(i, j).(color.RGBA) + want := color.RGBA{R: 7, G: 7, B: 7, A: 7} + if !sameColors(got, want, 2) { + t.Errorf("dst.At(%d, %d): got: %v, want: %v", i, j, got, want) + } + } + } +} + func TestShaderOptionsNegativeBounds(t *testing.T) { const w, h = 16, 16