all: allow integer uniform variables for Kage shaders

Closes #2305
Updates #2448
This commit is contained in:
Hajime Hoshi 2022-11-12 18:38:15 +09:00
parent 4ff9a12930
commit 1ecac8d834
20 changed files with 279 additions and 57 deletions

View File

@ -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.

View File

@ -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++ {

View File

@ -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:

View File

@ -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()))
}

View File

@ -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 {

View File

@ -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:

View File

@ -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 {

View File

@ -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 {

View File

@ -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),

View File

@ -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])))
}

View File

@ -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)
}

View File

@ -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)

View File

@ -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))
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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

View File

@ -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()))
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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)
}
})
}
}