mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-12-25 03:08:54 +01:00
internal/driver: Optimization: Replace interface{} with driver.Uniform
Converting a value from/to interface{} can create a value in heap and this is not efficient.
This commit is contained in:
parent
1f82bb297c
commit
06f4142ca0
@ -407,13 +407,13 @@ func (i *Image) processSrc(src *Image) {
|
|||||||
// 5: Color G
|
// 5: Color G
|
||||||
// 6: Color B
|
// 6: Color B
|
||||||
// 7: Color Y
|
// 7: Color Y
|
||||||
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool) {
|
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []driver.Uniform, evenOdd bool) {
|
||||||
backendsM.Lock()
|
backendsM.Lock()
|
||||||
defer backendsM.Unlock()
|
defer backendsM.Unlock()
|
||||||
i.drawTriangles(srcs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, shader, uniforms, evenOdd, false)
|
i.drawTriangles(srcs, vertices, indices, colorm, mode, filter, address, dstRegion, srcRegion, subimageOffsets, shader, uniforms, evenOdd, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *Image) drawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool, keepOnAtlas bool) {
|
func (i *Image) drawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []driver.Uniform, evenOdd bool, keepOnAtlas bool) {
|
||||||
if i.disposed {
|
if i.disposed {
|
||||||
panic("atlas: the drawing target image must not be disposed (DrawTriangles)")
|
panic("atlas: the drawing target image must not be disposed (DrawTriangles)")
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,7 @@ func (i *Image) replacePendingPixels(pix []byte, x, y, width, height int) {
|
|||||||
// DrawTriangles draws the src image with the given vertices.
|
// DrawTriangles draws the src image with the given vertices.
|
||||||
//
|
//
|
||||||
// Copying vertices and indices is the caller's responsibility.
|
// Copying vertices and indices is the caller's responsibility.
|
||||||
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool) {
|
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []driver.Uniform, evenOdd bool) {
|
||||||
for _, src := range srcs {
|
for _, src := range srcs {
|
||||||
if i == src {
|
if i == src {
|
||||||
panic("buffered: Image.DrawTriangles: source images must be different from the receiver")
|
panic("buffered: Image.DrawTriangles: source images must be different from the receiver")
|
||||||
|
@ -39,6 +39,11 @@ type ColorM interface {
|
|||||||
Elements(body *[16]float32, translate *[4]float32)
|
Elements(body *[16]float32, translate *[4]float32)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Uniform struct {
|
||||||
|
Float32 float32
|
||||||
|
Float32s []float32
|
||||||
|
}
|
||||||
|
|
||||||
type Graphics interface {
|
type Graphics interface {
|
||||||
Begin()
|
Begin()
|
||||||
End()
|
End()
|
||||||
@ -64,7 +69,7 @@ type Graphics interface {
|
|||||||
//
|
//
|
||||||
// * float32
|
// * float32
|
||||||
// * []float32
|
// * []float32
|
||||||
DrawTriangles(dst ImageID, srcs [graphics.ShaderImageNum]ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shader ShaderID, indexLen int, indexOffset int, mode CompositeMode, colorM ColorM, filter Filter, address Address, dstRegion, srcRegion Region, uniforms []interface{}, evenOdd bool) error
|
DrawTriangles(dst ImageID, srcs [graphics.ShaderImageNum]ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shader ShaderID, indexLen int, indexOffset int, mode CompositeMode, colorM ColorM, filter Filter, address Address, dstRegion, srcRegion Region, uniforms []Uniform, evenOdd bool) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphicsNotReady represents that the graphics driver is not ready for recovering from the context lost.
|
// GraphicsNotReady represents that the graphics driver is not ready for recovering from the context lost.
|
||||||
|
@ -145,7 +145,7 @@ func (q *commandQueue) appendIndices(indices []uint16, offset uint16) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// EnqueueDrawTrianglesCommand enqueues a drawing-image command.
|
// EnqueueDrawTrianglesCommand enqueues a drawing-image command.
|
||||||
func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, color affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) {
|
func (q *commandQueue) EnqueueDrawTrianglesCommand(dst *Image, srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, color affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []driver.Uniform, evenOdd bool) {
|
||||||
if len(indices) > graphics.IndicesNum {
|
if len(indices) > graphics.IndicesNum {
|
||||||
panic(fmt.Sprintf("graphicscommand: len(indices) must be <= graphics.IndicesNum but not at EnqueueDrawTrianglesCommand: len(indices): %d, graphics.IndicesNum: %d", len(indices), graphics.IndicesNum))
|
panic(fmt.Sprintf("graphicscommand: len(indices) must be <= graphics.IndicesNum but not at EnqueueDrawTrianglesCommand: len(indices): %d, graphics.IndicesNum: %d", len(indices), graphics.IndicesNum))
|
||||||
}
|
}
|
||||||
@ -363,7 +363,7 @@ type drawTrianglesCommand struct {
|
|||||||
dstRegion driver.Region
|
dstRegion driver.Region
|
||||||
srcRegion driver.Region
|
srcRegion driver.Region
|
||||||
shader *Shader
|
shader *Shader
|
||||||
uniforms []interface{}
|
uniforms []driver.Uniform
|
||||||
evenOdd bool
|
evenOdd bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ func (i *Image) InternalSize() (int, int) {
|
|||||||
//
|
//
|
||||||
// If the source image is not specified, i.e., src is nil and there is no image in the uniform variables, the
|
// If the source image is not specified, i.e., src is nil and there is no image in the uniform variables, the
|
||||||
// elements for the source image are not used.
|
// elements for the source image are not used.
|
||||||
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, clr affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) {
|
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, clr affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []driver.Uniform, evenOdd bool) {
|
||||||
if shader == nil {
|
if shader == nil {
|
||||||
// Fast path for rendering without a shader (#1355).
|
// Fast path for rendering without a shader (#1355).
|
||||||
img := srcs[0]
|
img := srcs[0]
|
||||||
|
@ -775,7 +775,7 @@ func (g *Graphics) flushRenderCommandEncoderIfNeeded() {
|
|||||||
g.lastDst = nil
|
g.lastDst = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion driver.Region, srcs [graphics.ShaderImageNum]*Image, indexLen int, indexOffset int, uniforms []interface{}, stencilMode stencilMode) error {
|
func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion driver.Region, srcs [graphics.ShaderImageNum]*Image, indexLen int, indexOffset int, uniforms []driver.Uniform, stencilMode stencilMode) error {
|
||||||
// When prepareing a stencil buffer, flush the current render command encoder
|
// When prepareing a stencil buffer, flush the current render command encoder
|
||||||
// to make sure the stencil buffer is cleared when loading.
|
// to make sure the stencil buffer is cleared when loading.
|
||||||
// TODO: What about clearing the stencil buffer by vertices?
|
// TODO: What about clearing the stencil buffer by vertices?
|
||||||
@ -840,15 +840,14 @@ func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion drive
|
|||||||
g.rce.SetVertexBuffer(g.vb, 0, 0)
|
g.rce.SetVertexBuffer(g.vb, 0, 0)
|
||||||
|
|
||||||
for i, u := range uniforms {
|
for i, u := range uniforms {
|
||||||
switch u := u.(type) {
|
if len(u.Float32s) == 0 {
|
||||||
case float32:
|
v := u.Float32
|
||||||
g.rce.SetVertexBytes(unsafe.Pointer(&u), unsafe.Sizeof(u), i+1)
|
g.rce.SetVertexBytes(unsafe.Pointer(&v), unsafe.Sizeof(v), i+1)
|
||||||
g.rce.SetFragmentBytes(unsafe.Pointer(&u), unsafe.Sizeof(u), i+1)
|
g.rce.SetFragmentBytes(unsafe.Pointer(&v), unsafe.Sizeof(v), i+1)
|
||||||
case []float32:
|
} else {
|
||||||
g.rce.SetVertexBytes(unsafe.Pointer(&u[0]), unsafe.Sizeof(u[0])*uintptr(len(u)), i+1)
|
v := u.Float32s
|
||||||
g.rce.SetFragmentBytes(unsafe.Pointer(&u[0]), unsafe.Sizeof(u[0])*uintptr(len(u)), i+1)
|
g.rce.SetVertexBytes(unsafe.Pointer(&v[0]), unsafe.Sizeof(v[0])*uintptr(len(v)), i+1)
|
||||||
default:
|
g.rce.SetFragmentBytes(unsafe.Pointer(&v[0]), unsafe.Sizeof(v[0])*uintptr(len(v)), i+1)
|
||||||
return fmt.Errorf("metal: unexpected uniform value: %[1]v (type: %[1]T)", u)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -867,7 +866,7 @@ func (g *Graphics) draw(rps mtl.RenderPipelineState, dst *Image, dstRegion drive
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shaderID driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, colorM driver.ColorM, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, uniforms []interface{}, evenOdd bool) error {
|
func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shaderID driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, colorM driver.ColorM, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, uniforms []driver.Uniform, evenOdd bool) error {
|
||||||
dst := g.images[dstID]
|
dst := g.images[dstID]
|
||||||
|
|
||||||
if dst.screen {
|
if dst.screen {
|
||||||
@ -880,7 +879,7 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
|
|||||||
}
|
}
|
||||||
|
|
||||||
rpss := map[stencilMode]mtl.RenderPipelineState{}
|
rpss := map[stencilMode]mtl.RenderPipelineState{}
|
||||||
var uniformVars []interface{}
|
var uniformVars []driver.Uniform
|
||||||
if shaderID == driver.InvalidShaderID {
|
if shaderID == driver.InvalidShaderID {
|
||||||
if dst.screen && filter == driver.FilterScreen {
|
if dst.screen && filter == driver.FilterScreen {
|
||||||
rpss[noStencil] = g.screenRPS
|
rpss[noStencil] = g.screenRPS
|
||||||
@ -915,18 +914,30 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
|
|||||||
if filter == driver.FilterScreen {
|
if filter == driver.FilterScreen {
|
||||||
scale = float32(dst.width) / float32(srcs[0].width)
|
scale = float32(dst.width) / float32(srcs[0].width)
|
||||||
}
|
}
|
||||||
uniformVars = []interface{}{
|
uniformVars = []driver.Uniform{
|
||||||
[]float32{float32(w), float32(h)},
|
{
|
||||||
sourceSize,
|
Float32s: []float32{float32(w), float32(h)},
|
||||||
esBody[:],
|
},
|
||||||
esTranslate[:],
|
{
|
||||||
scale,
|
Float32s: sourceSize,
|
||||||
[]float32{
|
},
|
||||||
|
{
|
||||||
|
Float32s: esBody[:],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Float32s: esTranslate[:],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Float32: scale,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Float32s: []float32{
|
||||||
srcRegion.X,
|
srcRegion.X,
|
||||||
srcRegion.Y,
|
srcRegion.Y,
|
||||||
srcRegion.X + srcRegion.Width,
|
srcRegion.X + srcRegion.Width,
|
||||||
srcRegion.Y + srcRegion.Height,
|
srcRegion.Y + srcRegion.Height,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for _, stencil := range []stencilMode{
|
for _, stencil := range []stencilMode{
|
||||||
@ -941,11 +952,13 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uniformVars = make([]interface{}, graphics.PreservedUniformVariablesNum+len(uniforms))
|
uniformVars = make([]driver.Uniform, graphics.PreservedUniformVariablesNum+len(uniforms))
|
||||||
|
|
||||||
// Set the destination texture size.
|
// Set the destination texture size.
|
||||||
dw, dh := dst.internalSize()
|
dw, dh := dst.internalSize()
|
||||||
uniformVars[graphics.DestinationTextureSizeUniformVariableIndex] = []float32{float32(dw), float32(dh)}
|
uniformVars[graphics.DestinationTextureSizeUniformVariableIndex] = driver.Uniform{
|
||||||
|
Float32s: []float32{float32(dw), float32(dh)},
|
||||||
|
}
|
||||||
|
|
||||||
// Set the source texture sizes.
|
// Set the source texture sizes.
|
||||||
usizes := make([]float32, 2*len(srcs))
|
usizes := make([]float32, 2*len(srcs))
|
||||||
@ -956,15 +969,21 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
|
|||||||
usizes[2*i+1] = float32(h)
|
usizes[2*i+1] = float32(h)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uniformVars[graphics.TextureSizesUniformVariableIndex] = usizes
|
uniformVars[graphics.TextureSizesUniformVariableIndex] = driver.Uniform{
|
||||||
|
Float32s: usizes,
|
||||||
|
}
|
||||||
|
|
||||||
// Set the destination region's origin.
|
// Set the destination region's origin.
|
||||||
udorigin := []float32{float32(dstRegion.X) / float32(dw), float32(dstRegion.Y) / float32(dh)}
|
udorigin := []float32{float32(dstRegion.X) / float32(dw), float32(dstRegion.Y) / float32(dh)}
|
||||||
uniformVars[graphics.TextureDestinationRegionOriginUniformVariableIndex] = udorigin
|
uniformVars[graphics.TextureDestinationRegionOriginUniformVariableIndex] = driver.Uniform{
|
||||||
|
Float32s: udorigin,
|
||||||
|
}
|
||||||
|
|
||||||
// Set the destination region's size.
|
// Set the destination region's size.
|
||||||
udsize := []float32{float32(dstRegion.Width) / float32(dw), float32(dstRegion.Height) / float32(dh)}
|
udsize := []float32{float32(dstRegion.Width) / float32(dw), float32(dstRegion.Height) / float32(dh)}
|
||||||
uniformVars[graphics.TextureDestinationRegionSizeUniformVariableIndex] = udsize
|
uniformVars[graphics.TextureDestinationRegionSizeUniformVariableIndex] = driver.Uniform{
|
||||||
|
Float32s: udsize,
|
||||||
|
}
|
||||||
|
|
||||||
// Set the source offsets.
|
// Set the source offsets.
|
||||||
uoffsets := make([]float32, 2*len(offsets))
|
uoffsets := make([]float32, 2*len(offsets))
|
||||||
@ -972,15 +991,21 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
|
|||||||
uoffsets[2*i] = offset[0]
|
uoffsets[2*i] = offset[0]
|
||||||
uoffsets[2*i+1] = offset[1]
|
uoffsets[2*i+1] = offset[1]
|
||||||
}
|
}
|
||||||
uniformVars[graphics.TextureSourceOffsetsUniformVariableIndex] = uoffsets
|
uniformVars[graphics.TextureSourceOffsetsUniformVariableIndex] = driver.Uniform{
|
||||||
|
Float32s: uoffsets,
|
||||||
|
}
|
||||||
|
|
||||||
// Set the source region's origin of texture0.
|
// Set the source region's origin of texture0.
|
||||||
usorigin := []float32{float32(srcRegion.X), float32(srcRegion.Y)}
|
usorigin := []float32{float32(srcRegion.X), float32(srcRegion.Y)}
|
||||||
uniformVars[graphics.TextureSourceRegionOriginUniformVariableIndex] = usorigin
|
uniformVars[graphics.TextureSourceRegionOriginUniformVariableIndex] = driver.Uniform{
|
||||||
|
Float32s: usorigin,
|
||||||
|
}
|
||||||
|
|
||||||
// Set the source region's size of texture0.
|
// Set the source region's size of texture0.
|
||||||
ussize := []float32{float32(srcRegion.Width), float32(srcRegion.Height)}
|
ussize := []float32{float32(srcRegion.Width), float32(srcRegion.Height)}
|
||||||
uniformVars[graphics.TextureSourceRegionSizeUniformVariableIndex] = ussize
|
uniformVars[graphics.TextureSourceRegionSizeUniformVariableIndex] = driver.Uniform{
|
||||||
|
Float32s: ussize,
|
||||||
|
}
|
||||||
|
|
||||||
// Set the additional uniform variables.
|
// Set the additional uniform variables.
|
||||||
for i, v := range uniforms {
|
for i, v := range uniforms {
|
||||||
|
@ -170,7 +170,7 @@ func (g *Graphics) uniformVariableName(idx int) string {
|
|||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shaderID driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, colorM driver.ColorM, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, uniforms []interface{}, evenOdd bool) error {
|
func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderImageNum]driver.ImageID, offsets [graphics.ShaderImageNum - 1][2]float32, shaderID driver.ShaderID, indexLen int, indexOffset int, mode driver.CompositeMode, colorM driver.ColorM, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, uniforms []driver.Uniform, evenOdd bool) error {
|
||||||
destination := g.images[dstID]
|
destination := g.images[dstID]
|
||||||
|
|
||||||
g.drawCalled = true
|
g.drawCalled = true
|
||||||
@ -197,14 +197,14 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
|
|||||||
dw, dh := destination.framebufferSize()
|
dw, dh := destination.framebufferSize()
|
||||||
g.uniformVars = append(g.uniformVars, uniformVariable{
|
g.uniformVars = append(g.uniformVars, uniformVariable{
|
||||||
name: "viewport_size",
|
name: "viewport_size",
|
||||||
value: uniformVariableValue{
|
value: driver.Uniform{
|
||||||
f32s: []float32{float32(dw), float32(dh)},
|
Float32s: []float32{float32(dw), float32(dh)},
|
||||||
},
|
},
|
||||||
typ: shaderir.Type{Main: shaderir.Vec2},
|
typ: shaderir.Type{Main: shaderir.Vec2},
|
||||||
}, uniformVariable{
|
}, uniformVariable{
|
||||||
name: "source_region",
|
name: "source_region",
|
||||||
value: uniformVariableValue{
|
value: driver.Uniform{
|
||||||
f32s: []float32{
|
Float32s: []float32{
|
||||||
srcRegion.X,
|
srcRegion.X,
|
||||||
srcRegion.Y,
|
srcRegion.Y,
|
||||||
srcRegion.X + srcRegion.Width,
|
srcRegion.X + srcRegion.Width,
|
||||||
@ -221,14 +221,14 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
|
|||||||
colorM.Elements(&esBody, &esTranslate)
|
colorM.Elements(&esBody, &esTranslate)
|
||||||
g.uniformVars = append(g.uniformVars, uniformVariable{
|
g.uniformVars = append(g.uniformVars, uniformVariable{
|
||||||
name: "color_matrix_body",
|
name: "color_matrix_body",
|
||||||
value: uniformVariableValue{
|
value: driver.Uniform{
|
||||||
f32s: esBody[:],
|
Float32s: esBody[:],
|
||||||
},
|
},
|
||||||
typ: shaderir.Type{Main: shaderir.Mat4},
|
typ: shaderir.Type{Main: shaderir.Mat4},
|
||||||
}, uniformVariable{
|
}, uniformVariable{
|
||||||
name: "color_matrix_translation",
|
name: "color_matrix_translation",
|
||||||
value: uniformVariableValue{
|
value: driver.Uniform{
|
||||||
f32s: esTranslate[:],
|
Float32s: esTranslate[:],
|
||||||
},
|
},
|
||||||
typ: shaderir.Type{Main: shaderir.Vec4},
|
typ: shaderir.Type{Main: shaderir.Vec4},
|
||||||
})
|
})
|
||||||
@ -238,8 +238,8 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
|
|||||||
sw, sh := g.images[srcIDs[0]].framebufferSize()
|
sw, sh := g.images[srcIDs[0]].framebufferSize()
|
||||||
g.uniformVars = append(g.uniformVars, uniformVariable{
|
g.uniformVars = append(g.uniformVars, uniformVariable{
|
||||||
name: "source_size",
|
name: "source_size",
|
||||||
value: uniformVariableValue{
|
value: driver.Uniform{
|
||||||
f32s: []float32{float32(sw), float32(sh)},
|
Float32s: []float32{float32(sw), float32(sh)},
|
||||||
},
|
},
|
||||||
typ: shaderir.Type{Main: shaderir.Vec2},
|
typ: shaderir.Type{Main: shaderir.Vec2},
|
||||||
})
|
})
|
||||||
@ -249,8 +249,8 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
|
|||||||
scale := float32(destination.width) / float32(g.images[srcIDs[0]].width)
|
scale := float32(destination.width) / float32(g.images[srcIDs[0]].width)
|
||||||
g.uniformVars = append(g.uniformVars, uniformVariable{
|
g.uniformVars = append(g.uniformVars, uniformVariable{
|
||||||
name: "scale",
|
name: "scale",
|
||||||
value: uniformVariableValue{
|
value: driver.Uniform{
|
||||||
f32: scale,
|
Float32: scale,
|
||||||
},
|
},
|
||||||
typ: shaderir.Type{Main: shaderir.Float},
|
typ: shaderir.Type{Main: shaderir.Float},
|
||||||
})
|
})
|
||||||
@ -270,7 +270,7 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
|
|||||||
const idx = graphics.DestinationTextureSizeUniformVariableIndex
|
const idx = graphics.DestinationTextureSizeUniformVariableIndex
|
||||||
w, h := destination.framebufferSize()
|
w, h := destination.framebufferSize()
|
||||||
g.uniformVars[idx].name = g.uniformVariableName(idx)
|
g.uniformVars[idx].name = g.uniformVariableName(idx)
|
||||||
g.uniformVars[idx].value.f32s = []float32{float32(w), float32(h)}
|
g.uniformVars[idx].value.Float32s = []float32{float32(w), float32(h)}
|
||||||
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
|
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@ -285,7 +285,7 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
|
|||||||
}
|
}
|
||||||
const idx = graphics.TextureSizesUniformVariableIndex
|
const idx = graphics.TextureSizesUniformVariableIndex
|
||||||
g.uniformVars[idx].name = g.uniformVariableName(idx)
|
g.uniformVars[idx].name = g.uniformVariableName(idx)
|
||||||
g.uniformVars[idx].value.f32s = sizes
|
g.uniformVars[idx].value.Float32s = sizes
|
||||||
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
|
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
|
||||||
}
|
}
|
||||||
dw, dh := destination.framebufferSize()
|
dw, dh := destination.framebufferSize()
|
||||||
@ -293,14 +293,14 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
|
|||||||
origin := []float32{float32(dstRegion.X) / float32(dw), float32(dstRegion.Y) / float32(dh)}
|
origin := []float32{float32(dstRegion.X) / float32(dw), float32(dstRegion.Y) / float32(dh)}
|
||||||
const idx = graphics.TextureDestinationRegionOriginUniformVariableIndex
|
const idx = graphics.TextureDestinationRegionOriginUniformVariableIndex
|
||||||
g.uniformVars[idx].name = g.uniformVariableName(idx)
|
g.uniformVars[idx].name = g.uniformVariableName(idx)
|
||||||
g.uniformVars[idx].value.f32s = origin
|
g.uniformVars[idx].value.Float32s = origin
|
||||||
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
|
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
size := []float32{float32(dstRegion.Width) / float32(dw), float32(dstRegion.Height) / float32(dh)}
|
size := []float32{float32(dstRegion.Width) / float32(dw), float32(dstRegion.Height) / float32(dh)}
|
||||||
const idx = graphics.TextureDestinationRegionSizeUniformVariableIndex
|
const idx = graphics.TextureDestinationRegionSizeUniformVariableIndex
|
||||||
g.uniformVars[idx].name = g.uniformVariableName(idx)
|
g.uniformVars[idx].name = g.uniformVariableName(idx)
|
||||||
g.uniformVars[idx].value.f32s = size
|
g.uniformVars[idx].value.Float32s = size
|
||||||
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
|
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@ -311,35 +311,28 @@ func (g *Graphics) DrawTriangles(dstID driver.ImageID, srcIDs [graphics.ShaderIm
|
|||||||
}
|
}
|
||||||
const idx = graphics.TextureSourceOffsetsUniformVariableIndex
|
const idx = graphics.TextureSourceOffsetsUniformVariableIndex
|
||||||
g.uniformVars[idx].name = g.uniformVariableName(idx)
|
g.uniformVars[idx].name = g.uniformVariableName(idx)
|
||||||
g.uniformVars[idx].value.f32s = voffsets
|
g.uniformVars[idx].value.Float32s = voffsets
|
||||||
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
|
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
origin := []float32{float32(srcRegion.X), float32(srcRegion.Y)}
|
origin := []float32{float32(srcRegion.X), float32(srcRegion.Y)}
|
||||||
const idx = graphics.TextureSourceRegionOriginUniformVariableIndex
|
const idx = graphics.TextureSourceRegionOriginUniformVariableIndex
|
||||||
g.uniformVars[idx].name = g.uniformVariableName(idx)
|
g.uniformVars[idx].name = g.uniformVariableName(idx)
|
||||||
g.uniformVars[idx].value.f32s = origin
|
g.uniformVars[idx].value.Float32s = origin
|
||||||
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
|
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
size := []float32{float32(srcRegion.Width), float32(srcRegion.Height)}
|
size := []float32{float32(srcRegion.Width), float32(srcRegion.Height)}
|
||||||
const idx = graphics.TextureSourceRegionSizeUniformVariableIndex
|
const idx = graphics.TextureSourceRegionSizeUniformVariableIndex
|
||||||
g.uniformVars[idx].name = g.uniformVariableName(idx)
|
g.uniformVars[idx].name = g.uniformVariableName(idx)
|
||||||
g.uniformVars[idx].value.f32s = size
|
g.uniformVars[idx].value.Float32s = size
|
||||||
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
|
g.uniformVars[idx].typ = shader.ir.Uniforms[idx]
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, v := range uniforms {
|
for i, v := range uniforms {
|
||||||
const offset = graphics.PreservedUniformVariablesNum
|
const offset = graphics.PreservedUniformVariablesNum
|
||||||
g.uniformVars[i+offset].name = fmt.Sprintf("U%d", i+offset)
|
g.uniformVars[i+offset].name = fmt.Sprintf("U%d", i+offset)
|
||||||
switch v := v.(type) {
|
g.uniformVars[i+offset].value = v
|
||||||
case float32:
|
|
||||||
g.uniformVars[i+offset].value.f32 = v
|
|
||||||
case []float32:
|
|
||||||
g.uniformVars[i+offset].value.f32s = v
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("opengl: unexpected uniform value: %v (type: %T)", v, v)
|
|
||||||
}
|
|
||||||
g.uniformVars[i+offset].typ = shader.ir.Uniforms[i+offset]
|
g.uniformVars[i+offset].typ = shader.ir.Uniforms[i+offset]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,11 +120,6 @@ type programKey struct {
|
|||||||
address driver.Address
|
address driver.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
type uniformVariableValue struct {
|
|
||||||
f32 float32
|
|
||||||
f32s []float32
|
|
||||||
}
|
|
||||||
|
|
||||||
// openGLState is a state for
|
// openGLState is a state for
|
||||||
type openGLState struct {
|
type openGLState struct {
|
||||||
// arrayBuffer is OpenGL's array buffer (vertices data).
|
// arrayBuffer is OpenGL's array buffer (vertices data).
|
||||||
@ -137,7 +132,7 @@ type openGLState struct {
|
|||||||
programs map[programKey]program
|
programs map[programKey]program
|
||||||
|
|
||||||
lastProgram program
|
lastProgram program
|
||||||
lastUniforms map[string]uniformVariableValue
|
lastUniforms map[string]driver.Uniform
|
||||||
lastActiveTexture int
|
lastActiveTexture int
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,7 +242,7 @@ func areSameFloat32Array(a, b []float32) bool {
|
|||||||
|
|
||||||
type uniformVariable struct {
|
type uniformVariable struct {
|
||||||
name string
|
name string
|
||||||
value uniformVariableValue
|
value driver.Uniform
|
||||||
typ shaderir.Type
|
typ shaderir.Type
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,7 +270,7 @@ func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textu
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, u := range uniforms {
|
for _, u := range uniforms {
|
||||||
if len(u.value.f32s) == 0 {
|
if len(u.value.Float32s) == 0 {
|
||||||
if u.typ.Main != shaderir.Float {
|
if u.typ.Main != shaderir.Float {
|
||||||
expected := &shaderir.Type{Main: shaderir.Float}
|
expected := &shaderir.Type{Main: shaderir.Float}
|
||||||
got := &u.typ
|
got := &u.typ
|
||||||
@ -283,33 +278,29 @@ func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textu
|
|||||||
}
|
}
|
||||||
|
|
||||||
cached, ok := g.state.lastUniforms[u.name]
|
cached, ok := g.state.lastUniforms[u.name]
|
||||||
if ok && cached.f32 == u.value.f32 {
|
if ok && cached.Float32 == u.value.Float32 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// TODO: Remember whether the location is available or not.
|
// TODO: Remember whether the location is available or not.
|
||||||
g.context.uniformFloat(program, u.name, u.value.f32)
|
g.context.uniformFloat(program, u.name, u.value.Float32)
|
||||||
if g.state.lastUniforms == nil {
|
if g.state.lastUniforms == nil {
|
||||||
g.state.lastUniforms = map[string]uniformVariableValue{}
|
g.state.lastUniforms = map[string]driver.Uniform{}
|
||||||
}
|
|
||||||
g.state.lastUniforms[u.name] = uniformVariableValue{
|
|
||||||
f32: u.value.f32,
|
|
||||||
}
|
}
|
||||||
|
g.state.lastUniforms[u.name] = u.value
|
||||||
} else {
|
} else {
|
||||||
if got, expected := len(u.value.f32s), u.typ.FloatNum(); got != expected {
|
if got, expected := len(u.value.Float32s), u.typ.FloatNum(); got != expected {
|
||||||
return fmt.Errorf("opengl: length of a uniform variables %s (%s) doesn't match: expected %d but %d", u.name, u.typ.String(), expected, got)
|
return fmt.Errorf("opengl: length of a uniform variables %s (%s) doesn't match: expected %d but %d", u.name, u.typ.String(), expected, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
cached, ok := g.state.lastUniforms[u.name]
|
cached, ok := g.state.lastUniforms[u.name]
|
||||||
if ok && areSameFloat32Array(cached.f32s, u.value.f32s) {
|
if ok && areSameFloat32Array(cached.Float32s, u.value.Float32s) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
g.context.uniformFloats(program, u.name, u.value.f32s, u.typ)
|
g.context.uniformFloats(program, u.name, u.value.Float32s, u.typ)
|
||||||
if g.state.lastUniforms == nil {
|
if g.state.lastUniforms == nil {
|
||||||
g.state.lastUniforms = map[string]uniformVariableValue{}
|
g.state.lastUniforms = map[string]driver.Uniform{}
|
||||||
}
|
|
||||||
g.state.lastUniforms[u.name] = uniformVariableValue{
|
|
||||||
f32s: u.value.f32s,
|
|
||||||
}
|
}
|
||||||
|
g.state.lastUniforms[u.name] = u.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ func (m *Mipmap) Pixels(x, y, width, height int) ([]byte, error) {
|
|||||||
return m.orig.Pixels(x, y, width, height)
|
return m.orig.Pixels(x, y, width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageNum]*Mipmap, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []interface{}, evenOdd bool, canSkipMipmap bool) {
|
func (m *Mipmap) DrawTriangles(srcs [graphics.ShaderImageNum]*Mipmap, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, subimageOffsets [graphics.ShaderImageNum - 1][2]float32, shader *Shader, uniforms []driver.Uniform, evenOdd bool, canSkipMipmap bool) {
|
||||||
if len(indices) == 0 {
|
if len(indices) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ type drawTrianglesHistoryItem struct {
|
|||||||
dstRegion driver.Region
|
dstRegion driver.Region
|
||||||
srcRegion driver.Region
|
srcRegion driver.Region
|
||||||
shader *Shader
|
shader *Shader
|
||||||
uniforms []interface{}
|
uniforms []driver.Uniform
|
||||||
evenOdd bool
|
evenOdd bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -365,7 +365,7 @@ func (i *Image) ReplacePixels(pixels []byte, x, y, width, height int) {
|
|||||||
// 5: Color G
|
// 5: Color G
|
||||||
// 6: Color B
|
// 6: Color B
|
||||||
// 7: Color Y
|
// 7: Color Y
|
||||||
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) {
|
func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []driver.Uniform, evenOdd bool) {
|
||||||
if i.priority {
|
if i.priority {
|
||||||
panic("restorable: DrawTriangles cannot be called on a priority image")
|
panic("restorable: DrawTriangles cannot be called on a priority image")
|
||||||
}
|
}
|
||||||
@ -410,7 +410,7 @@ func (i *Image) DrawTriangles(srcs [graphics.ShaderImageNum]*Image, offsets [gra
|
|||||||
}
|
}
|
||||||
|
|
||||||
// appendDrawTrianglesHistory appends a draw-image history item to the image.
|
// appendDrawTrianglesHistory appends a draw-image history item to the image.
|
||||||
func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []interface{}, evenOdd bool) {
|
func (i *Image) appendDrawTrianglesHistory(srcs [graphics.ShaderImageNum]*Image, offsets [graphics.ShaderImageNum - 1][2]float32, vertices []float32, indices []uint16, colorm affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, dstRegion, srcRegion driver.Region, shader *Shader, uniforms []driver.Uniform, evenOdd bool) {
|
||||||
if i.stale || i.volatile || i.screen {
|
if i.stale || i.volatile || i.screen {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
46
shader.go
46
shader.go
@ -21,6 +21,7 @@ import (
|
|||||||
"go/token"
|
"go/token"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/internal/driver"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/mipmap"
|
"github.com/hajimehoshi/ebiten/v2/internal/mipmap"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/shader"
|
"github.com/hajimehoshi/ebiten/v2/internal/shader"
|
||||||
@ -172,7 +173,7 @@ func (s *Shader) Dispose() {
|
|||||||
s.shader = nil
|
s.shader = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Shader) convertUniforms(uniforms map[string]interface{}) []interface{} {
|
func (s *Shader) convertUniforms(uniforms map[string]interface{}) []driver.Uniform {
|
||||||
type index struct {
|
type index struct {
|
||||||
resultIndex int
|
resultIndex int
|
||||||
shaderUniformIndex int
|
shaderUniformIndex int
|
||||||
@ -191,20 +192,26 @@ func (s *Shader) convertUniforms(uniforms map[string]interface{}) []interface{}
|
|||||||
idx++
|
idx++
|
||||||
}
|
}
|
||||||
|
|
||||||
us := make([]interface{}, len(names))
|
us := make([]driver.Uniform, len(names))
|
||||||
for name, idx := range names {
|
for name, idx := range names {
|
||||||
if v, ok := uniforms[name]; ok {
|
if v, ok := uniforms[name]; ok {
|
||||||
// TODO: Check the uniform variable types?
|
switch v := v.(type) {
|
||||||
us[idx.resultIndex] = v
|
case float32:
|
||||||
|
us[idx.resultIndex] = driver.Uniform{
|
||||||
|
Float32: v,
|
||||||
|
}
|
||||||
|
case []float32:
|
||||||
|
us[idx.resultIndex] = driver.Uniform{
|
||||||
|
Float32s: v,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("ebiten: unexpected uniform value type: %s, %T", name, v))
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
t := s.uniformTypes[idx.shaderUniformIndex]
|
t := s.uniformTypes[idx.shaderUniformIndex]
|
||||||
v := zeroUniformValue(t)
|
us[idx.resultIndex] = zeroUniformValue(name, t)
|
||||||
if v == nil {
|
|
||||||
panic(fmt.Sprintf("ebiten: unexpected uniform variable type: %s", t.String()))
|
|
||||||
}
|
|
||||||
us[idx.resultIndex] = v
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Panic if uniforms include an invalid name
|
// TODO: Panic if uniforms include an invalid name
|
||||||
@ -212,24 +219,17 @@ func (s *Shader) convertUniforms(uniforms map[string]interface{}) []interface{}
|
|||||||
return us
|
return us
|
||||||
}
|
}
|
||||||
|
|
||||||
func zeroUniformValue(t shaderir.Type) interface{} {
|
func zeroUniformValue(name string, t shaderir.Type) driver.Uniform {
|
||||||
switch t.Main {
|
switch t.Main {
|
||||||
case shaderir.Bool:
|
|
||||||
return false
|
|
||||||
case shaderir.Int:
|
|
||||||
return 0
|
|
||||||
case shaderir.Float:
|
case shaderir.Float:
|
||||||
return float32(0)
|
return driver.Uniform{
|
||||||
|
Float32: 0,
|
||||||
|
}
|
||||||
case shaderir.Array:
|
case shaderir.Array:
|
||||||
switch t.Sub[0].Main {
|
return driver.Uniform{
|
||||||
case shaderir.Bool:
|
Float32s: make([]float32, t.FloatNum()),
|
||||||
return make([]bool, t.Length)
|
|
||||||
case shaderir.Int:
|
|
||||||
return make([]int, t.Length)
|
|
||||||
default:
|
|
||||||
return make([]float32, t.FloatNum())
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return make([]float32, t.FloatNum())
|
panic(fmt.Sprintf("ebiten: unexpected uniform variable type: %s, %s", name, t.String()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user