Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make the rendering of the oscilloscope significantly less expensive #619

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

barbeque-squared
Copy link
Member

I played around a bit with gprof the other day, and noticed that especially when using six players, having oscilloscope enabled had a huge impact on performance.

This patch only renders 1/32 of the (4096) data points in the microphone data, which should be a close enough approximation for the oscilloscope to be useful. I didn't notice any negative side effects by doing this, perhaps the line is a bit thinner but that's about it.

The 32 is fairly arbitrarily chosen. I tried various powers of two, 64 was too unprecise/blocky at higher frequencies (it's possible this also happens with 32, but I can't reach those frequencies) whereas with 16 it looked pretty much the same as with 32.

An alternative (and probably even faster) approach would be to (also) only use only every nth sample when getting it from the microphone, but that also affects various other parts of the code, plus I don't know how 'long' those 4096 samples actually last. You'd also technically be throwing away some of the data.

@s09bQ5
Copy link
Collaborator

s09bQ5 commented Nov 5, 2022

The number of vertices should depend on the width of the oscilloscope on the screen.

Should we run a low pass filter on the audio?
Or maybe replace all line segments consisting of vertices that end up on the same horizontal position with a single vertical line from their minimum to their maximum?

Can we speed up the drawing even more by letting the GPU do the translation and scaling?
Something like

glPushMatrix();
glTranslatef(X, Y - MaxY/Low(Smallint), 0);
glScalef(MaxX/High(Sound.AnalysisBuffer), MaxY/Low(Smallint)), 1);
glBegin(GL_LINE_STRIP);
for SampleIndex := 0 to High(Sound.AnalysisBuffer) do
begin
    glVertex2i(SampleIndex, Sound.AnalysisBuffer[SampleIndex]);
end;
glEnd;
glPopMatrix();

?

@barbeque-squared
Copy link
Member Author

The number of vertices should depend on the width of the oscilloscope on the screen.
Yeah, this makes way more sense. There had to be a better way than an arbitrary number.

I also like the idea with the GPU translation. Even if the performance stays the same, it should make this kind of code way more readable without all the dividing. Also thanks for sharing that example code, I understand what it does but it's been ages since I've done anything with GL.

I'll play around with your suggestions and see what works best/where the biggest gains are. Might be a week or two before I get around to it, I've since found another part of the code that I want to get a ticket opened on first before I improve this one.

@barbeque-squared
Copy link
Member Author

The number of vertices should depend on the width of the oscilloscope on the screen.

Turns out, the W that this (and probably almost every other) function gets is not the width as it's actually shown to the user, but the W (and also X Y H) as defined by the theme file. Which appears to assume an 800x600 screen.

I usually run the game at around 1080p, and always felt that some UI elements were strangely scaled and not-quite-sharp in some way. Some notable exceptions are video/image backgrounds when singing, the timebar (with the boxes), and covers in at least Roulette mode (though there is still something off about the latter).

It's probably being caused by this bit of code in UGraphic.pas:

// define virtual (Render) and real (Screen) screen size
  RenderW := 800;
  RenderH := 600;
  ScreenW := Screen.w;
  ScreenH := Screen.h;

I'll continue this PR once I've figured out a way around this.

@basisbit
Copy link
Member

@barbeque-squared please read more of the code. The different places always should distinguish between rendered w/h and theme w/h and make use of screen w/h as needed.
All things that get displayed are either graphics which the GPU scales accordingly, or font which is rendered to fit to the screen resolution / game-on-screen surface.

@barbeque-squared
Copy link
Member Author

Just to see if it was possible, I tried RenderW := Screen.w; (with other modifications to actually make it properly scale the themes again). The way/quality the oscilloscope is drawn is one of the few things that weren't really impacted by this (because it was always pure GL-drawn anyway). Everything else just looked so much better.

That's absolutely not committable right now (it's a giant hack), but I did come across some areas that would be massively improved by some cleanup PRs, even if in the end we never move away from the 800x600 rendering. But they would be a prerequisite if we ever did want to, so expect some cleanup PRs over the coming weeks/months.

Since changing render size is non-trivial, would a temporary fix in the form of eg rendering only 1/4 (still more than enough for the widest oscilloscope in the default themes on 4k*) be accepted if the improvement in framerate is significant? I'll need to test how significant, but I'm only going to do those tests if the answer's 'yes'.

* = there are other hardcoded numbers and textures in the code that running the game on 4k is a stretch right now anyway. Even on 1080p I'm already hitting some of them.

@barbeque-squared
Copy link
Member Author

From what I recall this was by far the most expensive UI element to draw, especially as the number of players increases. I'll retest it soon (6 players, no video, no background, unlimited fps) and figure out some actual numbers.

@barbeque-squared barbeque-squared marked this pull request as draft March 10, 2023 16:09
@s09bQ5
Copy link
Collaborator

s09bQ5 commented Jun 9, 2023

I barely know anything about shaders, but can't we use a fragment shader that fetches the amplitude for the current x coordinate from a 1D texture, scales it to the height of the oscilloscope and then compares it to the y coordinate to determine the transparency of the fragment? Distance < 1 pixel => Distance is transparency. Distance >= 1 pixel => fully transparent.

OpenGL implementations without shaders can continue to use the old code for drawing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants