I am developing a 3D engine that is designed to support the implementation of any given graphics API. I would like your feedback on how I'm planning to manage the shader files:
I thought about creating a struct that contains 3 string variables, the directory and the file name (both vertex and fragment), something like this:
class ShaderFile : public SerializableAsset
{
std::string nameID; //Identifier
std::string directory;
std::string vertexName;
std::string fragmentName;
};
The user would be able to set these variables in the editor. Then, my engine would load the shader files in like:
void createShader(RenderAPI api)
{
ShaderFile shaderFile //Get it from some place
std::string vertexPath = shaderFile.directory + shader.vertexName + api.name + api.extension;
std::string fragmentPath = shaderFile.directory + shader.fragmentName + api.name + api.extension;
//Create shader...
}
Which would create something like: Project/Assets/Shaders/standardVulkan.spv.
Am I thinking in the right direction or is this a completely idiotic approach? Any feedback
It's an interesting idea and we've actually done exactly this, but we discovered some things along the way that are not easy to deal with:
If you take a deeper look at Shader API's, although they are close to offering the same capabilities on paper, they often do not support features in the same way and have to be managed differently. By extension, so do the shaders. The driver implementation is key here, and sometimes differs considerably when it comes to managing internal state (synchronization and buffer handling).
Flexibility
You'll find that OpenGL is flexible in the way it handles attributes and uniforms, where DirectX is more focussed on minimizing uploads to the hardware by binding them in blocks according to your renderpass configurations, usually on a per-object/per-frame/per-pass basis etc.. While you can also do this by creating tiny blocks, this obviously would give different performance.
Obviously, there are multiple ways to do binds, or even buffer objects, or shaders, even in a single API. Also, getting shader variable names and bind points is not that flexible to query in DirectX and some of the parameters needs to be set from code. In Vulkan, binding shader attributes and uniforms is even more generalized: you can completely configure the bind points as you wish.
Versioning
Another topic is everything that has to do with GLSL/HLSL shading versioning: You may need to write different shaders for different hardware capabilities that support lower shader models. If you're going to write unique shaders and are not going for the uber-shader approach (and also to a large extend IF you use this approach) this can get complicated if it ties too tightly into your design, and given the number of permutations might be unrealistic.
Extensions
OpenGL and Vulkan extensions can be 'queried' from within the shader, while other API's such as DirectX require setting this from the code side. Still, within the same compute_capability, you can have extensions that only work on NVidia, or are ARB approved but not CORE, etc. This is really quite messy and in most cases application specific.
Deprecation
Considerable parts of API's are getting deprecated all the time. This can be problematic if your engine expects those features to remain in place, especially if you like to deal with multiple API's that support that feature.
Compilation & Caching
Most API's by now support some form of offline compilation that can be loaded later. Compilation takes a considerable amount of time so caching that makes sense. Since the hardware shader code is compiled uniquely for the hardware that you have, you need to do this exercise for each platform that the code should run on, either the first time the app needs the shader, or in some other clever way in your production pipeline. Your filename would in that case be replaced by a hash so that the shader can be retrieved from the cache. But this means the cache needs a timestamp so it can detect new versions of the source shader, because if the shader source should change, then the cache entry needs to be rebuilt. etc.. :)
Long story short
If you aim for maximum flexibility in whatever API, you'd end up adding a useless layer in your engine that in the best case simply duplicates the underlying calls. If you aim for a generalized API, you'll quickly get trapped in the version story that is totally not synchronized between the different API's in terms of extensions, deprecation and driver implementation support.
Related
The mesa drivers, as part of their compilation process, reduce the size of the glsl shader files.
Some libraries, like this one, use this fact to create shader minification libraries. All minification libraries I have found are abandonware, so, unless mesa has functionality to get the intermediary glsl files directly, I may have to edit the actual code the way those libraries did it.
I was wondering if there is an executable within the mesa code base that can be used to do the stripping without having to edit the code.
I tried reading the official mesa documentation, but I didn't anything that suggests either way:
https://www.mesa3d.org/opengles.html
"Minification" is something different to optimization. Typically the term is used to describe a process that takes a source file in text form and removes all superfluous whitespace and replaces all identifiers with shorter ones.
Strictly speaking the whole idea of minification is a folly, since it has zero impact on performance; neither lexing the code, nor the compilation result are affected by it. The whole minification doofus started in web development to reduce webpage resource size; totally worthless, because you'll get far better performance if you just compress the text with gzip or similar. Heck after zipping the original and the minified versions' sized will probably within a few bytes of each other.
If you're really concerned about the size of your shader as a resource, just compress it (but mind the overhead of the decompression code). EDIT: However if your target is WebGL, then make use of HTTP transport gzip compression. The relevant browsers do support it all, and most HTTP servers can be configured to transparently deliver a supplementary .gz suffixed file (or do the compression and cache it on the fly).
For optimization, you should look to the other offerings of Khronos. Specifically the GLSL to SPIR-V compiler glslc, the SPIR-V optimizer spirv-opt, and the SPIR-V decompiler spirv-cross. You can chain those up to create optimized, "reduced" GLSL.
glslc --target-env=opengl -fshader-stage=... -o ${FILE}.spv ${FILE}
spirv-opt -Os -o ${FILE}.spv.opt ${FILE}.spv
spirv-cross --output ${FILE}.opt ${FILE}.spv.opt
Since the SPIR-V tools are part of the official Vulkan SDK, and SPIR-V is also a valid shader format to be loaded directly by OpenGL-4.6 (instead of just GLSL source), you can sleep well on the fact, that these tools are well maintained and will also be so in the future.
Are there any libraries for compression of structured messages? (like protobufs)
I am looking for something better than just passing a serialized stream through GZip. For example, if my message stores a triangle mesh, the coordinates of adjacent vertices will be highly correlated, so a smart compressor could store deltas instead of the raw coordinates, which would require less bits to encode.
Whereas a general compressor, that doesn't know anything about the stream structure, would be looking for repeating byte sequences, which in data like that, there won't be many.
Ideally, this should work completely automatically after being provided with a schema, but I wouldn't mind adding annotations to my schema, if it came to that.
The main problem here is that most of the time writing some schema will have a similar effort to programming a preprocessor for the data yourself. E.g. for your triangle mesh example, reordering the data or doing a delta on coordinates can be implemented very easy and will support any subsequent compressor very well.
A compressor going in that direction is ZPAQ. It can use config files tailored to specific data (the sample configuration site includes EXE, JPG, BMP configs as well as a specialized one to compress a file containing the mathematical constant pi). The downside is that the script language used here (ZPAQL) is quite complicated to use and you've got to know much of the ZPAQ internals.
Older versions of WinRAR used a virtual machine named RarVM (though deprecated now) that allowed for assembler-like code for custom data transformations, there's an open source project named rarvmtools on GitHub with some related tools.
For protobuf compression, there's a Google project called riegeli that might be able to further compress them.
I'm developing my own game engine with ECS framework.
This is the ECS framework I use:
Entity: Just an ID which connects its components.
Component: A struct which stores pure data, no methods at all (So I can write .xsd to describe components and auto-generate C++ struct code).
System: Handle game logic.
EventDispacher: Dispatch events to subscribers (Systems)
but I'm confused about how should systems update the members of components and inform other systems?
For example, I have a TransformComponent like this:
struct TransformComponent
{
Vec3 m_Position;
Float m_fScale;
Quaternion m_Quaternion;
};
Obviously, if any member of the TransformComponent of a renderable entity is changed, the RenderSystem should also update the shader uniform "worldMatrix" before render the next frame.
So if I do "comp->m_Position = ..." in a system, how should the RenderSystem "notice" the change of TransformComponent? I have come up with 3 solutions:
Send an UpdateEvent after updating the members, and handle the event in related System. This is ugly because once a system modify the component data, it must send an event like this:
{
...;
TransformComponent* comp = componentManager.GetComponent<TransformComponent>(entityId);
comp->m_Position = ...;
comp->m_Quaternion = ...;
eventDispatcher.Send<TransformUpdateEvent>(...);
...;
}
Make members private, and for each component class, write a relevant system with set/get methods (wrapping event sending in set methods). This will bring a lot of cumbersome codes.
Do not change anything, but add "Movable" component. RenderSystem will iteratively do update for renderable entities with "Movable" component in Update() method. This may not solve other similar problems, and I'm not sure about the performance.
I can't come up with an elegant way to solve this problem. Should I change my design?
I think that in this case, the simplest method will be the best: you could just keep a pointer to a Transform component in components that read/write it.
I don't think that using events (or some other indirection, like observers) solves any real problem in here.
Transform component is very simple - it's not something that will be changed during development. Abstracting access to it will actually make code more complex and harder to maintain.
Transform is a component that will be frequently changed for many objects, maybe even most of your objects will update it each frame. Sending events each time there's a change has a cost - probably much higher than simply copying matrix/vector/quaternion from one location to another.
I think that using events, or some other abstraction, won't solve other problems, like multiple components updating the same Transform component, or components using outdated transform data.
Typically, renderers just copy all matrices of rendered objects each frame. There's no point in caching them in a rendering system.
Components like Transform are used often. Making them overly complex might be a problem in many different parts of an engine, while using the simplest solution, a pointer, will give you greater freedom.
BTW, there's also very simple way to make sure that RenderComponent will read transform after it has been updated (e. g. by PhysicsComponent) - you can split work into two steps:
Update() in which systems may modify components, and
PostUpdate() in which systems can only read data from components
For example PhysicsSystem::Update() might copy transform data to respective TransformComponent components, and then RenderSystem::PostUpdate() could just read from TransformComponent, without the risk of using outdated data.
I think there are many things to consider here. I will go in parts, discussing first your soutions.
About your solution 1. Consider, that you could do the same with a boolean, or assigning an empty component acting as tag. Many times, using events in ECS is over complicating your system architecture. At least, I tend to avoid it, specially in smaller projects. Remember that a component acting as a tag, could be thought as basically, an event.
Your solution 2, follows up what we discussed in 1. But it reveals a problem about this general approach. If you are updating your TransformComponent in several Systems, you can't know if the TransformComponent has really changed until the last System has updated it, because one System could have moved it one direction, and another one could have moved it back, letting it as at the beginning of your tick. You could solve this by updating your TransformComponent just once, in one single System...
Which looks like your solution 3. But maybe the other way around. You could be updating the MovableComponent in several Systems, and later on in your ECS pipeline, have a single System, reading your MovableComponent and writting into your TransformComponent. In this case, is important that there is only one System allowed to write on the TransformComponents. At that time, having a boolean indicating if it has been moved or not, would do the job perfectly.
Until here, we have traded performance (because we avoid some processing on the RenderSystem when TransformComponent has not changed) for memory (because we are duplicating the content of TransformComponent in some way.
Another way to do the same, without having to add events, booleans, or components, is doing everything in the RenderSystem. Basically, in each RenderComponent, you could keep a copy (or a hash) of your TransformComponent from the last time you have updated it, and then compare it. If it's not the same, render it, and update your copy.
// In your RenderSystem...
if (renderComponent.lastTransformUpdate == transformComponent) {
continue;
}
renderComponent.lastTransformUpdate = transformComponent;
render(renderComponent);
This last one, would be my preferred solution. But it also depends on the characteristics of your system and your concerns. As always, don't try to optmize for performance blindly. First measure, and then compare.
If you wanted to:
model an object in a 3D editor, e.g. Blender, Maya, etc
export the model into a data/file format
import the model into an project using OpenGL and C/C++
Then:
What file format would you recommend exporting to, i.e. in terms of simplicity, portability, and compatibility (i.e. common/popular)?
What graphics libraries would you recommend using to import the model into your OpenGL C/C++ project (i.e. preferably open source)?
Additionally, are there data/file formats that also capture animation, i.e. an "animated model" format, such that the animation could be modeled in the 3D editor and somehow invoked inside the code (e.g. accessibility to frames in the animation sequence or some other paradigm for saving/loading details related to changes over time)?
Generally speaking, I'm seeking simplicity as the priority, i.e. help to get started with combining my backgrounds in both art and computer science. I am a computer science major at UMass while at the same time, sort of a "pseudo" double-major in art by taking electives in graphic design at my university as well as classes at the Art Institute of Boston during summer/winter sessions So, in other words, I am not a complete newbie, but at the same time I don't really want options that are so overloaded with crazy advanced configurations making it too difficult to get started with a basic demonstration project; i.e. as a first step to understanding how to bridge the gap between these two worlds, e.g. creating a program featuring a 3D character that the user can interact with.
COLLADA (I'm saying it with a "ah" at the end), and Assimp (ple as that).
And so, why COLLADA? Simple:
COLLADA is an open standard made by Khronos (Sony specifically). The beauty of an open standard format is that it's, well, a standard! You can be assured that any output of a standard-conformant product would also read correctly by another standard-conformant product. Sad to say though, some 3d modelling products aren't that particular in their measures for standards conformance for COLLADA. But still be rest assured: Blender, Maya, 3ds Max and all the other big names in 3d modelling has good support for the format.
COLLADA uses XML. This makes it much more simpler for you if your planning to create your own reader or writer.
ADDITIONAL: COLLADA is, I think, the only format that is not tied to a specific company. This is a very good thing for us, you know.
ADDITIONAL 2: It is known that COLLADA is slow to be parsed. That's true. But think of it: all other non-binary formats (like fbx) have also the same issues. For your needs, COLLADA should suffice.
ADDITIONAL 3: COLLADA supports animations!
For the importer library, I highly recommend Assimp. Why?
Assimp has support for any popular format you can imagine. It has a unified interface for all formats so that switching to another format is less of a pain.
Assimp is extensible. You can thus import your proprietary format and still not modify your code.
ADDITIONAL 4: Assimp is open source! Let's support open source software!
First , here you can read about suggested model loading lbs.Lib Assimp is really good and supports many formats.For the preferred formats.Collada-I wouldn't recommend because that is XML (text) based formats which is slow to parse. Obj format is also widespread but suffers from the same problems as Collada.It is still good if you want to write your own parser as its structure is very simple.But Instead I would suggest 3Ds which is binary.It doesn't support animations though.The most popular format today which supports both static mesh and animation is FBX.You can download for free FBX SDK from Autodesk and connect it to your engine.The reason I would choose FBX is because both SDK and the format are really robust.For example ,in FBX you can embed not just geometry and animation but also scene objects as lights ,cameras etc.Autodesk docs are very nice too.
Hope it helps.
I would reccomend using your own custom format that basically just a binary dump of the vertex buffer and index buffers used in your program. (Using d3d terms there, I know opengl has the same concepts but can't remember if they have different names).
I would then write a separate program using assimp that takes pretty much any format and writes out the file in your custom format. Then you can use collada or whatver to store your actual models in, but not have the complixity and slowness of loading that format at run time.
So there has been a lot of times where I needed to know what the enums returned by certain opengl operations are, to print them on the terminal to see what's going on.
It doesn't seem there's any kind of function available for stringifying the enums at the moment, so I'm thinking of going straight to gl.h (actually I'm gonna use libglew's header for now), grabbing the #defines and creating a huge switch table for convenience purposes.
Is there any better way, and how would you deal with having to port things to OpenGL ES?
gluErrorString is the function you're looking for in OpenGL, as GLU library is normally always available alongside with GL.
I don't have experience in OpenGL ES, but Google turned up GLUes that may help you.
OpenGL has some official .spec files that define the API, there is one enum.spec that lists all the enum names and their values. You just need to write a simple program to parse the file and produce a lookup mapping.
The file can be found at http://www.opengl.org/registry/ (specifically http://www.opengl.org/registry/api/enum.spec)
Manually processing gl.h would work but the spec files are updated for new versions and if you have a program to generate the code for you then you don't have to do anything to get the new enums in. Also gl.h file is implementation specific. So it might change between nVidia, ATI, and on different platforms.
The OpenGL Registry contains comprehensive information on the OpenGL API. Notably the gl.xml file in the XML API registry lists all enums, including their names and values. Creating a lookup table from that data should be simple enough.