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

Integrate new preset parser and rewrite/refactor preset rendering. #716

Merged
merged 51 commits into from
Sep 15, 2023

Conversation

kblaschke
Copy link
Member

@kblaschke kblaschke commented Apr 26, 2023

First of all, big sorry for creating such a huge PR.

Sadly, just replacing the preset and expression parser with the newly written one wasn't exactly easy, as the old code was highly integrated in basically all major effect classes. It worked on a few assumptions no longer compatible with the new expression compiler, so I had to reorganize code. This lead to the decision to reorganize the whole Milkdrop preset renderer into an overall better structure, removing the "ping pong" way of how the preset parts were rendered previously and replacing it with a more straightforward approach, effectively containing the rendering of a preset into a single function call.

Having to restructure classes, I've also went fully down into the rabbit hole and checked every rendering step for Milkdrop compatibility, fixed issues and added missing code. Also added framebuffer usage to the rendering process for various reasons listed below.

Changes

Here's a list of changes, improvements and other notable things contained in this PR, in no particular order:

  • Removed all existing preset file parser and expression-related code from libprojectM and replaced it with a new, simpler file parser class and the newly written projectM-eval expression compiler library.
  • Renamed target "MilkdropPresetFactory" to "MilkdropPreset".
  • Moved all Milkdrop-specific rendering code to "MilkdropPreset".
  • Moved and refactored code from "Renderer" and "ShaderEngine" classes into other places, e,g, new classes. This includes blur textures, comp/warp shader code and some other math for generating the composite mesh.
  • Added lazy texture loading to TextureManager, so it doesn't load all images into VRAM on startup.
  • Added unloading logic for unused textures, very close to the code used in Milkdrop to save additional VRAM.
  • Moved warp mesh texture coordinate calculation code to a vertex shader, relieving the CPU of some work.
  • Properly implemented the motion vector grid effect, was only a simple grid of static points before. Now it properly reverse-propagates the warp mesh "motion" of the last frame and draws lines as it should. In contrast to Milkdrop, the actual motion vector calculation is completely done on the GPU inside the vertex shader, using the U/V coordinate color attachment of the previous frame.
  • Properly implemented the "Darken Center" effect.
  • Consolidated code for classic 1.x filter effects like "Darken", "Invert" and "Solarize".
  • Made preset rendering per-frame aware of external parameter changes like screen resolution and warp mesh size, only reallocating the textures and meshes which actually have changed since the last frame. This makes the process of resizing the window way faster.
  • Blur textures are now rendered in their own (smaller) framebuffer instead of directly drawing into the main image texture.

Work left for later

Will fix or add the following features and bugs later, so we can get this huge changeset merged ASAP:

  • Fix "upside-down" rendering issues and, if possible, pass the correct U/V coordinates to the HLSL shaders to prevent math errors.
  • Implement random texture selection.
  • Implement simple smooth preset blending using shaders.
  • Audio-related issues, especially the spectrum values, which are off the charts, causing a multitude of rendering issues in many presets.
  • Proper framebuffer/render-to-texture support for rendering a frame.
  • User-configurable blending shaders and options.

@kblaschke kblaschke self-assigned this Apr 27, 2023
@kblaschke kblaschke added this to the 4.1 milestone Apr 27, 2023
@kblaschke kblaschke force-pushed the rewrite-preset-parser branch 3 times, most recently from 648f8ca to b08a1ae Compare April 29, 2023 13:58
@kblaschke kblaschke changed the title [DRAFT] Integrate new preset parser and rewrite/refactor preset rendering. Integrate new preset parser and rewrite/refactor preset rendering. Jul 24, 2023
@kblaschke kblaschke marked this pull request as ready for review July 24, 2023 10:03
@kblaschke
Copy link
Member Author

Emscripten version builds again, a crash was fixed and cleanups are done so far.
I'd say the branch is in a good enough state to be merged to master, even if there's some work left to get it into a releasable state.

Please test extensively, review the changes and give your opinions.

@@ -64,6 +60,28 @@ auto BeatDetect::GetPCMScale() const noexcept -> float
}


auto BeatDetect::GetFrameAudioData() const -> FrameAudioData
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my knowledge of modern C++ is very poor. how does data get garbage-collected here? are we returning a struct copy? if so, since this is a hot path, can we return a pointer to a struct that frees itself when there are no more references or something? or is it not a big deal to copy 8 floats around?

Copy link
Member Author

@kblaschke kblaschke Aug 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's an object that's - in theory - copied. The compiler optimizes such return values as implicit moves, so when the code actually runs, no copy is created, but the object instance created in the function is moved to the caller's context without creating another object.

The audio data is only requested once per frame, and changes every frame, so using shared pointers here would just add overhead. The presets will get a const reference of this object, so ideally, only this single instance exists during a render call.

Comment on lines +25 to +28
-1.0, -1.0, 0.0, 0.0,
1.0, -1.0, 1.0, 0.0,
-1.0, 1.0, 0.0, 1.0,
1.0, 1.0, 1.0, 1.0};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had ChatGPT explain this to me

This Vertex Buffer Object (VBO) represents a square (technically, two triangles) that cover the entire screen in Normalized Device Coordinates (NDC), with texture coordinates associated with each vertex.

Here's a breakdown of the values:

The first two values in each row (-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0) represent the x and y coordinates of four vertices in a 2D space. This is a square that covers the entire viewport in NDC, where (-1, -1) is the bottom left corner and (1, 1) is the top right corner.
The third and fourth values in each row (0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0) represent the texture coordinates (u and v) associated with each vertex. They range from (0, 0) at the bottom left to (1, 1) at the top right of the texture.
The drawing order of the vertices forms two triangles that together cover the entire viewport. The first triangle is formed by the vertices at (-1,-1), (1,-1), and (-1,1), and the second triangle is formed by the vertices at (-1,1), (1,-1), and (1,1).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a very sophisticated explanation of "what is a quad?" 😅

Yeah, the blur textures are rendered as a simple quad, drawn with a specific gaussian blur shader. The quad also doesn't always fill the whole screen (or framebuffer, in this case), only in the first iteration. Subsequent blur steps will use only half or a quarter of the texture to create even more blurry images.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I just didn't get the texture coords part of it

Copy link
Collaborator

@revmischa revmischa left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great stuff! thank you

assert(renderContext.textureManager);
m_state.renderContext = renderContext;

// Initialize variables and code now we have a proper render state.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Initialize variables and code now we have a proper render state.
// Initialize variables and code now that we have a proper render state.

public:
/**
* @brief LoadCode a MilkdropPreset by filename with input and output buffers specified.
* @param factory The factory class that created this preset instance.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param factory The factory class that created this preset instance.

/**
* @brief LoadCode a MilkdropPreset from an input stream with input and output buffers specified.
* @param presetData an already initialized input stream to read the MilkdropPreset file from
* @param presetOutputs initialized and filled with data parsed from a MilkdropPreset
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @param presetOutputs initialized and filled with data parsed from a MilkdropPreset

/**
* @brief Draws a flexible motion vector field.
*
* This is broken right now, as it only renders a relatively static 1px point grid, with no apparent motion trails.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this still true?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, not anymore. It's now properly implemented, I perform it purely on the GPU using a second color attachment and a vertex shader. Some presets like "Radar" and many others now actually look as they should.

@JohannesKauffmann
Copy link
Contributor

First of all, big sorry for creating such a huge PR.

Forgive me if this may come across as bikeshedding, but some CMake parts of bf344b6 (the PROJECTM_STATIC_DEFINE parts) could be extracted to make this PR a bit smaller :)

@kblaschke
Copy link
Member Author

Replaced the color masking with attaching/detaching the U/V texture for use with motion vectors, as GLES only supports per-buffer color masking with v3.2.

@kblaschke
Copy link
Member Author

kblaschke commented Sep 10, 2023

First of all, big sorry for creating such a huge PR.

Forgive me if this may come across as bikeshedding, but some CMake parts of bf344b6 (the PROJECTM_STATIC_DEFINE parts) could be extracted to make this PR a bit smaller :)

What exactly are you proposing there? Can't remember that I've done anything with the linking stuff, besides additing the evaluation library. Also unsure how this would make the PR substantially smaller, as the overwhelming part was rewriting code in the Renderer and MilkdropPreset[Factory] targets.

If we can make the static linking part easier, that's definitely a good thing to do. I'd do that separately though, as this PR already changes enough things, or better said, too many to even properly review. Could you create an issue please, detailing the proposed changes? This way I won't forget about that!

@kblaschke
Copy link
Member Author

I'd personally say this changeset is as good as it gets without adding even more stuff to it, so I'd like anyone to pick up the branch, build and test it for stability. I've not seen any serious issues besides a few remaining rendering problems noted in the description, but these will be addressed later. Overall, it's a huge improvement over the current master, visually and performance-wise.

kblaschke and others added 23 commits September 11, 2023 10:19
Now rendering everything upside down to make up for the difference in UV coordinates in HLSL shaders.
Should fix visuals in about half of the presets.
Main issue was a wrong -, replaced with + in the rotation calculation. Plus we shouldn't negate the Y pos, as this may mess up UV calculations done in HLSL.
Idle preset doesn't render the same way as before, as it was presumably adapted to projectM's previous math. It's now a bit more complex, with the M tilting, a sine waveform at the bottom and some other tweaks.

The filter mesh was broken, so the effects didn't render at all.
…oved ShaderEngine class.

Milkdrop shaders are now read from .vert/.frag and .inc files, then copied into a generated code file by CMake. Makes editing easier, as many IDEs can properly syntax-highlight the shader code.

The class was also moved into MilkdropPreset, as it's the only preset type which will ever make use of those shaders.

Deleted the now fully "dissolved" ShaderEngine class.
… failed.

Another option would be skipping the rendering if there's no active preset instead of displaying the idle preset.
Only GLES 3.2 supports glColorMaski, which would exclude any platforms only supporting GLES 3.0 or 3.1, so we simply change the color attachment accordingly. This change also improves VRAM usage, as now there's only a single u/v texture used for both framebuffers as required.
@kblaschke
Copy link
Member Author

FYI, last push was a rebase on latest master to prepare for merging.

@JohannesKauffmann
Copy link
Contributor

What exactly are you proposing there?

The commit A few Windows build fixes from this PR changed both CMakeLists.txt such that PROJECTM_STATIC_DEFINE is defined for a wider range/scope of targets. My proposal was to move these changes specifically to a separate commit/PR, as these CMakeLists.txt changes didn't seem related to the new preset parser/rendering rewrite, but would apply more globally. It'd make this PR a bit smaller (not much smaller admittedly).

Not sure it is worth changing now, however. PR looks good either way.

@kblaschke
Copy link
Member Author

Ah, I see. Yes, I had to do this because otherwise the class definitions in the PCM class headers were different between the compile units, which the MSVC compiler doesn't like (different declspec attributes), causing linker errors. The MilkdropPreset target uses the PCM class to retrieve the frame audio data, and I didn't also want to refactor this class structure as it needs more attention later due to the broken/high spectrum and beat detection values. Could've done this in an earlier PR, but it's really just a small fix for a typo and adding missing defines to other targets. Proper cleanup will be done in issue #708.

@kblaschke
Copy link
Member Author

Merging now, as the current branch state is stable enough for most uses. Emscripten might still have some issues left on top of the other things on the to-do list, so @Blaquewithaq please create an issue if there are any problems running on Emscripten, I'll tackle these in a later fix.

@kblaschke kblaschke merged commit c08de98 into projectM-visualizer:master Sep 15, 2023
7 checks passed
@kblaschke kblaschke deleted the rewrite-preset-parser branch September 15, 2023 18:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment