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

rendergraph: add rendergraph library #14007

Merged
merged 2 commits into from
Dec 14, 2024
Merged

Conversation

m0dB
Copy link
Contributor

@m0dB m0dB commented Dec 9, 2024

@github-actions github-actions bot added the build label Dec 9, 2024
Copy link
Member

@Holzhaus Holzhaus left a comment

Choose a reason for hiding this comment

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

Thanks for the PR. I started reviewing (see my comments) but then figured I'm likely not the right person to review this because I wasn't involved in the prior discussion and don't want to reopen discussion that you potentially already agreed upon. So it doesn't really make sense for me to review this further.

My general impression is that this code is quite complex and I'm a bit worried that it's more complex than it needs to be. But again, if you think this is needed that is okay for me because I wasn't involved in prior discussion and also don't know what the follow up PRs look like (might also be that you create the needed foundation for them).

struct AttributeInit;
}

// helper to create an AttributeSet using an initializer_list
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// helper to create an AttributeSet using an initializer_list
/// Helper to create an AttributeSet using an initializer_list.

// TODO decide if this should be virtual. QSGMaterial::compare is virtual
// to concrete Material can implement a compare function, but in rendergraph
// we can compare the uniforms cache and texture already here, which seems
// sufficient.
Copy link
Member

Choose a reason for hiding this comment

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

Should this use triple slash?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I should probably not leave TODOs!

Copy link
Member

Choose a reason for hiding this comment

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

Is this TODO solved somewhere in the following planned PRs? Otherwise, perhaps worth creating an issue and reference it there so we keep track and context of it?

@@ -0,0 +1,3 @@
// rendergraph is a module but we want to include util/assert.h from Mixxx,
// without adding the entire src/ dir to the include paths.
#include "../../../util/assert.h"
Copy link
Member

Choose a reason for hiding this comment

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

Not a fan of the relative import from the parent dir. We don't do that anywhere else. Not sure if there is a better way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It’s a bit ugly, but I didn’t want to add the entire src/ dir to the include paths.

Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering if we could get CMake to copy assert.h into the rendergraph include? CMake noob so I have no idea if it could work

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I really wouldn't go down that path. This is isolated and commented.

Comment on lines 3 to 31
../common/rendergraph/attributeinit.h
../common/rendergraph/attributeset.h
../common/rendergraph/geometry.h
../common/rendergraph/geometrynode.h
../common/rendergraph/material.h
../common/rendergraph/material/endoftrackmaterial.cpp
../common/rendergraph/material/endoftrackmaterial.h
../common/rendergraph/material/patternmaterial.cpp
../common/rendergraph/material/patternmaterial.h
../common/rendergraph/material/rgbamaterial.cpp
../common/rendergraph/material/rgbamaterial.h
../common/rendergraph/material/rgbmaterial.cpp
../common/rendergraph/material/rgbmaterial.h
../common/rendergraph/material/texturematerial.cpp
../common/rendergraph/material/texturematerial.h
../common/rendergraph/material/unicolormaterial.cpp
../common/rendergraph/material/unicolormaterial.h
../common/rendergraph/materialshader.h
../common/rendergraph/materialtype.h
../common/rendergraph/node.h
../common/rendergraph/opacitynode.h
../common/rendergraph/texture.h
../common/rendergraph/types.h
../common/rendergraph/uniform.h
../common/rendergraph/uniformscache.cpp
../common/rendergraph/uniformscache.h
../common/rendergraph/uniformset.cpp
../common/rendergraph/uniformset.h
../common/types.cpp
Copy link
Member

Choose a reason for hiding this comment

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

This is repeated multiple times. Can't we define a RENDERGRAPH_COMMON_HEADERS variable in the parent dir and then reference this instead of copy/paste?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

will do

Comment on lines 19 to 22
// This mimics QSGNode::appendChildNode.
// Use NodeInterface<T>::appendChildNode(std::unique_ptr<BaseNode> pNode)
// for a more clear transfer of ownership. pChild is considered owned by
// this at this point.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// This mimics QSGNode::appendChildNode.
// Use NodeInterface<T>::appendChildNode(std::unique_ptr<BaseNode> pNode)
// for a more clear transfer of ownership. pChild is considered owned by
// this at this point.
/// This mimics QSGNode::appendChildNode.
/// Use NodeInterface<T>::appendChildNode(std::unique_ptr<BaseNode> pNode)
/// for a more clear transfer of ownership. pChild is considered owned by
/// this at this point.

And elsewhere.

Comment on lines +1 to +7
#pragma once

#include "backend/baseopenglnode.h"

namespace rendergraph {
using OpenGLNode = BaseOpenGLNode;
} // namespace rendergraph
Copy link
Member

Choose a reason for hiding this comment

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

There are many files with less than 10 lines. Are you going to extend these files in the subsequent PRs? Because this makes the code harder to follows and reeks of YAGNI.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a consequence of the design of having two backend implementations, and a thin common API to interface with either. I don’t see away around this with the current design.


add_subdirectory(opengl)
add_subdirectory(scenegraph)
add_subdirectory(../../res/shaders/rendergraph shaders)
Copy link
Member

Choose a reason for hiding this comment

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

Isn't using a parent directory kind of the opposite of what add_subdirectory is supposed to do?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The alternative is to move the rendergraph shaders to src/rendergraph/shaders. I am not against that.

@m0dB
Copy link
Contributor Author

m0dB commented Dec 10, 2024

Thanks for the review, Jan @Holzhaus. You are right that there has been a lot of prior discussion and reviews and the current state is the end result after many iterations. Still, your comments are welcome.

As for the complexity, I don’t think it is that complex. What makes it seem more complex than it is, is having two implementations of the same API, one using OpenGL and one using Qt scenegraph. But if you look past that: the scenegraph layer is extremely thin, and the OpenGL classes are pretty straightforward, especially if you are familiar with the Qt scenegraph design.

Anyway, any complexity here is compensated in the waveform renders code, which become much cleaner by having a very simple and standard way of doing things: You render a tree of nodes, typically GeometryNode, each with a Material of choice and a Geometry that you populate. Completely hiding all the OpenGL boilerplate, shaders, bindings, etc.

I think that when you review the waveformrenderer code you will appreciate this. And of course the fact that the same code will run within QML!

Copy link
Member

@acolombier acolombier left a comment

Choose a reason for hiding this comment

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

This is looking good, no major issue, only bunch of nits. Let me know if you would like me to approve anyway.

Copy link
Member

Choose a reason for hiding this comment

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

Should this be a README.md? Arguably it doesn't have much formatting, but that might play nicer into some IDE or on Windows which uses the extension to guess the filename

@@ -0,0 +1,3 @@
// rendergraph is a module but we want to include util/assert.h from Mixxx,
// without adding the entire src/ dir to the include paths.
#include "../../../util/assert.h"
Copy link
Member

Choose a reason for hiding this comment

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

I'm wondering if we could get CMake to copy assert.h into the rendergraph include? CMake noob so I have no idea if it could work

// TODO decide if this should be virtual. QSGMaterial::compare is virtual
// to concrete Material can implement a compare function, but in rendergraph
// we can compare the uniforms cache and texture already here, which seems
// sufficient.
Copy link
Member

Choose a reason for hiding this comment

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

Is this TODO solved somewhere in the following planned PRs? Otherwise, perhaps worth creating an issue and reference it there so we keep track and context of it?

if (cacheCompareResult != 0) {
return cacheCompareResult < 0 ? -1 : 1;
}
// TODO multiple textures
Copy link
Member

Choose a reason for hiding this comment

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

Same here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I edited the comments.

// we can compare the uniforms cache and texture already here, which seems
// sufficient.
int compare(const Material* pOther) const {
DEBUG_ASSERT(type() == pOther->type());
Copy link
Member

Choose a reason for hiding this comment

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

Do we want to early return here in case this is not the same type? I guess we might want to do that if m_uniformsCache and pOther->m_uniformsCache have different size which could lead to out of bound access, right?

#include "rendergraph/engine.h"

#include <QDebug>
#include <cassert>
Copy link
Member

Choose a reason for hiding this comment

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

Shall we replace those with DEBUG_ASSERT/VERIFY_OR_DEBUG_ASSERT?

Comment on lines +35 to +46
/* TODO rendergraph ASK @acolombier to try if the following works as well
* (added the .copy())
if (image.format() == QImage::Format_RGBA8888_Premultiplied) {
return QImage(image.bits(), image.width(), image.height(), QImage::Format_RGBA8888).copy();
}
return QImage(
image.convertToFormat(QImage::Format_RGBA8888_Premultiplied)
.bits(),
image.width(),
image.height(),
QImage::Format_RGBA8888).copy();
*/
Copy link
Member

Choose a reason for hiding this comment

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

Looking at the code and the doc, I think this should work! Do you want to just get in and we will fix forward if turns out not to?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, I prefer to leave it like this and check once all PRs are in and you can rebase your qml code.

QSGTexture** texture,
QSGMaterial* newMaterial,
QSGMaterial* oldMaterial) {
if (!newMaterial || !static_cast<Material*>(newMaterial)->texture(binding)) {
Copy link
Member

Choose a reason for hiding this comment

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

Shall we add a VERIFY_OR_DEBUG_ASSERT to prevent the nullptr exception?

case DrawingMode::TriangleStrip:
return QSGGeometry::DrawTriangleStrip;
default:
throw std::runtime_error("not implemented");
Copy link
Member

Choose a reason for hiding this comment

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

Ditto C++ exception

while (numTuples--) {
int k = tupleSize;
while (k--) {
*to++ = *from++;
Copy link
Member

Choose a reason for hiding this comment

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

Ditto nullptr exception

@m0dB
Copy link
Contributor Author

m0dB commented Dec 12, 2024

Thanks. I prefer to merge as is, and when all rendergraph code is merged do a follow up PR to address the things spotted here. When you merge this I will create the next PR.

(For the next two PRs goes the same: unless you spot something particularly bad or broken, let's merge anyway and keep polishing / nitpicking for the end. After that, there are the PRs for the waveform render nodes, these can be done in parallel, so any changes don't require me to rebase the rest)

@m0dB
Copy link
Contributor Author

m0dB commented Dec 14, 2024

I improved some of the comments and removed the common source/headers list duplication. I prefer to do the rest of @acolombier 's (valid) comments to be done in a follow up PR, once all rendergraph PRs are in.

Copy link
Member

@acolombier acolombier left a comment

Choose a reason for hiding this comment

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

LGTM!

Let's get the render-graph train started 🚄

@acolombier acolombier merged commit c1160ff into mixxxdj:main Dec 14, 2024
13 checks passed
@m0dB
Copy link
Contributor Author

m0dB commented Dec 14, 2024

Thanks! I have created the next PR #14021

Copy link
Member

@Swiftb0y Swiftb0y left a comment

Choose a reason for hiding this comment

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

review I started by never finished. Better late than never, feel free to address in a follow up whenever you find time.

Comment on lines +8 to +13
Texture::Texture(Context* pContext, const QImage& image)
: m_pTexture(pContext->window()->createTextureFromImage(image)) {
VERIFY_OR_DEBUG_ASSERT(pContext->window() != nullptr) {
return;
}
DEBUG_ASSERT(!m_pTexture->textureSize().isNull());
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this works properly. The ctor initializer list is evaluated before the ctor body, so if pContext->window() == nullptr the code would've crashed before it got to the check. Its also difficult to model the error case: afaik if an error occurs in a class constructor, the proper C++ solution is to throw. That ensures the object which is now in an unknown state (since the ctor didn't complete) can not be used. Since we refuse to use exceptions, the only other alternative would be to introduce a validity state into the object (eg if Texture couldn't get properly constructed set m_pTexture to nullptr and require the caller to verify that it isn't invalid).

EDIT: I also just noticed that rendergraph::Context::window returns a gsl::not_null anyways, so we could just remove the check, right?

Comment on lines +13 to +16
const UniformSet& uniformSet,
const AttributeSet& attributeSet) {
(void)uniformSet;
(void)attributeSet;
Copy link
Member

Choose a reason for hiding this comment

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

NIT:

Suggested change
const UniformSet& uniformSet,
const AttributeSet& attributeSet) {
(void)uniformSet;
(void)attributeSet;
const UniformSet&,
const AttributeSet&) {

case DrawingMode::TriangleStrip:
return GL_TRIANGLE_STRIP;
default:
throw std::runtime_error("not implemented");
Copy link
Member

Choose a reason for hiding this comment

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

I think the consensus was that using exceptions currently will result in brittle code (because you never know what throws and when) so the consensus was to avoid exceptions currently and instead use std::expected. In this case a dummy value or a std::optional could work.

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

Successfully merging this pull request may close these issues.

4 participants