-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Conversation
There was a problem hiding this 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// 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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
../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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will do
// 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// 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.
#pragma once | ||
|
||
#include "backend/baseopenglnode.h" | ||
|
||
namespace rendergraph { | ||
using OpenGLNode = BaseOpenGLNode; | ||
} // namespace rendergraph |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
src/rendergraph/CMakeLists.txt
Outdated
|
||
add_subdirectory(opengl) | ||
add_subdirectory(scenegraph) | ||
add_subdirectory(../../res/shaders/rendergraph shaders) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
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! |
There was a problem hiding this 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.
src/rendergraph/README
Outdated
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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
?
/* 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(); | ||
*/ |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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++; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto nullptr exception
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) |
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. |
There was a problem hiding this 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 🚄
Thanks! I have created the next PR #14021 |
There was a problem hiding this 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.
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()); |
There was a problem hiding this comment.
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?
const UniformSet& uniformSet, | ||
const AttributeSet& attributeSet) { | ||
(void)uniformSet; | ||
(void)attributeSet; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NIT:
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"); |
There was a problem hiding this comment.
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.
Part of the rendergraph PR split into multiple PRs.
See https://mixxx.zulipchat.com/#narrow/channel/109171-development/topic/rendergraph.20PR.20split/near/487124048