From 933ee5826060c2e7e2d7fc620d68a29334573fb7 Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Tue, 15 Aug 2023 21:18:14 +0200 Subject: [PATCH 1/9] Fix freshly imported beatmaps having missing strain graph in launcher --- app/beatmap/beatmap.go | 2 +- app/beatmap/objects/timing.go | 7 +++++++ app/beatmap/parser.go | 7 ++++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/beatmap/beatmap.go b/app/beatmap/beatmap.go index a4944505..79e9f92b 100644 --- a/app/beatmap/beatmap.go +++ b/app/beatmap/beatmap.go @@ -88,7 +88,7 @@ func (beatMap *BeatMap) Reset() { func (beatMap *BeatMap) Clear() { beatMap.HitObjects = make([]objects.IHitObject, 0) - beatMap.Timings = objects.NewTimings() + beatMap.Timings.Clear() } func (beatMap *BeatMap) Update(time float64) { diff --git a/app/beatmap/objects/timing.go b/app/beatmap/objects/timing.go index adb3ae93..58f6db21 100644 --- a/app/beatmap/objects/timing.go +++ b/app/beatmap/objects/timing.go @@ -162,6 +162,13 @@ func (tim *Timings) HasPoints() bool { return len(tim.points) > 0 } +func (tim *Timings) Clear() { + tim.originalPoints = tim.originalPoints[:0] + tim.points = tim.points[:0] + + tim.Current = tim.defaultTimingPoint +} + func (tim *Timings) Reset() { tim.Current = tim.points[0] } diff --git a/app/beatmap/parser.go b/app/beatmap/parser.go index 9b71e03a..3b19e771 100644 --- a/app/beatmap/parser.go +++ b/app/beatmap/parser.go @@ -1,6 +1,7 @@ package beatmap import ( + "cmp" "errors" "github.com/wieku/danser-go/app/beatmap/objects" "github.com/wieku/danser-go/app/settings" @@ -10,7 +11,7 @@ import ( "math" "os" "path/filepath" - "sort" + "slices" "strconv" "strings" "sync" @@ -342,8 +343,8 @@ func ParseObjects(beatMap *BeatMap, diffCalcOnly, parseColors bool) { } } - sort.SliceStable(beatMap.HitObjects, func(i, j int) bool { - return beatMap.HitObjects[i].GetStartTime() < beatMap.HitObjects[j].GetStartTime() + slices.SortStableFunc(beatMap.HitObjects, func(a, b objects.IHitObject) int { + return cmp.Compare(a.GetStartTime(), b.GetStartTime()) }) if parseColors { From 6b1a37173510dbdb151b6513dd8f8da98ce1531f Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Tue, 15 Aug 2023 21:50:48 +0200 Subject: [PATCH 2/9] Show approx allocs/s in debug --- app/states/player.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/app/states/player.go b/app/states/player.go index 02ab5b0b..4ef1126a 100644 --- a/app/states/player.go +++ b/app/states/player.go @@ -124,10 +124,17 @@ type Player struct { failing bool failAt float64 failed bool + + mProfiler *frame.Counter + mStats1 *runtime.MemStats + mStats2 *runtime.MemStats } func NewPlayer(beatMap *beatmap.BeatMap) *Player { player := new(Player) + player.mProfiler = frame.NewCounter() + player.mStats1 = new(runtime.MemStats) + player.mStats2 = new(runtime.MemStats) graphics.LoadTextures() @@ -1030,13 +1037,20 @@ func (player *Player) drawDebug() { pos++ drawWithBackground("Memory:") - var m runtime.MemStats - runtime.ReadMemStats(&m) + runtime.ReadMemStats(player.mStats2) + + mDelta := float64(int64(player.mStats2.Alloc) - int64(player.mStats1.Alloc)) + if mDelta > 0 { + player.mProfiler.PutSample(mDelta / 1000) + } + + drawWithBackground(fmt.Sprintf("Allocated: %s", humanize.Bytes(player.mStats2.Alloc))) + drawWithBackground(fmt.Sprintf("Allocs/s: %s", humanize.Bytes(uint64(player.mProfiler.GetAverage()*player.profiler.GetFPS()*1000)))) + drawWithBackground(fmt.Sprintf("System: %s", humanize.Bytes(player.mStats2.Sys))) + drawWithBackground(fmt.Sprintf("GC Runs: %d", player.mStats2.NumGC)) + drawWithBackground(fmt.Sprintf("GC Time: %.3fms", float64(player.mStats2.PauseTotalNs)/1000000)) - drawWithBackground(fmt.Sprintf("Allocated: %s", humanize.Bytes(m.Alloc))) - drawWithBackground(fmt.Sprintf("System: %s", humanize.Bytes(m.Sys))) - drawWithBackground(fmt.Sprintf("GC Runs: %d", m.NumGC)) - drawWithBackground(fmt.Sprintf("GC Time: %.3fms", float64(m.PauseTotalNs)/1000000)) + player.mStats1, player.mStats2 = player.mStats2, player.mStats1 player.batch.ResetTransform() From d0a47b50b6ac630bfa39a26ba06db6bbf459d625 Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Wed, 16 Aug 2023 22:02:44 +0200 Subject: [PATCH 3/9] Reduce allocations in shader --- framework/graphics/shader/rshader.go | 126 ++++++++++++++++++--------- 1 file changed, 83 insertions(+), 43 deletions(-) diff --git a/framework/graphics/shader/rshader.go b/framework/graphics/shader/rshader.go index f6592ecd..0b1c9124 100644 --- a/framework/graphics/shader/rshader.go +++ b/framework/graphics/shader/rshader.go @@ -20,6 +20,27 @@ type RShader struct { bound bool disposed bool + + iCache *int32 + fCache *float32 + + v2Cache *mgl32.Vec2 + v3Cache *mgl32.Vec3 + v4Cache *mgl32.Vec4 + + m2Cache *mgl32.Mat2 + m23Cache *mgl32.Mat2x3 + m24Cache *mgl32.Mat2x4 + + m3Cache *mgl32.Mat3 + m32Cache *mgl32.Mat3x2 + m34Cache *mgl32.Mat3x4 + + m4Cache *mgl32.Mat4 + m42Cache *mgl32.Mat4x2 + m43Cache *mgl32.Mat4x3 + + cache []float32 } func NewRShader(sources ...*Source) *RShader { @@ -30,6 +51,26 @@ func NewRShader(sources ...*Source) *RShader { } s := new(RShader) + + s.iCache = new(int32) + s.fCache = new(float32) + + s.v2Cache = new(mgl32.Vec2) + s.v3Cache = new(mgl32.Vec3) + s.v4Cache = new(mgl32.Vec4) + + s.m2Cache = new(mgl32.Mat2) + s.m23Cache = new(mgl32.Mat2x3) + s.m24Cache = new(mgl32.Mat2x4) + + s.m3Cache = new(mgl32.Mat3) + s.m32Cache = new(mgl32.Mat3x2) + s.m34Cache = new(mgl32.Mat3x4) + + s.m4Cache = new(mgl32.Mat4) + s.m42Cache = new(mgl32.Mat4x2) + s.m43Cache = new(mgl32.Mat4x3) + s.attributes = make(map[string]attribute.VertexAttribute) s.uniforms = make(map[string]attribute.VertexAttribute) @@ -66,11 +107,10 @@ func NewRShader(sources ...*Source) *RShader { } func (s *RShader) fetchAttributes() { - var max int32 - gl.GetProgramiv(s.handle, gl.ACTIVE_ATTRIBUTES, &max) - - for i := int32(0); i < max; i++ { + var attribs int32 + gl.GetProgramiv(s.handle, gl.ACTIVE_ATTRIBUTES, &attribs) + for i := int32(0); i < attribs; i++ { var maxLength int32 gl.GetProgramiv(s.handle, gl.ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxLength) @@ -94,10 +134,10 @@ func (s *RShader) fetchAttributes() { } func (s *RShader) fetchUniforms() { - var max int32 - gl.GetProgramiv(s.handle, gl.ACTIVE_UNIFORMS, &max) + var uniforms int32 + gl.GetProgramiv(s.handle, gl.ACTIVE_UNIFORMS, &uniforms) - for i := int32(0); i < max; i++ { + for i := int32(0); i < uniforms; i++ { var maxLength int32 gl.GetProgramiv(s.handle, gl.ACTIVE_UNIFORM_MAX_LENGTH, &maxLength) @@ -148,60 +188,60 @@ func (s *RShader) SetUniform(name string, value interface{}) { switch uniform.Type { case attribute.Float: - value := value.(float32) - gl.ProgramUniform1fv(s.handle, uniform.Location, 1, &value) + *s.fCache = value.(float32) + gl.ProgramUniform1fv(s.handle, uniform.Location, 1, s.fCache) case attribute.Vec2: - value := value.(mgl32.Vec2) - gl.ProgramUniform2fv(s.handle, uniform.Location, 1, &value[0]) + *s.v2Cache = value.(mgl32.Vec2) + gl.ProgramUniform2fv(s.handle, uniform.Location, 1, &s.v2Cache[0]) case attribute.Vec3: - value := value.(mgl32.Vec3) - gl.ProgramUniform3fv(s.handle, uniform.Location, 1, &value[0]) + *s.v3Cache = value.(mgl32.Vec3) + gl.ProgramUniform3fv(s.handle, uniform.Location, 1, &s.v3Cache[0]) case attribute.Vec4: if c, ok := value.(color.Color); ok { - gl.ProgramUniform4fv(s.handle, uniform.Location, 1, &c.ToArray()[0]) - - break + s.v4Cache[0] = c.R + s.v4Cache[1] = c.G + s.v4Cache[2] = c.B + s.v4Cache[3] = c.A + } else { + *s.v4Cache = value.(mgl32.Vec4) } - value := value.(mgl32.Vec4) - gl.ProgramUniform4fv(s.handle, uniform.Location, 1, &value[0]) + gl.ProgramUniform4fv(s.handle, uniform.Location, 1, &s.v4Cache[0]) case attribute.Mat2: - value := value.(mgl32.Mat2) - gl.ProgramUniformMatrix2fv(s.handle, uniform.Location, 1, false, &value[0]) + *s.m2Cache = value.(mgl32.Mat2) + gl.ProgramUniformMatrix2fv(s.handle, uniform.Location, 1, false, &s.m2Cache[0]) case attribute.Mat23: - value := value.(mgl32.Mat2x3) - gl.ProgramUniformMatrix2x3fv(s.handle, uniform.Location, 1, false, &value[0]) + *s.m23Cache = value.(mgl32.Mat2x3) + gl.ProgramUniformMatrix2x3fv(s.handle, uniform.Location, 1, false, &s.m23Cache[0]) case attribute.Mat24: - value := value.(mgl32.Mat2x4) - gl.ProgramUniformMatrix2x4fv(s.handle, uniform.Location, 1, false, &value[0]) + *s.m24Cache = value.(mgl32.Mat2x4) + gl.ProgramUniformMatrix2x4fv(s.handle, uniform.Location, 1, false, &s.m24Cache[0]) case attribute.Mat3: - value := value.(mgl32.Mat3) - gl.ProgramUniformMatrix3fv(s.handle, uniform.Location, 1, false, &value[0]) + *s.m3Cache = value.(mgl32.Mat3) + gl.ProgramUniformMatrix3fv(s.handle, uniform.Location, 1, false, &s.m3Cache[0]) case attribute.Mat32: - value := value.(mgl32.Mat3x2) - gl.ProgramUniformMatrix3x2fv(s.handle, uniform.Location, 1, false, &value[0]) + *s.m32Cache = value.(mgl32.Mat3x2) + gl.ProgramUniformMatrix3x2fv(s.handle, uniform.Location, 1, false, &s.m32Cache[0]) case attribute.Mat34: - value := value.(mgl32.Mat3x4) - gl.ProgramUniformMatrix3x4fv(s.handle, uniform.Location, 1, false, &value[0]) + *s.m34Cache = value.(mgl32.Mat3x4) + gl.ProgramUniformMatrix3x4fv(s.handle, uniform.Location, 1, false, &s.m34Cache[0]) case attribute.Mat4: - value := value.(mgl32.Mat4) - gl.ProgramUniformMatrix4fv(s.handle, uniform.Location, 1, false, &value[0]) + *s.m4Cache = value.(mgl32.Mat4) + gl.ProgramUniformMatrix4fv(s.handle, uniform.Location, 1, false, &s.m4Cache[0]) case attribute.Mat42: - value := value.(mgl32.Mat4x2) - gl.ProgramUniformMatrix4x2fv(s.handle, uniform.Location, 1, false, &value[0]) + *s.m42Cache = value.(mgl32.Mat4x2) + gl.ProgramUniformMatrix4x2fv(s.handle, uniform.Location, 1, false, &s.m42Cache[0]) case attribute.Mat43: - value := value.(mgl32.Mat4x3) - gl.ProgramUniformMatrix4x3fv(s.handle, uniform.Location, 1, false, &value[0]) + *s.m43Cache = value.(mgl32.Mat4x3) + gl.ProgramUniformMatrix4x3fv(s.handle, uniform.Location, 1, false, &s.m43Cache[0]) default: // We assume that uniform is of type int or sampler - if value, ok := value.(int); ok { - value := int32(value) - gl.ProgramUniform1iv(s.handle, uniform.Location, 1, &value) - - break + if vI, ok := value.(int); ok { + *s.iCache = int32(vI) + } else { + *s.iCache = value.(int32) } - value := value.(int32) - gl.ProgramUniform1iv(s.handle, uniform.Location, 1, &value) + gl.ProgramUniform1iv(s.handle, uniform.Location, 1, s.iCache) } } From f156b7ada8f046d372dd54361daebfab1932616c Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Thu, 17 Aug 2023 00:15:18 +0200 Subject: [PATCH 4/9] Reduce allocations in debug mode --- app/states/player.go | 82 +++++++++++++++---------------- framework/graphics/font/font.go | 32 ++++++++++-- framework/graphics/font/loader.go | 9 ++++ 3 files changed, 78 insertions(+), 45 deletions(-) diff --git a/app/states/player.go b/app/states/player.go index 4ef1126a..c6b4bff8 100644 --- a/app/states/player.go +++ b/app/states/player.go @@ -128,6 +128,7 @@ type Player struct { mProfiler *frame.Counter mStats1 *runtime.MemStats mStats2 *runtime.MemStats + mBuffer []byte } func NewPlayer(beatMap *beatmap.BeatMap) *Player { @@ -135,6 +136,7 @@ func NewPlayer(beatMap *beatmap.BeatMap) *Player { player.mProfiler = frame.NewCounter() player.mStats1 = new(runtime.MemStats) player.mStats2 = new(runtime.MemStats) + player.mBuffer = make([]byte, 0, 256) graphics.LoadTextures() @@ -964,7 +966,7 @@ func (player *Player) drawDebug() { padDown := 4.0 size := 16.0 - drawShadowed := func(right bool, pos float64, text string) { + drawShadowed := func(right bool, pos float64, format string, a ...any) { pX := 0.0 origin := vector.BottomLeft @@ -975,11 +977,14 @@ func (player *Player) drawDebug() { pY := player.ScaledHeight - (size+padDown)*pos - padDown + player.mBuffer = player.mBuffer[:0] + player.mBuffer = fmt.Appendf(player.mBuffer, format, a...) + player.batch.SetColor(0, 0, 0, 1) - player.font.DrawOrigin(player.batch, pX+size*0.1, pY+size*0.1, origin, size, true, text) + player.font.DrawOrigin(player.batch, pX+size*0.1, pY+size*0.1, origin, size, true, string(player.mBuffer)) player.batch.SetColor(1, 1, 1, 1) - player.font.DrawOrigin(player.batch, pX, pY, origin, size, true, text) + player.font.DrawOrigin(player.batch, pX, pY, origin, size, true, string(player.mBuffer)) } player.batch.Begin() @@ -994,44 +999,41 @@ func (player *Player) drawDebug() { player.batch.SetColor(1, 1, 1, 1) player.font.DrawOrigin(player.batch, 0, padDown, vector.TopLeft, size*1.5, false, player.mapFullName) - type tx struct { - pos float64 - text string - } - - var queue []tx - pos := 3.0 - drawWithBackground := func(text string) { - width := player.font.GetWidthMonospaced(size, text) - player.batch.DrawStObject(vector.NewVec2d(0, (size+padDown)*pos), vector.CentreLeft, vector.NewVec2d(width, size+padDown), false, false, 0, color2.NewLA(0, 0.8), false, graphics.Pixel.GetRegion()) + player.font.DrawBg(true) + player.font.SetBgBorderSize(padDown / 2) + player.font.SetBgColor(color2.NewLA(0, 0.8)) - queue = append(queue, tx{pos, text}) + drawWithBackground := func(format string, a ...any) { + player.mBuffer = player.mBuffer[:0] + player.mBuffer = fmt.Appendf(player.mBuffer, format, a...) + + player.font.DrawOrigin(player.batch, 0, (size+padDown)*pos, vector.CentreLeft, size, true, string(player.mBuffer)) pos++ } - drawWithBackground(fmt.Sprintf("VSync: %t", settings.Graphics.VSync)) - drawWithBackground(fmt.Sprintf("Blur: %t", settings.Playfield.Background.Blur.Enabled)) - drawWithBackground(fmt.Sprintf("Bloom: %t", settings.Playfield.Bloom.Enabled)) + drawWithBackground("VSync: %t", settings.Graphics.VSync) + drawWithBackground("Blur: %t", settings.Playfield.Background.Blur.Enabled) + drawWithBackground("Bloom: %t", settings.Playfield.Bloom.Enabled) msaa := "OFF" if settings.Graphics.MSAA > 0 { msaa = strconv.Itoa(int(settings.Graphics.MSAA)) + "x" } - drawWithBackground(fmt.Sprintf("MSAA: %s", msaa)) + drawWithBackground("MSAA: %s", msaa) - drawWithBackground(fmt.Sprintf("FBO Binds: %d", statistic.GetPrevious(statistic.FBOBinds))) - drawWithBackground(fmt.Sprintf("VAO Binds: %d", statistic.GetPrevious(statistic.VAOBinds))) - drawWithBackground(fmt.Sprintf("VBO Binds: %d", statistic.GetPrevious(statistic.VBOBinds))) - drawWithBackground(fmt.Sprintf("Vertex Upload: %.2fk", float64(statistic.GetPrevious(statistic.VertexUpload))/1000)) - drawWithBackground(fmt.Sprintf("Vertices Drawn: %.2fk", float64(statistic.GetPrevious(statistic.VerticesDrawn))/1000)) - drawWithBackground(fmt.Sprintf("Draw Calls: %d", statistic.GetPrevious(statistic.DrawCalls))) - drawWithBackground(fmt.Sprintf("Sprites Drawn: %d", statistic.GetPrevious(statistic.SpritesDrawn))) + drawWithBackground("FBO Binds: %d", statistic.GetPrevious(statistic.FBOBinds)) + drawWithBackground("VAO Binds: %d", statistic.GetPrevious(statistic.VAOBinds)) + drawWithBackground("VBO Binds: %d", statistic.GetPrevious(statistic.VBOBinds)) + drawWithBackground("Vertex Upload: %.2fk", float64(statistic.GetPrevious(statistic.VertexUpload))/1000) + drawWithBackground("Vertices Drawn: %.2fk", float64(statistic.GetPrevious(statistic.VerticesDrawn))/1000) + drawWithBackground("Draw Calls: %d", statistic.GetPrevious(statistic.DrawCalls)) + drawWithBackground("Sprites Drawn: %d", statistic.GetPrevious(statistic.SpritesDrawn)) if storyboard := player.background.GetStoryboard(); storyboard != nil { - drawWithBackground(fmt.Sprintf("SB sprites: %d", player.storyboardDrawn)) + drawWithBackground("SB sprites: %d", player.storyboardDrawn) } pos++ @@ -1044,29 +1046,27 @@ func (player *Player) drawDebug() { player.mProfiler.PutSample(mDelta / 1000) } - drawWithBackground(fmt.Sprintf("Allocated: %s", humanize.Bytes(player.mStats2.Alloc))) - drawWithBackground(fmt.Sprintf("Allocs/s: %s", humanize.Bytes(uint64(player.mProfiler.GetAverage()*player.profiler.GetFPS()*1000)))) - drawWithBackground(fmt.Sprintf("System: %s", humanize.Bytes(player.mStats2.Sys))) - drawWithBackground(fmt.Sprintf("GC Runs: %d", player.mStats2.NumGC)) - drawWithBackground(fmt.Sprintf("GC Time: %.3fms", float64(player.mStats2.PauseTotalNs)/1000000)) + drawWithBackground("Allocated: %s", humanize.Bytes(player.mStats2.Alloc)) + drawWithBackground("Allocs/s: %s", humanize.Bytes(uint64(player.mProfiler.GetAverage()*player.profiler.GetFPS()*1000))) + drawWithBackground("System: %s", humanize.Bytes(player.mStats2.Sys)) + drawWithBackground("GC Runs: %d", player.mStats2.NumGC) + drawWithBackground("GC Time: %.3fms", float64(player.mStats2.PauseTotalNs)/1000000) + + player.font.DrawBg(false) player.mStats1, player.mStats2 = player.mStats2, player.mStats1 player.batch.ResetTransform() - for _, t := range queue { - player.font.DrawOrigin(player.batch, 0, (size+padDown)*t.pos, vector.CentreLeft, size, true, t.text) - } - currentTime := int(player.musicPlayer.GetPosition()) totalTime := int(player.musicPlayer.GetLength()) mapTime := int(player.bMap.HitObjects[len(player.bMap.HitObjects)-1].GetEndTime() / 1000) - drawShadowed(false, 2, fmt.Sprintf("%02d:%02d / %02d:%02d (%02d:%02d)", currentTime/60, currentTime%60, totalTime/60, totalTime%60, mapTime/60, mapTime%60)) - drawShadowed(false, 1, fmt.Sprintf("%d(*%d) hitobjects, %d total", player.objectContainer.GetNumProcessed(), settings.DIVIDES, len(player.bMap.HitObjects))) + drawShadowed(false, 2, "%02d:%02d / %02d:%02d (%02d:%02d)", currentTime/60, currentTime%60, totalTime/60, totalTime%60, mapTime/60, mapTime%60) + drawShadowed(false, 1, "%d(*%d) hitobjects, %d total", player.objectContainer.GetNumProcessed(), settings.DIVIDES, len(player.bMap.HitObjects)) if storyboard := player.background.GetStoryboard(); storyboard != nil { - drawShadowed(false, 0, fmt.Sprintf("%d storyboard sprites, %d in queue (%d total)", player.background.GetStoryboard().GetProcessedSprites(), storyboard.GetQueueSprites(), storyboard.GetTotalSprites())) + drawShadowed(false, 0, "%d storyboard sprites, %d in queue (%d total)", player.background.GetStoryboard().GetProcessedSprites(), storyboard.GetQueueSprites(), storyboard.GetTotalSprites()) } else { drawShadowed(false, 0, "No storyboard") } @@ -1093,11 +1093,11 @@ func (player *Player) drawDebug() { shift := strconv.Itoa(max(len(drawFPS), max(len(updateFPS), len(sbFPS)))) - drawShadowed(true, 1+off, fmt.Sprintf("Draw: %"+shift+"s", drawFPS)) - drawShadowed(true, 0+off, fmt.Sprintf("Update: %"+shift+"s", updateFPS)) + drawShadowed(true, 1+off, "Draw: %"+shift+"s", drawFPS) + drawShadowed(true, 0+off, "Update: %"+shift+"s", updateFPS) if sbThread { - drawShadowed(true, 0, fmt.Sprintf("Storyboard: %"+shift+"s", sbFPS)) + drawShadowed(true, 0, "Storyboard: %"+shift+"s", sbFPS) } } diff --git a/framework/graphics/font/font.go b/framework/graphics/font/font.go index e4f66967..6cf71518 100644 --- a/framework/graphics/font/font.go +++ b/framework/graphics/font/font.go @@ -39,9 +39,23 @@ type Font struct { Overlap float64 ascent float64 flip bool + pixel *texture.TextureRegion + + drawBg bool + bgBorder float64 + bgColor color2.Color } -func (font *Font) drawInternal(renderer *batch.QuadBatch, x, y, size, rotation float64, text string, monospaced bool, color color2.Color) { +func (font *Font) drawInternal(renderer *batch.QuadBatch, x, y, width, size, rotation float64, text string, monospaced bool, color color2.Color) { + rotation += renderer.GetRotation() + + if font.drawBg && font.pixel != nil { + pos2 := vector.NewVec2d(-font.bgBorder, -font.bgBorder).Rotate(rotation).AddS(x, y) + vSize := vector.NewVec2d(width+2*font.bgBorder, size+2*font.bgBorder) + + renderer.DrawStObject(pos2, vector.TopLeft, vSize, false, false, rotation, font.bgColor, false, *font.pixel) + } + scale := size / font.initialSize scBase := scale * renderer.GetScale().Y * renderer.GetSubScale().Y @@ -50,8 +64,6 @@ func (font *Font) drawInternal(renderer *batch.QuadBatch, x, y, size, rotation f advance := 0.0 - rotation += renderer.GetRotation() - cos := math.Cos(rotation) sin := math.Sin(rotation) @@ -143,6 +155,18 @@ func (font *Font) GetAscent() float64 { return font.ascent } +func (font *Font) DrawBg(v bool) { + font.drawBg = v +} + +func (font *Font) SetBgBorderSize(size float64) { + font.bgBorder = size +} + +func (font *Font) SetBgColor(color color2.Color) { + font.bgColor = color +} + func (font *Font) DrawOrigin(renderer *batch.QuadBatch, x, y float64, origin vector.Vector2d, size float64, monospaced bool, text string) { font.DrawOriginRotation(renderer, x, y, origin, size, 0, monospaced, text) } @@ -155,7 +179,7 @@ func (font *Font) DrawOriginRotationColor(renderer *batch.QuadBatch, x, y float6 width := font.getWidthInternal(size, text, monospaced) align := origin.AddS(1, 1).Mult(vector.NewVec2d(-width/2, -(size/font.initialSize*font.ascent)/2)).Mult(renderer.GetScale()).Mult(renderer.GetSubScale()).Rotate(rotation) - font.drawInternal(renderer, x+align.X, y+align.Y, size, rotation, text, monospaced, color) + font.drawInternal(renderer, x+align.X, y+align.Y, width, size, rotation, text, monospaced, color) } func (font *Font) DrawOriginV(renderer *batch.QuadBatch, position vector.Vector2d, origin vector.Vector2d, size float64, monospaced bool, text string) { diff --git a/framework/graphics/font/loader.go b/framework/graphics/font/loader.go index 5326bbaf..6b8cba58 100644 --- a/framework/graphics/font/loader.go +++ b/framework/graphics/font/loader.go @@ -36,6 +36,15 @@ func LoadFont(reader io.Reader) *Font { fnt.atlas = texture.NewTextureAtlas(1024, 5) fnt.atlas.SetManualMipmapping(true) + buf := make([]byte, 25*4) + for i := range buf { + buf[i] = 0xff + } + + fnt.pixel = fnt.atlas.AddTexture("pixel", 5, 5, buf) + fnt.pixel.Width = 1 + fnt.pixel.Height = 1 + fc, err := opentype.NewFace(ttf, &opentype.FaceOptions{Size: fnt.initialSize, DPI: 72, Hinting: font.HintingFull}) if err != nil { panic("Error reading font: " + err.Error()) From a807896de9e20dbfe286f663175b47611f8c2a07 Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Thu, 17 Aug 2023 00:43:59 +0200 Subject: [PATCH 5/9] Sample memory on a different thread --- app/states/player.go | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/app/states/player.go b/app/states/player.go index c6b4bff8..2c5b02f4 100644 --- a/app/states/player.go +++ b/app/states/player.go @@ -39,6 +39,7 @@ import ( "path/filepath" "runtime" "strconv" + "time" ) const windowsOffset = 15 @@ -129,6 +130,7 @@ type Player struct { mStats1 *runtime.MemStats mStats2 *runtime.MemStats mBuffer []byte + memTicker *time.Ticker } func NewPlayer(beatMap *beatmap.BeatMap) *Player { @@ -962,6 +964,33 @@ func (player *Player) drawOverlayPart(drawFunc func(*batch2.QuadBatch, []color2. } func (player *Player) drawDebug() { + if settings.DEBUG && player.memTicker == nil { + player.memTicker = time.NewTicker(100 * time.Millisecond) + + goroutines.RunOS(func() { + prevT := time.Now() + runtime.ReadMemStats(player.mStats2) + + for t := range player.memTicker.C { + diff := t.Sub(prevT) + prevT = t + + player.mStats1, player.mStats2 = player.mStats2, player.mStats1 + runtime.ReadMemStats(player.mStats2) + + mDelta := float64(int64(player.mStats2.Alloc)-int64(player.mStats1.Alloc)) * (1000 / float64(diff.Milliseconds())) + if mDelta > 0 { + player.mProfiler.PutSample(mDelta) + } + + if !settings.DEBUG && player.memTicker != nil { + player.memTicker.Stop() + player.memTicker = nil + } + } + }) + } + if settings.DEBUG || settings.Graphics.ShowFPS { padDown := 4.0 size := 16.0 @@ -1039,23 +1068,14 @@ func (player *Player) drawDebug() { pos++ drawWithBackground("Memory:") - runtime.ReadMemStats(player.mStats2) - - mDelta := float64(int64(player.mStats2.Alloc) - int64(player.mStats1.Alloc)) - if mDelta > 0 { - player.mProfiler.PutSample(mDelta / 1000) - } - drawWithBackground("Allocated: %s", humanize.Bytes(player.mStats2.Alloc)) - drawWithBackground("Allocs/s: %s", humanize.Bytes(uint64(player.mProfiler.GetAverage()*player.profiler.GetFPS()*1000))) + drawWithBackground("Allocs/s: %s", humanize.Bytes(uint64(player.mProfiler.GetAverage()))) drawWithBackground("System: %s", humanize.Bytes(player.mStats2.Sys)) drawWithBackground("GC Runs: %d", player.mStats2.NumGC) drawWithBackground("GC Time: %.3fms", float64(player.mStats2.PauseTotalNs)/1000000) player.font.DrawBg(false) - player.mStats1, player.mStats2 = player.mStats2, player.mStats1 - player.batch.ResetTransform() currentTime := int(player.musicPlayer.GetPosition()) From 42cd0d1e80a831a40d790f4cd5072665dc04986c Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Tue, 29 Aug 2023 23:47:12 +0200 Subject: [PATCH 6/9] Fix missing kick sliders not resetting combo --- app/rulesets/osu/slider.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/app/rulesets/osu/slider.go b/app/rulesets/osu/slider.go index 1a9ded7d..aeac6939 100644 --- a/app/rulesets/osu/slider.go +++ b/app/rulesets/osu/slider.go @@ -292,7 +292,7 @@ func (slider *Slider) UpdatePostFor(player *difficultyPlayer, time int64, proces state := slider.state[player] if time > int64(slider.hitSlider.GetStartTime())+player.diff.Hit50 && !state.isStartHit { - if len(slider.players) == 1 { + if len(slider.players) == 1 && !state.isHit { //don't fade if slider already ended (and armed the start) slider.hitSlider.ArmStart(false, float64(time)) } @@ -313,12 +313,8 @@ func (slider *Slider) UpdatePostFor(player *difficultyPlayer, time int64, proces } if (time >= int64(slider.hitSlider.GetEndTime()) || (processSliderEndsAhead && int64(slider.hitSlider.GetEndTime())-time == 1)) && !state.isHit { - if !state.isStartHit { - if len(slider.players) == 1 { - slider.hitSlider.ArmStart(false, float64(time)) - } - - state.isStartHit = true + if len(slider.players) == 1 && !state.isStartHit { + slider.hitSlider.ArmStart(false, float64(time)) } if state.startResult != Miss { From e462365616f422ea9931ed79487e90b62dff05ca Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Wed, 30 Aug 2023 02:53:30 +0200 Subject: [PATCH 7/9] Fix zero-delta replay frames breaking spinners --- app/rulesets/osu/spinner.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/rulesets/osu/spinner.go b/app/rulesets/osu/spinner.go index 5cd66a48..bf458e41 100644 --- a/app/rulesets/osu/spinner.go +++ b/app/rulesets/osu/spinner.go @@ -118,7 +118,11 @@ func (spinner *Spinner) UpdateFor(player *difficultyPlayer, time int64, _ bool) if math.Abs(angleDiff) < math.Pi { if player.diff.GetModifiedTime(state.frameVariance) > FrameTime*1.04 { - state.theoreticalVelocity = angleDiff / player.diff.GetModifiedTime(timeDiff) + if timeDiff > 0 { + state.theoreticalVelocity = angleDiff / player.diff.GetModifiedTime(timeDiff) + } else { + state.theoreticalVelocity = 0 + } } else { state.theoreticalVelocity = angleDiff / FrameTime } From d64df0186fb21ca8006d64207d289b4a8ac358df Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Thu, 31 Aug 2023 02:54:41 +0200 Subject: [PATCH 8/9] Calculate spinners velocity before theoretical velocity. It *should* fix all spinner discrepancies --- app/rulesets/osu/spinner.go | 66 ++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/app/rulesets/osu/spinner.go b/app/rulesets/osu/spinner.go index bf458e41..fe018032 100644 --- a/app/rulesets/osu/spinner.go +++ b/app/rulesets/osu/spinner.go @@ -14,7 +14,7 @@ type spinnerstate struct { rotationCount int64 lastRotationCount int64 scoringRotationCount int64 - rotationCountF float64 + rotationCountF float32 rotationCountFD float64 frameVariance float64 theoreticalVelocity float64 @@ -80,6 +80,36 @@ func (spinner *Spinner) UpdateFor(player *difficultyPlayer, time int64, _ bool) numFinishedTotal++ if player.cursor.IsReplayFrame && time > int64(spinner.hitSpinner.GetStartTime()) && time < int64(spinner.hitSpinner.GetEndTime()) { + maxAccelThisFrame := player.diff.GetModifiedTime(spinner.maxAcceleration * timeDiff) + + if player.diff.CheckModActive(difficulty.SpunOut) || player.diff.CheckModActive(difficulty.Relax2) { + state.currentVelocity = 0.03 + } else if state.theoreticalVelocity > state.currentVelocity { + accel := maxAccelThisFrame + if state.currentVelocity < 0 && player.diff.CheckModActive(difficulty.Relax) { + accel /= 4 + } + + state.currentVelocity += min(state.theoreticalVelocity-state.currentVelocity, accel) + } else { + accel := -maxAccelThisFrame + if state.currentVelocity > 0 && player.diff.CheckModActive(difficulty.Relax) { + accel /= 4 + } + + state.currentVelocity += max(state.theoreticalVelocity-state.currentVelocity, accel) + } + + state.currentVelocity = max(-0.05, min(state.currentVelocity, 0.05)) + + if len(spinner.players) == 1 { + if state.currentVelocity == 0 { + spinner.hitSpinner.PauseSpinSample() + } else { + spinner.hitSpinner.StartSpinSample() + } + } + decay1 := math.Pow(0.9, timeDiff/FrameTime) state.rpm = state.rpm*decay1 + (1.0-decay1)*(math.Abs(state.currentVelocity)*1000)/(math.Pi*2)*60 @@ -133,45 +163,15 @@ func (spinner *Spinner) UpdateFor(player *difficultyPlayer, time int64, _ bool) state.lastAngle = mouseAngle - maxAccelThisFrame := player.diff.GetModifiedTime(spinner.maxAcceleration * timeDiff) - - if player.diff.CheckModActive(difficulty.SpunOut) || player.diff.CheckModActive(difficulty.Relax2) { - state.currentVelocity = 0.03 - } else if state.theoreticalVelocity > state.currentVelocity { - accel := maxAccelThisFrame - if state.currentVelocity < 0 && player.diff.CheckModActive(difficulty.Relax) { - accel /= 4 - } - - state.currentVelocity += min(state.theoreticalVelocity-state.currentVelocity, accel) - } else { - accel := -maxAccelThisFrame - if state.currentVelocity > 0 && player.diff.CheckModActive(difficulty.Relax) { - accel /= 4 - } - - state.currentVelocity += max(state.theoreticalVelocity-state.currentVelocity, accel) - } - - state.currentVelocity = max(-0.05, min(state.currentVelocity, 0.05)) - - if len(spinner.players) == 1 { - if state.currentVelocity == 0 { - spinner.hitSpinner.PauseSpinSample() - } else { - spinner.hitSpinner.StartSpinSample() - } - } - rotationAddition := state.currentVelocity * timeDiff state.rotationCountFD += rotationAddition - state.rotationCountF += math.Abs(rotationAddition / math.Pi) + state.rotationCountF += float32(math.Abs(float64(float32(rotationAddition)) / math.Pi)) if len(spinner.players) == 1 { spinner.hitSpinner.SetRotation(player.diff.GetModifiedTime(state.rotationCountFD)) spinner.hitSpinner.SetRPM(state.rpm) - spinner.hitSpinner.UpdateCompletion(state.rotationCountF / float64(state.requirement)) + spinner.hitSpinner.UpdateCompletion(float64(state.rotationCountF) / float64(state.requirement)) } state.rotationCount = int64(state.rotationCountF) From 5407724ad06394474648f168c5ec1e2c7af5f8ca Mon Sep 17 00:00:00 2001 From: Wieku Date: Mon, 18 Sep 2023 21:11:57 +0200 Subject: [PATCH 9/9] Fix passive hp drop rate being too high in some cases --- app/rulesets/osu/healthprocessor.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/rulesets/osu/healthprocessor.go b/app/rulesets/osu/healthprocessor.go index c4d8dee1..1135fb4c 100644 --- a/app/rulesets/osu/healthprocessor.go +++ b/app/rulesets/osu/healthprocessor.go @@ -126,7 +126,10 @@ func (hp *HealthProcessor) CalculateRate() { //nolint:gocyclo break } - hp.Increase(-hp.PassiveDrain*(o.GetEndTime()-o.GetStartTime()), false) + decr := hp.PassiveDrain * (o.GetEndTime() - o.GetStartTime()) + hpUnder := min(0, hp.Health-decr) + + hp.Increase(-decr, false) if s, ok := o.(*objects.Slider); ok { for j := 0; j < len(s.TickReverse)+1; j++ { @@ -143,6 +146,14 @@ func (hp *HealthProcessor) CalculateRate() { //nolint:gocyclo } } + //noinspection GoBoolExpressions - false positive + if hpUnder < 0 && hp.Health+hpUnder <= lowestHpEver { + fail = true + hp.PassiveDrain *= 0.96 + + break + } + if i == len(hp.beatMap.HitObjects)-1 || hp.beatMap.HitObjects[i+1].IsNewCombo() { hp.AddResult(Hit300g)