diff --git a/image.go b/image.go index 6ff6b5d85..1eacf5bc3 100644 --- a/image.go +++ b/image.go @@ -505,7 +505,7 @@ type DrawTrianglesShaderOptions struct { // Uniforms is a set of uniform variables for the shader. // The keys are the names of the uniform variables. - // The values must be float or []float. + // The values must be a numeric type or a slice of a numeric type. // If the uniform variable type is an array, a vector or a matrix, // you have to specify linearly flattened values as a slice. // For example, if the uniform variable type is [4]vec4, the number of the slice values will be 16. @@ -665,7 +665,7 @@ type DrawRectShaderOptions struct { // Uniforms is a set of uniform variables for the shader. // The keys are the names of the uniform variables. - // The values must be float or []float. + // The values must be a numeric type or a slice of a numeric type. // If the uniform variable type is an array, a vector or a matrix, // you have to specify linearly flattened values as a slice. // For example, if the uniform variable type is [4]vec4, the number of the slice values will be 16. diff --git a/internal/graphicsdriver/directx/graphics_windows.go b/internal/graphicsdriver/directx/graphics_windows.go index 97460fed7..20275abd0 100644 --- a/internal/graphicsdriver/directx/graphics_windows.go +++ b/internal/graphicsdriver/directx/graphics_windows.go @@ -1732,6 +1732,12 @@ func (s *Shader) flattenUniforms(uniforms [][]uint32) []uint32 { } else { fs = append(fs, 0) } + case shaderir.Int: + if u != nil { + fs = append(fs, u...) + } else { + fs = append(fs, 0) + } case shaderir.Vec2: if u != nil { fs = append(fs, u...) @@ -1806,6 +1812,17 @@ func (s *Shader) flattenUniforms(uniforms [][]uint32) []uint32 { } else { fs = append(fs, make([]uint32, (t.Length-1)*4+1)...) } + case shaderir.Int: + if u != nil { + for j := 0; j < t.Length; j++ { + fs = append(fs, u[j]) + if j < t.Length-1 { + fs = append(fs, 0, 0, 0) + } + } + } else { + fs = append(fs, make([]uint32, (t.Length-1)*4+1)...) + } case shaderir.Vec2: if u != nil { for j := 0; j < t.Length; j++ { diff --git a/internal/graphicsdriver/opengl/context_gl.go b/internal/graphicsdriver/opengl/context_gl.go index aeb234f8a..1e98633df 100644 --- a/internal/graphicsdriver/opengl/context_gl.go +++ b/internal/graphicsdriver/opengl/context_gl.go @@ -375,7 +375,7 @@ func (c *context) uniformInt(p program, location string, v int) bool { return true } -func (c *context) uniformFloats(p program, location string, v []float32, typ shaderir.Type) bool { +func (c *context) uniforms(p program, location string, v []uint32, typ shaderir.Type) bool { l := int32(c.locationCache.GetUniformLocation(c, p, location)) if l == invalidUniform { return false @@ -391,6 +391,8 @@ func (c *context) uniformFloats(p program, location string, v []float32, typ sha switch base { case shaderir.Float: gl.Uniform1fv(l, len, (*float32)(gl.Ptr(v))) + case shaderir.Int: + gl.Uniform1iv(l, len, (*int32)(gl.Ptr(v))) case shaderir.Vec2: gl.Uniform2fv(l, len, (*float32)(gl.Ptr(v))) case shaderir.Vec3: diff --git a/internal/graphicsdriver/opengl/context_gles.go b/internal/graphicsdriver/opengl/context_gles.go index d5208c93e..224168721 100644 --- a/internal/graphicsdriver/opengl/context_gles.go +++ b/internal/graphicsdriver/opengl/context_gles.go @@ -344,7 +344,7 @@ func (c *context) uniformInt(p program, location string, v int) bool { return true } -func (c *context) uniformFloats(p program, location string, v []float32, typ shaderir.Type) bool { +func (c *context) uniforms(p program, location string, v []uint32, typ shaderir.Type) bool { l := c.locationCache.GetUniformLocation(c, p, location) if l == invalidUniform { return false @@ -357,19 +357,21 @@ func (c *context) uniformFloats(p program, location string, v []float32, typ sha switch base { case shaderir.Float: - c.ctx.Uniform1fv(int32(l), v) + c.ctx.Uniform1fv(int32(l), uint32sToFloat32s(v)) + case shaderir.Int: + c.ctx.Uniform1iv(int32(l), uint32sToInt32s(v)) case shaderir.Vec2: - c.ctx.Uniform2fv(int32(l), v) + c.ctx.Uniform2fv(int32(l), uint32sToFloat32s(v)) case shaderir.Vec3: - c.ctx.Uniform3fv(int32(l), v) + c.ctx.Uniform3fv(int32(l), uint32sToFloat32s(v)) case shaderir.Vec4: - c.ctx.Uniform4fv(int32(l), v) + c.ctx.Uniform4fv(int32(l), uint32sToFloat32s(v)) case shaderir.Mat2: - c.ctx.UniformMatrix2fv(int32(l), false, v) + c.ctx.UniformMatrix2fv(int32(l), false, uint32sToFloat32s(v)) case shaderir.Mat3: - c.ctx.UniformMatrix3fv(int32(l), false, v) + c.ctx.UniformMatrix3fv(int32(l), false, uint32sToFloat32s(v)) case shaderir.Mat4: - c.ctx.UniformMatrix4fv(int32(l), false, v) + c.ctx.UniformMatrix4fv(int32(l), false, uint32sToFloat32s(v)) default: panic(fmt.Sprintf("opengl: unexpected type: %s", typ.String())) } diff --git a/internal/graphicsdriver/opengl/context_js.go b/internal/graphicsdriver/opengl/context_js.go index c278b2d20..5d82ee0e0 100644 --- a/internal/graphicsdriver/opengl/context_js.go +++ b/internal/graphicsdriver/opengl/context_js.go @@ -451,7 +451,7 @@ func (c *context) uniformInt(p program, location string, v int) bool { return true } -func (c *context) uniformFloats(p program, location string, v []float32, typ shaderir.Type) bool { +func (c *context) uniforms(p program, location string, v []uint32, typ shaderir.Type) bool { gl := c.gl l := c.locationCache.GetUniformLocation(c, p, location) if l.equal(invalidUniform) { @@ -463,46 +463,58 @@ func (c *context) uniformFloats(p program, location string, v []float32, typ sha base = typ.Sub[0].Main } - arr := jsutil.TemporaryFloat32Array(len(v), v) - switch base { case shaderir.Float: + arr := jsutil.TemporaryFloat32Array(len(v), uint32sToFloat32s(v)) if c.usesWebGL2() { gl.uniform1fv.Invoke(js.Value(l), arr, 0, len(v)) } else { gl.uniform1fv.Invoke(js.Value(l), arr.Call("subarray", 0, len(v))) } + case shaderir.Int: + arr := jsutil.TemporaryInt32Array(len(v), uint32sToInt32s(v)) + if c.usesWebGL2() { + gl.uniform1iv.Invoke(js.Value(l), arr, 0, len(v)) + } else { + gl.uniform1iv.Invoke(js.Value(l), arr.Call("subarray", 0, len(v))) + } case shaderir.Vec2: + arr := jsutil.TemporaryFloat32Array(len(v), uint32sToFloat32s(v)) if c.usesWebGL2() { gl.uniform2fv.Invoke(js.Value(l), arr, 0, len(v)) } else { gl.uniform2fv.Invoke(js.Value(l), arr.Call("subarray", 0, len(v))) } case shaderir.Vec3: + arr := jsutil.TemporaryFloat32Array(len(v), uint32sToFloat32s(v)) if c.usesWebGL2() { gl.uniform3fv.Invoke(js.Value(l), arr, 0, len(v)) } else { gl.uniform3fv.Invoke(js.Value(l), arr.Call("subarray", 0, len(v))) } case shaderir.Vec4: + arr := jsutil.TemporaryFloat32Array(len(v), uint32sToFloat32s(v)) if c.usesWebGL2() { gl.uniform4fv.Invoke(js.Value(l), arr, 0, len(v)) } else { gl.uniform4fv.Invoke(js.Value(l), arr.Call("subarray", 0, len(v))) } case shaderir.Mat2: + arr := jsutil.TemporaryFloat32Array(len(v), uint32sToFloat32s(v)) if c.usesWebGL2() { gl.uniformMatrix2fv.Invoke(js.Value(l), false, arr, 0, len(v)) } else { gl.uniformMatrix2fv.Invoke(js.Value(l), false, arr.Call("subarray", 0, len(v))) } case shaderir.Mat3: + arr := jsutil.TemporaryFloat32Array(len(v), uint32sToFloat32s(v)) if c.usesWebGL2() { gl.uniformMatrix3fv.Invoke(js.Value(l), false, arr, 0, len(v)) } else { gl.uniformMatrix3fv.Invoke(js.Value(l), false, arr.Call("subarray", 0, len(v))) } case shaderir.Mat4: + arr := jsutil.TemporaryFloat32Array(len(v), uint32sToFloat32s(v)) if c.usesWebGL2() { gl.uniformMatrix4fv.Invoke(js.Value(l), false, arr, 0, len(v)) } else { diff --git a/internal/graphicsdriver/opengl/gl/conversions.go b/internal/graphicsdriver/opengl/gl/conversions.go index 0401a99d2..d99ffa32b 100644 --- a/internal/graphicsdriver/opengl/gl/conversions.go +++ b/internal/graphicsdriver/opengl/gl/conversions.go @@ -31,6 +31,8 @@ func Ptr(data any) unsafe.Pointer { addr = unsafe.Pointer(&v[0]) case []uint16: addr = unsafe.Pointer(&v[0]) + case []uint32: + addr = unsafe.Pointer(&v[0]) case []float32: addr = unsafe.Pointer(&v[0]) default: diff --git a/internal/graphicsdriver/opengl/gl/package_notpurego.go b/internal/graphicsdriver/opengl/gl/package_notpurego.go index 4255bedd2..f0ac72d6b 100644 --- a/internal/graphicsdriver/opengl/gl/package_notpurego.go +++ b/internal/graphicsdriver/opengl/gl/package_notpurego.go @@ -163,8 +163,9 @@ package gl // typedef void (APIENTRYP GPTEXIMAGE2D)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void * pixels); // typedef void (APIENTRYP GPTEXPARAMETERI)(GLenum target, GLenum pname, GLint param); // typedef void (APIENTRYP GPTEXSUBIMAGE2D)(GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void * pixels); -// typedef void (APIENTRYP GPUNIFORM1I)(GLint location, GLint v0); // typedef void (APIENTRYP GPUNIFORM1FV)(GLint location, GLsizei count, const GLfloat * value); +// typedef void (APIENTRYP GPUNIFORM1I)(GLint location, GLint v0); +// typedef void (APIENTRYP GPUNIFORM1IV)(GLint location, GLsizei count, const GLint * value); // typedef void (APIENTRYP GPUNIFORM2FV)(GLint location, GLsizei count, const GLfloat * value); // typedef void (APIENTRYP GPUNIFORM3FV)(GLint location, GLsizei count, const GLfloat * value); // typedef void (APIENTRYP GPUNIFORM4FV)(GLint location, GLsizei count, const GLfloat * value); @@ -382,10 +383,13 @@ package gl // static void glowTexSubImage2D(GPTEXSUBIMAGE2D fnptr, GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void * pixels) { // (*fnptr)(target, level, xoffset, yoffset, width, height, format, type, pixels); // } +// static void glowUniform1fv(GPUNIFORM1FV fnptr, GLint location, GLsizei count, const GLfloat * value) { +// (*fnptr)(location, count, value); +// } // static void glowUniform1i(GPUNIFORM1I fnptr, GLint location, GLint v0) { // (*fnptr)(location, v0); // } -// static void glowUniform1fv(GPUNIFORM1FV fnptr, GLint location, GLsizei count, const GLfloat * value) { +// static void glowUniform1iv(GPUNIFORM1IV fnptr, GLint location, GLsizei count, const GLint * value) { // (*fnptr)(location, count, value); // } // static void glowUniform2fv(GPUNIFORM2FV fnptr, GLint location, GLsizei count, const GLfloat * value) { @@ -494,8 +498,9 @@ var ( gpTexImage2D C.GPTEXIMAGE2D gpTexParameteri C.GPTEXPARAMETERI gpTexSubImage2D C.GPTEXSUBIMAGE2D - gpUniform1i C.GPUNIFORM1I gpUniform1fv C.GPUNIFORM1FV + gpUniform1i C.GPUNIFORM1I + gpUniform1iv C.GPUNIFORM1IV gpUniform2fv C.GPUNIFORM2FV gpUniform3fv C.GPUNIFORM3FV gpUniform4fv C.GPUNIFORM4FV @@ -791,12 +796,16 @@ func TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, wid C.glowTexSubImage2D(gpTexSubImage2D, (C.GLenum)(target), (C.GLint)(level), (C.GLint)(xoffset), (C.GLint)(yoffset), (C.GLsizei)(width), (C.GLsizei)(height), (C.GLenum)(format), (C.GLenum)(xtype), pixels) } +func Uniform1fv(location int32, count int32, value *float32) { + C.glowUniform1fv(gpUniform1fv, (C.GLint)(location), (C.GLsizei)(count), (*C.GLfloat)(unsafe.Pointer(value))) +} + func Uniform1i(location int32, v0 int32) { C.glowUniform1i(gpUniform1i, (C.GLint)(location), (C.GLint)(v0)) } -func Uniform1fv(location int32, count int32, value *float32) { - C.glowUniform1fv(gpUniform1fv, (C.GLint)(location), (C.GLsizei)(count), (*C.GLfloat)(unsafe.Pointer(value))) +func Uniform1iv(location int32, count int32, value *int32) { + C.glowUniform1iv(gpUniform1iv, (C.GLint)(location), (C.GLsizei)(count), (*C.GLint)(unsafe.Pointer(value))) } func Uniform2fv(location int32, count int32, value *float32) { @@ -1044,13 +1053,17 @@ func InitWithProcAddrFunc(getProcAddr func(name string) unsafe.Pointer) error { if gpTexSubImage2D == nil { return errors.New("gl: glTexSubImage2D is missing") } + gpUniform1fv = (C.GPUNIFORM1FV)(getProcAddr("glUniform1fv")) + if gpUniform1fv == nil { + return errors.New("gl: glUniform1fv is missing") + } gpUniform1i = (C.GPUNIFORM1I)(getProcAddr("glUniform1i")) if gpUniform1i == nil { return errors.New("gl: glUniform1i is missing") } - gpUniform1fv = (C.GPUNIFORM1FV)(getProcAddr("glUniform1fv")) - if gpUniform1fv == nil { - return errors.New("gl: glUniform1fv is missing") + gpUniform1iv = (C.GPUNIFORM1IV)(getProcAddr("glUniform1iv")) + if gpUniform1iv == nil { + return errors.New("gl: glUniform1iv is missing") } gpUniform2fv = (C.GPUNIFORM2FV)(getProcAddr("glUniform2fv")) if gpUniform2fv == nil { diff --git a/internal/graphicsdriver/opengl/gl/package_purego.go b/internal/graphicsdriver/opengl/gl/package_purego.go index ef6654e11..4f09be316 100644 --- a/internal/graphicsdriver/opengl/gl/package_purego.go +++ b/internal/graphicsdriver/opengl/gl/package_purego.go @@ -81,8 +81,9 @@ var ( gpTexImage2D uintptr gpTexParameteri uintptr gpTexSubImage2D uintptr - gpUniform1i uintptr gpUniform1fv uintptr + gpUniform1i uintptr + gpUniform1iv uintptr gpUniform2fv uintptr gpUniform3fv uintptr gpUniform4fv uintptr @@ -378,12 +379,16 @@ func TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, wid purego.SyscallN(gpTexSubImage2D, uintptr(target), uintptr(level), uintptr(xoffset), uintptr(yoffset), uintptr(width), uintptr(height), uintptr(format), uintptr(xtype), uintptr(pixels)) } +func Uniform1fv(location int32, count int32, value *float32) { + purego.SyscallN(gpUniform1fv, uintptr(location), uintptr(count), uintptr(unsafe.Pointer(value))) +} + func Uniform1i(location int32, v0 int32) { purego.SyscallN(gpUniform1i, uintptr(location), uintptr(v0)) } -func Uniform1fv(location int32, count int32, value *float32) { - purego.SyscallN(gpUniform1fv, uintptr(location), uintptr(count), uintptr(unsafe.Pointer(value))) +func Uniform1iv(location int32, count int32, value *int32) { + purego.SyscallN(gpUniform1iv, uintptr(location), uintptr(count), uintptr(unsafe.Pointer(value))) } func Uniform2fv(location int32, count int32, value *float32) { @@ -631,13 +636,17 @@ func InitWithProcAddrFunc(getProcAddr func(name string) uintptr) error { if gpTexSubImage2D == 0 { return errors.New("gl: glTexSubImage2D is missing") } + gpUniform1fv = getProcAddr("glUniform1fv") + if gpUniform1fv == 0 { + return errors.New("gl: glUniform1fv is missing") + } gpUniform1i = getProcAddr("glUniform1i") if gpUniform1i == 0 { return errors.New("gl: glUniform1i is missing") } - gpUniform1fv = getProcAddr("glUniform1fv") - if gpUniform1fv == 0 { - return errors.New("gl: glUniform1fv is missing") + gpUniform1iv = getProcAddr("glUniform1iv") + if gpUniform1iv == 0 { + return errors.New("gl: glUniform1iv is missing") } gpUniform2fv = getProcAddr("glUniform2fv") if gpUniform2fv == 0 { diff --git a/internal/graphicsdriver/opengl/gl_js.go b/internal/graphicsdriver/opengl/gl_js.go index ce4c60fb2..e4f562fc7 100644 --- a/internal/graphicsdriver/opengl/gl_js.go +++ b/internal/graphicsdriver/opengl/gl_js.go @@ -80,10 +80,11 @@ type gl struct { texSubImage2D js.Value texParameteri js.Value uniform1fv js.Value + uniform1i js.Value + uniform1iv js.Value uniform2fv js.Value uniform3fv js.Value uniform4fv js.Value - uniform1i js.Value uniformMatrix2fv js.Value uniformMatrix3fv js.Value uniformMatrix4fv js.Value @@ -156,10 +157,11 @@ func (c *context) newGL(v js.Value) *gl { texSubImage2D: v.Get("texSubImage2D").Call("bind", v), texParameteri: v.Get("texParameteri").Call("bind", v), uniform1fv: v.Get("uniform1fv").Call("bind", v), + uniform1i: v.Get("uniform1i").Call("bind", v), + uniform1iv: v.Get("uniform1iv").Call("bind", v), uniform2fv: v.Get("uniform2fv").Call("bind", v), uniform3fv: v.Get("uniform3fv").Call("bind", v), uniform4fv: v.Get("uniform4fv").Call("bind", v), - uniform1i: v.Get("uniform1i").Call("bind", v), uniformMatrix2fv: v.Get("uniformMatrix2fv").Call("bind", v), uniformMatrix3fv: v.Get("uniformMatrix3fv").Call("bind", v), uniformMatrix4fv: v.Get("uniformMatrix4fv").Call("bind", v), diff --git a/internal/graphicsdriver/opengl/gles/default.go b/internal/graphicsdriver/opengl/gles/default.go index 860c18817..077618f16 100644 --- a/internal/graphicsdriver/opengl/gles/default.go +++ b/internal/graphicsdriver/opengl/gles/default.go @@ -332,6 +332,10 @@ func (DefaultContext) Uniform1i(location int32, v0 int32) { C.glUniform1i(C.GLint(location), C.GLint(v0)) } +func (DefaultContext) Uniform1iv(location int32, value []int32) { + C.glUniform1iv(C.GLint(location), C.GLsizei(len(value)), (*C.GLint)(unsafe.Pointer(&value[0]))) +} + func (DefaultContext) Uniform2fv(location int32, value []float32) { C.glUniform2fv(C.GLint(location), C.GLsizei(len(value)/2), (*C.GLfloat)(unsafe.Pointer(&value[0]))) } diff --git a/internal/graphicsdriver/opengl/gles/gomobile.go b/internal/graphicsdriver/opengl/gles/gomobile.go index c6d4fae0f..5f346bd2d 100644 --- a/internal/graphicsdriver/opengl/gles/gomobile.go +++ b/internal/graphicsdriver/opengl/gles/gomobile.go @@ -306,6 +306,10 @@ func (g *GomobileContext) Uniform1i(location int32, v0 int32) { g.ctx.Uniform1i(gl.Uniform{Value: location}, int(v0)) } +func (g *GomobileContext) Uniform1iv(location int32, value []int32) { + g.ctx.Uniform1iv(gl.Uniform{Value: location}, value) +} + func (g *GomobileContext) Uniform2fv(location int32, value []float32) { g.ctx.Uniform2fv(gl.Uniform{Value: location}, value) } diff --git a/internal/graphicsdriver/opengl/gles/interface.go b/internal/graphicsdriver/opengl/gles/interface.go index 40735cbc7..b4704f370 100644 --- a/internal/graphicsdriver/opengl/gles/interface.go +++ b/internal/graphicsdriver/opengl/gles/interface.go @@ -76,6 +76,7 @@ type Context interface { TexSubImage2D(target uint32, level int32, xoffset int32, yoffset int32, width int32, height int32, format uint32, xtype uint32, pixels []byte) Uniform1fv(location int32, value []float32) Uniform1i(location int32, v0 int32) + Uniform1iv(location int32, value []int32) Uniform2fv(location int32, value []float32) Uniform3fv(location int32, value []float32) Uniform4fv(location int32, value []float32) diff --git a/internal/graphicsdriver/opengl/program.go b/internal/graphicsdriver/opengl/program.go index 04bdfaaa7..b60bb8fc1 100644 --- a/internal/graphicsdriver/opengl/program.go +++ b/internal/graphicsdriver/opengl/program.go @@ -223,7 +223,7 @@ func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textu if u.value == nil { continue } - if got, expected := len(u.value), u.typ.FloatCount(); got != expected { + if got, expected := len(u.value), u.typ.Uint32Count(); got != expected { // Copy a shaderir.Type value once. Do not pass u.typ directly to fmt.Errorf arguments, or // the value u would be allocated on heap. typ := u.typ @@ -234,7 +234,7 @@ func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textu if ok && areSameUint32Array(cached, u.value) { continue } - g.context.uniformFloats(program, u.name, uint32sToFloat32s(u.value), u.typ) + g.context.uniforms(program, u.name, u.value, u.typ) if g.state.lastUniforms == nil { g.state.lastUniforms = map[string][]uint32{} } @@ -284,3 +284,7 @@ loop: func uint32sToFloat32s(s []uint32) []float32 { return unsafe.Slice((*float32)(unsafe.Pointer(&s[0])), len(s)) } + +func uint32sToInt32s(s []uint32) []int32 { + return unsafe.Slice((*int32)(unsafe.Pointer(&s[0])), len(s)) +} diff --git a/internal/jsutil/buf_js.go b/internal/jsutil/buf_js.go index 9b3c029c6..31b838f44 100644 --- a/internal/jsutil/buf_js.go +++ b/internal/jsutil/buf_js.go @@ -23,6 +23,7 @@ var ( arrayBuffer = js.Global().Get("ArrayBuffer") uint8Array = js.Global().Get("Uint8Array") float32Array = js.Global().Get("Float32Array") + int32Array = js.Global().Get("Int32Array") ) var ( @@ -38,6 +39,9 @@ var ( // temporaryFloat32Array is a Float32ArrayBuffer whose underlying buffer is always temporaryArrayBuffer. temporaryFloat32Array = float32Array.New(temporaryArrayBuffer) + + // temporaryInt32Array is a Float32ArrayBuffer whose underlying buffer is always temporaryArrayBuffer. + temporaryInt32Array = int32Array.New(temporaryArrayBuffer) ) func ensureTemporaryArrayBufferSize(byteLength int) { @@ -49,6 +53,7 @@ func ensureTemporaryArrayBufferSize(byteLength int) { temporaryArrayBuffer = arrayBuffer.New(bufl) temporaryUint8Array = uint8Array.New(temporaryArrayBuffer) temporaryFloat32Array = float32Array.New(temporaryArrayBuffer) + temporaryInt32Array = int32Array.New(temporaryArrayBuffer) } } @@ -66,7 +71,7 @@ func TemporaryUint8ArrayFromUint8Slice(minLength int, data []uint8) js.Value { // data must be a slice of a numeric type for initialization, or nil if you don't need initialization. func TemporaryUint8ArrayFromUint16Slice(minLength int, data []uint16) js.Value { ensureTemporaryArrayBufferSize(minLength * 2) - copyUint16SliceToTemporaryArrayBuffer(data) + copySliceToTemporaryArrayBuffer(data) return temporaryUint8Array } @@ -75,7 +80,7 @@ func TemporaryUint8ArrayFromUint16Slice(minLength int, data []uint16) js.Value { // data must be a slice of a numeric type for initialization, or nil if you don't need initialization. func TemporaryUint8ArrayFromFloat32Slice(minLength int, data []float32) js.Value { ensureTemporaryArrayBufferSize(minLength * 4) - copyFloat32SliceToTemporaryArrayBuffer(data) + copySliceToTemporaryArrayBuffer(data) return temporaryUint8Array } @@ -84,6 +89,15 @@ func TemporaryUint8ArrayFromFloat32Slice(minLength int, data []float32) js.Value // data must be a slice of a numeric type for initialization, or nil if you don't need initialization. func TemporaryFloat32Array(minLength int, data []float32) js.Value { ensureTemporaryArrayBufferSize(minLength * 4) - copyFloat32SliceToTemporaryArrayBuffer(data) + copySliceToTemporaryArrayBuffer(data) return temporaryFloat32Array } + +// TemporaryInt32Array returns a Int32Array whose length is at least minLength. +// Be careful that the length can exceed the given minLength. +// data must be a slice of a numeric type for initialization, or nil if you don't need initialization. +func TemporaryInt32Array(minLength int, data []int32) js.Value { + ensureTemporaryArrayBufferSize(minLength * 4) + copySliceToTemporaryArrayBuffer(data) + return temporaryInt32Array +} diff --git a/internal/jsutil/slice_js.go b/internal/jsutil/slice_js.go index f120a2669..ea9a840b6 100644 --- a/internal/jsutil/slice_js.go +++ b/internal/jsutil/slice_js.go @@ -27,18 +27,14 @@ func copyUint8SliceToTemporaryArrayBuffer(src []uint8) { js.CopyBytesToJS(temporaryUint8Array, src) } -func copyUint16SliceToTemporaryArrayBuffer(src []uint16) { - if len(src) == 0 { - return - } - js.CopyBytesToJS(temporaryUint8Array, unsafe.Slice((*byte)(unsafe.Pointer(&src[0])), len(src)*2)) - runtime.KeepAlive(src) +type numeric interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 } -func copyFloat32SliceToTemporaryArrayBuffer(src []float32) { +func copySliceToTemporaryArrayBuffer[T numeric](src []T) { if len(src) == 0 { return } - js.CopyBytesToJS(temporaryUint8Array, unsafe.Slice((*byte)(unsafe.Pointer(&src[0])), len(src)*4)) + js.CopyBytesToJS(temporaryUint8Array, unsafe.Slice((*byte)(unsafe.Pointer(&src[0])), len(src)*int(unsafe.Sizeof(T(0))))) runtime.KeepAlive(src) } diff --git a/internal/shaderir/glsl/glsl.go b/internal/shaderir/glsl/glsl.go index b03c76f5a..dcaf1df89 100644 --- a/internal/shaderir/glsl/glsl.go +++ b/internal/shaderir/glsl/glsl.go @@ -59,6 +59,7 @@ func FragmentPrelude(version GLSLVersion) string { } prelude := prefix + `#if defined(GL_ES) precision highp float; +precision highp int; #else #define lowp #define mediump diff --git a/internal/shaderir/hlsl/packing.go b/internal/shaderir/hlsl/packing.go index 3fa58098d..c31987586 100644 --- a/internal/shaderir/hlsl/packing.go +++ b/internal/shaderir/hlsl/packing.go @@ -43,6 +43,9 @@ func calculateMemoryOffsets(uniforms []shaderir.Type) []int { case shaderir.Float: offsets = append(offsets, head) head += 4 + case shaderir.Int: + offsets = append(offsets, head) + head += 4 case shaderir.Vec2: if head%boundaryInBytes >= 4*3 { head = align(head) @@ -79,7 +82,7 @@ func calculateMemoryOffsets(uniforms []shaderir.Type) []int { // TODO: What if the array has 2 or more dimensions? head = align(head) offsets = append(offsets, head) - n := u.Sub[0].FloatCount() + n := u.Sub[0].Uint32Count() switch u.Sub[0].Main { case shaderir.Mat2: n = 6 @@ -95,7 +98,7 @@ func calculateMemoryOffsets(uniforms []shaderir.Type) []int { // TODO: Implement this panic("hlsl: offset for a struct is not implemented yet") default: - panic(fmt.Sprintf("hlsl: unexpected type: %d", u.Main)) + panic(fmt.Sprintf("hlsl: unexpected type: %s", u.String())) } } diff --git a/internal/shaderir/type.go b/internal/shaderir/type.go index ae36dc2be..b70e91a28 100644 --- a/internal/shaderir/type.go +++ b/internal/shaderir/type.go @@ -66,7 +66,7 @@ func (t *Type) String() string { case Mat4: return "mat4" case Array: - return fmt.Sprintf("%s[%d]", t.Sub[0].String(), t.Length) + return fmt.Sprintf("[%d]%s", t.Length, t.Sub[0].String()) case Struct: str := "struct{" sub := make([]string, 0, len(t.Sub)) @@ -81,8 +81,10 @@ func (t *Type) String() string { } } -func (t *Type) FloatCount() int { +func (t *Type) Uint32Count() int { switch t.Main { + case Int: + return 1 case Float: return 1 case Vec2: @@ -98,7 +100,7 @@ func (t *Type) FloatCount() int { case Mat4: return 16 case Array: - return t.Length * t.Sub[0].FloatCount() + return t.Length * t.Sub[0].Uint32Count() default: // TODO: Parse a struct correctly return -1 } diff --git a/internal/ui/shader.go b/internal/ui/shader.go index 922f9ba16..6e5d4786c 100644 --- a/internal/ui/shader.go +++ b/internal/ui/shader.go @@ -17,6 +17,7 @@ package ui import ( "fmt" "math" + "reflect" "strings" "github.com/hajimehoshi/ebiten/v2/internal/mipmap" @@ -48,17 +49,37 @@ func (s *Shader) MarkDisposed() { func (s *Shader) ConvertUniforms(uniforms map[string]any) [][]uint32 { nameToU32s := map[string][]uint32{} for name, v := range uniforms { - switch v := v.(type) { - case float32: - nameToU32s[name] = []uint32{math.Float32bits(v)} - case []float32: - u32s := make([]uint32, len(v)) - for i := range v { - u32s[i] = math.Float32bits(v[i]) + v := reflect.ValueOf(v) + t := v.Type() + switch t.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + nameToU32s[name] = []uint32{uint32(v.Int())} + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + nameToU32s[name] = []uint32{uint32(v.Uint())} + case reflect.Float32, reflect.Float64: + nameToU32s[name] = []uint32{math.Float32bits(float32(v.Float()))} + case reflect.Slice: + // TODO: Allow reflect.Array (#2448) + u32s := make([]uint32, v.Len()) + switch t.Elem().Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + for i := range u32s { + u32s[i] = uint32(v.Index(i).Int()) + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + for i := range u32s { + u32s[i] = uint32(v.Index(i).Uint()) + } + case reflect.Float32, reflect.Float64: + for i := range u32s { + u32s[i] = math.Float32bits(float32(v.Index(i).Float())) + } + default: + panic(fmt.Sprintf("ebiten: unexpected uniform value type: %s (%s)", name, v.Kind().String())) } nameToU32s[name] = u32s default: - panic(fmt.Sprintf("ebiten: unexpected uniform value type: %s, %T", name, v)) + panic(fmt.Sprintf("ebiten: unexpected uniform value type: %s (%s)", name, v.Kind().String())) } } @@ -84,7 +105,7 @@ func (s *Shader) ConvertUniforms(uniforms map[string]any) [][]uint32 { continue } t := s.uniformNameToType[name] - us[idx] = make([]uint32, t.FloatCount()) + us[idx] = make([]uint32, t.Uint32Count()) } // TODO: Panic if uniforms include an invalid name diff --git a/shader_test.go b/shader_test.go index d31f55dc6..d6e6be8a9 100644 --- a/shader_test.go +++ b/shader_test.go @@ -548,10 +548,13 @@ func Fragment(position vec4, texCoord vec2, color vec4) vec4 { const w, h = 1, 1 dst := ebiten.NewImage(w, h) + defer dst.Dispose() + s, err := ebiten.NewShader([]byte(shader.Shader)) if err != nil { t.Fatal(err) } + defer s.Dispose() op := &ebiten.DrawRectShaderOptions{} op.Uniforms = shader.Uniforms @@ -1269,3 +1272,113 @@ func Fragment(position vec4, texCoord vec2, color vec4) vec4 { } } } + +func TestShaderUniformInt(t *testing.T) { + const ints = `package main + +var U0 int +var U1 int +var U2 int +var U3 int + +func Fragment(position vec4, texCoord vec2, color vec4) vec4 { + return vec4(float(U0)/255.0, float(U1)/255.0, float(U2)/255.0, float(U3)/255.0) +} +` + + const intArray = `package main + +var U [4]int + +func Fragment(position vec4, texCoord vec2, color vec4) vec4 { + return vec4(float(U[0])/255.0, float(U[1])/255.0, float(U[2])/255.0, float(U[3])/255.0) +} +` + + testCases := []struct { + Name string + Uniforms map[string]any + Shader string + Want color.RGBA + }{ + { + Name: "0xff", + Uniforms: map[string]any{ + "U0": 0xff, + "U1": 0xff, + "U2": 0xff, + "U3": 0xff, + }, + Shader: ints, + Want: color.RGBA{0xff, 0xff, 0xff, 0xff}, + }, + { + Name: "int", + Uniforms: map[string]any{ + "U0": int8(0x24), + "U1": int16(0x3f), + "U2": int32(0x6a), + "U3": int64(0x88), + }, + Shader: ints, + Want: color.RGBA{0x24, 0x3f, 0x6a, 0x88}, + }, + { + Name: "uint", + Uniforms: map[string]any{ + "U0": uint8(0x85), + "U1": uint16(0xa3), + "U2": uint32(0x08), + "U3": uint64(0xd3), + }, + Shader: ints, + Want: color.RGBA{0x85, 0xa3, 0x08, 0xd3}, + }, + { + Name: "0xff,array", + Uniforms: map[string]any{ + "U": []int{0xff, 0xff, 0xff, 0xff}, + }, + Shader: intArray, + Want: color.RGBA{0xff, 0xff, 0xff, 0xff}, + }, + { + Name: "int,array", + Uniforms: map[string]any{ + "U": []int16{0x24, 0x3f, 0x6a, 0x88}, + }, + Shader: intArray, + Want: color.RGBA{0x24, 0x3f, 0x6a, 0x88}, + }, + { + Name: "uint,array", + Uniforms: map[string]any{ + "U": []uint8{0x85, 0xa3, 0x08, 0xd3}, + }, + Shader: intArray, + Want: color.RGBA{0x85, 0xa3, 0x08, 0xd3}, + }, + } + for _, tc := range testCases { + tc := tc + t.Run(tc.Name, func(t *testing.T) { + const w, h = 1, 1 + + dst := ebiten.NewImage(w, h) + defer dst.Dispose() + + s, err := ebiten.NewShader([]byte(tc.Shader)) + if err != nil { + t.Fatal(err) + } + defer s.Dispose() + + op := &ebiten.DrawRectShaderOptions{} + op.Uniforms = tc.Uniforms + dst.DrawRectShader(w, h, s, op) + if got, want := dst.At(0, 0).(color.RGBA), tc.Want; !sameColors(got, want, 1) { + t.Errorf("got: %v, want: %v", got, want) + } + }) + } +}