mirror of
https://github.com/hajimehoshi/ebiten.git
synced 2024-11-10 04:57:26 +01:00
Compare commits
No commits in common. "c3a358b44b7fb6099956bfad4f9a0435f8c5c4f3" and "577664c1cbb5d8731536283839b7ed3095ef5f79" have entirely different histories.
c3a358b44b
...
577664c1cb
@ -15,13 +15,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"image"
|
"image"
|
||||||
_ "image/jpeg"
|
_ "image/jpeg"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -73,9 +71,11 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Game struct {
|
type Game struct {
|
||||||
|
count int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Update() error {
|
func (g *Game) Update() error {
|
||||||
|
g.count++
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,8 +114,6 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
|||||||
opts.GeoM.Reset()
|
opts.GeoM.Reset()
|
||||||
opts.GeoM.Translate(dstSize, dstSize)
|
opts.GeoM.Translate(dstSize, dstSize)
|
||||||
screen.DrawImage(dsts[3], opts)
|
screen.DrawImage(dsts[3], opts)
|
||||||
|
|
||||||
ebitenutil.DebugPrint(screen, fmt.Sprintf("FPS: %.2f", ebiten.ActualFPS()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
||||||
@ -124,10 +122,9 @@ func (g *Game) Layout(outsideWidth, outsideHeight int) (int, int) {
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ebiten.SetWindowSize(screenWidth, screenHeight)
|
ebiten.SetWindowSize(screenWidth, screenHeight)
|
||||||
ebiten.SetVsyncEnabled(false)
|
|
||||||
ebiten.SetWindowTitle("MRT (Ebitengine Demo)")
|
ebiten.SetWindowTitle("MRT (Ebitengine Demo)")
|
||||||
if err := ebiten.RunGameWithOptions(&Game{}, &ebiten.RunGameOptions{
|
if err := ebiten.RunGameWithOptions(&Game{}, &ebiten.RunGameOptions{
|
||||||
GraphicsLibrary: ebiten.GraphicsLibraryOpenGL,
|
GraphicsLibrary: ebiten.GraphicsLibraryDirectX,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -128,10 +128,6 @@ package gl
|
|||||||
// typedef void (*fn)(GLuint index);
|
// typedef void (*fn)(GLuint index);
|
||||||
// ((fn)(fnptr))(index);
|
// ((fn)(fnptr))(index);
|
||||||
// }
|
// }
|
||||||
// static void glowDrawBuffers(uintptr_t fnptr, GLsizei n, const GLenum* bufs) {
|
|
||||||
// typedef void (*fn)(GLsizei n, const GLenum* bufs);
|
|
||||||
// ((fn)(fnptr))(n, bufs);
|
|
||||||
// }
|
|
||||||
// static void glowDrawElements(uintptr_t fnptr, GLenum mode, GLsizei count, GLenum type, const uintptr_t indices) {
|
// static void glowDrawElements(uintptr_t fnptr, GLenum mode, GLsizei count, GLenum type, const uintptr_t indices) {
|
||||||
// typedef void (*fn)(GLenum mode, GLsizei count, GLenum type, const uintptr_t indices);
|
// typedef void (*fn)(GLenum mode, GLsizei count, GLenum type, const uintptr_t indices);
|
||||||
// ((fn)(fnptr))(mode, count, type, indices);
|
// ((fn)(fnptr))(mode, count, type, indices);
|
||||||
@ -355,7 +351,6 @@ type defaultContext struct {
|
|||||||
gpDeleteVertexArrays C.uintptr_t
|
gpDeleteVertexArrays C.uintptr_t
|
||||||
gpDisable C.uintptr_t
|
gpDisable C.uintptr_t
|
||||||
gpDisableVertexAttribArray C.uintptr_t
|
gpDisableVertexAttribArray C.uintptr_t
|
||||||
gpDrawBuffers C.uintptr_t
|
|
||||||
gpDrawElements C.uintptr_t
|
gpDrawElements C.uintptr_t
|
||||||
gpEnable C.uintptr_t
|
gpEnable C.uintptr_t
|
||||||
gpEnableVertexAttribArray C.uintptr_t
|
gpEnableVertexAttribArray C.uintptr_t
|
||||||
@ -570,10 +565,6 @@ func (c *defaultContext) DisableVertexAttribArray(index uint32) {
|
|||||||
C.glowDisableVertexAttribArray(c.gpDisableVertexAttribArray, C.GLuint(index))
|
C.glowDisableVertexAttribArray(c.gpDisableVertexAttribArray, C.GLuint(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *defaultContext) DrawBuffers(bufs []uint32) {
|
|
||||||
C.glowDrawBuffers(c.gpDrawBuffers, C.GLsizei(len(bufs)), (*C.GLenum)(unsafe.Pointer(&bufs[0])))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *defaultContext) DrawElements(mode uint32, count int32, xtype uint32, offset int) {
|
func (c *defaultContext) DrawElements(mode uint32, count int32, xtype uint32, offset int) {
|
||||||
C.glowDrawElements(c.gpDrawElements, C.GLenum(mode), C.GLsizei(count), C.GLenum(xtype), C.uintptr_t(offset))
|
C.glowDrawElements(c.gpDrawElements, C.GLenum(mode), C.GLsizei(count), C.GLenum(xtype), C.uintptr_t(offset))
|
||||||
}
|
}
|
||||||
@ -810,7 +801,6 @@ func (c *defaultContext) LoadFunctions() error {
|
|||||||
c.gpDeleteVertexArrays = C.uintptr_t(g.get("glDeleteVertexArrays"))
|
c.gpDeleteVertexArrays = C.uintptr_t(g.get("glDeleteVertexArrays"))
|
||||||
c.gpDisable = C.uintptr_t(g.get("glDisable"))
|
c.gpDisable = C.uintptr_t(g.get("glDisable"))
|
||||||
c.gpDisableVertexAttribArray = C.uintptr_t(g.get("glDisableVertexAttribArray"))
|
c.gpDisableVertexAttribArray = C.uintptr_t(g.get("glDisableVertexAttribArray"))
|
||||||
c.gpDrawBuffers = C.uintptr_t(g.get("glDrawBuffers"))
|
|
||||||
c.gpDrawElements = C.uintptr_t(g.get("glDrawElements"))
|
c.gpDrawElements = C.uintptr_t(g.get("glDrawElements"))
|
||||||
c.gpEnable = C.uintptr_t(g.get("glEnable"))
|
c.gpEnable = C.uintptr_t(g.get("glEnable"))
|
||||||
c.gpEnableVertexAttribArray = C.uintptr_t(g.get("glEnableVertexAttribArray"))
|
c.gpEnableVertexAttribArray = C.uintptr_t(g.get("glEnableVertexAttribArray"))
|
||||||
|
@ -47,6 +47,7 @@ type Graphics struct {
|
|||||||
|
|
||||||
uniformVariableNameCache map[int]string
|
uniformVariableNameCache map[int]string
|
||||||
textureVariableNameCache map[int]string
|
textureVariableNameCache map[int]string
|
||||||
|
colorBufferVariableNameCache map[int]string
|
||||||
|
|
||||||
uniformVars []uniformVariable
|
uniformVars []uniformVariable
|
||||||
|
|
||||||
|
@ -258,6 +258,18 @@ func (g *Graphics) textureVariableName(idx int) string {
|
|||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Graphics) colorBufferVariableName(idx int) string {
|
||||||
|
if v, ok := g.colorBufferVariableNameCache[idx]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if g.colorBufferVariableNameCache == nil {
|
||||||
|
g.colorBufferVariableNameCache = map[int]string{}
|
||||||
|
}
|
||||||
|
name := fmt.Sprintf("gl_FragData[%d]", idx)
|
||||||
|
g.colorBufferVariableNameCache[idx] = name
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
// useProgram uses the program (programTexture).
|
// useProgram uses the program (programTexture).
|
||||||
func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textures [graphics.ShaderSrcImageCount]textureVariable) error {
|
func (g *Graphics) useProgram(program program, uniforms []uniformVariable, textures [graphics.ShaderSrcImageCount]textureVariable) error {
|
||||||
if g.state.lastProgram != program {
|
if g.state.lastProgram != program {
|
||||||
|
@ -822,12 +822,10 @@ func (cs *compileState) parseFunc(block *block, d *ast.FuncDecl) (function, bool
|
|||||||
return function{}, false
|
return function{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// The first out-param is treated as fragColor0 in GLSL.
|
// The first out-param is treated as gl_FragColor in GLSL.
|
||||||
for i := range outParams {
|
if outParams[0].typ.Main != shaderir.Vec4 {
|
||||||
if outParams[i].typ.Main != shaderir.Vec4 {
|
cs.addError(d.Pos(), "fragment entry point must have at least one returning vec4 value for a color")
|
||||||
cs.addError(d.Pos(), "fragment entry point must only have vec4 return values for colors")
|
return function{}, false
|
||||||
return function{}, false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Adjust the number of textures to write to
|
// Adjust the number of textures to write to
|
||||||
cs.ir.ColorsOutCount = len(outParams)
|
cs.ir.ColorsOutCount = len(outParams)
|
||||||
|
@ -334,8 +334,7 @@ func (cs *compileState) parseStmt(block *block, fname string, stmt ast.Stmt, inP
|
|||||||
|
|
||||||
case *ast.ReturnStmt:
|
case *ast.ReturnStmt:
|
||||||
if len(stmt.Results) != len(outParams) && len(stmt.Results) != 1 {
|
if len(stmt.Results) != len(outParams) && len(stmt.Results) != 1 {
|
||||||
// Fragment function does not have to return a value due to discard
|
if !(len(stmt.Results) == 0 && len(outParams) > 0 && outParams[0].name != "") {
|
||||||
if fname != cs.fragmentEntry && !(len(stmt.Results) == 0 && len(outParams) > 0 && outParams[0].name != "") {
|
|
||||||
// TODO: Check variable shadowings.
|
// TODO: Check variable shadowings.
|
||||||
// https://go.dev/ref/spec#Return_statements
|
// https://go.dev/ref/spec#Return_statements
|
||||||
cs.addError(stmt.Pos(), fmt.Sprintf("the number of returning variables must be %d but %d", len(outParams), len(stmt.Results)))
|
cs.addError(stmt.Pos(), fmt.Sprintf("the number of returning variables must be %d but %d", len(outParams), len(stmt.Results)))
|
||||||
|
@ -86,7 +86,8 @@ precision highp int;
|
|||||||
#define lowp
|
#define lowp
|
||||||
#define mediump
|
#define mediump
|
||||||
#define highp
|
#define highp
|
||||||
#endif`
|
#endif
|
||||||
|
`
|
||||||
if version == GLSLVersionDefault {
|
if version == GLSLVersionDefault {
|
||||||
prelude += "\n\n" + utilFunctions
|
prelude += "\n\n" + utilFunctions
|
||||||
}
|
}
|
||||||
@ -290,8 +291,6 @@ func Compile(p *shaderir.Program, version GLSLVersion) (vertexShader, fragmentSh
|
|||||||
vs = strings.TrimSpace(vs) + "\n"
|
vs = strings.TrimSpace(vs) + "\n"
|
||||||
fs = strings.TrimSpace(fs) + "\n"
|
fs = strings.TrimSpace(fs) + "\n"
|
||||||
|
|
||||||
fmt.Println("FS:", fs)
|
|
||||||
|
|
||||||
return vs, fs
|
return vs, fs
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -604,7 +603,7 @@ func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Bl
|
|||||||
}
|
}
|
||||||
case shaderir.Discard:
|
case shaderir.Discard:
|
||||||
// 'discard' is invoked only in the fragment shader entry point.
|
// 'discard' is invoked only in the fragment shader entry point.
|
||||||
lines = append(lines, idt+"discard;") //, idt+"return vec4(0.0);")
|
lines = append(lines, idt+"discard;", idt+"return vec4(0.0);")
|
||||||
default:
|
default:
|
||||||
lines = append(lines, fmt.Sprintf("%s?(unexpected stmt: %d)", idt, s.Type))
|
lines = append(lines, fmt.Sprintf("%s?(unexpected stmt: %d)", idt, s.Type))
|
||||||
}
|
}
|
||||||
@ -652,7 +651,7 @@ func adjustProgram(p *shaderir.Program) *shaderir.Program {
|
|||||||
Main: shaderir.Vec4,
|
Main: shaderir.Vec4,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
newP.FragmentFunc.Block.LocalVarIndexOffset += (p.ColorsOutCount - 1)
|
newP.FragmentFunc.Block.LocalVarIndexOffset += (p.ColorsOutCount-1)
|
||||||
|
|
||||||
newP.Funcs = append(newP.Funcs, shaderir.Func{
|
newP.Funcs = append(newP.Funcs, shaderir.Func{
|
||||||
Index: funcIdx,
|
Index: funcIdx,
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten/v2/internal/graphics"
|
||||||
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
|
"github.com/hajimehoshi/ebiten/v2/internal/shaderir"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -88,7 +89,6 @@ float4x4 float4x4FromScalar(float x) {
|
|||||||
|
|
||||||
func Compile(p *shaderir.Program) (vertexShader, pixelShader string, offsets []int) {
|
func Compile(p *shaderir.Program) (vertexShader, pixelShader string, offsets []int) {
|
||||||
offsets = calculateMemoryOffsets(p.Uniforms)
|
offsets = calculateMemoryOffsets(p.Uniforms)
|
||||||
p = adjustProgram(p)
|
|
||||||
|
|
||||||
c := &compileContext{
|
c := &compileContext{
|
||||||
unit: p.Unit,
|
unit: p.Unit,
|
||||||
@ -193,7 +193,7 @@ func Compile(p *shaderir.Program) (vertexShader, pixelShader string, offsets []i
|
|||||||
pslines = append(pslines, "")
|
pslines = append(pslines, "")
|
||||||
pslines = append(pslines, "struct PS_OUTPUT")
|
pslines = append(pslines, "struct PS_OUTPUT")
|
||||||
pslines = append(pslines, "{")
|
pslines = append(pslines, "{")
|
||||||
for i := 0; i < p.ColorsOutCount; i++ {
|
for i := 0; i < graphics.ShaderDstImageCount; i++ {
|
||||||
pslines = append(pslines, fmt.Sprintf("\tfloat4 Color%d: SV_Target%d;", i, i))
|
pslines = append(pslines, fmt.Sprintf("\tfloat4 Color%d: SV_Target%d;", i, i))
|
||||||
}
|
}
|
||||||
pslines = append(pslines, "};")
|
pslines = append(pslines, "};")
|
||||||
@ -404,9 +404,6 @@ func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Bl
|
|||||||
}
|
}
|
||||||
|
|
||||||
var lines []string
|
var lines []string
|
||||||
for i := range block.LocalVars {
|
|
||||||
lines = append(lines, c.initVariable(p, topBlock, block, block.LocalVarIndexOffset+i, true, level)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
var expr func(e *shaderir.Expr) string
|
var expr func(e *shaderir.Expr) string
|
||||||
expr = func(e *shaderir.Expr) string {
|
expr = func(e *shaderir.Expr) string {
|
||||||
@ -513,7 +510,8 @@ func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Bl
|
|||||||
case shaderir.Assign:
|
case shaderir.Assign:
|
||||||
lhs := s.Exprs[0]
|
lhs := s.Exprs[0]
|
||||||
rhs := s.Exprs[1]
|
rhs := s.Exprs[1]
|
||||||
if lhs.Type == shaderir.LocalVariable {
|
isOutput := strings.HasPrefix(expr(&lhs), "output.Color")
|
||||||
|
if !isOutput && lhs.Type == shaderir.LocalVariable {
|
||||||
if t := p.LocalVariableType(topBlock, block, lhs.Index); t.Main == shaderir.Array {
|
if t := p.LocalVariableType(topBlock, block, lhs.Index); t.Main == shaderir.Array {
|
||||||
for i := 0; i < t.Length; i++ {
|
for i := 0; i < t.Length; i++ {
|
||||||
lines = append(lines, fmt.Sprintf("%[1]s%[2]s[%[3]d] = %[4]s[%[3]d];", idt, expr(&lhs), i, expr(&rhs)))
|
lines = append(lines, fmt.Sprintf("%[1]s%[2]s[%[3]d] = %[4]s[%[3]d];", idt, expr(&lhs), i, expr(&rhs)))
|
||||||
@ -574,18 +572,14 @@ func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Bl
|
|||||||
switch {
|
switch {
|
||||||
case topBlock == p.VertexFunc.Block:
|
case topBlock == p.VertexFunc.Block:
|
||||||
lines = append(lines, fmt.Sprintf("%sreturn %s;", idt, vsOut))
|
lines = append(lines, fmt.Sprintf("%sreturn %s;", idt, vsOut))
|
||||||
case topBlock == p.FragmentFunc.Block:
|
|
||||||
// Call to the pseudo fragment func based on out parameters
|
|
||||||
lines = append(lines, idt+expr(&s.Exprs[0])+";")
|
|
||||||
lines = append(lines, idt+"return output;")
|
|
||||||
case len(s.Exprs) == 0:
|
case len(s.Exprs) == 0:
|
||||||
lines = append(lines, idt+"return;")
|
lines = append(lines, idt+"return output;")
|
||||||
default:
|
default:
|
||||||
lines = append(lines, fmt.Sprintf("%sreturn %s;", idt, expr(&s.Exprs[0])))
|
lines = append(lines, fmt.Sprintf("%sreturn %s;", idt, expr(&s.Exprs[0])))
|
||||||
}
|
}
|
||||||
case shaderir.Discard:
|
case shaderir.Discard:
|
||||||
// 'discard' is invoked only in the fragment shader entry point.
|
// 'discard' is invoked only in the fragment shader entry point.
|
||||||
lines = append(lines, idt+"discard;")
|
lines = append(lines, idt+"discard;", idt+"return float4(0.0, 0.0, 0.0, 0.0);")
|
||||||
default:
|
default:
|
||||||
lines = append(lines, fmt.Sprintf("%s?(unexpected stmt: %d)", idt, s.Type))
|
lines = append(lines, fmt.Sprintf("%s?(unexpected stmt: %d)", idt, s.Type))
|
||||||
}
|
}
|
||||||
@ -593,86 +587,3 @@ func (c *compileContext) block(p *shaderir.Program, topBlock, block *shaderir.Bl
|
|||||||
|
|
||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
func adjustProgram(p *shaderir.Program) *shaderir.Program {
|
|
||||||
if p.FragmentFunc.Block == nil {
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shallow-clone the program in order not to modify p itself.
|
|
||||||
newP := *p
|
|
||||||
|
|
||||||
// Create a new slice not to affect the original p.
|
|
||||||
newP.Funcs = make([]shaderir.Func, len(p.Funcs))
|
|
||||||
copy(newP.Funcs, p.Funcs)
|
|
||||||
|
|
||||||
// Create a new function whose body is the same is the fragment shader's entry point.
|
|
||||||
// Determine a unique index of the new function.
|
|
||||||
var funcIdx int
|
|
||||||
for _, f := range newP.Funcs {
|
|
||||||
if funcIdx <= f.Index {
|
|
||||||
funcIdx = f.Index + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For parameters of a fragment func, see the comment in internal/shaderir/program.go.
|
|
||||||
inParams := make([]shaderir.Type, 1+len(newP.Varyings))
|
|
||||||
inParams[0] = shaderir.Type{
|
|
||||||
Main: shaderir.Vec4, // gl_FragCoord
|
|
||||||
}
|
|
||||||
copy(inParams[1:], newP.Varyings)
|
|
||||||
// Out parameters of a fragment func are colors
|
|
||||||
outParams := make([]shaderir.Type, p.ColorsOutCount)
|
|
||||||
for i := range outParams {
|
|
||||||
outParams[i] = shaderir.Type{
|
|
||||||
Main: shaderir.Vec4,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newP.FragmentFunc.Block.LocalVarIndexOffset += (p.ColorsOutCount - 1)
|
|
||||||
|
|
||||||
newP.Funcs = append(newP.Funcs, shaderir.Func{
|
|
||||||
Index: funcIdx,
|
|
||||||
InParams: inParams,
|
|
||||||
OutParams: outParams,
|
|
||||||
Block: newP.FragmentFunc.Block,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create an AST to call the new function.
|
|
||||||
call := []shaderir.Expr{
|
|
||||||
{
|
|
||||||
Type: shaderir.FunctionExpr,
|
|
||||||
Index: funcIdx,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for i := 0; i < 1+len(newP.Varyings)+p.ColorsOutCount; i++ {
|
|
||||||
call = append(call, shaderir.Expr{
|
|
||||||
Type: shaderir.LocalVariable,
|
|
||||||
Index: i,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace the entry point with just calling the new function.
|
|
||||||
stmts := []shaderir.Stmt{
|
|
||||||
{
|
|
||||||
// Return: This will be replaced with a call to the new function.
|
|
||||||
// Then the output structure containing colors will be returned.
|
|
||||||
Type: shaderir.Return,
|
|
||||||
Exprs: []shaderir.Expr{
|
|
||||||
// The function call
|
|
||||||
{
|
|
||||||
Type: shaderir.Call,
|
|
||||||
Exprs: call,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
newP.FragmentFunc = shaderir.FragmentFunc{
|
|
||||||
Block: &shaderir.Block{
|
|
||||||
LocalVars: nil,
|
|
||||||
LocalVarIndexOffset: 1 + len(newP.Varyings) + 1,
|
|
||||||
Stmts: stmts,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return &newP
|
|
||||||
}
|
|
||||||
|
Loading…
Reference in New Issue
Block a user